Öffentliches Register
Software-GesundheitsberichtSchema 0.27.0 · Metriken 1.13.0 · 2026-07-27 16:03 UTC

MHilhorst / ainfra

GoMIT★ 0 Sterne⑂ 0 Forksseit Mai 2026Auf GitHub ansehen ↗

MHilhorst/ainfra erreicht einen Gesundheitsindex von 49 von 100 und liegt damit im Bereich Gefährdet. Am stärksten schneidet es bei Vitality (72/100) ab, am schwächsten bei Community & Adoption (24/100). Zuletzt vor 5 Tagen aktualisiert. Ein einzelner Mitwirkender trägt den Großteil der jüngsten Arbeit.

49
gesamt / 100
Gefährdet

Software-Gesundheitsindex

Metriken werden auf einer Skala von 1–100 in gewichtete Kategorien gruppiert. Der Gesamtwert beginnt als ihr Mittel; sobald öffentliche Evidenz die Richtlinie für Hochrisikojurisdiktionen auslöst, wird die Bewertung angepasst und erhält die Obergrenze 49 (Gefährdet). AI Readiness liegt außerhalb.

49
Exzellent85-100Vorbildlich; erfüllt im Wesentlichen alle geprüften Kriterien
Gut70-84Gesund; geringfügige Lücken
Mittel50-69Akzeptabel mit deutlichen Lücken; Überprüfung empfohlen
Gefährdet30-49Erhebliche Schwächen; eine Übernahme erfordert Vorsicht
Kritisch1-29Schwerwiegende Probleme (aufgegeben, nur ein Maintainer, keine Hygiene)
VitalitätCommunity &VerbreitungNachhaltigkeit &GovernanceEngineering-QualitätSicherheitAI Readiness

Bewertungsprofil

Jede Achse ist eine Kategorie. Die Form zählt mehr als der Durchschnitt — ein gesundes Projekt füllt die gesamte Fläche, während ein Profil aus Spitzen und Kratern bedeutet, dass Stärke in einer Dimension Risiken in einer anderen verdeckt.

Eigentümerschaft

Michael HilhorstPersönliches Konto
2 Follower23 öffentliche Reposseit Feb. 2019

Dieses Repository gehört einem persönlichen Konto. Ein Projekt mit nur einem Eigentümer trägt ein höheres Kontinuitätsrisiko als ein organisationsgetragenes.

Paket-Ökosysteme

RegistryPaketVersionDownloads / MonatVersionenZuletzt veröffentlicht
Gogithub.com/MHilhorst/ainfrav0.2.27-29vor 5 Tagen

Metriken nach Kategorie

Vitalität

Lebt das Projekt — wird Code geschrieben und werden Releases ausgeliefert?

72Gut · 22 % des Gesamtindex
Wie die Bewertung erfolgt
36/36Push-Aktualität — letzter Push vor 5 Tagen
5.5/36Commit-Rhythmus — 8/52 Wochen mit Commits
18/18Commit-Volumen — 308 Commits im letzten Jahr
0/10OpenSSF Scorecard: Maintained — project was created within the last 90 days. Please review its contents carefully
Verwendete Eingangsdaten
commits_last_year308
human_commit_share1
days_since_last_push5
active_weeks_last_year8
Wie die Bewertung erfolgt
27/27Liefert Releases aus — 29 Releases veröffentlicht
36/36Release-Aktualität — letztes Release vor 5 Tagen
27/27Release-Rhythmus — ein Release etwa alle 0,6 Tage
0/10OpenSSF Scorecard: Signed-Releases — Project has not signed or included provenance with any releases.
Verwendete Eingangsdaten
releases_count29
latest_release_tagv0.2.27
releases_from_tagsnein
days_since_latest_release5
mean_days_between_releases0,6

Community & Verbreitung

Hat das Projekt Nutzer, Downloads, Aufmerksamkeit und ein einladendes Umfeld für Beitragende?

24Kritisch · 18 % des Gesamtindex
Wie die Bewertung erfolgt
0/60Stars — 0 Stars
0/25Forks — 0 Forks
0/15Watcher — 0 Watcher
Verwendete Eingangsdaten
forks0
stars0
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Wie die Bewertung erfolgt
22.5/22.5README
22.5/22.5Lizenz — anerkannte Lizenz (MIT)
0/18CONTRIBUTING-Leitfaden
0/13.5Verhaltenskodex
0/7.2Issue-Vorlage
0/6.3PR-Vorlage
Verwendete Eingangsdaten
has_readmeja
has_licenseja
has_contributingnein
has_issue_templatenein
has_code_of_conductnein
has_pull_request_templatenein

Nachhaltigkeit & Governance

Überdauert das Projekt die Menschen, die es tragen — Bus-Faktor, Reaktionsfähigkeit, Trägerschaft und Paketpflege?

44Gefährdet · 24 % des Gesamtindex
Wie die Bewertung erfolgt
9/54Bus-Faktor — 1 Beitragende decken die Hälfte aller Commits ab
0/22.5Commit-Verteilung — wichtigste beitragende Person verfasste 100 % der Commits
1.4/13.5Breite der Beitragenden — 1 Beitragende
0/10OpenSSF Scorecard: Contributors — project has 0 contributing companies or organizations -- score normalized to 0
Verwendete Eingangsdaten
bus_factor1
contributors_sampled1
top_contributor_share1
Wie die Bewertung erfolgt
0/46.8Issue-Lösungsquote — 0 % der Issues geschlossen
37.8/38.3PR-Annahme — 82/83 entschiedene PRs gemergt
0/15OpenSSF Scorecard: Code-Review — Found 0/30 approved changesets -- score normalized to 0
Verwendete Eingangsdaten
merged_prs82
open_issues3
closed_issues0
issue_closed_ratio0
closed_unmerged_prs1
Wie die Bewertung erfolgt
10/30Organisatorische Trägerschaft — persönliches (Nutzer-)Konto
0/20Verifizierte Domain — für Nutzerkonten nicht anwendbar
3.4/25Reichweite des Inhabers — 2 Follower von MHilhorst
22.1/25Kontohistorie — 23 öffentliche Repos, Kontoalter ca. 7 Jahre
Verwendete Eingangsdaten
followers2
owner_typeUser
is_verified
owner_loginMHilhorst
public_repos23
account_age_days2.728
Von der Bewertung ausgeschlossen (keine Daten oder nicht anwendbar): Verifizierte Domain. Die verbleibenden Gewichte wurden renormalisiert.

Paketpflege

100Exzellent
Wie die Bewertung erfolgt
25/25Veröffentlicht & auflösbar — 1 Paket(e) auf go
35/35Veröffentlichungsaktualität — letzte Veröffentlichung vor 5 Tagen
20/20Versionshistorie — 29 veröffentlichte Versionen
20/20Nicht veraltet — aktiv, nicht veraltet oder zurückgezogen
Verwendete Eingangsdaten
packagesgithub.com/MHilhorst/ainfra
ecosystemsgo
any_deprecatednein
min_days_since_publish5

Engineering-Qualität

Sind grundlegende Engineering- und Dokumentationspraktiken vorhanden?

63Mittel · 20 % des Gesamtindex
Wie die Bewertung erfolgt
24/24CI-Workflows — 2 Workflow(s)
24/24Tests vorhanden
0/16Linter-Konfiguration
0/9.6Pre-Commit-Hooks
0/6.4.editorconfig
14/20OpenSSF Scorecard: CI-Tests — 22 out of 28 merged PRs checked by a CI test -- score normalized to 7
Verwendete Eingangsdaten
has_cija
has_testsja
has_editorconfignein
has_linter_confignein
has_precommit_confignein
Wie die Bewertung erfolgt
30/30README
25/25Dokumentationsverzeichnis
0/15Dokumentations-/Homepage-Site
0/10Repository-Beschreibung
0/10Topics
10/10Wiki
Verwendete Eingangsdaten
topics
has_wikija
homepage
has_readmeja
has_docs_dirja
has_descriptionnein

Sicherheit

Sind die sichtbaren Sicherheits- und Lieferkettenpraktiken belastbar, ohne ungeklärte Exposition gegenüber Hochrisikojurisdiktionen?

33Gefährdet · 16 % des Gesamtindex

Sicherheitslage

33Gefährdet
Wie die Bewertung erfolgt
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.5Lizenz — 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
Verwendete Eingangsdaten
sourceopenssf_scorecard
checks_evaluated18
scorecard_versionv5.5.0
checks_inconclusive0
scorecard_aggregate3,3

AI Readiness

Wie gut ist das Repository dafür ausgestattet, mit KI-Coding-Agenten entwickelt und gepflegt zu werden? Ein unabhängiges, experimentelles Badge — Gewicht 0,0, es wird eigenständig ausgewiesen und verändert den Gesamt-Gesundheitswert nicht.

64Mittel · 0 % des Gesamtindex
Wie die Bewertung erfolgt
18/45Agentenanweisungen — internal/adopt/testdata/simple/CLAUDE.md (Rumpf)
0/15Maschinenlesbare Doku (llms.txt)
40/40Lesbare Commit-Historie — 86 von 100 menschlichen Commits benennen ihre Absicht (strukturierter Betreff oder erläuternder Text)
Verwendete Eingangsdaten
has_llms_txtnein
legible_history_share0,86
agent_instruction_filesinternal/adopt/testdata/simple/CLAUDE.md
agent_instruction_max_bytes26
Wie die Bewertung erfolgt
12.6/18Bootstrap mit einem Befehl — go.mod (Toolchain-Konvention, kein Task-Runner)
22/22Automatisierte Tests
0/11Lint-/Format-Konfiguration
11/11Statische Typprüfung — Go (statisch typisiert)
10/10Reproduzierbare Umgebung — lockfile
0/10Belegte Agentenpraxis — keine von Agenten verfassten Commits unter den letzten 100
0/8Automatisierte Wartung — keine automatisierten Abhängigkeits-Updates beobachtet
0/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
Verwendete Eingangsdaten
has_nixnein
has_testsja
lockfilesgo.sum
has_dockerfilenein
typed_languageja
bootstrap_files
has_devcontainernein
has_linter_confignein
typecheck_configs
agent_commit_share0
toolchain_manifestsgo.mod
dependency_bot_commit_share0
Wie die Bewertung erfolgt
45/45Typprüfbarer Code — Go (statisch typisiert)
55/55Handhabbare Dateigrößen — 0/255 Quelldateien über 60 KB
Verwendete Eingangsdaten
primary_languageGo
largest_source_bytes33.958
source_files_sampled255
oversized_source_files0
Wie die Bewertung erfolgt
0/40API-Schema (OpenAPI/GraphQL/proto)
20/20MCP-Server
40/40Lauffähige Beispiele — examples
Verwendete Eingangsdaten
example_dirsexamples
has_mcp_signalja
api_schema_files

Eckdaten

0GitHub-Sterne
1Mitwirkende
308Commits, letzte 12 Monate
5Tage seit letztem Push
29Releases
1Bus-Faktor
3offene Issues
Go, npmPaket-Ökosysteme

Warnungen zur Datenerhebung

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

Weitere Details

OpenSSF Scorecard 3.3 / 10
3.3Gesamtwert

Unabhängige, werkzeugneutrale Sicherheitsbewertung durch das quelloffene OpenSSF Scorecard. Jede Prüfung honoriert eine Sicherheits-Praxis, nicht das Werkzeug eines bestimmten Anbieters. Prüfungen, die Scorecard nicht ermitteln konnte, sind mit k. A. markiert und vom Sicherheitswert ausgeschlossen (nie als null gezählt).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
Direkte Abhängigkeiten 2
RegistryPaketVersionsvorgabeManifest
Gogopkg.in/yaml.v3v3.0.1go.mod
Gogithub.com/BurntSushi/tomlv1.6.0go.mod
Alle Abhängigkeiten nicht erhoben

Der aufgelöste Abhängigkeitssatz konnte für diesen Bericht nicht erhoben werden: GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

JSON-Rohbericht maschinenlesbar
{
  "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"
}

Bewertungen sind Signale, keine Garantien. Sie spiegeln öffentlich sichtbare Praxis auf GitHub wider — kein Code-Audit und keine Sicherheitsgarantie.

Fehlende Daten werden ausgeschlossen und die Gewichte neu normiert, nie als null bewertet. Die Methodik ist versioniert und offen: Metriken v1.13.0, Schema v0.27.0 — vollständige Methodik · Metriken-Wiki.

Wie ein einzelnes Ergebnis im Gesamtregister steht: aggregierte StatistikenGo.