@event4u/agent-config 14.7.0 → 14.8.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 (138) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +245 -388
  3. package/README.md +1 -1
  4. package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
  5. package/dist/agent-src/commands/evals/roadmap.json +17 -1
  6. package/dist/agent-src/commands/pr/create.md +14 -2
  7. package/dist/agent-src/commands/pr/merge.md +379 -0
  8. package/dist/agent-src/commands/roadmap/process-full.md +157 -3
  9. package/dist/agent-src/commands/roadmap.md +1 -1
  10. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
  11. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
  12. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +64 -2
  13. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
  14. package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
  15. package/dist/agent-src/rules/decision-revisit-gate.md +65 -33
  16. package/dist/agent-src/rules/recurring-criticism.md +1 -1
  17. package/dist/agent-src/rules/roadmap-progress-sync.md +19 -0
  18. package/dist/agent-src/rules/source-confidentiality.md +1 -1
  19. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
  20. package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
  21. package/dist/agent-src/scripts/update_roadmap_progress.ts +47 -48
  22. package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
  23. package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
  24. package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
  25. package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
  26. package/dist/agent-src/skills/security/SKILL.md +8 -0
  27. package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
  28. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  29. package/dist/agent-src/templates/roadmaps.md +20 -2
  30. package/dist/cli/registry.js +1 -0
  31. package/dist/cli/registry.js.map +1 -1
  32. package/dist/cli-delegate/{chunk-JZ4NR2TW.js → chunk-7PRE6ZV2.js} +1 -1
  33. package/dist/cli-delegate/{chunk-7F3VYSM2.js → chunk-EEJUMCDZ.js} +14 -14
  34. package/dist/cli-delegate/{chunk-74E3TKWV.js → chunk-H4VIVD4C.js} +5 -5
  35. package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
  36. package/dist/cli-delegate/{chunk-4VR4ORQ6.js → chunk-LPPWO5CE.js} +4 -3
  37. package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-N7CDDGI3.js} +4 -23
  38. package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
  39. package/dist/cli-delegate/cmd_conformance.js +14 -14
  40. package/dist/cli-delegate/cmd_converge.js +3 -3
  41. package/dist/cli-delegate/cmd_doctor.js +7 -7
  42. package/dist/cli-delegate/cmd_export.js +6 -6
  43. package/dist/cli-delegate/cmd_handoff.js +7 -6
  44. package/dist/cli-delegate/cmd_memory_get.js +2 -1
  45. package/dist/cli-delegate/cmd_migrate.js +2 -2
  46. package/dist/cli-delegate/cmd_preflight.js +5 -5
  47. package/dist/cli-delegate/cmd_prune.js +6 -6
  48. package/dist/cli-delegate/cmd_route_audit.js +6 -5
  49. package/dist/cli-delegate/cmd_route_explain.js +3 -3
  50. package/dist/cli-delegate/cmd_session_recycle.js +3 -3
  51. package/dist/cli-delegate/cmd_settings_get.js +3 -3
  52. package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
  53. package/dist/cli-delegate/cmd_sync.js +6 -6
  54. package/dist/cli-delegate/cmd_uninstall.js +6 -6
  55. package/dist/cli-delegate/cmd_update.js +3 -3
  56. package/dist/cli-delegate/cmd_upgrade.js +3 -3
  57. package/dist/cli-delegate/cmd_validate.js +6 -6
  58. package/dist/cli-delegate/cmd_versions.js +3 -3
  59. package/dist/discovery/deprecation-report.md +1 -1
  60. package/dist/discovery/discovery-manifest.json +53 -22
  61. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  62. package/dist/discovery/discovery-manifest.summary.md +3 -3
  63. package/dist/discovery/dormancy-report.md +1 -1
  64. package/dist/discovery/orphan-report.md +1 -1
  65. package/dist/discovery/packs.json +7 -6
  66. package/dist/discovery/trust-report.md +2 -2
  67. package/dist/discovery/workspaces.json +6 -5
  68. package/dist/hooks/dispatch.js +806 -681
  69. package/dist/install/globalRuleLayers.js +169 -0
  70. package/dist/install/globalRuleLayers.js.map +1 -0
  71. package/dist/install/partitionEligibility.js +156 -0
  72. package/dist/install/partitionEligibility.js.map +1 -1
  73. package/dist/install/ruleLayerPartition.js +157 -0
  74. package/dist/install/ruleLayerPartition.js.map +1 -0
  75. package/dist/mcp/registry-manifest.json +3 -3
  76. package/dist/mcp/server.json +2 -2
  77. package/dist/mcp/server.mjs +654 -578
  78. package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
  79. package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
  80. package/dist/scripts/condense.js +52 -89
  81. package/dist/scripts/condense.js.map +1 -1
  82. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
  83. package/docs/guidelines/agent-infra/false-green.md +47 -1
  84. package/package.json +1 -1
  85. package/src/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
  86. package/src/agent-src/scripts/dashboard_mode.ts +212 -0
  87. package/src/agent-src/scripts/update_roadmap_progress.ts +47 -48
  88. package/src/config/ci-local-parity.yml +21 -0
  89. package/src/config/estate-count-budget.json +275 -2
  90. package/src/config/evaluator-budgets.json +3 -3
  91. package/src/config/gate-coverage.yml +220 -12
  92. package/src/config/gate-violation-baselines.json +73 -63
  93. package/src/config/gitignore-block.txt +15 -2
  94. package/src/config/hook-latency-budget.json +5 -0
  95. package/src/config/rule-stub-ceilings.json +17 -3
  96. package/src/install/globalRuleLayers.ts +197 -0
  97. package/src/install/partitionEligibility.ts +177 -0
  98. package/src/install/ruleLayerPartition.ts +212 -0
  99. package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
  100. package/src/scripts/_dispatch.bash +11 -0
  101. package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
  102. package/src/scripts/_lib/adr_frontmatter.ts +459 -0
  103. package/src/scripts/_lib/envelope_grounding.ts +11 -1
  104. package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
  105. package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
  106. package/src/scripts/adr/evidence_census.ts +899 -0
  107. package/src/scripts/adr/regenerate_index.ts +12 -32
  108. package/src/scripts/adr_cite_check.ts +540 -39
  109. package/src/scripts/audit_adr_coverage.ts +103 -17
  110. package/src/scripts/bench_hook_latency.ts +204 -0
  111. package/src/scripts/build_archive_index.ts +34 -0
  112. package/src/scripts/check_adr_frontmatter.ts +574 -39
  113. package/src/scripts/check_hook_bundle_content.ts +219 -0
  114. package/src/scripts/check_new_adr_evidence.ts +529 -0
  115. package/src/scripts/check_references.ts +18 -0
  116. package/src/scripts/check_rule_layer_partition.ts +531 -0
  117. package/src/scripts/check_single_delivery.ts +137 -4
  118. package/src/scripts/check_standing_rule_delivery.ts +8 -2
  119. package/src/scripts/condense.ts +61 -91
  120. package/src/scripts/external_sources_denylist.json +4 -1
  121. package/src/scripts/hooks/block_unauthorized_git.ts +22 -2
  122. package/src/scripts/hooks/dispatch_hook.ts +154 -187
  123. package/src/scripts/hooks/dispatch_issues.ts +38 -19
  124. package/src/scripts/hooks/fallback_yaml.ts +66 -0
  125. package/src/scripts/hooks/host_semantics.ts +21 -0
  126. package/src/scripts/hooks/injection_budget.ts +13 -3
  127. package/src/scripts/hooks/py_json_dumps.ts +103 -0
  128. package/src/scripts/hooks/state_io.ts +88 -1
  129. package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
  130. package/src/scripts/injection_scan_hook.ts +137 -5
  131. package/src/scripts/lint_hook_manifest.ts +49 -0
  132. package/src/scripts/lint_provenance_vocabulary.ts +472 -4
  133. package/src/scripts/routing_doctor.ts +5 -1
  134. package/src/scripts/sync_pr_branch.ts +163 -20
  135. package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
  136. package/dist/cli-delegate/{chunk-6EOCNQAM.js → chunk-E4XIZDIB.js} +0 -0
  137. package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-MMSGVZKV.js} +3 -3
  138. package/dist/cli-delegate/{chunk-GJ5BVHXM.js → chunk-PMEKGDED.js} +3 -3
@@ -0,0 +1,1162 @@
1
+ import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
+ import {
3
+ DeadScopeError,
4
+ reportScanned
5
+ } from "./chunk-I3EIN2YG.js";
6
+ import "./chunk-J55WI3RR.js";
7
+
8
+ // src/scripts/_cli/cmd_adr_effective.ts
9
+ import * as fs2 from "node:fs";
10
+ import * as path3 from "node:path";
11
+ import process4 from "node:process";
12
+ import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from "node:url";
13
+
14
+ // src/scripts/adr_cite_check.ts
15
+ import * as fs from "node:fs";
16
+ import * as os from "node:os";
17
+ import * as path2 from "node:path";
18
+ import { fileURLToPath, pathToFileURL } from "node:url";
19
+
20
+ // src/scripts/_lib/adr_frontmatter.ts
21
+ var FOLD_MARKERS = /* @__PURE__ */ new Set([">", ">-", ">+", "|", "|-", "|+"]);
22
+ function consumeFolded(lines, start, parentIndent) {
23
+ const parts = [];
24
+ let i = start;
25
+ while (i < lines.length) {
26
+ const line = lines[i];
27
+ if (line.indent <= parentIndent) break;
28
+ parts.push(line.text);
29
+ i += 1;
30
+ }
31
+ return [parts.join(" ").trim(), i];
32
+ }
33
+ var QUOTED = /^["'](.*)["']$/;
34
+ function stripQuotes(value) {
35
+ return value.replace(QUOTED, "$1");
36
+ }
37
+ function parseInlineList(raw) {
38
+ const trimmed = raw.trim();
39
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return null;
40
+ const inner = trimmed.slice(1, -1).trim();
41
+ if (inner === "") return [];
42
+ return inner.split(",").map((part) => stripQuotes(part.trim())).filter((part) => part !== "");
43
+ }
44
+ function significantLines(block) {
45
+ const out = [];
46
+ for (const raw of block.split("\n")) {
47
+ const line = raw.replace(/\s+$/, "");
48
+ if (line === "") continue;
49
+ const trimmed = line.trimStart();
50
+ if (trimmed.startsWith("#")) continue;
51
+ out.push({ indent: line.length - trimmed.length, text: trimmed });
52
+ }
53
+ return out;
54
+ }
55
+ function parseBlock(lines, start, parentIndent) {
56
+ const first = lines[start];
57
+ if (first === void 0) return ["", start];
58
+ if (first.text.startsWith("- ") || first.text === "-") {
59
+ const items = [];
60
+ let i2 = start;
61
+ while (i2 < lines.length) {
62
+ const line = lines[i2];
63
+ if (line.indent <= parentIndent || !line.text.startsWith("-")) break;
64
+ items.push(stripQuotes(line.text.replace(/^-\s*/, "").trim()));
65
+ i2 += 1;
66
+ }
67
+ return [items, i2];
68
+ }
69
+ const colon = first.text.indexOf(":");
70
+ if (colon > 0) {
71
+ const map = {};
72
+ let i2 = start;
73
+ while (i2 < lines.length) {
74
+ const line = lines[i2];
75
+ if (line.indent <= parentIndent) break;
76
+ const idx = line.text.indexOf(":");
77
+ if (idx <= 0) break;
78
+ const key = line.text.slice(0, idx).trim();
79
+ const rest = line.text.slice(idx + 1).trim();
80
+ i2 += 1;
81
+ if (rest === "" || FOLD_MARKERS.has(rest)) {
82
+ const next = lines[i2];
83
+ if (next !== void 0 && next.indent > line.indent) {
84
+ if (FOLD_MARKERS.has(rest)) {
85
+ const [folded, consumed2] = consumeFolded(lines, i2, line.indent);
86
+ map[key] = folded;
87
+ i2 = consumed2;
88
+ continue;
89
+ }
90
+ const [child, consumed] = parseBlock(lines, i2, line.indent);
91
+ map[key] = child;
92
+ i2 = consumed;
93
+ continue;
94
+ }
95
+ map[key] = "";
96
+ continue;
97
+ }
98
+ const inline = parseInlineList(rest);
99
+ map[key] = inline ?? stripQuotes(rest);
100
+ }
101
+ return [map, i2];
102
+ }
103
+ const parts = [];
104
+ let i = start;
105
+ while (i < lines.length) {
106
+ const line = lines[i];
107
+ if (line.indent <= parentIndent) break;
108
+ parts.push(line.text);
109
+ i += 1;
110
+ }
111
+ return [parts.join(" ").trim(), i];
112
+ }
113
+ function foldToString(node) {
114
+ if (typeof node === "string") return node;
115
+ if (Array.isArray(node)) return `[${node.join(", ")}]`;
116
+ return Object.entries(node).map(([k, v]) => `${k}: ${foldToString(v)}`).join(" ").trim();
117
+ }
118
+ function readAdrFrontmatter(text) {
119
+ if (!text.startsWith("---\n")) return null;
120
+ const end = text.indexOf("\n---\n", 4);
121
+ if (end === -1) return null;
122
+ const lines = significantLines(text.slice(4, end));
123
+ const scalars = {};
124
+ const nested = {};
125
+ const keyOrder = [];
126
+ let i = 0;
127
+ while (i < lines.length) {
128
+ const line = lines[i];
129
+ if (line.indent > 0) {
130
+ i += 1;
131
+ continue;
132
+ }
133
+ const idx = line.text.indexOf(":");
134
+ if (idx <= 0) {
135
+ i += 1;
136
+ continue;
137
+ }
138
+ const key = line.text.slice(0, idx).trim();
139
+ const rest = line.text.slice(idx + 1).trim();
140
+ keyOrder.push(key);
141
+ i += 1;
142
+ const next = lines[i];
143
+ const hasBlock = next !== void 0 && next.indent > 0;
144
+ if (FOLD_MARKERS.has(rest) && hasBlock) {
145
+ const [folded, consumed] = consumeFolded(lines, i, 0);
146
+ scalars[key] = folded;
147
+ i = consumed;
148
+ continue;
149
+ }
150
+ if (rest === "" && hasBlock) {
151
+ const [child, consumed] = parseBlock(lines, i, 0);
152
+ i = consumed;
153
+ if (typeof child === "string") {
154
+ scalars[key] = child;
155
+ } else {
156
+ nested[key] = child;
157
+ }
158
+ continue;
159
+ }
160
+ const inline = parseInlineList(rest);
161
+ if (inline !== null) {
162
+ nested[key] = inline;
163
+ continue;
164
+ }
165
+ if (hasBlock) {
166
+ const [folded, consumed] = consumeFolded(lines, i, 0);
167
+ scalars[key] = `${stripQuotes(rest)} ${folded}`.trim();
168
+ i = consumed;
169
+ continue;
170
+ }
171
+ scalars[key] = stripQuotes(rest).replace(/^>-?$/, "");
172
+ }
173
+ return { scalars, nested, keyOrder };
174
+ }
175
+ function readAdrFrontmatterScalars(text) {
176
+ const parsed = readAdrFrontmatter(text);
177
+ if (parsed === null) return null;
178
+ const out = { ...parsed.scalars };
179
+ for (const [key, node] of Object.entries(parsed.nested)) {
180
+ out[key] = foldToString(node);
181
+ }
182
+ return out;
183
+ }
184
+ function asString(node) {
185
+ if (typeof node === "string") return node === "" ? null : node;
186
+ return null;
187
+ }
188
+ function asList(node) {
189
+ if (Array.isArray(node)) return node;
190
+ if (typeof node === "string" && node !== "") return [node];
191
+ return [];
192
+ }
193
+ function provenanceOf(fm) {
194
+ const node = fm.nested.provenance;
195
+ if (node === void 0 || typeof node === "string" || Array.isArray(node)) return null;
196
+ return {
197
+ kind: asString(node.kind),
198
+ decisionMakers: asList(node.decision_makers),
199
+ humanDirected: asString(node.human_directed),
200
+ agenticMode: asString(node.agentic_mode)
201
+ };
202
+ }
203
+ function evidenceOf(fm) {
204
+ const node = fm.nested.evidence;
205
+ if (node === void 0 || typeof node === "string" || Array.isArray(node)) return null;
206
+ return {
207
+ strength: asString(node.strength),
208
+ discovery: asString(node.discovery),
209
+ basis: asList(node.basis)
210
+ };
211
+ }
212
+ function authorityBasisOf(fm) {
213
+ return fm.scalars.authority_basis ?? null;
214
+ }
215
+ function isLowEvidenceAccepted(fm) {
216
+ if ((fm.scalars.status ?? "") !== "accepted") return false;
217
+ if (authorityBasisOf(fm) === "owner_intent") return false;
218
+ const evidence = evidenceOf(fm);
219
+ return evidence?.strength === "E0" || evidence?.strength === "E1";
220
+ }
221
+
222
+ // src/scripts/_lib/gate_ledger.ts
223
+ import process2 from "node:process";
224
+ var SKIP_REASON_MESSAGE = {
225
+ excluded_directory: "the target sits under a directory this gate deliberately does not walk",
226
+ size_limit: "the target exceeds the size ceiling this gate reads within",
227
+ binary_content: "the target is not decodable text, so the check does not apply to it",
228
+ missing_credentials: "the check needs a credential that is not present in this environment",
229
+ rules_unavailable: "the rule or policy corpus this check compares against could not be loaded",
230
+ manifest_absent: "the manifest that declares what to check for this target does not exist",
231
+ no_applicable_files: "the target resolved to zero files the check applies to",
232
+ disabled_by_configuration: "a setting in this repository turns this check off for the target",
233
+ generated_artifact: "the target is generated output whose source is checked instead",
234
+ dead_scan_root: "the root this target was enumerated from no longer exists",
235
+ declared_exemption: "the target carries an explicit, reviewable exemption from this check",
236
+ not_applicable_kind: "the target is a kind of artifact this check does not apply to",
237
+ precondition_unmet: "a precondition of this run settled the verdict before the target could be inspected",
238
+ check_did_not_run: "the check itself could not be executed, so the target was never read"
239
+ };
240
+ var UnaccountedTargetsError = class extends Error {
241
+ gate;
242
+ targets;
243
+ constructor(gate, targets) {
244
+ const shown = targets.slice(0, 20);
245
+ const more = targets.length - shown.length;
246
+ super(
247
+ `${gate}: ${String(targets.length)} planned target(s) reached no terminal outcome \u2014 the gate enumerated them and then neither checked, failed, skipped, nor excluded them, so its green result covers less than it appears to. Unaccounted: ${shown.join(", ")}${more > 0 ? ` \u2026 and ${String(more)} more` : ""}`
248
+ );
249
+ this.name = "UnaccountedTargetsError";
250
+ this.gate = gate;
251
+ this.targets = targets;
252
+ }
253
+ };
254
+ var LedgerUsageError = class extends Error {
255
+ constructor(message) {
256
+ super(message);
257
+ this.name = "LedgerUsageError";
258
+ }
259
+ };
260
+ var GateLedger = class {
261
+ gate;
262
+ /** Insertion-ordered so the unaccounted list reads in enumeration order. */
263
+ planned = /* @__PURE__ */ new Map();
264
+ constructor(gate) {
265
+ this.gate = gate;
266
+ }
267
+ /** Declare one or more targets this run intends to consider. */
268
+ plan(target) {
269
+ const targets = typeof target === "string" ? [target] : target;
270
+ for (const t of targets) {
271
+ if (this.planned.has(t)) {
272
+ throw new LedgerUsageError(
273
+ `${this.gate}: target planned twice: ${t}. A duplicate plan makes the denominator wrong in the direction that hides work.`
274
+ );
275
+ }
276
+ this.planned.set(t, void 0);
277
+ }
278
+ }
279
+ /** The target was checked and satisfied the gate. */
280
+ complete(target) {
281
+ this.resolve(target, { kind: "completed", reason: "" });
282
+ }
283
+ /** The target was checked and violated the gate. `reason` is the finding text. */
284
+ fail(target, reason) {
285
+ this.resolve(target, { kind: "failed", reason });
286
+ }
287
+ /** The target was NOT checked; `reason` names why, from the closed vocabulary. */
288
+ skip(target, reason) {
289
+ this.resolve(target, { kind: "skipped", reason });
290
+ }
291
+ /** The target does not belong to this gate's corpus; `reason` names why. */
292
+ outOfScope(target, reason) {
293
+ this.resolve(target, { kind: "out_of_scope", reason });
294
+ }
295
+ resolve(target, resolution) {
296
+ if (!this.planned.has(target)) {
297
+ throw new LedgerUsageError(
298
+ `${this.gate}: ${resolution.kind} recorded for an unplanned target: ${target}. Every target reaching an outcome must first be planned, or the denominator understates the work.`
299
+ );
300
+ }
301
+ const existing = this.planned.get(target);
302
+ if (existing !== void 0) {
303
+ throw new LedgerUsageError(
304
+ `${this.gate}: target resolved twice (${existing.kind} then ${resolution.kind}): ${target}.`
305
+ );
306
+ }
307
+ this.planned.set(target, resolution);
308
+ }
309
+ /** Planned targets that have not yet reached a terminal outcome, in enumeration order. */
310
+ unaccountedTargets() {
311
+ const out = [];
312
+ for (const [target, resolution] of this.planned) {
313
+ if (resolution === void 0) {
314
+ out.push(target);
315
+ }
316
+ }
317
+ return out;
318
+ }
319
+ /**
320
+ * Close the ledger and return the tally.
321
+ *
322
+ * @throws {UnaccountedTargetsError} when any planned target reached no outcome.
323
+ */
324
+ finalize() {
325
+ const unaccounted = this.unaccountedTargets();
326
+ if (unaccounted.length > 0) {
327
+ throw new UnaccountedTargetsError(this.gate, unaccounted);
328
+ }
329
+ const tally = {
330
+ planned: this.planned.size,
331
+ completed: 0,
332
+ skipped: 0,
333
+ failed: 0,
334
+ out_of_scope: 0,
335
+ unaccounted: 0,
336
+ skips_by_reason: {}
337
+ };
338
+ for (const resolution of this.planned.values()) {
339
+ if (resolution === void 0) {
340
+ continue;
341
+ }
342
+ if (resolution.kind === "completed") {
343
+ tally.completed += 1;
344
+ } else if (resolution.kind === "failed") {
345
+ tally.failed += 1;
346
+ } else {
347
+ if (resolution.kind === "skipped") {
348
+ tally.skipped += 1;
349
+ } else {
350
+ tally.out_of_scope += 1;
351
+ }
352
+ const code = resolution.reason;
353
+ tally.skips_by_reason[code] = (tally.skips_by_reason[code] ?? 0) + 1;
354
+ }
355
+ }
356
+ return tally;
357
+ }
358
+ /**
359
+ * Finalize and print the denominator on the success path.
360
+ *
361
+ * The line goes to stdout unconditionally, exactly as
362
+ * {@link import('./scan_scope.js').reportScanned} does and for the same
363
+ * recorded reason: CI passes `--quiet`, and a count only visible without it
364
+ * is not a count. It is deliberately NOT the `scanned: <N>` line
365
+ * `check_gate_coverage` parses — that stays the single machine-read
366
+ * coverage signal, and this is the human-auditable completeness breakdown
367
+ * beside it.
368
+ *
369
+ * @throws {UnaccountedTargetsError} via {@link finalize}.
370
+ */
371
+ report(write = process2.stdout.write.bind(process2.stdout)) {
372
+ const tally = this.finalize();
373
+ const checked = tally.completed + tally.failed;
374
+ const parts = [
375
+ `scanned=${String(checked)}`,
376
+ `planned=${String(tally.planned)}`,
377
+ `skipped=${String(tally.skipped + tally.out_of_scope)}`
378
+ ];
379
+ write(`${this.gate} ledger: ${parts.join(" ")}
380
+ `);
381
+ const codes = Object.keys(tally.skips_by_reason).sort();
382
+ for (const code of codes) {
383
+ const n = tally.skips_by_reason[code] ?? 0;
384
+ write(` ${code} \xD7${String(n)} \u2014 ${SKIP_REASON_MESSAGE[code]}
385
+ `);
386
+ }
387
+ return tally;
388
+ }
389
+ };
390
+
391
+ // src/scripts/_lib/gate_self_test.ts
392
+ import { spawnSync } from "node:child_process";
393
+ import * as path from "node:path";
394
+ import process3 from "node:process";
395
+ function runGateCli(repoRoot, scriptRelPath, args, cwd) {
396
+ const tsx = path.join(
397
+ repoRoot,
398
+ "node_modules",
399
+ ".bin",
400
+ process3.platform === "win32" ? "tsx.cmd" : "tsx"
401
+ );
402
+ const res = spawnSync(tsx, [path.join(repoRoot, scriptRelPath), ...args], {
403
+ cwd,
404
+ encoding: "utf-8",
405
+ maxBuffer: 64 * 1024 * 1024,
406
+ // The child must not recurse into its own self-test.
407
+ env: { ...process3.env, GATE_SELF_TEST_CHILD: "1" }
408
+ });
409
+ return res.status ?? 1;
410
+ }
411
+ function runSelfTest(opts) {
412
+ const write = opts.write ?? process3.stdout.write.bind(process3.stdout);
413
+ const minReject = opts.minRejectCases ?? 1;
414
+ const rejectCases = opts.cases.filter((c) => c.expect === "reject").length;
415
+ if (opts.cases.length < opts.minCases || rejectCases < minReject) {
416
+ write(
417
+ `\u274C ${opts.gate} --self-test: ${String(opts.cases.length)} case(s) (${String(rejectCases)} rejecting) is below the declared floor of ${String(opts.minCases)} (${String(minReject)} rejecting). A truncated self-test must fail rather than print success \u2014 deleting cases is otherwise the cheapest route to a green one.
418
+ `
419
+ );
420
+ return 1;
421
+ }
422
+ let failed = 0;
423
+ for (const testCase of opts.cases) {
424
+ let exit;
425
+ try {
426
+ exit = testCase.run();
427
+ } catch (e) {
428
+ write(`\u274C ${opts.gate} --self-test: ${testCase.name} \u2014 threw: ${String(e)}
429
+ `);
430
+ failed += 1;
431
+ continue;
432
+ }
433
+ const behaved = testCase.expect === "reject" ? exit !== 0 : exit === 0;
434
+ if (behaved) {
435
+ write(`\u2705 ${testCase.name} (expected ${testCase.expect}, exit ${String(exit)})
436
+ `);
437
+ } else {
438
+ write(
439
+ `\u274C ${testCase.name}: expected the gate to ${testCase.expect} this fixture, got exit ${String(exit)}. The gate's detection has stopped firing on a case it is supposed to catch.
440
+ `
441
+ );
442
+ failed += 1;
443
+ }
444
+ }
445
+ write(
446
+ `
447
+ ${opts.gate} --self-test: ${String(opts.cases.length - failed)}/${String(opts.cases.length)} case(s) behaved (${String(rejectCases)} rejecting, floor ${String(opts.minCases)})
448
+ `
449
+ );
450
+ return failed > 0 ? 1 : 0;
451
+ }
452
+
453
+ // src/scripts/adr_cite_check.ts
454
+ var _HERE = path2.dirname(fileURLToPath(import.meta.url));
455
+ var REPO_ROOT = path2.resolve(_HERE, "..", "..");
456
+ function scan_root() {
457
+ return process.env["ADR_CITE_CHECK_ROOT"] ?? REPO_ROOT;
458
+ }
459
+ var ADR_DIRS = ["docs/decisions", "docs/adrs"];
460
+ var SURFACES_NOT_SCANNED = [
461
+ "docs/contracts/adr-*.md \u2014 slug-named contracts, no ADR number to resolve against",
462
+ "agents/settings/contexts/adr-*.md \u2014 shadow notes, own status vocabulary",
463
+ "agents/decisions/ \u2014 row ledger, not per-decision files"
464
+ ];
465
+ var PARTIAL_COVERAGE = [];
466
+ var LOW_EVIDENCE_NOTICE = [
467
+ "authority_effect: disabled-shadow-mode",
468
+ "This record documents the prior choice. It does not by itself",
469
+ "establish that alternatives remain invalid."
470
+ ].join("\n");
471
+ function normalise_ref(ref) {
472
+ const raw = ref.trim();
473
+ const byPath = /(?:^|\/)docs\/adrs\/([a-z0-9-]+)\/(\d{1,4})-/i.exec(raw);
474
+ if (byPath?.[1] !== void 0 && byPath[2] !== void 0) {
475
+ const num = byPath[2].padStart(4, "0");
476
+ return { id: `ADR-${byPath[1]}-${num}`, area: byPath[1], num };
477
+ }
478
+ const byArea = /^adr-([a-z][a-z0-9-]*?)-(\d{1,4})$/i.exec(raw.replace(/\.md$/i, ""));
479
+ if (byArea?.[1] !== void 0 && byArea[2] !== void 0) {
480
+ const area = byArea[1].toLowerCase();
481
+ const num = byArea[2].padStart(4, "0");
482
+ return { id: `ADR-${area}-${num}`, area, num };
483
+ }
484
+ const m = /(?:^|[^0-9])(\d{1,4})(?:[^0-9]|$)/.exec(raw.replace(/^.*\//, ""));
485
+ if (m?.[1] === void 0) return null;
486
+ return { id: `ADR-${m[1].padStart(3, "0")}`, area: null, num: String(Number(m[1])) };
487
+ }
488
+ function walk_md(dir) {
489
+ if (!fs.existsSync(dir)) return [];
490
+ const out = [];
491
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
492
+ const full = path2.join(dir, entry.name);
493
+ if (entry.isDirectory()) out.push(...walk_md(full));
494
+ else if (entry.name.endsWith(".md") && entry.name !== "INDEX.md") out.push(full);
495
+ }
496
+ return out.sort();
497
+ }
498
+ function adr_files(repo_root = REPO_ROOT) {
499
+ return ADR_DIRS.flatMap((d) => walk_md(path2.join(repo_root, d)));
500
+ }
501
+ var CITATION_ROOTS = ["src/rules", "src/skills", "src/domains", "docs"];
502
+ var CITATION_FILE_EXTS = /* @__PURE__ */ new Set([".md", ".ts", ".yml", ".yaml"]);
503
+ var CITATION_RE = /ADR-(?:[a-z][a-z0-9]*-\d{1,4}|\d{1,4})(?![0-9A-Za-z])/g;
504
+ function walk_citable(dir) {
505
+ if (!fs.existsSync(dir)) return [];
506
+ const out = [];
507
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
508
+ const full = path2.join(dir, entry.name);
509
+ if (entry.isDirectory()) out.push(...walk_citable(full));
510
+ else if (CITATION_FILE_EXTS.has(path2.extname(entry.name))) out.push(full);
511
+ }
512
+ return out.sort();
513
+ }
514
+ function cited_refs(repo_root = REPO_ROOT) {
515
+ const found = /* @__PURE__ */ new Set();
516
+ for (const root of CITATION_ROOTS) {
517
+ for (const file of walk_citable(path2.join(repo_root, root))) {
518
+ for (const m of fs.readFileSync(file, "utf-8").matchAll(CITATION_RE)) {
519
+ found.add(m[0]);
520
+ }
521
+ }
522
+ }
523
+ return [...found].sort();
524
+ }
525
+ function parse_frontmatter(text) {
526
+ return readAdrFrontmatterScalars(text);
527
+ }
528
+ function protected_dimensions_of(fm) {
529
+ const node = fm.nested["protected_dimensions"];
530
+ if (Array.isArray(node)) return node;
531
+ if (typeof node === "string" && node !== "") return [node];
532
+ return [];
533
+ }
534
+ function unresolved_basis_refs(basis, repo_root) {
535
+ const missing = [];
536
+ for (const ref of basis) {
537
+ if (basis_ref_kind(ref) !== "path") continue;
538
+ if (!fs.existsSync(path2.join(repo_root, basis_ref_path(ref)))) missing.push(ref);
539
+ }
540
+ return missing;
541
+ }
542
+ function basis_ref_kind(ref) {
543
+ const t = ref.trim();
544
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(t)) return "url";
545
+ if (/^claim:/i.test(t)) return "claim";
546
+ return "path";
547
+ }
548
+ function basis_ref_path(ref) {
549
+ return ref.trim().replace(/\s*\([^)]*\)\s*$/, "").replace(/:\d+(-\d+)?$/, "").trim();
550
+ }
551
+ function amendment_blocks(body) {
552
+ const found = [];
553
+ for (const line of body.split("\n")) {
554
+ if (/^#{2,3}\s+Amendment\b/i.test(line) || /^\*\*Amended\b/i.test(line)) {
555
+ found.push(line.trim().replace(/^#+\s*/, "").replace(/\*\*/g, ""));
556
+ }
557
+ }
558
+ return found;
559
+ }
560
+ function trigger_state(fm) {
561
+ const t = (fm["review_trigger"] ?? "").trim();
562
+ if (t === "") return "none";
563
+ if (t.toLowerCase() === "unclassified") return "none";
564
+ return "indeterminate";
565
+ }
566
+ function verdict_for(r) {
567
+ if (!r.resolved) return "UNRESOLVED \u2014 no ADR file carries this number on a scanned surface.";
568
+ const status = (r.status ?? "").toLowerCase();
569
+ if (status === "superseded" || status === "deprecated") {
570
+ const by = r.superseded_by !== void 0 && r.superseded_by !== "\u2014" ? ` (by ${r.superseded_by})` : "";
571
+ return `NOT A LIVE LOCK \u2014 status is \`${status}\`${by}. Citing it as a blocker is a stale-state claim.`;
572
+ }
573
+ if (status === "rejected") {
574
+ return "LIVE \u2014 status `rejected` records a REJECTED PROPOSAL, so the rejection is the decision and it still binds. Reopening it means showing the premise it rejected on has changed; it does not mean the record is stale.";
575
+ }
576
+ if (r.amendment_blocks.length > 0 || r.amended_by !== void 0 && r.amended_by !== "\u2014") {
577
+ return "AMENDED \u2014 read the amendment before citing the original decision; the text above it may no longer hold.";
578
+ }
579
+ if (r.trigger_state === "none") {
580
+ return "LIVE, NO REOPEN CONDITION \u2014 the ADR records no `review_trigger`, so nothing would ever reopen it on its own. Treat that as a defect in the ADR, not as strength of the lock.";
581
+ }
582
+ return "LIVE, TRIGGER INDETERMINATE \u2014 the reopen condition is semantic and this tool cannot decide it. Not an unqualified lock: evaluate the condition against the current tree and route the result.";
583
+ }
584
+ function cite_check(refs, repo_root = REPO_ROOT) {
585
+ const files = adr_files(repo_root);
586
+ const contents = /* @__PURE__ */ new Map();
587
+ for (const f of files) contents.set(f, fs.readFileSync(f, "utf-8"));
588
+ return refs.map((ref) => {
589
+ const parsed = normalise_ref(ref);
590
+ if (parsed === null) {
591
+ const empty = {
592
+ ref,
593
+ resolved: false,
594
+ trigger_state: "none",
595
+ amendment_blocks: [],
596
+ referenced_by: []
597
+ };
598
+ return { ...empty, verdict: verdict_for(empty) };
599
+ }
600
+ const { id, area, num } = parsed;
601
+ const match = area === null ? files.find((f) => new RegExp(`(^|/)ADR-0*${num}[-.]`, "i").test(f)) : files.find(
602
+ (f) => new RegExp(`/docs/adrs/${area}/0*${Number(num)}-`, "i").test(f)
603
+ );
604
+ if (match === void 0) {
605
+ const empty = {
606
+ ref: id,
607
+ resolved: false,
608
+ trigger_state: "none",
609
+ amendment_blocks: [],
610
+ referenced_by: []
611
+ };
612
+ return { ...empty, verdict: verdict_for(empty) };
613
+ }
614
+ const text = contents.get(match) ?? "";
615
+ const fm = parse_frontmatter(text) ?? {};
616
+ const structured = readAdrFrontmatter(text);
617
+ const provenance = structured === null ? null : provenanceOf(structured);
618
+ const evidence = structured === null ? null : evidenceOf(structured);
619
+ const basis = structured === null ? null : authorityBasisOf(structured);
620
+ const evidence_basis = evidence?.basis ?? [];
621
+ const declared_policy = fm["reopen_policy"];
622
+ const reopen_policy = declared_policy !== void 0 && declared_policy !== "" ? declared_policy : "unclassified";
623
+ const reopen_policy_defaulted = declared_policy === void 0 || declared_policy === "";
624
+ const protected_dimensions = structured === null ? [] : protected_dimensions_of(structured);
625
+ const provisional = structured !== null && isLowEvidenceAccepted(structured);
626
+ const bodyStart = text.indexOf("\n---\n", 4);
627
+ const body = bodyStart === -1 ? text : text.slice(bodyStart + 5);
628
+ const cited = area === null ? new RegExp(`ADR-0*${num}\\b`, "i") : new RegExp(`ADR-${area}-0*${Number(num)}\\b`, "i");
629
+ const referenced_by = files.filter((f) => f !== match && cited.test(contents.get(f) ?? "")).map((f) => path2.relative(repo_root, f));
630
+ const partial = {
631
+ ref: id,
632
+ resolved: true,
633
+ file: path2.relative(repo_root, match),
634
+ ...fm["status"] !== void 0 ? { status: fm["status"] } : {},
635
+ ...fm["date"] !== void 0 ? { date: fm["date"] } : {},
636
+ ...fm["decision"] !== void 0 ? { decision: fm["decision"] } : {},
637
+ ...fm["review_trigger"] !== void 0 && fm["review_trigger"] !== "" ? { review_trigger: fm["review_trigger"] } : {},
638
+ trigger_state: trigger_state(fm),
639
+ ...fm["supersedes"] !== void 0 ? { supersedes: fm["supersedes"] } : {},
640
+ ...fm["superseded_by"] !== void 0 ? { superseded_by: fm["superseded_by"] } : {},
641
+ ...fm["amends"] !== void 0 ? { amends: fm["amends"] } : {},
642
+ ...fm["amended_by"] !== void 0 ? { amended_by: fm["amended_by"] } : {},
643
+ amendment_blocks: amendment_blocks(body),
644
+ referenced_by,
645
+ ...provenance?.kind !== void 0 && provenance.kind !== null ? { provenance_kind: provenance.kind } : {},
646
+ ...provenance?.agenticMode !== void 0 && provenance.agenticMode !== null ? { provenance_agentic_mode: provenance.agenticMode } : {},
647
+ ...evidence?.strength !== void 0 && evidence.strength !== null ? { evidence_strength: evidence.strength } : {},
648
+ ...evidence?.discovery !== void 0 && evidence.discovery !== null ? { evidence_discovery: evidence.discovery } : {},
649
+ ...evidence_basis.length > 0 ? {
650
+ evidence_basis,
651
+ evidence_basis_unresolved: unresolved_basis_refs(evidence_basis, repo_root)
652
+ } : {},
653
+ ...basis !== null ? { authority_basis: basis } : {},
654
+ reopen_policy,
655
+ reopen_policy_defaulted,
656
+ protected_dimensions,
657
+ ...provisional ? { authority_effect: "disabled-shadow-mode" } : {}
658
+ };
659
+ return { ...partial, verdict: verdict_for(partial) };
660
+ });
661
+ }
662
+ function render(results) {
663
+ const lines = [];
664
+ for (const r of results) {
665
+ lines.push(`
666
+ ${r.ref}${r.file !== void 0 ? ` \xB7 ${r.file}` : ""}`);
667
+ if (!r.resolved) {
668
+ lines.push(` \u274C ${r.verdict}`);
669
+ continue;
670
+ }
671
+ lines.push(` status ${r.status ?? "\u2014"} date ${r.date ?? "\u2014"}`);
672
+ if (r.decision !== void 0) lines.push(` decision ${r.decision}`);
673
+ lines.push(` review_trigger ${r.review_trigger ?? "\u2014 (none recorded)"}`);
674
+ lines.push(` trigger state ${r.trigger_state}`);
675
+ if (r.supersedes !== void 0 && r.supersedes !== "\u2014") lines.push(` supersedes ${r.supersedes}`);
676
+ if (r.superseded_by !== void 0 && r.superseded_by !== "\u2014")
677
+ lines.push(` superseded_by ${r.superseded_by}`);
678
+ if (r.amends !== void 0 && r.amends !== "\u2014") lines.push(` amends ${r.amends}`);
679
+ if (r.amended_by !== void 0 && r.amended_by !== "\u2014") lines.push(` amended_by ${r.amended_by}`);
680
+ if (r.amendment_blocks.length > 0)
681
+ lines.push(` amendments ${String(r.amendment_blocks.length)}: ${r.amendment_blocks.join(" \xB7 ")}`);
682
+ if (r.referenced_by.length > 0)
683
+ lines.push(` referenced by ${String(r.referenced_by.length)} other ADR(s): ${r.referenced_by.join(", ")}`);
684
+ const provenanceLine = r.provenance_kind ?? "\u2014 (no provenance axis)";
685
+ const mode = r.provenance_agentic_mode !== void 0 ? ` \xB7 mode ${r.provenance_agentic_mode}` : "";
686
+ lines.push(` provenance ${provenanceLine}${mode}`);
687
+ lines.push(
688
+ ` evidence ${r.evidence_strength ?? "\u2014 (ungraded)"} \xB7 discovery ${r.evidence_discovery ?? "\u2014"}`
689
+ );
690
+ lines.push(` authority_basis ${r.authority_basis ?? "\u2014 (absent \u2192 evidence)"}`);
691
+ if (r.evidence_basis === void 0 || r.evidence_basis.length === 0) {
692
+ lines.push(" evidence_basis \u2014 (none recorded)");
693
+ } else {
694
+ const unresolved = new Set(r.evidence_basis_unresolved ?? []);
695
+ lines.push(
696
+ ` evidence_basis ${String(r.evidence_basis.length)} ref(s), ${String(unresolved.size)} unresolved:`
697
+ );
698
+ for (const ref of r.evidence_basis) {
699
+ const kind = basis_ref_kind(ref);
700
+ const marker = kind !== "path" ? `[not checked: ${kind}]` : unresolved.has(ref) ? "[MISSING]" : "[found]";
701
+ lines.push(` ${marker} ${ref}`);
702
+ }
703
+ }
704
+ const policyNote = r.reopen_policy_defaulted === true ? " (absent \u2192 default)" : " (declared)";
705
+ const dims = r.protected_dimensions !== void 0 && r.protected_dimensions.length > 0 ? r.protected_dimensions.join(", ") : "\u2014 (none declared)";
706
+ lines.push(` reopen_policy ${r.reopen_policy ?? "unclassified"}${policyNote}`);
707
+ lines.push(` protected dims ${dims}`);
708
+ lines.push(` \u2192 ${r.verdict}`);
709
+ if (r.authority_effect === "disabled-shadow-mode") lines.push(LOW_EVIDENCE_NOTICE);
710
+ }
711
+ return lines.join("\n");
712
+ }
713
+ function selfTest() {
714
+ const tmp = fs.mkdtempSync(path2.join(os.tmpdir(), "acc-"));
715
+ const mk = (adr, citation) => {
716
+ const root = fs.mkdtempSync(path2.join(tmp, "root-"));
717
+ fs.mkdirSync(path2.join(root, "docs", "decisions"), { recursive: true });
718
+ fs.mkdirSync(path2.join(root, "src", "rules"), { recursive: true });
719
+ if (adr !== null) {
720
+ fs.writeFileSync(
721
+ path2.join(root, "docs", "decisions", `ADR-${adr}-probe.md`),
722
+ `---
723
+ status: accepted
724
+ date: 2026-08-21
725
+ decision: probe
726
+ ---
727
+
728
+ # Probe
729
+ `,
730
+ "utf-8"
731
+ );
732
+ }
733
+ fs.writeFileSync(
734
+ path2.join(root, "src", "rules", "probe.md"),
735
+ citation === null ? "# Probe\n\nNo decision is named here.\n" : `# Probe
736
+
737
+ See ${citation}.
738
+ `,
739
+ "utf-8"
740
+ );
741
+ return root;
742
+ };
743
+ const run = (root) => {
744
+ process.env["ADR_CITE_CHECK_ROOT"] = root;
745
+ try {
746
+ return runGateCli(REPO_ROOT, "src/scripts/adr_cite_check.ts", ["--cited"], REPO_ROOT);
747
+ } finally {
748
+ delete process.env["ADR_CITE_CHECK_ROOT"];
749
+ }
750
+ };
751
+ try {
752
+ return runSelfTest({
753
+ gate: "adr_cite_check",
754
+ minCases: 3,
755
+ minRejectCases: 2,
756
+ cases: [
757
+ {
758
+ name: "a citation whose ADR exists passes",
759
+ expect: "accept",
760
+ run: () => run(mk("001", "ADR-001"))
761
+ },
762
+ {
763
+ name: "a citation to an ADR that does not exist is rejected \u2014 the gate's whole subject",
764
+ expect: "reject",
765
+ run: () => run(mk("001", "ADR-993"))
766
+ },
767
+ {
768
+ name: "citation roots that name no ADR at all are rejected as a dead scope, never green",
769
+ expect: "reject",
770
+ run: () => run(mk("001", null))
771
+ }
772
+ ]
773
+ });
774
+ } finally {
775
+ fs.rmSync(tmp, { recursive: true, force: true });
776
+ }
777
+ }
778
+ function main(argv) {
779
+ const as_json = argv.includes("--json");
780
+ if (argv.includes("--self-test")) return selfTest();
781
+ const corpus_mode = argv.includes("--cited");
782
+ const explicit = argv.filter((a) => !a.startsWith("--"));
783
+ if (!corpus_mode && explicit.length === 0) {
784
+ process.stderr.write(
785
+ `usage: adr_cite_check <ADR-NNN> [ADR-NNN \u2026] [--json]
786
+ adr_cite_check --cited [--json]
787
+ evaluate a decision before citing it as a reason not to act
788
+ --cited: resolve every ADR citation in ${CITATION_ROOTS.join(", ")} (the CI gate)
789
+ `
790
+ );
791
+ return 2;
792
+ }
793
+ const root = scan_root();
794
+ const refs = corpus_mode ? cited_refs(root) : explicit;
795
+ const results = cite_check(refs, root);
796
+ let ledger = null;
797
+ if (corpus_mode) {
798
+ ledger = new GateLedger("adr_cite_check");
799
+ ledger.plan(refs);
800
+ for (const r of results) {
801
+ if (r.resolved) ledger.complete(r.ref);
802
+ else ledger.fail(r.ref, "citation resolves to no decision record");
803
+ }
804
+ }
805
+ if (as_json) {
806
+ process.stdout.write(
807
+ JSON.stringify(
808
+ {
809
+ results,
810
+ surfaces_not_scanned: SURFACES_NOT_SCANNED,
811
+ partial_coverage: PARTIAL_COVERAGE,
812
+ ...ledger !== null ? { ledger: ledger.finalize() } : {}
813
+ },
814
+ null,
815
+ 2
816
+ ) + "\n"
817
+ );
818
+ } else {
819
+ const shown = corpus_mode ? results.filter((r) => !r.resolved) : results;
820
+ if (corpus_mode) {
821
+ process.stdout.write(
822
+ `ADR citations: ${String(results.length)} distinct reference(s) across ${CITATION_ROOTS.join(", ")} \xB7 ${String(results.filter((r) => !r.resolved).length)} unresolved
823
+ `
824
+ );
825
+ }
826
+ if (shown.length > 0) process.stdout.write(render(shown) + "\n");
827
+ if (ledger !== null) {
828
+ try {
829
+ reportScanned({
830
+ gate: "adr_cite_check",
831
+ scanned: results.length,
832
+ units: "cited ADR reference(s)",
833
+ roots: [...CITATION_ROOTS, ...ADR_DIRS]
834
+ });
835
+ } catch (exc) {
836
+ if (exc instanceof DeadScopeError) {
837
+ process.stderr.write(`\u274C ${exc.message}
838
+ `);
839
+ return 1;
840
+ }
841
+ throw exc;
842
+ }
843
+ ledger.report();
844
+ }
845
+ }
846
+ const unresolved = results.filter((r) => !r.resolved);
847
+ if (unresolved.length > 0) {
848
+ process.stderr.write(
849
+ `
850
+ \u274C ${String(unresolved.length)} reference(s) did not resolve on a scanned surface. Not scanned: ${SURFACES_NOT_SCANNED.join(" \xB7 ")}
851
+ `
852
+ );
853
+ return 1;
854
+ }
855
+ return 0;
856
+ }
857
+ if (process.argv[1] !== void 0) {
858
+ try {
859
+ const here = fs.realpathSync(fileURLToPath(import.meta.url));
860
+ const argv1 = fs.realpathSync(path2.resolve(process.argv[1]));
861
+ if (here === argv1) {
862
+ process.exitCode = main(process.argv.slice(2));
863
+ }
864
+ } catch {
865
+ const argvUrl = pathToFileURL(path2.resolve(process.argv[1])).href;
866
+ if (import.meta.url === argvUrl) {
867
+ process.exitCode = main(process.argv.slice(2));
868
+ }
869
+ }
870
+ }
871
+
872
+ // src/scripts/_cli/cmd_adr_effective.ts
873
+ var _HERE2 = path3.dirname(fileURLToPath2(import.meta.url));
874
+ var REPO_ROOT2 = path3.resolve(_HERE2, "..", "..", "..");
875
+ var ASSERTIVE_SECTIONS = ["decision", "consequences", "amendment"];
876
+ var RETIRES_RE = /^>?\s*retires:\s*(.+)$/i;
877
+ var CLAUSE_MARKER_RE = /\*\*\((?:Superseded|Narrowed|Reopened) by ([^—)]+?)\s*(?:—[^)]*)?\)\*\*/;
878
+ function cleanToken(raw) {
879
+ return raw.trim().replace(/^`+|`+$/g, "").trim();
880
+ }
881
+ function sectionOf(headings, line) {
882
+ let current = "(preamble)";
883
+ for (const h of headings) {
884
+ if (h.line > line) break;
885
+ current = h.text;
886
+ }
887
+ return current;
888
+ }
889
+ function isAssertive(section) {
890
+ const first = section.toLowerCase().replace(/^#+\s*/, "").split(/[\s·—-]/)[0] ?? "";
891
+ return ASSERTIVE_SECTIONS.includes(first);
892
+ }
893
+ function splitClauses(lines, headings) {
894
+ const out = [];
895
+ let start = -1;
896
+ let buf = [];
897
+ const flush = () => {
898
+ if (start === -1) return;
899
+ out.push({ line: start, section: sectionOf(headings, start), text: buf.join("\n") });
900
+ start = -1;
901
+ buf = [];
902
+ };
903
+ for (let i = 0; i < lines.length; i += 1) {
904
+ const raw = lines[i];
905
+ const num = i + 1;
906
+ if (raw.trim() === "") {
907
+ flush();
908
+ continue;
909
+ }
910
+ if (/^\s*(?:[-*+]\s|\d+\.\s)/.test(raw)) flush();
911
+ if (start === -1) start = num;
912
+ buf.push(raw);
913
+ }
914
+ flush();
915
+ return out;
916
+ }
917
+ function headingsOf(lines) {
918
+ const out = [];
919
+ for (let i = 0; i < lines.length; i += 1) {
920
+ const raw = lines[i];
921
+ if (/^#{2,3}\s+/.test(raw)) out.push({ line: i + 1, text: raw.replace(/^#+\s*/, "").trim() });
922
+ }
923
+ return out;
924
+ }
925
+ function amendmentsOf(lines) {
926
+ const headings = headingsOf(lines);
927
+ const out = [];
928
+ for (let h = 0; h < headings.length; h += 1) {
929
+ const heading = headings[h];
930
+ if (!/^amend/i.test(heading.text)) continue;
931
+ const next = headings[h + 1];
932
+ const end = next === void 0 ? lines.length : next.line - 1;
933
+ const retires = [];
934
+ for (let i = heading.line; i < end; i += 1) {
935
+ const m = RETIRES_RE.exec(lines[i].trim());
936
+ if (m?.[1] === void 0) continue;
937
+ for (const part of m[1].split(",")) {
938
+ const token = cleanToken(part);
939
+ if (token !== "") retires.push(token);
940
+ }
941
+ }
942
+ out.push({ heading: heading.text, line: heading.line, retires, range: [heading.line, end] });
943
+ }
944
+ return out;
945
+ }
946
+ function decisionSection(lines) {
947
+ const headings = headingsOf(lines);
948
+ for (let h = 0; h < headings.length; h += 1) {
949
+ const heading = headings[h];
950
+ if (!/^decision\b/i.test(heading.text)) continue;
951
+ const next = headings[h + 1];
952
+ const end = next === void 0 ? lines.length : next.line - 1;
953
+ return lines.slice(heading.line, end).filter((l, idx, all) => !(l.trim() === "" && all[idx + 1] === void 0));
954
+ }
955
+ return [];
956
+ }
957
+ function clauseFindings(lines, amendments) {
958
+ const headings = headingsOf(lines);
959
+ const clauses = splitClauses(lines, headings);
960
+ const superseded = [];
961
+ const contradictions = [];
962
+ for (const clause of clauses) {
963
+ const marker = CLAUSE_MARKER_RE.exec(clause.text);
964
+ const excerpt = (clause.text.split("\n")[0] ?? "").trim();
965
+ if (marker?.[1] !== void 0) {
966
+ superseded.push({
967
+ line: clause.line,
968
+ section: clause.section,
969
+ subject: marker[1].trim(),
970
+ excerpt
971
+ });
972
+ continue;
973
+ }
974
+ if (!isAssertive(clause.section)) continue;
975
+ for (const amendment of amendments) {
976
+ const inDeclaringBlock = clause.line >= amendment.range[0] && clause.line <= amendment.range[1];
977
+ if (inDeclaringBlock) continue;
978
+ for (const token of amendment.retires) {
979
+ if (!clause.text.includes(token)) continue;
980
+ contradictions.push({
981
+ line: clause.line,
982
+ section: clause.section,
983
+ subject: token,
984
+ excerpt
985
+ });
986
+ break;
987
+ }
988
+ }
989
+ }
990
+ return { superseded, contradictions };
991
+ }
992
+ function resolveAdrFile(ref, repoRoot) {
993
+ const parsed = normalise_ref(ref);
994
+ if (parsed === null) return null;
995
+ const files = adr_files(repoRoot);
996
+ const { area, num } = parsed;
997
+ const match = area === null ? files.find((f) => new RegExp(`(^|/)ADR-0*${num}[-.]`, "i").test(f)) : files.find((f) => new RegExp(`/docs/adrs/${area}/0*${String(Number(num))}-`, "i").test(f));
998
+ return match ?? null;
999
+ }
1000
+ function effectiveState(ref, repoRoot = REPO_ROOT2) {
1001
+ const parsed = normalise_ref(ref);
1002
+ const id = parsed?.id ?? ref;
1003
+ const match = resolveAdrFile(ref, repoRoot);
1004
+ if (match === null) {
1005
+ return {
1006
+ ref: id,
1007
+ resolved: false,
1008
+ trigger_state: "none",
1009
+ amendments: [],
1010
+ retired_tokens: [],
1011
+ superseded_clauses: [],
1012
+ contradictions: [],
1013
+ effective_decision: []
1014
+ };
1015
+ }
1016
+ const text = fs2.readFileSync(match, "utf-8");
1017
+ const scalars = parse_frontmatter(text) ?? {};
1018
+ const structured = readAdrFrontmatter(text);
1019
+ const provenance = structured === null ? null : provenanceOf(structured);
1020
+ const evidence = structured === null ? null : evidenceOf(structured);
1021
+ const basis = structured === null ? null : authorityBasisOf(structured);
1022
+ const bodyStart = text.indexOf("\n---\n", 4);
1023
+ const frontmatterLines = bodyStart === -1 ? 0 : text.slice(0, bodyStart + 5).split("\n").length - 1;
1024
+ const lines = text.split("\n");
1025
+ const amendments = amendmentsOf(lines).filter((a) => a.line > frontmatterLines);
1026
+ const { superseded, contradictions } = clauseFindings(lines, amendments);
1027
+ const retired = [...new Set(amendments.flatMap((a) => a.retires))];
1028
+ return {
1029
+ ref: id,
1030
+ resolved: true,
1031
+ file: path3.relative(repoRoot, match),
1032
+ ...scalars["status"] !== void 0 ? { status: scalars["status"] } : {},
1033
+ ...scalars["date"] !== void 0 ? { date: scalars["date"] } : {},
1034
+ ...scalars["decision"] !== void 0 ? { decision: scalars["decision"] } : {},
1035
+ ...provenance?.kind != null ? { provenance_kind: provenance.kind } : {},
1036
+ ...evidence?.strength != null ? { evidence_strength: evidence.strength } : {},
1037
+ ...evidence?.discovery != null ? { evidence_discovery: evidence.discovery } : {},
1038
+ ...basis !== null ? { authority_basis: basis } : {},
1039
+ ...scalars["review_trigger"] !== void 0 && scalars["review_trigger"] !== "" ? { review_trigger: scalars["review_trigger"] } : {},
1040
+ trigger_state: trigger_state(scalars),
1041
+ ...scalars["amended_by"] !== void 0 && scalars["amended_by"] !== "\u2014" ? { amended_by: scalars["amended_by"] } : {},
1042
+ amendments,
1043
+ retired_tokens: retired,
1044
+ superseded_clauses: superseded,
1045
+ contradictions,
1046
+ effective_decision: decisionSection(lines)
1047
+ };
1048
+ }
1049
+ function render2(state) {
1050
+ const out = [];
1051
+ out.push(`${state.ref}${state.file !== void 0 ? ` \xB7 ${state.file}` : ""}`);
1052
+ if (!state.resolved) {
1053
+ out.push(" \u274C UNRESOLVED \u2014 no decision record carries this number on a scanned surface.");
1054
+ return out;
1055
+ }
1056
+ out.push(` status ${state.status ?? "\u2014"} date ${state.date ?? "\u2014"}`);
1057
+ if (state.decision !== void 0) out.push(` decision ${state.decision}`);
1058
+ out.push(` provenance ${state.provenance_kind ?? "\u2014 (no provenance axis)"}`);
1059
+ out.push(
1060
+ ` evidence ${state.evidence_strength ?? "\u2014 (ungraded)"} \xB7 discovery ${state.evidence_discovery ?? "\u2014"}`
1061
+ );
1062
+ out.push(` authority_basis ${state.authority_basis ?? "\u2014 (absent \u2192 evidence)"}`);
1063
+ out.push(` review_trigger ${state.review_trigger ?? "\u2014 (none recorded)"}`);
1064
+ out.push(` trigger state ${state.trigger_state}`);
1065
+ if (state.amended_by !== void 0) out.push(` amended_by ${state.amended_by}`);
1066
+ out.push("", " active amendments");
1067
+ if (state.amendments.length === 0) {
1068
+ out.push(" \u2014 none in the body");
1069
+ } else {
1070
+ for (const a of state.amendments) {
1071
+ const declares = a.retires.length > 0 ? ` \xB7 retires ${a.retires.map((t) => `\`${t}\``).join(", ")}` : "";
1072
+ out.push(` :${String(a.line)} ${a.heading}${declares}`);
1073
+ }
1074
+ }
1075
+ out.push("", " superseded clauses");
1076
+ if (state.retired_tokens.length === 0 && state.superseded_clauses.length === 0) {
1077
+ out.push(" \u2014 no amendment declares a `retires:` list and no clause carries a marker,");
1078
+ out.push(" so nothing is claimed about this record's clauses. Read the amendments.");
1079
+ } else if (state.superseded_clauses.length === 0) {
1080
+ out.push(" \u2014 none marked");
1081
+ } else {
1082
+ for (const c of state.superseded_clauses) {
1083
+ out.push(` :${String(c.line)} ${c.section} \u2014 superseded by ${c.subject}`);
1084
+ out.push(` ${c.excerpt}`);
1085
+ }
1086
+ }
1087
+ out.push("", " effective decision");
1088
+ if (state.effective_decision.length === 0) {
1089
+ out.push(" \u2014 no `## Decision` section");
1090
+ } else {
1091
+ for (const line of state.effective_decision) out.push(` ${line}`);
1092
+ }
1093
+ if (state.contradictions.length > 0) {
1094
+ out.push("", " \u274C current-vs-amendment contradiction");
1095
+ for (const c of state.contradictions) {
1096
+ out.push(` :${String(c.line)} ${c.section} asserts \`${c.subject}\`, which an`);
1097
+ out.push(" amendment on this record declares retired, and carries no marker.");
1098
+ out.push(` ${c.excerpt}`);
1099
+ }
1100
+ }
1101
+ return out;
1102
+ }
1103
+ function parseArgv(argv) {
1104
+ let json = false;
1105
+ const refs = [];
1106
+ for (const a of argv) {
1107
+ if (a === "--json") json = true;
1108
+ else if (a === "-h" || a === "--help")
1109
+ return { ok: false, message: "usage: agent-config adr:effective <ADR-NNN> [ADR-NNN \u2026] [--json]" };
1110
+ else if (a.startsWith("--")) return { ok: false, message: `unknown argument: ${a}` };
1111
+ else refs.push(a);
1112
+ }
1113
+ if (refs.length === 0)
1114
+ return { ok: false, message: "usage: agent-config adr:effective <ADR-NNN> [ADR-NNN \u2026] [--json]" };
1115
+ return { ok: true, refs, json };
1116
+ }
1117
+ function main2(argv = process4.argv.slice(2)) {
1118
+ const parsed = parseArgv(argv);
1119
+ if (!parsed.ok) {
1120
+ process4.stderr.write(`${parsed.message ?? "usage error"}
1121
+ `);
1122
+ return 2;
1123
+ }
1124
+ const states = (parsed.refs ?? []).map((ref) => effectiveState(ref));
1125
+ if (parsed.json === true) {
1126
+ process4.stdout.write(`${JSON.stringify(states, null, 2)}
1127
+ `);
1128
+ } else {
1129
+ for (const state of states) {
1130
+ for (const line of render2(state)) process4.stdout.write(`${line}
1131
+ `);
1132
+ process4.stdout.write("\n");
1133
+ }
1134
+ }
1135
+ const bad = states.filter((s) => !s.resolved || s.contradictions.length > 0);
1136
+ return bad.length > 0 ? 1 : 0;
1137
+ }
1138
+ function _isCliEntry() {
1139
+ if (process4.argv[1] === void 0) return false;
1140
+ const argvUrl = pathToFileURL2(path3.resolve(process4.argv[1])).href;
1141
+ if (import.meta.url === argvUrl) return true;
1142
+ try {
1143
+ return fs2.realpathSync(fileURLToPath2(import.meta.url)) === fs2.realpathSync(path3.resolve(process4.argv[1]));
1144
+ } catch {
1145
+ return false;
1146
+ }
1147
+ }
1148
+ if (_isCliEntry()) {
1149
+ process4.exitCode = main2();
1150
+ }
1151
+ export {
1152
+ ASSERTIVE_SECTIONS,
1153
+ amendmentsOf,
1154
+ clauseFindings,
1155
+ decisionSection,
1156
+ effectiveState,
1157
+ main2 as main,
1158
+ parseArgv,
1159
+ render2 as render,
1160
+ resolveAdrFile,
1161
+ splitClauses
1162
+ };