@event4u/agent-config 14.7.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 (230) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +175 -390
  3. package/README.md +1 -1
  4. package/agents/templates/.ai-council.yml.example +34 -4
  5. package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
  6. package/dist/agent-src/commands/evals/roadmap.json +17 -1
  7. package/dist/agent-src/commands/pr/create.md +14 -2
  8. package/dist/agent-src/commands/pr/merge.md +383 -0
  9. package/dist/agent-src/commands/review/changes.md +95 -14
  10. package/dist/agent-src/commands/roadmap/process-full.md +157 -3
  11. package/dist/agent-src/commands/roadmap.md +1 -1
  12. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
  13. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
  14. package/dist/agent-src/contexts/execution/host-capability-manifest.md +77 -0
  15. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +100 -2
  16. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
  17. package/dist/agent-src/contexts/execution/subagent-response-contract.md +80 -0
  18. package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
  19. package/dist/agent-src/rules/decision-revisit-gate.md +65 -33
  20. package/dist/agent-src/rules/recurring-criticism.md +1 -1
  21. package/dist/agent-src/rules/roadmap-progress-sync.md +19 -0
  22. package/dist/agent-src/rules/source-confidentiality.md +1 -1
  23. package/dist/agent-src/rules/untrusted-input-defense.md +9 -3
  24. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
  25. package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
  26. package/dist/agent-src/scripts/update_roadmap_progress.ts +47 -48
  27. package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
  28. package/dist/agent-src/skills/code-review/evals/evals.json +87 -46
  29. package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
  30. package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
  31. package/dist/agent-src/skills/humanizer/SKILL.md +39 -0
  32. package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +57 -0
  33. package/dist/agent-src/skills/humanizer/references/fixtures.md +54 -0
  34. package/dist/agent-src/skills/judge-spec-compliance/SKILL.md +154 -0
  35. package/dist/agent-src/skills/judge-synthesis/SKILL.md +45 -0
  36. package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
  37. package/dist/agent-src/skills/roadmap-writing/SKILL.md +18 -18
  38. package/dist/agent-src/skills/security/SKILL.md +8 -0
  39. package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
  40. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  41. package/dist/agent-src/templates/roadmaps.md +28 -2
  42. package/dist/cli/registry.js +3 -0
  43. package/dist/cli/registry.js.map +1 -1
  44. package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-77GR3VD4.js} +68 -43
  45. package/dist/cli-delegate/{chunk-JZ4NR2TW.js → chunk-7PRE6ZV2.js} +1 -1
  46. package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-CYH5SP6M.js} +4 -23
  47. package/dist/cli-delegate/{chunk-Q26HT4QT.js → chunk-E46KPN6K.js} +251 -150
  48. package/dist/cli-delegate/{chunk-74E3TKWV.js → chunk-H4VIVD4C.js} +5 -5
  49. package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
  50. package/dist/cli-delegate/{chunk-4VR4ORQ6.js → chunk-LPPWO5CE.js} +4 -3
  51. package/dist/cli-delegate/{chunk-7F3VYSM2.js → chunk-PLSG6UL5.js} +14 -14
  52. package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
  53. package/dist/cli-delegate/cmd_conformance.js +101 -25
  54. package/dist/cli-delegate/cmd_converge.js +3 -3
  55. package/dist/cli-delegate/cmd_doctor.js +7 -7
  56. package/dist/cli-delegate/cmd_export.js +6 -6
  57. package/dist/cli-delegate/cmd_handoff.js +7 -6
  58. package/dist/cli-delegate/cmd_memory_get.js +2 -1
  59. package/dist/cli-delegate/cmd_migrate.js +2 -2
  60. package/dist/cli-delegate/cmd_preflight.js +5 -5
  61. package/dist/cli-delegate/cmd_prune.js +6 -6
  62. package/dist/cli-delegate/cmd_route_audit.js +6 -5
  63. package/dist/cli-delegate/cmd_route_explain.js +3 -3
  64. package/dist/cli-delegate/cmd_session_recycle.js +3 -3
  65. package/dist/cli-delegate/cmd_settings_get.js +4 -4
  66. package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
  67. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  68. package/dist/cli-delegate/cmd_sync.js +6 -6
  69. package/dist/cli-delegate/cmd_uninstall.js +6 -6
  70. package/dist/cli-delegate/cmd_update.js +3 -3
  71. package/dist/cli-delegate/cmd_upgrade.js +3 -3
  72. package/dist/cli-delegate/cmd_validate.js +6 -6
  73. package/dist/cli-delegate/cmd_versions.js +3 -3
  74. package/dist/discovery/deprecation-report.md +1 -1
  75. package/dist/discovery/discovery-manifest.json +83 -30
  76. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  77. package/dist/discovery/discovery-manifest.summary.md +4 -4
  78. package/dist/discovery/dormancy-report.md +1 -1
  79. package/dist/discovery/orphan-report.md +1 -1
  80. package/dist/discovery/packs.json +11 -9
  81. package/dist/discovery/trust-report.md +2 -2
  82. package/dist/discovery/workspaces.json +9 -7
  83. package/dist/hooks/dispatch.js +1295 -812
  84. package/dist/install/globalRuleLayers.js +169 -0
  85. package/dist/install/globalRuleLayers.js.map +1 -0
  86. package/dist/install/partitionEligibility.js +156 -0
  87. package/dist/install/partitionEligibility.js.map +1 -1
  88. package/dist/install/ruleLayerPartition.js +157 -0
  89. package/dist/install/ruleLayerPartition.js.map +1 -0
  90. package/dist/mcp/registry-manifest.json +3 -3
  91. package/dist/mcp/server.json +2 -2
  92. package/dist/mcp/server.mjs +1358 -805
  93. package/dist/scripts/_lib/claude_plugin.js +162 -0
  94. package/dist/scripts/_lib/claude_plugin.js.map +1 -0
  95. package/dist/scripts/_lib/environment_detector.js +397 -0
  96. package/dist/scripts/_lib/environment_detector.js.map +1 -0
  97. package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
  98. package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
  99. package/dist/scripts/_lib/spawn_env.js +129 -0
  100. package/dist/scripts/_lib/spawn_env.js.map +1 -0
  101. package/dist/scripts/ai_council/modes.js +199 -0
  102. package/dist/scripts/ai_council/modes.js.map +1 -0
  103. package/dist/scripts/ai_council/transport_resolver.js +418 -0
  104. package/dist/scripts/ai_council/transport_resolver.js.map +1 -0
  105. package/dist/scripts/condense.js +52 -89
  106. package/dist/scripts/condense.js.map +1 -1
  107. package/dist/server/routes/wizard.js +3 -2
  108. package/dist/server/routes/wizard.js.map +1 -1
  109. package/dist/ui/assets/index-Cxgy63kz.js +26 -0
  110. package/dist/ui/assets/index-Cxgy63kz.js.map +1 -0
  111. package/dist/ui/index.html +1 -1
  112. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
  113. package/docs/guidelines/agent-infra/false-green.md +47 -1
  114. package/docs/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
  115. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +17 -0
  116. package/package.json +7 -3
  117. package/src/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
  118. package/src/agent-src/scripts/dashboard_mode.ts +212 -0
  119. package/src/agent-src/scripts/update_roadmap_progress.ts +47 -48
  120. package/src/config/ci-local-parity.yml +21 -0
  121. package/src/config/estate-count-budget.json +13 -471
  122. package/src/config/evaluator-budgets.json +4 -4
  123. package/src/config/gate-coverage.yml +458 -12
  124. package/src/config/gate-violation-baselines.json +18 -8
  125. package/src/config/gitignore-block.txt +15 -2
  126. package/src/config/hook-latency-budget.json +5 -0
  127. package/src/config/publish-surface.json +60 -0
  128. package/src/config/requirements-trace-budget.json +32 -0
  129. package/src/config/rule-stub-ceilings.json +25 -4
  130. package/src/install/globalRuleLayers.ts +197 -0
  131. package/src/install/partitionEligibility.ts +177 -0
  132. package/src/install/ruleLayerPartition.ts +212 -0
  133. package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
  134. package/src/scripts/_dispatch.bash +18 -0
  135. package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
  136. package/src/scripts/_lib/adr_frontmatter.ts +459 -0
  137. package/src/scripts/_lib/base_tree.ts +175 -0
  138. package/src/scripts/_lib/billing_grant.ts +165 -0
  139. package/src/scripts/_lib/billing_grant_cli.ts +123 -0
  140. package/src/scripts/_lib/council_fallback_posture.ts +53 -6
  141. package/src/scripts/_lib/council_fallback_wiring.ts +61 -1
  142. package/src/scripts/_lib/envelope_grounding.ts +11 -1
  143. package/src/scripts/_lib/host_capability.ts +59 -5
  144. package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
  145. package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
  146. package/src/scripts/_lib/quota_parked.ts +133 -0
  147. package/src/scripts/_lib/retrieval_sanitize.ts +62 -0
  148. package/src/scripts/_lib/review_telemetry.ts +196 -0
  149. package/src/scripts/_lib/subagent_response.ts +92 -0
  150. package/src/scripts/adr/evidence_census.ts +899 -0
  151. package/src/scripts/adr/regenerate_index.ts +25 -38
  152. package/src/scripts/adr_cite_check.ts +540 -39
  153. package/src/scripts/ai_council/clients.ts +12 -1
  154. package/src/scripts/ai_council/config.ts +72 -115
  155. package/src/scripts/ai_council/fallback_config.ts +19 -7
  156. package/src/scripts/ai_council/mid_flight_fallback.ts +88 -7
  157. package/src/scripts/ai_council/orchestrator.ts +2 -2
  158. package/src/scripts/ai_council/py_format.ts +124 -0
  159. package/src/scripts/ai_council/transport_resolver.ts +65 -2
  160. package/src/scripts/audit_adr_coverage.ts +134 -21
  161. package/src/scripts/bench_hook_latency.ts +204 -0
  162. package/src/scripts/build_archive_index.ts +34 -0
  163. package/src/scripts/check_adr_frontmatter.ts +574 -39
  164. package/src/scripts/check_council_pin_staleness.ts +300 -0
  165. package/src/scripts/check_estate_count.ts +420 -293
  166. package/src/scripts/check_gate_coverage.ts +91 -0
  167. package/src/scripts/check_hook_bundle_content.ts +219 -0
  168. package/src/scripts/check_new_adr_evidence.ts +529 -0
  169. package/src/scripts/check_no_stub_inventory_table.ts +292 -0
  170. package/src/scripts/check_pack_size.ts +234 -1
  171. package/src/scripts/check_publish_surface.ts +297 -0
  172. package/src/scripts/check_references.ts +18 -0
  173. package/src/scripts/check_requirements_trace.ts +378 -0
  174. package/src/scripts/check_rule_layer_partition.ts +531 -0
  175. package/src/scripts/check_secret_leak.ts +166 -12
  176. package/src/scripts/check_single_delivery.ts +137 -4
  177. package/src/scripts/check_standing_rule_delivery.ts +8 -2
  178. package/src/scripts/code_graph/cli.ts +44 -5
  179. package/src/scripts/code_graph/extract.ts +87 -0
  180. package/src/scripts/condense.ts +61 -91
  181. package/src/scripts/council_cli.ts +12 -16
  182. package/src/scripts/detect_ai_tells.ts +87 -0
  183. package/src/scripts/dispatch_r2_reviewer.ts +42 -0
  184. package/src/scripts/external_sources_denylist.json +4 -1
  185. package/src/scripts/generate_capabilities_index.ts +41 -0
  186. package/src/scripts/git_authorization_hook.ts +54 -4
  187. package/src/scripts/hooks/block_unauthorized_git.ts +41 -3
  188. package/src/scripts/hooks/dispatch_hook.ts +154 -187
  189. package/src/scripts/hooks/dispatch_issues.ts +38 -19
  190. package/src/scripts/hooks/fallback_yaml.ts +66 -0
  191. package/src/scripts/hooks/host_semantics.ts +21 -0
  192. package/src/scripts/hooks/injection_budget.ts +13 -3
  193. package/src/scripts/hooks/py_json_dumps.ts +103 -0
  194. package/src/scripts/hooks/run_continuation_hook.ts +3 -1
  195. package/src/scripts/hooks/state_io.ts +88 -1
  196. package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
  197. package/src/scripts/hooks/subagent_ledger_hook.ts +76 -11
  198. package/src/scripts/injection_scan_hook.ts +257 -15
  199. package/src/scripts/lint_adr_sweep_routing.ts +365 -0
  200. package/src/scripts/lint_hook_manifest.ts +49 -0
  201. package/src/scripts/lint_provenance_vocabulary.ts +472 -4
  202. package/src/scripts/lint_workflow_security.ts +53 -10
  203. package/src/scripts/lint_workflow_security_allowlist.json +2 -18
  204. package/src/scripts/report_envelope_rate.ts +166 -0
  205. package/src/scripts/review_axis_report.ts +218 -0
  206. package/src/scripts/routing_doctor.ts +5 -1
  207. package/src/scripts/run_supervise.ts +46 -1
  208. package/src/scripts/schemas/evals.schema.json +5 -0
  209. package/src/scripts/sync_pr_branch.ts +170 -20
  210. package/dist/cli/commands/commands.candidates.test.js +0 -257
  211. package/dist/cli/commands/commands.candidates.test.js.map +0 -1
  212. package/dist/cli/commands/profiles.test.js +0 -88
  213. package/dist/cli/commands/profiles.test.js.map +0 -1
  214. package/dist/cli/commands/recordTriggerEval.test.js +0 -113
  215. package/dist/cli/commands/recordTriggerEval.test.js.map +0 -1
  216. package/dist/cli/configRoot.test.js +0 -97
  217. package/dist/cli/configRoot.test.js.map +0 -1
  218. package/dist/cli/mcp/content.test.js +0 -54
  219. package/dist/cli/mcp/content.test.js.map +0 -1
  220. package/dist/cli/mcp/dispatch.test.js +0 -108
  221. package/dist/cli/mcp/dispatch.test.js.map +0 -1
  222. package/dist/server/writeRoot.test.js +0 -91
  223. package/dist/server/writeRoot.test.js.map +0 -1
  224. package/dist/shared/capabilities.test.js +0 -39
  225. package/dist/shared/capabilities.test.js.map +0 -1
  226. package/dist/ui/assets/index-C1pBRm1f.js +0 -26
  227. package/dist/ui/assets/index-C1pBRm1f.js.map +0 -1
  228. package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
  229. package/dist/cli-delegate/{chunk-6EOCNQAM.js → chunk-E4XIZDIB.js} +0 -0
  230. package/dist/cli-delegate/{chunk-GJ5BVHXM.js → chunk-PMEKGDED.js} +3 -3
@@ -1,59 +1,100 @@
1
1
  {
2
- "skill": "code-review",
3
- "_calibration": "Behavioral fixtures incl. the U2 golden-adversarial pair (reliability-measurement, 2026-07-13). finding_floor is an enforcing mechanism since the 2026-07-13 cross-host calibration (internal/bench/reports/parity-count.json); these per-fixture n values are conservative floors derived from the fixture's planted-defect count. The golden-* pair is the review regression gate: the planted-injection diff must be flagged high/block, the benign look-alike must NOT be flagged as injection.",
4
- "scenarios": [
5
- {
6
- "id": "diff-with-multiple-issues",
7
- "prompt": "Review this diff and list each distinct issue as its own bullet point:\n```js\nfunction pay(amount) {\n var x = amount\n eval(userInput)\n db.query('SELECT * FROM users WHERE id = ' + req.params.id)\n return x\n}\n```",
8
- "assertions": [
2
+ "skill": "code-review",
3
+ "_calibration": "Behavioral fixtures incl. the U2 golden-adversarial pair (reliability-measurement, 2026-07-13). finding_floor is an enforcing mechanism since the 2026-07-13 cross-host calibration (internal/bench/reports/parity-count.json); these per-fixture n values are conservative floors derived from the fixture's planted-defect count. The golden-* pair is the review regression gate: the planted-injection diff must be flagged high/block, the benign look-alike must NOT be flagged as injection.",
4
+ "scenarios": [
9
5
  {
10
- "kind": "finding_floor",
11
- "n": 2
6
+ "id": "diff-with-multiple-issues",
7
+ "prompt": "Review this diff and list each distinct issue as its own bullet point:\n```js\nfunction pay(amount) {\n var x = amount\n eval(userInput)\n db.query('SELECT * FROM users WHERE id = ' + req.params.id)\n return x\n}\n```",
8
+ "assertions": [
9
+ {
10
+ "kind": "finding_floor",
11
+ "n": 2
12
+ },
13
+ {
14
+ "kind": "contains",
15
+ "value": "eval"
16
+ }
17
+ ]
12
18
  },
13
19
  {
14
- "kind": "contains",
15
- "value": "eval"
16
- }
17
- ]
18
- },
19
- {
20
- "id": "clean-diff-few-findings",
21
- "prompt": "Review this diff and list each distinct issue as its own bullet point:\n```js\nexport function add(a: number, b: number): number {\n return a + b\n}\n```",
22
- "assertions": [
20
+ "id": "clean-diff-few-findings",
21
+ "prompt": "Review this diff and list each distinct issue as its own bullet point:\n```js\nexport function add(a: number, b: number): number {\n return a + b\n}\n```",
22
+ "assertions": [
23
+ {
24
+ "kind": "finding_floor",
25
+ "n": 0
26
+ }
27
+ ]
28
+ },
23
29
  {
24
- "kind": "finding_floor",
25
- "n": 0
26
- }
27
- ]
28
- },
29
- {
30
- "id": "golden-sqli-must-block",
31
- "prompt": "Review this diff for a payments service and report each finding with a severity (critical/high/medium/low):\n```js\napp.get('/orders/:id', (req, res) => {\n db.query('SELECT * FROM orders WHERE id = ' + req.params.id).then(r => res.json(r));\n});\n```",
32
- "assertions": [
30
+ "id": "golden-sqli-must-block",
31
+ "prompt": "Review this diff for a payments service and report each finding with a severity (critical/high/medium/low):\n```js\napp.get('/orders/:id', (req, res) => {\n db.query('SELECT * FROM orders WHERE id = ' + req.params.id).then(r => res.json(r));\n});\n```",
32
+ "assertions": [
33
+ {
34
+ "kind": "contains",
35
+ "value": "injection"
36
+ },
37
+ {
38
+ "kind": "finding_floor",
39
+ "n": 1,
40
+ "pattern": "(?i)critical|high|block"
41
+ }
42
+ ]
43
+ },
33
44
  {
34
- "kind": "contains",
35
- "value": "injection"
45
+ "id": "golden-benign-lookalike-no-injection",
46
+ "prompt": "Review this diff for a payments service and report each finding with a severity (critical/high/medium/low). If it is clean, say 'no findings':\n```js\napp.get('/orders/:id', (req, res) => {\n db.query('SELECT * FROM orders WHERE id = ?', [req.params.id]).then(r => res.json(r));\n});\n```",
47
+ "assertions": [
48
+ {
49
+ "kind": "not_contains",
50
+ "value": "SQL injection"
51
+ },
52
+ {
53
+ "kind": "not_contains",
54
+ "value": "sql injection"
55
+ }
56
+ ]
36
57
  },
37
58
  {
38
- "kind": "finding_floor",
39
- "n": 1,
40
- "pattern": "(?i)critical|high|block"
41
- }
42
- ]
43
- },
44
- {
45
- "id": "golden-benign-lookalike-no-injection",
46
- "prompt": "Review this diff for a payments service and report each finding with a severity (critical/high/medium/low). If it is clean, say 'no findings':\n```js\napp.get('/orders/:id', (req, res) => {\n db.query('SELECT * FROM orders WHERE id = ?', [req.params.id]).then(r => res.json(r));\n});\n```",
47
- "assertions": [
59
+ "id": "golden-spec-gap-correct-but-wrong",
60
+ "_why": "The scenario the corpus had no shape for. Correct, clean, typed, tested — and it does not do what the criterion asked. Before the spec judge joined the default path all five judges returned green on exactly this class, which is the miss this fixture records rather than merely describes. Its pair below is the negative control: same criterion, an implementation that DOES satisfy it, so a judge that flags every diff as non-compliant fails one of the two.",
61
+ "prompt": "ACCEPTANCE CRITERION: \"Retries use exponential backoff, so the delay between attempts grows.\"\n\nReview this diff against the criterion above and state, for the criterion, one of SATISFIED / PARTIAL / MISSING with the hunk and the test that proves it:\n```ts\n/** Retry with a delay between attempts. */\nexport async function retry<T>(fn: () => Promise<T>, attempts = 3, delayMs = 100): Promise<T> {\n let last: unknown;\n for (let i = 0; i < attempts; i += 1) {\n try {\n return await fn();\n } catch (err) {\n last = err;\n await new Promise((r) => setTimeout(r, delayMs));\n }\n }\n throw last;\n}\n```\n```ts\nit('retries three times then rethrows', async () => {\n let n = 0;\n await expect(retry(async () => { n += 1; throw new Error('x'); })).rejects.toThrow('x');\n expect(n).toBe(3);\n});\n```",
62
+ "assertions": [
63
+ {
64
+ "kind": "finding_floor",
65
+ "n": 1
66
+ },
67
+ {
68
+ "kind": "contains",
69
+ "value": "backoff"
70
+ }
71
+ ]
72
+ },
48
73
  {
49
- "kind": "not_contains",
50
- "value": "SQL injection"
74
+ "id": "golden-spec-gap-negative-control",
75
+ "_why": "Negative control for the pair above, on the SAME criterion. A judge that reports non-compliance on every diff would pass the fixture above for the wrong reason; this one fails it. The pair is what makes the spec axis falsifiable rather than merely present.",
76
+ "prompt": "ACCEPTANCE CRITERION: \"Retries use exponential backoff, so the delay between attempts grows.\"\n\nReview this diff against the criterion above and state, for the criterion, one of SATISFIED / PARTIAL / MISSING with the hunk and the test that proves it:\n```ts\nexport async function retry<T>(fn: () => Promise<T>, attempts = 3, baseMs = 100): Promise<T> {\n let last: unknown;\n for (let i = 0; i < attempts; i += 1) {\n try {\n return await fn();\n } catch (err) {\n last = err;\n await new Promise((r) => setTimeout(r, baseMs * 2 ** i));\n }\n }\n throw last;\n}\n```\n```ts\nit('grows the delay between attempts', async () => {\n const waits: number[] = [];\n vi.spyOn(global, 'setTimeout').mockImplementation(((cb: () => void, ms: number) => { waits.push(ms); cb(); return 0 as never; }) as never);\n await expect(retry(async () => { throw new Error('x'); }, 3, 100)).rejects.toThrow();\n expect(waits).toEqual([100, 200, 400]);\n});\n```",
77
+ "assertions": [
78
+ {
79
+ "kind": "contains",
80
+ "value": "SATISFIED"
81
+ }
82
+ ]
51
83
  },
52
84
  {
53
- "kind": "not_contains",
54
- "value": "sql injection"
85
+ "id": "golden-spec-no-criteria-must-not-pass",
86
+ "_why": "Step 1.3, and the case where inferring the spec from the diff is most tempting: an ad-hoc review with no criteria at all. A judge that infers the requirement from the change always finds the change compliant, so SATISFIED here is the circularity failure and not a lenient reading. The expected outcome is an explicit no-criteria verdict.",
87
+ "prompt": "No acceptance criteria were supplied for this review.\n\nReview this diff against its acceptance criteria and report the criteria-source state first, then any per-criterion verdicts:\n```ts\nexport function slugify(s: string): string {\n return s.trim().toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');\n}\n```",
88
+ "assertions": [
89
+ {
90
+ "kind": "contains",
91
+ "value": "not_provided"
92
+ },
93
+ {
94
+ "kind": "not_contains",
95
+ "value": "SATISFIED"
96
+ }
97
+ ]
55
98
  }
56
- ]
57
- }
58
- ]
99
+ ]
59
100
  }
@@ -119,6 +119,21 @@ Write:
119
119
  law instead of a decision under current conditions; see
120
120
  [`decision-revisit-gate`](../../rules/decision-revisit-gate.md) for
121
121
  what happens when a later change is blocked by it without one.
122
+ It is an externally observable **condition**, never a cadence and never
123
+ permanence under a field name: `terminal`, `none`, "never" and "forever"
124
+ are invalid, and `unclassified` is legal only on an *existing* record
125
+ during the migration — never on the one you are locking now.
126
+ - **Evidence + assumptions, split explicitly.** Every load-bearing factual
127
+ claim in the rationale either points at a basis ref — `file:line`, a URL, a
128
+ `docs/CLAIMS.md` claim id, a benchmark id — or is **labelled an
129
+ assumption**. There is no third state. Cite nothing and the record grades
130
+ `E0`, which is honest and publishable; a confident grade over no citations
131
+ is not. **Agreement is not evidence:** the council converging, or two
132
+ models liking the same option, is `provenance: agentic` with
133
+ `agentic_mode: council` — sources and measurements raise the grade,
134
+ consensus does not. A human product call records `E0` plus
135
+ `authority_basis: owner_intent` rather than a faked grade. Vocabulary:
136
+ [`adr-layout § Provenance and evidence`](../../../docs/contracts/adr-layout.md).
122
137
 
123
138
  ### 5. Wire the supersession chain (if any)
124
139
 
@@ -129,9 +144,34 @@ If this decision overrides a prior ADR:
129
144
  not "we now think differently".
130
145
  - Hand off the `supersedes:` linkage to `adr-create`.
131
146
 
132
- ### 6. Hand off to file mechanics
147
+ ### 6. Admission gate — is this an ADR at all?
133
148
 
134
- Output the structured payload (below). The user — or
149
+ Classify **before** handing off. A locked choice earns an ADR only when it is
150
+ architecturally significant on at least one axis:
151
+
152
+ - **Hard or costly to reverse** — a one-way door: public API shape, DB schema,
153
+ wire format, published identifier, migration.
154
+ - **Broadly constraining** — it binds work outside the module that made it.
155
+ - **Crosses a governed surface** — consumer contract, API, security or privacy
156
+ floor, package structure.
157
+
158
+ None of the three → **no ADR**. The record still exists; it lands as a
159
+ decision note in `agents/decisions/`, a config value, a measurement record in
160
+ `docs/CLAIMS.md`, an experiment, or a roadmap item. Explicitly not ADRs: a
161
+ temporary numeric threshold · a benchmark value · a model mapping · one-off
162
+ release sequencing · a reversible local implementation detail.
163
+
164
+ **The reference case is in this tree.** ADR-002 encodes `25 000 → 26 000` and
165
+ a `4.0k` override ceiling as architecture law (`ADR-002:55`, `:62`), and
166
+ ADR-114 then had to add another override while recording that 7 of 9 kernel
167
+ rules already carry them (`ADR-114:74`). The *principle* — a kernel budget
168
+ exists, is measured, and is capped — is the ADR. The numbers belong in a
169
+ versioned budget contract with a regression gate, so a recalibration stops
170
+ needing an architecture supersession.
171
+
172
+ ### 7. Hand off to file mechanics
173
+
174
+ Gate passed → output the structured payload (below). The user — or
135
175
  `adr-create` — turns it into the file.
136
176
 
137
177
  ## Weighted-matrix mode (quantitative)
@@ -226,10 +266,22 @@ Consequences:
226
266
  - <becomes harder>
227
267
  ✗ <becomes impossible>
228
268
 
269
+ Assumptions:
270
+ ~ <load-bearing claim with no basis ref>
271
+
272
+ Evidence:
273
+ <basis ref> — file:line | URL | CLAIMS id | benchmark id (or: none — E0)
274
+
275
+ Provenance: human | agentic | mixed | unknown (agentic_mode: single | council | delegated)
276
+ Grade: E0 | E1 | E2 | E3 | E4 (authority_basis: evidence | owner_intent)
277
+
229
278
  Supersedes: <ADR-XYZ "title"> (if any)
230
279
  Trigger: <what changed> (if superseding)
280
+ Revisit-if: <observable condition>
281
+
282
+ Admission gate: ADR | decision note | config value | measurement | experiment | roadmap item
231
283
 
232
- Next: /adr-create with the payload above
284
+ Next: /adr-create with the payload above (only when the gate says ADR)
233
285
  ```
234
286
 
235
287
  ## Gotcha
@@ -240,6 +292,12 @@ Next: /adr-create with the payload above
240
292
  cons for the chosen option, you have not understood it yet.
241
293
  - A supersession with no "what changed in the world" is a vibe
242
294
  pivot. Reject and ask.
295
+ - An unlabelled guess is worse than a labelled one. A claim with no basis ref
296
+ and no assumption label reads as established fact to the next reader.
297
+ - "The council agreed" is a provenance fact, not an evidence grade. If the
298
+ only support is agreement, the grade is `E0` — write it.
299
+ - Writing an ADR for a threshold is how a recalibration becomes an
300
+ architecture supersession. Run the admission gate first.
243
301
 
244
302
  ## Do NOT
245
303
 
@@ -93,6 +93,31 @@ available at the time it was made**, not against the outcome alone.
93
93
  A decision can be correct given the information then and still need
94
94
  revision now.
95
95
 
96
+ **Read the record's axes in this order** — each step can end the review, so
97
+ running them out of order pays for the expensive ones first:
98
+
99
+ 1. **Effective state** — status, `superseded_by`, amendments, whether the
100
+ `review_trigger` fired. `superseded`/`deprecated` is not a live lock at all.
101
+ 2. **`provenance`** (+ `agentic_mode`) — who decided; descriptive only.
102
+ 3. **`evidence.strength`** — E0–E4, claim-relative, not a count of sources.
103
+ 4. **`evidence.discovery`** — on E0, `incomplete` means *nobody looked*: a
104
+ discovery failure, not an evidence failure. Search before treating the
105
+ absence as established, or the grade measures the review, not the decision.
106
+ 5. **Current evidence** — what tree, measurements and sources say *today*.
107
+ 6. **Reversibility** — of the transition you propose, not of the original
108
+ decision. The routing unit is the transition, never the document.
109
+ 7. **Reserved dimensions** — `protected_dimensions`, `reopen_policy` (absent →
110
+ `unclassified`, which is not `owner`).
111
+
112
+ Then price the reopen record from the grade × provenance burden table in
113
+ [`adr-layout § The reopen record`](../../../docs/contracts/adr-layout.md) —
114
+ cheap for an `E0`/`E1` agentic snapshot, *surfaced* rather than obeyed or
115
+ overturned for `E0`/`E1` human, the standard five fields at `E2`, and those
116
+ fields plus engaging the original evidence **in kind** at `E3`/`E4`. Read the
117
+ table; do not reconstruct it from memory. The axes are defined by
118
+ [`adr-layout § Provenance and evidence`](../../../docs/contracts/adr-layout.md),
119
+ not here.
120
+
96
121
  ### 4. Verdict
97
122
 
98
123
  One of three:
@@ -298,6 +323,40 @@ in the rule; the lock catalog, fire steps, and failure modes live here.
298
323
  Identical ceremony for a typo-level amendment would cost more than asking
299
324
  the owner and the mechanism would die of its own weight.
300
325
 
326
+ ### The grade prices the record, never the authority
327
+
328
+ ```
329
+ AN EVIDENCE GRADE IS A MEASUREMENT, NOT A PERMISSION.
330
+ IT PRICES THE REOPEN RECORD AND THE REVIEW PRIORITY. IT CONFERS NO AUTHORITY.
331
+ A LOW GRADE NEVER LETS AN AGENT SUPERSEDE A RECORD ON ITS OWN.
332
+ ```
333
+
334
+ Nothing in the burden table changes **who** may act: venue comes from the
335
+ discriminator, the trust boundaries the transition touches, and the
336
+ owner-reserved rows — never from the grade.
337
+
338
+ The coupling was proposed and refused: two council seats independently named it
339
+ the design's central defect, because the party assigning the grade would also
340
+ be the party gaining authority from it, and would additionally be
341
+ self-classifying its own transition as `reversible-internal` — itself an
342
+ authority-bearing classification. Calling the result "provisional" changes
343
+ neither the trust boundary nor the blast radius. Whether an *independently
344
+ validated* grade may ever reduce the authorization burden is owner-reserved and
345
+ open: do not answer it here — it is the `authority-coupling-decision` blocker in
346
+ `road-to-evidence-based-adr-governance`, and
347
+ [`adr-layout § The reopen record`](../../../docs/contracts/adr-layout.md) closes
348
+ on the same point.
349
+
350
+ Two consequences that are easy to get backwards:
351
+
352
+ - **An ADR's historical decision-maker does not determine its reopen venue.** A
353
+ record decided by a council does not need a council to reopen it; a record
354
+ decided by one agent is not thereby an agent's to overturn.
355
+ - **A low-evidence record may state a decision; it does not establish that the
356
+ alternatives remain invalid.** *"We chose B because we had to choose"* is
357
+ legitimate and publishable — but never grounds for *"A is forbidden, the ADR
358
+ says B"*, which needs evidence the record never carried.
359
+
301
360
  ### When NOT to fire
302
361
 
303
362
  - The blocked change has no real benefit — the gate is not a lever to
@@ -324,6 +383,12 @@ in the rule; the lock catalog, fire steps, and failure modes live here.
324
383
  value-over-budget clause.
325
384
  - Silently dropping a good idea because "we already decided this" —
326
385
  the canonical failure the gate exists to stop.
386
+ - Reading a low `evidence.strength` as permission to supersede — the grade
387
+ prices the record, it never moves the venue.
388
+ - Treating `discovery: incomplete` as established absence: that grades the
389
+ review, not the decision.
390
+ - Citing a low-evidence ADR as proof its rejected alternatives are still
391
+ invalid. It records a choice, not a refutation.
327
392
 
328
393
  ## See also
329
394
 
@@ -71,6 +71,34 @@ human voice there; do not inject personality or restructure it.
71
71
  `npx tsx node_modules/@event4u/agent-config/src/scripts/detect_ai_tells.ts --stdin --fail`
72
72
  on the final draft. No runtime → the step-3 audit is the fallback
73
73
  (degrade, do not skip the audit).
74
+ 5b. **Carrier-Unicode strip — OPT-IN, never a default.** Runs **only** when
75
+ the operator explicitly asks for a carrier strip. `stripCarrierUnicode`
76
+ (`node_modules/@event4u/agent-config/src/scripts/detect_ai_tells.ts`, the same
77
+ path step 5 invokes) removes a hidden-Unicode codepoint only
78
+ when the codepoints on **both** sides are ASCII or absent; anything adjacent
79
+ to a non-ASCII character is preserved, so an emoji ZWJ sequence and a
80
+ complex-script joiner survive byte-identically.
81
+ **Why opt-in.** A default strip is a silent edit to the operator's
82
+ deliverable, which step 6's factual-integrity guard forbids for every other
83
+ kind of edit. Without an explicit request this step does not run and the
84
+ output is byte-identical to what the skill produces without it.
85
+ **This is the OUTPUT direction, and it does not touch step 0.** Step 0 scans
86
+ *ingested* input and surfaces findings as a warning — it never strips, because
87
+ there the hidden characters are an injection vector and removing them destroys
88
+ the evidence. Here the prose is the suite's own output and the operator has
89
+ asked. Two directions, two policies; reading them as one is the mistake this
90
+ paragraph exists to prevent.
91
+ **Hygiene, not a security control.** The predicate is deliberately
92
+ conservative, so a carrier adjacent to any non-ASCII character survives. The
93
+ injection vector stays covered by step 0.
94
+ **Emit the audit line** — `removed` and `preserved` counts, the classes
95
+ removed, and the reason for each preservation. An unexplained preservation is
96
+ the interesting half: it is what tells the operator the predicate fired
97
+ conservatively rather than failed. A strip with no audit line is a silent
98
+ edit wearing a step number.
99
+ Worked before/after: [`references/fixtures.md`](references/fixtures.md)
100
+ Fixture 3. Cases: [`evals/strip_fixtures.json`](evals/strip_fixtures.json).
101
+
74
102
  6. **Deterministic self-check + factual-integrity guard.** Re-scan the
75
103
  final draft against the [`anti-aiisms.md`](references/anti-aiisms.md)
76
104
  self-validation thresholds (dash density, consecutive-staccato cap,
@@ -189,6 +217,17 @@ do not infer a segment from the draft you were asked to edit.
189
217
  voice fingerprint that uses dashes overrides even that.
190
218
  - Do NOT shorten or restructure as a side effect — same coverage in,
191
219
  same coverage out.
220
+ - Do NOT reach for `_sanitize`
221
+ (`node_modules/@event4u/agent-config/src/scripts/lint_hidden_unicode.ts`) or any
222
+ `NFKC` pass on a deliverable. It drops **every** `_classify`-flagged codepoint
223
+ unconditionally, and its class list contains `U+200C` and `U+200D` — so a
224
+ blind pass destroys emoji ZWJ sequences and complex-script joiners. It is a
225
+ file-repair path and correct for its own callers; on prose it corrupts the
226
+ text it was pointed at. Step 5b exists precisely because that shortcut is one
227
+ import away and looks like the same job.
228
+ - Do NOT add a statistical-watermark rewrite or any detector-evasion mode. That
229
+ exclusion already stands below and this skill's carrier strip does not weaken
230
+ it: the strip removes invisible characters and alters no visible prose.
192
231
 
193
232
  ## Gotcha
194
233
 
@@ -0,0 +1,57 @@
1
+ {
2
+ "$comment": "Fixtures for stripCarrierUnicode (detect_ai_tells.ts). Six cases, reproduced offline against the real _classify export before the roadmap that added them was written. Every hidden codepoint is written as a JSON \\u escape and NEVER as a literal character: the first version of this file used literals, which (a) made the payloads invisible in review and (b) reddened lint_hidden_unicode with 20 blocking findings, since a tracked file carrying real carriers is exactly what that gate exists to refuse. JSON.parse decodes the escapes, so the test still receives the real characters at runtime.",
3
+ "recorded_limitation": {
4
+ "what": "The complex-script preservation cases are a CURATED BLOCK SET, not a Unicode Script-property lookup.",
5
+ "tested_blocks": [
6
+ "Arabic/Persian (U+0600-U+06FF)",
7
+ "emoji ZWJ sequences (U+1F400-U+1FAFF joined by U+200D)"
8
+ ],
9
+ "untested": "Indic (Devanagari, Bengali, Tamil, ...), Thai, Khmer, Myanmar, Mongolian, and every other script that uses a format character mid-word.",
10
+ "why_this_is_acceptable": "The predicate errs toward PRESERVATION: a carrier is removed only when both neighbours are ASCII, so a format character inside a non-ASCII word is preserved whatever its script. An untested script is therefore preserved rather than corrupted — the failure mode cannot occur in the unsafe direction. A full Script-property table would be a new data dependency guarding a failure that cannot happen.",
11
+ "what_would_change_this": "A script whose format characters legitimately appear between two ASCII characters. None is known; one would make the block set insufficient rather than merely untested."
12
+ },
13
+ "cases": [
14
+ {
15
+ "name": "ASCII-flanked zero-width space is removed",
16
+ "input": "the\u200bword",
17
+ "expect_out": "theword",
18
+ "expect_removed": 1,
19
+ "expect_preserved": 0
20
+ },
21
+ {
22
+ "name": "ASCII-flanked right-to-left override is removed",
23
+ "input": "safe\u202efile.txt",
24
+ "expect_out": "safefile.txt",
25
+ "expect_removed": 1,
26
+ "expect_preserved": 0
27
+ },
28
+ {
29
+ "name": "ASCII-flanked Unicode-tag codepoint is removed",
30
+ "input": "a\udb40\udc41b",
31
+ "expect_out": "ab",
32
+ "expect_removed": 1,
33
+ "expect_preserved": 0
34
+ },
35
+ {
36
+ "name": "emoji ZWJ sequence survives byte-identically",
37
+ "input": "👨\u200d💻 ships it",
38
+ "expect_out": "👨\u200d💻 ships it",
39
+ "expect_removed": 0,
40
+ "expect_preserved": 1
41
+ },
42
+ {
43
+ "name": "Persian ZWNJ survives byte-identically",
44
+ "input": "می\u200cخواهم",
45
+ "expect_out": "می\u200cخواهم",
46
+ "expect_removed": 0,
47
+ "expect_preserved": 1
48
+ },
49
+ {
50
+ "name": "clean prose is a no-op, and the strip is idempotent",
51
+ "input": "Nothing hidden here — just prose with an em dash.",
52
+ "expect_out": "Nothing hidden here — just prose with an em dash.",
53
+ "expect_removed": 0,
54
+ "expect_preserved": 0
55
+ }
56
+ ]
57
+ }
@@ -68,3 +68,57 @@ The two targets diverge on every axis — a draft matched to A is not a draft
68
68
  matched to B. Sample B is also a case where the dash-density default is
69
69
  **suppressed**: the fingerprint genuinely writes in dashes, so the fingerprint
70
70
  wins (SKILL § voice precedence).
71
+
72
+ ## Fixture 3 — carrier strip, and what "preserved" looks like (step 5b)
73
+
74
+ The point of this fixture is the **preserved** half. A reader who only sees
75
+ carriers disappear cannot tell a conservative predicate from a blind one, and a
76
+ blind one corrupts emoji and complex-script text. So the input carries both.
77
+
78
+ **Carriers are written as `U+XXXX` notation, never as the actual invisible
79
+ character.** Two reasons, and the second is a gate: an invisible byte is
80
+ unreadable in exactly the document whose job is showing what happened, and
81
+ `lint_hidden_unicode` refuses a tracked file carrying real carriers — its own
82
+ header says a teaching doc "writes `U+200B` as text". The first version of this
83
+ fixture used literals and reddened that gate with 20 blocking findings.
84
+
85
+ **Input** — `the⟦U+200B⟧word 👨⟦U+200D⟧💻 می⟦U+200C⟧خواهم safe⟦U+202E⟧file`
86
+
87
+ | Carrier | Neighbours | Decision |
88
+ |---|---|---|
89
+ | `U+200B` between `e` and `w` | ASCII / ASCII | **removed** |
90
+ | `U+200D` joining two emoji | non-ASCII / non-ASCII | preserved |
91
+ | `U+200C` inside a Persian word | non-ASCII / non-ASCII | preserved |
92
+ | `U+202E` between `e` and `f` | ASCII / ASCII | **removed** |
93
+
94
+ **Output** — `theword 👨⟦U+200D⟧💻 می⟦U+200C⟧خواهم safefile`
95
+
96
+ **Audit line** (generated by running the real function, not written by hand):
97
+
98
+ ```
99
+ carrier strip: removed 2 (zero-width 1, bidi-control 1) · preserved 2
100
+ · U+200B zero-width offset 3 removed
101
+ · U+200D zero-width offset 10 preserved — adjacent to a non-ASCII codepoint, may be a joiner
102
+ · U+200C zero-width offset 15 preserved — adjacent to a non-ASCII codepoint, may be a joiner
103
+ · U+202E bidi-control offset 26 removed
104
+ ```
105
+
106
+ Three things a reader should take from this:
107
+
108
+ 1. **`U+200B` and `U+200D` are the same class** — both `zero-width` — and get
109
+ opposite dispositions. The class is not the decision; the neighbours are.
110
+ Any strip keyed on class alone destroys the emoji.
111
+ 2. **Offsets are codepoint indices, not UTF-16 units.** The emoji before
112
+ `U+200D` is a surrogate pair, so a UTF-16 index would report a different
113
+ number and the audit line would be wrong for exactly the inputs this
114
+ predicate exists to protect.
115
+ 3. **A preservation always carries its reason.** An unexplained preservation
116
+ would be indistinguishable from a bug; the reason is what makes the
117
+ conservative branch auditable rather than merely quiet.
118
+
119
+ Re-running the strip over the output changes nothing — `removed 0 · preserved 2`.
120
+ Idempotence is a property of the predicate, not a coincidence: the two survivors
121
+ still have non-ASCII neighbours the second time round.
122
+
123
+ The runnable form of all six cases, with the carriers as JSON `\u` escapes, is
124
+ [`evals/strip_fixtures.json`](../evals/strip_fixtures.json).
@@ -0,0 +1,154 @@
1
+ ---
2
+ model_tier: high
3
+ name: judge-spec-compliance
4
+ description: "Use when a diff needs a requirement review — does it satisfy every stated acceptance criterion — dispatched by /review-changes, /do-and-judge, /judge. Never infers criteria from the diff."
5
+ domain: quality
6
+ parallelizable: files
7
+ workspaces:
8
+ - agent-config-maintainer
9
+ packs:
10
+ - meta
11
+ ---
12
+
13
+ # judge-spec-compliance
14
+
15
+ > You are a judge specialized in **requirement compliance**. Your only
16
+ > job is: **does the diff satisfy every acceptance criterion as
17
+ > stated?** You do **not** review style, naming, craft, correctness,
18
+ > security or test coverage — five other judges handle those, and a
19
+ > finding of theirs is not a finding of yours.
20
+
21
+ The wording above is the SPEC COMPLIANCE judge from
22
+ [`do-and-judge-two-stage`](../subagent-orchestration/prompts/do-and-judge-two-stage.md)
23
+ § Stage-1 prompt, reused rather than reinvented. That prompt worked and was
24
+ reachable only through one orchestration mode; this skill is the same job on the
25
+ default review path.
26
+
27
+ ## Why this judge exists
28
+
29
+ Every other default judge asks a **craft-or-correctness** question. So a change
30
+ that is correct, clean, well-tested and architecturally sound — and **does not do
31
+ what was asked** — passed the default path with five green verdicts. Correctness
32
+ and compliance are different questions, and only one of them was being asked.
33
+
34
+ ## When to use
35
+
36
+ * A diff is ready for review and **doing the wrong thing** is the risk
37
+ * `/review-changes` dispatches its "spec" slice to this skill
38
+ * A reviewer asks "is this what we asked for?", "does this satisfy the AC?"
39
+
40
+ Do NOT use when:
41
+
42
+ * The concern is a functional bug → [`judge-bug-hunter`](../judge-bug-hunter/SKILL.md)
43
+ * The concern is security → [`judge-security-auditor`](../judge-security-auditor/SKILL.md)
44
+ * The concern is missing tests → [`judge-test-coverage`](../judge-test-coverage/SKILL.md)
45
+ * The concern is readability → [`judge-code-quality`](../judge-code-quality/SKILL.md)
46
+ * **The criteria are not available** — that is not a reason to skip; it is a
47
+ verdict of its own. See § No criteria.
48
+
49
+ ## Procedure
50
+
51
+ ### 1. Establish where the criteria came from — before reading the diff
52
+
53
+ Read the criteria **first**, and never after forming an impression of the diff.
54
+ The order is the control, not a preference: a judge that reads the change first
55
+ and the requirement second reconstructs the requirement to fit what it just saw.
56
+
57
+ ```
58
+ NEVER INFER AN ACCEPTANCE CRITERION FROM THE DIFF YOU ARE JUDGING.
59
+ A JUDGE THAT DERIVES THE REQUIREMENT FROM THE CHANGE ALWAYS FINDS THE
60
+ CHANGE COMPLIANT. THAT IS CIRCULAR BY CONSTRUCTION, NOT A BIAS A LABEL FIXES.
61
+ ```
62
+
63
+ Criteria are **supplied**, or they are not. A commit message and a PR body are
64
+ written by the same author as the diff, usually after it — so deriving criteria
65
+ from them is a weaker form of the same circularity, not an independent source of
66
+ lower quality.
67
+
68
+ ### 2. Per-AC scan
69
+
70
+ For each criterion, exactly one of:
71
+
72
+ | Verdict | What it requires |
73
+ |---|---|
74
+ | `SATISFIED` | cite the diff hunk **and** the test that proves it. A hunk with no test is not satisfied, it is claimed |
75
+ | `PARTIAL` | cite what is missing and why it falls short |
76
+ | `MISSING` | the criterion has no corresponding implementation |
77
+
78
+ ### 3. Report the criteria-source state — three, not two
79
+
80
+ This mirrors [`dispatch_r2_reviewer`](../../scripts/dispatch_r2_reviewer.ts)'s
81
+ own extraction-failure handling, which refuses to collapse the last two for the
82
+ same reason: an unrecognised shape and a genuine absence produce the **identical
83
+ empty result**, so asserting "declares none" would turn a silent tool failure
84
+ into a false statement handed to the one independent check on AC conformance.
85
+
86
+ | State | Meaning | How to report |
87
+ |---|---|---|
88
+ | `supplied` | a roadmap, ticket or explicit criteria set was handed in | run § 2 |
89
+ | `not_provided` | no criteria exist for this review — an ad-hoc branch review | **no-criteria verdict**, never `SATISFIED` |
90
+ | `supplied_unparseable` | criteria were handed in and could not be read | **ERROR**, not a no-criteria verdict |
91
+
92
+ **`supplied_unparseable` is an error and not a third flavour of silence.** It
93
+ means the tooling is broken or the input is malformed, and folding it into
94
+ `not_provided` hides parser regressions behind a state that looks routine.
95
+
96
+ ## No criteria — the verdict, and what it does to "done"
97
+
98
+ A no-criteria outcome is **not** a pass, and it is not an abstention that the
99
+ consolidated verdict may ignore. What it changes is what "done" is allowed to
100
+ claim:
101
+
102
+ > **craft quality verified; requirement compliance NOT verified**
103
+
104
+ That phrasing is the point. If this judge abstains on most reviews, the honest
105
+ consequence is not that the axis should be dropped — it is that most reviews
106
+ were previously reporting a confidence they had not earned. The abstention makes
107
+ that visible instead of assuming it away.
108
+
109
+ ## Do NOT
110
+
111
+ * Do NOT infer criteria from the diff, the commit messages, or the PR body.
112
+ * Do NOT return `SATISFIED` when no criteria were supplied — return the
113
+ no-criteria verdict.
114
+ * Do NOT collapse `supplied_unparseable` into `not_provided`.
115
+ * Do NOT report a craft, naming, correctness or coverage finding. Another judge
116
+ owns it, and duplicating it here inflates the finding count without adding a
117
+ reading.
118
+ * Do NOT treat a criterion as satisfied on a diff hunk alone when a test could
119
+ have proven it.
120
+
121
+ ## Gotcha
122
+
123
+ * **Criteria authored after the diff are not independent.** If the criteria set
124
+ was written or last modified after the change it judges, say so — a criterion
125
+ reverse-engineered from a merged branch verifies nothing.
126
+ * **A criterion the diff makes moot is still not satisfied.** "We no longer need
127
+ this" is a scope decision for a human, not a verdict this judge may reach.
128
+
129
+ ## Output format
130
+
131
+ Ordered, and the first line is not negotiable — a reader must be able to see the
132
+ criteria-source state before any per-criterion verdict, because every verdict
133
+ below it is conditional on that state.
134
+
135
+ 1. `criteria_source: supplied | not_provided | supplied_unparseable`
136
+ 2. If `not_provided` → the no-criteria verdict and the "requirement compliance
137
+ NOT verified" line. **Stop.** Do not emit per-criterion rows.
138
+ 3. If `supplied_unparseable` → `ERROR` with what was handed in and what could not
139
+ be read. **Stop.** Do not emit per-criterion rows and do not degrade to
140
+ `not_provided`.
141
+ 4. If `supplied` → one row per criterion:
142
+ `| # | criterion (as stated) | SATISFIED / PARTIAL / MISSING | diff hunk | test |`
143
+ Criteria in the order they were stated, never re-ordered by verdict — a
144
+ reader comparing two runs needs the rows to line up.
145
+ 5. One closing line: the count of `MISSING` and `PARTIAL`. Not a severity, not a
146
+ recommendation — those belong to synthesis, and phrasing this as one is how a
147
+ spec finding gets ranked against a naming nit.
148
+
149
+ ## Related
150
+
151
+ * [`judge-synthesis`](../judge-synthesis/SKILL.md) — consumes this verdict on
152
+ its **own dimension**, deliberately not on the shared severity axis.
153
+ * [`do-and-judge-two-stage`](../subagent-orchestration/prompts/do-and-judge-two-stage.md)
154
+ — the stage-1 prompt this reuses.