@event4u/agent-config 15.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 (283) 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 +301 -347
  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 +77 -29
  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 +97 -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/rules/user-interaction.md +31 -30
  40. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  41. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  42. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  43. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  44. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  45. package/dist/agent-src/skills/design-review/SKILL.md +50 -0
  46. package/dist/agent-src/skills/error-handling-patterns/SKILL.md +9 -0
  47. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
  48. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  49. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  50. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  51. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  52. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  53. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  54. package/dist/agent-src/templates/agent-settings.md +1 -1
  55. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  56. package/dist/agent-src/templates/roadmaps.md +188 -0
  57. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  58. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  59. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  60. package/dist/cli-delegate/check_update_banner.js +40 -2
  61. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  62. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  63. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  64. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  65. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  66. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
  67. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
  68. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
  69. package/dist/cli-delegate/chunk-NUHLI2RX.js +80 -0
  70. package/dist/cli-delegate/{chunk-HZ52BRGG.js → chunk-P6ZHP5W2.js} +15 -90
  71. package/dist/cli-delegate/{chunk-TYSY33MD.js → chunk-QJWAUMJW.js} +12 -2
  72. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  73. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  74. package/dist/cli-delegate/{chunk-2KKXJW25.js → chunk-S3VFXMTM.js} +1 -1
  75. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
  76. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  77. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  78. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  79. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  80. package/dist/cli-delegate/cmd_conformance.js +918 -325
  81. package/dist/cli-delegate/cmd_doctor.js +7 -5
  82. package/dist/cli-delegate/cmd_explain.js +5 -3
  83. package/dist/cli-delegate/cmd_export.js +9 -7
  84. package/dist/cli-delegate/cmd_fleet.js +4 -2
  85. package/dist/cli-delegate/cmd_handoff.js +6 -6
  86. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  87. package/dist/cli-delegate/cmd_migrate.js +2 -2
  88. package/dist/cli-delegate/cmd_packs_active.js +3 -2
  89. package/dist/cli-delegate/cmd_preflight.js +4 -2
  90. package/dist/cli-delegate/cmd_prune.js +10 -8
  91. package/dist/cli-delegate/cmd_route_audit.js +2 -2
  92. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  93. package/dist/cli-delegate/cmd_session_recycle.js +4 -4
  94. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  95. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  96. package/dist/cli-delegate/cmd_settings_set.js +14 -1
  97. package/dist/cli-delegate/cmd_sync.js +10 -8
  98. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  99. package/dist/cli-delegate/cmd_update.js +1 -1
  100. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  101. package/dist/cli-delegate/cmd_validate.js +12 -10
  102. package/dist/cli-delegate/cmd_versions.js +1 -1
  103. package/dist/discovery/deprecation-report.md +1 -1
  104. package/dist/discovery/discovery-manifest.json +87 -40
  105. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  106. package/dist/discovery/discovery-manifest.summary.md +4 -4
  107. package/dist/discovery/dormancy-report.md +1 -1
  108. package/dist/discovery/orphan-report.md +1 -1
  109. package/dist/discovery/packs.json +10 -8
  110. package/dist/discovery/trust-report.md +3 -3
  111. package/dist/discovery/workspaces.json +8 -6
  112. package/dist/hooks/dispatch.js +2601 -1073
  113. package/dist/install/claudeRuleRewrite.js +14 -3
  114. package/dist/install/conflict.js +60 -10
  115. package/dist/install/conflictTracker.js +218 -0
  116. package/dist/install/fsPrimitives.js +54 -0
  117. package/dist/install/install.mjs +2008 -1481
  118. package/dist/install/manifestFiles.js +259 -0
  119. package/dist/install/preflight.js +14 -2
  120. package/dist/install/preserve.js +157 -0
  121. package/dist/install/recordedOwnership.js +132 -0
  122. package/dist/install/txlog.js +11 -2
  123. package/dist/mcp/registry-manifest.json +3 -3
  124. package/dist/mcp/server.json +2 -2
  125. package/dist/mcp/server.mjs +1612 -667
  126. package/dist/router.json +1 -1
  127. package/dist/scripts/_lib/agent_settings.js +3 -2
  128. package/dist/scripts/_lib/fs_atomic.js +167 -0
  129. package/dist/scripts/_lib/installed_lock.js +1 -1
  130. package/dist/scripts/_lib/installed_tools.js +460 -0
  131. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  132. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  133. package/dist/server/io/yamlIO.js +50 -0
  134. package/dist/server/routes/install.js +8 -0
  135. package/dist/server/routes/wizard.js +9 -3
  136. package/dist/server/schemas/settings.js +17 -5
  137. package/dist/shared/missionExecution.js +97 -0
  138. package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
  139. package/dist/ui/index.html +1 -1
  140. package/docs/MIGRATION.md +103 -0
  141. package/docs/contracts/settings-classes.md +26 -7
  142. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  143. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  144. package/docs/guidelines/design-fidelity-routing.md +29 -0
  145. package/package.json +1 -1
  146. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  147. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  148. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  149. package/src/config/agent-settings.template.yml +139 -27
  150. package/src/config/auto-rule-budget.json +16 -0
  151. package/src/config/ci-local-parity.yml +18 -5
  152. package/src/config/continuity-surface.json +28 -0
  153. package/src/config/gate-coverage.yml +146 -13
  154. package/src/config/gate-violation-baselines.json +6 -6
  155. package/src/config/hook-token-budget.json +2 -0
  156. package/src/config/host-injection-effect.json +1 -1
  157. package/src/config/loop-surfaces.yaml +215 -0
  158. package/src/config/platform-anchor.json +3 -2
  159. package/src/config/preamble-payload-budget.json +12 -7
  160. package/src/config/preamble-payload-exceptions.json +28 -0
  161. package/src/config/release-gate-locality.yml +11 -0
  162. package/src/config/routing-coverage-seed.json +18 -4
  163. package/src/config/rule-enforcement-baseline.json +0 -1
  164. package/src/install/claudeRuleRewrite.ts +16 -2
  165. package/src/install/conflict.ts +65 -9
  166. package/src/install/conflictTracker.ts +299 -0
  167. package/src/install/fsPrimitives.ts +54 -0
  168. package/src/install/manifestFiles.ts +312 -0
  169. package/src/install/preflight.ts +16 -2
  170. package/src/install/preserve.ts +205 -0
  171. package/src/install/recordedOwnership.ts +149 -0
  172. package/src/install/txlog.ts +11 -2
  173. package/src/install/types.ts +19 -4
  174. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  175. package/src/scripts/_cli/cmd_doctor.ts +4 -3
  176. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  177. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  178. package/src/scripts/_cli/doctor_execution.ts +90 -0
  179. package/src/scripts/_dispatch.bash +3 -1
  180. package/src/scripts/_lib/activation_payload.ts +247 -0
  181. package/src/scripts/_lib/agent_settings.ts +4 -2
  182. package/src/scripts/_lib/authority_path.ts +102 -0
  183. package/src/scripts/_lib/base_ref_payload.ts +315 -0
  184. package/src/scripts/_lib/cascade_base.ts +94 -0
  185. package/src/scripts/_lib/changelog_eras.ts +7 -2
  186. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  187. package/src/scripts/_lib/council_transport.ts +129 -0
  188. package/src/scripts/_lib/delivery_ready.ts +112 -0
  189. package/src/scripts/_lib/dropped_decision.ts +142 -0
  190. package/src/scripts/_lib/forge_protection.ts +267 -0
  191. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  192. package/src/scripts/_lib/installed_lock.ts +1 -1
  193. package/src/scripts/_lib/json_python_parity.ts +127 -0
  194. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  195. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  196. package/src/scripts/_lib/measured_payload_ceiling.ts +424 -0
  197. package/src/scripts/_lib/mission_record.ts +190 -0
  198. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  199. package/src/scripts/_lib/obligations.ts +402 -0
  200. package/src/scripts/_lib/orchestration_record.ts +11 -20
  201. package/src/scripts/_lib/payload_catalogue_completeness.ts +211 -0
  202. package/src/scripts/_lib/release_findings_ingest.ts +324 -0
  203. package/src/scripts/_lib/release_highlights.ts +199 -10
  204. package/src/scripts/_lib/release_holds.ts +493 -0
  205. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  206. package/src/scripts/_lib/release_tag_dispatch.ts +63 -0
  207. package/src/scripts/_lib/review_baseline.ts +254 -0
  208. package/src/scripts/_lib/review_skipped_record.ts +61 -1
  209. package/src/scripts/_lib/rides_along.ts +102 -0
  210. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  211. package/src/scripts/_lib/standing_bound_ratchet.ts +216 -103
  212. package/src/scripts/_lib/test_delta.ts +166 -0
  213. package/src/scripts/_lib/test_provenance.ts +171 -0
  214. package/src/scripts/_lib/turn_end_refusals.ts +51 -4
  215. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  216. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  217. package/src/scripts/ai_council/config.ts +8 -11
  218. package/src/scripts/ai_council/fallback_config.ts +5 -3
  219. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  220. package/src/scripts/ask_block_census.ts +285 -15
  221. package/src/scripts/check_beta_review_markers.ts +35 -4
  222. package/src/scripts/check_enforcement_matrix.ts +624 -0
  223. package/src/scripts/check_finding_dispositions.ts +167 -16
  224. package/src/scripts/check_gate_reachability.ts +173 -10
  225. package/src/scripts/check_generator_sync.ts +954 -0
  226. package/src/scripts/check_held_object_arrivals.ts +381 -0
  227. package/src/scripts/check_preamble_payload_budget.ts +474 -124
  228. package/src/scripts/check_release_highlights.ts +33 -1
  229. package/src/scripts/check_release_holds.ts +567 -0
  230. package/src/scripts/check_release_published.ts +1 -1
  231. package/src/scripts/check_reply_consistency.ts +86 -17
  232. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  233. package/src/scripts/check_test_delta.ts +294 -0
  234. package/src/scripts/check_test_weakening.ts +299 -0
  235. package/src/scripts/closure_scan.ts +333 -0
  236. package/src/scripts/compile_hook_manifest.ts +43 -3
  237. package/src/scripts/council_record_shape.ts +209 -0
  238. package/src/scripts/hook_manifest.json +1 -1
  239. package/src/scripts/hook_manifest.yaml +127 -12
  240. package/src/scripts/hooks/block_kernel_rule_writes.ts +12 -2
  241. package/src/scripts/hooks/concern_registry.ts +6 -0
  242. package/src/scripts/hooks/design_pass_hook.ts +128 -3
  243. package/src/scripts/hooks/end_review_nudge_hook.ts +101 -21
  244. package/src/scripts/hooks/evidence_independence.ts +96 -0
  245. package/src/scripts/hooks/memory_recall_hook.ts +540 -0
  246. package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
  247. package/src/scripts/hooks/review_baseline_hook.ts +130 -0
  248. package/src/scripts/hooks/rule_inject_hook.ts +49 -1
  249. package/src/scripts/hooks/run_continuation_hook.ts +17 -20
  250. package/src/scripts/hooks/turn_end_gate_hook.ts +232 -14
  251. package/src/scripts/hooks_doctor.ts +89 -1
  252. package/src/scripts/hooks_status.ts +19 -0
  253. package/src/scripts/install.ts +107 -207
  254. package/src/scripts/interruption_report.ts +8 -2
  255. package/src/scripts/janitor.ts +15 -1
  256. package/src/scripts/lint_decision_classes.ts +464 -0
  257. package/src/scripts/lint_hook_manifest.ts +11 -0
  258. package/src/scripts/lint_major_migration_sections.ts +450 -0
  259. package/src/scripts/lint_plan_risk_register.ts +47 -3
  260. package/src/scripts/lint_roadmap_blockers.ts +61 -0
  261. package/src/scripts/lint_roadmap_producers.ts +202 -0
  262. package/src/scripts/measure_rule_budget.ts +101 -5
  263. package/src/scripts/measure_turn_end_gate.ts +302 -5
  264. package/src/scripts/new_roadmap.ts +37 -0
  265. package/src/scripts/orchestration_record.ts +0 -5
  266. package/src/scripts/release.ts +66 -162
  267. package/src/scripts/release_drill.ts +460 -15
  268. package/src/scripts/release_env.ts +1 -1
  269. package/src/scripts/release_publication.ts +52 -1
  270. package/src/scripts/report_enforcement_drift.ts +566 -0
  271. package/src/scripts/report_held_object_arrivals.ts +456 -0
  272. package/src/scripts/report_host_injection_effect.ts +3 -11
  273. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  274. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  275. package/src/scripts/schemas/command.schema.json +4 -0
  276. package/src/scripts/scope_growth.ts +219 -0
  277. package/src/scripts/sync_agent_settings.ts +140 -18
  278. package/src/scripts/trigger_eval_grandfather.json +0 -1
  279. package/src/scripts/ui_conformance_probe.ts +622 -0
  280. package/src/server/io/yamlIO.ts +48 -0
  281. package/src/server/schemas/settings.ts +31 -7
  282. package/src/shared/missionExecution.ts +126 -0
  283. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
@@ -69,10 +69,12 @@ import { main as delegationNudgeMain } from './delegation_nudge_hook.js';
69
69
  import { main as skillRouteMain } from './skill_route_hook.js';
70
70
  import { main as ruleInjectMain } from './rule_inject_hook.js';
71
71
  import { main as endReviewNudgeMain } from './end_review_nudge_hook.js';
72
+ import { main as reviewBaselineMain } from './review_baseline_hook.js';
72
73
  import { main as turnEndGateMain } from './turn_end_gate_hook.js';
73
74
  import { main as editShapeMain } from './edit_shape_hook.js';
74
75
  import { main as commentDisciplineMain } from './comment_discipline_hook.js';
75
76
  import { main as rereadGuardMain } from './reread_guard_hook.js';
77
+ import { main as memoryRecallMain } from './memory_recall_hook.js';
76
78
  import { main as sessionEolMain } from './session_eol_hook.js';
77
79
  import { main as subagentLedgerMain } from './subagent_ledger_hook.js';
78
80
  import { main as toolResultBytesMain } from './tool_result_bytes_hook.js';
@@ -81,6 +83,7 @@ import { main as sourceFirstGateMain } from './source_first_gate_hook.js';
81
83
  import { main as interruptionLedgerMain } from './interruption_ledger_hook.js';
82
84
  import { main as runContinuationMain } from './run_continuation_hook.js';
83
85
  import { main as journalRecordMain } from './journal_record_hook.js';
86
+ import { main as obligationSettleMain } from './obligation_settle_hook.js';
84
87
 
85
88
  /** A concern `main` — argv-taking or not; both shapes exist. */
86
89
  export type ConcernMain = (argv?: string[]) => number | undefined | void;
@@ -132,10 +135,13 @@ export const CONCERN_REGISTRY: Readonly<Record<string, ConcernMain>> = {
132
135
  'src/scripts/hooks/skill_route_hook.ts': skillRouteMain as ConcernMain,
133
136
  'src/scripts/hooks/rule_inject_hook.ts': ruleInjectMain as ConcernMain,
134
137
  'src/scripts/hooks/end_review_nudge_hook.ts': endReviewNudgeMain as ConcernMain,
138
+ 'src/scripts/hooks/obligation_settle_hook.ts': obligationSettleMain as ConcernMain,
139
+ 'src/scripts/hooks/review_baseline_hook.ts': reviewBaselineMain as ConcernMain,
135
140
  'src/scripts/hooks/turn_end_gate_hook.ts': turnEndGateMain as ConcernMain,
136
141
  'src/scripts/hooks/edit_shape_hook.ts': editShapeMain as ConcernMain,
137
142
  'src/scripts/hooks/comment_discipline_hook.ts': commentDisciplineMain as ConcernMain,
138
143
  'src/scripts/hooks/reread_guard_hook.ts': rereadGuardMain as ConcernMain,
144
+ 'src/scripts/hooks/memory_recall_hook.ts': memoryRecallMain as ConcernMain,
139
145
  'src/scripts/hooks/session_eol_hook.ts': sessionEolMain as ConcernMain,
140
146
  'src/scripts/hooks/subagent_ledger_hook.ts': subagentLedgerMain as ConcernMain,
141
147
  'src/scripts/hooks/tool_result_bytes_hook.ts': toolResultBytesMain as ConcernMain,
@@ -56,6 +56,7 @@ import fs from 'node:fs';
56
56
  import path from 'node:path';
57
57
  import { fileURLToPath, pathToFileURL } from 'node:url';
58
58
 
59
+ import { appendDischarge, stamp } from '../_lib/obligations.js';
59
60
  import { isUiPath, isUiTreePath } from '../_lib/ui_surface.js';
60
61
  import { loadDesignContext, scanFile } from '../lint_design_slop.js';
61
62
  import { readHookStdin } from './hook_stdin.js';
@@ -65,6 +66,7 @@ const EXIT_WARN = 2;
65
66
  const SETTINGS_FILE = '.agent-settings.yml';
66
67
  const STATE_REL = path.join('agents', 'runtime', 'state', 'design-pass-hook.json');
67
68
  const AUDIT_REL = path.join('agents', 'runtime', 'state', 'ui-audit.json');
69
+ const CONFORMANCE_REL = path.join('agents', 'runtime', 'state', 'ui-conformance.json');
68
70
 
69
71
  /**
70
72
  * The P0 set — objective floors only, and the list is closed on purpose.
@@ -128,6 +130,77 @@ export function enabled(root: string, key = 'design_pass'): boolean {
128
130
 
129
131
  export const isUiSurface = (p: string): boolean => isUiPath(p) || isUiTreePath(p);
130
132
 
133
+ /**
134
+ * The shadow read of `ui-conformance.json` — Phase 5 of
135
+ * `road-to-behaviour-evidence-over-pixels`.
136
+ *
137
+ * SHADOW MEANS SHADOW. This produces a line in the reported verdict and
138
+ * touches nothing else: it is not consulted by `decide`, never reaches
139
+ * `blocked`, and cannot change an exit code. The roadmap adds evidence, not
140
+ * enforcement, and the block path is deliberately untouched by this change.
141
+ *
142
+ * An ABSENT artefact is reported as absent, never as clean — the same rule the
143
+ * probe applies to its own dimensions. A malformed one is reported as
144
+ * unreadable for the same reason.
145
+ */
146
+ export interface ConformanceVerdict {
147
+ /**
148
+ * Whether this verdict is worth surfacing on its own. An ABSENT artefact is
149
+ * not: most repositories never run the probe, and a line on every clean UI
150
+ * write is the noise that gets a carrier switched off — Risk 1 of the parent
151
+ * roadmap. Absent is still reported honestly whenever the pass speaks for
152
+ * another reason.
153
+ */
154
+ noteworthy: boolean;
155
+ line: string;
156
+ }
157
+
158
+ export function conformanceVerdict(root: string): ConformanceVerdict {
159
+ let raw: string;
160
+ try {
161
+ raw = fs.readFileSync(path.join(root, CONFORMANCE_REL), 'utf-8');
162
+ } catch {
163
+ return {
164
+ noteworthy: false,
165
+ line: 'ui-conformance: absent — no probe artefact for this change (run `ui_conformance_probe --target <file> --reference <file>`). Absent is not clean.',
166
+ };
167
+ }
168
+ try {
169
+ const a = JSON.parse(raw) as {
170
+ structure_gate?: string;
171
+ dimensions?: { dimension: string; status: string; findings: number | null; reason?: string }[];
172
+ findings?: unknown[];
173
+ };
174
+ const rows = a.dimensions ?? [];
175
+ const exercised = rows.filter((r) => r.status === 'exercised');
176
+ const notApplicable = rows.filter((r) => r.status !== 'exercised');
177
+ const counts = exercised.map((r) => `${r.dimension}=${r.findings}`).join(' ');
178
+ const parts = [
179
+ `ui-conformance: ${(a.findings ?? []).length} behavioural finding(s) — ${counts || 'no dimension exercised'}`,
180
+ ];
181
+ if (notApplicable.length) {
182
+ parts.push(
183
+ ` · not applicable: ${notApplicable
184
+ .map((r) => `${r.dimension} (${r.reason ?? 'no reason given'})`)
185
+ .join('; ')}`,
186
+ );
187
+ }
188
+ if (a.structure_gate === 'stopped') {
189
+ parts.push(' · structure gate stopped on at least one node — style comparison was skipped for it');
190
+ }
191
+ parts.push(' (shadow verdict: reported, never enforced — this pass does not block on it)');
192
+ return {
193
+ noteworthy: (a.findings ?? []).length > 0 || a.structure_gate === 'stopped' || notApplicable.length > 0,
194
+ line: parts.join('\n'),
195
+ };
196
+ } catch {
197
+ return {
198
+ noteworthy: true,
199
+ line: 'ui-conformance: unreadable — the artefact exists but did not parse. Reported rather than ignored.',
200
+ };
201
+ }
202
+ }
203
+
131
204
  /**
132
205
  * E2.2 — is the audit artefact newer than the target?
133
206
  *
@@ -135,6 +208,43 @@ export const isUiSurface = (p: string): boolean => isUiPath(p) || isUiTreePath(p
135
208
  * because it looks like evidence. An artefact older than the file it is
136
209
  * supposed to describe has not seen the change.
137
210
  */
211
+ /**
212
+ * Record that the UI-audit obligation was discharged, when it was.
213
+ *
214
+ * This concern is the only thing in the tree that knows. It already decides
215
+ * audit freshness for its own advisory line; until now that decision was spent
216
+ * on one message and thrown away, so a turn-end reader asking "was the audit
217
+ * done?" had to re-derive a verdict it does not own. The decision existed —
218
+ * only the write was missing.
219
+ *
220
+ * Discharged means: at least one target was touched AND every one of them has
221
+ * a fresh audit. A partial pass records nothing, because `ui-audit-gate`'s
222
+ * obligation is about the surfaces this turn wrote, not about a quorum of them.
223
+ *
224
+ * NO PER-WRITE VALIDATOR IS ADDED. The write is one small append behind the
225
+ * same lock the ledger already uses, on a path that has an mtime comparison in
226
+ * front of it — deliberately, because this concern sits on `post_tool_use`,
227
+ * the hottest slot it binds, and a validation pass per write is exactly the
228
+ * cost the phase forbids.
229
+ */
230
+ export function recordAuditDischarge(
231
+ root: string,
232
+ targets: readonly string[],
233
+ auditMissing: readonly string[],
234
+ ): void {
235
+ if (targets.length === 0 || auditMissing.length > 0) return;
236
+ const session = (process.env["CLAUDE_CODE_SESSION_ID"] ?? "").trim();
237
+ if (session === "") return;
238
+ try {
239
+ appendDischarge(root, session, [
240
+ { rule: "ui-audit-gate", by: "design-pass", at: stamp() },
241
+ ]);
242
+ } catch {
243
+ // A discharge that cannot be recorded is a missing reading, never a
244
+ // reason to fail the write that prompted it.
245
+ }
246
+ }
247
+
138
248
  export function auditIsFresh(root: string, target: string): boolean {
139
249
  try {
140
250
  const a = fs.statSync(path.join(root, AUDIT_REL)).mtimeMs;
@@ -252,7 +362,7 @@ export function targetPath(payload: unknown): string | null {
252
362
  return null;
253
363
  }
254
364
 
255
- export function render(result: PassResult): string {
365
+ export function render(result: PassResult, conformance?: string): string {
256
366
  const lines: string[] = [];
257
367
  if (result.blocked.length) {
258
368
  lines.push(
@@ -272,6 +382,9 @@ export function render(result: PassResult): string {
272
382
  lines.push(` · no ui-audit.json newer than ${m} — run \`agent-config ui:audit <path>\``);
273
383
  lines.push(`verification: ${result.verification}`);
274
384
  if (result.degradation_reason) lines.push(`degradation_reason: ${result.degradation_reason}`);
385
+ // Appended last and never consulted above: the conformance verdict is a
386
+ // report, not an input to any decision this pass makes.
387
+ if (conformance) lines.push(conformance);
275
388
  return lines.join('\n');
276
389
  }
277
390
 
@@ -329,6 +442,7 @@ function main(): number {
329
442
  }
330
443
 
331
444
  const auditMissing = targets.filter((t) => !auditIsFresh(root, t));
445
+ recordAuditDischarge(root, targets, auditMissing);
332
446
  let result = decide(slot, findings, auditMissing, new Set(state.surfaced), renderAvailable(root));
333
447
  if (scanFailed) {
334
448
  result = { ...result, verification: 'unverified', degradation_reason: `detector unavailable: ${scanFailed}` };
@@ -338,11 +452,22 @@ function main(): number {
338
452
  if (slot === 'stop') state.touched = [];
339
453
  writeState(root, state);
340
454
 
341
- if (!result.findings.length && !result.audit_missing.length && result.verification === 'verified') {
455
+ // Shadow mount. The verdict can make the pass SPEAK when it would otherwise
456
+ // be silent — a behavioural finding is exactly the thing no static scan can
457
+ // see — but it cannot make it refuse. `result.blocked` above is computed
458
+ // without it and the exit code below is unchanged.
459
+ const conformance = conformanceVerdict(root);
460
+
461
+ if (
462
+ !result.findings.length &&
463
+ !result.audit_missing.length &&
464
+ result.verification === 'verified' &&
465
+ !conformance.noteworthy
466
+ ) {
342
467
  return EXIT_ALLOW;
343
468
  }
344
469
 
345
- process.stdout.write(`${JSON.stringify({ reason: render(result) })}\n`);
470
+ process.stdout.write(`${JSON.stringify({ reason: render(result, conformance.line) })}\n`);
346
471
  // Deliberately EXIT_WARN even with a P0 verdict. EXIT_BLOCK is unreachable
347
472
  // for an advisory concern (the dispatcher downgrades it), so returning it
348
473
  // would claim a refusal the transport discards.
@@ -28,17 +28,26 @@
28
28
  * (`git ls-files --others --exclude-standard`, each one READ from the
29
29
  * filesystem rather than diffed in a subprocess — see "Untracked files" below;
30
30
  * fixes a review finding, F6, that a brand-new file was previously invisible
31
- * to this count entirely). Chosen over a session-start SHA/timestamp
32
- * baseline because no such baseline exists anywhere a `stop` concern can
33
- * read it (recording one would be new session-wide state, out of this
34
- * roadmap phase's one-concern scope). Two consequences, stated rather than
35
- * hidden:
36
- * - Uncommitted changes that PREDATE this session (a dirty working tree
37
- * the user already had) are counted as if they were this session's
38
- * mutation. Per `commit-policy`, an agent almost never commits without
39
- * explicit permission, so "uncommitted since HEAD" is usually a good
40
- * proxy for "mutated this session" in practice — but it is a proxy, not
41
- * an exact measure.
31
+ * to this count entirely).
32
+ *
33
+ * SINCE 2026-09-11 THIS READING IS BASELINE-CORRECTED ON `claude`, and this
34
+ * block used to say the opposite — that a session-start baseline was rejected
35
+ * "because no such baseline exists anywhere a `stop` concern can read it". That
36
+ * was true of a one-concern roadmap phase and stale by the time it mattered:
37
+ * `session_start` is bound on seven hosts, and `review-baseline` now records
38
+ * HEAD plus the already-dirty non-doc line count there. `_lib/review_baseline.ts`
39
+ * cites this paragraph as the defect it closes, so leaving the paragraph
40
+ * standing would have made the two files contradict each other.
41
+ *
42
+ * What the correction does and does not cover:
43
+ * - Uncommitted changes that PREDATE the session are SUBTRACTED where a
44
+ * baseline is readable and its HEAD and measure still match. Absent,
45
+ * unreadable, head-moved or mixed-measure → the unsubtracted count, with
46
+ * the reason carried in the `review_skipped` row.
47
+ * - The baseline is written once per session and never refreshed, so from a
48
+ * session's first commit onward `head-moved` holds and the pre-existing
49
+ * tree is charged again. Over-reporting is the safe direction; the limit is
50
+ * real and is stated in `review_baseline.ts` rather than implied away.
42
51
  * - A session that DID commit mid-turn (one of `commit-policy`'s four
43
52
  * exceptions) moves HEAD, so mutations already committed are invisible
44
53
  * to this diff. Declared, not silently assumed away.
@@ -207,11 +216,20 @@
207
216
  * exit-2 warn onto an unverified propagation path is exactly the
208
217
  * speculative mapping `host_semantics.ts` exists to avoid.
209
218
  *
210
- * CONTRACT: never blocks THE ACTUAL TURN. The DISPATCHER-INTERNAL exit is 2
211
- * on a fire (never 1/BLOCK, never >=3), `fail_closed: false`; the HOST-FACING
212
- * exit on `claude` is 0 either way, per the proof above. Doc-only
213
- * diffs, a reviewer having run, or any unreadable/malformed input all
214
- * resolve to silence (dispatcher-internal exit 0), not a crash.
219
+ * CONTRACT: never REFUSES a turn. The DISPATCHER-INTERNAL exit is 2 on a fire
220
+ * (never 1/BLOCK, never >=3), `fail_closed: false`; the HOST-FACING exit on
221
+ * `claude` is 0 either way, per the proof above. Doc-only diffs, a reviewer
222
+ * having run, or any unreadable/malformed input all resolve to silence
223
+ * (dispatcher-internal exit 0), not a crash.
224
+ *
225
+ * WHAT THE PROOF ABOVE DOES NOT COVER. Every term of it is an exit code, so it
226
+ * settles refusal and nothing else. Whether the `additionalContext` payload
227
+ * this concern emits at `stop` causes the host to run the agent AGAIN is a
228
+ * separate question, and this tree does not answer it — see
229
+ * `docs/contracts/hook-architecture-v1.md` under the exit-code table, which
230
+ * names the measurement that would. So do not read the proof above as
231
+ * establishing that this concern leaves the turn alone; it establishes only
232
+ * that the concern never refuses it.
215
233
  */
216
234
  import { spawnSync } from 'node:child_process';
217
235
  import * as crypto from 'node:crypto';
@@ -220,7 +238,18 @@ import * as os from 'node:os';
220
238
  import * as path from 'node:path';
221
239
  import { fileURLToPath, pathToFileURL } from 'node:url';
222
240
 
223
- import { buildReviewSkippedLine, type MutationMeasure } from '../_lib/review_skipped_record.js';
241
+ import {
242
+ applyBaseline,
243
+ currentHeadSha,
244
+ readBaseline,
245
+ type BaselineFallback,
246
+ type BaselineOutcome,
247
+ } from '../_lib/review_baseline.js';
248
+ import {
249
+ buildReviewSkippedLine,
250
+ type BaselineApplication,
251
+ type MutationMeasure,
252
+ } from '../_lib/review_skipped_record.js';
224
253
  import { unwrap, type JsonObject, type JsonValue } from './envelope.js';
225
254
  import { readHookStdin } from './hook_stdin.js';
226
255
  import { atomic_write_json, is_replay_mode } from './state_io.js';
@@ -606,6 +635,7 @@ export function appendReviewSkippedTelemetry(
606
635
  workspaceRoot: string,
607
636
  diffLines: number,
608
637
  mutationMeasure: MutationMeasure,
638
+ baseline?: BaselineApplication,
609
639
  ): void {
610
640
  if (is_replay_mode()) return;
611
641
  const ts = new Date().toISOString();
@@ -614,6 +644,7 @@ export function appendReviewSkippedTelemetry(
614
644
  mutation_measure: mutationMeasure,
615
645
  ts,
616
646
  id: crypto.randomUUID(),
647
+ baseline,
617
648
  });
618
649
  if (errors.length || line === null) return; // refused input — never write a broken line
619
650
  try {
@@ -694,6 +725,23 @@ function _read_stdin(): string {
694
725
  return readHookStdin();
695
726
  }
696
727
 
728
+ /**
729
+ * The one place the two spellings meet.
730
+ *
731
+ * `BaselineFallback` is kebab-cased because it is a TypeScript union read only
732
+ * in this process; `BaselineApplication` is snake_cased because it is written
733
+ * into an audit line and every other enum in that stream is. A hand-written
734
+ * ternary translated them until a review pointed out that `head-moved` and
735
+ * `head_moved` differ by one character in a conditional nobody would re-read —
736
+ * an exhaustive record makes a new fallback a compile error instead.
737
+ */
738
+ const FALLBACK_TO_TELEMETRY: Readonly<Record<BaselineFallback, BaselineApplication>> = {
739
+ absent: 'absent',
740
+ unreadable: 'unreadable',
741
+ 'head-moved': 'head_moved',
742
+ 'mixed-measure': 'mixed_measure',
743
+ };
744
+
697
745
  export function main(): number {
698
746
  const [envelope, payload] = unwrap(_read_stdin(), 'claude');
699
747
 
@@ -705,15 +753,43 @@ export function main(): number {
705
753
  const workspace_root =
706
754
  String(envelope['workspace_root'] ?? '').trim() || process.cwd();
707
755
 
708
- let diffLines: number;
756
+ let measuredLines: number;
709
757
  let mutationMeasure: MutationMeasure;
710
758
  try {
711
759
  const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
712
- diffLines = measured.lines;
760
+ measuredLines = measured.lines;
713
761
  mutationMeasure = measured.measure;
714
762
  } catch {
715
763
  return 0; // fail-open — never block the agent loop
716
764
  }
765
+
766
+ // The session key is derived HERE rather than after the threshold test,
767
+ // because the threshold is now taken on the SUBTRACTED count and the
768
+ // baseline is keyed on the session. That is a real reordering: one extra
769
+ // `sha256` of a string and one `readFileSync` of a ~100-byte JSON file now
770
+ // run on every stop, including the majority that end below threshold. The
771
+ // alternative — testing the unsubtracted count first as a cheap
772
+ // pre-filter — is wrong in the one direction that matters: on an
773
+ // accumulator branch EVERY turn clears the unsubtracted threshold, so the
774
+ // pre-filter would pass exactly when the subtraction is needed and save
775
+ // nothing.
776
+ const sessionKey = deriveSessionKey(envelope, payload);
777
+ const baselineOutcome: BaselineOutcome = applyBaseline(
778
+ measuredLines,
779
+ readBaseline(workspace_root, sessionKey),
780
+ // A THUNK, not a value: on the `absent` and `unreadable` branches the
781
+ // result is discarded, and those two are the steady state on every host
782
+ // without this nudge and on every session before its first baseline
783
+ // write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
784
+ // each of those stops for nothing.
785
+ () => currentHeadSha(workspace_root),
786
+ mutationMeasure,
787
+ );
788
+ const diffLines = baselineOutcome.lines;
789
+ const baselineApplication: BaselineApplication = baselineOutcome.applied
790
+ ? 'applied'
791
+ : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
792
+
717
793
  if (diffLines <= MUTATION_LINE_THRESHOLD) {
718
794
  return 0; // below threshold, or a doc-only diff — nothing to nudge
719
795
  }
@@ -723,7 +799,6 @@ export function main(): number {
723
799
  // JSON parse below. A session that already fired stops here, at a
724
800
  // single `fs.existsSync` call, without re-reading a (potentially
725
801
  // large) transcript file it already knows the answer for.
726
- const sessionKey = deriveSessionKey(envelope, payload);
727
802
  if (hasFiredThisSession(workspace_root, sessionKey)) {
728
803
  return 0;
729
804
  }
@@ -756,7 +831,12 @@ export function main(): number {
756
831
  markFiredThisSession(workspace_root, sessionKey, new Date().toISOString());
757
832
 
758
833
  try {
759
- appendReviewSkippedTelemetry(workspace_root, diffLines, mutationMeasure);
834
+ appendReviewSkippedTelemetry(
835
+ workspace_root,
836
+ diffLines,
837
+ mutationMeasure,
838
+ baselineApplication,
839
+ );
760
840
  } catch {
761
841
  // never let a telemetry failure block or fail the turn
762
842
  }
@@ -43,6 +43,7 @@
43
43
  *
44
44
  * Exit codes: 0 allow · 2 block (stderr carries the reason).
45
45
  */
46
+ import { spawnSync } from "node:child_process";
46
47
  import crypto from "node:crypto";
47
48
  import fs from "node:fs";
48
49
  import path from "node:path";
@@ -50,6 +51,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
50
51
 
51
52
  import { atomic_write_json } from "./state_io.js";
52
53
  import { readHookStdin } from "./hook_stdin.js";
54
+ import { isCodePath, isTestPath } from "../_lib/test_delta.js";
53
55
 
54
56
  const EXIT_ALLOW = 0;
55
57
  // MUST equal dispatch_hook.EXIT_BLOCK. The dispatcher's internal ladder is
@@ -324,6 +326,79 @@ export function decide(
324
326
  };
325
327
  }
326
328
 
329
+ /**
330
+ * The one-commit test-authorship flag —
331
+ * `road-to-adversarial-verification-and-long-runs` 2.4, third clause.
332
+ *
333
+ * A commit staging BOTH a test and code it could cover is the shape of L0 on the
334
+ * independence scale — the implementer wrote the evaluator — and it is the one
335
+ * case nobody sees, because the diff looks exactly like a well-tested change.
336
+ *
337
+ * **It reads the STAGED SET and the command, and nothing else.** It does NOT
338
+ * read a session id, an author or a timestamp, so it cannot distinguish an
339
+ * implementer who wrote both from one committing a test another session authored
340
+ * — an independent review of this function caught an earlier docstring here
341
+ * claiming a session boundary the code never reads, which is exactly the
342
+ * over-claim `evaluator-independence` exists over. The honest reading is: this
343
+ * is the shape L0 takes in a diff, surfaced so the level is stated rather than
344
+ * inferred.
345
+ *
346
+ * **WARN, never block.** L0 is explicitly permitted as a fallback, so refusing it
347
+ * would forbid a legal state; and a signal this coarse may surface, never refuse.
348
+ *
349
+ * Returns `null` when nothing is worth saying — i.e. this is not a commit.
350
+ */
351
+ export function sameSessionTestFlag(command: string | null): string | null {
352
+ if (command === null || !/\bgit\b[^|;&]*\bcommit\b/.test(command)) return null;
353
+ return SAME_SESSION_TEXT;
354
+ }
355
+
356
+ const SAME_SESSION_TEXT =
357
+ "This commit stages both a test and code it could cover — the shape L0 takes " +
358
+ "in a diff, where the implementer authored the evaluator. This check reads the " +
359
+ "staged set only: it cannot tell who wrote the test, so it is a prompt to STATE " +
360
+ "the level, not a finding that you are at L0. L0 is a permitted FALLBACK, not " +
361
+ "the default: where a second session, a second model or a second provider was " +
362
+ "available, the test should have come from one of them. Say which in the commit " +
363
+ "or the PR body — a diff looks identical either way. See evaluator-independence " +
364
+ "§ Tests are evaluators.";
365
+
366
+ /** Both a test path and a production path in one changed set. */
367
+ export function touchesTestAndCode(paths: readonly string[]): boolean {
368
+ return paths.some(isTestPath) && paths.some(isCodePath);
369
+ }
370
+
371
+ /** The shell command this envelope is about, across the key names hosts use. */
372
+ function _commandOf(envelope: JsonObject): string | null {
373
+ const payload = _isObject(envelope["payload"]) ? envelope["payload"] : envelope;
374
+ const input = _isObject(payload["tool_input"]) ? payload["tool_input"] : payload;
375
+ for (const key of ["command", "cmd", "shell_command"]) {
376
+ const v = input[key];
377
+ if (typeof v === "string" && v !== "") return v;
378
+ }
379
+ return null;
380
+ }
381
+
382
+ /**
383
+ * Staged paths, or `null` when git cannot answer.
384
+ *
385
+ * `null` is not "nothing staged" — it is "no reading", and the caller must not
386
+ * turn an unreadable index into a silent clean bill. A hook that cannot measure
387
+ * says nothing rather than implying a measurement.
388
+ */
389
+ function _stagedPaths(consumer_root: string): string[] | null {
390
+ const r = spawnSync("git", ["diff", "--cached", "--name-only"], {
391
+ cwd: consumer_root,
392
+ encoding: "utf8",
393
+ });
394
+ if (r.status !== 0) return null;
395
+ const out = r.stdout
396
+ .split("\n")
397
+ .map((s) => s.trim())
398
+ .filter((s) => s !== "");
399
+ return out.length === 0 ? null : out;
400
+ }
401
+
327
402
  export function run(stdin_text: string, options: { consumer_root: string }): number {
328
403
  let envelope: JsonObject = {};
329
404
  if (stdin_text.trim()) {
@@ -355,6 +430,27 @@ export function run(stdin_text: string, options: { consumer_root: string }): num
355
430
  }
356
431
 
357
432
  const [tool, prompt] = extractDispatch(envelope);
433
+
434
+ // 2.4 — the one-commit test-authorship flag, checked BEFORE the dispatch
435
+ // branch and returning early. A commit envelope carries no evaluation prompt,
436
+ // so `decide` would classify it as not-an-evaluation and allow it silently;
437
+ // running both would mean the commit path fell through to a counter that is
438
+ // about evaluation dispatches and would be wrong to advance here.
439
+ //
440
+ // CHEAP TEST FIRST, and that ordering is the finding rather than a style
441
+ // preference: `_stagedPaths` spawns `git diff --cached`, and this concern is
442
+ // bound on `pre_tool_use`, so reading the index before the regex made every
443
+ // Read, Edit, Grep and Task dispatch in every session pay a subprocess. The
444
+ // regex answers "is this even a commit" for free.
445
+ const flag = sameSessionTestFlag(_commandOf(envelope));
446
+ if (flag !== null) {
447
+ const staged = _stagedPaths(options.consumer_root);
448
+ if (staged !== null && touchesTestAndCode(staged)) {
449
+ process.stdout.write(`${JSON.stringify({ decision: "warn", reason: flag })}\n`);
450
+ return EXIT_ALLOW;
451
+ }
452
+ }
453
+
358
454
  const decision = decide(tool, prompt, state.evaluations.length);
359
455
 
360
456
  if (decision.evaluations > state.evaluations.length) {