@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,484 @@
1
+ /**
2
+ * `agent-config adr:effective ADR-NNN` — the effective state of one decision.
3
+ *
4
+ * Step 2.2 of `road-to-evidence-based-adr-governance`, Phase 2 — surfacing
5
+ * only. Nothing here gates, permits, or denies anything: the verb reports what
6
+ * a record says about itself and exits 1 only when the record contradicts its
7
+ * OWN amendment, which is a defect in the document, never a verdict about
8
+ * whether a transition may proceed.
9
+ *
10
+ * ## The defect it closes
11
+ *
12
+ * A long ADR is read linearly, so a reader meets whichever half comes first.
13
+ * `adr-layout § Amendments` records the measured cost twice over: ADR-035
14
+ * asserted in two places a rejection ADR-232 had reopened, and the contract
15
+ * paragraph naming that failure was itself stale by the time it was read. The
16
+ * live fixture is ADR-020, whose 2026-07-13 amendment retired the committed
17
+ * bridge marker while its Decision still mandates writing one and its
18
+ * Consequences still narrate it as a live failure mode.
19
+ *
20
+ * ## Why a declaration and not an inference
21
+ *
22
+ * "Which clauses did this amendment supersede" is a semantic question, and this
23
+ * roadmap's own architecture forbids answering it by guess — an agent grading
24
+ * its own homework is the failure round 5 refused one layer up. So the record
25
+ * declares it, in two literals that read as prose and parse deterministically:
26
+ *
27
+ * ```markdown
28
+ * ## Amendment — 2026-07-13 · bridge marker retired
29
+ *
30
+ * > retires: `agents/.event4u-bridge.yml`, `bridge marker`
31
+ *
32
+ * ... elsewhere, in the clause the amendment overtook:
33
+ *
34
+ * **(Superseded by the 2026-07-13 amendment — the marker is not written.)**
35
+ * ```
36
+ *
37
+ * A marked clause is reported as superseded. A clause in an ASSERTIVE section
38
+ * naming a retired token with no marker is a contradiction. Absent a `retires:`
39
+ * declaration the verb reports amendments, axes and trigger state and claims
40
+ * nothing about clauses — which is the honest output for the 17 other amended
41
+ * records, not a silent pass.
42
+ *
43
+ * ## Why only the assertive sections
44
+ *
45
+ * `Context`, `Alternatives considered` and `References` describe the situation
46
+ * as it stood when the decision was taken; a retired term appearing there is
47
+ * the history the ADR exists to keep, not a live claim. `adr-layout` frames the
48
+ * defect the same way — "an ADR whose top half still asserts what its own
49
+ * amendment reversed" — so the scan covers `Decision`, `Consequences` and
50
+ * earlier amendment blocks, and says so rather than reporting the whole file
51
+ * and burying the three lines that matter.
52
+ *
53
+ * Exit codes: 0 clean · 1 a contradiction, or the ref resolved nowhere · 2 usage.
54
+ */
55
+
56
+ import * as fs from 'node:fs';
57
+ import * as path from 'node:path';
58
+ import process from 'node:process';
59
+ import { fileURLToPath, pathToFileURL } from 'node:url';
60
+
61
+ import {
62
+ adr_files,
63
+ normalise_ref,
64
+ parse_frontmatter,
65
+ trigger_state,
66
+ type TriggerState,
67
+ } from '../adr_cite_check.js';
68
+ import {
69
+ authorityBasisOf,
70
+ evidenceOf,
71
+ provenanceOf,
72
+ readAdrFrontmatter,
73
+ } from '../_lib/adr_frontmatter.js';
74
+
75
+ const _HERE = path.dirname(fileURLToPath(import.meta.url));
76
+ const REPO_ROOT = path.resolve(_HERE, '..', '..', '..');
77
+
78
+ /**
79
+ * Sections whose clauses are read as live assertions.
80
+ *
81
+ * Matched on the `## ` heading's leading word so the three amendment
82
+ * conventions in the corpus (`## Amendment N (date)`, `## Amendment — date ·
83
+ * topic`, plus the `**Amended <date> —` inline form) all land in the set.
84
+ */
85
+ export const ASSERTIVE_SECTIONS = ['decision', 'consequences', 'amendment'] as const;
86
+
87
+ /** `> retires: \`a\`, \`b\`` — the tokens an amendment block declares retired. */
88
+ const RETIRES_RE = /^>?\s*retires:\s*(.+)$/i;
89
+
90
+ /**
91
+ * A clause-level supersession marker.
92
+ *
93
+ * `Superseded`, `Narrowed` and `Reopened` are all accepted: the corpus already
94
+ * carries the third (`ADR-035:47`, `**(Reopened by ADR-232 — see the banner
95
+ * above.)**`) and inventing a fourth spelling for the same act would leave that
96
+ * marker unread by the one tool written to read markers.
97
+ */
98
+ const CLAUSE_MARKER_RE = /\*\*\((?:Superseded|Narrowed|Reopened) by ([^—)]+?)\s*(?:—[^)]*)?\)\*\*/;
99
+
100
+ export interface AmendmentBlock {
101
+ /** The heading line, verbatim minus the leading `#`s. */
102
+ heading: string;
103
+ /** 1-based line number of the heading. */
104
+ line: number;
105
+ /** Tokens this block declares retired, verbatim (backticks stripped). */
106
+ retires: string[];
107
+ /** 1-based line range the block spans, end inclusive. */
108
+ range: [number, number];
109
+ }
110
+
111
+ export interface ClauseFinding {
112
+ /** 1-based line number of the clause's first line. */
113
+ line: number;
114
+ /** The `## ` heading the clause sits under, or `(frontmatter)`. */
115
+ section: string;
116
+ /** The retired token, or the marker's referent for a marked clause. */
117
+ subject: string;
118
+ /** The clause's first line, trimmed — enough to locate it by eye. */
119
+ excerpt: string;
120
+ }
121
+
122
+ export interface EffectiveState {
123
+ ref: string;
124
+ resolved: boolean;
125
+ file?: string;
126
+ status?: string;
127
+ date?: string;
128
+ decision?: string;
129
+ provenance_kind?: string;
130
+ evidence_strength?: string;
131
+ evidence_discovery?: string;
132
+ authority_basis?: string;
133
+ review_trigger?: string;
134
+ trigger_state: TriggerState;
135
+ amended_by?: string;
136
+ amendments: AmendmentBlock[];
137
+ /** Retired tokens declared across every amendment block, deduplicated. */
138
+ retired_tokens: string[];
139
+ /** Clauses carrying a supersession marker. */
140
+ superseded_clauses: ClauseFinding[];
141
+ /** Assertive clauses naming a retired token with no marker. */
142
+ contradictions: ClauseFinding[];
143
+ /** The Decision section, verbatim, as the effective text to read. */
144
+ effective_decision: string[];
145
+ }
146
+
147
+ interface Clause {
148
+ line: number;
149
+ section: string;
150
+ text: string;
151
+ }
152
+
153
+ /** Strip backticks and surrounding whitespace off one declared token. */
154
+ function cleanToken(raw: string): string {
155
+ return raw.trim().replace(/^`+|`+$/g, '').trim();
156
+ }
157
+
158
+ /** The heading a line sits under, `(preamble)` before the first one. */
159
+ function sectionOf(headings: { line: number; text: string }[], line: number): string {
160
+ let current = '(preamble)';
161
+ for (const h of headings) {
162
+ if (h.line > line) break;
163
+ current = h.text;
164
+ }
165
+ return current;
166
+ }
167
+
168
+ function isAssertive(section: string): boolean {
169
+ const first = section.toLowerCase().replace(/^#+\s*/, '').split(/[\s·—-]/)[0] ?? '';
170
+ return (ASSERTIVE_SECTIONS as readonly string[]).includes(first);
171
+ }
172
+
173
+ /**
174
+ * Split a document body into clauses.
175
+ *
176
+ * A clause ends at a blank line OR at the start of the next list item, and the
177
+ * second half is load-bearing: a run of bullets with no blank line between them
178
+ * is one contiguous block, so paragraph-only splitting would let a marker on
179
+ * one bullet clear the bullet below it.
180
+ */
181
+ export function splitClauses(lines: string[], headings: { line: number; text: string }[]): Clause[] {
182
+ const out: Clause[] = [];
183
+ let start = -1;
184
+ let buf: string[] = [];
185
+ const flush = (): void => {
186
+ if (start === -1) return;
187
+ out.push({ line: start, section: sectionOf(headings, start), text: buf.join('\n') });
188
+ start = -1;
189
+ buf = [];
190
+ };
191
+ for (let i = 0; i < lines.length; i += 1) {
192
+ const raw = lines[i] as string;
193
+ const num = i + 1;
194
+ if (raw.trim() === '') {
195
+ flush();
196
+ continue;
197
+ }
198
+ if (/^\s*(?:[-*+]\s|\d+\.\s)/.test(raw)) flush();
199
+ if (start === -1) start = num;
200
+ buf.push(raw);
201
+ }
202
+ flush();
203
+ return out;
204
+ }
205
+
206
+ /** Every `## `/`### ` heading, plus the inline `**Amended <date>` convention. */
207
+ function headingsOf(lines: string[]): { line: number; text: string }[] {
208
+ const out: { line: number; text: string }[] = [];
209
+ for (let i = 0; i < lines.length; i += 1) {
210
+ const raw = lines[i] as string;
211
+ if (/^#{2,3}\s+/.test(raw)) out.push({ line: i + 1, text: raw.replace(/^#+\s*/, '').trim() });
212
+ }
213
+ return out;
214
+ }
215
+
216
+ /** Amendment blocks with their extents and declared retirements. */
217
+ export function amendmentsOf(lines: string[]): AmendmentBlock[] {
218
+ const headings = headingsOf(lines);
219
+ const out: AmendmentBlock[] = [];
220
+ for (let h = 0; h < headings.length; h += 1) {
221
+ const heading = headings[h] as { line: number; text: string };
222
+ if (!/^amend/i.test(heading.text)) continue;
223
+ const next = headings[h + 1];
224
+ const end = next === undefined ? lines.length : next.line - 1;
225
+ const retires: string[] = [];
226
+ for (let i = heading.line; i < end; i += 1) {
227
+ const m = RETIRES_RE.exec((lines[i] as string).trim());
228
+ if (m?.[1] === undefined) continue;
229
+ for (const part of m[1].split(',')) {
230
+ const token = cleanToken(part);
231
+ if (token !== '') retires.push(token);
232
+ }
233
+ }
234
+ out.push({ heading: heading.text, line: heading.line, retires, range: [heading.line, end] });
235
+ }
236
+ return out;
237
+ }
238
+
239
+ /** The verbatim `## Decision` section, or `[]` when the record has none. */
240
+ export function decisionSection(lines: string[]): string[] {
241
+ const headings = headingsOf(lines);
242
+ for (let h = 0; h < headings.length; h += 1) {
243
+ const heading = headings[h] as { line: number; text: string };
244
+ if (!/^decision\b/i.test(heading.text)) continue;
245
+ const next = headings[h + 1];
246
+ const end = next === undefined ? lines.length : next.line - 1;
247
+ return lines.slice(heading.line, end).filter((l, idx, all) => !(l.trim() === '' && all[idx + 1] === undefined));
248
+ }
249
+ return [];
250
+ }
251
+
252
+ /**
253
+ * Superseded clauses and contradictions, from the declarations alone.
254
+ *
255
+ * A clause inside the amendment block that declares the retirement is skipped:
256
+ * naming the thing it retires is what an amendment does.
257
+ */
258
+ export function clauseFindings(
259
+ lines: string[],
260
+ amendments: AmendmentBlock[],
261
+ ): { superseded: ClauseFinding[]; contradictions: ClauseFinding[] } {
262
+ const headings = headingsOf(lines);
263
+ const clauses = splitClauses(lines, headings);
264
+ const superseded: ClauseFinding[] = [];
265
+ const contradictions: ClauseFinding[] = [];
266
+
267
+ for (const clause of clauses) {
268
+ const marker = CLAUSE_MARKER_RE.exec(clause.text);
269
+ const excerpt = (clause.text.split('\n')[0] ?? '').trim();
270
+ if (marker?.[1] !== undefined) {
271
+ superseded.push({
272
+ line: clause.line,
273
+ section: clause.section,
274
+ subject: marker[1].trim(),
275
+ excerpt,
276
+ });
277
+ continue;
278
+ }
279
+ if (!isAssertive(clause.section)) continue;
280
+ for (const amendment of amendments) {
281
+ const inDeclaringBlock =
282
+ clause.line >= amendment.range[0] && clause.line <= amendment.range[1];
283
+ if (inDeclaringBlock) continue;
284
+ for (const token of amendment.retires) {
285
+ if (!clause.text.includes(token)) continue;
286
+ contradictions.push({
287
+ line: clause.line,
288
+ section: clause.section,
289
+ subject: token,
290
+ excerpt,
291
+ });
292
+ break;
293
+ }
294
+ }
295
+ }
296
+ return { superseded, contradictions };
297
+ }
298
+
299
+ /** Resolve one reference to a decision file on a scanned surface. */
300
+ export function resolveAdrFile(ref: string, repoRoot: string): string | null {
301
+ const parsed = normalise_ref(ref);
302
+ if (parsed === null) return null;
303
+ const files = adr_files(repoRoot);
304
+ const { area, num } = parsed;
305
+ const match =
306
+ area === null
307
+ ? files.find((f) => new RegExp(`(^|/)ADR-0*${num}[-.]`, 'i').test(f))
308
+ : files.find((f) => new RegExp(`/docs/adrs/${area}/0*${String(Number(num))}-`, 'i').test(f));
309
+ return match ?? null;
310
+ }
311
+
312
+ export function effectiveState(ref: string, repoRoot: string = REPO_ROOT): EffectiveState {
313
+ const parsed = normalise_ref(ref);
314
+ const id = parsed?.id ?? ref;
315
+ const match = resolveAdrFile(ref, repoRoot);
316
+ if (match === null) {
317
+ return {
318
+ ref: id,
319
+ resolved: false,
320
+ trigger_state: 'none',
321
+ amendments: [],
322
+ retired_tokens: [],
323
+ superseded_clauses: [],
324
+ contradictions: [],
325
+ effective_decision: [],
326
+ };
327
+ }
328
+
329
+ const text = fs.readFileSync(match, 'utf-8');
330
+ const scalars = parse_frontmatter(text) ?? {};
331
+ const structured = readAdrFrontmatter(text);
332
+ const provenance = structured === null ? null : provenanceOf(structured);
333
+ const evidence = structured === null ? null : evidenceOf(structured);
334
+ const basis = structured === null ? null : authorityBasisOf(structured);
335
+
336
+ const bodyStart = text.indexOf('\n---\n', 4);
337
+ const frontmatterLines = bodyStart === -1 ? 0 : text.slice(0, bodyStart + 5).split('\n').length - 1;
338
+ const lines = text.split('\n');
339
+ const amendments = amendmentsOf(lines).filter((a) => a.line > frontmatterLines);
340
+ const { superseded, contradictions } = clauseFindings(lines, amendments);
341
+ const retired = [...new Set(amendments.flatMap((a) => a.retires))];
342
+
343
+ return {
344
+ ref: id,
345
+ resolved: true,
346
+ file: path.relative(repoRoot, match),
347
+ ...(scalars['status'] !== undefined ? { status: scalars['status'] } : {}),
348
+ ...(scalars['date'] !== undefined ? { date: scalars['date'] } : {}),
349
+ ...(scalars['decision'] !== undefined ? { decision: scalars['decision'] } : {}),
350
+ ...(provenance?.kind != null ? { provenance_kind: provenance.kind } : {}),
351
+ ...(evidence?.strength != null ? { evidence_strength: evidence.strength } : {}),
352
+ ...(evidence?.discovery != null ? { evidence_discovery: evidence.discovery } : {}),
353
+ ...(basis !== null ? { authority_basis: basis } : {}),
354
+ ...(scalars['review_trigger'] !== undefined && scalars['review_trigger'] !== ''
355
+ ? { review_trigger: scalars['review_trigger'] }
356
+ : {}),
357
+ trigger_state: trigger_state(scalars),
358
+ ...(scalars['amended_by'] !== undefined && scalars['amended_by'] !== '—'
359
+ ? { amended_by: scalars['amended_by'] }
360
+ : {}),
361
+ amendments,
362
+ retired_tokens: retired,
363
+ superseded_clauses: superseded,
364
+ contradictions,
365
+ effective_decision: decisionSection(lines),
366
+ };
367
+ }
368
+
369
+ export function render(state: EffectiveState): string[] {
370
+ const out: string[] = [];
371
+ out.push(`${state.ref}${state.file !== undefined ? ` · ${state.file}` : ''}`);
372
+ if (!state.resolved) {
373
+ out.push(' ❌ UNRESOLVED — no decision record carries this number on a scanned surface.');
374
+ return out;
375
+ }
376
+ out.push(` status ${state.status ?? '—'} date ${state.date ?? '—'}`);
377
+ if (state.decision !== undefined) out.push(` decision ${state.decision}`);
378
+ out.push(` provenance ${state.provenance_kind ?? '— (no provenance axis)'}`);
379
+ out.push(
380
+ ` evidence ${state.evidence_strength ?? '— (ungraded)'}` +
381
+ ` · discovery ${state.evidence_discovery ?? '—'}`,
382
+ );
383
+ out.push(` authority_basis ${state.authority_basis ?? '— (absent → evidence)'}`);
384
+ out.push(` review_trigger ${state.review_trigger ?? '— (none recorded)'}`);
385
+ out.push(` trigger state ${state.trigger_state}`);
386
+ if (state.amended_by !== undefined) out.push(` amended_by ${state.amended_by}`);
387
+
388
+ out.push('', ' active amendments');
389
+ if (state.amendments.length === 0) {
390
+ out.push(' — none in the body');
391
+ } else {
392
+ for (const a of state.amendments) {
393
+ const declares =
394
+ a.retires.length > 0 ? ` · retires ${a.retires.map((t) => `\`${t}\``).join(', ')}` : '';
395
+ out.push(` :${String(a.line)} ${a.heading}${declares}`);
396
+ }
397
+ }
398
+
399
+ out.push('', ' superseded clauses');
400
+ if (state.retired_tokens.length === 0 && state.superseded_clauses.length === 0) {
401
+ out.push(' — no amendment declares a `retires:` list and no clause carries a marker,');
402
+ out.push(' so nothing is claimed about this record\'s clauses. Read the amendments.');
403
+ } else if (state.superseded_clauses.length === 0) {
404
+ out.push(' — none marked');
405
+ } else {
406
+ for (const c of state.superseded_clauses) {
407
+ out.push(` :${String(c.line)} ${c.section} — superseded by ${c.subject}`);
408
+ out.push(` ${c.excerpt}`);
409
+ }
410
+ }
411
+
412
+ out.push('', ' effective decision');
413
+ if (state.effective_decision.length === 0) {
414
+ out.push(' — no `## Decision` section');
415
+ } else {
416
+ for (const line of state.effective_decision) out.push(` ${line}`);
417
+ }
418
+
419
+ if (state.contradictions.length > 0) {
420
+ out.push('', ' ❌ current-vs-amendment contradiction');
421
+ for (const c of state.contradictions) {
422
+ out.push(` :${String(c.line)} ${c.section} asserts \`${c.subject}\`, which an`);
423
+ out.push(' amendment on this record declares retired, and carries no marker.');
424
+ out.push(` ${c.excerpt}`);
425
+ }
426
+ }
427
+ return out;
428
+ }
429
+
430
+ interface ParsedArgv {
431
+ ok: boolean;
432
+ message?: string;
433
+ refs?: string[];
434
+ json?: boolean;
435
+ }
436
+
437
+ export function parseArgv(argv: readonly string[]): ParsedArgv {
438
+ let json = false;
439
+ const refs: string[] = [];
440
+ for (const a of argv) {
441
+ if (a === '--json') json = true;
442
+ else if (a === '-h' || a === '--help')
443
+ return { ok: false, message: 'usage: agent-config adr:effective <ADR-NNN> [ADR-NNN …] [--json]' };
444
+ else if (a.startsWith('--')) return { ok: false, message: `unknown argument: ${a}` };
445
+ else refs.push(a);
446
+ }
447
+ if (refs.length === 0)
448
+ return { ok: false, message: 'usage: agent-config adr:effective <ADR-NNN> [ADR-NNN …] [--json]' };
449
+ return { ok: true, refs, json };
450
+ }
451
+
452
+ export function main(argv: readonly string[] = process.argv.slice(2)): number {
453
+ const parsed = parseArgv(argv);
454
+ if (!parsed.ok) {
455
+ process.stderr.write(`${parsed.message ?? 'usage error'}\n`);
456
+ return 2;
457
+ }
458
+ const states = (parsed.refs ?? []).map((ref) => effectiveState(ref));
459
+ if (parsed.json === true) {
460
+ process.stdout.write(`${JSON.stringify(states, null, 2)}\n`);
461
+ } else {
462
+ for (const state of states) {
463
+ for (const line of render(state)) process.stdout.write(`${line}\n`);
464
+ process.stdout.write('\n');
465
+ }
466
+ }
467
+ const bad = states.filter((s) => !s.resolved || s.contradictions.length > 0);
468
+ return bad.length > 0 ? 1 : 0;
469
+ }
470
+
471
+ function _isCliEntry(): boolean {
472
+ if (process.argv[1] === undefined) return false;
473
+ const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
474
+ if (import.meta.url === argvUrl) return true;
475
+ try {
476
+ return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(path.resolve(process.argv[1]));
477
+ } catch {
478
+ return false;
479
+ }
480
+ }
481
+
482
+ if (_isCliEntry()) {
483
+ process.exitCode = main();
484
+ }
@@ -238,6 +238,12 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
238
238
  capabilities:index Regenerate CAPABILITIES.yaml — the package coverage index
239
239
  (capability area → coverage → backing skills/commands → gaps).
240
240
  Pass --check to fail if stale (for CI). Reads src/ (package repo).
241
+ adr:effective Effective state of one decision record: status, the
242
+ Decision section verbatim, clauses its own amendments
243
+ superseded, active amendments, provenance, evidence,
244
+ review trigger and trigger state. Read-only, and it
245
+ authorizes nothing. Exit 1 when the record still
246
+ asserts what its own amendment retired. Flag: --json
241
247
  settings:check Validate .agent-settings.yml against the YAML-subset contract
242
248
  (docs/contracts/settings-sync-yaml-subset.md). Read-only.
243
249
  Exit 0 clean, 1 finding(s), 2 file absent / unreadable.
@@ -754,6 +760,10 @@ cmd_capabilities_index() {
754
760
  exec_ts "$script" "$@"
755
761
  }
756
762
 
763
+ cmd_adr_effective() {
764
+ exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_adr_effective.ts" "$@"
765
+ }
766
+
757
767
  cmd_first_run() {
758
768
  local script
759
769
  script="$(resolve_script "src/scripts/first-run.sh")"
@@ -1451,6 +1461,7 @@ main() {
1451
1461
  roadmap:archive) cmd_roadmap_archive "$@" ;;
1452
1462
  gates) cmd_gates "$@" ;;
1453
1463
  capabilities:index) cmd_capabilities_index "$@" ;;
1464
+ adr:effective) cmd_adr_effective "$@" ;;
1454
1465
  hooks:install) cmd_hooks_install "$@" ;;
1455
1466
  keys:install-anthropic) cmd_keys_install_anthropic "$@" ;;
1456
1467
  keys:install-openai) cmd_keys_install_openai "$@" ;;