@event4u/agent-config 14.8.0 → 14.9.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 (136) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +165 -237
  3. package/README.md +1 -1
  4. package/agents/templates/.ai-council.yml.example +34 -4
  5. package/dist/agent-src/commands/pr/merge.md +9 -5
  6. package/dist/agent-src/commands/review/changes.md +95 -14
  7. package/dist/agent-src/commands/roadmap/process-full.md +2 -2
  8. package/dist/agent-src/contexts/execution/host-capability-manifest.md +77 -0
  9. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +36 -0
  10. package/dist/agent-src/contexts/execution/subagent-response-contract.md +80 -0
  11. package/dist/agent-src/rules/untrusted-input-defense.md +9 -3
  12. package/dist/agent-src/skills/code-review/evals/evals.json +87 -46
  13. package/dist/agent-src/skills/humanizer/SKILL.md +39 -0
  14. package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +57 -0
  15. package/dist/agent-src/skills/humanizer/references/fixtures.md +54 -0
  16. package/dist/agent-src/skills/judge-spec-compliance/SKILL.md +154 -0
  17. package/dist/agent-src/skills/judge-synthesis/SKILL.md +45 -0
  18. package/dist/agent-src/skills/roadmap-writing/SKILL.md +18 -18
  19. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  20. package/dist/agent-src/templates/roadmaps.md +8 -0
  21. package/dist/cli/registry.js +2 -0
  22. package/dist/cli/registry.js.map +1 -1
  23. package/dist/cli-delegate/{chunk-MMSGVZKV.js → chunk-77GR3VD4.js} +65 -40
  24. package/dist/cli-delegate/{chunk-Q26HT4QT.js → chunk-E46KPN6K.js} +251 -150
  25. package/dist/cli-delegate/{chunk-EEJUMCDZ.js → chunk-PLSG6UL5.js} +1 -1
  26. package/dist/cli-delegate/cmd_conformance.js +89 -13
  27. package/dist/cli-delegate/cmd_doctor.js +3 -3
  28. package/dist/cli-delegate/cmd_handoff.js +2 -2
  29. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  30. package/dist/cli-delegate/cmd_route_audit.js +1 -1
  31. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  32. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  33. package/dist/discovery/deprecation-report.md +1 -1
  34. package/dist/discovery/discovery-manifest.json +37 -15
  35. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  36. package/dist/discovery/discovery-manifest.summary.md +3 -3
  37. package/dist/discovery/dormancy-report.md +1 -1
  38. package/dist/discovery/orphan-report.md +1 -1
  39. package/dist/discovery/packs.json +6 -5
  40. package/dist/discovery/trust-report.md +2 -2
  41. package/dist/discovery/workspaces.json +5 -4
  42. package/dist/hooks/dispatch.js +562 -204
  43. package/dist/mcp/registry-manifest.json +3 -3
  44. package/dist/mcp/server.json +2 -2
  45. package/dist/mcp/server.mjs +808 -331
  46. package/dist/scripts/_lib/claude_plugin.js +162 -0
  47. package/dist/scripts/_lib/claude_plugin.js.map +1 -0
  48. package/dist/scripts/_lib/environment_detector.js +397 -0
  49. package/dist/scripts/_lib/environment_detector.js.map +1 -0
  50. package/dist/scripts/_lib/spawn_env.js +129 -0
  51. package/dist/scripts/_lib/spawn_env.js.map +1 -0
  52. package/dist/scripts/ai_council/modes.js +199 -0
  53. package/dist/scripts/ai_council/modes.js.map +1 -0
  54. package/dist/scripts/ai_council/transport_resolver.js +418 -0
  55. package/dist/scripts/ai_council/transport_resolver.js.map +1 -0
  56. package/dist/server/routes/wizard.js +3 -2
  57. package/dist/server/routes/wizard.js.map +1 -1
  58. package/dist/ui/assets/index-Cxgy63kz.js +26 -0
  59. package/dist/ui/assets/index-Cxgy63kz.js.map +1 -0
  60. package/dist/ui/index.html +1 -1
  61. package/docs/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
  62. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +17 -0
  63. package/package.json +7 -3
  64. package/src/config/estate-count-budget.json +13 -744
  65. package/src/config/evaluator-budgets.json +4 -4
  66. package/src/config/gate-coverage.yml +247 -9
  67. package/src/config/gate-violation-baselines.json +72 -72
  68. package/src/config/publish-surface.json +60 -0
  69. package/src/config/requirements-trace-budget.json +32 -0
  70. package/src/config/rule-stub-ceilings.json +8 -1
  71. package/src/scripts/_dispatch.bash +7 -0
  72. package/src/scripts/_lib/base_tree.ts +175 -0
  73. package/src/scripts/_lib/billing_grant.ts +165 -0
  74. package/src/scripts/_lib/billing_grant_cli.ts +123 -0
  75. package/src/scripts/_lib/council_fallback_posture.ts +53 -6
  76. package/src/scripts/_lib/council_fallback_wiring.ts +61 -1
  77. package/src/scripts/_lib/host_capability.ts +59 -5
  78. package/src/scripts/_lib/quota_parked.ts +133 -0
  79. package/src/scripts/_lib/retrieval_sanitize.ts +62 -0
  80. package/src/scripts/_lib/review_telemetry.ts +196 -0
  81. package/src/scripts/_lib/subagent_response.ts +92 -0
  82. package/src/scripts/adr/regenerate_index.ts +14 -7
  83. package/src/scripts/ai_council/clients.ts +12 -1
  84. package/src/scripts/ai_council/config.ts +72 -115
  85. package/src/scripts/ai_council/fallback_config.ts +19 -7
  86. package/src/scripts/ai_council/mid_flight_fallback.ts +88 -7
  87. package/src/scripts/ai_council/orchestrator.ts +2 -2
  88. package/src/scripts/ai_council/py_format.ts +124 -0
  89. package/src/scripts/ai_council/transport_resolver.ts +65 -2
  90. package/src/scripts/audit_adr_coverage.ts +33 -6
  91. package/src/scripts/check_council_pin_staleness.ts +300 -0
  92. package/src/scripts/check_estate_count.ts +420 -293
  93. package/src/scripts/check_gate_coverage.ts +91 -0
  94. package/src/scripts/check_no_stub_inventory_table.ts +292 -0
  95. package/src/scripts/check_pack_size.ts +234 -1
  96. package/src/scripts/check_publish_surface.ts +297 -0
  97. package/src/scripts/check_requirements_trace.ts +378 -0
  98. package/src/scripts/check_secret_leak.ts +166 -12
  99. package/src/scripts/code_graph/cli.ts +44 -5
  100. package/src/scripts/code_graph/extract.ts +87 -0
  101. package/src/scripts/council_cli.ts +12 -16
  102. package/src/scripts/detect_ai_tells.ts +87 -0
  103. package/src/scripts/dispatch_r2_reviewer.ts +42 -0
  104. package/src/scripts/generate_capabilities_index.ts +41 -0
  105. package/src/scripts/git_authorization_hook.ts +54 -4
  106. package/src/scripts/hooks/block_unauthorized_git.ts +19 -1
  107. package/src/scripts/hooks/run_continuation_hook.ts +3 -1
  108. package/src/scripts/hooks/subagent_ledger_hook.ts +76 -11
  109. package/src/scripts/injection_scan_hook.ts +120 -10
  110. package/src/scripts/lint_adr_sweep_routing.ts +365 -0
  111. package/src/scripts/lint_workflow_security.ts +53 -10
  112. package/src/scripts/lint_workflow_security_allowlist.json +2 -18
  113. package/src/scripts/report_envelope_rate.ts +166 -0
  114. package/src/scripts/review_axis_report.ts +218 -0
  115. package/src/scripts/run_supervise.ts +46 -1
  116. package/src/scripts/schemas/evals.schema.json +5 -0
  117. package/src/scripts/sync_pr_branch.ts +14 -7
  118. package/dist/cli/commands/commands.candidates.test.js +0 -257
  119. package/dist/cli/commands/commands.candidates.test.js.map +0 -1
  120. package/dist/cli/commands/profiles.test.js +0 -88
  121. package/dist/cli/commands/profiles.test.js.map +0 -1
  122. package/dist/cli/commands/recordTriggerEval.test.js +0 -113
  123. package/dist/cli/commands/recordTriggerEval.test.js.map +0 -1
  124. package/dist/cli/configRoot.test.js +0 -97
  125. package/dist/cli/configRoot.test.js.map +0 -1
  126. package/dist/cli/mcp/content.test.js +0 -54
  127. package/dist/cli/mcp/content.test.js.map +0 -1
  128. package/dist/cli/mcp/dispatch.test.js +0 -108
  129. package/dist/cli/mcp/dispatch.test.js.map +0 -1
  130. package/dist/server/writeRoot.test.js +0 -91
  131. package/dist/server/writeRoot.test.js.map +0 -1
  132. package/dist/shared/capabilities.test.js +0 -39
  133. package/dist/shared/capabilities.test.js.map +0 -1
  134. package/dist/ui/assets/index-C1pBRm1f.js +0 -26
  135. package/dist/ui/assets/index-C1pBRm1f.js.map +0 -1
  136. /package/dist/cli-delegate/{chunk-N7CDDGI3.js → chunk-CYH5SP6M.js} +0 -0
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![Smoke](https://github.com/event4u-app/agent-config/actions/workflows/smoke.yml/badge.svg)](https://github.com/event4u-app/agent-config/actions/workflows/smoke.yml) [![Public install smoke (3 OS × 2 Node)](https://github.com/event4u-app/agent-config/actions/workflows/smoke-public-install.yml/badge.svg)](https://github.com/event4u-app/agent-config/actions/workflows/smoke-public-install.yml) [![npm](https://img.shields.io/npm/v/@event4u/agent-config?style=flat-square&label=npm&color=orange)](https://www.npmjs.com/package/@event4u/agent-config) [![agent-config MCP server](https://glama.ai/mcp/servers/event4u-app/agent-config/badges/score.svg)](https://glama.ai/mcp/servers/event4u-app/agent-config) [![MCP Toplist](https://mcptoplist.com/badge/glama%2Fevent4u-app%2Fagent-config.svg)](https://mcptoplist.com/server/glama%2Fevent4u-app%2Fagent-config)
6
6
 
7
- [![Skills](https://img.shields.io/badge/Skills-290-orange?style=flat-square)](dist/agent-src/skills/) [![Rules](https://img.shields.io/badge/Rules-119-orange?style=flat-square)](dist/agent-src/rules/) [![Commands](https://img.shields.io/badge/Commands-201-orange?style=flat-square)](dist/agent-src/commands/) [![Guidelines](https://img.shields.io/badge/Guidelines-107-orange?style=flat-square)](docs/guidelines/) [![Personas](https://img.shields.io/badge/Personas-29-orange?style=flat-square)](dist/agent-src/personas/) [![Advisors](https://img.shields.io/badge/Advisors-5-orange?style=flat-square)](dist/agent-src/personas/advisors/)
7
+ [![Skills](https://img.shields.io/badge/Skills-291-orange?style=flat-square)](dist/agent-src/skills/) [![Rules](https://img.shields.io/badge/Rules-119-orange?style=flat-square)](dist/agent-src/rules/) [![Commands](https://img.shields.io/badge/Commands-201-orange?style=flat-square)](dist/agent-src/commands/) [![Guidelines](https://img.shields.io/badge/Guidelines-108-orange?style=flat-square)](docs/guidelines/) [![Personas](https://img.shields.io/badge/Personas-29-orange?style=flat-square)](dist/agent-src/personas/) [![Advisors](https://img.shields.io/badge/Advisors-5-orange?style=flat-square)](dist/agent-src/personas/advisors/)
8
8
 
9
9
  **Try it in 30 seconds** — drop one read-only subagent into any repo and watch it gate "done": `@production-validator check this branch is actually done`. No wizard, no lock-in, nothing else installed — the [30-second wedge ↓](#quickstart) is the whole first step. Start at the proof, not the catalog: **[event4u-app.github.io/agent-config/proof/](https://event4u-app.github.io/agent-config/proof/)**.
10
10
 
@@ -289,8 +289,20 @@ quorum: majority
289
289
  # markers.
290
290
  members:
291
291
  anthropic:
292
+ # `sonnet` is a vendor ALIAS, not a version. `claude --help` documents
293
+ # 'fable', 'opus' and 'sonnet' as "an alias for the latest model" in their
294
+ # band, so this line cannot go stale the way a dated id can — the same
295
+ # property `codex-default` gives the openai member below.
296
+ #
297
+ # Until 2026-08-22 this read a dated, several-generation-old id: an enabled
298
+ # member of a quality council pinned in a template nobody re-reads, with
299
+ # nothing in the file able to notice. Read from the provider's own CLI
300
+ # surface, never from recall.
301
+ #
302
+ # A member on an alias or a sentinel needs no `verified_at:` — see the
303
+ # disabled members below for the stamped form.
292
304
  enabled: true
293
- model: claude-sonnet-4-5
305
+ model: sonnet
294
306
  api_key_ref: file:anthropic.key
295
307
  # binary: /usr/local/bin/claude # only valid when mode == cli
296
308
  # Phase 7 — size-fit downgrade ladder (smallest → largest). The
@@ -298,9 +310,9 @@ members:
298
310
  # classifier flags a request as oversized, it suggests the next
299
311
  # rung *down*; the top rung is the default for complex prompts.
300
312
  model_ladder:
301
- - claude-haiku-4-5
302
- - claude-sonnet-4-5
303
- - claude-opus-4-5
313
+ - haiku
314
+ - sonnet
315
+ - opus
304
316
  # Prompt caching (GA — no beta header) is ON by default: the stable
305
317
  # system + artefact prefix is sent as a cache-controlled block, so
306
318
  # members 2..N in a round (and round-2+ within the 5-min TTL) read
@@ -381,20 +393,38 @@ members:
381
393
 
382
394
  gemini:
383
395
  enabled: false
396
+ # not-listed: gemini — the `gemini` CLI is installed here and its `--help` exposes only `-m, --model <string>` — no listing subcommand and no documented "latest" alias, so no sentinel could be substituted. An honest null for this provider, not a guess.
397
+ # The pin is therefore stamped as-of the drain date with NO refresh claimed;
398
+ # `verified_at` records when a human last looked, which is the only thing it
399
+ # has ever meant. This member ships disabled, so the pin costs nothing today
400
+ # and `check_council_pin_staleness` will say so when the stamp ages out.
384
401
  model: gemini-2.5-pro
402
+ verified_at: "2026-08-22"
385
403
  api_key_ref: env:GEMINI_API_KEY
386
404
  # binary: /usr/local/bin/gemini # only valid when mode == cli
387
405
 
388
406
  xai:
389
407
  enabled: false
408
+ # not-listed: grok — no `grok` binary on this machine, so the provider's own surface was unreachable.
409
+ # The pin is therefore stamped as-of the drain date with NO refresh claimed;
410
+ # `verified_at` records when a human last looked, which is the only thing it
411
+ # has ever meant. This member ships disabled, so the pin costs nothing today
412
+ # and `check_council_pin_staleness` will say so when the stamp ages out.
390
413
  model: grok-4
414
+ verified_at: "2026-08-22"
391
415
  api_key_ref: env:XAI_API_KEY
392
416
  # binary: /usr/local/bin/grok # The CLI consumes XAI_API_KEY and remains billable=True;
393
417
  # # cost_budget.max_total_usd STILL applies. Opt-in only.
394
418
 
395
419
  perplexity:
396
420
  enabled: false
421
+ # not-listed: sonar — no `sonar` binary on this machine, so the provider's own surface was unreachable.
422
+ # The pin is therefore stamped as-of the drain date with NO refresh claimed;
423
+ # `verified_at` records when a human last looked, which is the only thing it
424
+ # has ever meant. This member ships disabled, so the pin costs nothing today
425
+ # and `check_council_pin_staleness` will say so when the stamp ages out.
397
426
  model: sonar-pro
427
+ verified_at: "2026-08-22"
398
428
  api_key_ref: env:PERPLEXITY_API_KEY
399
429
  # binary: /usr/local/bin/perplexity # The CLI consumes PERPLEXITY_API_KEY and remains billable=True;
400
430
  # # cost_budget.max_total_usd STILL applies. Opt-in only.
@@ -134,14 +134,19 @@ pushed ([`git-history-discipline`](../../../../rules/git-history-discipline.md))
134
134
  ## 3. Resolve conflicts by class, never by taste
135
135
 
136
136
  Conflicts between queued PRs are expected, not exceptional: in this repository
137
- every roadmap PR touches `agents/roadmaps-progress.md` and
138
- `src/config/estate-count-budget.json`, so each merge re-conflicts every
139
- remaining PR. Resolve semantically, by these enumerated classes:
137
+ roadmap PRs touch the same handful of shared paths, so each merge can
138
+ re-conflict every remaining PR. Resolve semantically, by these enumerated
139
+ classes.
140
+
141
+ `src/config/estate-count-budget.json` used to be the second-worst offender here
142
+ and no longer belongs in any class below: ADR-243 removed its stored baseline, so
143
+ it changes on a policy decision rather than once per roadmap touch. A conflict in
144
+ it now means two humans edited the same policy sentence — read both sides.
140
145
 
141
146
  | Class | Files | Resolution |
142
147
  |---|---|---|
143
148
  | Roadmap files | `agents/roadmaps/**` | Union of completions. Never un-check a box either side checked; never resurrect a roadmap either side archived or parked. |
144
- | Generated artefacts | `agents/roadmaps-progress.md` · `src/config/estate-count-budget.json` · `docs/catalog.md` · `docs/command-flows.md` · `agents/index.md` · any file whose first three lines say generated / do-not-edit | Never hand-merge. Take either side, then **regenerate** with the repo's own task and commit the regenerated output. |
149
+ | Generated artefacts | `agents/roadmaps-progress.md` · `docs/catalog.md` · `docs/command-flows.md` · `agents/index.md` · any file whose first three lines say generated / do-not-edit | Never hand-merge. Take either side, then **regenerate** with the repo's own task and commit the regenerated output. |
145
150
  | Archive move vs. edit | `agents/roadmaps/{archive,later,skipped}/**` — one side renamed or deleted, the other edited | The archived end-state wins. Re-apply the edit at the new path if it still matters; otherwise drop it and record the drop in the summary. |
146
151
  | Evidence files | `agents/evidence/**` | Append-only. Keep both sides. |
147
152
 
@@ -169,7 +174,6 @@ regenerate. Exclude those paths before deciding:
169
174
  ```bash
170
175
  git diff origin/<base>...HEAD --stat -- . \
171
176
  ':(exclude)agents/roadmaps-progress.md' \
172
- ':(exclude)src/config/estate-count-budget.json' \
173
177
  ':(exclude)docs/catalog.md' ':(exclude)agents/index.md'
174
178
  ```
175
179
 
@@ -9,7 +9,7 @@ visibility: internal
9
9
  sub: changes
10
10
  cluster: review
11
11
  skills: [code-review, subagent-orchestration, judge-bug-hunter, judge-security-auditor, judge-test-coverage, judge-code-quality, architecture-review-lens, judge-synthesis, git-workflow]
12
- description: Self-review local changes before creating a PR — dispatches to five specialized judges (bug, security, tests, quality, architecture) and consolidates verdicts
12
+ description: Self-review local changes before creating a PR — dispatches to six specialized judges (bug, security, tests, quality, architecture, spec) and consolidates verdicts
13
13
  suggestion:
14
14
  eligible: false
15
15
  rationale: "Cluster sub-command — reached via its cluster head's routing or its explicit /cluster:sub name; not independently suggested (surface-consolidation)."
@@ -24,7 +24,7 @@ packs:
24
24
  ## Instructions
25
25
 
26
26
  Review all uncommitted and committed-but-not-pushed changes against
27
- the default branch (`main`) by dispatching to four specialized judge
27
+ the default branch (`main`) by dispatching to six specialized judge
28
28
  sub-skills and consolidating their verdicts.
29
29
 
30
30
  ### 1. Update the current branch
@@ -51,6 +51,18 @@ to make sure the current branch is up to date with its base chain:
51
51
 
52
52
  ### 2. Gather the diff
53
53
 
54
+ **Verify the base ref before diffing against it.** The commands below assume
55
+ `origin/main` exists and is the right base, and nothing checked. This step
56
+ already knows how to ask git a question — `git rev-parse --abbrev-ref HEAD`
57
+ above — so the shape was present and simply not applied to the base:
58
+
59
+ - `git rev-parse --verify origin/main` — the base ref resolves.
60
+ - **Does not resolve → stop here and say so.** Do not fall back to a
61
+ different base, and do not diff against `HEAD` alone. A review whose scope
62
+ silently became "the whole branch" or "the last commit" reports findings
63
+ against a diff nobody asked about, and the reader has no way to see that the
64
+ scope moved. Name the ref that failed.
65
+
54
66
  - `git diff origin/main..HEAD --stat` — overview of changed files
55
67
  - `git diff origin/main..HEAD` — full committed-but-not-pushed diff
56
68
  - `git diff --stat` + `git diff` — unstaged changes on top
@@ -65,7 +77,39 @@ Read `.agent-settings.yml`:
65
77
 
66
78
  Unknown alias → stop. Never silently fall back.
67
79
 
68
- ### 4. Dispatch to the five judges
80
+ ### 3b. Bind the criteria source before dispatching
81
+
82
+ The spec judge below is only as good as the criteria it reads, so where they
83
+ came from is recorded **before** the dispatch, not reconstructed after it.
84
+ Exactly one of three states, and the state is written into the review report:
85
+
86
+ | `criteria_source` | When | What the report says |
87
+ |---|---|---|
88
+ | `supplied` | a roadmap, ticket, issue or AC block was explicitly handed in — name it by path or id | the criteria, verbatim, and where they came from |
89
+ | `not_provided` | an ad-hoc branch review with no criteria anywhere | "no acceptance criteria supplied — requirement compliance NOT verified" |
90
+ | `supplied_unparseable` | criteria were handed over and could not be read in a recognised shape | an **error**: the review skipped a dimension it was asked to check |
91
+
92
+ ```
93
+ CRITERIA ARE READ ONLY WHEN SUPPLIED. NEVER DERIVED FROM THE BRANCH,
94
+ THE COMMIT MESSAGES, OR THE PR BODY — AND NEVER FROM THE DIFF.
95
+ A JUDGE THAT INFERS THE REQUIREMENT FROM THE CHANGE IT IS JUDGING
96
+ ALWAYS FINDS THE CHANGE COMPLIANT. SILENCE IS `not_provided`, NEVER A PASS.
97
+ ```
98
+
99
+ There is deliberately **no `derived` state**. Deriving criteria from the branch
100
+ was the alternative considered and rejected: the `derived` label would have to
101
+ be honoured by every consumer of the report to be worth anything, and a label
102
+ that is requested rather than enforced degrades to silence exactly when it
103
+ matters. Decided by AI council on 2026-08-22 (degraded, 1 of 2 seats — the
104
+ second returned an empty response), and the alternative is recorded here rather
105
+ than only in the decision so a later reader can see it was weighed.
106
+
107
+ `supplied_unparseable` is separated from `not_provided` for the same reason:
108
+ folded together, a handover the reviewer could not parse would report as "there
109
+ was nothing to check", which is the one outcome that hides a real failure
110
+ behind a benign one.
111
+
112
+ ### 4. Dispatch to the six judges
69
113
 
70
114
  Each judge receives **the same diff plus the task context** (ticket,
71
115
  PR body, commit messages) and runs independently. The judges are:
@@ -77,20 +121,37 @@ PR body, commit messages) and runs independently. The judges are:
77
121
  | [`judge-test-coverage`](../../skills/judge-test-coverage/SKILL.md) | Missing assertions, uncovered branches, over-mocking, regression-test gaps |
78
122
  | [`judge-code-quality`](../../skills/judge-code-quality/SKILL.md) | Naming, SRP, DRY, dead code, consistency with codebase conventions |
79
123
  | [`architecture-review-lens`](../../skills/architecture-review-lens/SKILL.md) | Layer violations, dependency direction, leaky abstractions, cross-service contract drift |
124
+ | [`judge-spec-compliance`](../../skills/judge-spec-compliance/SKILL.md) | Does the diff satisfy every acceptance criterion **as stated**? Reads the criteria before the diff, and never infers them from it |
80
125
 
81
- The five judges weight equally in the consolidated verdict — none
126
+ The six judges weight equally in the consolidated verdict — none
82
127
  overrides another.
83
128
 
129
+ **Why the sixth exists, and why it is not a severity.** The other five all ask a
130
+ craft-or-correctness question, so a change that is correct, clean, well-tested
131
+ and architecturally sound — and **does not do what was asked** — used to pass
132
+ this path with five green verdicts. Its finding does **not** enter the shared
133
+ severity axis: `judge-synthesis` carries it on its own dimension, because "this
134
+ does not do what was asked" is not comparable to "this variable is badly named",
135
+ and on one scale the second can outrank the first whenever three judges raise
136
+ nits and one raises the spec gap.
137
+
138
+ **When no criteria were supplied** — an ad-hoc branch review — this judge
139
+ returns a no-criteria verdict, never `SATISFIED`. That is not an abstention the
140
+ consolidated verdict may drop: it changes what "done" is allowed to claim to
141
+ *"craft quality verified; requirement compliance NOT verified"*. If that reads as
142
+ most reviews, the honest reading is that most reviews were previously reporting a
143
+ confidence they had not earned.
144
+
84
145
  Pick dispatch mode based on diff size and environment:
85
146
 
86
147
  - **Sequential** (default, simplest) — run bug-hunter → security-auditor
87
- → test-coverage → code-quality → architecture-review-lens, collect
88
- each verdict
148
+ → test-coverage → code-quality → architecture-review-lens →
149
+ spec-compliance, collect each verdict
89
150
  - **Parallel** — if `subagents.max_parallel` in `.agent-settings.yml` is
90
- ≥ 5 and subagent dispatch is available, run all five concurrently
151
+ ≥ 5 and subagent dispatch is available, run all six concurrently
91
152
  following the `do-in-parallel` pattern in
92
153
  [`subagent-orchestration`](../../skills/subagent-orchestration/SKILL.md);
93
- the five judges operate on the same diff but produce independent
154
+ the six judges operate on the same diff but produce independent
94
155
  reports, so no shared-state risk
95
156
 
96
157
  Each judge returns its own `Judge / Model / Target / Verdict /
@@ -108,14 +169,14 @@ Read `verbosity.offer_council_in_delivery` from `.agent-settings.yml`
108
169
  - `true`: when `ai_council.enabled: true` **and** at least one member
109
170
  enabled, ask (in the user's language):
110
171
 
111
- > 1. Add an external council review alongside the four internal judges? (billable)
172
+ > 1. Add an external council review alongside the six internal judges? (billable)
112
173
  > 2. Skip — internal judges only
113
174
 
114
175
  Also suppress when `personal.autonomy: on` (council is billable).
115
176
 
116
177
  If picked **1**:
117
178
 
118
- - Run `/council diff:<base>..<head>` in parallel with the four
179
+ - Run `/council diff:<base>..<head>` in parallel with the six
119
180
  internal judges (or sequentially after them — whichever the
120
181
  dispatch mode picked in step 4 supports).
121
182
  - Treat each council member as one extra "judge" in the consolidated
@@ -140,7 +201,7 @@ Read `subagents.adversarial_council` from `.agent-settings.yml` (default
140
201
  which supplies the four slots, the one-question budget, and the storage line
141
202
  (not persisted: `subagents.adversarial_council` is class C). What only this
142
203
  command knows is *why now*: the diff is high-risk, and the offer is a paid
143
- distinct-model red-team for finding coverage against the five internal judges.
204
+ distinct-model red-team for finding coverage against the six internal judges.
144
205
  - `on`: run it automatically on a high-risk diff.
145
206
 
146
207
  When run, dispatch the
@@ -164,16 +225,36 @@ step 4 (plus any external council blocks from 4b) and produces one report:
164
225
  - A **must-fix / should-fix / advisory** split, each entry tagged with the
165
226
  judge(s) that raised it
166
227
 
228
+ - A **spec-compliance block**, kept apart from that split — the
229
+ `criteria_source` state from step 3b, the per-criterion verdicts, and the
230
+ count of `MISSING` plus `PARTIAL`. See
231
+ [`judge-synthesis` § 4c](../../skills/judge-synthesis/SKILL.md); a spec
232
+ finding never becomes a craft finding.
233
+
167
234
  `judge-synthesis` emits no single quality score and never auto-gates — it
168
235
  structures the verdicts so the next step's decision is informed.
169
236
 
237
+ **The report names its criteria source, always.** A reader six months later
238
+ cannot otherwise tell "we checked the requirements and they were met" from
239
+ "nobody checked" — and those are the two readings a bare `proceed` collapses.
240
+ Where the state is `not_provided`, the overall sentence says *craft quality
241
+ verified; requirement compliance NOT verified*, never a plain pass.
242
+
243
+ **One telemetry line per review** records the structural outcome: which judges
244
+ ran, whether the spec axis was reachable, and whether it changed the
245
+ recommendation. Built and validated by
246
+ `src/scripts/_lib/review_telemetry.ts::buildReviewLine`, which carries no field
247
+ able to hold free-form content and **rejects** an input that adds one. Read the
248
+ window back with `./scripts-run src/scripts/review_axis_report`; it reports
249
+ "nothing observed" and "the axis changed nothing" as two different answers.
250
+
170
251
  ### 6. Decide next steps
171
252
 
172
253
  - If **any** judge returned `reject` → stop; the approach must change
173
254
  before proceeding
174
255
  - If **any** judge returned `revise` → fix 🔴 findings automatically,
175
256
  ask before fixing 🟡 findings, report 🟢 as suggestions
176
- - If all five returned `apply` → the diff is ready; report and stop
257
+ - If all six returned `apply` → the diff is ready; report and stop
177
258
 
178
259
  **Opt-in (never auto-on):** when the `revise` findings are *test-driven*
179
260
  (failing checks, not subjective craft) and you want bounded auto-repair
@@ -218,7 +299,7 @@ Per `verbosity.routine_confirmations` (default `false`):
218
299
  ## Use this command when
219
300
 
220
301
  - Preparing a self-review before opening a PR
221
- - Stress-testing a local branch with the same five lenses a reviewer
302
+ - Stress-testing a local branch with the same six lenses a reviewer
222
303
  would apply
223
304
  - Sanity-checking a diff before handing it to `/create-pr`
224
305
 
@@ -247,6 +328,6 @@ Per `verbosity.routine_confirmations` (default `false`):
247
328
 
248
329
  - **LLM-as-a-Judge** — [arxiv.org/abs/2306.05685](https://arxiv.org/abs/2306.05685)
249
330
  MT-Bench and Chatbot Arena — judging LLM outputs with LLM judges.
250
- This command adapts the pattern by dispatching to four specialized
331
+ This command adapts the pattern by dispatching to six specialized
251
332
  judges (bug, security, tests, quality) instead of a single generic
252
333
  judge, and consolidating their verdicts.
@@ -175,8 +175,8 @@ contract.
175
175
 
176
176
  **Mergeability is per-PR against a recorded base, never a queue property.**
177
177
  When every PR in the estate touches the same generated files — in this
178
- repository `agents/roadmaps-progress.md` and
179
- `src/config/estate-count-budget.json` — making PR *n* mergeable against base
178
+ repository `agents/roadmaps-progress.md` and the archive index — making PR *n*
179
+ mergeable against base
180
180
  SHA `M` says nothing about its state once PR *n−1* advances the base to `M1`.
181
181
  Report "mergeable against base `<SHA>`", never "the queue is mergeable".
182
182
 
@@ -100,6 +100,83 @@ provenance before treating a `false` as a host limitation: on an unrecognized
100
100
  host every field is `default`, which is an absence of knowledge and not a
101
101
  measurement.
102
102
 
103
+ ## Observation protocol — what may be written into a registry row
104
+
105
+ **Added 2026-08-22** (`road-to-host-capability-observations` Phase 1). The
106
+ registry held **one** row while **eight** platform keys were declared, so on
107
+ seven hosts every field was the safe default — and `false` carried two meanings
108
+ at once. This protocol is what makes them distinguishable.
109
+
110
+ ```
111
+ A ROW IS WRITTEN FROM AN OBSERVATION IN A REAL SESSION, NEVER FROM THE HOST'S
112
+ DOCUMENTATION. A CAPABILITY THAT EXISTS ON PAPER AND NOT IN THE SESSION PRODUCES
113
+ A ROUTING DECISION THAT FAILS SILENTLY.
114
+ ```
115
+
116
+ **This is a written criterion a human applies, and deliberately not a script.**
117
+ A prober that decides for itself what counts as evidence is the inference
118
+ `host_capability.ts` already forbids, wearing an executable's authority.
119
+
120
+ ### Per-field criterion — each one transcript-observable
121
+
122
+ | Field | What counts as an observation |
123
+ |---|---|
124
+ | `subagent_spawn` | a dispatch record showing a **child leg with its own turn** — a start and a stop that pair, so the child's duration is measurable rather than inferred |
125
+ | `parallel_spawn` | **two child legs whose intervals overlap** — concurrently open children at one instant, not two children in sequence |
126
+ | `status_polling` | a **completed poll returning a state the parent did not already hold**. A poll that returns what the parent knew is not an observation of polling |
127
+ | `separate_quota_pool` | a **child leg continuing across a parent quota condition** — the parent constrained, the child proceeding |
128
+ | `worker_respawn` | **one task continuing across a killed and re-spawned worker, same task id.** Never inferred from the fact that spawning and killing both exist separately |
129
+
130
+ ### `agent_teams` is out of scope for a row, by construction
131
+
132
+ It is resolved **only** by the live environment probe and is never inferred from
133
+ a host id — `host_capability.ts` states that in the field's own doc comment, and
134
+ records that this repository has observed the flag's documented existence and
135
+ never its shape on any host. So: do not add it to a registry row. Observe it, if
136
+ at all, through the live probe.
137
+
138
+ ### Observed-absent is not the same row as never-looked
139
+
140
+ Both render as `false`, and collapsing them is the defect this protocol opens
141
+ against — a reader then cannot tell whether a capability was tested.
142
+
143
+ - **Never looked** — the field is **absent from the row**. It resolves to the
144
+ safe default, and `describeHostCapabilities` reports its source as `default`.
145
+ This is the state that needs no marker, because the absence *is* the record.
146
+ - **Observed absent** — the field is present and `false`, and carries a
147
+ `<field>_absent` citation in the same doc comment. Present-and-`false` is the
148
+ marker: it is only reachable by someone who wrote it deliberately, and the
149
+ provenance surface then reports `registry` rather than `default` — which is
150
+ exactly the distinction, readable from `routing:doctor` without opening the
151
+ file.
152
+
153
+ ### The four-part citation — a row without all four is not admissible
154
+
155
+ 1. **the host** — the platform key from `hook_manifest.yaml`;
156
+ 2. **the host version** at the time of observation;
157
+ 3. **the transcript or artefact reference** the observation is read from;
158
+ 4. **the date**.
159
+
160
+ Worked example, which a later row can be pattern-matched against:
161
+
162
+ ```
163
+ // subagent_spawn: OBSERVED — claude (Claude Code, Opus 5 1M session, 2026-08-22)
164
+ // agents/runtime/state/subagent-ledger/2026-08.jsonl: 445 `subagent_start`
165
+ // records and 420 stops carrying a measured `duration_ms`, i.e. 420 child legs
166
+ // whose start and stop pair. A measurable child duration is the criterion.
167
+ ```
168
+
169
+ ### Pre-state, pinned so the Phase 2 diff reads as an addition
170
+
171
+ At the commit this protocol landed on, the registry was exactly:
172
+
173
+ ```ts
174
+ const HOST_CAPABILITY_REGISTRY = { claude: { subagent_spawn: true, parallel_spawn: true } };
175
+ ```
176
+
177
+ One host, two fields. Six fields across seven other hosts resolved to the
178
+ all-`false` safe default.
179
+
103
180
  ## Related
104
181
 
105
182
  - [`subagent-configuration`](../subagent-configuration.md) — the `subagents.*`
@@ -159,6 +159,42 @@ preservation test in that rule already splits them the same way:
159
159
  use; the default stays `wait` either way. -->
160
160
 
161
161
 
162
+ ### 2c. `traceability` — requirement to acceptance to evidence, OPTIONAL
163
+
164
+ **Optional for every complexity, including `structural`.** Absent means *not
165
+ declared*, which is a different claim from "there is no requirement". Nothing
166
+ fails for omitting it, and no gate enforces it — the only consumer is a listing
167
+ inventory (`check_requirements_trace`) that exits 0 always. Requiredness is
168
+ deliberately **not** decided here (AI council 2/2, 2026-08-22): deciding it
169
+ before the listing phase has produced a count is deciding it on intuition.
170
+
171
+ A repeated **row**, not three flat fields:
172
+
173
+ ```yaml
174
+ traceability:
175
+ - requirement_id: pointer-reaches-the-worker
176
+ acceptance_id: pointer-under-line-cap
177
+ evidence_refs:
178
+ - src/scripts/dispatch_r2_reviewer.ts
179
+ ```
180
+
181
+ Three flat top-level fields would be **countable but not traceable**: with more
182
+ than one requirement and a shared pool of refs the relation is an ambiguous
183
+ many-to-many, so a gate could report a populated count while providing no
184
+ dependable trace. The row carries the relation.
185
+
186
+ Both grammars are reused, never invented — the claim-ledger kebab slug
187
+ (`docs/CLAIMS.md` § Entry schema) for the two ids, and the envelope ref-token
188
+ rule (`_lib/subagent_response.ts`) for `evidence_refs`: ref tokens, never
189
+ bodies.
190
+
191
+ Identity semantics, revision semantics, the three senses of "gate" and the
192
+ `[AC:<id>]` convention — including the verified fact that nothing parses
193
+ `verify:` lines structurally today — are in
194
+ [`guideline:agent-infra/traceability-field-mechanics`](../../../../docs/guidelines/agent-infra/traceability-field-mechanics.md).
195
+ They are not optional reading: a slug grammar does not imply a namespace, and
196
+ `evidence_refs` are syntactically safe tokens rather than verified evidence.
197
+
162
198
  ## 3. Grants activated by acceptance
163
199
 
164
200
  | Grant | Boundary |
@@ -31,6 +31,86 @@ orchestrator synthesises and re-verifies.
31
31
  chars (~3k tokens against a measured ~251k spawn floor). Constants + checks:
32
32
  `src/scripts/_lib/subagent_response.ts`.
33
33
 
34
+ ## The canonical shape, and the three-way divergence it resolves
35
+
36
+ **Reconciled 2026-08-22** (`road-to-subagent-envelope-adoption` Phase 0, AI
37
+ council 2/2 convergent). One contract existed in three mutually inconsistent
38
+ states, and the ledger's `0 valid envelopes of 1,845 stops` could not be cited
39
+ as evidence about any one of them, because a reader had to assume which.
40
+
41
+ ```
42
+ THE BODY SCHEMA IS `validateResponse`'S FIVE REQUIRED FIELDS. PRODUCERS CONVERGE
43
+ ON IT; THE VALIDATOR IS NOT WIDENED TO THE UNION OF WHAT PRODUCERS HAPPEN TO SEND.
44
+ ```
45
+
46
+ Direction chosen: **narrow the contract to what the validator already accepts**,
47
+ not widen the validator. Widening makes every future divergence legal by
48
+ construction, which is how one contract came to have three states.
49
+
50
+ ### Per-field divergence, one row per field
51
+
52
+ | Field | spawn-contract (f) | `team_dispatch` model JSON (`:297`) | `subagent_response.ts` validator | `dispatch_r2_reviewer` |
53
+ |---|---|---|---|---|
54
+ | `summary` | — (not a field clause) | present | **required** | silent |
55
+ | `handoff` | — | **absent** | **required** | silent |
56
+ | `confidence` | — | **absent** | **required** (`low\|medium\|high`) | silent |
57
+ | `findings` | — | present, `{severity, evidence, suggested_fix, location}` | **required**, `{title, evidence_refs?, mutating?}` | silent |
58
+ | `risks` | — | **absent** | **required** | silent |
59
+ | `artifact_paths` | — | absent | optional | silent |
60
+ | `assumptions` | — | absent | optional | silent |
61
+ | `status` | — | present, 3 model values | **not a body field** | silent |
62
+
63
+ An empty cell is a finding, not a pass. `dispatch_r2_reviewer` is silent on
64
+ every row — 1,292 lines with **0** hits for `envelope`, `DONE_WITH_CONCERNS`,
65
+ `response.contract` or `response_contract`.
66
+
67
+ ### Four boundaries, because "the contract" was conflating them
68
+
69
+ The council's sharpest correction: schema, lifecycle state, delivery and
70
+ classification were being treated as one thing.
71
+
72
+ 1. **Body schema** — the five required fields above, plus the two optional ones.
73
+ 2. **Lifecycle frame** — `DONE` / `DONE_WITH_CONCERNS` / `NEEDS_CONTEXT` from a
74
+ model, plus `BLOCKED` orchestrator-side only (`team_dispatch.ts:391`'s
75
+ `_MODEL_STATUSES` is the three-value set a model may send). **Not a body
76
+ field**, which is why narrowing the body does not delete it.
77
+ 3. **Delivery protocol** — serialize once, persist that exact serialization,
78
+ then emit the identical value as the final text-only message. This is
79
+ spawn-contract rule (f), and it **survives the narrowing**: its two clauses
80
+ are delivery invariants, not fields, so they were never candidates for the
81
+ validator's five.
82
+ 4. **Classification** — validate and record against an explicit contract and
83
+ classifier version.
84
+
85
+ ### Who owns the delivery protocol — and it is not the model
86
+
87
+ `team_dispatch.ts:280` asks a **read-only** model with no command or filesystem
88
+ access to return its review JSON. That model **cannot** satisfy "disk copy
89
+ written first", so rule (f) is an impossible prompt obligation for this
90
+ producer. The trusted dispatcher owns it: parse the producer's response, project
91
+ it into the canonical body, add the lifecycle frame, write the durable copy,
92
+ then emit the identical serialized value.
93
+
94
+ Recorded rather than implemented here — the projection adapter is a mechanism
95
+ this roadmap's Phase 1 does not ship, and saying so is cheaper than a contract
96
+ clause nobody can satisfy.
97
+
98
+ ### The validator is not the reason the rate is zero
99
+
100
+ Checked directly on 2026-08-22, because "zero valid envelopes over 1,845 stops"
101
+ has three possible causes and they need separating: a validator that rejects
102
+ valid input, producers that do not emit the fields, or a shape nobody can
103
+ produce.
104
+
105
+ `validateResponse` was run by hand against a minimal envelope
106
+ (`{summary, handoff, confidence, findings: [], risks: []}`) and a rich one
107
+ (with `findings[{title, evidence_refs, mutating}]` and `artifact_paths`).
108
+ **Both validate.** The `team_dispatch` model shape fails on four of five.
109
+
110
+ So the contract is implementable and the validator is correct; the rate is zero
111
+ because producers do not emit the shape. That removes the precondition one
112
+ council seat set before any narrowing.
113
+
34
114
  ## Durable copy — the envelope on disk before the message
35
115
 
36
116
  ```
@@ -69,9 +69,15 @@ delegating unbounded execution of everything the task's data happens to say.
69
69
 
70
70
  No deterministic gate inspects fetched/tool/RAG content for injected
71
71
  instructions — the quarantine is carried by the model (ADR-135 lists it in the
72
- CRITICAL policy class precisely BECAUSE no mechanical backstop exists today;
73
- a future content-scanning hook would be the first candidate to change this).
74
- Counting an adjacent lint here would be coverage inflation, so none is counted.
72
+ CRITICAL policy class). Counting an adjacent lint here would be coverage
73
+ inflation, so none is counted.
74
+
75
+ **A content-scanning hook now exists and this field is unchanged.**
76
+ `injection_scan_hook.ts` is warn-only and default-OFF: it cannot refuse, and a
77
+ hook that cannot refuse does not enforce. This section used to say no such
78
+ backstop existed and that a future one would change the field — the first half
79
+ is now false, the second always was. Detail:
80
+ [`untrusted-input-spotlighting § The content-scanning hook`](../docs/guidelines/agent-infra/untrusted-input-spotlighting.md).
75
81
 
76
82
  Body migrated to [`guideline:agent-infra/untrusted-input-spotlighting`](../docs/guidelines/agent-infra/untrusted-input-spotlighting.md) (per P4 of `road-to-kernel-and-router.md`) — runtime defense protocol (separate / spotlight / refuse role-takeover / no silent egress / untrusted agent-instruction files), hidden-instruction awareness (invisible Unicode + confusables), injection-signal taxonomy, least-agency → existing-gate OWASP mapping.
77
83
  Trigger-set above activates this routing on demand, independent of the discipline profile (ADR-110).