Registro público
Informe de salud del softwareesquema 0.27.0 · métricas 1.13.0 · 2026-07-27 16:03 UTC

MHilhorst / ainfra

GoMIT★ 0 estrellas⑂ 0 forksdesde may 2026Ver en GitHub ↗

MHilhorst/ainfra tiene un índice de salud de 49 sobre 100, lo que lo sitúa en la banda En riesgo. Su puntuación más alta es Vitality (72/100) y la más baja, Community & Adoption (24/100). Se actualizó por última vez hace 5 días. Una sola persona concentra la mayor parte del trabajo reciente.

49
global / 100
En riesgo

Índice de salud del software

Las métricas se agrupan en categorías ponderadas sobre una escala de 1 a 100. El resultado global parte de su media; cuando la evidencia pública activa la Política de Jurisdicciones de Alto Riesgo, la calificación se ajusta y recibe el límite 49 (En riesgo). Preparación para IA queda fuera.

49
Excelente85-100Ejemplar; cumple prácticamente todos los criterios evaluados
Bueno70-84Saludable; carencias menores
Moderado50-69Aceptable con carencias notables; se recomienda revisión
En riesgo30-49Debilidades significativas; su adopción exige cautela
Crítico1-29Problemas graves (proyecto abandonado, un solo mantenedor, sin higiene)
VitalidadComunidad yAdopciónSostenibilidady GobernanzaCalidad deIngenieríaSeguridadPreparaciónpara IA

Perfil de puntuación

Cada eje es una categoría. La forma importa más que la media: un proyecto sano llena toda la figura, mientras que un perfil de picos y cráteres indica que la fortaleza en una dimensión enmascara el riesgo en otra.

Titularidad

Michael HilhorstCuenta personal
2 seguidores23 repositorios públicosdesde feb 2019

Este repositorio pertenece a una cuenta personal. Un proyecto con un único propietario conlleva más riesgo de continuidad que uno respaldado por una organización.

Ecosistemas de paquetes

RegistroPaqueteVersiónDescargas / mesVersionesÚltima publicación
Gogithub.com/MHilhorst/ainfrav0.2.27-29hace 5 días

Métricas por categoría

Vitalidad

¿Está vivo el proyecto: se escribe código y se publican versiones?

72Bueno · 22% del índice global
Cómo se puntúa
36/36Recencia de push — último push hace 5 días
5.5/36Cadencia de commits — 8/52 semanas con commits
18/18Volumen de commits — 308 commits en el último año
0/10OpenSSF Scorecard: Maintained — project was created within the last 90 days. Please review its contents carefully
Datos de entrada utilizados
commits_last_year308
human_commit_share1
days_since_last_push5
active_weeks_last_year8
Cómo se puntúa
27/27Publica versiones — 29 versiones publicadas
36/36Recencia de las versiones — última versión hace 5 días
27/27Cadencia de publicación — una versión cada ~0,6 días
0/10OpenSSF Scorecard: Signed-Releases — Project has not signed or included provenance with any releases.
Datos de entrada utilizados
releases_count29
latest_release_tagv0.2.27
releases_from_tagsno
days_since_latest_release5
mean_days_between_releases0,6

Comunidad y Adopción

¿Tiene el proyecto usuarios, descargas, atención y unas condiciones acogedoras para quienes contribuyen?

24Crítico · 18% del índice global
Cómo se puntúa
0/60Estrellas — 0 estrellas
0/25Forks — 0 forks
0/15Observadores — 0 observadores
Datos de entrada utilizados
forks0
stars0
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Cómo se puntúa
22.5/22.5README
22.5/22.5Licencia — licencia reconocida (MIT)
0/18Guía CONTRIBUTING
0/13.5Código de conducta
0/7.2Plantilla de issues
0/6.3Plantilla de PR
Datos de entrada utilizados
has_readme
has_license
has_contributingno
has_issue_templateno
has_code_of_conductno
has_pull_request_templateno

Sostenibilidad y Gobernanza

¿Sobrevivirá el proyecto a sus personas: factor bus, capacidad de respuesta, quién lo respalda y mantenimiento del paquete?

44En riesgo · 24% del índice global
Cómo se puntúa
9/54Factor bus — la mitad de los commits recae en 1 contribuyente(s)
0/22.5Distribución de commits — el principal contribuyente firma el 100% de los commits
1.4/13.5Amplitud de contribuyentes — 1 contribuyentes
0/10OpenSSF Scorecard: Contributors — project has 0 contributing companies or organizations -- score normalized to 0
Datos de entrada utilizados
bus_factor1
contributors_sampled1
top_contributor_share1
Cómo se puntúa
0/46.8Resolución de issues — 0% de issues cerradas
37.8/38.3Aceptación de PR — 82/83 PR decididos fusionados
0/15OpenSSF Scorecard: Code-Review — Found 0/30 approved changesets -- score normalized to 0
Datos de entrada utilizados
merged_prs82
open_issues3
closed_issues0
issue_closed_ratio0
closed_unmerged_prs1
Cómo se puntúa
10/30Respaldo de la propiedad — cuenta personal (usuario)
0/20Dominio verificado — no aplicable a cuentas de usuario
3.4/25Alcance del propietario — 2 seguidores de MHilhorst
22.1/25Trayectoria — 23 repos públicos, cuenta de ~7 años
Datos de entrada utilizados
followers2
owner_typeUser
is_verified
owner_loginMHilhorst
public_repos23
account_age_days2728
Excluidos de la puntuación (sin datos o no aplicable): Dominio verificado. Los pesos restantes se han renormalizado.
Cómo se puntúa
25/25Publicado y resoluble — 1 paquete(s) en go
35/35Recencia de publicación — última publicación hace 5 días
20/20Historial de versiones — 29 versiones en el registro
20/20No obsoleto — activo, ni obsoleto ni retirado
Datos de entrada utilizados
packagesgithub.com/MHilhorst/ainfra
ecosystemsgo
any_deprecatedno
min_days_since_publish5

Calidad de Ingeniería

¿Existen unas prácticas mínimas de ingeniería y documentación?

63Moderado · 20% del índice global
Cómo se puntúa
24/24Flujos de trabajo de CI — 2 flujo(s) de trabajo
24/24Pruebas presentes
0/16Configuración de linter
0/9.6Hooks de pre-commit
0/6.4.editorconfig
14/20OpenSSF Scorecard: CI-Tests — 22 out of 28 merged PRs checked by a CI test -- score normalized to 7
Datos de entrada utilizados
has_ci
has_tests
has_editorconfigno
has_linter_configno
has_precommit_configno

Documentación

65Moderado
Cómo se puntúa
30/30README
25/25Directorio de documentación
0/15Sitio de documentación / página del proyecto
0/10Descripción del repositorio
0/10Topics
10/10Wiki
Datos de entrada utilizados
topics
has_wiki
homepage
has_readme
has_docs_dir
has_descriptionno

Seguridad

¿Son sólidas las prácticas visibles de seguridad y de cadena de suministro, sin exposición jurisdiccional de alto riesgo sin resolver?

33En riesgo · 16% del índice global
Cómo se puntúa
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
1.8/2.5CI-Tests — 22 out of 28 merged PRs checked by a CI test -- score normalized to 7
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/30 approved changesets -- score normalized to 0
0/2.5Contributors — project has 0 contributing companies or organizations -- score normalized to 0
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5Licencia — license file detected
0/7.5Maintained — project was created within the last 90 days. Please review its contents carefully
5/5Packaging — packaging workflow detected
0/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — Project has not signed or included provenance with any releases.
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
7.5/7.5Vulnerabilities — 0 existing vulnerabilities detected
Datos de entrada utilizados
sourceopenssf_scorecard
checks_evaluated18
scorecard_versionv5.5.0
checks_inconclusive0
scorecard_aggregate3,3

Preparación para IA

¿Hasta qué punto está el repositorio preparado para desarrollarse y mantenerse con agentes de codificación de IA? Es una insignia independiente y experimental — peso 0,0, de modo que se presenta por separado y no afecta a la puntuación de salud global.

64Moderado · 0% del índice global
Cómo se puntúa
18/45Instrucciones para agentes — internal/adopt/testdata/simple/CLAUDE.md (esbozo)
0/15Documentación legible por máquinas (llms.txt)
40/40Historial de commits legible — 86 de 100 commits humanos declaran su intención (asunto estructurado o cuerpo explicativo)
Datos de entrada utilizados
has_llms_txtno
legible_history_share0,86
agent_instruction_filesinternal/adopt/testdata/simple/CLAUDE.md
agent_instruction_max_bytes26
Cómo se puntúa
12.6/18Arranque con un solo comando — go.mod (convención del toolchain, sin ejecutor de tareas)
22/22Pruebas automatizadas
0/11Configuración de lint / formato
11/11Verificación estática de tipos — Go (tipado estático)
10/10Entorno reproducible — lockfile
0/10Práctica demostrada con agentes — ningún commit con autoría de agente entre los últimos 100
0/8Mantenimiento automatizado — no se observan actualizaciones automáticas de dependencias
0/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
Datos de entrada utilizados
has_nixno
has_tests
lockfilesgo.sum
has_dockerfileno
typed_language
bootstrap_files
has_devcontainerno
has_linter_configno
typecheck_configs
agent_commit_share0
toolchain_manifestsgo.mod
dependency_bot_commit_share0
Cómo se puntúa
45/45Código verificable por tipos — Go (tipado estático)
55/55Tamaños de archivo manejables — 0/255 archivos fuente de más de 60 KB
Datos de entrada utilizados
primary_languageGo
largest_source_bytes33.958
source_files_sampled255
oversized_source_files0
Cómo se puntúa
0/40Esquema de API (OpenAPI/GraphQL/proto)
20/20Servidor MCP
40/40Ejemplos ejecutables — examples
Datos de entrada utilizados
example_dirsexamples
has_mcp_signal
api_schema_files

Datos clave

0estrellas de GitHub
1contribuidores
308commits en los últimos 12 meses
5días desde el último push
29versiones publicadas
1factor bus
3issues abiertas
Go, npmecosistemas de paquetes

Advertencias de recopilación de datos

  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

Más detalle

OpenSSF Scorecard 3.3 / 10
3.3agregado

Evaluación de seguridad independiente y agnóstica en cuanto a herramientas, procedente del proyecto de código abierto OpenSSF Scorecard. Cada comprobación premia una práctica de seguridad, no la herramienta de un proveedor concreto. Las comprobaciones que Scorecard no pudo determinar se marcan como n/d y se excluyen de la puntuación de seguridad (nunca se cuentan como cero).Scorecard v5.5.0 · 2026-07-27 16:03 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
7CI-Tests22 out of 28 merged PRs checked by a CI test -- score normalized to 7
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/30 approved changesets -- score normalized to 0
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
0Maintainedproject was created within the last 90 days. Please review its contents carefully
10Packagingpackaging workflow detected
0Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 0
0SASTSAST tool is not run on all commits -- score normalized to 0
0Security-Policysecurity policy file not detected
0Signed-ReleasesProject has not signed or included provenance with any releases.
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
10Vulnerabilities0 existing vulnerabilities detected
Dependencias directas 2
RegistroPaqueteRestricción de versiónManifiesto
Gogopkg.in/yaml.v3v3.0.1go.mod
Gogithub.com/BurntSushi/tomlv1.6.0go.mod
Todas las dependencias no recopilado

No fue posible recopilar el conjunto de dependencias resuelto para este informe: GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

Informe JSON sin procesar legible por máquina
{
  "data": {
    "repo": {
      "topics": [],
      "is_fork": false,
      "size_kb": 1668,
      "has_wiki": true,
      "homepage": null,
      "languages": {
        "Go": 1291914,
        "Shell": 1846
      },
      "pushed_at": "2026-07-22T11:17:09Z",
      "created_at": "2026-05-21T06:18:36Z",
      "owner_type": "User",
      "updated_at": "2026-07-22T11:17:09Z",
      "description": null,
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "MIT",
      "default_branch": "main",
      "license_spdx_raw": "MIT",
      "primary_language": "Go",
      "significant_languages": [
        "Go"
      ]
    },
    "owner": {
      "blog": null,
      "name": "Michael Hilhorst",
      "type": "User",
      "login": "MHilhorst",
      "company": null,
      "location": "Utrecht",
      "followers": 2,
      "avatar_url": "https://avatars.githubusercontent.com/u/47367316?v=4",
      "created_at": "2019-02-05T19:52:31Z",
      "is_verified": null,
      "public_repos": 23,
      "account_age_days": 2728
    },
    "license": {
      "state": "standard",
      "spdx_id": "MIT",
      "raw_spdx": "MIT",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v0.2.27",
          "kind": "patch",
          "published_at": "2026-07-22T11:17:53Z"
        },
        {
          "tag": "v0.2.26",
          "kind": "patch",
          "published_at": "2026-07-22T10:26:02Z"
        },
        {
          "tag": "v0.2.25",
          "kind": "patch",
          "published_at": "2026-07-20T17:50:25Z"
        },
        {
          "tag": "v0.2.24",
          "kind": "patch",
          "published_at": "2026-07-20T17:34:10Z"
        },
        {
          "tag": "v0.2.23",
          "kind": "patch",
          "published_at": "2026-07-20T17:25:59Z"
        },
        {
          "tag": "v0.2.22",
          "kind": "patch",
          "published_at": "2026-07-20T17:18:30Z"
        },
        {
          "tag": "v0.2.21",
          "kind": "patch",
          "published_at": "2026-07-20T17:13:25Z"
        },
        {
          "tag": "v0.2.20",
          "kind": "patch",
          "published_at": "2026-07-17T11:04:40Z"
        },
        {
          "tag": "v0.2.19",
          "kind": "patch",
          "published_at": "2026-07-17T10:03:54Z"
        },
        {
          "tag": "v0.2.18",
          "kind": "patch",
          "published_at": "2026-07-17T07:50:56Z"
        },
        {
          "tag": "v0.2.17",
          "kind": "patch",
          "published_at": "2026-07-17T06:33:55Z"
        },
        {
          "tag": "v0.2.16",
          "kind": "patch",
          "published_at": "2026-07-17T06:31:59Z"
        },
        {
          "tag": "v0.2.15",
          "kind": "patch",
          "published_at": "2026-07-17T06:27:03Z"
        },
        {
          "tag": "v0.2.14",
          "kind": "patch",
          "published_at": "2026-07-17T06:22:27Z"
        },
        {
          "tag": "v0.2.13",
          "kind": "patch",
          "published_at": "2026-07-17T06:21:29Z"
        },
        {
          "tag": "v0.2.12",
          "kind": "patch",
          "published_at": "2026-07-17T06:08:50Z"
        },
        {
          "tag": "v0.2.11",
          "kind": "patch",
          "published_at": "2026-07-16T19:06:46Z"
        },
        {
          "tag": "v0.2.10",
          "kind": "patch",
          "published_at": "2026-07-16T18:40:12Z"
        },
        {
          "tag": "v0.2.9",
          "kind": "patch",
          "published_at": "2026-07-16T17:11:47Z"
        },
        {
          "tag": "v0.2.8",
          "kind": "patch",
          "published_at": "2026-07-16T13:27:34Z"
        },
        {
          "tag": "v0.2.7",
          "kind": "patch",
          "published_at": "2026-07-16T12:31:47Z"
        },
        {
          "tag": "v0.2.6",
          "kind": "patch",
          "published_at": "2026-07-16T08:18:16Z"
        },
        {
          "tag": "v0.2.5",
          "kind": "patch",
          "published_at": "2026-06-08T08:18:02Z"
        },
        {
          "tag": "v0.2.4",
          "kind": "patch",
          "published_at": "2026-06-08T08:01:12Z"
        },
        {
          "tag": "v0.2.3",
          "kind": "patch",
          "published_at": "2026-06-08T07:56:52Z"
        },
        {
          "tag": "v0.2.2",
          "kind": "patch",
          "published_at": "2026-06-05T11:24:16Z"
        },
        {
          "tag": "v0.2.1",
          "kind": "patch",
          "published_at": "2026-06-05T10:41:37Z"
        },
        {
          "tag": "v0.2.0",
          "kind": "minor",
          "published_at": "2026-06-02T08:58:53Z"
        },
        {
          "tag": "v0.1.0",
          "kind": "minor",
          "published_at": "2026-05-27T05:29:09Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "c38a2eeb5f26ec82759452e8e168783e27e2b651",
          "body": "* Do not call a gitignored file's absence a difference\n\nThe redirect shipped in 0.2.26 never fired. It demanded every manifest input\nbe byte-identical, but ainfra.personal.yaml is gitignored, so a linked\nworktree never receives the copy sitting in the main checkout — 0 of 68\nworktrees had one on the\n[…]\nuses the redirect.\n\nA successful redirect said nothing, which removed the only signal that the\nshim now resolves from a different directory than the install just did. It\nreports where the shim points.",
          "is_bot": false,
          "headline": "Do not call a gitignored file's absence a difference (#86)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-22T11:17:00Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5564b8d003fc73aaf79d45178598bee6d85de3ef",
          "body": "… (#85)\n\n* Stop a throwaway worktree from pinning the machine-wide launcher shim\n\nThere is one claude shim per machine and it bakes in the directory install\nran from, so installing from a per-task git worktree pinned every future\nlaunch to a path that vanished with that worktree. From then on claude\n[…]\no\ncheckouts could differ in exactly those and still be treated as identical.\nThe set is now pinned by a test that names each file, since a test iterating\nthe list cannot notice an entry going missing.",
          "is_bot": false,
          "headline": "Stop a throwaway worktree from pinning the machine-wide launcher shim…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-22T10:25:16Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d1821670534fd301c8c5dcc9589af7cd801ff9f1",
          "body": "marketplaces.go had the same dead-code guard #83 fixed in plugins.go:\nisAlreadyRegisteredError matched err.Error(), which under ExecRunner's\nCombinedOutput is only \"exit status 1\", so it never fired. Its test hid it\nthe same way, by scripting the CLI's message into the error.\n\nThe delete path had no\n[…]\noving a marketplace\nthat was already gone failed the whole channel -- the same fan-out that\nmade one stale plugin report six failures.\n\nBoth error texts captured from the real CLI rather than guessed.",
          "is_bot": false,
          "headline": "Apply the output-matching fix to marketplaces too (#84)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-20T17:49:32Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "77a514c2175f356ef0ae44d7d7977208f3a76aef",
          "body": "ExecRunner is exec.Command(...).CombinedOutput(): the error is an\n*exec.ExitError reading \"exit status 1\", and everything the CLI printed is\nin the returned bytes, which both call sites discarded with `_`.\n\nSo isAlreadyInstalledError never fired in production -- it has been dead\ncode since it was wr\n[…]\nentry.\n\nFakeRunner hid this: the tests scripted the CLI's message into the error,\na shape the real runner never produces. Both tests now use the production\nshape and fail against the previous matcher.",
          "is_bot": false,
          "headline": "Match plugin CLI messages against output, not the error string (#83)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-20T17:33:07Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "892dfb4b226cefd343df46d9e58ab65a4cf30aaf",
          "body": "…he human's (#82)\n\nTwo defects in the gate that shipped in #80, both found by a second review pass.\nBoth are the failure this change exists to prevent: a credential silently\nwithheld from a caller that legitimately needs it.\n\nThe cause of both is that secretAppliesTo asked two different questions an\n[…]\ntityForSecrets: it\ndiscarded `resolved` and asserted only on `failures`, and a silently-skipped\nsecret produces no failures -- so it passed while the bug it was written to catch\nshipped underneath it.",
          "is_bot": false,
          "headline": "Make --agent installs reach their own secrets, and stop them losing t…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-20T17:25:03Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "55d8385d0d0533777e9c349587c8b7f95844d105",
          "body": "…es (#81)\n\n* Keep lockfile fields when an MCP server cannot be probed\n\nCommand, Args and Env are declarative -- they come from the manifest, not\nfrom the probe -- but they were written inside the introspection success\nbranch. Re-locking while a server was unreachable (SSH tunnel down, VPN\noff) silen\n[…]\noving one stale entry reported all six plugins as failed\nwhile the healthy ones never ran.\n\nMirror the existing already-installed handling on the install path. A real\nuninstall failure still surfaces.",
          "is_bot": false,
          "headline": "Stop silently dropping lockfile fields and misreporting plugin failur…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-20T17:17:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "89a708618856429aacbe180d6368f862efe5821a",
          "body": "* Stop non-human identities attempting per-human vaults\n\nA headless agent box resolves secrets through a 1Password service account that\ncan read the shared vault and, by construction, no human's Private vault. Every\n`ainfra exec` there attempted the personal blob anyway, failed, and warned. On a\nbox\n[…]\netRef already carries scope, so the same implicit rule applies.\n\n4 regression tests, one per finding plus identity precedence. Suite and vet clean;\nre-verified against the real claude-config manifest.",
          "is_bot": false,
          "headline": "Stop non-human identities attempting per-human vaults (#80)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-20T17:12:24Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e9be78b8253fcf1cfdf842fc16b947547d9e9b77",
          "body": "* Reject flags placed after positional arguments\n\nCodex review of the --prune release found that `ainfra add`'s own usage line\ndocumented `--global` after the positionals -- the exact trap the prune work\nhad just papered over in its docs. Go's flag package stops parsing at the\nfirst positional, so t\n[…]\ns a flag. It still catches every real case: the trap that started\nthis (`add command ship <src> --global`), `init junk --force`, and a trailing\n--no-install. terminatorConsumed/takesValue are deleted.",
          "is_bot": false,
          "headline": "Reject flags placed after positional arguments (#79)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-17T11:03:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "446d37bdb6d6378be466ea6e9192f79770b4174f",
          "body": "* Design install --prune for untracked repo config\n\nainfra install only ever adds and updates, so repos accumulate local-only\nconfig no manifest describes and no install removes. Adopting it back into\nainfra.yaml is the existing remedy; this covers the case where the local\nentries are cruft rather t\n[…]\nhe\ndeclare-or-clear two-step, the honest scope, and the two ways the add flags\nmislead: --personal is per-repo so ~/.claude entries need --global, and a flag\nafter the positionals is silently ignored.",
          "is_bot": false,
          "headline": "Add ainfra install --prune with a declare-or-clear guard (#78)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-17T10:02:52Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "edffd3614d369d9d647336782cccaf1d0a00ab92",
          "body": "An unpinned plugin hashes {marketplace, \"\"} on the desired side while the\nmachine observes {marketplace, resolvedVersion}, so the two can never be\nequal and the plugin replans forever. That mismatch is deliberate — it is\nwhat keeps `claude plugin update` running so unpinned plugins track\nupstream — \n[…]\nwn slot.\n\nTests covered neither the summary line nor this path, so the format\nstring could change unnoticed; both are covered now, including that\nApply still runs `claude plugin update` for a refresh.",
          "is_bot": false,
          "headline": "Report unpinned plugin refreshes as refreshes, not drift (#77)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-17T07:49:55Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "226d82912d20bfdfc41cd5ede1491e894c4a87cc",
          "body": "Releasing required someone to remember `git tag vX.Y.Z && git push --tags`, so\nshipping was a separate manual step from merging and a merged fix could sit\nunreleased indefinitely.\n\nPushes to main now compute the next patch tag, push it, and release it in one\njob. The tags: trigger stays so a human c\n[…]\nTagging and releasing deliberately share a job rather than firing the tag\ntrigger: GitHub does not run workflows for refs pushed with the default\nGITHUB_TOKEN, so a split would silently never release.",
          "is_bot": false,
          "headline": "Release on every push to main (#75)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-17T06:32:58Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5ec5a23c064fcf5b615f9bf4811a7d13560b1b5b",
          "body": "…(#74)\n\nGUI-spawned processes get a minimal PATH without the Homebrew prefix,\nso op was not found in exactly the contexts the launcher shims target.\nExecRunner now falls back to /opt/homebrew/bin, /usr/local/bin, and\n~/.local/bin when PATH lookup fails.",
          "is_bot": false,
          "headline": "Probe standard install dirs when a secret-backend CLI is not on PATH …",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-17T06:30:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "97ff29851535b9d26ff0fbbd7ccf9b3724fec0fd",
          "body": "GUI-spawned processes get a minimal PATH without /opt/homebrew/bin, so\na shim calling bare ainfra fails with 'not found' in exactly the\ncontexts the shims exist for. Install now bakes in the LookPath result,\nfalling back to the running executable.",
          "is_bot": false,
          "headline": "Reference the ainfra binary absolutely in launcher shims (#73)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-17T06:25:24Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ab08d2b5bc8d1ee03f32b5814f349777402def04",
          "body": "`ainfra install` has never updated a plugin after the first install. Observe\nhashed the plugin cache at ~/.claude/plugins/cache/<name>@<marketplace>/, a path\nClaude Code does not use — the real layout is cache/<marketplace>/<name>/<version>/.\nThe read always missed, ContentHash came back empty, and \n[…]\ns\nomitempty (emitting \"\" would pin users to the empty string), release refuses on\na SHA-versioned plugin, and build/release now validate the block so a typo'd\nmode cannot silently fall back to semver.",
          "is_bot": false,
          "headline": "Fix plugin reconciliation and add SHA-versioning (#71)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-17T06:20:52Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5d2886375863ce7b079db92c1ec530b8b0efc71e",
          "body": "…hosts (#72)\n\nThird-party claude wrappers (cmux) resolve the real binary by scanning\nPATH skipping only their own dir, so the shim and the wrapper resolved\neach other forever; exec now strips the shim dir from the child PATH.\nGUI hosts that launch claude by absolute path bypass the shim entirely,\nso install also writes claude-app, an absolute-target shim resolved to\nthe native binary at install time (wrapper scripts are skipped).",
          "is_bot": false,
          "headline": "Prevent wrapper loops in ainfra exec and add claude-app shim for GUI …",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-17T06:20:20Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d8534482f166ec992d79775896ded05ee83f2995",
          "body": "env.sh and the ~/.zshenv source line put credential values into every\nprocess on the machine and made rotation depend on re-running install.\nA hidden exec verb now resolves secrets fresh into the child's process\nenvironment; install writes a claude launcher shim plus a secretless\nPATH line, and removes the legacy env.sh wiring on upgraded machines.",
          "is_bot": false,
          "headline": "Deliver secrets at launch via ainfra exec instead of shell exports (#70)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-17T06:07:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "635688d5ff46148e24fd15978d0ff38b481e36f7",
          "body": "Two delivery gaps: a secret rotated in its backend never propagated because\nthe no-drift path skipped syncSecrets entirely, and secrets written only to\nthe settings env block are invisible to Claude Code's ${VAR} expansion in\nHTTP MCP server headers, which reads the real process environment.\n\nA no-o\n[…]\noffline installs stay clean), and syncSecrets additionally writes\n~/.config/ainfra/env.sh as shell exports, wired into ~/.zshenv idempotently.\nSecretless manifests never touch the user's shell config.",
          "is_bot": false,
          "headline": "Refresh secrets on no-drift installs and export them for shell env (#69)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-16T19:05:30Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "63317259e67b793a8c1b81a33f7eb43ad51367ec",
          "body": "Background services never converged: the lockfile hashed a service by its\nspec alone while the installer diffed against a hash that also folds in the\nkind and script-generator version, so every install re-detected drift.\nTemplate-derived lifecycle hooks had a sibling bug, never being written to\nthe \n[…]\nrces' content hash (the exact\nvalue the next diff recomputes) and synthesize entries for rendered resources\nabsent from the lock. This removes the pipeline-vs-render hash divergence for\nevery channel.",
          "is_bot": false,
          "headline": "Record rendered content hash in the applied ledger (#68)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-16T18:15:11Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f6f95ff134f44cab0ec06ec90470bcb7e0cfaf6f",
          "body": "The applied ledger is per-agent, but ApplyAllRendered built it from the\nagent-agnostic lock while planning used the agent-filtered rendered set. A\nresource gated to another agent via agents: therefore landed in this agent's\nledger, and the next run read it back as prior-without-desired and planned a\n[…]\nBuild the ledger from the same rendered view the plan uses, and treat an\nalready-absent rule fragment as a no-op so ledgers polluted by the previous\nbehaviour heal on the next run instead of erroring.",
          "is_bot": false,
          "headline": "Scope the applied ledger to the target agent (#67)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-16T14:18:51Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "29f0b5bd781dcf4f14eb2dcd28e14a9c0d3f7ee0",
          "body": null,
          "is_bot": false,
          "headline": "Use agent override as default render identity",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-16T13:53:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4d77ee3ab4458b75843c9d2eb933621deb49e9d4",
          "body": null,
          "is_bot": false,
          "headline": "Support Codex side-by-side installs",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-16T13:53:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e291d6fdaa3cc25f1df20f84c20fc7c9e3706e4c",
          "body": "Available() gated only on `op whoami`, which reports session state: with just\nthe desktop-app integration (the setup our own error message recommends) it\nfails while reads succeed via per-read biometrics, so install refused to run\non a perfectly usable machine. Fall back to a real read probe before failing.",
          "is_bot": false,
          "headline": "Accept 1Password app integration without a CLI session (#66)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-16T13:25:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "967a2054f3a12d42babae2ce4205eae838900a09",
          "body": "RunLock's inline-server pass substituted secret placeholders into Env/Headers\nmaps still aliased by the loaded layer, then hashed the mutated layers into\nainfra.lock — so CurrentManifestHash never matched and every install warned\nthe lockfile was stale, even seconds after ainfra lock. Clone the alia\n[…]\n re-running the bound-but-unused gate at render time: lock already\nvalidates with produced-service usage visible, so install no longer rejects\nsecrets consumed only by a template's background service.",
          "is_bot": false,
          "headline": "Fix phantom stale-lock warning from in-place secret substitution (#65)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-16T12:30:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4dcb829c8a017224a63b1ba818d2f9b6d8b12dc1",
          "body": "* Register agents/*.md in generated plugin.json\n\nplugin build/release regenerated plugin.json with a hardcoded empty agents\narray, so agent definitions in a plugin repo could never ship. Enumerate\nagents/*.md when the content paths declare the directory (schema rejects a\nbare directory ref), and add\n[…]\nt markers\nin the service spec/lifecycle/check as usage. Also wire the showcase github\ntoken into env and fix the slack reference prefix (secrets. -> secret.), and\ncommit the regenerated example locks.",
          "is_bot": false,
          "headline": "Register agents/*.md in generated plugin.json (#64)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-16T08:16:20Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "49a0b33b0f6f4c682514a2788b61a4e425e931b1",
          "body": "Implements the github-release adapter for ainfra's package-manager system,\nallowing teams to automate installation of CLI tools distributed as GitHub\nrelease binaries. This closes the gap for tools like slack-mcp-server that\nship only as platform-specific binaries without brew/npm/composer packages.\n[…]\n ainfra.yaml cliTools entries like:\n  slack-mcp-server:\n    install:\n      github-release:\n        owner: korotovsky\n        repo: slack-mcp-server\n        assetPattern: \"slack-mcp-server-{os}-{arch}\"",
          "is_bot": false,
          "headline": "Add github-release install channel for CLI tools (#63)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-10T08:43:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5d8e01e7ce603364120418de0c3c302fd148a673",
          "body": null,
          "is_bot": false,
          "headline": "Document Slack MCP setup gap: ainfra needs to sync ~/.claude.json (#62)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-10T08:04:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "58eca67c670566a693bab8d43480e79ad1028803",
          "body": null,
          "is_bot": false,
          "headline": "Add setup-all.sh wrapper for one-command Slack MCP setup (#61)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-10T08:00:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "eb0d87bd33c1be24b5dacde2722c4391c199bb8b",
          "body": "* Fix 1Password path format for Slack session token\n\n* Add setup script to wire slack-mcp-server into Claude Code's ~/.claude.json",
          "is_bot": false,
          "headline": "Add setup script to wire slack-mcp-server into Claude Code (#60)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-10T07:58:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5e3a7c6cbc0aad91c66879dfed8efe3da788964f",
          "body": null,
          "is_bot": false,
          "headline": "Fix 1Password path format for Slack session token (#59)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-10T07:03:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "998874506893183cb5330f627f2368624fbdecb8",
          "body": "…#58)\n\n* Add Slack MCP server for team access to Slack messages\n\n* Simplify Slack MCP config: each developer sets their own SLACK_MCP_XOXD_TOKEN env var\n\n* Use 1Password for Slack token (each dev uses their own vault)\n\n* Update lock file",
          "is_bot": false,
          "headline": "Use 1Password for Slack token (each developer uses their own vault) (…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-10T06:59:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a0ee8e9a99093e90828489515298c11b796104c9",
          "body": "…crets (#57)\n\n* Add Slack MCP server for team access to Slack messages\n\n* Simplify Slack MCP config: each developer sets their own SLACK_MCP_XOXD_TOKEN env var\n\n* Update lock file\n\n* Fix duplicate slack server definition from rebase",
          "is_bot": false,
          "headline": "Simplify Slack MCP config: use personal env vars instead of shared se…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-10T06:53:09Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5237e34e170558764cbbbe7b7b63cf701543bbe2",
          "body": "* Add Slack MCP server for team access to Slack messages\n\n* Add Slack MCP setup guide and lock files",
          "is_bot": false,
          "headline": "Add Slack MCP server for team access to Slack messages (#56)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-07-10T06:50:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3fafdd5c3a2b4557959584fd6379c096a2dd7af9",
          "body": "…DME (#55)\n\n* Detect 1Password readiness and reject dead secret bindings\n\nAn op:// secret only failed when syncSecrets ran, after .mcp.json was\nalready written and \"Applied N changes\" printed, leaving a half-configured\nrepo and raw 1Password output. And a secret bound to an MCP server but never\nrefe\n[…]\nt is now\ninit --adopt; inspect and plugin were missing). Restructure around the three\ncommands users actually run, trim the heavy sections to docs/, and fix the\ntable to match the registered commands.",
          "is_bot": false,
          "headline": "Detect 1Password readiness, reject dead secret bindings, simplify REA…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-06-16T07:42:39Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "90395c4f80c7379262c5c18da9579f1ad5027474",
          "body": "Every ainfra install (including --dry-run) re-resolved ainfra.yaml and\nrewrote ainfra.lock with a fresh generatedAt and whatever MCP introspection\nsaw on that machine, so installs always dirtied the working tree and a\nVPN-down run could strip toolsets from the committed lock. Resolve in\nmemory for render/install (introspection skipped; rendering never reads\ntoolset data) and reserve lockfile writes for lock/update/add, which now\nalso preserve generatedAt when nothing changed.",
          "is_bot": false,
          "headline": "Make install lock-consuming instead of lock-writing (#54)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-06-13T05:34:07Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "29bc742e9967ba920152c5108f319132bef0ca5e",
          "body": "…h-tunnel (#53)\n\n#51 taught ainfra to render `ssh -f -N -L` commands for kind: ssh-tunnel.\nThat bakes transport-specific knowledge -- and a fixed sshUser@host auth\nmodel -- into the tool, against the design's \"must not hardcode knowledge\nof specific things\" rule. The user@host form it generated also\n[…]\n, and auth -- live in the team's\nainfra.yaml and ~/.ssh/config, not in ainfra.\n\nserviceScriptGen bumped 2 -> 3 so existing installs re-render their\nscripts on the next apply despite an unchanged spec.",
          "is_bot": false,
          "headline": "Make background-service start scripts config-driven; drop built-in ss…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-06-08T09:15:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "fe0c22b3ee1b76a0a85ba290598819356e274e9c",
          "body": "Background services rendered start.sh/stop.sh but nothing ran them, and\nbuildStartScript emitted a TODO stub for ssh-tunnel kind (no spec.command).\nSo the prod-DB MCP tunnels never came up on their own.\n\n- Render real, idempotent ssh -f -N -L start scripts (with a port-listening\n  guard) and matchin\n[…]\n).\n- Fold a script-generator version into the service content hash so existing\n  installs re-render their scripts on the next apply despite an unchanged spec.\n\nAdds unit, render, and end-to-end tests.",
          "is_bot": false,
          "headline": "Auto-start ssh-tunnel services via generated SessionStart hook (#51)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-06-08T08:16:39Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "963b19508c6c000f7f6036bb62d1325579bc9623",
          "body": "os.WriteFile's mode only applies on creation. Claude Code creates\nsettings.local.json at 0644 first, so the secrets syncSecrets merges in\nsat world-readable. Chmod explicitly after every write.",
          "is_bot": false,
          "headline": "Tighten settings.local.json to 0600 on every write (#50)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-06-08T07:58:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "9977d1603ebf0c3ef88674a50c9505b93d906800",
          "body": "A typed-nil map[string]string boxed in an any is not == nil, so the\nenv/headers guards in buildMCPServerObject passed and wrote \"env\": null /\n\"headers\": null for stdio servers. Claude Code rejects null for stdio\nMCP entries and skips the server. Match the hasher and use isEmpty().",
          "is_bot": false,
          "headline": "Omit empty MCP fields instead of writing null (#49)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-06-08T07:45:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "89232d371630809ccff4fe738bf56f237b6dcb52",
          "body": "cliTools declaring 'install: uv: {package, python}' (e.g. meta-ads) could not\nbe installed on a clean machine: pkg.Select had no uv adapter, so applyOne fell\nthrough to the declare-and-check probe and failed with 'no supported install\nmethod is declared', erroring the whole 'ainfra install'. It only\n[…]\n install / uv tool list), register it in Select and\nMethods. IsInstalled matches the package against the first field of each\n'uv tool list' line so a substring of an exposed executable does not count.",
          "is_bot": false,
          "headline": "feat: add uv package adapter for installing Python CLI tools (#48)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-06-05T11:23:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "576e110cae765e06274015495f809f9a55f41d89",
          "body": "…te (#47)\n\nainfra lock/update rebuild the committed lock from a fresh resolve, which has\nno plugin baseline (that is written only by 'ainfra plugin release'). The write\npath did not carry the existing baseline forward, so every 'ainfra update'\nsilently dropped the plugin: block from ainfra.lock — lo\n[…]\nwriting.\n\nAlso fix the stale-lockfile warning, which told users to run 'ainfra lock' (a\nhidden command). Point it at 'ainfra update' and note that refreshing the lock\nis a maintainer action to commit.",
          "is_bot": false,
          "headline": "fix: preserve plugin baseline on re-resolve; point stale hint at upda…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-06-05T10:39:57Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f8b478c0829e6abf8b791be09eff4050c2ab48a7",
          "body": "A disabled server was silently omitted from desired state, so apply only\nremoved it when ainfra itself had installed it. Servers a teammate added by\nhand (present on the machine, never in the applied ledger) lingered after the\nteam retired them. Render disabled servers as tombstones so the diff actively\nremoves them wherever present, while servers the manifest never mentions stay\nuntouched.",
          "is_bot": false,
          "headline": "Treat enabled: false MCP servers as tombstones (#46)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-06-01T14:27:17Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d433e0383b87253fce45b4b6d2f6e7cc63096561",
          "body": "The marketplace listing's description is human-authored and intentionally\ndiffers from plugin.json's; rewriting the self-entry clobbered it and produced\nnoisy diffs. ainfra now only verifies the entry exists.",
          "is_bot": false,
          "headline": "fix(plugin): verify marketplace entry instead of rewriting it (#45)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-31T13:41:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e3efedbd9241d6c0cb38fb676b50f2bacc2a81e2",
          "body": "* docs: design for ainfra plugin build/release\n\nSpec for managing the team's own Claude Code plugin (manifest generation,\nexplicit-bump release with drift guard) so a forgotten version bump can no\nlonger silently withhold updates from consumers.\n\n* feat(manifest): parse plugin build block\n\n* feat(ma\n[…]\n release decision with drift guard\n\n* feat(cli): ainfra plugin build/release command\n\n* docs: implementation plan for ainfra plugin build/release\n\n* fix(plugin): include metadata in release drift hash",
          "is_bot": false,
          "headline": "Add ainfra plugin build/release command (#44)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-31T13:09:05Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "41fa65ddecb2b9bcde7d55a679f807ecd2a226c8",
          "body": "…ob (#43)\n\n* docs(problem-space): add primary-evidence section from team-transition thread\n\nCites the March 2026 r/ClaudeCode team thread as primary evidence: confirms\npain point 6 (skills/commands distribution), flags the skills-drift and\nonboarding framings the thread weakens, and records two unco\n[…]\n command\n\nThe dogfood step invoked `ainfra validate`, which no longer exists\n(renamed to `lock`), so the build job has been failing on main for\nevery commit. Resolve both manifests via `lock` instead.",
          "is_bot": false,
          "headline": "docs(problem-space): primary-evidence section + fix broken CI build j…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-31T11:53:04Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3c46cea2048b2ea5e324ad084306d499ce3ecf81",
          "body": "Two ergonomic fixes when adopt scans settings.json:\n\n1. Skip ainfra-owned hooks. 'ainfra install' injects a SessionStart\n   hook that runs 'ainfra _staleness-check' to nudge users about\n   drift. That hook isn't user content — it's an implementation\n   detail — but adopt used to ingest it and synthe\n[…]\now becomes 'pretooluse-bash' instead of\n   'pretooluse-bash-172d96a1'.\n\nExisting personal manifests that already use the long form keep\nworking — the user can rename to drop the hash at their leisure.",
          "is_bot": false,
          "headline": "fix(adopt): cleaner hook IDs, hide ainfra-owned hooks (#40)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T14:15:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "06ef6a2a1e92be86af208f442683aa4f3d3dc0da",
          "body": "You expected 'inspect --global' to surface 'what is defined in\nclaude-config' (the team plugin). Added the flag; it walks the\ninstalled-plugins manifest at ~/.claude/plugins/installed_plugins.json\nand emits one row per (channel, id) each plugin contributes:\n\n  commands → <plugin-path>/commands/<id>.\n[…]\nvisible at a glance rather than\nsilently shadowed.\n\n--all (personal layer) and --global (personal + plugins) compose\nnaturally; --global implies --all so users only need one flag for\nthe full picture.",
          "is_bot": false,
          "headline": "feat(inspect): add --global to show entries from installed plugins (#39)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T14:05:00Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "50e9de5bb2c85c70cdd8d7013dca53c21ed8348f",
          "body": "… (#38)\n\nThree small clarity fixes after dogfooding the install flow:\n\n1. 'ainfra lock' singularizes channel labels when count is 1.\n   Was: 'Resolved ainfra.yaml: 2 MCP servers, 1 hooks, 9 commands.'\n   Now: 'Resolved ainfra.yaml: 2 MCP servers, 1 hook, 9 commands.'\n\n2. 'ainfra lock' MCP server lin\n[…]\n scope are no-ops.\n   - 'Wrote 0 secret(s)' line is suppressed when no secrets were\n     written.\n   - Final line: 'Done — your environment now matches ainfra.yaml.'\n     (was 'Apply complete — ...').",
          "is_bot": false,
          "headline": "fix(cli): friendlier lock + apply summary, drop empty user-scope echo…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T13:52:18Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5cbda5bf607ae8784875015bd812cbf21cae643e",
          "body": "… (#37)\n\nThe plan renderer printed lines like:\n  ~ mcpServers.chrome-devtools  mcpServers chrome-devtools differs from lockfile\n\nThe Detail field redundantly prefixed every line with 'channel id'\nright after RenderPlan already prefixed 'channel.id'. And the phrasing\n('differs from lockfile', 'not pr\n[…]\nxt.\n    + Lead with the diff body, end with 'Plan: N to install, N to update,\n      N to remove.' (was 'to add' — install reads like the actual action).\n\nUpdated tests that referenced the old wording.",
          "is_bot": false,
          "headline": "fix(install): friendlier plan output, no duplicate channel/id strings…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T13:10:21Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "25975791c943147f5bed346e7090251ddc53730c",
          "body": "…s (#36)\n\n'ainfra init --adopt' now produces an ainfra.yaml that 'ainfra lock'\naccepts on the first run, in three previously-broken cases:\n\n1. .mcp.json args that use dist tags (chrome-devtools-mcp@latest,\n   @playwright/mcp@latest) used to be adopted verbatim. ainfra then\n   refused to lock because\n[…]\n  Added MCPFileFallback to Layout and accept either top-level key\n   in readMCP. Together these mean adopt picks up MCP config from\n   either location regardless of which schema variant the repo uses.",
          "is_bot": false,
          "headline": "feat(adopt): scan skills, fallback MCP path, resolve dist-tag version…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T13:06:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c52ac3881ce35b931cc7fdf4f537091b2adb6028",
          "body": "You wanted to know *where* each detected entry lives. Add a Source\nfield to inspectRow (also exposed in JSON) and render it dimmed in\nthe table after the status phrase.\n\nSource resolution per channel:\n  mcpServers — .mcp.json or .claude/mcp.json (whichever held the entry)\n  commands   — .claude/comm\n[…]\nthe Rule's Source field (e.g. ./CLAUDE.md)\n  skills     — .claude/skills/<id>/ or ~/.claude/skills/<id>/\n\nRepo-relative paths render as-is; $HOME-rooted paths shrink to ~/...\nto keep the table narrow.",
          "is_bot": false,
          "headline": "feat(inspect): show source path for every row (#35)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T12:06:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "31bdf122b0bab61b2fd29d26d86d7e5e1bbe9140",
          "body": "…ocal personal collisions (#34)\n\nTwo follow-up fixes after the plain-English rewrite:\n\n1. A repo-local file whose only manifest coverage was the user's\n   personal layer rendered as 'managed by your personal config (global)'.\n   Misleading: from the repo's perspective, no one is managing it\n   ainfr\n[…]\n  this repo yet' instead.\n\nAlso trim the per-row phrase to one or two words ('local-only',\n'managed by ainfra', 'declared but not installed'); the full\nexplanation lives in the Legend below the table.",
          "is_bot": false,
          "headline": "fix(inspect): drop redundant 'ainfra.yaml' mention; reclassify repo-l…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T12:02:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a2ee87b187e0a35f47d5b0932a9e1b626df64d18",
          "body": "The old output repeated 'on disk, not in ainfra.yaml - run ainfra\ninit --adopt --force to absorb' on every untracked row. Two problems:\n\n1. 'On disk' is git/tool jargon. Most readers think of git, not\n   'present locally on this machine vs declared in the team's\n   ainfra.yaml'.\n2. The same hint rep\n[…]\nppear in this report\n- one Next: block with the concrete remediation commands, also\n  rephrased to drop tool jargon\n\nChannel headers also get friendlier labels ('MCP servers' instead\nof 'mcpServers').",
          "is_bot": false,
          "headline": "fix(inspect): plain-English output, no per-row repetition (#33)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T11:57:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5c07919fd000945290c99aafef7a15051b2b92b5",
          "body": "…CP path, repo-local CLAUDE.md (#32)\n\n* feat(inspect): hide personal-layer by default, add skills channel\n\nTwo follow-ups after dogfooding inspect on a repo without ainfra.yaml:\n\n1. Hide personal-layer entries by default. Running inspect in any repo\n   surfaced the user's global ~/.config/ainfra/per\n[…]\ning it is wrong.\n\n3. .claude/settings.local.json is intentionally not classified (it's\n   gitignored personal state, not config). Surface a Note line so users\n   understand what was skipped vs missed.",
          "is_bot": false,
          "headline": "feat(inspect): hide personal-layer by default, add skills, fallback M…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T11:15:37Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f69d94ba293f24d62462def909ea3a0158003f9f",
          "body": "* feat(inspect): add ainfra inspect command\n\nA read-only scanner that reports what Claude Code config is present in\na repo and how it relates to ainfra's manifest. Surfaces three states:\n\n  tracked   declared in some manifest layer AND present on disk\n  untracked present on disk, not declared in ain\n[…]\n\nhints, so a virgin repo with no .claude/ on the runner printed only\n'No mcpServers...' without the 'ainfra init --adopt' suggestion.\nHoist the hint block into a helper and call it from both branches.",
          "is_bot": false,
          "headline": "feat(inspect): add ainfra inspect command (#31)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T10:46:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c9d56cddbf267f1ff7df2a86e207401d658219a1",
          "body": "… cleanup (#30)\n\n* fix(resolve): stop infinite drift on empty or absolute-path command sources\n\nTwo related bugs caused 'ainfra update' to report the same drift on every\nrun for user-scope commands defined in the XDG personal manifest:\n\n1. When a command's source file existed but was empty, the lock\n[…]\ner machine.\n\n* chore: gofmt -w pre-existing unformatted files\n\nCI's gofmt check is blocking the hook fix; bring these into compliance.\nNo behavior change — only whitespace and doc-comment indentation.",
          "is_bot": false,
          "headline": "fix(hooks): observe XDG applied ledger in user scope + test isolation…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T10:05:53Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4026a849750cddf0200766538bc6403331a57b8c",
          "body": "…ources (#29)\n\nTwo related bugs caused 'ainfra update' to report the same drift on every\nrun for user-scope commands defined in the XDG personal manifest:\n\n1. When a command's source file existed but was empty, the lock hash fell\n   back to a manifest-shape hash. Apply still wrote the empty payload \n[…]\ning against dir. The commands\nhash now uses ok to choose between content-hash and manifest-shape\nfallback, so a deliberately-empty source file hashes as empty content\nand apply converges in one round.",
          "is_bot": false,
          "headline": "fix(resolve): stop infinite drift on empty or absolute-path command s…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T09:33:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "adfd91baaef4cefd9b1322b50e07d39f8ca05d5e",
          "body": "Consolidates onboarding under a single verb. `ainfra init team <path>`\nscaffolds a shared claude-config repo by scanning ~/.claude/ (default;\n--empty for a skeleton), runs git init, and writes a README. The old\n`adopt` verb becomes `init --adopt`; the standalone command is removed\nsince every flow now starts from `init`.",
          "is_bot": false,
          "headline": "feat(init): add team subcommand, fold adopt into init --adopt (#28)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T07:01:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b95acc3a62e691ccce6f143ee13914071b82c307",
          "body": "Every user-facing string now answers two questions a newcomer asks: \"is\nthis success or failure?\" and \"what do I run next?\". Bare phrases like\n\"Aborted.\", \"Nothing to do.\", and \"No entries.\" now carry a one-line\nexplanation; lock/install summaries name the files they wrote and what\nto commit; errors\n[…]\nfixes a dead-end bug where `list` and `outdated` told users to run\n`ainfra install` when the lockfile was missing, but install itself\nrequires the lockfile — both now correctly point to `ainfra lock`.",
          "is_bot": false,
          "headline": "ux: rewrite CLI output to be concise and explain what's happening (#27)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T06:31:43Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1875ad51bb040ebdb1a8ea9bbfaa1f54f87d2466",
          "body": "… (#26)\n\nAdopt is the brownfield onramp — once a manifest exists, the manifest is the\nsource of truth and `ainfra install` reconciles disk back to it. Re-running\nadopt to fold disk drift into an existing manifest inverted that model and\nsolved a problem install already covers. Removing --merge colla\n[…]\nnow explicitly directs users to\ninstall for drift reconciliation.\n\nAlso drops the unused WarnMergeAdd warning kind and the five addNew*\nhelpers (~150 LoC), and updates README + quickstart accordingly.",
          "is_bot": false,
          "headline": "refactor(adopt): drop --merge; bootstrap-only, point drift at install…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T06:25:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4973a00d516e14b1190687ce526b536bf3e509d1",
          "body": "Builds the public docs from existing docs/ markdown plus a build-time sync\nof spec/*.md. Defaults to dark mode with a custom near-black palette.",
          "is_bot": false,
          "headline": "docs: add VitePress site deployed to Vercel (#25)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T06:20:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fd436ecd063009ffc00b9abd6513eb89b40fbecd",
          "body": "…s (#24)\n\nAdopt now groups warnings by kind (stripped credentials, merged keys,\nreview-manually) with section headers, counts, and per-section prose\nthat tells a first-time user what happened and what they have to do.\nThe 'Next:' hint names the stripped-secret count so the action is\nunambiguous.",
          "is_bot": false,
          "headline": "feat(adopt): clearer grouped warnings with explainers and aligned row…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T06:13:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e93191dc1200501c5943b4a715b7b79cb407bf9e",
          "body": "Closes the brownfield gap for the user-global layer: developers with an\nexisting ~/.claude/ setup can now bootstrap $XDG_CONFIG_HOME/ainfra/\npersonal.yaml the same way adopt bootstraps a repo ainfra.yaml.\n\nRefactors the scanner around a Layout struct so repo and user scopes\nshare one code path; ~/.claude.json MCP servers stay deferred behind a\nwarning rather than being silently dropped.",
          "is_bot": false,
          "headline": "feat(adopt): --scope=user emits global personal manifest",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T05:55:23Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "03d8a03d096ceac3354a4028439ee4d8eb7ebade",
          "body": "Plan 002 landed via PR #17; plan 001's load-bearing pieces shipped with\nthe resolve/template/content-hash work in earlier PRs; plan 003 closes\nwith the staleness hook + shadowed-list commit. README, quickstart, and\nthe using-ainfra skill teach the SessionStart hook + opt-out.",
          "is_bot": false,
          "headline": "docs: mark CLI rename, UX tightening, and multi-repo plans shipped",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T05:25:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "de480a64530b856f690c28979b3ac8823b011abc",
          "body": "Auto-emit a SessionStart hook into .claude/settings.json on install so a\nteammate's un-installed manifest pull surfaces on next Claude startup; opt\nout per repo with stalenessWarning: false. The hook calls the new hidden\n'ainfra _staleness-check' subcommand, which compares the manifest hash to\nthe a\n[…]\nison actually fires.\n\n'ainfra list' annotates cross-layer collisions (team > repo > personal) as\n'(shadowed by <layer>)' and exposes shadowedBy in --json, making layered\noverrides visible at a glance.",
          "is_bot": false,
          "headline": "feat: SessionStart staleness hook + shadowed-layer list rows",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-28T05:25:44Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0d796f19fc81dac1e2ad846ea29f7801d47938b4",
          "body": "Drops the apply/plan/check/validate/schema/sync/exec/history alias verbs along with the DeprecatedFor/DeprecationNote fields and once-per-process latch. Install + its flags fully cover the surface; runPrintSchema moves into commands.go, secret materialization stays as syncSecrets, and expandUser stays in expand_user.go.",
          "is_bot": false,
          "headline": "refactor: remove deprecated CLI aliases and machinery",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T15:29:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d9c4aa4efed3df0cecfd2d3ebd56c7697939c068",
          "body": "* fix(plugins): align with Claude Code plugin cache contract\n\nFive Claude Code plugins-reference compliance fixes, bundled:\n\n- Qualify uninstall with @marketplace so a name shared across two\n  registered marketplaces is unambiguous.\n- Always run `claude plugin update` on ChangeUpdate. The SHA-versio\n[…]\nr them in the apply\nsummary; document the version-pin semantics in spec/manifest-schema.md §10.\n\n* chore: gofmt -w pre-existing formatting drift\n\n* chore: gofmt -w remaining drift post-merge with main",
          "is_bot": false,
          "headline": "fix(plugins): align with Claude Code plugin cache contract (#23)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T13:52:51Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "538cb120ec657112edb0093338a08ac2f2cfcd44",
          "body": "…mote sources (#22)\n\n* feat(fetch): github, npm, and https source resolvers with content cache\n\nReplaces the LocalFetcher \"remote source not supported\" stub with a\ndispatching multi-scheme fetcher. github: pins to commit SHA via\nthe GitHub API; npm: verifies dist.integrity from the registry;\nhttps: \n[…]\n\nPhase 6 entry. validation.md adds a sixth scenario.\n\nspec/lockfile-schema.md confirms toolsetHash and documents the\nadditive lockedTools / command / args / env entry fields used at\ncheck-time replay.",
          "is_bot": false,
          "headline": "feat: v1 ship sharpener — adopt, toolset hashing, drift detection, re…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T13:26:02Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8d31869802d7927727358c7d75dd3278e1943346",
          "body": "Lays down the evidence-and-philosophy basis for the next round of v1\nwork. problem-space.md grounds ainfra's positioning against documented\nteam pains (MCP rug-pulls, secret leakage, drift, onboarding cost);\ndesign-philosophy-references.md cross-references npm and Terraform's\nmanifest+lockfile, plan\n[…]\nour concrete additions\nworth landing before v1 is declared shipped (toolset hashing, status,\nadopt, remote-source resolver) and explicitly defer Cursor renderer,\nSigstore, and persona-drift hardening.",
          "is_bot": false,
          "headline": "docs: capture v1 ship sharpener research, brainstorm, and plan (#20)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T13:25:58Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "1b56ae4ab4ba0f8840c0e703a95960856c4f5814",
          "body": "…onal layer (#21)\n\n* docs: add multi-repo activation brainstorm and plan\n\n* feat(provider,xdg): user-scope applied ledger at $XDG_CONFIG_HOME/ainfra/\n\nIntroduces internal/xdg/ as the single source of truth for ainfra's\nuser-scope paths (personal manifest, applied ledger). ReadAppliedUser /\nWriteAppl\n[…]\ndger tracks ownership\n- Second install is 'Nothing to do.' (idempotent)\n- Existing per-repo behavior unchanged (repo-layer entries still land in\n  repo/.claude/, repo ledger separate from user ledger)",
          "is_bot": false,
          "headline": "feat: install once, available everywhere — dual-pass install for pers…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T12:52:01Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7b0dc459a000313c2cc57244347041b63a9fd141",
          "body": "…-aware orchestrator (#19)\n\n* docs: add multi-repo activation brainstorm and plan\n\n* feat(provider,xdg): user-scope applied ledger at $XDG_CONFIG_HOME/ainfra/\n\nIntroduces internal/xdg/ as the single source of truth for ainfra's\nuser-scope paths (personal manifest, applied ledger). ReadAppliedUser /\n\n[…]\nor existing callers.\n\nThis is the foundation for the dual-pass install that routes personal-\nlayer entries to ~/.claude/. The install command itself still needs the\npartition + dual-pass wiring (U2b).",
          "is_bot": false,
          "headline": "feat: substrate for user-scope install — LoadLayers tolerance + scope…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T12:36:17Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "626af8b794cc5964b7cff0f97801dba31e8d4ce6",
          "body": "…sion pin (#18)\n\n* docs: add multi-repo activation brainstorm and plan\n\n* feat(provider,xdg): user-scope applied ledger at $XDG_CONFIG_HOME/ainfra/\n\nIntroduces internal/xdg/ as the single source of truth for ainfra's\nuser-scope paths (personal manifest, applied ledger). ReadAppliedUser /\nWriteApplie\n[…]\ntarget install, user-scope cleanup, shadowed-list display, and the\nSessionStart staleness hook (U2-U5 in the plan) are deferred until we\nnail down the per-channel user-scope file paths in Claude Code.",
          "is_bot": false,
          "headline": "feat: multi-repo activation substrate — user-scope ledger + ainfraVer…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T12:15:05Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "eb7c5be34b81a6541de725084b8daa404c6a9f10",
          "body": "…ated (#17)\n\n* fix(resolve): interpolate template MCP server args, command, and url\n\nTemplate Instantiate copied src.Args verbatim, so ${params.*} placeholders\nin args[] leaked through to the generated .mcp.json. Env and Headers were\nalready interpolated; this brings Args, Command, and URL to parity\n[…]\nabase: README + ainfra.yaml + commands/db-console.md\n  use install --dry-run --strict for VPN checks and install for reconcile.\n\ndocs/superpowers/specs/* left untouched as a historical design journal.",
          "is_bot": false,
          "headline": "feat: package-manager CLI shape — install/add/remove/update/list/outd…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T11:10:57Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "cbddc7a32f199407ac34a9a38ffa24d050246569",
          "body": "Ships a Claude Code skill that teaches AI agents the plan/apply/lock/check\nworkflow, and a new init flag that scaffolds it into a project's manifest so\nadopters can opt in at setup time.",
          "is_bot": false,
          "headline": "Add using-ainfra skill and ainfra init --with-skill flag",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T08:24:38Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b971ecd16326703857e0d749ec1c5a4ba6ce2a3b",
          "body": "README now leads with install + the three commands a joiner runs, then\nthe manifest example, then the pitch. Quickstart's install block leads\nwith Homebrew. Reference docs (design, validation, sx-comparison,\nassessment-vs-real-config) move into docs/reference/ so the entry point\nunder docs/ is just the quick start.",
          "is_bot": false,
          "headline": "Simplify docs for new-user onboarding (#16)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T08:15:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "afeee9f60e4dd8444605293359145871002cace1",
          "body": "* Distribute ainfra via Homebrew\n\nAdds a GoReleaser config that builds darwin/linux binaries on each\nv* tag and pushes a Homebrew cask to MHilhorst/homebrew-ainfra, so\nusers can `brew install MHilhorst/ainfra/ainfra`. Includes an MIT\nLICENSE so the cask has a proper license stanza and the archive\nships one.\n\n* README: document Homebrew install",
          "is_bot": false,
          "headline": "README: document Homebrew install (#15)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T05:32:14Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3eaf5a123962b9aae89cc00ad437ca9450cf694d",
          "body": "Adds a GoReleaser config that builds darwin/linux binaries on each\nv* tag and pushes a Homebrew cask to MHilhorst/homebrew-ainfra, so\nusers can `brew install MHilhorst/ainfra/ainfra`. Includes an MIT\nLICENSE so the cask has a proper license stanza and the archive\nships one.",
          "is_bot": false,
          "headline": "Distribute ainfra via Homebrew (#14)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-27T05:27:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b0baa2cef3d99b706d44e9d5143bc6fed112a864",
          "body": "…tory log (#13)\n\n* Add apply history log and ainfra history command\n\nAppend-only .ainfra/history.jsonl records one event per non-noop change\nafter each apply (and apply --from). New ainfra history reads it with\n--since/--actor/--channel/--json filters. Cheap groundwork for the\ndeferred Govern produc\n[…]\ndopted (selectors, identity, global personal layer, history\nlog), and the ones rejected (vault model, cloud relay, query MCP,\npackage-manager verbs). design.md gains a short section 15 pointing at\nit.",
          "is_bot": false,
          "headline": "Adopt sx ideas: scope selectors, identity, global personal layer, his…",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-26T06:15:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "07454cef10f8e08edb0eefec310f57aabfd99832",
          "body": "Captures the post-migration state — what landed, what's verified live,\nthe four design-level items the ownership-boundaries spec covers, and\nthe honest gaps (fresh-instance install untested, VPN-gated servers\nonly handshake-proven, scheduled jobs deferred).",
          "is_bot": false,
          "headline": "Status snapshot: where ainfra is and what's open",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-26T06:00:31Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "fe380d66319bacc583a6bbb650cdbe29a9366397",
          "body": "Captures the four design-level findings from the tvt-config review:\nMCP single-source-of-truth, cliTools as substrate, SSH tunnels, and\nrecipe-to-artifact reproducibility. Records the root principle (own\nconfiguration; declare and verify substrate) and the two fixes already\nshipped.",
          "is_bot": false,
          "headline": "Spec: ainfra ownership boundaries — own, declare, or delegate",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T19:26:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d0bf9d1554c84231761df3c7d418bb0f73ed4ec2",
          "body": "ainfra check reported config drift only — it never ran preconditions,\nso a failing VPN/DNS precondition went unreported. And the dns-resolves\ncheck type was never implemented: the converter only understood a\nshell command, so the vpn-tvt-internal precondition was silently\nskipped even by apply. precond now evaluates dns-resolves via\nnet.LookupHost, and check runs preconditions alongside drift.",
          "is_bot": false,
          "headline": "Run preconditions during ainfra check, with a real dns-resolves check",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T19:23:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d9ea8c746965df203991c3152591125bd627bc5b",
          "body": "ResourcesByChannel, channelPrefix, ApplyOrder, and the lockfile-read\nnil-map normalization each enumerated channels by hand and omitted\nmarketplaces — added after the channel landed but never wired in.\nResourcesByChannel feeds the diff hash-backfill, so its omission left\nan applied marketplace perpetually drifting. Completes the buildLedger\nand mergeLocks fixes.",
          "is_bot": false,
          "headline": "Carry the marketplaces channel through all channel enumerations",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T12:53:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c1bbb6aa3b6ecdd0ef213c41582e568cfefa6c35",
          "body": "mergeLocks built the desired lock from 9 channels, omitting\nmarketplaces — so the desired lock passed to apply had no marketplace\nentry, the ledger recorded none, and every apply re-detected drift.\nPairs with the buildLedger fix.",
          "is_bot": false,
          "headline": "Include marketplaces when merging the committed and personal locks",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T12:50:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0a9b1bf8aa9ff4b9aa3273e235c5f1029e23302a",
          "body": "buildLedger built the ledger from 9 channels but omitted marketplaces,\nso an applied marketplace was never recorded. Every subsequent apply\nre-observed it as drift and re-applied it, never converging. Add it to\nbuildLedger alongside the other channels.",
          "is_bot": false,
          "headline": "Record the marketplaces channel in the applied ledger",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T12:47:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2d58e02626a4404f4a72557f221f94cb5044a74f",
          "body": "A secret with path: set is the file-destination counterpart of env:.\nainfra sync resolves the ref and writes the value verbatim to the path\n(parent dir 0700, file 0600), for a tool that reads a credential file\nrather than an environment variable. ainfra stays content-blind — the\nwhole file is one opaque blob in the resolver; ainfra never composes.\n\nRecords the refinement in the credential-files design doc: sync is the\nsingle explicit step where references become concrete files.",
          "is_bot": false,
          "headline": "Add path: secrets — materialize a credential file from one reference",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T12:17:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "082aa46b7fe95a0b79e8b240b896ce8521bec732",
          "body": "A secret marked envFile: true is resolved to a .env blob (KEY=value\nlines) and every line expands into its own variable. One 1Password\nitem can then stand in for an entire environment, instead of declaring\neach secret separately.",
          "is_bot": false,
          "headline": "Support envFile secrets — one ref expands to a whole environment",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T11:40:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4c3a73fad60b573f92a0194f829d80e7bb452d24",
          "body": "apply now resolves the manifest's secrets and writes them to the\nClaude Code settings env block (via the shared syncSecrets helper), so\n'ainfra apply' is a complete setup — config plus credentials — and\nClaude launches normally afterward. 'ainfra sync' remains as the\nsecrets-only refresh.",
          "is_bot": false,
          "headline": "ainfra apply: write secrets as its final step",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T10:47:47Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "248325c7b8ef90038dbd71f430163c0e8c61df77",
          "body": null,
          "is_bot": false,
          "headline": "Merge branch 'worktree-purring-wibbling-whistle'",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T10:38:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7009145665995b0c76e1a18ff780337849f27580",
          "body": "ainfra sync resolves every manifest secret from 1Password and writes\nthe values into ~/.claude/settings.local.json's env block (merging,\npreserving other keys, 0600). This is the file-based alternative to\n'ainfra exec': run it once, then launch Claude normally — every launch\npath, no wrapper. The direct equivalent of 'tvt sync'.",
          "is_bot": false,
          "headline": "Add 'ainfra sync' — write resolved secrets to the settings env block",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T10:38:03Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2da0299410ed9ad5ee5380d04854a6d079d56256",
          "body": null,
          "is_bot": false,
          "headline": "Merge branch 'worktree-purring-wibbling-whistle'",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T09:48:02Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "5b06354c5ea5ec1396409acc8bf81e99683ee51d",
          "body": "A secret may set env: <NAME>, and ainfra exec exports it under that\nname instead of a generated AINFRA_SECRET_* one. This makes ainfra exec\na drop-in for an MCP config that already expects specific variable\nnames — no .mcp.json rewrite needed to migrate.",
          "is_bot": false,
          "headline": "Let a secret declare its exported env-var name",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T09:46:06Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "743977c143a98a8c9bd8e40b41b4b3fc357a5825",
          "body": "# Conflicts:\n#\tinternal/manifest/validate.go",
          "is_bot": false,
          "headline": "Merge origin/main into validation-hardening branch",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T09:25:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2df91028fad517f89d80e792cc0f8574e4e4d6d8",
          "body": null,
          "is_bot": false,
          "headline": "Reject reference-mode secrets with no ref",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T09:22:41Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2230081fbaacdd0e8266c8de3b31fcb810de325e",
          "body": null,
          "is_bot": false,
          "headline": "Clarify op:// hint and cover 4-segment and non-scheme refs",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T09:18:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "e1126cd709ce45cd8960f46e337148b9d37e042c",
          "body": null,
          "is_bot": false,
          "headline": "Structurally validate op:// and env:// secret references",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T09:16:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7a3f85c0cded2856c135987bd38935706e8a0247",
          "body": null,
          "is_bot": false,
          "headline": "Polish cliTool install warning: readable message, empty-install test",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T09:14:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3b4098dc0c11e5598bff5d007afa9dc98077f432",
          "body": null,
          "is_bot": false,
          "headline": "Warn at lock time about CLI tools ainfra cannot install",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T09:10:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0c578f729f1bfec3ab8b64b99f4f3d96824259e9",
          "body": null,
          "is_bot": false,
          "headline": "Add validation-hardening plan (items 3 and 6)",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T09:08:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2392da5170fc651c09add3c1e5cea5f4e4668f35",
          "body": null,
          "is_bot": false,
          "headline": "Merge branch 'main' into worktree-floating-wobbling-riddle",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T08:02:34Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c7ae6824a8cf109114ebd2eb03dd1958b911ddbd",
          "body": "# Conflicts:\n#\tREADME.md\n#\tcmd/ainfra/commands.go",
          "is_bot": false,
          "headline": "Merge branch 'main' into worktree-floating-wobbling-riddle",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T08:02:08Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "2693e5f07732a0711c66e9ae9abfd46583346402",
          "body": null,
          "is_bot": false,
          "headline": "Merge origin/main into apply-coverage branch",
          "author_name": "Michael Hilhorst",
          "author_login": "MHilhorst",
          "committed_at": "2026-05-22T08:01:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        }
      ],
      "releases_count": 29,
      "commits_last_year": 308,
      "latest_release_at": "2026-07-22T11:17:53Z",
      "latest_release_tag": "v0.2.27",
      "releases_from_tags": false,
      "days_since_last_push": 5,
      "active_weeks_last_year": 8,
      "days_since_latest_release": 5,
      "mean_days_between_releases": 0.6
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": false,
      "has_contributing": false,
      "health_percentage": 28,
      "has_issue_template": false,
      "has_code_of_conduct": false,
      "has_pull_request_template": false
    },
    "ecosystem": {
      "packages": [
        {
          "name": "github.com/MHilhorst/ainfra",
          "exists": true,
          "license": null,
          "keywords": [],
          "ecosystem": "go",
          "matches_repo": true,
          "registry_url": "https://pkg.go.dev/github.com/MHilhorst/ainfra",
          "is_deprecated": false,
          "latest_version": "v0.2.27",
          "repository_url": "https://github.com/MHilhorst/ainfra",
          "versions_count": 29,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": null,
          "first_published_at": null,
          "latest_published_at": "2026-07-22T11:17:00Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 5
        }
      ]
    },
    "popularity": {
      "forks": 0,
      "stars": 0,
      "watchers": 0,
      "fork_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_forks": 0
      },
      "star_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_stars": 0,
        "collected_at": null
      },
      "open_issues_and_prs": 3
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [
        "examples"
      ],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": true,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [],
      "toolchain_manifests": [
        "go.mod"
      ],
      "largest_source_bytes": 33958,
      "source_files_sampled": 255,
      "oversized_source_files": 0,
      "agent_instruction_files": [
        "internal/adopt/testdata/simple/CLAUDE.md"
      ],
      "agent_instruction_max_bytes": 26
    },
    "dependencies": {
      "manifests": [
        "go.mod",
        "package.json"
      ],
      "advisories": {
        "error": null,
        "scope": null,
        "source": null,
        "findings": [],
        "collected": false,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 0,
        "malicious_count": 0,
        "assessed_package": null,
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "go",
        "npm"
      ],
      "dependencies": [
        {
          "name": "gopkg.in/yaml.v3",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v3.0.1"
        },
        {
          "name": "github.com/BurntSushi/toml",
          "manifest": "go.mod",
          "ecosystem": "go",
          "version_constraint": "v1.6.0"
        }
      ],
      "all_dependencies": {
        "error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
        "source": null,
        "packages": [],
        "collected": false,
        "truncated": false,
        "total_count": null,
        "direct_count": null,
        "indirect_count": null
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 0,
        "merged_prs": 82,
        "open_issues": 3,
        "closed_ratio": 0,
        "closed_issues": 0,
        "closed_unmerged_prs": 1
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "MHilhorst",
          "commits": 308,
          "avatar_url": "https://avatars.githubusercontent.com/u/47367316?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "ci.yml",
        "release.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": false,
      "has_precommit_config": false
    },
    "security_signals": {
      "lockfiles": [
        "go.sum"
      ],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": 0,
            "reason": "branch protection not enabled on development/release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 7,
            "reason": "22 out of 28 merged PRs checked by a CI test -- score normalized to 7",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 0,
            "reason": "Found 0/30 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 0,
            "reason": "project has 0 contributing companies or organizations -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 0,
            "reason": "project was created within the last 90 days. Please review its contents carefully",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 0,
            "reason": "dependency not pinned by hash detected -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "SAST tool is not run on all commits -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 0,
            "reason": "security policy file not detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": 0,
            "reason": "Project has not signed or included provenance with any releases.",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 0,
            "reason": "detected GitHub workflow tokens with excessive permissions",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 10,
            "reason": "0 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "c38a2eeb5f26ec82759452e8e168783e27e2b651",
        "ran_at": "2026-07-27T16:03:24Z",
        "aggregate_score": 3.3,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": false,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-22T11:17:50Z",
      "oldest_open_prs": [],
      "last_merged_pr_at": "2026-07-22T11:17:01Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": [
        {
          "number": 41,
          "created_at": "2026-05-31T11:45:22Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 42,
          "created_at": "2026-05-31T11:45:36Z",
          "last_comment_at": null,
          "last_comment_author": null
        },
        {
          "number": 76,
          "created_at": "2026-07-17T07:35:22Z",
          "last_comment_at": null,
          "last_comment_author": null
        }
      ]
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/MHilhorst/ainfra",
    "host": "github.com",
    "name": "ainfra",
    "owner": "MHilhorst"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "at_risk",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 49,
      "inputs": {
        "security": 33,
        "vitality": 72,
        "community": 24,
        "governance": 44,
        "engineering": 63
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "good",
        "name": "Vitality",
        "value": 72,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "moderate",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 60,
            "inputs": {
              "commits_last_year": 308,
              "human_commit_share": 1,
              "days_since_last_push": 5,
              "active_weeks_last_year": 8
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 5 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "8/52 weeks with commits",
                "points": 5.5,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 8
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "308 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 308
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": null,
            "notes": [],
            "value": 90,
            "inputs": {
              "releases_count": 29,
              "latest_release_tag": "v0.2.27",
              "releases_from_tags": false,
              "days_since_latest_release": 5,
              "mean_days_between_releases": 0.6
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "29 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 29
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 5 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~0.6 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 0.6
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "critical",
        "name": "Community & Adoption",
        "value": 24,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 1,
            "inputs": {
              "forks": 0,
              "stars": 0,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "0 stars",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "0 forks",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "0 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "moderate",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": false,
              "has_issue_template": false,
              "has_code_of_conduct": false,
              "has_pull_request_template": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "recognized license (MIT)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "MIT"
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.3
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "at_risk",
        "name": "Sustainability & Governance",
        "value": 44,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 10,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 1,
              "top_contributor_share": 1
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "1 contributors",
                "points": 1.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "at_risk",
            "name": "Issue & PR responsiveness",
            "note": null,
            "notes": [],
            "value": 38,
            "inputs": {
              "merged_prs": 82,
              "open_issues": 3,
              "closed_issues": 0,
              "issue_closed_ratio": 0,
              "closed_unmerged_prs": 1
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "0% of issues closed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "issues_closed_share",
                    "params": {
                      "share": 0
                    }
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "82/83 decided PRs merged",
                "points": 37.8,
                "status": "partial",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 82,
                      "decided": 83
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "at_risk",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 44,
            "inputs": {
              "followers": 2,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "MHilhorst",
              "public_repos": 23,
              "account_age_days": 2728
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "personal (user) account",
                "points": 10,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_personal",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": "not applicable to user accounts",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_applicable_to_user_accounts",
                    "params": {}
                  }
                ],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "2 followers of MHilhorst",
                "points": 3.4,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 2,
                      "login": "MHilhorst"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "23 public repos, account ~7 yr old",
                "points": 22.1,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 23
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 7
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "github.com/MHilhorst/ainfra"
              ],
              "ecosystems": "go",
              "any_deprecated": false,
              "min_days_since_publish": 5
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on go",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "go"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 5 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 5
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "29 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 29
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "moderate",
        "name": "Engineering Quality",
        "value": 63,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "moderate",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 62,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": false,
              "has_precommit_config": false
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "2 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 2
                    }
                  }
                ],
                "max_points": 24
              },
              {
                "key": "tests_present",
                "name": "Tests present",
                "detail": null,
                "points": 24,
                "status": "met",
                "details": [],
                "max_points": 24
              },
              {
                "key": "linter_config",
                "name": "Linter config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "22 out of 28 merged PRs checked by a CI test -- score normalized to 7",
                "points": 14,
                "status": "partial",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "moderate",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 65,
            "inputs": {
              "topics": [],
              "has_wiki": true,
              "homepage": null,
              "has_readme": true,
              "has_docs_dir": true,
              "has_description": false
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 25,
                "status": "met",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "at_risk",
        "name": "Security",
        "value": 33,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "at_risk",
            "name": "Security posture",
            "note": null,
            "notes": [],
            "value": 33,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 18,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 0,
              "scorecard_aggregate": 3.3
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection not enabled on development/release branches",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "22 out of 28 merged PRs checked by a CI test -- score normalized to 7",
                "points": 1.8,
                "status": "partial",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 0/30 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is not run on all commits -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file not detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "Project has not signed or included provenance with any releases.",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "detected GitHub workflow tokens with excessive permissions",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "0 existing vulnerabilities detected",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              }
            ]
          },
          {
            "key": "high_risk_jurisdiction_exposure",
            "band": "excellent",
            "name": "High-Risk Jurisdiction Exposure",
            "note": "Only high-confidence self-published location evidence affects this multiplier. Ambiguous matches are review-only; country evidence is not proof of nationality, citizenship, legal registration, malicious intent, or sanctions status.",
            "notes": [
              {
                "code": "jurisdiction_evidence_limits",
                "params": {}
              }
            ],
            "value": 100,
            "inputs": {
              "meaning": "self-published location evidence; not nationality or citizenship",
              "red_flag": false,
              "exposures": [],
              "policy_countries": [
                "Russia",
                "Iran",
                "North Korea"
              ],
              "review_only_matches": 0,
              "assessed_self_published_locations": 2
            },
            "components": [
              {
                "key": "policy_exposure_multiplier",
                "name": "Policy exposure multiplier",
                "detail": "no confirmed policy-scope location match",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "jurisdiction_no_match",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "moderate",
        "name": "AI Readiness",
        "value": 64,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "moderate",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 58,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.86,
              "agent_instruction_files": [
                "internal/adopt/testdata/simple/CLAUDE.md"
              ],
              "agent_instruction_max_bytes": 26
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "internal/adopt/testdata/simple/CLAUDE.md (stub)",
                "points": 18,
                "status": "partial",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "internal/adopt/testdata/simple/CLAUDE.md"
                    }
                  },
                  {
                    "code": "agent_instructions_stub",
                    "params": {}
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "86 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 86,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "moderate",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 56,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [
                "go.sum"
              ],
              "has_dockerfile": false,
              "typed_language": true,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": false,
              "typecheck_configs": [],
              "agent_commit_share": 0,
              "toolchain_manifests": [
                "go.mod"
              ],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": "go.mod (toolchain convention, no task runner)",
                "points": 12.6,
                "status": "partial",
                "details": [
                  {
                    "code": "toolchain_convention",
                    "params": {
                      "files": "go.mod"
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "Go (statically typed)",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "statically_typed_language",
                    "params": {
                      "language": "Go"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": "lockfile",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "lockfile"
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "no agent-authored commits among the last 100",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_authored_commits",
                    "params": {
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "excellent",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "primary_language": "Go",
              "largest_source_bytes": 33958,
              "source_files_sampled": 255,
              "oversized_source_files": 0
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Go (statically typed)",
                "points": 45,
                "status": "met",
                "details": [
                  {
                    "code": "statically_typed_language",
                    "params": {
                      "language": "Go"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "0/255 source files over 60KB",
                "points": 55,
                "status": "met",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 255,
                      "oversized": 0
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          },
          {
            "key": "ai_interfaces",
            "band": "moderate",
            "name": "Machine-readable interfaces",
            "note": null,
            "notes": [],
            "value": 60,
            "inputs": {
              "example_dirs": [
                "examples"
              ],
              "has_mcp_signal": true,
              "api_schema_files": []
            },
            "components": [
              {
                "key": "api_schema_openapi_graphql_proto",
                "name": "API schema (OpenAPI/GraphQL/proto)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 40
              },
              {
                "key": "mcp_server",
                "name": "MCP server",
                "detail": null,
                "points": 20,
                "status": "met",
                "details": [],
                "max_points": 20
              },
              {
                "key": "runnable_examples",
                "name": "Runnable examples",
                "detail": "examples",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "examples"
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
      }
    ],
    "metrics_version": "1.13.0"
  },
  "warnings": [
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-27T16:03:41.860508Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/m/MHilhorst/ainfra.svg",
  "full_name": "MHilhorst/ainfra",
  "license_state": "standard",
  "license_spdx": "MIT"
}

Las puntuaciones son señales, no garantías. Reflejan prácticas públicamente visibles en GitHub; no son una auditoría de código ni una garantía de seguridad.

Los datos ausentes se excluyen y los pesos se renormalizan; nunca se puntúan como cero. La metodología es versionada y abierta: métricas v1.13.0, esquema v0.27.0 — metodología completa · wiki de métricas.

Cómo se sitúa un resultado dentro del registro general: estadísticas agregadasGo.