@event4u/agent-config 8.13.0 → 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +136 -0
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/agent-handoff.md +35 -0
  5. package/dist/agent-src/commands/agents/init.md +1 -0
  6. package/dist/agent-src/commands/agents/optimize.md +1 -0
  7. package/dist/agent-src/commands/agents/user/accept.md +3 -2
  8. package/dist/agent-src/commands/agents/user/init.md +5 -4
  9. package/dist/agent-src/commands/agents/user/update.md +3 -2
  10. package/dist/agent-src/commands/agents/user.md +1 -0
  11. package/dist/agent-src/commands/agents.md +1 -0
  12. package/dist/agent-src/commands/analytics/show.md +1 -0
  13. package/dist/agent-src/commands/analytics.md +1 -0
  14. package/dist/agent-src/commands/analyze/decision.md +1 -0
  15. package/dist/agent-src/commands/analyze/incident.md +1 -0
  16. package/dist/agent-src/commands/analyze/near-miss.md +1 -0
  17. package/dist/agent-src/commands/analyze/postmortem.md +1 -0
  18. package/dist/agent-src/commands/analyze/premortem.md +1 -0
  19. package/dist/agent-src/commands/analyze/reference-repo.md +1 -0
  20. package/dist/agent-src/commands/analyze.md +3 -2
  21. package/dist/agent-src/commands/brand/identity.md +6 -5
  22. package/dist/agent-src/commands/brand/review.md +6 -5
  23. package/dist/agent-src/commands/brand/strategy.md +6 -4
  24. package/dist/agent-src/commands/brand/tokens.md +7 -6
  25. package/dist/agent-src/commands/brand/voice.md +5 -4
  26. package/dist/agent-src/commands/brand.md +9 -7
  27. package/dist/agent-src/commands/bug/fix.md +1 -0
  28. package/dist/agent-src/commands/bug/investigate.md +1 -0
  29. package/dist/agent-src/commands/bug.md +2 -1
  30. package/dist/agent-src/commands/challenge-me/vision.md +1 -0
  31. package/dist/agent-src/commands/challenge-me/with-docs.md +1 -0
  32. package/dist/agent-src/commands/challenge-me.md +28 -6
  33. package/dist/agent-src/commands/chat-history.md +1 -0
  34. package/dist/agent-src/commands/check-current-md.md +1 -0
  35. package/dist/agent-src/commands/commit.md +1 -0
  36. package/dist/agent-src/commands/context/create.md +3 -2
  37. package/dist/agent-src/commands/context.md +1 -0
  38. package/dist/agent-src/commands/cost/profile.md +1 -0
  39. package/dist/agent-src/commands/cost.md +2 -1
  40. package/dist/agent-src/commands/council/analysis.md +1 -0
  41. package/dist/agent-src/commands/council/debate.md +1 -0
  42. package/dist/agent-src/commands/council/default.md +21 -20
  43. package/dist/agent-src/commands/council/design.md +1 -0
  44. package/dist/agent-src/commands/council/optimize.md +1 -0
  45. package/dist/agent-src/commands/council/pr.md +1 -0
  46. package/dist/agent-src/commands/council.md +1 -0
  47. package/dist/agent-src/commands/estimate-ticket.md +1 -0
  48. package/dist/agent-src/commands/feature/dev.md +1 -0
  49. package/dist/agent-src/commands/feature/explore.md +1 -0
  50. package/dist/agent-src/commands/feature/plan.md +1 -0
  51. package/dist/agent-src/commands/feature/refactor.md +1 -0
  52. package/dist/agent-src/commands/feature/roadmap.md +1 -0
  53. package/dist/agent-src/commands/feature.md +2 -1
  54. package/dist/agent-src/commands/fix/pr-comments.md +19 -0
  55. package/dist/agent-src/commands/fix.md +2 -1
  56. package/dist/agent-src/commands/ghostwriter/delete.md +1 -0
  57. package/dist/agent-src/commands/ghostwriter/fetch.md +1 -0
  58. package/dist/agent-src/commands/ghostwriter/show.md +1 -0
  59. package/dist/agent-src/commands/ghostwriter/write.md +1 -0
  60. package/dist/agent-src/commands/ghostwriter.md +1 -0
  61. package/dist/agent-src/commands/grill-me.md +17 -12
  62. package/dist/agent-src/commands/humanize.md +1 -0
  63. package/dist/agent-src/commands/image/analyse.md +2 -1
  64. package/dist/agent-src/commands/image/create.md +1 -0
  65. package/dist/agent-src/commands/image/verify.md +1 -0
  66. package/dist/agent-src/commands/image.md +16 -12
  67. package/dist/agent-src/commands/implement-ticket.md +1 -0
  68. package/dist/agent-src/commands/judge/on-diff.md +1 -0
  69. package/dist/agent-src/commands/judge/solo.md +1 -0
  70. package/dist/agent-src/commands/judge/steps.md +1 -0
  71. package/dist/agent-src/commands/judge.md +1 -0
  72. package/dist/agent-src/commands/knowledge/cross-repo.md +1 -0
  73. package/dist/agent-src/commands/knowledge/forget.md +1 -0
  74. package/dist/agent-src/commands/knowledge/ingest.md +1 -0
  75. package/dist/agent-src/commands/knowledge/list.md +10 -0
  76. package/dist/agent-src/commands/knowledge.md +1 -0
  77. package/dist/agent-src/commands/memory/learn-low-impact.md +1 -0
  78. package/dist/agent-src/commands/memory/mine-session.md +1 -0
  79. package/dist/agent-src/commands/memory/promote.md +3 -2
  80. package/dist/agent-src/commands/memory.md +1 -0
  81. package/dist/agent-src/commands/mission/upgrade.md +45 -24
  82. package/dist/agent-src/commands/mode.md +1 -0
  83. package/dist/agent-src/commands/module/create.md +1 -0
  84. package/dist/agent-src/commands/module/explore.md +1 -0
  85. package/dist/agent-src/commands/module.md +1 -0
  86. package/dist/agent-src/commands/optimize/agents-dir.md +1 -0
  87. package/dist/agent-src/commands/optimize/project.md +1 -0
  88. package/dist/agent-src/commands/optimize/prompt.md +1 -0
  89. package/dist/agent-src/commands/optimize.md +2 -1
  90. package/dist/agent-src/commands/orchestrate.md +1 -0
  91. package/dist/agent-src/commands/override.md +1 -0
  92. package/dist/agent-src/commands/package.md +2 -1
  93. package/dist/agent-src/commands/post-as/ghostwriter.md +1 -0
  94. package/dist/agent-src/commands/post-as/me.md +1 -0
  95. package/dist/agent-src/commands/post-as.md +1 -0
  96. package/dist/agent-src/commands/pr/create/description-only.md +1 -0
  97. package/dist/agent-src/commands/pr/create.md +19 -17
  98. package/dist/agent-src/commands/prediction-pool.md +7 -6
  99. package/dist/agent-src/commands/prepare-for-review.md +1 -0
  100. package/dist/agent-src/commands/profile/activate.md +1 -0
  101. package/dist/agent-src/commands/profile/deactivate.md +1 -0
  102. package/dist/agent-src/commands/profile.md +1 -0
  103. package/dist/agent-src/commands/project.md +2 -1
  104. package/dist/agent-src/commands/refine-ticket.md +7 -0
  105. package/dist/agent-src/commands/research/deep.md +1 -0
  106. package/dist/agent-src/commands/research.md +1 -0
  107. package/dist/agent-src/commands/review/routing.md +1 -0
  108. package/dist/agent-src/commands/review.md +2 -1
  109. package/dist/agent-src/commands/roadmap/ai-council.md +1 -0
  110. package/dist/agent-src/commands/roadmap/materialize.md +1 -0
  111. package/dist/agent-src/commands/roadmap/process-full.md +1 -0
  112. package/dist/agent-src/commands/roadmap/process-phase.md +1 -0
  113. package/dist/agent-src/commands/roadmap/process-step.md +1 -0
  114. package/dist/agent-src/commands/roadmap.md +1 -0
  115. package/dist/agent-src/commands/security-audit-config.md +2 -1
  116. package/dist/agent-src/commands/skill/preview.md +1 -0
  117. package/dist/agent-src/commands/skill.md +1 -0
  118. package/dist/agent-src/commands/skills/discover.md +1 -0
  119. package/dist/agent-src/commands/skills.md +1 -0
  120. package/dist/agent-src/commands/sync/agent-settings.md +1 -0
  121. package/dist/agent-src/commands/sync.md +2 -1
  122. package/dist/agent-src/commands/team/adversarial.md +3 -2
  123. package/dist/agent-src/commands/team/delegate.md +3 -2
  124. package/dist/agent-src/commands/team/review.md +3 -2
  125. package/dist/agent-src/commands/team/status.md +49 -18
  126. package/dist/agent-src/commands/team-knowledge.md +8 -6
  127. package/dist/agent-src/commands/team.md +21 -16
  128. package/dist/agent-src/commands/tests/e2e-heal.md +1 -0
  129. package/dist/agent-src/commands/tests/e2e-plan.md +1 -0
  130. package/dist/agent-src/commands/tests/execute.md +1 -0
  131. package/dist/agent-src/commands/tests.md +2 -1
  132. package/dist/agent-src/commands/update-form-request-messages.md +1 -0
  133. package/dist/agent-src/commands/upstream-contribute.md +1 -0
  134. package/dist/agent-src/commands/video/from-script.md +3 -2
  135. package/dist/agent-src/commands/video/from-song.md +22 -21
  136. package/dist/agent-src/commands/video/scene.md +1 -0
  137. package/dist/agent-src/commands/video/stitch.md +2 -1
  138. package/dist/agent-src/commands/video/storyboard.md +1 -0
  139. package/dist/agent-src/commands/video.md +1 -0
  140. package/dist/agent-src/commands/work.md +1 -0
  141. package/dist/agent-src/commands/worktree/cleanup.md +8 -7
  142. package/dist/agent-src/commands/worktree/create.md +1 -0
  143. package/dist/agent-src/commands/worktree/verify.md +1 -0
  144. package/dist/agent-src/commands/worktree.md +1 -0
  145. package/dist/agent-src/rules/active-remediation.md +12 -36
  146. package/dist/agent-src/rules/artifact-drafting-protocol.md +7 -64
  147. package/dist/agent-src/rules/context-hygiene.md +19 -113
  148. package/dist/agent-src/rules/design-fidelity.md +4 -60
  149. package/dist/agent-src/rules/domain-adoption-policy.md +10 -106
  150. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +12 -60
  151. package/dist/agent-src/rules/improve-before-implement.md +4 -0
  152. package/dist/agent-src/rules/minimal-safe-diff.md +6 -86
  153. package/dist/agent-src/rules/size-enforcement.md +10 -0
  154. package/dist/agent-src/rules/tool-safety.md +17 -3
  155. package/dist/agent-src/skills/agent-security-review/SKILL.md +12 -1
  156. package/dist/agent-src/skills/ai-council/SKILL.md +11 -0
  157. package/dist/agent-src/skills/bug-analyzer/SKILL.md +24 -2
  158. package/dist/agent-src/skills/code-review/SKILL.md +105 -93
  159. package/dist/agent-src/skills/code-review/checklists/backend.md +50 -0
  160. package/dist/agent-src/skills/code-review/checklists/database.md +19 -0
  161. package/dist/agent-src/skills/code-review/checklists/dependency.md +18 -0
  162. package/dist/agent-src/skills/code-review/checklists/docs.md +14 -0
  163. package/dist/agent-src/skills/code-review/checklists/frontend.md +19 -0
  164. package/dist/agent-src/skills/code-review/checklists/infra.md +17 -0
  165. package/dist/agent-src/skills/code-review/checklists/producing-the-review.md +62 -0
  166. package/dist/agent-src/skills/code-review/checklists/test-only.md +13 -0
  167. package/dist/agent-src/skills/code-review/evals/evals.json +53 -15
  168. package/dist/agent-src/skills/condense-memory/SKILL.md +3 -2
  169. package/dist/agent-src/skills/conventional-commits-writing/evals/evals.json +15 -0
  170. package/dist/agent-src/skills/docker/SKILL.md +10 -0
  171. package/dist/agent-src/skills/docx-authoring/SKILL.md +102 -0
  172. package/dist/agent-src/skills/github-ci/evals/evals.json +25 -0
  173. package/dist/agent-src/skills/humanizer/SKILL.md +79 -3
  174. package/dist/agent-src/skills/humanizer/references/anti-aiisms.md +112 -0
  175. package/dist/agent-src/skills/humanizer/references/fixtures.md +70 -0
  176. package/dist/agent-src/skills/judge-bug-hunter/SKILL.md +7 -1
  177. package/dist/agent-src/skills/laravel/SKILL.md +10 -0
  178. package/dist/agent-src/skills/launch-readiness/SKILL.md +35 -0
  179. package/dist/agent-src/skills/merge-conflicts/SKILL.md +30 -0
  180. package/dist/agent-src/skills/pdf-tools/SKILL.md +101 -0
  181. package/dist/agent-src/skills/quality-tools/evals/evals.json +15 -0
  182. package/dist/agent-src/skills/security-audit/SKILL.md +29 -0
  183. package/dist/agent-src/skills/security-maturity-assessment/SKILL.md +90 -0
  184. package/dist/agent-src/skills/security-maturity-assessment/evals/triggers.json +52 -0
  185. package/dist/agent-src/skills/skill-writing/SKILL.md +79 -0
  186. package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +11 -3
  187. package/dist/agent-src/skills/subagent-orchestration/schemas/team-review-status.json +103 -0
  188. package/dist/agent-src/skills/terraform/SKILL.md +10 -0
  189. package/dist/agent-src/skills/test-driven-development/SKILL.md +31 -0
  190. package/dist/agent-src/skills/threat-modeling/SKILL.md +15 -0
  191. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  192. package/dist/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  193. package/dist/agent-src/templates/scripts/telemetry/engagement.ts +30 -0
  194. package/dist/agent-src/templates/scripts/telemetry/report_renderer.ts +22 -9
  195. package/dist/agent-src/templates/scripts/telemetry_record.ts +5 -0
  196. package/dist/discovery/deprecation-report.md +1 -1
  197. package/dist/discovery/discovery-manifest.json +256 -190
  198. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  199. package/dist/discovery/discovery-manifest.summary.md +5 -5
  200. package/dist/discovery/orphan-report.md +1 -1
  201. package/dist/discovery/packs.json +20 -17
  202. package/dist/discovery/trust-report.md +3 -3
  203. package/dist/discovery/workspaces.json +16 -13
  204. package/dist/install/install.mjs +10 -2
  205. package/dist/mcp/registry-manifest.json +3 -3
  206. package/dist/router.json +1 -1
  207. package/dist/scripts/condense.js +5 -0
  208. package/dist/scripts/condense.js.map +1 -1
  209. package/dist/server/schemas/settings.js +5 -1
  210. package/dist/server/schemas/settings.js.map +1 -1
  211. package/docs/CLAIMS.md +8 -1
  212. package/docs/SKILL_CENSUS.md +8 -0
  213. package/docs/architecture.md +2 -2
  214. package/docs/benchmark.md +67 -2
  215. package/docs/catalog.md +2 -2
  216. package/docs/contracts/ai-council-config.md +9 -2
  217. package/docs/contracts/ai-team-config.md +37 -4
  218. package/docs/contracts/prelaunch-diagnostics.md +151 -0
  219. package/docs/contracts/release-pr-gating.md +1 -0
  220. package/docs/decisions/ADR-049-configuration-trust-boundary.md +9 -1
  221. package/docs/featured-skills.md +2 -2
  222. package/docs/getting-started-by-role.md +1 -1
  223. package/docs/getting-started.md +16 -5
  224. package/docs/governance-advantage.md +1 -1
  225. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +45 -0
  226. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +28 -0
  227. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +76 -0
  228. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +90 -0
  229. package/docs/guidelines/agent-infra/domain-adoption-gates.md +112 -0
  230. package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +66 -0
  231. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +99 -0
  232. package/docs/guidelines/agent-infra/skill-quality-checklist.md +13 -0
  233. package/docs/guidelines/design-fidelity-mechanics.md +76 -0
  234. package/docs/parity/command-frontmatter.md +60 -0
  235. package/docs/proof.md +6 -5
  236. package/package.json +1 -1
  237. package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  238. package/src/agent-src/templates/scripts/telemetry/engagement.ts +30 -0
  239. package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +22 -9
  240. package/src/agent-src/templates/scripts/telemetry_record.ts +5 -0
  241. package/src/config/agent-settings.template.yml +34 -10
  242. package/src/config/prelaunch-areas.yml +36 -0
  243. package/src/scripts/_cli/cmd_doctor.ts +118 -26
  244. package/src/scripts/_lib/claude_plugin.ts +77 -2
  245. package/src/scripts/_lib/orchestration_record.ts +5 -0
  246. package/src/scripts/_lib/tool_adapter_registry.ts +120 -0
  247. package/src/scripts/ai_team/config.ts +73 -1
  248. package/src/scripts/ai_team/review_gate.ts +636 -0
  249. package/src/scripts/ai_team/team_dispatch.ts +612 -0
  250. package/src/scripts/bench_ab_task_runner.ts +11 -1
  251. package/src/scripts/bench_parity_count.ts +279 -0
  252. package/src/scripts/build_linear_digest.ts +3 -1
  253. package/src/scripts/check_bridge_derivation.ts +172 -0
  254. package/src/scripts/check_consumer_scope_flip.ts +179 -0
  255. package/src/scripts/check_council_config_location.ts +115 -15
  256. package/src/scripts/check_host_loadability.ts +81 -0
  257. package/src/scripts/condense.ts +5 -0
  258. package/src/scripts/hook_manifest.yaml +12 -1
  259. package/src/scripts/lint_handoffs.ts +42 -0
  260. package/src/scripts/lint_skill_descriptions.ts +228 -0
  261. package/src/scripts/lint_skill_descriptions_allowlist.json +1 -0
  262. package/src/scripts/lint_skill_scripts_readonly.ts +145 -0
  263. package/src/scripts/lint_skill_scripts_readonly_allowlist.json +8 -0
  264. package/src/scripts/lint_supported_tools_matrix.ts +35 -0
  265. package/src/scripts/optimize_skill_description.ts +144 -0
  266. package/src/scripts/orchestration_record.ts +2 -1
  267. package/src/scripts/prelaunch_diagnostics.ts +248 -0
  268. package/src/scripts/run_skill_evals.ts +70 -4
  269. package/src/scripts/schemas/command.schema.json +7 -0
  270. package/src/scripts/schemas/evals.schema.json +105 -14
  271. package/src/scripts/schemas/prelaunch-report.schema.json +107 -0
  272. package/src/scripts/schemas/skill.schema.json +18 -1
  273. package/src/scripts/schemas/subagent.schema.json +6 -0
  274. package/src/scripts/skill_linter.ts +43 -3
  275. package/src/scripts/smoke_host_loadability.sh +73 -0
  276. package/src/scripts/team_review_gate_hook.ts +115 -0
  277. package/src/scripts/trigger_eval_grandfather.json +1 -2
  278. package/src/scripts/utilization_report.ts +225 -0
  279. package/src/server/schemas/settings.ts +9 -1
@@ -63,6 +63,7 @@ interface RecordArgs {
63
63
  boundary: string;
64
64
  consulted: string[] | null;
65
65
  applied: string[] | null;
66
+ loaded: string[] | null;
66
67
  outcome: string[] | null;
67
68
  ts: string;
68
69
  payload_file: string | null;
@@ -78,6 +79,7 @@ function _build_event_from_args(args: RecordArgs): EngagementEvent {
78
79
  boundary_kind: args.boundary,
79
80
  consulted: _parse_kv_list(args.consulted ?? []),
80
81
  applied: _parse_kv_list(args.applied ?? []),
82
+ loaded: args.loaded && args.loaded.length > 0 ? _parse_kv_list(args.loaded) : null,
81
83
  outcomes: args.outcome && args.outcome.length > 0 ? [...args.outcome] : null,
82
84
  });
83
85
  }
@@ -101,6 +103,7 @@ function _build_event_from_payload(raw: string): EngagementEvent {
101
103
  boundary_kind: (d['boundary_kind'] as string) ?? '',
102
104
  consulted: _orEmpty(d['consulted']),
103
105
  applied: _orEmpty(d['applied']),
106
+ loaded: d['loaded'] === undefined || d['loaded'] === null ? null : _orEmpty(d['loaded']),
104
107
  outcomes: (d['outcomes'] as string[] | null | undefined) ?? null,
105
108
  tokens_estimate: (d['tokens_estimate'] as Record<string, number> | null | undefined) ?? null,
106
109
  });
@@ -121,6 +124,7 @@ function _parseArgs(argv: string[]): RecordArgs {
121
124
  boundary: 'task',
122
125
  consulted: null,
123
126
  applied: null,
127
+ loaded: null,
124
128
  outcome: null,
125
129
  ts: '',
126
130
  payload_file: null,
@@ -155,6 +159,7 @@ function _parseArgs(argv: string[]): RecordArgs {
155
159
  }
156
160
  a.boundary = v;
157
161
  } else if (tok === '--consulted') (a.consulted ??= []).push(next());
162
+ else if (tok === '--loaded') (a.loaded ??= []).push(next());
158
163
  else if (tok === '--applied') (a.applied ??= []).push(next());
159
164
  else if (tok === '--outcome') {
160
165
  const v = next();
@@ -62,10 +62,15 @@ projection:
62
62
  # rules ALWAYS project (unconditional by definition); untagged rules fail
63
63
  # safe and ship. Example consumer scope:
64
64
  # rule_workspaces: [engineering]
65
- # Default flip to a scoped value is a human release gate — see
66
- # agents/settings/contexts/consumer-scoping-audit-2026-07-07.md.
65
+ # DEFAULT FLIPPED 2026-07-13 (the Phase-1 human gate, maintainer-approved,
66
+ # evidence-gated): the default below lists EVERY consumer workspace, so
67
+ # exactly the exclusively-maintainer specification rules drop from consumer
68
+ # installs (the audited 16-rule lever — see
69
+ # agents/settings/contexts/consumer-scoping-audit-2026-07-07.md) while all
70
+ # domain safety floors and domain rules keep shipping. Narrowing further
71
+ # (e.g. [engineering] only) stays a per-install user choice.
67
72
  # An EMPTY list is inactive (= legacy-all); only a non-empty list scopes.
68
- rule_workspaces: []
73
+ rule_workspaces: [engineering, product, finance, founder, gtm, ops, small-business, construction, legal-review-prep]
69
74
  #
70
75
  # Optional second axis, same semantics per pack ids (packs.yml). When set,
71
76
  # a non-kernel rule also needs a `packs:` intersection to project — e.g.
@@ -622,10 +627,14 @@ worktrees:
622
627
 
623
628
  # --- AI Council (external second-opinion network) ---
624
629
  #
625
- # Configuration moved to `agents/.ai-council.yml` as of the Step-2
626
- # consolidation roadmap. Consumer projects opt in by copying the
627
- # reference file from the package (`agents/.ai-council.yml`) into their
628
- # own `agents/` tree and flipping `enabled: true` per provider.
630
+ # The council config is NOT here and is NOT project-local. It lives ONLY
631
+ # user-global at `~/.event4u/agent-config/settings/.ai-council.yml` — one
632
+ # file, configured once, serving every project / worktree / CWD (ADR-104,
633
+ # superseding ADR-093). The project tree is NEVER searched; a
634
+ # `.ai-council.yml` under any `agents/` tree is ignored. To opt in, copy
635
+ # the shape from `agents/templates/.ai-council.yml.example` to that
636
+ # user-global path and flip `enabled: true` per provider — do NOT drop it
637
+ # into a project's `agents/` tree.
629
638
  #
630
639
  # See `docs/contracts/ai-council-config.md` for the schema.
631
640
 
@@ -665,9 +674,24 @@ ai_team:
665
674
  # plugin on Claude-Code hosts. Cosmetic only — never changes behavior.
666
675
  suppress_setup_hint: false
667
676
 
668
- # NOTE for future keys: `review_gate` (managed loop-bound governance of
669
- # the plugin's Stop-hook Review Gate) lands in a later team-mode phase
670
- # (Phase 4) — do not add it here ahead of that phase.
677
+ # Managed governance of the codex plugin's Stop-hook Review Gate
678
+ # (road-to-team-mode Phase 4). The gate itself stays upstream's; these
679
+ # keys only govern it. Upstream's own warning: "The review gate can
680
+ # create a long-running Claude/Codex loop and may drain usage limits
681
+ # quickly."
682
+ review_gate:
683
+ # false (default) = byte-identical pre-Phase-4 behavior: no counting,
684
+ # no circuit breaker, our Stop path never touches the gate. true =
685
+ # count consecutive BLOCK verdicts per session and trip the circuit
686
+ # breaker at the bound below.
687
+ managed: false
688
+
689
+ # Circuit-breaker bound: after this many CONSECUTIVE BLOCK verdicts
690
+ # in one session, a visible notice is injected exactly once and the
691
+ # managed layer stops re-blocking — the user decides, never an
692
+ # infinite Claude↔Codex loop. An ALLOW verdict resets the counter.
693
+ # Positive integer.
694
+ max_consecutive_blocks: 3
671
695
 
672
696
  # --- Onboarding ---
673
697
  #
@@ -0,0 +1,36 @@
1
+ # Pre-launch diagnostic — fixed coverage-area vocabulary
2
+ # (road-to-ecosystem-harvest-prelaunch-diagnostics Phase 1, Source L).
3
+ #
4
+ # The AREA code is the middle segment of the immutable finding-ID grammar
5
+ # `AC-<AREA>-NNN` (docs/contracts/prelaunch-diagnostics.md). Codes are
6
+ # append-only: renaming or re-using a code would re-assign IDs and break
7
+ # every committed baseline. Add new areas at the end; never delete.
8
+ #
9
+ # `launch_gate: true` areas participate in the regression gate's
10
+ # Pass→Finding CI trigger and block a "ready" verdict while Unknown.
11
+ schema_version: prelaunch-areas-v1
12
+ areas:
13
+ auth:
14
+ code: AUTH
15
+ launch_gate: true
16
+ description: Authentication, authorization, tenancy boundaries on the launch surface.
17
+ migrations:
18
+ code: MIG
19
+ launch_gate: true
20
+ description: Schema/data migrations shipped with the launch — reversibility, expand-contract, backfill safety.
21
+ secrets:
22
+ code: SEC
23
+ launch_gate: true
24
+ description: Secrets, tokens, key handling touched by the launch — storage, rotation, exposure paths.
25
+ observability:
26
+ code: OBS
27
+ launch_gate: false
28
+ description: Telemetry proving rollout health — the metric, alert, and pager path for the new surface.
29
+ rollback:
30
+ code: RBK
31
+ launch_gate: true
32
+ description: Rollback contract — trip wires, decision windows, abort paths, named irreversible steps.
33
+ agent-governance:
34
+ code: AGOV
35
+ launch_gate: false
36
+ description: AI/agent surfaces shipped with the launch — tool grants, egress paths, injection exposure.
@@ -99,6 +99,7 @@ import {
99
99
  } from '../_lib/agent_settings.js';
100
100
  import * as ai_council_clients from '../ai_council/clients.js';
101
101
  import * as ai_council_config from '../ai_council/config.js';
102
+ import { review_gate_doctor_signal } from '../ai_team/review_gate.js';
102
103
 
103
104
  type Dict = Record<string, unknown>;
104
105
 
@@ -2028,6 +2029,79 @@ function _settings_flag(val: unknown): boolean {
2028
2029
  return false;
2029
2030
  }
2030
2031
 
2032
+ /**
2033
+ * Decode a JWT payload's `exp` claim (epoch seconds) — base64 decode only,
2034
+ * NO signature verification, no network. Non-JWT / undecodable → null.
2035
+ */
2036
+ function _jwt_exp(token: unknown): number | null {
2037
+ if (typeof token !== 'string') return null;
2038
+ const parts = token.split('.');
2039
+ if (parts.length !== 3) return null;
2040
+ try {
2041
+ const payload: unknown = JSON.parse(
2042
+ Buffer.from(parts[1] as string, 'base64url').toString('utf-8'),
2043
+ );
2044
+ if (typeof payload !== 'object' || payload === null) return null;
2045
+ const exp = (payload as Record<string, unknown>)['exp'];
2046
+ return typeof exp === 'number' && Number.isFinite(exp) && exp > 0 ? exp : null;
2047
+ } catch {
2048
+ return null;
2049
+ }
2050
+ }
2051
+
2052
+ /** Coerce an explicit expiry field to epoch seconds (number or ISO string). */
2053
+ function _epoch_seconds(value: unknown): number | null {
2054
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
2055
+ // Heuristic: values past ~2286 in seconds are epoch milliseconds.
2056
+ return value > 1e12 ? value / 1000 : value;
2057
+ }
2058
+ if (typeof value === 'string' && value.trim() !== '') {
2059
+ const ms = Date.parse(value);
2060
+ return Number.isNaN(ms) ? null : ms / 1000;
2061
+ }
2062
+ return null;
2063
+ }
2064
+
2065
+ /**
2066
+ * Latest locally-derivable expiry (epoch seconds) from a codex `auth.json`,
2067
+ * or null when none is derivable. Sources, all offline:
2068
+ *
2069
+ * - explicit `expires_at` / `expiry` / `exp` fields (top level; numbers in
2070
+ * seconds or ms, or ISO date strings), and
2071
+ * - the `exp` claim of JWT tokens under `tokens.{access_token,id_token}`
2072
+ * (or the same keys top-level) — base64 payload decode only, never
2073
+ * signature verification.
2074
+ *
2075
+ * LATEST wins: the real codex auth.json holds a refresh_token, so an
2076
+ * expired id_token sitting next to a live access_token is not an auth
2077
+ * failure — WARN only when everything derivable is past.
2078
+ */
2079
+ function _codex_auth_expiry(auth_path: string): number | null {
2080
+ let data: unknown;
2081
+ try {
2082
+ data = JSON.parse(fs.readFileSync(auth_path, 'utf-8'));
2083
+ } catch {
2084
+ return null;
2085
+ }
2086
+ if (typeof data !== 'object' || data === null) return null;
2087
+ const d = data as Record<string, unknown>;
2088
+ const candidates: number[] = [];
2089
+ for (const key of ['expires_at', 'expiry', 'exp']) {
2090
+ const v = _epoch_seconds(d[key]);
2091
+ if (v !== null) candidates.push(v);
2092
+ }
2093
+ const tokens_raw = d['tokens'];
2094
+ const tokens =
2095
+ typeof tokens_raw === 'object' && tokens_raw !== null && !Array.isArray(tokens_raw)
2096
+ ? (tokens_raw as Record<string, unknown>)
2097
+ : {};
2098
+ for (const key of ['access_token', 'id_token']) {
2099
+ const v = _jwt_exp(tokens[key] ?? d[key]);
2100
+ if (v !== null) candidates.push(v);
2101
+ }
2102
+ return candidates.length > 0 ? Math.max(...candidates) : null;
2103
+ }
2104
+
2031
2105
  /** The `ai_team` block from `.agent-settings.yml` — lenient, `{}` when absent. */
2032
2106
  function _read_ai_team_block(project_root: string): Dict {
2033
2107
  const settings_file = project_settings_path(project_root);
@@ -2049,14 +2123,21 @@ function _read_ai_team_block(project_root: string): Dict {
2049
2123
  * `team` health check — three sub-signals folded into one result:
2050
2124
  *
2051
2125
  * (a) codex binary present (council-probe binary discovery reused) AND
2052
- * auth present. Auth is a READ-ONLY presence probe of
2053
- * `$CODEX_HOME/auth.json` (default `~/.codex/auth.json`) — token
2054
- * expiry cannot be validated without a billable call; runtime expiry
2055
- * is caught by `OpenAICliClient._AUTH_FAILURE_PATTERNS`.
2126
+ * auth present. Auth is a READ-ONLY probe of `$CODEX_HOME/auth.json`
2127
+ * (default `~/.codex/auth.json`): presence, plus locally-derivable
2128
+ * expiry evidence (`_codex_auth_expiry` — explicit expiry fields or
2129
+ * JWT `exp` payloads; no network, no signature verification). Past
2130
+ * expiry → WARN; no derivable expiry → presence-only note in the
2131
+ * detail. Runtime expiry is still caught authoritatively by
2132
+ * `OpenAICliClient._AUTH_FAILURE_PATTERNS`.
2056
2133
  * (b) on Claude-Code hosts (claude binary on PATH or the Claude config dir
2057
2134
  * present): the official codex plugin recorded in
2058
- * `plugins/installed_plugins.json`. Detection is read-only — doctor
2059
- * never writes under `~/.claude/`.
2135
+ * `plugins/installed_plugins.json`, with namespace-resistant identity
2136
+ * via `codex_plugin_identity()` — a prefix match whose marketplace
2137
+ * source repo cannot be verified against `openai/codex-plugin-cc`
2138
+ * keeps ✅ but is annotated "identity not fully verified (prefix
2139
+ * match)". Detection is read-only — doctor never writes under
2140
+ * `~/.claude/`.
2060
2141
  * (c) Review-Gate consistency: `ai_team.review_gate.managed` on while the
2061
2142
  * loop bound (`max_consecutive_blocks`, Phase 4 — unbuilt) is absent
2062
2143
  * → WARN.
@@ -2074,8 +2155,6 @@ function _check_team(project_root: string): Dict {
2074
2155
  ? (gateRaw as Dict)
2075
2156
  : {};
2076
2157
  const gate_managed = _settings_flag(gate['managed']);
2077
- const bound = gate['max_consecutive_blocks'];
2078
- const bound_present = typeof bound === 'number' && Number.isInteger(bound) && bound > 0;
2079
2158
 
2080
2159
  if (!enabled) {
2081
2160
  if (gate_managed) {
@@ -2110,12 +2189,26 @@ function _check_team(project_root: string): Dict {
2110
2189
  remedies.push('install the codex CLI: `npm install -g @openai/codex`');
2111
2190
  }
2112
2191
 
2113
- // (a, cont.) codex auth — presence probe of $CODEX_HOME/auth.json.
2192
+ // (a, cont.) codex auth — READ-ONLY probe of $CODEX_HOME/auth.json:
2193
+ // presence + locally-derivable expiry evidence (no network).
2114
2194
  const auth_path = path.join(_codex_home(), 'auth.json');
2115
2195
  const authed = pathExists(auth_path);
2116
- const auth_glyph = authed ? '✅' : '❌';
2196
+ let auth_str: string;
2117
2197
  if (!authed) {
2198
+ auth_str = `codex auth ❌ (${auth_path})`;
2118
2199
  remedies.push('run `codex login`');
2200
+ } else {
2201
+ const expiry = _codex_auth_expiry(auth_path);
2202
+ if (expiry !== null && expiry * 1000 < Date.now()) {
2203
+ auth_str = `codex auth ⚠️ expired (${auth_path})`;
2204
+ remedies.push('auth token appears expired — run `codex login`');
2205
+ } else if (expiry === null) {
2206
+ auth_str =
2207
+ `codex auth ✅ (${auth_path}; ` +
2208
+ 'presence-only check — expiry not verifiable locally)';
2209
+ } else {
2210
+ auth_str = `codex auth ✅ (${auth_path})`;
2211
+ }
2119
2212
  }
2120
2213
 
2121
2214
  // (b) official codex plugin — Claude-Code hosts only.
@@ -2123,34 +2216,33 @@ function _check_team(project_root: string): Dict {
2123
2216
  shutilWhich('claude') !== null || pathExists(claude_plugin.claude_config_dir());
2124
2217
  let plugin_str: string;
2125
2218
  if (is_claude_host) {
2126
- const plugin_present = claude_plugin.codex_plugin_installed();
2127
- plugin_str = `codex plugin ${plugin_present ? '✅' : '❌'}`;
2128
- if (!plugin_present) {
2219
+ const identity = claude_plugin.codex_plugin_identity();
2220
+ if (!identity.installed) {
2221
+ plugin_str = 'codex plugin ❌';
2129
2222
  remedies.push(
2130
2223
  'install the official codex plugin: ' +
2131
2224
  claude_plugin.CODEX_PLUGIN_INSTALL_HINT.map((c) => `\`${c}\``).join(' then '),
2132
2225
  );
2226
+ } else if (!identity.identity_verified) {
2227
+ plugin_str = 'codex plugin ✅ — identity not fully verified (prefix match)';
2228
+ } else {
2229
+ plugin_str = 'codex plugin ✅';
2133
2230
  }
2134
2231
  } else {
2135
2232
  plugin_str = 'codex plugin — (not a Claude Code host; fallback path applies)';
2136
2233
  }
2137
2234
 
2138
- // (c) Review-Gate consistency.
2139
- let gate_str: string;
2140
- if (gate_managed && !bound_present) {
2141
- gate_str = 'review-gate ⚠️ on, loop bound absent';
2142
- remedies.push(
2143
- 'set `ai_team.review_gate.max_consecutive_blocks` (loop bound; ' +
2144
- 'ships with the Review-Gate governance phase) or set ' +
2145
- '`ai_team.review_gate.managed: false`',
2146
- );
2147
- } else {
2148
- gate_str = `review-gate ${gate_managed ? 'on' : 'off'}`;
2149
- }
2235
+ // (c) Review-Gate governance (Phase 4) — logic lives in
2236
+ // src/scripts/ai_team/review_gate.ts (upstream gate probe + WARN
2237
+ // states, incl. plugin-gate-on-while-unmanaged with the quoted
2238
+ // upstream cost warning).
2239
+ const gate_signal = review_gate_doctor_signal(project_root, gate);
2240
+ const gate_str = gate_signal.gate_str;
2241
+ remedies.push(...gate_signal.remedies);
2150
2242
 
2151
2243
  const detail =
2152
2244
  `codex binary ${binary_glyph} (${binary_name}) · ` +
2153
- `codex auth ${auth_glyph} (${auth_path}) · ${plugin_str} · ${gate_str}`;
2245
+ `${auth_str} · ${plugin_str} · ${gate_str}`;
2154
2246
  if (remedies.length > 0) {
2155
2247
  return {
2156
2248
  id: 'team',
@@ -79,14 +79,89 @@ export function claude_plugin_installed(): boolean {
79
79
  return _plugin_id_recorded(CLAUDE_PLUGIN_ID);
80
80
  }
81
81
 
82
+ /**
83
+ * Codex plugin detection richness — presence plus namespace-resistant
84
+ * identity verification.
85
+ *
86
+ * `installed` — a `codex@<marketplace>` key exists in
87
+ * `plugins/installed_plugins.json` (prefix match, any marketplace).
88
+ * `identity_verified` — at least one such marketplace resolves, via the
89
+ * sibling `plugins/known_marketplaces.json` registry
90
+ * (`{ "<marketplace>": { source: { source: "github", repo: "owner/repo" } } }`
91
+ * — the real on-disk shape; `installed_plugins.json` entries themselves
92
+ * carry no repo/owner field), to the official upstream repo
93
+ * (`CODEX_MARKETPLACE_REPO`). When the registry file is absent, unreadable,
94
+ * or its entry has no `source.repo`, identity stays UNVERIFIED (honest
95
+ * fallback: prefix match only) — never inferred from the marketplace
96
+ * name alone, which any squatter can reuse.
97
+ */
98
+ export interface CodexPluginIdentity {
99
+ readonly installed: boolean;
100
+ readonly identity_verified: boolean;
101
+ }
102
+
103
+ /** Marketplace names (the `@<marketplace>` suffix) a plugin id is installed from. */
104
+ function _plugin_marketplaces(plugin_id: string): string[] {
105
+ const p = path.join(claude_config_dir(), 'plugins', 'installed_plugins.json');
106
+ let data: unknown;
107
+ try {
108
+ data = JSON.parse(fs.readFileSync(p, 'utf-8'));
109
+ } catch {
110
+ return [];
111
+ }
112
+ if (data === null || typeof data !== 'object') return [];
113
+ const plugins = (data as { plugins?: unknown }).plugins;
114
+ if (plugins === null || plugins === undefined || typeof plugins !== 'object') return [];
115
+ const prefix = `${plugin_id}@`;
116
+ return Object.keys(plugins as Record<string, unknown>)
117
+ .filter((k) => k.startsWith(prefix))
118
+ .map((k) => k.slice(prefix.length));
119
+ }
120
+
121
+ /** `source.repo` recorded for a marketplace in `known_marketplaces.json`, or null. */
122
+ function _marketplace_source_repo(marketplace: string): string | null {
123
+ const p = path.join(claude_config_dir(), 'plugins', 'known_marketplaces.json');
124
+ let data: unknown;
125
+ try {
126
+ data = JSON.parse(fs.readFileSync(p, 'utf-8'));
127
+ } catch {
128
+ return null;
129
+ }
130
+ if (data === null || typeof data !== 'object') return null;
131
+ const entry = (data as Record<string, unknown>)[marketplace];
132
+ if (entry === null || entry === undefined || typeof entry !== 'object') return null;
133
+ const source = (entry as { source?: unknown }).source;
134
+ if (source === null || source === undefined || typeof source !== 'object') return null;
135
+ const repo = (source as { repo?: unknown }).repo;
136
+ return typeof repo === 'string' && repo.trim() !== '' ? repo.trim() : null;
137
+ }
138
+
139
+ /**
140
+ * Presence + identity of the official OpenAI codex plugin. Read-only:
141
+ * detection never writes under `~/.claude/`. See `CodexPluginIdentity`
142
+ * for the verification contract.
143
+ */
144
+ export function codex_plugin_identity(): CodexPluginIdentity {
145
+ const marketplaces = _plugin_marketplaces(CODEX_PLUGIN_ID);
146
+ if (marketplaces.length === 0) {
147
+ return { installed: false, identity_verified: false };
148
+ }
149
+ const want = CODEX_MARKETPLACE_REPO.toLowerCase();
150
+ const identity_verified = marketplaces.some(
151
+ (m) => (_marketplace_source_repo(m) ?? '').toLowerCase() === want,
152
+ );
153
+ return { installed: true, identity_verified };
154
+ }
155
+
82
156
  /**
83
157
  * True when the official OpenAI codex plugin is recorded in Claude Code's
84
158
  * `plugins/installed_plugins.json` (any marketplace — matched by the
85
159
  * `codex@` id prefix). Unreadable / absent file → false. Read-only:
86
- * detection never writes under `~/.claude/`.
160
+ * detection never writes under `~/.claude/`. Presence only — for
161
+ * namespace-resistant identity use `codex_plugin_identity()`.
87
162
  */
88
163
  export function codex_plugin_installed(): boolean {
89
- return _plugin_id_recorded(CODEX_PLUGIN_ID);
164
+ return codex_plugin_identity().installed;
90
165
  }
91
166
 
92
167
  /**
@@ -62,6 +62,10 @@ export interface RecordInput {
62
62
  first_pass_success?: boolean | undefined;
63
63
  /** QUALITY: slice retried on a higher tier after a verification failure. Optional boolean (null = not measured). */
64
64
  escalated?: boolean | undefined;
65
+ /** U5 (reliability-measurement): the subagent-type combination this dispatch
66
+ * ran (ordered agent-type ids, e.g. ['implementer','implementer','judge']) —
67
+ * with `outcome`, lets the orchestrator prefer combos that worked. Ids only. */
68
+ agent_combo?: string[] | undefined;
65
69
  // Audit-log envelope (sensible defaults for a dispatch record)
66
70
  phase?: LinePhase | undefined;
67
71
  outcome?: LineOutcome | undefined;
@@ -173,6 +177,7 @@ export function buildOrchestrationLine(input: RecordInput): BuiltLine {
173
177
  session_tier: input.session_tier ?? null,
174
178
  first_pass_success: input.first_pass_success ?? null,
175
179
  escalated: input.escalated ?? null,
180
+ agent_combo: input.agent_combo ?? [],
176
181
  };
177
182
 
178
183
  const line: Record<string, unknown> = {
@@ -0,0 +1,120 @@
1
+ /**
2
+ * tool_adapter_registry.ts — canonical supported-tools adapter set.
3
+ *
4
+ * Single source of truth binding three surfaces that previously drifted
5
+ * independently (road-to-ecosystem-harvest-skill-quality-gates Phase 4,
6
+ * Source AA):
7
+ *
8
+ * 1. the README `## Supported tools` § "Project-installed" matrix,
9
+ * 2. this registry (reviewed, canonical),
10
+ * 3. the generator output roots in `check_generator_output_coverage.ts`.
11
+ *
12
+ * `lint_supported_tools_matrix.ts` fails CI when (1) ≠ (2) in either
13
+ * direction, and when a generator tool tag in (3) has no registry entry
14
+ * covering its root. Adding/removing a supported tool therefore requires
15
+ * touching README + registry (+ generator roots when native) in one commit.
16
+ */
17
+ import { GENERATOR_OUTPUT_ROOTS } from '../check_generator_output_coverage.js';
18
+
19
+ export interface AdapterEntry {
20
+ /** Tool name exactly as the README matrix first column renders it (without bold markers / parenthetical). */
21
+ readonly tool: string;
22
+ /** How the tool consumes the package. */
23
+ readonly kind: 'native' | 'agents-md' | 'marker';
24
+ /** Generated / written roots this tool reads (repo-relative). Empty for agents-md / marker tools. */
25
+ readonly roots: readonly string[];
26
+ /** Tool tag used by GENERATOR_OUTPUT_ROOTS, when the condense pipeline owns a root for it. */
27
+ readonly generator_tag?: string;
28
+ }
29
+
30
+ export const ADAPTER_REGISTRY: readonly AdapterEntry[] = [
31
+ {
32
+ tool: 'Claude Code',
33
+ kind: 'native',
34
+ roots: ['.claude/rules', '.claude/skills', '.claude/agents', '.claude/personas', '.claude/user-types'],
35
+ generator_tag: 'claude-code',
36
+ },
37
+ { tool: 'Cursor', kind: 'native', roots: ['.cursor/rules', '.cursor/commands'], generator_tag: 'cursor' },
38
+ { tool: 'Cline', kind: 'native', roots: ['.clinerules'] },
39
+ {
40
+ tool: 'Windsurf',
41
+ kind: 'native',
42
+ roots: ['.windsurfrules', '.windsurf/rules', '.windsurf/workflows'],
43
+ generator_tag: 'windsurf',
44
+ },
45
+ { tool: 'Gemini CLI', kind: 'native', roots: ['GEMINI.md'], generator_tag: 'gemini' },
46
+ { tool: 'GitHub Copilot', kind: 'native', roots: ['.github/copilot-instructions.md'] },
47
+ { tool: 'Roo Code', kind: 'agents-md', roots: [] },
48
+ { tool: 'Codex CLI', kind: 'agents-md', roots: [] },
49
+ { tool: 'Continue.dev', kind: 'agents-md', roots: [] },
50
+ { tool: 'Aider', kind: 'marker', roots: [] },
51
+ { tool: 'Augment', kind: 'marker', roots: ['.augment'], generator_tag: 'augment' },
52
+ { tool: 'Claude Desktop', kind: 'marker', roots: [] },
53
+ ];
54
+
55
+ /**
56
+ * Extract the tool names from the README's `## Supported tools` §
57
+ * "Project-installed" table. Returns names with bold markers and trailing
58
+ * parentheticals stripped, in table order.
59
+ */
60
+ export function parse_supported_tools(readme: string): string[] {
61
+ const section = readme.split(/^## Supported tools\s*$/m)[1];
62
+ if (section === undefined) return [];
63
+ // Stop at the next H2/H3 AFTER the first table (the "Plugin-installed" H3).
64
+ const lines = section.split('\n');
65
+ const tools: string[] = [];
66
+ let inTable = false;
67
+ for (const line of lines) {
68
+ const t = line.trim();
69
+ if (/^###\s/.test(t) && inTable) break; // next sub-section after the table
70
+ if (/^\|/.test(t)) {
71
+ inTable = true;
72
+ const cell = t.split('|')[1]?.trim() ?? '';
73
+ if (cell === '' || cell === 'Tool' || /^:?-+:?$/.test(cell)) continue;
74
+ const name = cell
75
+ .replace(/\*\*/g, '')
76
+ .replace(/\s*\(.*\)\s*$/, '')
77
+ .trim();
78
+ if (name && name !== '---') tools.push(name);
79
+ } else if (inTable && t !== '' && !/^\|/.test(t)) {
80
+ // Table ended (legend line etc.).
81
+ break;
82
+ }
83
+ }
84
+ return tools;
85
+ }
86
+
87
+ /** Compare README matrix ↔ registry ↔ generator roots. Returns human-readable errors; empty = green. */
88
+ export function lint_matrix(readme: string): string[] {
89
+ const errors: string[] = [];
90
+ const readmeTools = parse_supported_tools(readme);
91
+ const readmeSet = new Set(readmeTools);
92
+ const registrySet = new Set(ADAPTER_REGISTRY.map((e) => e.tool));
93
+
94
+ for (const t of registrySet) {
95
+ if (!readmeSet.has(t)) {
96
+ errors.push(`registry tool '${t}' missing from the README Supported-tools matrix`);
97
+ }
98
+ }
99
+ for (const t of readmeSet) {
100
+ if (!registrySet.has(t)) {
101
+ errors.push(`README Supported-tools row '${t}' has no ADAPTER_REGISTRY entry`);
102
+ }
103
+ }
104
+
105
+ // Generator leg: every tool-tagged generator output root must be claimed
106
+ // by the registry entry carrying that tag.
107
+ const byTag = new Map(ADAPTER_REGISTRY.filter((e) => e.generator_tag).map((e) => [e.generator_tag, e]));
108
+ for (const { root, tool } of GENERATOR_OUTPUT_ROOTS) {
109
+ if (tool === null) continue;
110
+ const entry = byTag.get(tool);
111
+ if (!entry) {
112
+ errors.push(`generator output root '${root}' (tool '${tool}') has no ADAPTER_REGISTRY entry with that generator_tag`);
113
+ continue;
114
+ }
115
+ if (!entry.roots.includes(root)) {
116
+ errors.push(`generator output root '${root}' (tool '${tool}') is not listed in ADAPTER_REGISTRY entry '${entry.tool}'`);
117
+ }
118
+ }
119
+ return errors;
120
+ }