@event4u/agent-config 9.16.0 → 9.18.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 (227) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/AGENTS.md +1 -1
  3. package/CHANGELOG.md +80 -130
  4. package/README.md +1 -1
  5. package/dist/agent-src/commands/agent-handoff.md +100 -48
  6. package/dist/agent-src/commands/challenge-me/vision.md +5 -0
  7. package/dist/agent-src/commands/commit.md +1 -1
  8. package/dist/agent-src/commands/condense.md +19 -64
  9. package/dist/agent-src/commands/evals/agent-handoff.json +13 -1
  10. package/dist/agent-src/commands/evals/rule-compliance-audit.json +46 -0
  11. package/dist/agent-src/commands/feature/plan.md +20 -0
  12. package/dist/agent-src/commands/feature/roadmap.md +19 -0
  13. package/dist/agent-src/commands/memory/promote.md +14 -1
  14. package/dist/agent-src/commands/mode.md +9 -5
  15. package/dist/agent-src/commands/optimize/deep.md +72 -11
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/roadmap/create.md +29 -0
  18. package/dist/agent-src/commands/rule-compliance-audit.md +28 -5
  19. package/dist/agent-src/contexts/authority/commit-mechanics.md +5 -5
  20. package/dist/agent-src/contexts/communication/rules-auto/guidelines-mechanics.md +1 -0
  21. package/dist/agent-src/contexts/communication/rules-auto/skill-quality-mechanics.md +1 -1
  22. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +6 -1
  23. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +4 -1
  24. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +27 -1
  25. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +133 -0
  26. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +5 -2
  27. package/dist/agent-src/profiles/agency.yml +3 -3
  28. package/dist/agent-src/profiles/content_creator.yml +2 -2
  29. package/dist/agent-src/profiles/developer.yml +2 -2
  30. package/dist/agent-src/profiles/finance.yml +1 -1
  31. package/dist/agent-src/profiles/founder.yml +2 -2
  32. package/dist/agent-src/profiles/ops.yml +2 -2
  33. package/dist/agent-src/rules/active-remediation.md +2 -0
  34. package/dist/agent-src/rules/architecture.md +4 -2
  35. package/dist/agent-src/rules/augment-edit-discipline.md +2 -0
  36. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  37. package/dist/agent-src/rules/brand-consistency.md +10 -62
  38. package/dist/agent-src/rules/brand-source-of-truth.md +62 -11
  39. package/dist/agent-src/rules/broken-access-control.md +4 -0
  40. package/dist/agent-src/rules/cli-output-handling.md +5 -0
  41. package/dist/agent-src/rules/code-comment-discipline.md +3 -0
  42. package/dist/agent-src/rules/commit-conventions.md +2 -0
  43. package/dist/agent-src/rules/context-hygiene.md +3 -2
  44. package/dist/agent-src/rules/cross-source-consistency.md +3 -0
  45. package/dist/agent-src/rules/design-fidelity.md +2 -0
  46. package/dist/agent-src/rules/doc-screenshot-hygiene.md +2 -0
  47. package/dist/agent-src/rules/docker-commands.md +4 -0
  48. package/dist/agent-src/rules/domain-adoption-policy.md +2 -0
  49. package/dist/agent-src/rules/domain-safety-disclaimer.md +5 -2
  50. package/dist/agent-src/rules/domain-safety-pii.md +1 -1
  51. package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
  52. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +9 -0
  53. package/dist/agent-src/rules/image-likeness-and-rights.md +4 -0
  54. package/dist/agent-src/rules/improve-before-implement.md +4 -0
  55. package/dist/agent-src/rules/laravel-routing.md +4 -0
  56. package/dist/agent-src/rules/legal-safety-floor.md +3 -0
  57. package/dist/agent-src/rules/lethal-trifecta-guard.md +3 -0
  58. package/dist/agent-src/rules/media-governance-routing.md +5 -0
  59. package/dist/agent-src/rules/minimal-safe-diff.md +2 -0
  60. package/dist/agent-src/rules/no-roadmap-references.md +2 -0
  61. package/dist/agent-src/rules/notes-first-reasoning.md +2 -0
  62. package/dist/agent-src/rules/output-discipline.md +9 -2
  63. package/dist/agent-src/rules/php-coding.md +2 -0
  64. package/dist/agent-src/rules/prefer-enums-over-literals.md +3 -0
  65. package/dist/agent-src/rules/provider-lifecycle-discipline.md +5 -0
  66. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +2 -0
  67. package/dist/agent-src/rules/roadmap-progress-sync.md +2 -0
  68. package/dist/agent-src/rules/rule-type-governance.md +2 -0
  69. package/dist/agent-src/rules/scale-discipline.md +3 -0
  70. package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
  71. package/dist/agent-src/rules/security-sensitive-stop.md +9 -2
  72. package/dist/agent-src/rules/senior-engineering-discipline.md +6 -0
  73. package/dist/agent-src/rules/skill-quality.md +2 -0
  74. package/dist/agent-src/rules/source-confidentiality.md +4 -0
  75. package/dist/agent-src/rules/source-discovery-gate.md +3 -0
  76. package/dist/agent-src/rules/source-of-truth.md +2 -0
  77. package/dist/agent-src/rules/strategy-safety-floor.md +2 -0
  78. package/dist/agent-src/rules/think-before-action.md +5 -0
  79. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  80. package/dist/agent-src/rules/ui-audit-gate.md +2 -0
  81. package/dist/agent-src/rules/user-interaction.md +3 -4
  82. package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -1
  83. package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +4 -4
  84. package/dist/agent-src/skills/copilot-config/SKILL.md +1 -1
  85. package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +7 -4
  86. package/dist/agent-src/skills/description-assist/SKILL.md +1 -1
  87. package/dist/agent-src/skills/design-intelligence/SKILL.md +6 -180
  88. package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +100 -0
  89. package/dist/agent-src/skills/design-intelligence/references/integration-mapping.md +91 -0
  90. package/dist/agent-src/skills/design-review/SKILL.md +4 -141
  91. package/dist/agent-src/skills/design-review/references/review-communication.md +92 -0
  92. package/dist/agent-src/skills/design-review/references/verification-automation.md +61 -0
  93. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +8 -27
  94. package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +31 -0
  95. package/dist/agent-src/skills/fe-design/SKILL.md +4 -289
  96. package/dist/agent-src/skills/fe-design/references/design-patterns.md +227 -0
  97. package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +75 -0
  98. package/dist/agent-src/skills/market-entry-analysis/SKILL.md +1 -1
  99. package/dist/agent-src/skills/persona-writing/SKILL.md +2 -2
  100. package/dist/agent-src/skills/readme-reviewer/SKILL.md +2 -1
  101. package/dist/agent-src/skills/roadmap-management/SKILL.md +10 -0
  102. package/dist/agent-src/skills/roadmap-writing/SKILL.md +12 -1
  103. package/dist/agent-src/skills/rule-writing/SKILL.md +4 -2
  104. package/dist/agent-src/skills/skill-reviewer/SKILL.md +1 -1
  105. package/dist/agent-src/skills/skill-writing/SKILL.md +2 -2
  106. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +3 -1
  107. package/dist/agent-src/skills/token-optimizer/SKILL.md +2 -2
  108. package/dist/agent-src/skills/ui-component-architect/SKILL.md +6 -3
  109. package/dist/agent-src/templates/agent-settings.md +13 -15
  110. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +6 -2
  111. package/dist/agent-src/templates/persona.md +2 -2
  112. package/dist/agent-src/templates/roadmaps.md +13 -1
  113. package/dist/agent-src/templates/rule.md +1 -1
  114. package/dist/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
  115. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
  116. package/dist/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
  117. package/dist/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
  118. package/dist/agent-src/templates/skill.md +1 -1
  119. package/dist/cli/registry.js +3 -0
  120. package/dist/cli/registry.js.map +1 -1
  121. package/dist/cli-delegate/chunk-AIIUU73N.js +2040 -0
  122. package/dist/cli-delegate/{chunk-ZXXJLL5K.js → chunk-BSLUHV2F.js} +127 -1487
  123. package/dist/cli-delegate/{chunk-H574KOUH.js → chunk-ELMFOQPW.js} +35 -1
  124. package/dist/cli-delegate/chunk-LF2KKJ4Q.js +183 -0
  125. package/dist/cli-delegate/chunk-VSYT7OHO.js +1382 -0
  126. package/dist/cli-delegate/chunk-XGA2NTU4.js +3727 -0
  127. package/dist/cli-delegate/cmd_conformance.js +4 -3
  128. package/dist/cli-delegate/cmd_converge.js +3 -3
  129. package/dist/cli-delegate/cmd_doctor.js +3 -2
  130. package/dist/cli-delegate/cmd_explain.js +36 -36
  131. package/dist/cli-delegate/cmd_export.js +4 -4
  132. package/dist/cli-delegate/cmd_handoff.js +1169 -0
  133. package/dist/cli-delegate/cmd_memory_get.js +35 -3754
  134. package/dist/cli-delegate/cmd_migrate.js +2 -2
  135. package/dist/cli-delegate/cmd_prune.js +4 -4
  136. package/dist/cli-delegate/cmd_route_audit.js +249 -0
  137. package/dist/cli-delegate/cmd_route_explain.js +285 -0
  138. package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
  139. package/dist/cli-delegate/cmd_sync.js +4 -4
  140. package/dist/cli-delegate/cmd_uninstall.js +4 -4
  141. package/dist/cli-delegate/cmd_update.js +3 -3
  142. package/dist/cli-delegate/cmd_upgrade.js +3 -3
  143. package/dist/cli-delegate/cmd_validate.js +4 -4
  144. package/dist/cli-delegate/cmd_versions.js +3 -3
  145. package/dist/discovery/deprecation-report.md +1 -1
  146. package/dist/discovery/discovery-manifest.json +84 -84
  147. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  148. package/dist/discovery/discovery-manifest.summary.md +1 -1
  149. package/dist/discovery/orphan-report.md +1 -1
  150. package/dist/discovery/packs.json +2 -2
  151. package/dist/discovery/trust-report.md +1 -1
  152. package/dist/discovery/workspaces.json +2 -2
  153. package/dist/hooks/dispatch.js +602 -507
  154. package/dist/install/install.mjs +34 -0
  155. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  156. package/dist/mcp/mcp-cloudflare-catalogue.json +2 -2
  157. package/dist/mcp/registry-manifest.json +4 -4
  158. package/dist/mcp/server.json +2 -2
  159. package/dist/mcp/server.mjs +25 -3
  160. package/dist/router.json +1 -1
  161. package/dist/scripts/_lib/agent_src.js +39 -6
  162. package/dist/scripts/_lib/agent_src.js.map +1 -1
  163. package/dist/server/schemas/settings.js +25 -0
  164. package/dist/server/schemas/settings.js.map +1 -1
  165. package/docs/guidelines/abstraction-thresholds.md +53 -0
  166. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -1
  167. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +7 -0
  168. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +2 -2
  169. package/docs/guidelines/agent-infra/minimal-safe-diff-mechanics.md +5 -3
  170. package/docs/guidelines/agent-infra/size-and-scope.md +8 -2
  171. package/docs/guidelines/component-oriented-and-oop-development.md +11 -6
  172. package/docs/guidelines/docs/readme-size-and-splitting.md +5 -2
  173. package/llms.txt +1 -0
  174. package/package.json +1 -1
  175. package/src/agent-src/templates/scripts/work_engine/directives/ui/_fix_lane.ts +52 -0
  176. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +8 -0
  177. package/src/agent-src/templates/scripts/work_engine/directives/ui/design.ts +6 -0
  178. package/src/agent-src/templates/scripts/work_engine/intent/classify.ts +56 -4
  179. package/src/config/agent-settings.template.yml +68 -0
  180. package/src/config/evaluator-budgets.json +8 -11
  181. package/src/config/gate-coverage.yml +70 -0
  182. package/src/scripts/_cli/cmd_explain.ts +41 -36
  183. package/src/scripts/_cli/cmd_handoff.ts +405 -0
  184. package/src/scripts/_cli/cmd_route_audit.ts +290 -0
  185. package/src/scripts/_cli/cmd_route_explain.ts +269 -0
  186. package/src/scripts/_cli/handoff_generate.ts +453 -0
  187. package/src/scripts/_cli/handoff_sessions.ts +435 -0
  188. package/src/scripts/_dispatch.bash +32 -0
  189. package/src/scripts/_lib/agent_src.ts +36 -6
  190. package/src/scripts/_lib/git_env.ts +40 -0
  191. package/src/scripts/_lib/md_table.ts +27 -0
  192. package/src/scripts/_lib/planning_settings.ts +43 -0
  193. package/src/scripts/_lib/release_material.ts +156 -0
  194. package/src/scripts/_lib/router_match.ts +301 -0
  195. package/src/scripts/annotate_r1_outcomes.ts +258 -0
  196. package/src/scripts/check_completion_review.ts +1140 -0
  197. package/src/scripts/check_condensation.ts +2 -1
  198. package/src/scripts/check_finding_dispositions.ts +278 -0
  199. package/src/scripts/check_pack_size.ts +4 -3
  200. package/src/scripts/check_references.ts +1 -0
  201. package/src/scripts/check_release_highlights.ts +300 -0
  202. package/src/scripts/check_release_pr_shape.ts +4 -0
  203. package/src/scripts/check_release_surface_equality.ts +132 -0
  204. package/src/scripts/check_reply_consistency.ts +187 -138
  205. package/src/scripts/compile_router.ts +20 -3
  206. package/src/scripts/dispatch_r2_reviewer.ts +968 -0
  207. package/src/scripts/eval_ui_triviality.ts +156 -0
  208. package/src/scripts/handoff_context_hook.ts +148 -0
  209. package/src/scripts/hook_manifest.yaml +17 -7
  210. package/src/scripts/hooks/concern_registry.ts +2 -0
  211. package/src/scripts/lint_abstraction_thresholds.ts +327 -0
  212. package/src/scripts/lint_framework_leakage_allowlist.json +2 -2
  213. package/src/scripts/lint_plan_risk_register.ts +931 -0
  214. package/src/scripts/lint_profile_personas.ts +153 -0
  215. package/src/scripts/lint_token_budget_discipline.ts +148 -0
  216. package/src/scripts/lint_trigger_collisions.ts +257 -0
  217. package/src/scripts/release.ts +156 -32
  218. package/src/scripts/release_drill.ts +491 -0
  219. package/src/scripts/router_telemetry.ts +16 -290
  220. package/src/scripts/schemas/command.schema.json +33 -0
  221. package/src/scripts/schemas/rule.schema.json +10 -0
  222. package/src/scripts/self_review_gate.ts +41 -3
  223. package/src/scripts/skill_linter.ts +3 -1
  224. package/src/scripts/smoke/router.sh +1 -1
  225. package/src/scripts/update_counts.ts +60 -12
  226. package/src/server/schemas/settings.ts +41 -0
  227. package/dist/cli-delegate/{chunk-XX6TOPD7.js → chunk-WR6GTRHJ.js} +3 -3
@@ -14,7 +14,8 @@
14
14
  * - All headings from source present in condensed
15
15
  * - All code blocks preserved exactly
16
16
  * - YAML frontmatter identical
17
- * - Word count reduction within healthy range (10-60%)
17
+ * - Word count: excessive-reduction alarm only (> 60% lost warns; expected
18
+ * reduction is 0% post-ADR-201 verbatim copy — see the note near the check)
18
19
  * - Iron Law sections (## Iron Law / ### Iron Law / ## The Iron Law /
19
20
  * Iron Laws / numbered) preserved per `preservation-guard`: heading
20
21
  * verbatim at original level, structural-unit survival.
@@ -0,0 +1,278 @@
1
+ /**
2
+ * Finding-disposition gate (release-truth Phase 3).
3
+ *
4
+ * The recorded failure: the 9.14.0 release-PR self-review reported a critical
5
+ * symlink-traversal finding; the PR merged, the release head said "Security
6
+ * and correctness: none", and whether the finding was fixed or adjudicated
7
+ * false-positive was not reconstructable from the record.
8
+ *
9
+ * The record is a COMMITTED ledger — `agents/evidence/release-findings/
10
+ * <version>.json` — never the PR comment (council 2026-08-03: a comment is
11
+ * mutable and unaudited; it is transport, not a record). Every blocking/high
12
+ * finding in the ledger must carry a complete disposition
13
+ * `{status: fixed|false_positive|accepted_risk, commit (when fixed),
14
+ * rationale, verified_by}`; the release workflow is red while one does not.
15
+ *
16
+ * Modes:
17
+ * --release X.Y.Z [--dir <ledger dir>] validate the committed ledger
18
+ * --pr <n> additionally treat the gate's PR
19
+ * comment (machine block) as a TRIGGER: every blocking finding the
20
+ * comment reports must exist in the ledger — an un-ingested blocking
21
+ * finding is red. Deleting the comment can evade the trigger, but the
22
+ * committed ledger stays the durable record either way.
23
+ * --ingest <findings.json> --release X.Y.Z merge findings (from
24
+ * self_review_gate --findings-out) into the ledger with an empty
25
+ * disposition, so validation is red until a human fills it.
26
+ *
27
+ * Exit codes: 0 complete · 1 missing/incomplete dispositions · 2 usage or
28
+ * schema error. Deliberately NOT fail-open: this script never calls a model;
29
+ * a broken ledger file is a hard error, not a neutral skip.
30
+ */
31
+ import { spawnSync } from 'node:child_process';
32
+ import * as fs from 'node:fs';
33
+ import * as path from 'node:path';
34
+ import process from 'node:process';
35
+ import { fileURLToPath } from 'node:url';
36
+
37
+ const _HERE = fileURLToPath(import.meta.url);
38
+ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
39
+
40
+ export const LEDGER_DIR = 'agents/evidence/release-findings';
41
+ export const DISPOSITION_STATUSES = ['fixed', 'false_positive', 'accepted_risk'] as const;
42
+ export type DispositionStatus = (typeof DISPOSITION_STATUSES)[number];
43
+
44
+ export interface LedgerFinding {
45
+ finding_id: string;
46
+ severity: 'critical' | 'high' | 'medium' | 'low';
47
+ kind: 'security' | 'claim' | 'correctness' | 'style';
48
+ title: string;
49
+ file?: string | null;
50
+ status?: string;
51
+ commit?: string;
52
+ rationale?: string;
53
+ verified_by?: string;
54
+ date?: string;
55
+ }
56
+
57
+ export interface Ledger {
58
+ schema_version: number;
59
+ release: string;
60
+ findings: LedgerFinding[];
61
+ }
62
+
63
+ /** Mirrors self_review_gate.classifyBlocking — security/claim × critical/high. */
64
+ export function isBlocking(f: Pick<LedgerFinding, 'kind' | 'severity'>): boolean {
65
+ return (
66
+ (f.kind === 'security' || f.kind === 'claim') &&
67
+ (f.severity === 'critical' || f.severity === 'high')
68
+ );
69
+ }
70
+
71
+ /**
72
+ * Pure core: every blocking finding must carry a complete disposition.
73
+ * Returns one problem string per violation; empty array = gate green.
74
+ * Empty rationale / verified_by is a hygiene violation (waiver precedent:
75
+ * "empty string = hygiene violation"); status `fixed` requires a commit.
76
+ */
77
+ export function missing_dispositions(findings: readonly LedgerFinding[]): string[] {
78
+ const problems: string[] = [];
79
+ for (const f of findings) {
80
+ if (!isBlocking(f)) {
81
+ continue;
82
+ }
83
+ const label = `${f.finding_id} (${f.severity} ${f.kind}: ${f.title})`;
84
+ const status = (f.status ?? '').trim();
85
+ if (!status) {
86
+ problems.push(`${label}: no disposition status`);
87
+ continue;
88
+ }
89
+ if (!(DISPOSITION_STATUSES as readonly string[]).includes(status)) {
90
+ problems.push(`${label}: unknown status ${JSON.stringify(status)}`);
91
+ continue;
92
+ }
93
+ if (!(f.rationale ?? '').trim()) {
94
+ problems.push(`${label}: empty rationale`);
95
+ }
96
+ if (!(f.verified_by ?? '').trim()) {
97
+ problems.push(`${label}: empty verified_by`);
98
+ }
99
+ if (status === 'fixed' && !(f.commit ?? '').trim()) {
100
+ problems.push(`${label}: status "fixed" without a commit`);
101
+ }
102
+ }
103
+ return problems;
104
+ }
105
+
106
+ /** Findings the gate comment reported but the ledger never ingested. */
107
+ export function unrecorded_findings(
108
+ reported: ReadonlyArray<Pick<LedgerFinding, 'finding_id' | 'severity' | 'kind' | 'title'>>,
109
+ ledger: readonly LedgerFinding[],
110
+ ): string[] {
111
+ const known = new Set(ledger.map((f) => f.finding_id));
112
+ return reported
113
+ .filter((f) => isBlocking(f) && !known.has(f.finding_id))
114
+ .map((f) => `${f.finding_id} (${f.severity} ${f.kind}: ${f.title}) reported by the self-review but not in the ledger`);
115
+ }
116
+
117
+ export function parse_ledger(raw: string, source: string): Ledger {
118
+ let data: unknown;
119
+ try {
120
+ data = JSON.parse(raw);
121
+ } catch (e) {
122
+ throw new Error(`${source}: invalid JSON (${e instanceof Error ? e.message : String(e)})`);
123
+ }
124
+ const obj = data as Partial<Ledger>;
125
+ if (obj.schema_version !== 1 || typeof obj.release !== 'string' || !Array.isArray(obj.findings)) {
126
+ throw new Error(`${source}: expected {schema_version: 1, release, findings[]}`);
127
+ }
128
+ for (const f of obj.findings) {
129
+ if (!f || typeof f.finding_id !== 'string' || !f.finding_id.trim()) {
130
+ throw new Error(`${source}: finding without a finding_id`);
131
+ }
132
+ }
133
+ return obj as Ledger;
134
+ }
135
+
136
+ const _MACHINE_BLOCK_RE = /<!-- release-findings-json: (\[.*?\]) -->/su;
137
+
138
+ /** Extract reported findings from the gate's PR comments (latest block wins). */
139
+ export function parse_comment_findings(commentBodies: readonly string[]): LedgerFinding[] {
140
+ for (let i = commentBodies.length - 1; i >= 0; i--) {
141
+ const m = _MACHINE_BLOCK_RE.exec(commentBodies[i]!);
142
+ if (m) {
143
+ try {
144
+ return JSON.parse(m[1]!) as LedgerFinding[];
145
+ } catch {
146
+ return [];
147
+ }
148
+ }
149
+ }
150
+ return [];
151
+ }
152
+
153
+ function _ledger_path(dir: string, release: string): string {
154
+ return path.join(dir, `${release}.json`);
155
+ }
156
+
157
+ function _gh_comment_bodies(pr: string): string[] {
158
+ const r = spawnSync(
159
+ 'gh',
160
+ ['pr', 'view', pr, '--json', 'comments', '-q', '[.comments[].body] | @json'],
161
+ { encoding: 'utf-8', cwd: REPO_ROOT, maxBuffer: 64 * 1024 * 1024 },
162
+ );
163
+ if (r.status !== 0) {
164
+ process.stderr.write(`gh pr view ${pr} failed: ${r.stderr}\n`);
165
+ process.exit(2);
166
+ }
167
+ try {
168
+ return JSON.parse(JSON.parse(r.stdout.trim())) as string[];
169
+ } catch {
170
+ return [];
171
+ }
172
+ }
173
+
174
+ function main(argv: readonly string[]): number {
175
+ let release: string | null = null;
176
+ let dir = path.join(REPO_ROOT, LEDGER_DIR);
177
+ let pr: string | null = null;
178
+ let ingest: string | null = null;
179
+ for (let i = 0; i < argv.length; i++) {
180
+ const a = argv[i];
181
+ if (a === '--release') release = argv[++i] ?? null;
182
+ else if (a === '--dir') dir = argv[++i] ?? dir;
183
+ else if (a === '--pr') pr = argv[++i] ?? null;
184
+ else if (a === '--ingest') ingest = argv[++i] ?? null;
185
+ else {
186
+ process.stderr.write(`unknown argument: ${a}\n`);
187
+ return 2;
188
+ }
189
+ }
190
+ if (!release) {
191
+ const pkg = JSON.parse(
192
+ fs.readFileSync(path.join(REPO_ROOT, 'package.json'), 'utf-8'),
193
+ ) as Record<string, unknown>;
194
+ release = String(pkg['version']);
195
+ }
196
+ const ledgerPath = _ledger_path(dir, release);
197
+
198
+ if (ingest) {
199
+ const incoming = parse_ledger(
200
+ JSON.stringify({
201
+ schema_version: 1,
202
+ release,
203
+ findings: (JSON.parse(fs.readFileSync(ingest, 'utf-8')) as { findings: LedgerFinding[] }).findings,
204
+ }),
205
+ ingest,
206
+ );
207
+ let ledger: Ledger = { schema_version: 1, release, findings: [] };
208
+ if (fs.existsSync(ledgerPath)) {
209
+ ledger = parse_ledger(fs.readFileSync(ledgerPath, 'utf-8'), ledgerPath);
210
+ }
211
+ const known = new Set(ledger.findings.map((f) => f.finding_id));
212
+ let added = 0;
213
+ for (const f of incoming.findings) {
214
+ if (!known.has(f.finding_id)) {
215
+ ledger.findings.push(f);
216
+ added++;
217
+ }
218
+ }
219
+ fs.mkdirSync(path.dirname(ledgerPath), { recursive: true });
220
+ fs.writeFileSync(ledgerPath, JSON.stringify(ledger, null, 2) + '\n');
221
+ process.stdout.write(
222
+ `📥 ingested ${added} new finding(s) into ${path.relative(REPO_ROOT, ledgerPath)} — ` +
223
+ 'fill dispositions before the release validation goes green\n',
224
+ );
225
+ return 0;
226
+ }
227
+
228
+ let ledger: Ledger = { schema_version: 1, release, findings: [] };
229
+ if (fs.existsSync(ledgerPath)) {
230
+ try {
231
+ ledger = parse_ledger(fs.readFileSync(ledgerPath, 'utf-8'), ledgerPath);
232
+ } catch (e) {
233
+ process.stderr.write(`❌ ${e instanceof Error ? e.message : String(e)}\n`);
234
+ return 2;
235
+ }
236
+ }
237
+
238
+ const problems = missing_dispositions(ledger.findings);
239
+ if (pr) {
240
+ const reported = parse_comment_findings(_gh_comment_bodies(pr));
241
+ problems.push(...unrecorded_findings(reported, ledger.findings));
242
+ }
243
+
244
+ if (problems.length === 0) {
245
+ const n = ledger.findings.length;
246
+ process.stdout.write(
247
+ n === 0
248
+ ? `✅ no recorded findings for ${release} (ledger ${fs.existsSync(ledgerPath) ? 'empty' : 'absent'})\n`
249
+ : `✅ all ${n} recorded finding(s) for ${release} dispositioned (blocking ones completely)\n`,
250
+ );
251
+ return 0;
252
+ }
253
+ process.stderr.write(`❌ undispositioned blocking finding(s) for ${release}:\n`);
254
+ for (const p of problems) {
255
+ process.stderr.write(` - ${p}\n`);
256
+ }
257
+ process.stderr.write(
258
+ ` Record dispositions in ${path.relative(REPO_ROOT, ledgerPath)} ` +
259
+ '({finding_id, status: fixed|false_positive|accepted_risk, commit, rationale, verified_by}).\n' +
260
+ ' Ingest reported findings: ./scripts-run src/scripts/check_finding_dispositions ' +
261
+ `--ingest <findings.json> --release ${release}\n`,
262
+ );
263
+ return 1;
264
+ }
265
+
266
+ const _isMain = (() => {
267
+ const entry = process.argv[1];
268
+ if (!entry) return false;
269
+ try {
270
+ return fs.realpathSync(entry) === fs.realpathSync(_HERE);
271
+ } catch {
272
+ return false;
273
+ }
274
+ })();
275
+
276
+ if (_isMain) {
277
+ process.exit(main(process.argv.slice(2)));
278
+ }
@@ -10,9 +10,10 @@
10
10
  * 2. PER-SKILL share of the skills subtree — no single skill may silently
11
11
  * reclaim space freed elsewhere. Named exceptions carry a reason.
12
12
  *
13
- * The UNPACKED size is deliberately NOT re-gated here: it is already owned by
14
- * `evaluator-budgets.unpacked_size_mb`, measured at release time on the BUILT
15
- * artifact. Two gates for one lever is how conflicting numbers start.
13
+ * The UNPACKED size is deliberately NOT gated here — and since 2026-08-04 not
14
+ * anywhere: the former `evaluator-budgets.unpacked_size_mb` key was removed by
15
+ * maintainer decision (see `removed_2026_08_04` in evaluator-budgets.json);
16
+ * the evaluator umbrella still measures it as evidence.
16
17
  *
17
18
  * Usage:
18
19
  * ./scripts-run src/scripts/check_pack_size [--json]
@@ -58,6 +58,7 @@ const SKIP_DIRS = [
58
58
  'agents/reports', // transient run reports — historical/scratch refs, not stable artefacts
59
59
  'agents/roadmaps/skipped', // skipped roadmaps — abandoned plans w/ forward-refs that never shipped
60
60
  'agents/runtime', // volatile / machine-generated artefacts (gitignored)
61
+ 'agents/state', // Tier-1 hook runtime state (gitignored, agents-layout § state/) — targets are runtime-created, never in the tree
61
62
  'agents/tmp', // user inbox (gitignored) — user-dropped notes/todos; agents only read + move to tmp.old/
62
63
  'agents/tmp.old', // processed inbox archive (gitignored) — consumed tmp/ files; janitor TTL 30 days
63
64
  'agents/.harvest-local', // deliberate gitignored evidence store (source-confidentiality) — refs to it can never resolve in CI
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Highlight plausibility gate (release-truth Phase 2).
3
+ *
4
+ * The recorded failure: the curated release head said `_none_` twice while
5
+ * false — 9.13.0 shipped behaviour changes, a removed public trigger type, a
6
+ * security fix, and honest nulls; the head claimed none of it. Same at
7
+ * 9.14.0.
8
+ *
9
+ * This gate derives GENERATED evidence categories from the release span
10
+ * (security-tagged commits, behaviour/default changes from conventional
11
+ * commit types + rule/schema diffs, honest-null markers, removed public
12
+ * surface) and FAILS when a populated generated category meets a `_none_`
13
+ * curated field. It blocks the contradiction only — a human still writes the
14
+ * prose; nothing here auto-formulates highlights, and a filled field is
15
+ * never judged for quality.
16
+ *
17
+ * Derivation is deliberately conservative (documented per-label below):
18
+ * a false red makes every release annoying; a miss only returns the head to
19
+ * the pre-gate state. Adjudication of derived evidence stays human.
20
+ *
21
+ * Usage:
22
+ * check_release_highlights --version X.Y.Z [--from <ref>] [--to <ref>]
23
+ * [--changelog <path>]
24
+ *
25
+ * `--from` defaults to the latest reachable release tag before `--to`
26
+ * (default HEAD). Exit codes: 0 plausible · 1 contradiction · 2 usage error.
27
+ */
28
+ import { spawnSync } from 'node:child_process';
29
+ import * as fs from 'node:fs';
30
+ import * as path from 'node:path';
31
+ import process from 'node:process';
32
+ import { fileURLToPath } from 'node:url';
33
+
34
+ import { extract_changelog_section } from './_lib/release_material.js';
35
+
36
+ const _HERE = fileURLToPath(import.meta.url);
37
+ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
38
+
39
+ // The five curated labels, mirrored from release.ts RELEASE_HEAD_SECTIONS
40
+ // (kept literal here so this checker has no import edge into the release
41
+ // pipeline it audits; the unit tests pin the two lists against each other).
42
+ export const HEAD_LABELS: readonly string[] = [
43
+ 'Behaviour changes',
44
+ 'Default changes + migration',
45
+ 'Security and correctness',
46
+ 'Honest nulls',
47
+ 'Known limitations',
48
+ ];
49
+
50
+ export const HEAD_NONE = '_none_';
51
+
52
+ export interface SpanCommit {
53
+ sha: string;
54
+ subject: string;
55
+ body: string;
56
+ /** `A` / `M` / `D` / `R…` status per touched path. */
57
+ files: ReadonlyArray<{ status: string; path: string }>;
58
+ breaking: boolean;
59
+ }
60
+
61
+ /** Public artefact trees whose deletions count as removed public surface. */
62
+ const _PUBLIC_SURFACE_PREFIXES = [
63
+ 'src/skills/',
64
+ 'src/rules/',
65
+ 'src/agent-src/commands/',
66
+ 'src/domains/',
67
+ ];
68
+
69
+ const _RULE_OR_SCHEMA_PREFIXES = ['src/rules/', 'src/scripts/schemas/'];
70
+
71
+ /**
72
+ * Derive evidence per curated label from the span. Rules per label:
73
+ *
74
+ * - Security and correctness: conventional scope matching /secur/i, or the
75
+ * whole word "security" in the subject.
76
+ * - Behaviour changes: breaking (`!` / BREAKING CHANGE) commits, diffs
77
+ * touching `src/rules/` or `src/scripts/schemas/`, and deletions under the
78
+ * public artefact trees (removed public surface).
79
+ * - Default changes + migration: subject carrying the whole word "default",
80
+ * "migration", or "migrate".
81
+ * - Honest nulls: subject or body carrying an "honest null" marker.
82
+ * - Known limitations: never derived — pure prose, not gate-checkable.
83
+ */
84
+ export function derive_categories(commits: readonly SpanCommit[]): Record<string, string[]> {
85
+ const out: Record<string, string[]> = {};
86
+ for (const label of HEAD_LABELS) {
87
+ out[label] = [];
88
+ }
89
+ const evidence = (c: SpanCommit): string => `${c.sha.slice(0, 7)} ${c.subject}`;
90
+ for (const c of commits) {
91
+ const scopeMatch = /^\w+\(([^)]*)\)!?:/u.exec(c.subject);
92
+ const scope = scopeMatch ? scopeMatch[1]! : '';
93
+ if (/secur/iu.test(scope) || /\bsecurity\b/iu.test(c.subject)) {
94
+ out['Security and correctness']!.push(evidence(c));
95
+ }
96
+ const removedPublic = c.files.filter(
97
+ (f) => f.status.startsWith('D') && _PUBLIC_SURFACE_PREFIXES.some((p) => f.path.startsWith(p)),
98
+ );
99
+ const ruleOrSchema = c.files.some((f) =>
100
+ _RULE_OR_SCHEMA_PREFIXES.some((p) => f.path.startsWith(p)),
101
+ );
102
+ if (c.breaking || ruleOrSchema || removedPublic.length > 0) {
103
+ const detail =
104
+ removedPublic.length > 0
105
+ ? `${evidence(c)} (removes ${removedPublic.map((f) => f.path).join(', ')})`
106
+ : evidence(c);
107
+ out['Behaviour changes']!.push(detail);
108
+ }
109
+ if (/\b(default|migration|migrate)\b/iu.test(c.subject)) {
110
+ out['Default changes + migration']!.push(evidence(c));
111
+ }
112
+ if (/honest[ -]null/iu.test(`${c.subject}\n${c.body}`)) {
113
+ out['Honest nulls']!.push(evidence(c));
114
+ }
115
+ }
116
+ return out;
117
+ }
118
+
119
+ /**
120
+ * Parse the curated head from a changelog section body. Returns null when
121
+ * the section carries no `### Release highlights` head (pre-9.9.0 entries).
122
+ */
123
+ export function parse_curated_head(sectionBody: string): Record<string, string> | null {
124
+ if (!sectionBody.includes('### Release highlights')) {
125
+ return null;
126
+ }
127
+ const out: Record<string, string> = {};
128
+ for (const label of HEAD_LABELS) {
129
+ const re = new RegExp(
130
+ `^-\\s+\\*\\*${label.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&')}:\\*\\*\\s*(.*)$`,
131
+ 'm',
132
+ );
133
+ const m = re.exec(sectionBody);
134
+ if (m) {
135
+ out[label] = m[1]!.trim();
136
+ }
137
+ }
138
+ return Object.keys(out).length > 0 ? out : null;
139
+ }
140
+
141
+ export interface Contradiction {
142
+ label: string;
143
+ evidence: string[];
144
+ }
145
+
146
+ /**
147
+ * A populated generated category meeting a `_none_` curated field is a
148
+ * contradiction. Filled fields and underived labels never fail.
149
+ */
150
+ export function highlight_contradictions(
151
+ curated: Readonly<Record<string, string>>,
152
+ derived: Readonly<Record<string, readonly string[]>>,
153
+ ): Contradiction[] {
154
+ const out: Contradiction[] = [];
155
+ for (const label of HEAD_LABELS) {
156
+ const curatedValue = (curated[label] ?? '').trim();
157
+ const ev = derived[label] ?? [];
158
+ if (curatedValue === HEAD_NONE && ev.length > 0) {
159
+ out.push({ label, evidence: [...ev] });
160
+ }
161
+ }
162
+ return out;
163
+ }
164
+
165
+ // ─── git span collection (CLI only — the core above is pure) ────────────────
166
+
167
+ const _RECORD = '\u001e';
168
+ const _FIELD = '\u001f';
169
+
170
+ export function _parse_git_log(raw: string): SpanCommit[] {
171
+ const commits: SpanCommit[] = [];
172
+ for (const record of raw.split(_RECORD)) {
173
+ if (!record.trim()) {
174
+ continue;
175
+ }
176
+ const [head, ...restLines] = record.split('\n');
177
+ const parts = (head ?? '').split(_FIELD);
178
+ if (parts.length < 3) {
179
+ continue;
180
+ }
181
+ const sha = parts[0]!.trim();
182
+ const subject = parts[1]!;
183
+ const bodyParts: string[] = [parts.slice(2).join(_FIELD)];
184
+ const files: Array<{ status: string; path: string }> = [];
185
+ for (const line of restLines) {
186
+ const m = /^([A-Z]\d*)\t(.+)$/u.exec(line);
187
+ if (m) {
188
+ // Renames carry two paths; the last tab field is the new path,
189
+ // the first the old — record both so a rename out of a public
190
+ // tree still counts its origin path.
191
+ const segs = m[2]!.split('\t');
192
+ for (const p of segs) {
193
+ files.push({ status: m[1]!, path: p });
194
+ }
195
+ } else {
196
+ // %b is multi-line — everything that is not a name-status
197
+ // line is body continuation (honest-null markers live there).
198
+ bodyParts.push(line);
199
+ }
200
+ }
201
+ const body = bodyParts.join('\n');
202
+ commits.push({
203
+ sha,
204
+ subject,
205
+ body,
206
+ files,
207
+ breaking: /^\w+(\([^)]*\))?!:/u.test(subject) || /BREAKING CHANGE/u.test(body),
208
+ });
209
+ }
210
+ return commits;
211
+ }
212
+
213
+ function _git(args: string[]): string {
214
+ const r = spawnSync('git', args, { encoding: 'utf-8', cwd: REPO_ROOT, maxBuffer: 64 * 1024 * 1024 });
215
+ if (r.status !== 0) {
216
+ process.stderr.write(`git ${args.join(' ')} failed: ${r.stderr}\n`);
217
+ process.exit(2);
218
+ }
219
+ return r.stdout;
220
+ }
221
+
222
+ function _collect_span(from: string, to: string): SpanCommit[] {
223
+ const raw = _git([
224
+ 'log',
225
+ `${from}..${to}`,
226
+ '--no-merges',
227
+ '--name-status',
228
+ '--format=%x1e%H%x1f%s%x1f%b',
229
+ ]);
230
+ return _parse_git_log(raw);
231
+ }
232
+
233
+ function main(argv: readonly string[]): number {
234
+ let version: string | null = null;
235
+ let from: string | null = null;
236
+ let to = 'HEAD';
237
+ let changelogPath = path.join(REPO_ROOT, 'CHANGELOG.md');
238
+ for (let i = 0; i < argv.length; i++) {
239
+ const a = argv[i];
240
+ if (a === '--version') version = argv[++i] ?? null;
241
+ else if (a === '--from') from = argv[++i] ?? null;
242
+ else if (a === '--to') to = argv[++i] ?? to;
243
+ else if (a === '--changelog') changelogPath = argv[++i] ?? changelogPath;
244
+ else {
245
+ process.stderr.write(`unknown argument: ${a}\n`);
246
+ return 2;
247
+ }
248
+ }
249
+ if (!version) {
250
+ process.stderr.write('--version X.Y.Z is required\n');
251
+ return 2;
252
+ }
253
+ if (!from) {
254
+ from = _git(['describe', '--tags', '--abbrev=0', '--match', '[0-9]*.[0-9]*.[0-9]*', `${to}^`]).trim();
255
+ }
256
+ const section = extract_changelog_section(fs.readFileSync(changelogPath, 'utf-8'), version);
257
+ if (!section) {
258
+ process.stderr.write(`CHANGELOG carries no section for ${version}\n`);
259
+ return 2;
260
+ }
261
+ const curated = parse_curated_head(section.body);
262
+ if (!curated) {
263
+ process.stdout.write(`ℹ️ no curated head in the ${version} section — nothing to check\n`);
264
+ return 0;
265
+ }
266
+ const derived = derive_categories(_collect_span(from, to));
267
+ const contradictions = highlight_contradictions(curated, derived);
268
+ if (contradictions.length === 0) {
269
+ process.stdout.write(`✅ curated head plausible for ${version} (span ${from}..${to})\n`);
270
+ return 0;
271
+ }
272
+ process.stderr.write(
273
+ `❌ curated head contradicts the release span for ${version} (${from}..${to}):\n`,
274
+ );
275
+ for (const c of contradictions) {
276
+ process.stderr.write(` - **${c.label}:** is \`_none_\` but the span carries:\n`);
277
+ for (const e of c.evidence) {
278
+ process.stderr.write(` ${e}\n`);
279
+ }
280
+ }
281
+ process.stderr.write(
282
+ ' Fill the curated head in CHANGELOG.md (a human writes the prose) or adjudicate ' +
283
+ 'the evidence in the PR — the gate only blocks the `_none_` contradiction.\n',
284
+ );
285
+ return 1;
286
+ }
287
+
288
+ const _isMain = (() => {
289
+ const entry = process.argv[1];
290
+ if (!entry) return false;
291
+ try {
292
+ return fs.realpathSync(entry) === fs.realpathSync(_HERE);
293
+ } catch {
294
+ return false;
295
+ }
296
+ })();
297
+
298
+ if (_isMain) {
299
+ process.exit(main(process.argv.slice(2)));
300
+ }
@@ -34,6 +34,10 @@ const ALLOWLIST_GLOBS = [
34
34
  'src/domains/*/pack.yaml',
35
35
  'src/domains/*/README.md',
36
36
  'docs/archive/CHANGELOG-pre-*.md',
37
+ // Finding-disposition ledger (release-truth Phase 3) — a release PR must
38
+ // be able to carry the dispositions of its own self-review findings; the
39
+ // finding-dispositions gate is red until blocking ones are recorded here.
40
+ 'agents/evidence/release-findings/*.json',
37
41
  // Project-settings template pin — bumped by release.ts set_template_pin and
38
42
  // its regenerated dist twin (kept in lockstep with package.json.version).
39
43
  'src/agent-src/templates/agents/agent-project-settings.example.yml',