@event4u/agent-config 14.7.0 → 14.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/CHANGELOG.md +175 -390
  3. package/README.md +1 -1
  4. package/agents/templates/.ai-council.yml.example +34 -4
  5. package/dist/agent-src/commands/evals/git-pr-merge.json +13 -0
  6. package/dist/agent-src/commands/evals/roadmap.json +17 -1
  7. package/dist/agent-src/commands/pr/create.md +14 -2
  8. package/dist/agent-src/commands/pr/merge.md +383 -0
  9. package/dist/agent-src/commands/review/changes.md +95 -14
  10. package/dist/agent-src/commands/roadmap/process-full.md +157 -3
  11. package/dist/agent-src/commands/roadmap.md +1 -1
  12. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +46 -36
  13. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +37 -2
  14. package/dist/agent-src/contexts/execution/host-capability-manifest.md +77 -0
  15. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +100 -2
  16. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +154 -2
  17. package/dist/agent-src/contexts/execution/subagent-response-contract.md +80 -0
  18. package/dist/agent-src/rules/artifact-drafting-protocol.md +5 -1
  19. package/dist/agent-src/rules/decision-revisit-gate.md +65 -33
  20. package/dist/agent-src/rules/recurring-criticism.md +1 -1
  21. package/dist/agent-src/rules/roadmap-progress-sync.md +19 -0
  22. package/dist/agent-src/rules/source-confidentiality.md +1 -1
  23. package/dist/agent-src/rules/untrusted-input-defense.md +9 -3
  24. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
  25. package/dist/agent-src/scripts/dashboard_mode.ts +212 -0
  26. package/dist/agent-src/scripts/update_roadmap_progress.ts +47 -48
  27. package/dist/agent-src/skills/adr-create/SKILL.md +124 -5
  28. package/dist/agent-src/skills/code-review/evals/evals.json +87 -46
  29. package/dist/agent-src/skills/decision-record/SKILL.md +61 -3
  30. package/dist/agent-src/skills/decision-review/SKILL.md +65 -0
  31. package/dist/agent-src/skills/humanizer/SKILL.md +39 -0
  32. package/dist/agent-src/skills/humanizer/evals/strip_fixtures.json +57 -0
  33. package/dist/agent-src/skills/humanizer/references/fixtures.md +54 -0
  34. package/dist/agent-src/skills/judge-spec-compliance/SKILL.md +154 -0
  35. package/dist/agent-src/skills/judge-synthesis/SKILL.md +45 -0
  36. package/dist/agent-src/skills/merge-conflicts/SKILL.md +26 -0
  37. package/dist/agent-src/skills/roadmap-writing/SKILL.md +18 -18
  38. package/dist/agent-src/skills/security/SKILL.md +8 -0
  39. package/dist/agent-src/skills/security-audit/SKILL.md +12 -1
  40. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  41. package/dist/agent-src/templates/roadmaps.md +28 -2
  42. package/dist/cli/registry.js +3 -0
  43. package/dist/cli/registry.js.map +1 -1
  44. package/dist/cli-delegate/{chunk-FBKL6JWT.js → chunk-77GR3VD4.js} +68 -43
  45. package/dist/cli-delegate/{chunk-JZ4NR2TW.js → chunk-7PRE6ZV2.js} +1 -1
  46. package/dist/cli-delegate/{chunk-UEZ7D35A.js → chunk-CYH5SP6M.js} +4 -23
  47. package/dist/cli-delegate/{chunk-Q26HT4QT.js → chunk-E46KPN6K.js} +251 -150
  48. package/dist/cli-delegate/{chunk-74E3TKWV.js → chunk-H4VIVD4C.js} +5 -5
  49. package/dist/cli-delegate/chunk-I3EIN2YG.js +36 -0
  50. package/dist/cli-delegate/{chunk-4VR4ORQ6.js → chunk-LPPWO5CE.js} +4 -3
  51. package/dist/cli-delegate/{chunk-7F3VYSM2.js → chunk-PLSG6UL5.js} +14 -14
  52. package/dist/cli-delegate/cmd_adr_effective.js +1162 -0
  53. package/dist/cli-delegate/cmd_conformance.js +101 -25
  54. package/dist/cli-delegate/cmd_converge.js +3 -3
  55. package/dist/cli-delegate/cmd_doctor.js +7 -7
  56. package/dist/cli-delegate/cmd_export.js +6 -6
  57. package/dist/cli-delegate/cmd_handoff.js +7 -6
  58. package/dist/cli-delegate/cmd_memory_get.js +2 -1
  59. package/dist/cli-delegate/cmd_migrate.js +2 -2
  60. package/dist/cli-delegate/cmd_preflight.js +5 -5
  61. package/dist/cli-delegate/cmd_prune.js +6 -6
  62. package/dist/cli-delegate/cmd_route_audit.js +6 -5
  63. package/dist/cli-delegate/cmd_route_explain.js +3 -3
  64. package/dist/cli-delegate/cmd_session_recycle.js +3 -3
  65. package/dist/cli-delegate/cmd_settings_get.js +4 -4
  66. package/dist/cli-delegate/cmd_settings_migrate.js +6 -6
  67. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  68. package/dist/cli-delegate/cmd_sync.js +6 -6
  69. package/dist/cli-delegate/cmd_uninstall.js +6 -6
  70. package/dist/cli-delegate/cmd_update.js +3 -3
  71. package/dist/cli-delegate/cmd_upgrade.js +3 -3
  72. package/dist/cli-delegate/cmd_validate.js +6 -6
  73. package/dist/cli-delegate/cmd_versions.js +3 -3
  74. package/dist/discovery/deprecation-report.md +1 -1
  75. package/dist/discovery/discovery-manifest.json +83 -30
  76. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  77. package/dist/discovery/discovery-manifest.summary.md +4 -4
  78. package/dist/discovery/dormancy-report.md +1 -1
  79. package/dist/discovery/orphan-report.md +1 -1
  80. package/dist/discovery/packs.json +11 -9
  81. package/dist/discovery/trust-report.md +2 -2
  82. package/dist/discovery/workspaces.json +9 -7
  83. package/dist/hooks/dispatch.js +1295 -812
  84. package/dist/install/globalRuleLayers.js +169 -0
  85. package/dist/install/globalRuleLayers.js.map +1 -0
  86. package/dist/install/partitionEligibility.js +156 -0
  87. package/dist/install/partitionEligibility.js.map +1 -1
  88. package/dist/install/ruleLayerPartition.js +157 -0
  89. package/dist/install/ruleLayerPartition.js.map +1 -0
  90. package/dist/mcp/registry-manifest.json +3 -3
  91. package/dist/mcp/server.json +2 -2
  92. package/dist/mcp/server.mjs +1358 -805
  93. package/dist/scripts/_lib/claude_plugin.js +162 -0
  94. package/dist/scripts/_lib/claude_plugin.js.map +1 -0
  95. package/dist/scripts/_lib/environment_detector.js +397 -0
  96. package/dist/scripts/_lib/environment_detector.js.map +1 -0
  97. package/dist/scripts/_lib/prune_empty_dirs.js +85 -0
  98. package/dist/scripts/_lib/prune_empty_dirs.js.map +1 -0
  99. package/dist/scripts/_lib/spawn_env.js +129 -0
  100. package/dist/scripts/_lib/spawn_env.js.map +1 -0
  101. package/dist/scripts/ai_council/modes.js +199 -0
  102. package/dist/scripts/ai_council/modes.js.map +1 -0
  103. package/dist/scripts/ai_council/transport_resolver.js +418 -0
  104. package/dist/scripts/ai_council/transport_resolver.js.map +1 -0
  105. package/dist/scripts/condense.js +52 -89
  106. package/dist/scripts/condense.js.map +1 -1
  107. package/dist/server/routes/wizard.js +3 -2
  108. package/dist/server/routes/wizard.js.map +1 -1
  109. package/dist/ui/assets/index-Cxgy63kz.js +26 -0
  110. package/dist/ui/assets/index-Cxgy63kz.js.map +1 -0
  111. package/dist/ui/index.html +1 -1
  112. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +45 -1
  113. package/docs/guidelines/agent-infra/false-green.md +47 -1
  114. package/docs/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
  115. package/docs/guidelines/agent-infra/untrusted-input-spotlighting.md +17 -0
  116. package/package.json +7 -3
  117. package/src/agent-src/scripts/archive_completed_roadmaps.ts +7 -4
  118. package/src/agent-src/scripts/dashboard_mode.ts +212 -0
  119. package/src/agent-src/scripts/update_roadmap_progress.ts +47 -48
  120. package/src/config/ci-local-parity.yml +21 -0
  121. package/src/config/estate-count-budget.json +13 -471
  122. package/src/config/evaluator-budgets.json +4 -4
  123. package/src/config/gate-coverage.yml +458 -12
  124. package/src/config/gate-violation-baselines.json +18 -8
  125. package/src/config/gitignore-block.txt +15 -2
  126. package/src/config/hook-latency-budget.json +5 -0
  127. package/src/config/publish-surface.json +60 -0
  128. package/src/config/requirements-trace-budget.json +32 -0
  129. package/src/config/rule-stub-ceilings.json +25 -4
  130. package/src/install/globalRuleLayers.ts +197 -0
  131. package/src/install/partitionEligibility.ts +177 -0
  132. package/src/install/ruleLayerPartition.ts +212 -0
  133. package/src/scripts/_cli/cmd_adr_effective.ts +484 -0
  134. package/src/scripts/_dispatch.bash +18 -0
  135. package/src/scripts/_lib/adr_frontmatter.test.ts +365 -0
  136. package/src/scripts/_lib/adr_frontmatter.ts +459 -0
  137. package/src/scripts/_lib/base_tree.ts +175 -0
  138. package/src/scripts/_lib/billing_grant.ts +165 -0
  139. package/src/scripts/_lib/billing_grant_cli.ts +123 -0
  140. package/src/scripts/_lib/council_fallback_posture.ts +53 -6
  141. package/src/scripts/_lib/council_fallback_wiring.ts +61 -1
  142. package/src/scripts/_lib/envelope_grounding.ts +11 -1
  143. package/src/scripts/_lib/host_capability.ts +59 -5
  144. package/src/scripts/_lib/layer_overlap_notice.ts +11 -2
  145. package/src/scripts/_lib/prune_empty_dirs.ts +80 -0
  146. package/src/scripts/_lib/quota_parked.ts +133 -0
  147. package/src/scripts/_lib/retrieval_sanitize.ts +62 -0
  148. package/src/scripts/_lib/review_telemetry.ts +196 -0
  149. package/src/scripts/_lib/subagent_response.ts +92 -0
  150. package/src/scripts/adr/evidence_census.ts +899 -0
  151. package/src/scripts/adr/regenerate_index.ts +25 -38
  152. package/src/scripts/adr_cite_check.ts +540 -39
  153. package/src/scripts/ai_council/clients.ts +12 -1
  154. package/src/scripts/ai_council/config.ts +72 -115
  155. package/src/scripts/ai_council/fallback_config.ts +19 -7
  156. package/src/scripts/ai_council/mid_flight_fallback.ts +88 -7
  157. package/src/scripts/ai_council/orchestrator.ts +2 -2
  158. package/src/scripts/ai_council/py_format.ts +124 -0
  159. package/src/scripts/ai_council/transport_resolver.ts +65 -2
  160. package/src/scripts/audit_adr_coverage.ts +134 -21
  161. package/src/scripts/bench_hook_latency.ts +204 -0
  162. package/src/scripts/build_archive_index.ts +34 -0
  163. package/src/scripts/check_adr_frontmatter.ts +574 -39
  164. package/src/scripts/check_council_pin_staleness.ts +300 -0
  165. package/src/scripts/check_estate_count.ts +420 -293
  166. package/src/scripts/check_gate_coverage.ts +91 -0
  167. package/src/scripts/check_hook_bundle_content.ts +219 -0
  168. package/src/scripts/check_new_adr_evidence.ts +529 -0
  169. package/src/scripts/check_no_stub_inventory_table.ts +292 -0
  170. package/src/scripts/check_pack_size.ts +234 -1
  171. package/src/scripts/check_publish_surface.ts +297 -0
  172. package/src/scripts/check_references.ts +18 -0
  173. package/src/scripts/check_requirements_trace.ts +378 -0
  174. package/src/scripts/check_rule_layer_partition.ts +531 -0
  175. package/src/scripts/check_secret_leak.ts +166 -12
  176. package/src/scripts/check_single_delivery.ts +137 -4
  177. package/src/scripts/check_standing_rule_delivery.ts +8 -2
  178. package/src/scripts/code_graph/cli.ts +44 -5
  179. package/src/scripts/code_graph/extract.ts +87 -0
  180. package/src/scripts/condense.ts +61 -91
  181. package/src/scripts/council_cli.ts +12 -16
  182. package/src/scripts/detect_ai_tells.ts +87 -0
  183. package/src/scripts/dispatch_r2_reviewer.ts +42 -0
  184. package/src/scripts/external_sources_denylist.json +4 -1
  185. package/src/scripts/generate_capabilities_index.ts +41 -0
  186. package/src/scripts/git_authorization_hook.ts +54 -4
  187. package/src/scripts/hooks/block_unauthorized_git.ts +41 -3
  188. package/src/scripts/hooks/dispatch_hook.ts +154 -187
  189. package/src/scripts/hooks/dispatch_issues.ts +38 -19
  190. package/src/scripts/hooks/fallback_yaml.ts +66 -0
  191. package/src/scripts/hooks/host_semantics.ts +21 -0
  192. package/src/scripts/hooks/injection_budget.ts +13 -3
  193. package/src/scripts/hooks/py_json_dumps.ts +103 -0
  194. package/src/scripts/hooks/run_continuation_hook.ts +3 -1
  195. package/src/scripts/hooks/state_io.ts +88 -1
  196. package/src/scripts/hooks/stdin_failure_policy.ts +96 -0
  197. package/src/scripts/hooks/subagent_ledger_hook.ts +76 -11
  198. package/src/scripts/injection_scan_hook.ts +257 -15
  199. package/src/scripts/lint_adr_sweep_routing.ts +365 -0
  200. package/src/scripts/lint_hook_manifest.ts +49 -0
  201. package/src/scripts/lint_provenance_vocabulary.ts +472 -4
  202. package/src/scripts/lint_workflow_security.ts +53 -10
  203. package/src/scripts/lint_workflow_security_allowlist.json +2 -18
  204. package/src/scripts/report_envelope_rate.ts +166 -0
  205. package/src/scripts/review_axis_report.ts +218 -0
  206. package/src/scripts/routing_doctor.ts +5 -1
  207. package/src/scripts/run_supervise.ts +46 -1
  208. package/src/scripts/schemas/evals.schema.json +5 -0
  209. package/src/scripts/sync_pr_branch.ts +170 -20
  210. package/dist/cli/commands/commands.candidates.test.js +0 -257
  211. package/dist/cli/commands/commands.candidates.test.js.map +0 -1
  212. package/dist/cli/commands/profiles.test.js +0 -88
  213. package/dist/cli/commands/profiles.test.js.map +0 -1
  214. package/dist/cli/commands/recordTriggerEval.test.js +0 -113
  215. package/dist/cli/commands/recordTriggerEval.test.js.map +0 -1
  216. package/dist/cli/configRoot.test.js +0 -97
  217. package/dist/cli/configRoot.test.js.map +0 -1
  218. package/dist/cli/mcp/content.test.js +0 -54
  219. package/dist/cli/mcp/content.test.js.map +0 -1
  220. package/dist/cli/mcp/dispatch.test.js +0 -108
  221. package/dist/cli/mcp/dispatch.test.js.map +0 -1
  222. package/dist/server/writeRoot.test.js +0 -91
  223. package/dist/server/writeRoot.test.js.map +0 -1
  224. package/dist/shared/capabilities.test.js +0 -39
  225. package/dist/shared/capabilities.test.js.map +0 -1
  226. package/dist/ui/assets/index-C1pBRm1f.js +0 -26
  227. package/dist/ui/assets/index-C1pBRm1f.js.map +0 -1
  228. package/dist/cli-delegate/{chunk-PPQO4NGR.js → chunk-6RJA2F5A.js} +3 -3
  229. package/dist/cli-delegate/{chunk-6EOCNQAM.js → chunk-E4XIZDIB.js} +0 -0
  230. package/dist/cli-delegate/{chunk-GJ5BVHXM.js → chunk-PMEKGDED.js} +3 -3
@@ -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
+ }
@@ -162,6 +162,11 @@ Tier 1 — power-user (release shape, audit, migration):
162
162
  Usage: council:render <responses.json>
163
163
  council:status Report whether an AI council is configured, and from where
164
164
  (no API call, no spend)
165
+ council:grant-billing Record the human yes that lets an exhausted plan quota
166
+ retry on the metered rung, for ONE run.
167
+ Usage: council:grant-billing <run-id>
168
+ council:revoke-billing End a run-scoped billing grant (exit 0 when none)
169
+ Usage: council:revoke-billing <run-id>
165
170
  council:quota Show today's per-provider CLI-call usage against the
166
171
  enforced cap; --reset <provider> --confirm clears one
167
172
  (no API call, no spend)
@@ -238,6 +243,12 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
238
243
  capabilities:index Regenerate CAPABILITIES.yaml — the package coverage index
239
244
  (capability area → coverage → backing skills/commands → gaps).
240
245
  Pass --check to fail if stale (for CI). Reads src/ (package repo).
246
+ adr:effective Effective state of one decision record: status, the
247
+ Decision section verbatim, clauses its own amendments
248
+ superseded, active amendments, provenance, evidence,
249
+ review trigger and trigger state. Read-only, and it
250
+ authorizes nothing. Exit 1 when the record still
251
+ asserts what its own amendment retired. Flag: --json
241
252
  settings:check Validate .agent-settings.yml against the YAML-subset contract
242
253
  (docs/contracts/settings-sync-yaml-subset.md). Read-only.
243
254
  Exit 0 clean, 1 finding(s), 2 file absent / unreadable.
@@ -754,6 +765,10 @@ cmd_capabilities_index() {
754
765
  exec_ts "$script" "$@"
755
766
  }
756
767
 
768
+ cmd_adr_effective() {
769
+ exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_adr_effective.ts" "$@"
770
+ }
771
+
757
772
  cmd_first_run() {
758
773
  local script
759
774
  script="$(resolve_script "src/scripts/first-run.sh")"
@@ -1451,6 +1466,7 @@ main() {
1451
1466
  roadmap:archive) cmd_roadmap_archive "$@" ;;
1452
1467
  gates) cmd_gates "$@" ;;
1453
1468
  capabilities:index) cmd_capabilities_index "$@" ;;
1469
+ adr:effective) cmd_adr_effective "$@" ;;
1454
1470
  hooks:install) cmd_hooks_install "$@" ;;
1455
1471
  keys:install-anthropic) cmd_keys_install_anthropic "$@" ;;
1456
1472
  keys:install-openai) cmd_keys_install_openai "$@" ;;
@@ -1491,6 +1507,8 @@ main() {
1491
1507
  council:render) cmd_council render "$@" ;;
1492
1508
  council:status) cmd_council status "$@" ;;
1493
1509
  council:quota) cmd_council quota "$@" ;;
1510
+ council:grant-billing) cmd_council grant-billing "$@" ;;
1511
+ council:revoke-billing) cmd_council revoke-billing "$@" ;;
1494
1512
  self-repair:status) cmd_self_repair status "$@" ;;
1495
1513
  self-repair:release) cmd_self_repair release "$@" ;;
1496
1514
  decision:memo) cmd_decision_memo "$@" ;;