@event4u/agent-config 8.12.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 (284) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +164 -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 +28 -0
  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 +97 -0
  123. package/dist/agent-src/commands/team/delegate.md +110 -0
  124. package/dist/agent-src/commands/team/review.md +85 -0
  125. package/dist/agent-src/commands/team/status.md +123 -0
  126. package/dist/agent-src/commands/team-knowledge.md +8 -6
  127. package/dist/agent-src/commands/team.md +152 -0
  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 +6 -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 +378 -174
  198. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  199. package/dist/discovery/discovery-manifest.summary.md +4 -4
  200. package/dist/discovery/orphan-report.md +1 -1
  201. package/dist/discovery/packs.json +16 -8
  202. package/dist/discovery/trust-report.md +3 -3
  203. package/dist/discovery/workspaces.json +14 -6
  204. package/dist/install/install.mjs +46 -0
  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 +11 -0
  210. package/dist/server/schemas/settings.js.map +1 -1
  211. package/docs/CLAIMS.md +9 -2
  212. package/docs/SKILL_CENSUS.md +8 -0
  213. package/docs/architecture.md +3 -3
  214. package/docs/benchmark.md +67 -2
  215. package/docs/catalog.md +10 -4
  216. package/docs/command-flows.md +6 -1
  217. package/docs/contracts/ai-council-config.md +9 -2
  218. package/docs/contracts/ai-team-config.md +224 -0
  219. package/docs/contracts/command-clusters.md +1 -0
  220. package/docs/contracts/prelaunch-diagnostics.md +151 -0
  221. package/docs/contracts/release-pr-gating.md +1 -0
  222. package/docs/decisions/ADR-049-configuration-trust-boundary.md +9 -1
  223. package/docs/featured-skills.md +2 -2
  224. package/docs/getting-started-by-role.md +3 -1
  225. package/docs/getting-started.md +42 -1
  226. package/docs/governance-advantage.md +1 -1
  227. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +45 -0
  228. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +28 -0
  229. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +76 -0
  230. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +90 -0
  231. package/docs/guidelines/agent-infra/domain-adoption-gates.md +112 -0
  232. package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +66 -0
  233. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +99 -0
  234. package/docs/guidelines/agent-infra/skill-quality-checklist.md +13 -0
  235. package/docs/guidelines/design-fidelity-mechanics.md +76 -0
  236. package/docs/maintainers/branch-protection.md +18 -0
  237. package/docs/parity/command-frontmatter.md +60 -0
  238. package/docs/proof.md +7 -6
  239. package/package.json +1 -1
  240. package/src/agent-src/templates/scripts/telemetry/aggregator.ts +0 -0
  241. package/src/agent-src/templates/scripts/telemetry/engagement.ts +30 -0
  242. package/src/agent-src/templates/scripts/telemetry/report_renderer.ts +22 -9
  243. package/src/agent-src/templates/scripts/telemetry_record.ts +5 -0
  244. package/src/config/agent-settings.template.yml +71 -7
  245. package/src/config/prelaunch-areas.yml +36 -0
  246. package/src/scripts/_cli/cmd_doctor.ts +288 -1
  247. package/src/scripts/_lib/claude_plugin.ts +114 -4
  248. package/src/scripts/_lib/orchestration_record.ts +5 -0
  249. package/src/scripts/_lib/tool_adapter_registry.ts +120 -0
  250. package/src/scripts/ai_team/config.ts +234 -0
  251. package/src/scripts/ai_team/review_gate.ts +636 -0
  252. package/src/scripts/ai_team/team_dispatch.ts +612 -0
  253. package/src/scripts/bench_ab_task_runner.ts +11 -1
  254. package/src/scripts/bench_parity_count.ts +279 -0
  255. package/src/scripts/build_linear_digest.ts +3 -1
  256. package/src/scripts/check_bridge_derivation.ts +172 -0
  257. package/src/scripts/check_consumer_scope_flip.ts +179 -0
  258. package/src/scripts/check_council_config_location.ts +115 -15
  259. package/src/scripts/check_host_loadability.ts +81 -0
  260. package/src/scripts/condense.ts +5 -0
  261. package/src/scripts/hook_manifest.yaml +12 -1
  262. package/src/scripts/install.ts +39 -0
  263. package/src/scripts/lint_handoffs.ts +42 -0
  264. package/src/scripts/lint_skill_descriptions.ts +228 -0
  265. package/src/scripts/lint_skill_descriptions_allowlist.json +1 -0
  266. package/src/scripts/lint_skill_scripts_readonly.ts +145 -0
  267. package/src/scripts/lint_skill_scripts_readonly_allowlist.json +8 -0
  268. package/src/scripts/lint_supported_tools_matrix.ts +35 -0
  269. package/src/scripts/optimize_skill_description.ts +144 -0
  270. package/src/scripts/orchestration_record.ts +2 -1
  271. package/src/scripts/prelaunch_diagnostics.ts +248 -0
  272. package/src/scripts/rule_backlinks.ts +0 -0
  273. package/src/scripts/run_skill_evals.ts +70 -4
  274. package/src/scripts/schemas/command.schema.json +7 -0
  275. package/src/scripts/schemas/evals.schema.json +105 -14
  276. package/src/scripts/schemas/prelaunch-report.schema.json +107 -0
  277. package/src/scripts/schemas/skill.schema.json +18 -1
  278. package/src/scripts/schemas/subagent.schema.json +6 -0
  279. package/src/scripts/skill_linter.ts +43 -3
  280. package/src/scripts/smoke_host_loadability.sh +73 -0
  281. package/src/scripts/team_review_gate_hook.ts +115 -0
  282. package/src/scripts/trigger_eval_grandfather.json +1 -2
  283. package/src/scripts/utilization_report.ts +225 -0
  284. package/src/server/schemas/settings.ts +25 -0
@@ -29,7 +29,7 @@
29
29
  * scope · global-binary · claude-plugin · stale-orphans · manifest-integrity ·
30
30
  * lockfile-freshness · bridge-drift · mcp-mode · mcp-beta-readiness ·
31
31
  * offline-readiness · python-runtime · humanizer-runtime · tier-usage-readiness ·
32
- * council-cli ·
32
+ * council-cli · team ·
33
33
  * unsupported-combos · wizard-state.
34
34
  * Each emits a structured `{id, status, message, remedy}` record with
35
35
  * `status` ∈ `ok` / `warn` / `fail` / `skipped` (rendered
@@ -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
 
@@ -747,6 +748,7 @@ const CHECK_IDS = [
747
748
  'humanizer-runtime',
748
749
  'tier-usage-readiness',
749
750
  'council-cli',
751
+ 'team',
750
752
  'unsupported-combos',
751
753
  'wizard-state',
752
754
  'settings-review-pending',
@@ -769,6 +771,7 @@ const GLOBAL_CHECK_IDS: ReadonlySet<string> = new Set([
769
771
  'humanizer-runtime',
770
772
  'tier-usage-readiness',
771
773
  'council-cli',
774
+ 'team',
772
775
  'wizard-state',
773
776
  'settings-review-pending',
774
777
  ]);
@@ -1975,6 +1978,287 @@ function pyFloat(value: number): string {
1975
1978
  return String(value);
1976
1979
  }
1977
1980
 
1981
+ // ---------------------------------------------------------------------------
1982
+ // team check (road-to-team-mode Phase 1) — codex binary + auth, official
1983
+ // codex plugin on Claude-Code hosts, Review-Gate consistency.
1984
+ // ---------------------------------------------------------------------------
1985
+
1986
+ /**
1987
+ * Codex CLI home — `CODEX_HOME` env override (the codex CLI's own
1988
+ * convention), else `~/.codex`.
1989
+ */
1990
+ function _codex_home(): string {
1991
+ const env = (process.env['CODEX_HOME'] ?? '').trim();
1992
+ return env !== '' ? env : path.join(os.homedir(), '.codex');
1993
+ }
1994
+
1995
+ /**
1996
+ * Codex binary name for the team check — REUSES the council probe's binary
1997
+ * discovery: when a council config exists and declares an enabled cli-mode
1998
+ * `openai` member with a `binary` override, honour it; else the provider
1999
+ * default from `_CLI_PROVIDER_META` (`codex`). Never throws — an unreadable
2000
+ * or invalid council config falls back to the default binary (the council-cli
2001
+ * check owns reporting that problem).
2002
+ */
2003
+ function _team_codex_binary_name(project_root: string): string {
2004
+ const default_bin = (_CLI_PROVIDER_META['openai'] as [string, boolean])[0];
2005
+ const { load_council_config, resolve_config_path } = ai_council_config;
2006
+ const council_path = resolve_config_path(project_root);
2007
+ if (!pathExists(String(council_path))) {
2008
+ return default_bin;
2009
+ }
2010
+ let cfg: ai_council_config.CouncilConfig;
2011
+ try {
2012
+ cfg = load_council_config(council_path);
2013
+ } catch {
2014
+ return default_bin;
2015
+ }
2016
+ const member = cfg.members.get('openai');
2017
+ if (member && member.enabled && member.mode === 'cli' && member.binary) {
2018
+ return member.binary;
2019
+ }
2020
+ return default_bin;
2021
+ }
2022
+
2023
+ /** Settings-flag coercion shared with the tier-usage check (bool or string). */
2024
+ function _settings_flag(val: unknown): boolean {
2025
+ if (typeof val === 'boolean') return val;
2026
+ if (typeof val === 'string') {
2027
+ return ['true', 'yes', 'on', '1'].includes(val.trim().toLowerCase());
2028
+ }
2029
+ return false;
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
+
2105
+ /** The `ai_team` block from `.agent-settings.yml` — lenient, `{}` when absent. */
2106
+ function _read_ai_team_block(project_root: string): Dict {
2107
+ const settings_file = project_settings_path(project_root);
2108
+ if (!isFile(settings_file)) return {};
2109
+ let content: string;
2110
+ try {
2111
+ content = readText0(settings_file);
2112
+ } catch {
2113
+ return {};
2114
+ }
2115
+ const loaded = yamlSafeLoad(content);
2116
+ if (typeof loaded !== 'object' || loaded === null || Array.isArray(loaded)) return {};
2117
+ const raw = (loaded as Dict)['ai_team'];
2118
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return {};
2119
+ return raw as Dict;
2120
+ }
2121
+
2122
+ /**
2123
+ * `team` health check — three sub-signals folded into one result:
2124
+ *
2125
+ * (a) codex binary present (council-probe binary discovery reused) AND
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`.
2133
+ * (b) on Claude-Code hosts (claude binary on PATH or the Claude config dir
2134
+ * present): the official codex plugin recorded in
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/`.
2141
+ * (c) Review-Gate consistency: `ai_team.review_gate.managed` on while the
2142
+ * loop bound (`max_consecutive_blocks`, Phase 4 — unbuilt) is absent
2143
+ * → WARN.
2144
+ *
2145
+ * Absent feature ≠ broken feature: with `ai_team` absent or disabled the
2146
+ * check reports ok ("team mode not configured (default-off)"); WARN fires
2147
+ * only on failing sub-signals or inconsistent half-configured states.
2148
+ */
2149
+ function _check_team(project_root: string): Dict {
2150
+ const ai_team = _read_ai_team_block(project_root);
2151
+ const enabled = _settings_flag(ai_team['enabled']);
2152
+ const gateRaw = ai_team['review_gate'];
2153
+ const gate =
2154
+ typeof gateRaw === 'object' && gateRaw !== null && !Array.isArray(gateRaw)
2155
+ ? (gateRaw as Dict)
2156
+ : {};
2157
+ const gate_managed = _settings_flag(gate['managed']);
2158
+
2159
+ if (!enabled) {
2160
+ if (gate_managed) {
2161
+ return {
2162
+ id: 'team',
2163
+ status: 'warn',
2164
+ message:
2165
+ 'half-configured: ai_team.review_gate.managed is on but ' +
2166
+ 'ai_team.enabled is false — the gate never runs',
2167
+ remedy:
2168
+ 'set `ai_team.enabled: true` in .agent-settings.yml, or ' +
2169
+ 'remove `ai_team.review_gate.managed`',
2170
+ };
2171
+ }
2172
+ return {
2173
+ id: 'team',
2174
+ status: 'ok',
2175
+ message: 'team mode not configured (default-off)',
2176
+ remedy:
2177
+ 'opt in via `ai_team.enabled: true` in .agent-settings.yml, ' +
2178
+ 'then re-run `agent-config doctor --check team`',
2179
+ };
2180
+ }
2181
+
2182
+ const remedies: string[] = [];
2183
+
2184
+ // (a) codex binary — council-probe discovery reused.
2185
+ const binary_name = _team_codex_binary_name(project_root);
2186
+ const binary_resolved = shutilWhich(binary_name);
2187
+ const binary_glyph = binary_resolved !== null ? '✅' : '❌';
2188
+ if (binary_resolved === null) {
2189
+ remedies.push('install the codex CLI: `npm install -g @openai/codex`');
2190
+ }
2191
+
2192
+ // (a, cont.) codex auth — READ-ONLY probe of $CODEX_HOME/auth.json:
2193
+ // presence + locally-derivable expiry evidence (no network).
2194
+ const auth_path = path.join(_codex_home(), 'auth.json');
2195
+ const authed = pathExists(auth_path);
2196
+ let auth_str: string;
2197
+ if (!authed) {
2198
+ auth_str = `codex auth ❌ (${auth_path})`;
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
+ }
2212
+ }
2213
+
2214
+ // (b) official codex plugin — Claude-Code hosts only.
2215
+ const is_claude_host =
2216
+ shutilWhich('claude') !== null || pathExists(claude_plugin.claude_config_dir());
2217
+ let plugin_str: string;
2218
+ if (is_claude_host) {
2219
+ const identity = claude_plugin.codex_plugin_identity();
2220
+ if (!identity.installed) {
2221
+ plugin_str = 'codex plugin ❌';
2222
+ remedies.push(
2223
+ 'install the official codex plugin: ' +
2224
+ claude_plugin.CODEX_PLUGIN_INSTALL_HINT.map((c) => `\`${c}\``).join(' then '),
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 ✅';
2230
+ }
2231
+ } else {
2232
+ plugin_str = 'codex plugin — (not a Claude Code host; fallback path applies)';
2233
+ }
2234
+
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);
2242
+
2243
+ const detail =
2244
+ `codex binary ${binary_glyph} (${binary_name}) · ` +
2245
+ `${auth_str} · ${plugin_str} · ${gate_str}`;
2246
+ if (remedies.length > 0) {
2247
+ return {
2248
+ id: 'team',
2249
+ status: 'warn',
2250
+ message: `${remedies.length} team sub-signal(s) failing · ${detail}`,
2251
+ remedy: remedies.join('; '),
2252
+ };
2253
+ }
2254
+ return {
2255
+ id: 'team',
2256
+ status: 'ok',
2257
+ message: `team mode enabled · ${detail}`,
2258
+ remedy: '',
2259
+ };
2260
+ }
2261
+
1978
2262
  function _check_unsupported_combos(manifest: Dict): Dict {
1979
2263
  const global_only = new Set(['droid', 'qoder']);
1980
2264
  const bad: string[] = [];
@@ -2257,6 +2541,7 @@ function _run_checks(
2257
2541
  'humanizer-runtime': () => _check_humanizer_runtime(),
2258
2542
  'tier-usage-readiness': () => _check_tier_usage_readiness(project_root),
2259
2543
  'council-cli': () => _check_council_cli(project_root),
2544
+ team: () => _check_team(project_root),
2260
2545
  'unsupported-combos': () => _check_unsupported_combos(manifest),
2261
2546
  'wizard-state': _check_wizard_state,
2262
2547
  'settings-review-pending': _check_settings_review_pending,
@@ -2329,6 +2614,7 @@ function _run_checks_no_manifest(
2329
2614
  'humanizer-runtime': () => _check_humanizer_runtime(),
2330
2615
  'tier-usage-readiness': () => _check_tier_usage_readiness(project_root),
2331
2616
  'council-cli': () => _check_council_cli(project_root),
2617
+ team: () => _check_team(project_root),
2332
2618
  'unsupported-combos': () => _skipped_manifest_check('unsupported-combos'),
2333
2619
  'wizard-state': _check_wizard_state,
2334
2620
  'settings-review-pending': _check_settings_review_pending,
@@ -2818,6 +3104,7 @@ export {
2818
3104
  _check_mcp_beta_readiness,
2819
3105
  _check_tier_usage_readiness,
2820
3106
  _check_council_cli,
3107
+ _check_team,
2821
3108
  _check_unsupported_combos,
2822
3109
  _check_wizard_state,
2823
3110
  _check_settings_review_pending,
@@ -27,6 +27,22 @@ export const CLAUDE_PLUGIN_INSTALL_HINT: readonly string[] = [
27
27
  `claude plugin install ${CLAUDE_PLUGIN_ID}@${CLAUDE_MARKETPLACE_NAME}`,
28
28
  ];
29
29
 
30
+ /**
31
+ * Official OpenAI codex plugin for Claude Code (team mode's Claude-Code
32
+ * transport). Marketplace name verified against the upstream repo's
33
+ * `.claude-plugin/marketplace.json` (`name: "openai-codex"`, plugin
34
+ * `name: "codex"`) at HEAD 2026-07-12.
35
+ */
36
+ export const CODEX_PLUGIN_ID = 'codex';
37
+ export const CODEX_MARKETPLACE_NAME = 'openai-codex';
38
+ export const CODEX_MARKETPLACE_REPO = 'openai/codex-plugin-cc';
39
+
40
+ /** The one-time install commands surfaced when the codex plugin is missing. */
41
+ export const CODEX_PLUGIN_INSTALL_HINT: readonly string[] = [
42
+ `claude plugin marketplace add ${CODEX_MARKETPLACE_REPO}`,
43
+ `claude plugin install ${CODEX_PLUGIN_ID}@${CODEX_MARKETPLACE_NAME}`,
44
+ ];
45
+
30
46
  /** Claude Code config dir — `CLAUDE_CONFIG_DIR` env override, else `~/.claude`. */
31
47
  export function claude_config_dir(): string {
32
48
  const env = (process.env['CLAUDE_CONFIG_DIR'] ?? '').trim();
@@ -34,11 +50,11 @@ export function claude_config_dir(): string {
34
50
  }
35
51
 
36
52
  /**
37
- * True when the agent-config plugin is recorded in Claude Code's
53
+ * True when a plugin with the given id is recorded in Claude Code's
38
54
  * `plugins/installed_plugins.json` (any marketplace — matched by the
39
- * `agent-config@` id prefix). Unreadable / absent file → false.
55
+ * `<id>@` key prefix). Unreadable / absent file → false. Read-only.
40
56
  */
41
- export function claude_plugin_installed(): boolean {
57
+ function _plugin_id_recorded(plugin_id: string): boolean {
42
58
  const p = path.join(claude_config_dir(), 'plugins', 'installed_plugins.json');
43
59
  let data: unknown;
44
60
  try {
@@ -50,8 +66,102 @@ export function claude_plugin_installed(): boolean {
50
66
  const plugins = (data as { plugins?: unknown }).plugins;
51
67
  if (plugins === null || plugins === undefined || typeof plugins !== 'object') return false;
52
68
  return Object.keys(plugins as Record<string, unknown>).some((k) =>
53
- k.startsWith(`${CLAUDE_PLUGIN_ID}@`),
69
+ k.startsWith(`${plugin_id}@`),
70
+ );
71
+ }
72
+
73
+ /**
74
+ * True when the agent-config plugin is recorded in Claude Code's
75
+ * `plugins/installed_plugins.json` (any marketplace — matched by the
76
+ * `agent-config@` id prefix). Unreadable / absent file → false.
77
+ */
78
+ export function claude_plugin_installed(): boolean {
79
+ return _plugin_id_recorded(CLAUDE_PLUGIN_ID);
80
+ }
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,
54
152
  );
153
+ return { installed: true, identity_verified };
154
+ }
155
+
156
+ /**
157
+ * True when the official OpenAI codex plugin is recorded in Claude Code's
158
+ * `plugins/installed_plugins.json` (any marketplace — matched by the
159
+ * `codex@` id prefix). Unreadable / absent file → false. Read-only:
160
+ * detection never writes under `~/.claude/`. Presence only — for
161
+ * namespace-resistant identity use `codex_plugin_identity()`.
162
+ */
163
+ export function codex_plugin_installed(): boolean {
164
+ return codex_plugin_identity().installed;
55
165
  }
56
166
 
57
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
+ }