@event4u/agent-config 14.8.0 → 14.10.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 (170) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +214 -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/async-python-patterns/SKILL.md +1 -1
  13. package/dist/agent-src/skills/code-refactoring/SKILL.md +1 -1
  14. package/dist/agent-src/skills/code-review/evals/evals.json +87 -46
  15. package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
  16. package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +1 -1
  17. package/dist/agent-src/skills/design-review/SKILL.md +1 -1
  18. package/dist/agent-src/skills/discovery-interview/SKILL.md +1 -1
  19. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
  20. package/dist/agent-src/skills/docx-authoring/SKILL.md +1 -1
  21. package/dist/agent-src/skills/editorial-calendar/SKILL.md +1 -1
  22. package/dist/agent-src/skills/github-ci/SKILL.md +1 -1
  23. package/dist/agent-src/skills/humanizer/SKILL.md +39 -0
  24. package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +57 -0
  25. package/dist/agent-src/skills/humanizer/references/fixtures.md +54 -0
  26. package/dist/agent-src/skills/judge-artifact-completeness/SKILL.md +1 -1
  27. package/dist/agent-src/skills/judge-injection-defense/SKILL.md +1 -1
  28. package/dist/agent-src/skills/judge-spec-compliance/SKILL.md +154 -0
  29. package/dist/agent-src/skills/judge-synthesis/SKILL.md +45 -0
  30. package/dist/agent-src/skills/laravel-validation/SKILL.md +1 -1
  31. package/dist/agent-src/skills/legal-intake-triage/SKILL.md +1 -1
  32. package/dist/agent-src/skills/legal-practice-profile/SKILL.md +1 -1
  33. package/dist/agent-src/skills/messaging-architecture/SKILL.md +1 -1
  34. package/dist/agent-src/skills/migration-architect/SKILL.md +1 -1
  35. package/dist/agent-src/skills/module-detect-on-the-fly/SKILL.md +1 -1
  36. package/dist/agent-src/skills/module-management/SKILL.md +1 -1
  37. package/dist/agent-src/skills/motion-choreographer/SKILL.md +1 -1
  38. package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
  39. package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
  40. package/dist/agent-src/skills/prompt-optimizer/SKILL.md +1 -1
  41. package/dist/agent-src/skills/receiving-code-review/SKILL.md +1 -1
  42. package/dist/agent-src/skills/roadmap-writing/SKILL.md +18 -18
  43. package/dist/agent-src/skills/scene-expander/SKILL.md +1 -1
  44. package/dist/agent-src/skills/secrets-management/SKILL.md +1 -1
  45. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -1
  46. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  47. package/dist/agent-src/templates/roadmaps.md +8 -0
  48. package/dist/cli/registry.js +2 -0
  49. package/dist/cli/registry.js.map +1 -1
  50. package/dist/cli-delegate/{chunk-MMSGVZKV.js → chunk-77GR3VD4.js} +65 -40
  51. package/dist/cli-delegate/{chunk-Q26HT4QT.js → chunk-E46KPN6K.js} +251 -150
  52. package/dist/cli-delegate/{chunk-EEJUMCDZ.js → chunk-PLSG6UL5.js} +1 -1
  53. package/dist/cli-delegate/cmd_conformance.js +89 -13
  54. package/dist/cli-delegate/cmd_doctor.js +3 -3
  55. package/dist/cli-delegate/cmd_handoff.js +2 -2
  56. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  57. package/dist/cli-delegate/cmd_route_audit.js +1 -1
  58. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  59. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  60. package/dist/discovery/deprecation-report.md +1 -1
  61. package/dist/discovery/discovery-manifest.json +64 -42
  62. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  63. package/dist/discovery/discovery-manifest.summary.md +3 -3
  64. package/dist/discovery/dormancy-report.md +1 -1
  65. package/dist/discovery/orphan-report.md +1 -1
  66. package/dist/discovery/packs.json +6 -5
  67. package/dist/discovery/trust-report.md +2 -2
  68. package/dist/discovery/workspaces.json +5 -4
  69. package/dist/hooks/dispatch.js +562 -204
  70. package/dist/mcp/registry-manifest.json +3 -3
  71. package/dist/mcp/server.json +2 -2
  72. package/dist/mcp/server.mjs +808 -331
  73. package/dist/scripts/_lib/claude_plugin.js +162 -0
  74. package/dist/scripts/_lib/claude_plugin.js.map +1 -0
  75. package/dist/scripts/_lib/environment_detector.js +397 -0
  76. package/dist/scripts/_lib/environment_detector.js.map +1 -0
  77. package/dist/scripts/_lib/spawn_env.js +129 -0
  78. package/dist/scripts/_lib/spawn_env.js.map +1 -0
  79. package/dist/scripts/ai_council/modes.js +199 -0
  80. package/dist/scripts/ai_council/modes.js.map +1 -0
  81. package/dist/scripts/ai_council/transport_resolver.js +418 -0
  82. package/dist/scripts/ai_council/transport_resolver.js.map +1 -0
  83. package/dist/server/routes/wizard.js +3 -2
  84. package/dist/server/routes/wizard.js.map +1 -1
  85. package/dist/ui/assets/index-Cxgy63kz.js +26 -0
  86. package/dist/ui/assets/index-Cxgy63kz.js.map +1 -0
  87. package/dist/ui/index.html +1 -1
  88. package/docs/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
  89. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +17 -0
  90. package/package.json +7 -3
  91. package/src/config/estate-count-budget.json +13 -744
  92. package/src/config/evaluator-budgets.json +4 -4
  93. package/src/config/gate-coverage.yml +267 -9
  94. package/src/config/gate-violation-baselines.json +72 -72
  95. package/src/config/preamble-payload-budget.json +71 -67
  96. package/src/config/publish-surface.json +60 -0
  97. package/src/config/requirements-trace-budget.json +32 -0
  98. package/src/config/rule-stub-ceilings.json +8 -1
  99. package/src/scripts/_dispatch.bash +7 -0
  100. package/src/scripts/_lib/base_tree.ts +175 -0
  101. package/src/scripts/_lib/billing_grant.ts +165 -0
  102. package/src/scripts/_lib/billing_grant_cli.ts +123 -0
  103. package/src/scripts/_lib/council_fallback_posture.ts +53 -6
  104. package/src/scripts/_lib/council_fallback_wiring.ts +61 -1
  105. package/src/scripts/_lib/host_capability.ts +59 -5
  106. package/src/scripts/_lib/quota_parked.ts +133 -0
  107. package/src/scripts/_lib/retrieval_sanitize.ts +62 -0
  108. package/src/scripts/_lib/review_telemetry.ts +196 -0
  109. package/src/scripts/_lib/subagent_response.ts +92 -0
  110. package/src/scripts/adr/regenerate_index.ts +14 -7
  111. package/src/scripts/ai_council/clients.ts +12 -1
  112. package/src/scripts/ai_council/config.ts +72 -115
  113. package/src/scripts/ai_council/fallback_config.ts +19 -7
  114. package/src/scripts/ai_council/mid_flight_fallback.ts +88 -7
  115. package/src/scripts/ai_council/orchestrator.ts +2 -2
  116. package/src/scripts/ai_council/py_format.ts +124 -0
  117. package/src/scripts/ai_council/seating.ts +211 -0
  118. package/src/scripts/ai_council/transport_resolver.ts +65 -2
  119. package/src/scripts/audit_adr_coverage.ts +33 -6
  120. package/src/scripts/check_council_pin_staleness.ts +300 -0
  121. package/src/scripts/check_estate_count.ts +420 -293
  122. package/src/scripts/check_gate_coverage.ts +91 -0
  123. package/src/scripts/check_no_stub_inventory_table.ts +292 -0
  124. package/src/scripts/check_pack_passport_reconciliation.ts +274 -0
  125. package/src/scripts/check_pack_size.ts +234 -1
  126. package/src/scripts/check_publish_surface.ts +297 -0
  127. package/src/scripts/check_requirements_trace.ts +378 -0
  128. package/src/scripts/check_secret_leak.ts +166 -12
  129. package/src/scripts/code_graph/cli.ts +44 -5
  130. package/src/scripts/code_graph/extract.ts +87 -0
  131. package/src/scripts/council_cli.ts +12 -16
  132. package/src/scripts/detect_ai_tells.ts +87 -0
  133. package/src/scripts/dispatch_r2_reviewer.ts +42 -0
  134. package/src/scripts/generate_capabilities_index.ts +41 -0
  135. package/src/scripts/generate_pack_manifests.ts +78 -0
  136. package/src/scripts/git_authorization_hook.ts +54 -4
  137. package/src/scripts/hooks/block_unauthorized_git.ts +19 -1
  138. package/src/scripts/hooks/run_continuation_hook.ts +3 -1
  139. package/src/scripts/hooks/subagent_ledger_hook.ts +76 -11
  140. package/src/scripts/injection_scan_hook.ts +120 -10
  141. package/src/scripts/lint_adr_sweep_routing.ts +365 -0
  142. package/src/scripts/lint_workflow_security.ts +53 -10
  143. package/src/scripts/lint_workflow_security_allowlist.json +2 -18
  144. package/src/scripts/report_envelope_rate.ts +166 -0
  145. package/src/scripts/review_axis_report.ts +218 -0
  146. package/src/scripts/run_supervise.ts +46 -1
  147. package/src/scripts/schemas/evals.schema.json +5 -0
  148. package/src/scripts/schemas/pack.schema.json +62 -4
  149. package/src/scripts/schemas/skill.schema.json +2 -2
  150. package/src/scripts/sync_pr_branch.ts +14 -7
  151. package/src/scripts/tighten_skill_descriptions.ts +264 -0
  152. package/dist/cli/commands/commands.candidates.test.js +0 -257
  153. package/dist/cli/commands/commands.candidates.test.js.map +0 -1
  154. package/dist/cli/commands/profiles.test.js +0 -88
  155. package/dist/cli/commands/profiles.test.js.map +0 -1
  156. package/dist/cli/commands/recordTriggerEval.test.js +0 -113
  157. package/dist/cli/commands/recordTriggerEval.test.js.map +0 -1
  158. package/dist/cli/configRoot.test.js +0 -97
  159. package/dist/cli/configRoot.test.js.map +0 -1
  160. package/dist/cli/mcp/content.test.js +0 -54
  161. package/dist/cli/mcp/content.test.js.map +0 -1
  162. package/dist/cli/mcp/dispatch.test.js +0 -108
  163. package/dist/cli/mcp/dispatch.test.js.map +0 -1
  164. package/dist/server/writeRoot.test.js +0 -91
  165. package/dist/server/writeRoot.test.js.map +0 -1
  166. package/dist/shared/capabilities.test.js +0 -39
  167. package/dist/shared/capabilities.test.js.map +0 -1
  168. package/dist/ui/assets/index-C1pBRm1f.js +0 -26
  169. package/dist/ui/assets/index-C1pBRm1f.js.map +0 -1
  170. /package/dist/cli-delegate/{chunk-N7CDDGI3.js → chunk-CYH5SP6M.js} +0 -0
@@ -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
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: inherit
3
3
  name: condense-memory
4
- description: "Use when shrinking always-loaded memory files (AGENTS.md, CLAUDE.md, .cursorrules) exceeding ~150 lines or ~4,000 chars — telegraph grammar, refuses sensitive paths, .original.md round-trip."
4
+ description: "When shrinking always-loaded memory files (AGENTS.md, CLAUDE.md, .cursorrules) exceeding ~150 lines or ~4,000 chars — telegraph grammar, refuses sensitive paths, .original.md round-trip."
5
5
  domain: process
6
6
  execution:
7
7
  type: assisted
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: medium
3
3
  name: conventional-commits-writing
4
- description: "Use when writing commit messages or squash-merge titles — `feat:`, `fix:`, `chore:`, scopes, breaking changes — even when the user just says 'commit this' without naming Conventional Commits."
4
+ description: "When writing commit messages or squash-merge titles — `feat:`, `fix:`, `chore:`, scopes, breaking changes — even when the user just says 'commit this' without naming Conventional Commits."
5
5
  domain: process
6
6
  execution:
7
7
  type: assisted
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: high
3
3
  name: design-review
4
- description: "Use when the user says \"review the design\", \"check the UI\", or wants a comprehensive UI/UX review. Uses a 7-phase methodology covering interaction, responsiveness, accessibility, and more."
4
+ description: "When the user says \"review the design\", \"check the UI\", or wants a comprehensive UI/UX review. Uses a 7-phase methodology covering interaction, responsiveness, accessibility and more."
5
5
  domain: quality
6
6
  workspaces:
7
7
  - engineering
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: inherit
3
3
  name: discovery-interview
4
- description: "Use when running discovery interviews — question-bank build, bias audit, insight extraction. Triggers on 'audit my guide', 'extract insights from transcript', 'is my hypothesis falsifiable'."
4
+ description: "When running discovery interviews — question-bank build, bias audit, insight extraction. Triggers on 'audit my guide', 'extract insights from transcript', 'is my hypothesis falsifiable'."
5
5
  status: active
6
6
  tier: senior
7
7
  domain: product
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: inherit
3
3
  name: doc-coauthoring
4
- description: "Use when co-authoring a PRD, design doc, RFC, decision doc, or technical spec — 3-stage flow (context → section-by-section → reader-test) — even if the user just says 'help me write this spec'."
4
+ description: "When co-authoring a PRD, design doc, RFC, decision doc, or technical spec — 3-stage flow (context → section-by-section → reader-test) — even if the user just says 'help me write this spec'."
5
5
  domain: process
6
6
  workspaces:
7
7
  - agent-config-maintainer
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: medium
3
3
  name: docx-authoring
4
- description: "Use when generating or editing a Word .docx — create, fill a template, or edit body XML via a consumer library; round-trip validated. Triggers on 'generate a docx', 'fill this Word template'."
4
+ description: "When generating or editing a Word .docx — create, fill a template, or edit body XML via a consumer library; round-trip validated. Triggers on 'generate a docx', 'fill this Word template'."
5
5
  status: active
6
6
  tier: senior
7
7
  domain: process
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: inherit
3
3
  name: editorial-calendar
4
- description: "Use when shaping cadence — evergreen / campaign / reactive split, beat-mapping across channel stages, content-debt management. Triggers on 'plan our content cadence', 'what should we publish'."
4
+ description: "When shaping cadence — evergreen / campaign / reactive split, beat-mapping across channel stages, content-debt management. Triggers on 'plan our content cadence', 'what should we publish'."
5
5
  status: active
6
6
  tier: senior
7
7
  domain: product
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: medium
3
3
  name: github-ci
4
- description: "Use when working with GitHub Actions — workflow YAML, quality gates, test matrices, deployment triggers, reusable workflows — even when the user just says 'my CI is failing' or 'add a check'."
4
+ description: "When working with GitHub Actions — workflow YAML, quality gates, test matrices, deployment triggers, reusable workflows — even when the user just says 'my CI is failing' or 'add a check'."
5
5
  domain: devops
6
6
  workspaces:
7
7
  - engineering
@@ -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).
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: high
3
3
  name: judge-artifact-completeness
4
- description: "Use when scoring a roadmap, PR review, ADR, or ticket for completeness — risk, tests, migration, maintainability. Dispatched by /refine-ticket, /adr-create, /review-changes; never auto-gates."
4
+ description: "When scoring a roadmap, PR review, ADR, or ticket for completeness — risk, tests, migration, maintainability. Dispatched by /refine-ticket, /adr-create, /review-changes; never auto-gates."
5
5
  domain: quality
6
6
  workspaces:
7
7
  - agent-config-maintainer
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  model_tier: high
3
3
  name: judge-injection-defense
4
- description: "Use when scoring whether an agent response defended against an injection — treated untrusted content as data, refused role-takeover, ignored fake boundaries. Inverted axis; never auto-gates."
4
+ description: "When scoring whether an agent response defended against an injection — treated untrusted content as data, refused role-takeover, ignored fake boundaries. Inverted axis; never auto-gates."
5
5
  domain: quality
6
6
  workspaces:
7
7
  - agent-config-maintainer
@@ -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.
@@ -50,9 +50,15 @@ vocabularies in the suite map onto one ordered severity axis:
50
50
  | code judges (`judge-bug-hunter`, `-code-quality`, `-security-auditor`, `-test-coverage`, `architecture-review-lens`) | `apply` / `revise` / `reject` |
51
51
  | `judge-artifact-completeness` | `complete` / `partial` / `incomplete` |
52
52
  | `judge-injection-defense` | `defended` / `partial` / `breached` |
53
+ | `judge-spec-compliance` | per criterion: `SATISFIED` / `PARTIAL` / `MISSING` — plus a `criteria_source` state that is not a verdict |
53
54
 
54
55
  Ordered worst→best: **`reject`/`incomplete`/`breached` > `revise`/`partial` > `apply`/`complete`/`defended`**.
55
56
 
57
+ `judge-spec-compliance` is deliberately absent from that axis. Its verdicts
58
+ answer a different question — *did the change do what was asked* — and mapping
59
+ `MISSING` onto `reject` would let a craft-clean diff average it away, which is
60
+ the miss this judge was added to catch. It gets its own dimension in § 4c.
61
+
56
62
  ## Procedure
57
63
 
58
64
  ### 1. Inspect the inputs and tabulate
@@ -133,12 +139,51 @@ the reviewed change, and it is not the `code-provenance` knowledge-layer
133
139
  obligation, which governs what a durable artefact asserts rather than what a
134
140
  transient review does.
135
141
 
142
+ ### 4c. Spec compliance is its own dimension — never folded into the tiers
143
+
144
+ ```
145
+ A SPEC FINDING NEVER BECOMES A CRAFT FINDING.
146
+ REPORT THE SPEC DIMENSION SEPARATELY, WITH ITS `criteria_source` STATE.
147
+ A CRAFT-CLEAN DIFF THAT MISSES ITS CRITERION IS NOT A CLEAN REVIEW.
148
+ NO CRITERIA SUPPLIED IS AN UNVERIFIED DIMENSION, NEVER A PASS.
149
+ ```
150
+
151
+ `judge-spec-compliance` findings do **not** enter must-fix / should-fix /
152
+ advisory. They form a separate block carrying, in this order: the
153
+ `criteria_source` state, the per-criterion table, and the count of `MISSING`
154
+ plus `PARTIAL`.
155
+
156
+ The separation is the whole point. Folded into the craft tiers a `MISSING`
157
+ criterion competes with five other judges' findings and can be outvoted by
158
+ their silence; kept apart it cannot be, because there is nothing to average it
159
+ against. Consensus (§ 2) and conflict (§ 3) do not apply either: no other judge
160
+ reads the criteria, so a spec finding has no possible second voter and its
161
+ absence of corroboration says nothing about it.
162
+
163
+ Three `criteria_source` states, and each says something different about what
164
+ this review established:
165
+
166
+ | State | What the synthesis reports |
167
+ |---|---|
168
+ | `supplied` | the dimension was verified; report the per-criterion verdicts |
169
+ | `not_provided` | the dimension was **not verified** — say so, never report it as clean |
170
+ | `supplied_unparseable` | an **error**, not a no-criteria run: criteria were handed over and could not be read, so the reader must know their review silently skipped a dimension it was asked to check |
171
+
136
172
  ### 5. Overall recommendation
137
173
 
138
174
  One sentence, not a number: `block` (any worst-tier verdict), `revise` (any
139
175
  mid-tier, no worst-tier), or `proceed` (all clean). This is a recommendation the
140
176
  human acts on — it does not gate anything.
141
177
 
178
+ **The sentence names the spec dimension explicitly, in every case.** A `MISSING`
179
+ criterion is a `block` and a `PARTIAL` one is at least a `revise`, whatever the
180
+ craft judges said. Where no criteria were supplied, the sentence says what was
181
+ and was not established — *"craft quality verified; requirement compliance NOT
182
+ verified (no criteria supplied)"* — because a bare `proceed` over an unverified
183
+ dimension reads as a full pass, and that reading is the defect: a reviewer
184
+ cannot tell "we checked and it complies" from "nobody checked" unless the
185
+ sentence distinguishes them.
186
+
142
187
  ## Validation
143
188
 
144
189
  1. Every judge that ran appears exactly once in the table.