公开记录
软件健康报告模式 0.27.0 · 指标 1.13.0 · 2026-07-27 16:03 UTC

MHilhorst / ainfra

GoMIT★ 0 星标⑂ 0 复刻始于 2026年5月在 GitHub 上查看 ↗

MHilhorst/ainfra 的健康指数为 100 分中的 49 分,处于「存在风险」区间。 其得分最高的类别是Vitality(72/100),最低的是Community & Adoption(24/100)。 最近一次更新在 5 天前。 近期的大部分工作由 1 位贡献者完成。

49
总分 / 100
存在风险

软件健康指数

指标归入加权类别,统一采用 1–100 量表。总体分先取类别加权平均;当公开证据触发高风险司法辖区政策时,评级会按政策调整,并设置 49(有风险)的上限。AI 就绪度不计入总体分。

49
优秀85-100堪称典范;基本满足所有检验标准
良好70-84健康;仅有轻微不足
中等50-69可接受,但存在明显不足;建议进行审查
存在风险30-49存在重大薄弱环节;采用时应保持审慎
危急1-29问题严重(项目被弃置、仅有单一维护者、缺乏基本工程规范)
活力社区与采用可持续性与治理工程质量安全AI 就绪度

评分画像

每条轴代表一个类别。形状比平均值更重要——健康的对象会填满整个图形,而“一峰一谷”式画像意味着某一维度的优势正掩盖另一维度的风险。

所有权

Michael Hilhorst个人账户
2 关注者23 个公开仓库始于 2019年2月

该仓库由个人账户拥有。相较于组织支持的项目,单一所有者项目的延续性风险更高。

软件包生态系统

注册表软件包版本月下载量版本数最近发布
Gogithub.com/MHilhorst/ainfrav0.2.27-295 天前

按类别列示的指标

活力

项目是否仍有生命——是否仍在编写代码,是否仍在发布版本?

72良好 · 占总体的 22%
评分方式
36/36推送新近度 — 最近一次推送于 5 天前
5.5/36提交节奏 — 52 周中有 8 周有提交
18/18提交量 — 最近一年 308 次提交
0/10OpenSSF Scorecard:Maintained — project was created within the last 90 days. Please review its contents carefully
所用输入
commits_last_year308
human_commit_share1
days_since_last_push5
active_weeks_last_year8

发布纪律

90优秀
评分方式
27/27有发布版本 — 已发布 29 个发布版本
36/36发布时效 — 最近一次发布版本于 5 天前
27/27发布节奏 — 约每 0.6 天发布一次
0/10OpenSSF Scorecard:Signed-Releases — Project has not signed or included provenance with any releases.
所用输入
releases_count29
latest_release_tagv0.2.27
releases_from_tags
days_since_latest_release5
mean_days_between_releases0.6

社区与采用

项目是否拥有用户、下载量与关注度,并具备欢迎贡献者参与的配置?

24危急 · 占总体的 18%
评分方式
0/60星标 — 0 个星标
0/25复刻 — 0 个复刻
0/15关注者 — 0 位关注者
所用输入
forks0
stars0
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history

社区健康

50中等
评分方式
22.5/22.5README
22.5/22.5许可证 — 可识别的许可证(MIT)
0/18CONTRIBUTING 指南
0/13.5行为准则
0/7.2议题模板
0/6.3PR 模板
所用输入
has_readme
has_license
has_contributing
has_issue_template
has_code_of_conduct
has_pull_request_template

可持续性与治理

项目能否在其成员之外延续——巴士系数、响应能力、由谁支持,以及软件包的维护状况?

44存在风险 · 占总体的 24%
评分方式
9/54巴士系数 — 1 位贡献者贡献了半数提交
0/22.5提交分布 — 头号贡献者编写了 100% 的提交
1.4/13.5贡献者广度 — 1 位贡献者
0/10OpenSSF Scorecard:Contributors — project has 0 contributing companies or organizations -- score normalized to 0
所用输入
bus_factor1
contributors_sampled1
top_contributor_share1
评分方式
0/46.8议题解决 — 0% 的议题已关闭
37.8/38.3PR 接受 — 已裁定的 PR 中 82/83 已合并
0/15OpenSSF Scorecard:Code-Review — Found 0/30 approved changesets -- score normalized to 0
所用输入
merged_prs82
open_issues3
closed_issues0
issue_closed_ratio0
closed_unmerged_prs1
评分方式
10/30所有权背书 — 个人(用户)账户
0/20已验证域名 — 不适用于个人账户
3.4/25所有者影响力 — MHilhorst 有 2 位关注者
22.1/25既往记录 — 23 个公开仓库,账户约 7 年
所用输入
followers2
owner_typeUser
is_verified
owner_loginMHilhorst
public_repos23
account_age_days2,728
已排除计分(无数据或不适用):已验证域名。 其余权重已重新归一化。
评分方式
25/25已发布且可解析 — go 上有 1 个软件包
35/35发布时效 — 最近一次发布于 5 天前
20/20版本历史 — 29 个已发布版本
20/20未被弃用 — 活跃,未被弃用或撤回
所用输入
packagesgithub.com/MHilhorst/ainfra
ecosystemsgo
any_deprecated
min_days_since_publish5

工程质量

基础的工程与文档实践是否到位?

63中等 · 占总体的 20%

工程实践

62中等
评分方式
24/24CI 工作流 — 2 个工作流
24/24存在测试
0/16Linter 配置
0/9.6Pre-commit 钩子
0/6.4.editorconfig
14/20OpenSSF Scorecard:CI-Tests — 22 out of 28 merged PRs checked by a CI test -- score normalized to 7
所用输入
has_ci
has_tests
has_editorconfig
has_linter_config
has_precommit_config

文档

65中等
评分方式
30/30README
25/25文档目录
0/15文档 / 主页站点
0/10仓库描述
0/10主题标签
10/10Wiki
所用输入
topics
has_wiki
homepage
has_readme
has_docs_dir
has_description

安全

可见的安全与供应链实践是否稳固,且不存在未解决的高风险司法辖区暴露?

33存在风险 · 占总体的 16%

安全态势

33存在风险
评分方式
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.5许可证 — 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
所用输入
sourceopenssf_scorecard
checks_evaluated18
scorecard_versionv5.5.0
checks_inconclusive0
scorecard_aggregate3.3

AI 就绪度

该仓库在多大程度上具备与 AI 编码代理协同开发与维护的条件?这是一枚独立的实验性徽章——权重为 0.0,因此单独呈现,不影响总体健康评分。

64中等 · 占总体的 0%
评分方式
18/45代理指令 — internal/adopt/testdata/simple/CLAUDE.md(占位文件)
0/15机器可读文档(llms.txt)
40/40可读的提交历史 — 100 次人类提交中有 86 次说明了意图(结构化标题或解释性正文)
所用输入
has_llms_txt
legible_history_share0.86
agent_instruction_filesinternal/adopt/testdata/simple/CLAUDE.md
agent_instruction_max_bytes26
评分方式
12.6/18一条命令的引导启动 — go.mod(工具链约定,无任务运行器)
22/22自动化测试
0/11Lint / 格式化配置
11/11静态类型检查 — Go(静态类型)
10/10可复现环境 — lockfile
0/10已体现的代理实践 — 最近 100 次提交中没有代理编写的提交
0/8自动化维护 — 未观察到自动依赖更新
0/10OpenSSF Scorecard:Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 0
所用输入
has_nix
has_tests
lockfilesgo.sum
has_dockerfile
typed_language
bootstrap_files
has_devcontainer
has_linter_config
typecheck_configs
agent_commit_share0
toolchain_manifestsgo.mod
dependency_bot_commit_share0
评分方式
45/45可类型检查的代码 — Go(静态类型)
55/55可控的文件大小 — 采样的 255 个源文件中有 0 个超过 60KB
所用输入
primary_languageGo
largest_source_bytes33,958
source_files_sampled255
oversized_source_files0
评分方式
0/40API 模式(OpenAPI/GraphQL/proto)
20/20MCP 服务器
40/40可运行示例 — examples
所用输入
example_dirsexamples
has_mcp_signal
api_schema_files

关键数据

0GitHub 星标
1贡献者
308最近 12 个月提交数
5距最近推送天数
29发布版本数
1巴士系数(bus factor)
3开放议题
Go, npm软件包生态系统数

数据采集警告

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

更多细节

OpenSSF Scorecard 3.3 / 10
3.3综合

来自开源项目 OpenSSF Scorecard 的独立、工具无关的安全评估。每项检查奖励的是安全实践本身,而非特定供应商的工具。Scorecard 无法判定的检查项标记为 不适用,并从安全评分中剔除(绝不按零分计)。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
直接依赖 2
注册表软件包版本约束清单文件
Gogopkg.in/yaml.v3v3.0.1go.mod
Gogithub.com/BurntSushi/tomlv1.6.0go.mod
全部依赖 未采集

本报告未能采集到解析后的依赖集合:GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

原始 JSON 报告 机器可读
{
  "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"
}

评分是信号,而非担保。 评分反映的是 GitHub 上公开可见的实践——不是代码审计,也不是安全保证。

缺失数据将被剔除并重新归一化权重,绝不按零分计。方法论已版本化并公开:指标 v1.13.0、模式 v0.27.0—— 完整方法论 · 指标知识库.

单项结果在整体记录中的位置: 汇总统计Go.