@event4u/agent-config 16.0.0 → 16.1.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 (246) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +297 -137
  5. package/CONTRIBUTING.md +9 -0
  6. package/README.md +2 -2
  7. package/dist/agent-src/commands/analyze/inbox.md +26 -0
  8. package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
  9. package/dist/agent-src/commands/challenge-me/closure.md +178 -0
  10. package/dist/agent-src/commands/challenge-me.md +9 -2
  11. package/dist/agent-src/commands/council/default.md +25 -5
  12. package/dist/agent-src/commands/feature/plan.md +28 -1
  13. package/dist/agent-src/commands/feature/roadmap.md +28 -1
  14. package/dist/agent-src/commands/implement-ticket.md +27 -0
  15. package/dist/agent-src/commands/jira-ticket.md +27 -0
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/pr/merge.md +60 -9
  18. package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
  19. package/dist/agent-src/commands/roadmap/create.md +74 -27
  20. package/dist/agent-src/commands/roadmap/materialize.md +27 -0
  21. package/dist/agent-src/commands/roadmap/process-full.md +123 -18
  22. package/dist/agent-src/commands/team.md +22 -0
  23. package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
  24. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
  25. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
  26. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
  27. package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
  28. package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
  29. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
  30. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
  31. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  32. package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  33. package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
  34. package/dist/agent-src/rules/autonomous-execution.md +15 -8
  35. package/dist/agent-src/rules/delegation-policy.md +23 -45
  36. package/dist/agent-src/rules/design-fidelity.md +32 -37
  37. package/dist/agent-src/rules/evaluator-independence.md +43 -49
  38. package/dist/agent-src/rules/test-first.md +41 -0
  39. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  40. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  41. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  42. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  43. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  44. package/dist/agent-src/skills/design-review/SKILL.md +50 -0
  45. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
  46. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  47. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  48. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  49. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  50. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  51. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  52. package/dist/agent-src/templates/agent-settings.md +1 -1
  53. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  54. package/dist/agent-src/templates/roadmaps.md +188 -0
  55. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  56. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  57. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  58. package/dist/cli-delegate/check_update_banner.js +40 -2
  59. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  60. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  61. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  62. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  63. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  64. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
  65. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
  66. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
  67. package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
  68. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  69. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  70. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
  71. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  72. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  73. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  74. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  75. package/dist/cli-delegate/cmd_conformance.js +90 -56
  76. package/dist/cli-delegate/cmd_doctor.js +7 -5
  77. package/dist/cli-delegate/cmd_explain.js +2 -2
  78. package/dist/cli-delegate/cmd_export.js +9 -7
  79. package/dist/cli-delegate/cmd_fleet.js +4 -2
  80. package/dist/cli-delegate/cmd_handoff.js +4 -4
  81. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  82. package/dist/cli-delegate/cmd_migrate.js +2 -2
  83. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  84. package/dist/cli-delegate/cmd_preflight.js +4 -2
  85. package/dist/cli-delegate/cmd_prune.js +10 -8
  86. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  87. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  88. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  89. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  90. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  91. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  92. package/dist/cli-delegate/cmd_sync.js +10 -8
  93. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  94. package/dist/cli-delegate/cmd_update.js +1 -1
  95. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  96. package/dist/cli-delegate/cmd_validate.js +12 -10
  97. package/dist/cli-delegate/cmd_versions.js +1 -1
  98. package/dist/discovery/deprecation-report.md +1 -1
  99. package/dist/discovery/discovery-manifest.json +85 -38
  100. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  101. package/dist/discovery/discovery-manifest.summary.md +4 -4
  102. package/dist/discovery/dormancy-report.md +1 -1
  103. package/dist/discovery/orphan-report.md +1 -1
  104. package/dist/discovery/packs.json +10 -8
  105. package/dist/discovery/trust-report.md +3 -3
  106. package/dist/discovery/workspaces.json +8 -6
  107. package/dist/hooks/dispatch.js +1832 -1007
  108. package/dist/install/claudeRuleRewrite.js +14 -3
  109. package/dist/install/conflict.js +60 -10
  110. package/dist/install/conflictTracker.js +218 -0
  111. package/dist/install/fsPrimitives.js +54 -0
  112. package/dist/install/install.mjs +2007 -1481
  113. package/dist/install/manifestFiles.js +259 -0
  114. package/dist/install/preflight.js +14 -2
  115. package/dist/install/preserve.js +157 -0
  116. package/dist/install/recordedOwnership.js +132 -0
  117. package/dist/install/txlog.js +11 -2
  118. package/dist/mcp/registry-manifest.json +3 -3
  119. package/dist/mcp/server.json +2 -2
  120. package/dist/mcp/server.mjs +398 -39
  121. package/dist/router.json +1 -1
  122. package/dist/scripts/_lib/agent_settings.js +3 -2
  123. package/dist/scripts/_lib/fs_atomic.js +167 -0
  124. package/dist/scripts/_lib/installed_tools.js +460 -0
  125. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  126. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  127. package/dist/server/routes/install.js +8 -0
  128. package/dist/server/routes/wizard.js +9 -3
  129. package/dist/server/schemas/settings.js +17 -5
  130. package/dist/shared/missionExecution.js +97 -0
  131. package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
  132. package/dist/ui/index.html +1 -1
  133. package/docs/MIGRATION.md +103 -0
  134. package/docs/contracts/settings-classes.md +26 -7
  135. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  136. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  137. package/docs/guidelines/design-fidelity-routing.md +29 -0
  138. package/package.json +1 -1
  139. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  140. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  141. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  142. package/src/config/agent-settings.template.yml +139 -27
  143. package/src/config/auto-rule-budget.json +16 -0
  144. package/src/config/ci-local-parity.yml +18 -5
  145. package/src/config/continuity-surface.json +21 -0
  146. package/src/config/gate-coverage.yml +146 -13
  147. package/src/config/gate-violation-baselines.json +6 -6
  148. package/src/config/hook-token-budget.json +2 -0
  149. package/src/config/loop-surfaces.yaml +215 -0
  150. package/src/config/platform-anchor.json +3 -2
  151. package/src/config/release-gate-locality.yml +11 -0
  152. package/src/config/routing-coverage-seed.json +18 -4
  153. package/src/config/rule-enforcement-baseline.json +0 -1
  154. package/src/install/claudeRuleRewrite.ts +16 -2
  155. package/src/install/conflict.ts +65 -9
  156. package/src/install/conflictTracker.ts +299 -0
  157. package/src/install/fsPrimitives.ts +54 -0
  158. package/src/install/manifestFiles.ts +312 -0
  159. package/src/install/preflight.ts +16 -2
  160. package/src/install/preserve.ts +205 -0
  161. package/src/install/recordedOwnership.ts +149 -0
  162. package/src/install/txlog.ts +11 -2
  163. package/src/install/types.ts +19 -4
  164. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  165. package/src/scripts/_cli/cmd_doctor.ts +4 -3
  166. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  167. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  168. package/src/scripts/_cli/doctor_execution.ts +90 -0
  169. package/src/scripts/_lib/activation_payload.ts +247 -0
  170. package/src/scripts/_lib/agent_settings.ts +4 -2
  171. package/src/scripts/_lib/authority_path.ts +102 -0
  172. package/src/scripts/_lib/cascade_base.ts +94 -0
  173. package/src/scripts/_lib/changelog_eras.ts +7 -2
  174. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  175. package/src/scripts/_lib/council_transport.ts +129 -0
  176. package/src/scripts/_lib/delivery_ready.ts +112 -0
  177. package/src/scripts/_lib/forge_protection.ts +267 -0
  178. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  179. package/src/scripts/_lib/json_python_parity.ts +127 -0
  180. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  181. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  182. package/src/scripts/_lib/mission_record.ts +190 -0
  183. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  184. package/src/scripts/_lib/obligations.ts +402 -0
  185. package/src/scripts/_lib/orchestration_record.ts +11 -20
  186. package/src/scripts/_lib/release_highlights.ts +199 -10
  187. package/src/scripts/_lib/release_holds.ts +493 -0
  188. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  189. package/src/scripts/_lib/rides_along.ts +102 -0
  190. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  191. package/src/scripts/_lib/test_delta.ts +166 -0
  192. package/src/scripts/_lib/test_provenance.ts +171 -0
  193. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  194. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  195. package/src/scripts/ai_council/config.ts +8 -11
  196. package/src/scripts/ai_council/fallback_config.ts +5 -3
  197. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  198. package/src/scripts/ask_block_census.ts +285 -15
  199. package/src/scripts/check_enforcement_matrix.ts +624 -0
  200. package/src/scripts/check_gate_reachability.ts +173 -10
  201. package/src/scripts/check_generator_sync.ts +954 -0
  202. package/src/scripts/check_preamble_payload_budget.ts +75 -2
  203. package/src/scripts/check_release_highlights.ts +33 -1
  204. package/src/scripts/check_release_holds.ts +567 -0
  205. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  206. package/src/scripts/check_test_delta.ts +294 -0
  207. package/src/scripts/check_test_weakening.ts +299 -0
  208. package/src/scripts/closure_scan.ts +333 -0
  209. package/src/scripts/compile_hook_manifest.ts +43 -3
  210. package/src/scripts/council_record_shape.ts +209 -0
  211. package/src/scripts/hook_manifest.json +1 -1
  212. package/src/scripts/hook_manifest.yaml +88 -9
  213. package/src/scripts/hooks/concern_registry.ts +4 -0
  214. package/src/scripts/hooks/design_pass_hook.ts +128 -3
  215. package/src/scripts/hooks/evidence_independence.ts +96 -0
  216. package/src/scripts/hooks/memory_recall_hook.ts +540 -0
  217. package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
  218. package/src/scripts/hooks/rule_inject_hook.ts +49 -1
  219. package/src/scripts/hooks/run_continuation_hook.ts +17 -20
  220. package/src/scripts/hooks_doctor.ts +85 -0
  221. package/src/scripts/hooks_status.ts +19 -0
  222. package/src/scripts/install.ts +106 -206
  223. package/src/scripts/janitor.ts +7 -0
  224. package/src/scripts/lint_decision_classes.ts +464 -0
  225. package/src/scripts/lint_hook_manifest.ts +11 -0
  226. package/src/scripts/lint_major_migration_sections.ts +450 -0
  227. package/src/scripts/lint_plan_risk_register.ts +47 -3
  228. package/src/scripts/lint_roadmap_blockers.ts +61 -0
  229. package/src/scripts/lint_roadmap_producers.ts +202 -0
  230. package/src/scripts/measure_rule_budget.ts +101 -5
  231. package/src/scripts/new_roadmap.ts +37 -0
  232. package/src/scripts/orchestration_record.ts +0 -5
  233. package/src/scripts/release.ts +14 -98
  234. package/src/scripts/release_drill.ts +267 -1
  235. package/src/scripts/release_publication.ts +51 -0
  236. package/src/scripts/report_enforcement_drift.ts +566 -0
  237. package/src/scripts/report_host_injection_effect.ts +3 -11
  238. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  239. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  240. package/src/scripts/schemas/command.schema.json +4 -0
  241. package/src/scripts/scope_growth.ts +219 -0
  242. package/src/scripts/trigger_eval_grandfather.json +0 -1
  243. package/src/scripts/ui_conformance_probe.ts +622 -0
  244. package/src/server/schemas/settings.ts +31 -7
  245. package/src/shared/missionExecution.ts +126 -0
  246. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * obligation-settle — the reader the delivered-obligation record never had.
4
+ *
5
+ * The injector has always known which rules it delivered into a session. Until
6
+ * this concern, nothing asked whether the turn that received them discharged
7
+ * any. This is that question, asked at turn-end, and — for now — asked out
8
+ * loud and answered with nothing.
9
+ *
10
+ * SHADOW ONLY. IT REFUSES NOTHING, AND THAT IS NOT A PLACEHOLDER.
11
+ * It computes the verdict a future armed detector would reach, records it, and
12
+ * returns allow on every path. Arming is gated on a bar pre-registered in
13
+ * `docs/CLAIMS.md` BEFORE any code able to refuse exists, so that the bar
14
+ * cannot be chosen to fit the numbers the shadow window happened to produce.
15
+ * The estate already has one blocking turn-end concern and the first-ranked
16
+ * risk against this work is that a second one refuses a clean turn and an
17
+ * operator disables the carrier for good.
18
+ *
19
+ * THE TOUCHED SET COMES FROM THE DIFF, NOT FROM TOOL EVENTS.
20
+ * A tool event carries the path the tool was given. A shell heredoc writing
21
+ * three files reports one Bash call and no paths at all, so a detector reading
22
+ * tool events is blind to exactly the turns that wrote the most. `git diff
23
+ * --numstat HEAD` plus `git ls-files --others` sees the tree as it actually
24
+ * is, which is the only description that cannot be evaded by how the write was
25
+ * spelled. The helpers are the end-review concern's own, imported rather than
26
+ * re-derived.
27
+ *
28
+ * IT REFUSES ONLY ON CLASSES THAT COULD HAVE A DISCHARGE.
29
+ * `hook`, `validator` and `test` name carriers that run and leave traces.
30
+ * `observer`, `instruction-only` and `none` do not, so demanding a discharge
31
+ * for one would demand evidence nobody can produce — and 69 of 97 delivered
32
+ * rules on the frozen corpus are class `none`.
33
+ *
34
+ * IT IS SILENT WHILE A SUBAGENT DISPATCH IS OPEN.
35
+ * Three of the turn-end gate's six detectors are gated this way and three are
36
+ * not; the difference is whether the finding could be about work a dispatch is
37
+ * still doing. This one could — a subagent writing UI files is the canonical
38
+ * case — so it takes the gated side.
39
+ */
40
+
41
+ import path from 'node:path';
42
+ import process from 'node:process';
43
+
44
+ import { may_refuse_on } from '../_lib/obligation_frequency.js';
45
+ import { appendShadow, readDelivered, readDischarged, stamp } from '../_lib/obligations.js';
46
+ import { loadRouter, matchTierRules } from '../_lib/rule_injection.js';
47
+ import { gitNumstatRows, isDocPath, untrackedNonDocFiles } from './end_review_nudge_hook.js';
48
+ import { openRecordStats } from './subagent_ledger_hook.js';
49
+
50
+ const EXIT_ALLOW = 0;
51
+
52
+ /**
53
+ * Every non-doc path this turn wrote, tracked or not.
54
+ *
55
+ * Doc paths are excluded on the same grounds the end-review concern excludes
56
+ * them: a markdown edit is not the kind of change whose obligations this
57
+ * detector can speak to, and including them would make almost every turn a
58
+ * candidate.
59
+ */
60
+ export function touchedPaths(cwd: string): string[] {
61
+ const tracked = gitNumstatRows(cwd)
62
+ .map((r) => r.path)
63
+ .filter((p) => p !== '' && !isDocPath(p));
64
+ const untracked = untrackedNonDocFiles(cwd);
65
+ return [...new Set([...tracked, ...untracked])];
66
+ }
67
+
68
+ export interface SettleVerdict {
69
+ /** Non-doc paths this turn wrote. */
70
+ readonly touched: string[];
71
+ /** Delivered rules whose path triggers match something touched. */
72
+ readonly candidates: string[];
73
+ /** Candidates with no recorded discharge, and a class that could have one. */
74
+ readonly missing: string[];
75
+ /** True when a subagent dispatch is open — every other field is then advisory only. */
76
+ readonly dispatchOpen: boolean;
77
+ }
78
+
79
+ /**
80
+ * What an armed detector would conclude, computed without refusing anything.
81
+ *
82
+ * Exported whole rather than as a boolean so the shadow row, the tests and any
83
+ * later armed path all read the SAME computation. A detector whose shadow
84
+ * measurement and live behaviour come from different code measures nothing.
85
+ */
86
+ export function computeVerdict(root: string, session: string): SettleVerdict {
87
+ const dispatchOpen = (() => {
88
+ try {
89
+ return openRecordStats(root).open_count > 0;
90
+ } catch {
91
+ // An unreadable ledger is not a reason to change the verdict.
92
+ return false;
93
+ }
94
+ })();
95
+
96
+ const touched = touchedPaths(root);
97
+ if (touched.length === 0) {
98
+ return { touched, candidates: [], missing: [], dispatchOpen };
99
+ }
100
+
101
+ const delivered = readDelivered(root, session);
102
+ if (delivered.length === 0) {
103
+ return { touched, candidates: [], missing: [], dispatchOpen };
104
+ }
105
+
106
+ let pathMatched = new Set<string>();
107
+ try {
108
+ const router = loadRouter(root);
109
+ // Empty prompt, so only PATH triggers can fire. A turn's file writes
110
+ // are not a restatement of the user's request and must not re-fire
111
+ // keyword rules — the same reasoning the injector applies on its own
112
+ // tool slot.
113
+ pathMatched = new Set(matchTierRules(router, '', touched, null).map((m) => m.id));
114
+ } catch {
115
+ return { touched, candidates: [], missing: [], dispatchOpen };
116
+ }
117
+
118
+ const candidates = delivered
119
+ .filter((r) => pathMatched.has(r.rule) && may_refuse_on(r.cls))
120
+ .map((r) => r.rule);
121
+
122
+ const discharged = new Set(readDischarged(root, session).map((r) => r.rule));
123
+ const missing = candidates.filter((id) => !discharged.has(id));
124
+ return { touched, candidates: [...new Set(candidates)], missing: [...new Set(missing)] , dispatchOpen };
125
+ }
126
+
127
+ /**
128
+ * Whether this reading should force a continuation, were anything armed.
129
+ *
130
+ * An UNCHANGED missing set on a second reading means the previous nudge moved
131
+ * nothing, so nudging again would loop rather than progress. The obligations
132
+ * stay OPEN — that is the whole point, and it is the side of the source set's
133
+ * self-contradiction this ships. A budget running out is a fact about the
134
+ * budget; letting it write a policy verdict is how an unmet obligation becomes
135
+ * a satisfied one with nobody deciding, and an exhausted budget is exactly the
136
+ * moment the obligation is least likely to have been met.
137
+ *
138
+ * There is therefore no `waived` state anywhere in this file, and no code path
139
+ * that writes one.
140
+ */
141
+ export function shouldContinue(attempt: number): boolean {
142
+ return attempt <= 1;
143
+ }
144
+
145
+ export function main(): number {
146
+ const root = process.cwd();
147
+ const session = (process.env['CLAUDE_CODE_SESSION_ID'] ?? '').trim();
148
+ if (session === '') return EXIT_ALLOW;
149
+
150
+ let verdict: SettleVerdict;
151
+ try {
152
+ verdict = computeVerdict(root, session);
153
+ } catch {
154
+ // A detector that cannot compute has nothing to say. It never has a
155
+ // refusal to withhold, so there is no safe-vs-unsafe choice here.
156
+ return EXIT_ALLOW;
157
+ }
158
+
159
+ // Silent while a dispatch is open: the files may be a subagent's, still
160
+ // being written, and a shadow row claiming otherwise would poison the very
161
+ // corpus the arming decision is measured against.
162
+ if (verdict.dispatchOpen) return EXIT_ALLOW;
163
+ if (verdict.missing.length === 0) return EXIT_ALLOW;
164
+
165
+ const attempt = appendShadow(root, session, verdict.missing, stamp());
166
+ if (attempt === 0) return EXIT_ALLOW;
167
+
168
+ // ONE line for the whole missing set, never one per obligation — the same
169
+ // aggregation the row itself uses, for the same reason.
170
+ process.stderr.write(
171
+ `obligation-settle (shadow, refusing nothing): ${verdict.missing.length} `
172
+ + `obligation(s) with no recorded discharge after writing `
173
+ + `${verdict.touched.length} file(s): ${verdict.missing.join(', ')}`
174
+ + `${shouldContinue(attempt) ? '' : ' — unchanged since the last reading; still open'}\n`,
175
+ );
176
+ return EXIT_ALLOW;
177
+ }
178
+
179
+ // Bundle-safety: never auto-run when inlined into an esbuild bundle.
180
+ if (process.argv[1] !== undefined && path.basename(process.argv[1]).startsWith('obligation_settle')) {
181
+ process.exit(main());
182
+ }
@@ -11,7 +11,14 @@
11
11
  * the 36.2 %-against-48 % run at `docs/CLAIMS.md:188-189`. Whatever that
12
12
  * instrument's flaws — and ADR-202 closed it — it points one way.
13
13
  *
14
- * SHIPPED ON FOR CLAUDE CODE SINCE ADR-265 — corrected 2026-09-08 (R2 finding
14
+ * SHIPPED ON FOR CLAUDE CODE SINCE ADR-267 — the delivery-default record, and
15
+ * the citation is corrected here (2026-09-13) from ADR-265, which decides
16
+ * something else entirely: whether the iron-law reserve is activated, answered
17
+ * no, with the verifier kept inside the change. Nothing in it ships a
18
+ * projection mode. ADR-267 `delivery-default-for-claude-code` is the record
19
+ * that turns this concern on for this host, and it is the one whose
20
+ * `review_trigger` fires if a rule stops arriving. Previously corrected
21
+ * 2026-09-08 (R2 finding
15
22
  * 4), because this paragraph read "DEFAULT OFF, AND OFF MEANS ZERO BYTES …
16
23
  * under every shipped default it emits nothing … there is no measured emission
17
24
  * to register yet" and all four clauses became false in the same change that
@@ -94,6 +101,8 @@ import {
94
101
  normalizeLeanProjectionMode,
95
102
  resolveLeanProjectionHosts,
96
103
  } from '../_lib/lean_projection_mode.js';
104
+ import { enforcement_class_from_frontmatter } from '../_lib/obligation_frequency.js';
105
+ import { appendDelivered, stamp, type DeliveredRow } from '../_lib/obligations.js';
97
106
  import {
98
107
  loadRuleBody,
99
108
  loadRouter,
@@ -237,6 +246,44 @@ export interface Injection {
237
246
  body: string;
238
247
  }
239
248
 
249
+ /**
250
+ * Record what this fire delivered, so something can later ask whether the turn
251
+ * discharged it.
252
+ *
253
+ * The write happens HERE and not in a later slot because this is the only
254
+ * place that knows the answer. By the time a turn ends, the selection that
255
+ * produced these ids is gone — the router would have to be re-run against a
256
+ * prompt nobody kept, which is a different computation that could disagree.
257
+ *
258
+ * EMITTER RECORD, NEVER A COMPLIANCE ONE. A row says a body was emitted into
259
+ * this slot. It does not say the model read it, and the ledger's own header
260
+ * carries the council lock that forbids reading it that way.
261
+ *
262
+ * Best-effort by construction: every failure path inside `appendDelivered`
263
+ * returns rather than throws, and this wrapper adds one more guard so a
264
+ * ledger problem can never turn a successful injection into a failed hook.
265
+ * A missed reading is cheaper than a refused turn, and this concern has no
266
+ * refusal to offer anyway.
267
+ */
268
+ export function recordDelivered(root: string, session: string, ruleIds: string[]): number {
269
+ try {
270
+ const now = stamp();
271
+ const rows: DeliveredRow[] = ruleIds.map((id) => {
272
+ const body = loadRuleBody(root, id);
273
+ return {
274
+ rule: id,
275
+ // A rule with no projected body declares nothing this can read,
276
+ // which is `none` — the same answer as a body declaring none.
277
+ cls: body === null ? 'none' : enforcement_class_from_frontmatter(body),
278
+ at: now,
279
+ };
280
+ });
281
+ return appendDelivered(root, session, rows);
282
+ } catch {
283
+ return 0;
284
+ }
285
+ }
286
+
240
287
  /**
241
288
  * Build the injection for one event, or `null` for silence.
242
289
  *
@@ -371,6 +418,7 @@ export function main(argv: readonly string[] = process.argv.slice(2)): number {
371
418
 
372
419
  for (const id of injection.rules) seen.add(id);
373
420
  writeSeen(root, session, seen);
421
+ recordDelivered(root, session, injection.rules);
374
422
 
375
423
  process.stdout.write(
376
424
  `${JSON.stringify({
@@ -185,6 +185,7 @@ import { premiseMoved, readContextObservation } from '../_lib/context_observatio
185
185
  import { RUN_TERMINAL_VOCABULARY_VERSION } from '../_lib/outcome_vocabularies.js';
186
186
  import {
187
187
  HALT_ACTIONS,
188
+ buildLedgerEvent, parseDeliveryState, type DeliveryState,
188
189
  ladder,
189
190
  MAX_ITERATIONS,
190
191
  parseHaltStamp,
@@ -322,6 +323,8 @@ export interface RunState {
322
323
  * observed one, which is never a disagreement (`premiseMoved`).
323
324
  */
324
325
  context_fingerprint?: string;
326
+ /** DELIVERY position (8.1), written by the run; absent is no hold. */
327
+ delivery?: DeliveryState;
325
328
  }
326
329
 
327
330
  /**
@@ -651,6 +654,10 @@ function readState(file: string): RunState | null {
651
654
  if (o['inert_reported'] === true) {
652
655
  rec.inert_reported = true;
653
656
  }
657
+ // Round-tripped for the same reason `history_source` is: without this the
658
+ // 8.1 hold is dead code that reads as shipped.
659
+ const dlv = parseDeliveryState(o['delivery']);
660
+ if (dlv !== undefined) rec.delivery = dlv;
654
661
  // Round-tripped for the same reason `history_source` is: dropped, the
655
662
  // premise would be re-recorded every fire from the newest observation, so
656
663
  // the two sides could never differ and the rung would be dead code.
@@ -1384,14 +1391,14 @@ export function main(): number {
1384
1391
  const observed = readContextObservation(workspaceRoot)?.fingerprint ?? null;
1385
1392
  const premiseInvalidated = premiseMoved(state.context_fingerprint, observed);
1386
1393
 
1394
+ const delivery = parseDeliveryState(state.delivery) ?? null;
1387
1395
  const action = ladder(
1388
- state,
1389
- scan.open,
1390
- Date.now(),
1396
+ state, scan.open, Date.now(),
1391
1397
  scan.blocked,
1392
1398
  undefined,
1393
1399
  unavailable,
1394
1400
  premiseInvalidated,
1401
+ delivery,
1395
1402
  );
1396
1403
 
1397
1404
  if (action !== 'engage') {
@@ -1408,23 +1415,13 @@ export function main(): number {
1408
1415
  const alreadyBlocked =
1409
1416
  action === 'blocked' && eventAlreadyLogged(workspaceRoot, runId, slug, 'blocked');
1410
1417
  if (!alreadyStamped && !alreadyBlocked) {
1411
- appendEvent(workspaceRoot, {
1412
- event: action,
1413
- run_id: runId,
1414
- roadmap: slug,
1415
- turn: turnOrdinal,
1416
- iterations: state.iterations,
1417
- open: scan.open,
1418
- blocked: scan.blocked,
1419
- // A ledger line is a persisted shape carrying a `RunTerminalState`,
1420
- // so it names the vocabulary version it was written against: a
1421
- // reader meeting an unknown value can then tell a newer writer
1422
- // from a corrupt row.
1423
- terminal_state: terminalStateFor(action),
1424
- terminal_vocabulary_version: RUN_TERMINAL_VOCABULARY_VERSION,
1425
- at: new Date().toISOString(),
1426
- ...roots,
1427
- });
1418
+ const line = buildLedgerEvent(
1419
+ { action, runId, slug, turn: turnOrdinal, iterations: state.iterations,
1420
+ open: scan.open, blocked: scan.blocked, delivery },
1421
+ RUN_TERMINAL_VOCABULARY_VERSION,
1422
+ new Date(),
1423
+ );
1424
+ appendEvent(workspaceRoot, { ...line, ...roots } as JsonObject);
1428
1425
  }
1429
1426
  if (action === 'complete') {
1430
1427
  // Only a COMPLETION clears the state, so a later run on the same
@@ -45,6 +45,7 @@ import {
45
45
  type InstallBoundary,
46
46
  type RefusalStats,
47
47
  } from "./_lib/turn_end_refusals.js";
48
+ import { ledgerWritable, readDelivered } from "./_lib/obligations.js";
48
49
 
49
50
  // src/scripts/hooks_doctor.ts → parents[2] (../../..) is the repo root,
50
51
  // mirroring the Python `Path(__file__).resolve().parents[2]`.
@@ -191,10 +192,35 @@ export interface DoctorPayload {
191
192
  * concern's health is already read, so this is where the number belongs.
192
193
  */
193
194
  turn_end_refusals: RefusalStats;
195
+ /**
196
+ * The obligation ledger's two readiness figures.
197
+ *
198
+ * Reported here and nowhere else, because the doctor is where a concern's
199
+ * health is already read and the ledger's failure mode is silence: it never
200
+ * throws, never refuses and never fails a turn, so an unwritable ledger
201
+ * looks exactly like a session that triggered no rules. These two lines are
202
+ * the difference between those, and they are the ONLY consumer the ledger
203
+ * has — reporting a count is not acting on the rows.
204
+ */
205
+ obligation_ledger: ObligationLedgerStats;
194
206
  /** The machine's install stamp, for reading the version split below it. */
195
207
  install_boundary: InstallBoundary;
196
208
  }
197
209
 
210
+ /** What the doctor can say about the ledger without interpreting a single row. */
211
+ export interface ObligationLedgerStats {
212
+ /** Probed by writing and removing a sentinel, not by reading mode bits. */
213
+ writable: boolean;
214
+ /**
215
+ * Rows for THIS session, or `null` when no session id was resolvable.
216
+ *
217
+ * `null` and `0` are different answers and are rendered differently: `0`
218
+ * means this session was delivered no rule, `null` means the doctor could
219
+ * not tell which session it is in and so has nothing to count.
220
+ */
221
+ rows_this_session: number | null;
222
+ }
223
+
198
224
  /** Build the doctor payload — JSON-serialisable. */
199
225
  export function collect(
200
226
  project_root: string,
@@ -288,10 +314,67 @@ export function collect(
288
314
  trampolines,
289
315
  dispatch_issues: issues,
290
316
  turn_end_refusals,
317
+ obligation_ledger: collectLedgerStats(project_root),
291
318
  install_boundary: readInstallBoundary(),
292
319
  };
293
320
  }
294
321
 
322
+ /**
323
+ * The ledger's two figures, never throwing.
324
+ *
325
+ * The session id comes from the host's own environment variable, which is the
326
+ * same source the concerns key their state on. Absent means the doctor is
327
+ * being run outside a session — by a human at a shell, most often — and the
328
+ * honest answer is `null` rather than 0, because there is no session whose
329
+ * rows could be counted.
330
+ */
331
+ export function collectLedgerStats(project_root: string): ObligationLedgerStats {
332
+ let writable = false;
333
+ try {
334
+ writable = ledgerWritable(project_root);
335
+ } catch {
336
+ writable = false;
337
+ }
338
+ const session = (process.env["CLAUDE_CODE_SESSION_ID"] ?? "").trim();
339
+ if (session === "") return { writable, rows_this_session: null };
340
+ try {
341
+ return { writable, rows_this_session: readDelivered(project_root, session).length };
342
+ } catch {
343
+ return { writable, rows_this_session: null };
344
+ }
345
+ }
346
+
347
+ /**
348
+ * The ledger section.
349
+ *
350
+ * Deliberately states what a row is NOT. The council lock this ledger sits
351
+ * under says installation proves availability and not exposure, so a reader
352
+ * who sees a healthy row count and concludes the rules were followed has made
353
+ * the one claim these records may never support. The line is cheap; the
354
+ * misreading it prevents is the whole reason the ledger needed a non-goal.
355
+ */
356
+ export function _render_ledger(payload: DoctorPayload): string[] {
357
+ const s = payload.obligation_ledger;
358
+ const lines: string[] = [];
359
+ lines.push("Obligation ledger");
360
+ lines.push("-".repeat(60));
361
+ lines.push(
362
+ `${s.writable ? "✅ " : "❌ "}writable ${
363
+ s.writable ? "agents/runtime/state/obligations/" : "cannot write agents/runtime/state/obligations/"
364
+ }`,
365
+ );
366
+ if (s.rows_this_session === null) {
367
+ lines.push("· rows this session: no session id resolved — nothing to count");
368
+ } else {
369
+ lines.push(`· rows this session: ${s.rows_this_session}`);
370
+ }
371
+ lines.push(
372
+ " Rows record that an emitter ran. They are not evidence that a rule",
373
+ );
374
+ lines.push(" reached the model, and may not be read as compliance.");
375
+ return lines;
376
+ }
377
+
295
378
  function _strVal(v: JsonValue | undefined): string {
296
379
  return v === undefined || v === null ? "undefined" : String(v);
297
380
  }
@@ -415,6 +498,8 @@ export function _render_table(payload: DoctorPayload): string {
415
498
  }
416
499
  lines.push("");
417
500
  lines.push(..._render_refusals(payload));
501
+ lines.push("");
502
+ lines.push(..._render_ledger(payload));
418
503
  // Dispatch-issues detail — last 20 grouped by concern.
419
504
  if (payload.dispatch_issues.length > 0) {
420
505
  lines.push("");
@@ -73,11 +73,22 @@ export const PLATFORM_BRIDGES: Record<string, [string, string]> = {
73
73
  copilot: ["", "rule-only fallback — this package binds no hook here"],
74
74
  };
75
75
 
76
+ /** The shape of a host's user-facing question primitive. */
77
+ export type AskShape = "native" | "text";
78
+
76
79
  export interface PlatformRow {
77
80
  platform: string;
78
81
  status: string;
79
82
  bridge_path: string | null;
80
83
  fallback_only: boolean;
84
+ /**
85
+ * DECLARED in the manifest, never probed. A host with no row reads `text`:
86
+ * the conservative answer, and the one that is true of every host that has
87
+ * not said otherwise. Absent must not read as "unknown" here — an unknown
88
+ * that the contract then treats as native is exactly the silent degrade the
89
+ * row exists to prevent.
90
+ */
91
+ ask: AskShape;
81
92
  bindings: Record<string, string[]>;
82
93
  hint: string | null;
83
94
  }
@@ -122,6 +133,7 @@ export function collect(project_root: string, manifest: JsonObject): StatusMatri
122
133
  const blockRaw = platforms[platform];
123
134
  const block = _isObject(blockRaw) ? blockRaw : {};
124
135
  const fallback_only = Boolean(block["fallback_only"]);
136
+ const ask: AskShape = block["ask"] === "native" ? "native" : "text";
125
137
  const bindings: Record<string, string[]> = {};
126
138
  if (!fallback_only) {
127
139
  for (const [ev, c] of Object.entries(block)) {
@@ -138,6 +150,7 @@ export function collect(project_root: string, manifest: JsonObject): StatusMatri
138
150
  status,
139
151
  bridge_path: rel || null,
140
152
  fallback_only,
153
+ ask,
141
154
  bindings,
142
155
  hint: ["missing", "empty", "degraded", "n/a"].includes(status)
143
156
  ? hint
@@ -166,6 +179,12 @@ export function _render_table(matrix: StatusMatrix): string {
166
179
  head += ` (${row.bridge_path})`;
167
180
  }
168
181
  lines.push(head);
182
+ lines.push(
183
+ ` ask → ${row.ask}` +
184
+ (row.ask === "native"
185
+ ? " (structured-ask tool; the recommendation is its default option)"
186
+ : " (numbered text block — the fallback, and it says so)"),
187
+ );
169
188
  if (row.fallback_only) {
170
189
  lines.push(
171
190
  " degraded: rule-only fallback " +