@event4u/agent-config 16.0.0 → 16.2.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 (382) 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 +289 -140
  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/commands/work.md +4 -0
  24. package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
  25. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
  26. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
  27. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +19 -23
  28. package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
  29. package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
  30. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
  31. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
  32. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  33. package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  34. package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
  35. package/dist/agent-src/rules/autonomous-execution.md +15 -8
  36. package/dist/agent-src/rules/delegation-policy.md +23 -45
  37. package/dist/agent-src/rules/design-fidelity.md +32 -37
  38. package/dist/agent-src/rules/evaluator-independence.md +43 -49
  39. package/dist/agent-src/rules/test-first.md +41 -0
  40. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  41. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  42. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  43. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  44. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  45. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  46. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  47. package/dist/agent-src/skills/design-review/SKILL.md +80 -4
  48. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  49. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -5
  50. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  51. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  52. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  53. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  54. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  55. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  56. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  57. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  58. package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
  59. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  60. package/dist/agent-src/templates/agent-settings.md +1 -1
  61. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  62. package/dist/agent-src/templates/roadmaps.md +218 -0
  63. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  64. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  65. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  66. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  67. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  68. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  69. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  70. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  71. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  72. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  73. package/dist/cli/python/knowledge_ingest.js +1 -1
  74. package/dist/cli/python/workspace_drive.js +1 -1
  75. package/dist/cli-delegate/check_update_banner.js +40 -2
  76. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  77. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
  78. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  79. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  80. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  81. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-7XGBX6ZH.js} +973 -555
  82. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  83. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-FP36SWJD.js} +587 -141
  84. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-IGD6ERX6.js} +62 -7
  85. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-JNMVLWLR.js} +99 -14
  86. package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
  87. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  88. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  89. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  90. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  91. package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
  92. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  93. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  94. package/dist/cli-delegate/cmd_conformance.js +985 -831
  95. package/dist/cli-delegate/cmd_doctor.js +7 -5
  96. package/dist/cli-delegate/cmd_explain.js +2 -2
  97. package/dist/cli-delegate/cmd_export.js +9 -7
  98. package/dist/cli-delegate/cmd_fleet.js +4 -2
  99. package/dist/cli-delegate/cmd_handoff.js +7 -7
  100. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  101. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  102. package/dist/cli-delegate/cmd_migrate.js +2 -2
  103. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  104. package/dist/cli-delegate/cmd_preflight.js +4 -2
  105. package/dist/cli-delegate/cmd_prune.js +10 -8
  106. package/dist/cli-delegate/cmd_route_audit.js +3 -3
  107. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  108. package/dist/cli-delegate/cmd_session_recycle.js +4 -4
  109. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  110. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  111. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  112. package/dist/cli-delegate/cmd_sync.js +10 -8
  113. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  114. package/dist/cli-delegate/cmd_update.js +1 -1
  115. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  116. package/dist/cli-delegate/cmd_validate.js +12 -10
  117. package/dist/cli-delegate/cmd_versions.js +1 -1
  118. package/dist/discovery/deprecation-report.md +1 -1
  119. package/dist/discovery/discovery-manifest.json +90 -43
  120. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  121. package/dist/discovery/discovery-manifest.summary.md +4 -4
  122. package/dist/discovery/dormancy-report.md +1 -1
  123. package/dist/discovery/orphan-report.md +1 -1
  124. package/dist/discovery/packs.json +10 -8
  125. package/dist/discovery/trust-report.md +3 -3
  126. package/dist/discovery/workspaces.json +8 -6
  127. package/dist/hooks/dispatch.js +7682 -2772
  128. package/dist/hooks/dispatch.sha256 +1 -0
  129. package/dist/install/claudeRuleRewrite.js +14 -3
  130. package/dist/install/conflict.js +60 -10
  131. package/dist/install/conflictTracker.js +218 -0
  132. package/dist/install/fsPrimitives.js +54 -0
  133. package/dist/install/install.mjs +2200 -1559
  134. package/dist/install/manifestFiles.js +259 -0
  135. package/dist/install/preflight.js +14 -2
  136. package/dist/install/preserve.js +157 -0
  137. package/dist/install/recordedOwnership.js +132 -0
  138. package/dist/install/txlog.js +11 -2
  139. package/dist/mcp/registry-manifest.json +3 -3
  140. package/dist/mcp/server.json +2 -2
  141. package/dist/mcp/server.mjs +3066 -1470
  142. package/dist/router.json +1 -1
  143. package/dist/scripts/_lib/agent_settings.js +3 -2
  144. package/dist/scripts/_lib/fs_atomic.js +167 -0
  145. package/dist/scripts/_lib/installed_tools.js +460 -0
  146. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  147. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  148. package/dist/server/routes/install.js +8 -0
  149. package/dist/server/routes/wizard.js +9 -3
  150. package/dist/server/schemas/settings.js +40 -5
  151. package/dist/shared/missionExecution.js +97 -0
  152. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  153. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  154. package/dist/ui/index.html +2 -2
  155. package/docs/MIGRATION.md +103 -0
  156. package/docs/contracts/settings-classes.md +28 -7
  157. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  158. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  159. package/docs/guidelines/design-fidelity-routing.md +29 -0
  160. package/package.json +4 -4
  161. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  162. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  163. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  164. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  165. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  166. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  167. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  168. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  169. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  170. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  171. package/src/config/agent-settings.template.yml +168 -27
  172. package/src/config/auto-rule-budget.json +16 -0
  173. package/src/config/ci-local-parity.yml +36 -5
  174. package/src/config/continuity-surface.json +23 -2
  175. package/src/config/gate-coverage.yml +377 -13
  176. package/src/config/gate-violation-baselines.json +10 -6
  177. package/src/config/hook-latency-budget.json +21 -0
  178. package/src/config/hook-token-budget.json +4 -2
  179. package/src/config/loop-surfaces.yaml +215 -0
  180. package/src/config/metric-registry.yml +114 -0
  181. package/src/config/placeholder-drift-budget.json +10 -0
  182. package/src/config/platform-anchor.json +3 -2
  183. package/src/config/release-gate-locality.yml +11 -0
  184. package/src/config/routing-coverage-seed.json +33 -5
  185. package/src/config/rule-enforcement-baseline.json +0 -1
  186. package/src/install/claudeRuleRewrite.ts +16 -2
  187. package/src/install/conflict.ts +65 -9
  188. package/src/install/conflictTracker.ts +299 -0
  189. package/src/install/fsPrimitives.ts +54 -0
  190. package/src/install/manifestFiles.ts +312 -0
  191. package/src/install/preflight.ts +16 -2
  192. package/src/install/preserve.ts +205 -0
  193. package/src/install/recordedOwnership.ts +149 -0
  194. package/src/install/txlog.ts +11 -2
  195. package/src/install/types.ts +19 -4
  196. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  197. package/src/scripts/_cli/cmd_doctor.ts +7 -20
  198. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  199. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  200. package/src/scripts/_cli/doctor_execution.ts +173 -0
  201. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  202. package/src/scripts/_lib/activation_payload.ts +247 -0
  203. package/src/scripts/_lib/agent_settings.ts +4 -2
  204. package/src/scripts/_lib/authority_path.ts +102 -0
  205. package/src/scripts/_lib/body_portable.ts +102 -0
  206. package/src/scripts/_lib/cascade_base.ts +94 -0
  207. package/src/scripts/_lib/cc_transcript.ts +132 -1
  208. package/src/scripts/_lib/changelog_eras.ts +7 -2
  209. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  210. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  211. package/src/scripts/_lib/council_transport.ts +129 -0
  212. package/src/scripts/_lib/delivery_ready.ts +112 -0
  213. package/src/scripts/_lib/forge_protection.ts +381 -0
  214. package/src/scripts/_lib/forge_reader.ts +558 -0
  215. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  216. package/src/scripts/_lib/host_env_write.ts +111 -0
  217. package/src/scripts/_lib/json_python_parity.ts +127 -0
  218. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  219. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  220. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  221. package/src/scripts/_lib/mission_record.ts +190 -0
  222. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  223. package/src/scripts/_lib/obligations.ts +513 -0
  224. package/src/scripts/_lib/orchestration_record.ts +11 -20
  225. package/src/scripts/_lib/probe_inputs.ts +271 -0
  226. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  227. package/src/scripts/_lib/release_highlights.ts +201 -11
  228. package/src/scripts/_lib/release_holds.ts +493 -0
  229. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  230. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  231. package/src/scripts/_lib/rides_along.ts +102 -0
  232. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  233. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  234. package/src/scripts/_lib/structural_hiding.ts +622 -0
  235. package/src/scripts/_lib/test_delta.ts +166 -0
  236. package/src/scripts/_lib/test_provenance.ts +171 -0
  237. package/src/scripts/_lib/turn_end_refusals.ts +525 -4
  238. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  239. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  240. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  241. package/src/scripts/_lib/verification_command.ts +324 -0
  242. package/src/scripts/_lib/verification_evidence.ts +647 -0
  243. package/src/scripts/_lib/verify_clause.ts +280 -0
  244. package/src/scripts/ai_council/config.ts +8 -11
  245. package/src/scripts/ai_council/fallback_config.ts +5 -3
  246. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  247. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  248. package/src/scripts/ask_block_census.ts +285 -15
  249. package/src/scripts/before_complete_hook.ts +340 -23
  250. package/src/scripts/bench_hook_latency.ts +412 -5
  251. package/src/scripts/cache_realization_report.ts +61 -1
  252. package/src/scripts/check_argument_hint.ts +287 -0
  253. package/src/scripts/check_claims.ts +125 -1
  254. package/src/scripts/check_enforcement_coverage.ts +38 -9
  255. package/src/scripts/check_enforcement_matrix.ts +866 -0
  256. package/src/scripts/check_finding_dispositions.ts +22 -3
  257. package/src/scripts/check_gate_reachability.ts +173 -10
  258. package/src/scripts/check_generator_sync.ts +1025 -0
  259. package/src/scripts/check_host_docs_digest.ts +786 -0
  260. package/src/scripts/check_host_format_column.ts +559 -0
  261. package/src/scripts/check_input_references.ts +228 -0
  262. package/src/scripts/check_inputs_parity.ts +235 -0
  263. package/src/scripts/check_kernel_edit_ratified.ts +47 -5
  264. package/src/scripts/check_kill_switch_table.ts +294 -0
  265. package/src/scripts/check_placeholder_drift.ts +221 -0
  266. package/src/scripts/check_preamble_payload_budget.ts +75 -2
  267. package/src/scripts/check_read_surface_coverage.ts +515 -0
  268. package/src/scripts/check_release_highlights.ts +33 -1
  269. package/src/scripts/check_release_holds.ts +568 -0
  270. package/src/scripts/check_routing_coverage.ts +378 -11
  271. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  272. package/src/scripts/check_static_parity.ts +17 -0
  273. package/src/scripts/check_test_delta.ts +294 -0
  274. package/src/scripts/check_test_weakening.ts +299 -0
  275. package/src/scripts/ci_settle.ts +145 -19
  276. package/src/scripts/closure_scan.ts +447 -0
  277. package/src/scripts/compile_hook_manifest.ts +102 -18
  278. package/src/scripts/council_record_shape.ts +209 -0
  279. package/src/scripts/external_sources_denylist.json +29 -4
  280. package/src/scripts/hook_manifest.json +1 -1
  281. package/src/scripts/hook_manifest.yaml +127 -11
  282. package/src/scripts/hooks/block_config_weakening.ts +229 -4
  283. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  284. package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
  285. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  286. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  287. package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
  288. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  289. package/src/scripts/hooks/concern_registry.ts +6 -0
  290. package/src/scripts/hooks/concern_timings.ts +94 -0
  291. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  292. package/src/scripts/hooks/design_pass_hook.ts +221 -5
  293. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  294. package/src/scripts/hooks/dispatch_hook.ts +25 -19
  295. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  296. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  297. package/src/scripts/hooks/evidence_independence.ts +97 -2
  298. package/src/scripts/hooks/exit_codes.ts +128 -0
  299. package/src/scripts/hooks/host_lowering.json +1 -0
  300. package/src/scripts/hooks/host_lowering.ts +128 -3
  301. package/src/scripts/hooks/host_lowering.yaml +262 -46
  302. package/src/scripts/hooks/host_semantics.ts +30 -2
  303. package/src/scripts/hooks/injection_budget.ts +9 -2
  304. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  305. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  306. package/src/scripts/hooks/memory_recall_hook.ts +539 -0
  307. package/src/scripts/hooks/obligation_settle_hook.ts +328 -0
  308. package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
  309. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  310. package/src/scripts/hooks/permission_gate.ts +1 -2
  311. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  312. package/src/scripts/hooks/replay_hook.ts +2 -1
  313. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  314. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  315. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  316. package/src/scripts/hooks/rule_inject_hook.ts +55 -4
  317. package/src/scripts/hooks/run_continuation_hook.ts +30 -35
  318. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  319. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  320. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  321. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  322. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  323. package/src/scripts/hooks/table_fingerprint.ts +49 -0
  324. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  325. package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
  326. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  327. package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
  328. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  329. package/src/scripts/hooks_doctor.ts +85 -0
  330. package/src/scripts/hooks_status.ts +19 -0
  331. package/src/scripts/install.ts +109 -210
  332. package/src/scripts/janitor.ts +7 -0
  333. package/src/scripts/lint_decision_classes.ts +464 -0
  334. package/src/scripts/lint_deny_text.ts +419 -0
  335. package/src/scripts/lint_exit_codes.ts +262 -0
  336. package/src/scripts/lint_hook_manifest.ts +66 -0
  337. package/src/scripts/lint_major_migration_sections.ts +450 -0
  338. package/src/scripts/lint_plan_risk_register.ts +47 -3
  339. package/src/scripts/lint_roadmap_blockers.ts +139 -12
  340. package/src/scripts/lint_roadmap_producers.ts +202 -0
  341. package/src/scripts/lint_skill_descriptions.ts +1 -1
  342. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  343. package/src/scripts/mcp_server/prompts.ts +142 -1
  344. package/src/scripts/measure_rule_budget.ts +101 -5
  345. package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
  346. package/src/scripts/measure_turn_end_gate.ts +193 -0
  347. package/src/scripts/memory_lookup.ts +1 -1
  348. package/src/scripts/new_roadmap.ts +37 -0
  349. package/src/scripts/orchestration_record.ts +0 -5
  350. package/src/scripts/prepack-check.mjs +53 -0
  351. package/src/scripts/release.ts +14 -98
  352. package/src/scripts/release_drill.ts +267 -1
  353. package/src/scripts/release_publication.ts +64 -1
  354. package/src/scripts/report_enforcement_drift.ts +566 -0
  355. package/src/scripts/report_host_injection_effect.ts +3 -11
  356. package/src/scripts/report_invocation_surface.ts +383 -0
  357. package/src/scripts/report_obligation_carriers.ts +699 -0
  358. package/src/scripts/report_obligation_writer_split.ts +236 -0
  359. package/src/scripts/report_skill_menu_census.ts +208 -0
  360. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  361. package/src/scripts/roadmap_verify_share.ts +213 -0
  362. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  363. package/src/scripts/schemas/command.schema.json +48 -0
  364. package/src/scripts/schemas/review-findings.schema.json +4 -0
  365. package/src/scripts/schemas/skill.schema.json +44 -0
  366. package/src/scripts/scope_growth.ts +219 -0
  367. package/src/scripts/self_review_gate.ts +359 -10
  368. package/src/scripts/semantic_parity_census.ts +314 -0
  369. package/src/scripts/session_eol_report.ts +54 -1
  370. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  371. package/src/scripts/trigger_eval_grandfather.json +0 -3
  372. package/src/scripts/trigger_eval_rotation.ts +122 -27
  373. package/src/scripts/ui_conformance_probe.ts +774 -0
  374. package/src/scripts/update_prices.ts +85 -3
  375. package/src/scripts/write_bundle_digest.mjs +74 -0
  376. package/src/server/schemas/settings.ts +62 -7
  377. package/src/shared/missionExecution.ts +126 -0
  378. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  379. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
  380. package/dist/ui/assets/index-D0jaCRPM.js +0 -29
  381. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
  382. /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
@@ -1,16 +1,25 @@
1
1
  import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
2
  import {
3
+ NO_RED_EVIDENCE_REASON,
3
4
  deriveSessionKey,
5
+ describeRecordFinding,
4
6
  foldRefusal,
7
+ foldShadow,
8
+ hasRedThenGreen,
5
9
  isCiPoll,
6
10
  isVacuousOutput,
11
+ isVerificationCommand,
7
12
  parseRecord,
13
+ parseShadowRecord,
8
14
  pendingCount,
9
15
  readInstallBoundary,
16
+ readRunEvidence,
17
+ readTurnRunState,
10
18
  sessionRefusalFile,
19
+ sessionShadowFile,
11
20
  statePathFor,
12
21
  validateAssumption
13
- } from "./chunk-QJWAUMJW.js";
22
+ } from "./chunk-YJ72UIAK.js";
14
23
  import {
15
24
  readHookStdin
16
25
  } from "./chunk-F7DQWYR5.js";
@@ -36,14 +45,15 @@ import {
36
45
  frontmatterPaths,
37
46
  proseEqual,
38
47
  splitFrontmatter
39
- } from "./chunk-55ACLDUE.js";
48
+ } from "./chunk-FP36SWJD.js";
40
49
  import "./chunk-FTAISGDQ.js";
41
50
  import "./chunk-M2DPGFZ4.js";
51
+ import "./chunk-ROYYTEZU.js";
42
52
  import "./chunk-BTWUYDGN.js";
43
53
  import {
44
54
  projectStoreSlug,
45
55
  resolveSessionRole
46
- } from "./chunk-GYC3VRYN.js";
56
+ } from "./chunk-JNMVLWLR.js";
47
57
  import {
48
58
  atomic_write_json,
49
59
  atomic_write_text,
@@ -57,18 +67,23 @@ import {
57
67
  import {
58
68
  artefact_roots
59
69
  } from "./chunk-NUHLI2RX.js";
70
+ import {
71
+ readRecentEntries
72
+ } from "./chunk-5R7HOLAF.js";
60
73
  import {
61
74
  detectToolPresence
62
75
  } from "./chunk-A4QJ6ENT.js";
63
76
  import {
64
77
  atomicAppendLine,
78
+ probeHostCapabilities
79
+ } from "./chunk-6KM6ORRY.js";
80
+ import "./chunk-T7L2QK3K.js";
81
+ import "./chunk-DMSZHXZS.js";
82
+ import {
65
83
  manifest_path,
66
- probeHostCapabilities,
67
84
  read_manifest
68
- } from "./chunk-2TAQ2K4V.js";
85
+ } from "./chunk-4E2W63DA.js";
69
86
  import "./chunk-MWRD5YOH.js";
70
- import "./chunk-T7L2QK3K.js";
71
- import "./chunk-DMSZHXZS.js";
72
87
  import {
73
88
  require_dist
74
89
  } from "./chunk-OY53J6II.js";
@@ -77,7 +92,7 @@ import {
77
92
  } from "./chunk-MCJYCQPF.js";
78
93
  import {
79
94
  load_agent_settings
80
- } from "./chunk-2XY7ILBT.js";
95
+ } from "./chunk-FC75PGJE.js";
81
96
  import "./chunk-NDJLNC6Q.js";
82
97
  import {
83
98
  __toESM
@@ -85,46 +100,13 @@ import {
85
100
 
86
101
  // src/scripts/_cli/cmd_conformance.ts
87
102
  var YAML = __toESM(require_dist(), 1);
88
- import * as fs15 from "node:fs";
103
+ import * as fs16 from "node:fs";
89
104
  import * as os7 from "node:os";
90
105
  import * as path15 from "node:path";
91
106
  import process5 from "node:process";
92
- import { spawnSync as spawnSync3 } from "node:child_process";
107
+ import { spawnSync as spawnSync4 } from "node:child_process";
93
108
  import { fileURLToPath as fileURLToPath14, pathToFileURL as pathToFileURL14 } from "node:url";
94
109
 
95
- // src/install/txlog.ts
96
- import { createReadStream, createWriteStream, existsSync, readFileSync, renameSync, statSync, unlinkSync } from "node:fs";
97
- var ROTATION_MAX_BYTES = 10 * 1024 * 1024;
98
- var ROTATION_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1e3;
99
- var RECOVERY_DEPTH_CAP = 500;
100
- function readRecentEntries(logPath) {
101
- if (!existsSync(logPath)) {
102
- return [];
103
- }
104
- const raw = readFileSync(logPath, "utf8");
105
- const lines = raw.split("\n").filter((l) => l.length > 0);
106
- const tail = lines.slice(-RECOVERY_DEPTH_CAP);
107
- const out = [];
108
- for (const line of tail) {
109
- const parsed = tryParseEntry(line);
110
- if (parsed !== null) {
111
- out.push(parsed);
112
- }
113
- }
114
- return out;
115
- }
116
- function tryParseEntry(line) {
117
- try {
118
- const obj = JSON.parse(line);
119
- if (typeof obj.ts !== "string" || typeof obj.kind !== "string" || typeof obj.path !== "string") {
120
- return null;
121
- }
122
- return obj;
123
- } catch {
124
- return null;
125
- }
126
- }
127
-
128
110
  // src/scripts/router_target_paths.mjs
129
111
  var ROUTE_TARGET_TEMPLATES = Object.freeze({
130
112
  skill: Object.freeze(["dist/agent-src/skills/{id}/SKILL.md"]),
@@ -151,7 +133,7 @@ function routeTargetPathsPosix(target) {
151
133
  }
152
134
 
153
135
  // src/scripts/report_conformance_funnel.ts
154
- import * as fs14 from "node:fs";
136
+ import * as fs15 from "node:fs";
155
137
  import * as os6 from "node:os";
156
138
  import * as path14 from "node:path";
157
139
  import process4 from "node:process";
@@ -159,7 +141,7 @@ import { fileURLToPath as fileURLToPath13, pathToFileURL as pathToFileURL13 } fr
159
141
 
160
142
  // src/scripts/conformance_scan.ts
161
143
  import { createHash as createHash3 } from "node:crypto";
162
- import fs10 from "node:fs";
144
+ import fs11 from "node:fs";
163
145
  import os2 from "node:os";
164
146
  import path10 from "node:path";
165
147
  import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
@@ -1945,7 +1927,7 @@ var COMMAND_OBJECTS = {
1945
1927
  };
1946
1928
 
1947
1929
  // src/scripts/hooks/turn_end_gate_hook.ts
1948
- import * as fs7 from "node:fs";
1930
+ import * as fs8 from "node:fs";
1949
1931
  import * as path7 from "node:path";
1950
1932
  import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
1951
1933
 
@@ -2325,162 +2307,6 @@ function detectDroppedDecision(assistantTurnTexts, closingFromPayload = false) {
2325
2307
  return null;
2326
2308
  }
2327
2309
 
2328
- // src/scripts/hooks/end_review_nudge_hook.ts
2329
- import { spawnSync as spawnSync2 } from "node:child_process";
2330
- import * as crypto from "node:crypto";
2331
- import * as fs5 from "node:fs";
2332
- import * as os from "node:os";
2333
- import * as path5 from "node:path";
2334
- import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
2335
-
2336
- // src/scripts/_lib/review_baseline.ts
2337
- import { spawnSync } from "node:child_process";
2338
- import * as fs4 from "node:fs";
2339
- import * as path4 from "node:path";
2340
- function currentHeadSha(cwd) {
2341
- try {
2342
- const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
2343
- if (r.status !== 0) return null;
2344
- const out = (r.stdout ?? "").trim();
2345
- return out === "" ? null : out;
2346
- } catch {
2347
- return null;
2348
- }
2349
- }
2350
- var BASELINE_STATE_REL = path4.join("agents", "runtime", "state", "review-baseline");
2351
- function baselineStateDir(workspaceRoot) {
2352
- return path4.join(workspaceRoot, BASELINE_STATE_REL);
2353
- }
2354
- function baselineStateFile(workspaceRoot, sessionKey) {
2355
- return path4.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
2356
- }
2357
- function parseBaseline(raw) {
2358
- let decoded;
2359
- try {
2360
- decoded = JSON.parse(raw);
2361
- } catch {
2362
- return null;
2363
- }
2364
- if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
2365
- const o = decoded;
2366
- const lines = o["baseline_lines"];
2367
- if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
2368
- const written = o["written_at"];
2369
- if (typeof written !== "string") return null;
2370
- const head = o["head_sha"];
2371
- const measure = o["measure"];
2372
- return {
2373
- head_sha: typeof head === "string" && head !== "" ? head : null,
2374
- baseline_lines: lines,
2375
- ...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
2376
- written_at: written
2377
- };
2378
- }
2379
- function readBaseline(workspaceRoot, sessionKey) {
2380
- const file = baselineStateFile(workspaceRoot, sessionKey);
2381
- let raw;
2382
- try {
2383
- raw = fs4.readFileSync(file, "utf-8");
2384
- } catch (exc) {
2385
- const code = exc?.code;
2386
- return code === "ENOENT" ? "absent" : "unreadable";
2387
- }
2388
- return parseBaseline(raw) ?? "unreadable";
2389
- }
2390
- function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
2391
- if (baseline === "absent" || baseline === "unreadable") {
2392
- return { applied: false, fallback: baseline, lines: measuredLines };
2393
- }
2394
- if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
2395
- return { applied: false, fallback: "mixed-measure", lines: measuredLines };
2396
- }
2397
- if (baseline.head_sha !== headSha()) {
2398
- return { applied: false, fallback: "head-moved", lines: measuredLines };
2399
- }
2400
- return {
2401
- applied: true,
2402
- baseline,
2403
- lines: Math.max(0, measuredLines - baseline.baseline_lines)
2404
- };
2405
- }
2406
-
2407
- // src/scripts/_lib/audit_field_provenance.ts
2408
- var MAX_APPLIED_IDS = 32;
2409
- function appliedIds(values) {
2410
- const out = [];
2411
- for (const raw of values ?? []) {
2412
- const v = String(raw).trim();
2413
- if (v === "" || out.includes(v)) continue;
2414
- out.push(v);
2415
- if (out.length >= MAX_APPLIED_IDS) break;
2416
- }
2417
- return out;
2418
- }
2419
-
2420
- // src/scripts/_lib/review_skipped_record.ts
2421
- var BASELINE_APPLICATIONS = [
2422
- "applied",
2423
- "absent",
2424
- "unreadable",
2425
- "head_moved",
2426
- "mixed_measure"
2427
- ];
2428
- var PRODUCER_PRIVACY_CLASS = "ids-only";
2429
- function isNonNegInt(n) {
2430
- return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
2431
- }
2432
- var HIGH_RISK_DIFF_LINES = 200;
2433
- function buildReviewSkippedLine(input) {
2434
- const errors = [];
2435
- if (!isNonNegInt(input.diff_lines)) {
2436
- errors.push("diff_lines must be a non-negative integer");
2437
- }
2438
- if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
2439
- errors.push('mutation_measure must be "exact" or "capped_approximation"');
2440
- }
2441
- if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
2442
- if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
2443
- if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
2444
- errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
2445
- }
2446
- if (errors.length) return { line: null, errors };
2447
- const line = {
2448
- schema_version: 1,
2449
- id: input.id,
2450
- ts: input.ts,
2451
- work_id: input.work_id ?? `review-skipped-${input.ts}`,
2452
- phase: "report",
2453
- outcome: "skipped",
2454
- confidence_band: "medium",
2455
- risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
2456
- memory: { asks: 0, hits: 0 },
2457
- verify: { claims: 0, first_try_passes: 0 },
2458
- // COMPUTED, never a producer constant.
2459
- rules_applied: appliedIds(input.rules_applied),
2460
- // MANDATORY on every audit line, same as the orchestration producer.
2461
- privacy_class: PRODUCER_PRIVACY_CLASS,
2462
- // `skills_applied` is OMITTED here, deliberately and not by oversight.
2463
- // audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
2464
- // ("recorded, none applied"), and this writer observes a review that
2465
- // did not happen -- it has no skill observation to offer in either
2466
- // direction. Emitting `[]` would assert "no skills applied" on evidence
2467
- // this producer never gathered, which is the exact fabrication the
2468
- // absent/empty split exists to make impossible.
2469
- persona: null,
2470
- input_kind: "prompt",
2471
- type: "note",
2472
- review_skipped: {
2473
- diff_lines: input.diff_lines,
2474
- mutation_measure: input.mutation_measure,
2475
- // Omitted, not defaulted, when the producer recorded nothing —
2476
- // audit-log-v1's absent-versus-present split is the same one
2477
- // `skills_applied` uses above, for the same reason.
2478
- ...input.baseline === void 0 ? {} : { baseline: input.baseline }
2479
- }
2480
- };
2481
- return { line, errors: [] };
2482
- }
2483
-
2484
2310
  // src/scripts/hooks/envelope.ts
2485
2311
  var ENVELOPE_KEYS = [
2486
2312
  "schema_version",
@@ -2535,340 +2361,35 @@ function unwrap(stdin_text, default_platform = "generic") {
2535
2361
  ];
2536
2362
  }
2537
2363
 
2538
- // src/scripts/hooks/end_review_nudge_hook.ts
2539
- var MUTATION_LINE_THRESHOLD = 50;
2540
- var UNTRACKED_FILE_CAP = 20;
2541
- var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
2542
- function isSafeTranscriptPath(rawPath, opts = {}) {
2543
- if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
2544
- const homeDir = opts.homeDir ?? os.homedir();
2545
- const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
2546
- try {
2547
- const realHome = fs5.realpathSync(homeDir);
2548
- const realPath = fs5.realpathSync(rawPath);
2549
- const relative3 = path5.relative(realHome, realPath);
2550
- if (relative3 === "" || relative3.startsWith("..") || path5.isAbsolute(relative3)) {
2551
- return false;
2552
- }
2553
- const { size } = fs5.statSync(realPath);
2554
- return size <= maxBytes;
2555
- } catch {
2556
- return false;
2557
- }
2558
- }
2559
- var EXIT_WARN2 = 2;
2560
- function parseNumstat(raw) {
2561
- const rows = [];
2562
- for (const line of raw.split("\n")) {
2563
- const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
2564
- if (!m) continue;
2565
- const addedStr = m[1];
2566
- const deletedStr = m[2];
2567
- rows.push({
2568
- added: addedStr === "-" ? 0 : Number(addedStr),
2569
- deleted: deletedStr === "-" ? 0 : Number(deletedStr),
2570
- path: m[3]
2571
- });
2572
- }
2573
- return rows;
2574
- }
2575
- var DOC_PATH_RE = /\.md$/i;
2576
- function isDocPath(p) {
2577
- return DOC_PATH_RE.test(p);
2578
- }
2579
- function nonDocMutatedLines(rows) {
2580
- return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
2364
+ // src/scripts/hooks/subagent_ledger_hook.ts
2365
+ import * as crypto from "node:crypto";
2366
+ import * as fs4 from "node:fs";
2367
+ import * as path4 from "node:path";
2368
+ import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
2369
+
2370
+ // src/scripts/_lib/subagent_response.ts
2371
+ var MAX_SUMMARY_CHARS = 2e3;
2372
+ var MAX_RESPONSE_LINE_CHARS = 240;
2373
+ var MAX_RESPONSE_ENTRIES = 40;
2374
+ var MAX_ARTIFACT_REF_CHARS = 200;
2375
+ var MAX_ENVELOPE_CHARS = 12e3;
2376
+ var CONFIDENCE = /* @__PURE__ */ new Set(["low", "medium", "high"]);
2377
+ var RESPONSE_REQUIRED_FIELDS = [
2378
+ "summary",
2379
+ "handoff",
2380
+ "confidence",
2381
+ "findings",
2382
+ "risks"
2383
+ ];
2384
+ function countContractFields(input) {
2385
+ if (input === null || typeof input !== "object") return 0;
2386
+ const r = input;
2387
+ return RESPONSE_REQUIRED_FIELDS.filter((k) => Object.hasOwn(r, k)).length;
2581
2388
  }
2582
- function gitNumstatRows(cwd) {
2583
- try {
2584
- const r = spawnSync2("git", ["diff", "--numstat", "HEAD"], {
2585
- cwd,
2586
- encoding: "utf8",
2587
- maxBuffer: 16 * 1024 * 1024
2588
- });
2589
- if (r.status !== 0) return [];
2590
- return parseNumstat(r.stdout ?? "");
2591
- } catch {
2592
- return [];
2593
- }
2594
- }
2595
- function untrackedNonDocFiles(cwd) {
2596
- try {
2597
- const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
2598
- cwd,
2599
- encoding: "utf8",
2600
- maxBuffer: 16 * 1024 * 1024
2601
- });
2602
- if (r.status !== 0) return [];
2603
- return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
2604
- } catch {
2605
- return [];
2606
- }
2607
- }
2608
- var BINARY_PROBE_BYTES = 8e3;
2609
- var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
2610
- function untrackedFileLineCount(cwd, relPath) {
2611
- try {
2612
- const abs = path5.resolve(cwd, relPath);
2613
- const size = fs5.statSync(abs).size;
2614
- if (size === 0) return 0;
2615
- if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
2616
- return MUTATION_LINE_THRESHOLD + 1;
2617
- }
2618
- const buf = fs5.readFileSync(abs);
2619
- if (buf.length === 0) return 0;
2620
- const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
2621
- if (probe.includes(0)) return 0;
2622
- let lines = 0;
2623
- for (const byte of buf) {
2624
- if (byte === 10) lines += 1;
2625
- }
2626
- if (buf[buf.length - 1] !== 10) lines += 1;
2627
- return lines;
2628
- } catch {
2629
- return 0;
2630
- }
2631
- }
2632
- function totalNonDocMutatedLinesWithMeasure(cwd) {
2633
- const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
2634
- const untracked = untrackedNonDocFiles(cwd);
2635
- if (untracked.length > UNTRACKED_FILE_CAP) {
2636
- return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
2637
- }
2638
- const untrackedLines = untracked.reduce(
2639
- (sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
2640
- 0
2641
- );
2642
- return { lines: trackedLines + untrackedLines, measure: "exact" };
2643
- }
2644
- function isObject(v) {
2645
- return typeof v === "object" && v !== null && !Array.isArray(v);
2646
- }
2647
- function str(v) {
2648
- return typeof v === "string" ? v : "";
2649
- }
2650
- var DISPATCH_TOOL_RE = /^(agent|task)$/i;
2651
- var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
2652
- var REVIEW_SHAPED_PROMPT_RE = /\b(code\s+review|neutral\s+review|adversarial|judge|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
2653
- var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
2654
- function scanTranscriptForReviewer(transcriptText) {
2655
- for (const raw of transcriptText.split("\n")) {
2656
- const line = raw.trim();
2657
- if (!line) continue;
2658
- let obj;
2659
- try {
2660
- obj = JSON.parse(line);
2661
- } catch {
2662
- continue;
2663
- }
2664
- if (!isObject(obj) || obj["type"] !== "assistant") continue;
2665
- const msg = obj["message"];
2666
- if (!isObject(msg)) continue;
2667
- const content = msg["content"];
2668
- if (!Array.isArray(content)) continue;
2669
- for (const blk of content) {
2670
- if (!isObject(blk) || blk["type"] !== "tool_use") continue;
2671
- const name = str(blk["name"]);
2672
- const input = blk["input"];
2673
- const inputObj = isObject(input) ? input : {};
2674
- if (DISPATCH_TOOL_RE.test(name)) {
2675
- const label = [
2676
- str(inputObj["description"]),
2677
- str(inputObj["subagent_type"])
2678
- ].filter(Boolean).join(" ");
2679
- if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
2680
- const prompt = str(inputObj["prompt"]);
2681
- if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
2682
- continue;
2683
- }
2684
- if (/^skill$/i.test(name)) {
2685
- const skillName = str(inputObj["skill"]) || str(inputObj["name"]) || str(inputObj["command"]);
2686
- if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
2687
- continue;
2688
- }
2689
- if (REVIEW_SKILL_NAME_RE.test(name)) return true;
2690
- }
2691
- }
2692
- return false;
2693
- }
2694
- function buildAdvisoryLine(diffLines) {
2695
- return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
2696
- }
2697
- function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
2698
- if (is_replay_mode()) return;
2699
- const ts = (/* @__PURE__ */ new Date()).toISOString();
2700
- const { line, errors } = buildReviewSkippedLine({
2701
- diff_lines: diffLines,
2702
- mutation_measure: mutationMeasure,
2703
- ts,
2704
- id: crypto.randomUUID(),
2705
- baseline
2706
- });
2707
- if (errors.length || line === null) return;
2708
- try {
2709
- const dir = path5.join(workspaceRoot, "agents", "runtime", "state", "audit");
2710
- fs5.mkdirSync(dir, { recursive: true });
2711
- const file = path5.join(dir, `${ts.slice(0, 7)}.jsonl`);
2712
- fs5.appendFileSync(file, `${JSON.stringify(line)}
2713
- `, "utf8");
2714
- } catch {
2715
- }
2716
- }
2717
- function sessionStateDir(workspaceRoot) {
2718
- return path5.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
2719
- }
2720
- function sessionStateFile(workspaceRoot, sessionKey) {
2721
- return path5.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
2722
- }
2723
- function deriveSessionKey2(envelope, payload) {
2724
- const raw = str(envelope["session_id"]) || str(payload["transcript_path"] ?? payload["transcriptPath"]);
2725
- const material = raw || "unknown-session";
2726
- return crypto.createHash("sha256").update(material).digest("hex");
2727
- }
2728
- function hasFiredThisSession(workspaceRoot, sessionKey) {
2729
- try {
2730
- return fs5.existsSync(sessionStateFile(workspaceRoot, sessionKey));
2731
- } catch {
2732
- return false;
2733
- }
2734
- }
2735
- function markFiredThisSession(workspaceRoot, sessionKey, ts) {
2736
- try {
2737
- atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
2738
- } catch {
2739
- }
2740
- }
2741
- function _read_stdin() {
2742
- return readHookStdin();
2743
- }
2744
- var FALLBACK_TO_TELEMETRY = {
2745
- absent: "absent",
2746
- unreadable: "unreadable",
2747
- "head-moved": "head_moved",
2748
- "mixed-measure": "mixed_measure"
2749
- };
2750
- function main4() {
2751
- const [envelope, payload] = unwrap(_read_stdin(), "claude");
2752
- const event = String(envelope["event"] ?? "");
2753
- if (event !== "" && event !== "stop") {
2754
- return 0;
2755
- }
2756
- const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
2757
- let measuredLines;
2758
- let mutationMeasure;
2759
- try {
2760
- const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
2761
- measuredLines = measured.lines;
2762
- mutationMeasure = measured.measure;
2763
- } catch {
2764
- return 0;
2765
- }
2766
- const sessionKey = deriveSessionKey2(envelope, payload);
2767
- const baselineOutcome = applyBaseline(
2768
- measuredLines,
2769
- readBaseline(workspace_root, sessionKey),
2770
- // A THUNK, not a value: on the `absent` and `unreadable` branches the
2771
- // result is discarded, and those two are the steady state on every host
2772
- // without this nudge and on every session before its first baseline
2773
- // write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
2774
- // each of those stops for nothing.
2775
- () => currentHeadSha(workspace_root),
2776
- mutationMeasure
2777
- );
2778
- const diffLines = baselineOutcome.lines;
2779
- const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
2780
- if (diffLines <= MUTATION_LINE_THRESHOLD) {
2781
- return 0;
2782
- }
2783
- if (hasFiredThisSession(workspace_root, sessionKey)) {
2784
- return 0;
2785
- }
2786
- const transcriptPath = str(
2787
- payload["transcript_path"] ?? payload["transcriptPath"]
2788
- ).trim();
2789
- let reviewerRan = false;
2790
- if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
2791
- try {
2792
- reviewerRan = scanTranscriptForReviewer(fs5.readFileSync(transcriptPath, "utf-8"));
2793
- } catch {
2794
- reviewerRan = false;
2795
- }
2796
- }
2797
- if (reviewerRan) {
2798
- return 0;
2799
- }
2800
- markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
2801
- try {
2802
- appendReviewSkippedTelemetry(
2803
- workspace_root,
2804
- diffLines,
2805
- mutationMeasure,
2806
- baselineApplication
2807
- );
2808
- } catch {
2809
- }
2810
- process.stdout.write(
2811
- `${JSON.stringify({
2812
- decision: "warn",
2813
- reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
2814
- additional_context: buildAdvisoryLine(diffLines)
2815
- })}
2816
- `
2817
- );
2818
- return EXIT_WARN2;
2819
- }
2820
- function _isCliEntry4() {
2821
- if (true) {
2822
- return false;
2823
- }
2824
- if (process.argv[1] === void 0) {
2825
- return false;
2826
- }
2827
- const argvUrl = pathToFileURL4(path5.resolve(process.argv[1])).href;
2828
- if (import.meta.url === argvUrl) {
2829
- return true;
2830
- }
2831
- try {
2832
- const here = fs5.realpathSync(fileURLToPath4(import.meta.url));
2833
- const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
2834
- return here === argv;
2835
- } catch {
2836
- return false;
2837
- }
2838
- }
2839
- if (_isCliEntry4()) {
2840
- process.exit(main4());
2841
- }
2842
-
2843
- // src/scripts/hooks/subagent_ledger_hook.ts
2844
- import * as crypto2 from "node:crypto";
2845
- import * as fs6 from "node:fs";
2846
- import * as path6 from "node:path";
2847
- import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
2848
-
2849
- // src/scripts/_lib/subagent_response.ts
2850
- var MAX_SUMMARY_CHARS = 2e3;
2851
- var MAX_RESPONSE_LINE_CHARS = 240;
2852
- var MAX_RESPONSE_ENTRIES = 40;
2853
- var MAX_ARTIFACT_REF_CHARS = 200;
2854
- var MAX_ENVELOPE_CHARS = 12e3;
2855
- var CONFIDENCE = /* @__PURE__ */ new Set(["low", "medium", "high"]);
2856
- var RESPONSE_REQUIRED_FIELDS = [
2857
- "summary",
2858
- "handoff",
2859
- "confidence",
2860
- "findings",
2861
- "risks"
2862
- ];
2863
- function countContractFields(input) {
2864
- if (input === null || typeof input !== "object") return 0;
2865
- const r = input;
2866
- return RESPONSE_REQUIRED_FIELDS.filter((k) => Object.hasOwn(r, k)).length;
2867
- }
2868
- function validateResponse(input) {
2869
- const errors = [];
2870
- if (input === null || typeof input !== "object") {
2871
- return { valid: false, errors: ["response is not an object"] };
2389
+ function validateResponse(input) {
2390
+ const errors = [];
2391
+ if (input === null || typeof input !== "object") {
2392
+ return { valid: false, errors: ["response is not an object"] };
2872
2393
  }
2873
2394
  const r = input;
2874
2395
  if (typeof r.summary !== "string") errors.push("summary missing or not a string");
@@ -2945,22 +2466,57 @@ function validateResponse(input) {
2945
2466
  return { valid: errors.length === 0, errors };
2946
2467
  }
2947
2468
 
2948
- // src/scripts/hooks/subagent_ledger_hook.ts
2469
+ // src/scripts/hooks/exit_codes.ts
2949
2470
  var EXIT_ALLOW3 = 0;
2950
- function isObject2(v) {
2951
- return typeof v === "object" && v !== null && !Array.isArray(v);
2952
- }
2953
- var LEDGER_DIR = path6.join("agents", "runtime", "state", "subagent-ledger");
2954
- var OPEN_SUBDIR = "open";
2955
- var OPEN_RECORD_TTL_MS = 24 * 60 * 60 * 1e3;
2956
- function refFor(agentId) {
2957
- return crypto2.createHash("sha256").update(agentId, "utf8").digest("hex").slice(0, 12);
2471
+ var EXIT_BLOCK = 1;
2472
+ var EXIT_WARN2 = 2;
2473
+ var EXIT_ERROR = 3;
2474
+ var EXIT_CODES = Object.freeze([
2475
+ Object.freeze({
2476
+ code: EXIT_ALLOW3,
2477
+ name: "allow",
2478
+ meaning: "no objection; the dispatcher passes the call through",
2479
+ owner: "every concern \u2014 the default verdict",
2480
+ authorizedBy: "nothing; allowing needs no authority"
2481
+ }),
2482
+ Object.freeze({
2483
+ code: EXIT_BLOCK,
2484
+ name: "block",
2485
+ meaning: "refuse the call; the dispatcher lowers this onto the host deny channel",
2486
+ owner: "a concern declared `severity: blocking` in hook_manifest.yaml",
2487
+ authorizedBy: "the manifest entry plus its row in the BLOCKING_ALLOWLIST of tests/hooks/concern_severity.test.ts \u2014 an advisory concern emitting this is downgraded to warn by dispatch_hook._is_advisory"
2488
+ }),
2489
+ Object.freeze({
2490
+ code: EXIT_WARN2,
2491
+ name: "warn",
2492
+ meaning: "advisory; the dispatcher exits 0 and surfaces the reason",
2493
+ owner: "any concern",
2494
+ authorizedBy: "nothing; warning is the ceiling for an advisory concern and is always available to a blocking one"
2495
+ }),
2496
+ Object.freeze({
2497
+ code: EXIT_ERROR,
2498
+ name: "error",
2499
+ meaning: "the concern crashed, timed out, or could not decide \u2014 any rc >= 3. The dispatcher resolves it by the concern`s declared severity, never by the number itself",
2500
+ owner: "no concern emits this on purpose; the runtime does",
2501
+ authorizedBy: "hook_manifest.yaml `fail_closed` and `severity` together \u2014 see docs/contracts/hook-architecture-v1.md \xA7 Exit-code semantics"
2502
+ })
2503
+ ]);
2504
+
2505
+ // src/scripts/hooks/subagent_ledger_hook.ts
2506
+ function isObject(v) {
2507
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2508
+ }
2509
+ var LEDGER_DIR = path4.join("agents", "runtime", "state", "subagent-ledger");
2510
+ var OPEN_SUBDIR = "open";
2511
+ var OPEN_RECORD_TTL_MS = 24 * 60 * 60 * 1e3;
2512
+ function refFor(agentId) {
2513
+ return crypto.createHash("sha256").update(agentId, "utf8").digest("hex").slice(0, 12);
2958
2514
  }
2959
2515
  function unwrapPayload(envelope) {
2960
2516
  const inner = envelope["payload"];
2961
- return isObject2(inner) ? inner : envelope;
2517
+ return isObject(inner) ? inner : envelope;
2962
2518
  }
2963
- function str2(payload, ...keys) {
2519
+ function str(payload, ...keys) {
2964
2520
  for (const k of keys) {
2965
2521
  const v = payload[k];
2966
2522
  if (typeof v === "string" && v) return v;
@@ -2968,11 +2524,11 @@ function str2(payload, ...keys) {
2968
2524
  return null;
2969
2525
  }
2970
2526
  function extractParentId(payload, ownId) {
2971
- const explicit = str2(payload, "parent_agent_id", "parentAgentId");
2527
+ const explicit = str(payload, "parent_agent_id", "parentAgentId");
2972
2528
  if (explicit) return explicit;
2973
2529
  const session = payload["session"];
2974
- if (isObject2(session)) {
2975
- const sessionAgent = str2(session, "agent_id", "agentId");
2530
+ if (isObject(session)) {
2531
+ const sessionAgent = str(session, "agent_id", "agentId");
2976
2532
  if (sessionAgent && sessionAgent !== ownId) return sessionAgent;
2977
2533
  }
2978
2534
  return null;
@@ -3040,14 +2596,14 @@ function _jsonObjectCandidates(message) {
3040
2596
  return out;
3041
2597
  }
3042
2598
  function openDir(root) {
3043
- return path6.join(root, LEDGER_DIR, OPEN_SUBDIR);
2599
+ return path4.join(root, LEDGER_DIR, OPEN_SUBDIR);
3044
2600
  }
3045
2601
  function openFile(root, ref) {
3046
- return path6.join(openDir(root), `${ref}.json`);
2602
+ return path4.join(openDir(root), `${ref}.json`);
3047
2603
  }
3048
2604
  var DEPTH_BASES = /* @__PURE__ */ new Set(["observed", "asserted-parent", "assumed-root"]);
3049
2605
  function _asOpenRecord(value, expectedRef) {
3050
- if (!isObject2(value)) return null;
2606
+ if (!isObject(value)) return null;
3051
2607
  const ref = value["ref"];
3052
2608
  if (typeof ref !== "string" || ref !== expectedRef) return null;
3053
2609
  if (typeof value["started_at"] !== "string") return null;
@@ -3070,7 +2626,7 @@ function readOpenRecords(root) {
3070
2626
  const out = /* @__PURE__ */ new Map();
3071
2627
  let names;
3072
2628
  try {
3073
- names = fs6.readdirSync(openDir(root));
2629
+ names = fs4.readdirSync(openDir(root));
3074
2630
  } catch {
3075
2631
  return out;
3076
2632
  }
@@ -3078,7 +2634,7 @@ function readOpenRecords(root) {
3078
2634
  if (!name.endsWith(".json")) continue;
3079
2635
  const expectedRef = name.slice(0, -".json".length);
3080
2636
  try {
3081
- const raw = fs6.readFileSync(path6.join(openDir(root), name), "utf8");
2637
+ const raw = fs4.readFileSync(path4.join(openDir(root), name), "utf8");
3082
2638
  const rec = _asOpenRecord(JSON.parse(raw), expectedRef);
3083
2639
  if (rec) out.set(rec.ref, rec);
3084
2640
  } catch {
@@ -3093,13 +2649,13 @@ function resolveDepth(parentRef, open) {
3093
2649
  return { depth: parent.depth + 1, depth_basis: "observed" };
3094
2650
  }
3095
2651
  function ledgerFileFor2(root, ts) {
3096
- return path6.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
2652
+ return path4.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
3097
2653
  }
3098
2654
  function appendLedgerLine(root, ts, line) {
3099
2655
  if (is_replay_mode()) return;
3100
2656
  const file = ledgerFileFor2(root, ts);
3101
- fs6.mkdirSync(path6.dirname(file), { recursive: true });
3102
- fs6.appendFileSync(file, `${JSON.stringify(line)}
2657
+ fs4.mkdirSync(path4.dirname(file), { recursive: true });
2658
+ fs4.appendFileSync(file, `${JSON.stringify(line)}
3103
2659
  `, "utf8");
3104
2660
  }
3105
2661
  var WALL_CLOCK_ARMS_MS = [
@@ -3113,7 +2669,7 @@ function armsExceeded(elapsedMs) {
3113
2669
  return WALL_CLOCK_ARMS_MS.filter((a) => elapsedMs >= a.ms).map((a) => a.label);
3114
2670
  }
3115
2671
  function openRecordStats(root, nowMs = Date.now()) {
3116
- const ledgerPresent = fs6.existsSync(path6.join(root, LEDGER_DIR));
2672
+ const ledgerPresent = fs4.existsSync(path4.join(root, LEDGER_DIR));
3117
2673
  const open = readOpenRecords(root);
3118
2674
  let maxDepth = 0;
3119
2675
  let counted = 0;
@@ -3141,13 +2697,13 @@ function writeOpenRecord(root, rec) {
3141
2697
  function removeOpenRecord(root, ref) {
3142
2698
  if (is_replay_mode()) return;
3143
2699
  try {
3144
- fs6.unlinkSync(openFile(root, ref));
2700
+ fs4.unlinkSync(openFile(root, ref));
3145
2701
  } catch {
3146
2702
  }
3147
2703
  }
3148
2704
  function countOpen(root) {
3149
2705
  try {
3150
- return fs6.readdirSync(openDir(root)).filter((n) => n.endsWith(".json")).length;
2706
+ return fs4.readdirSync(openDir(root)).filter((n) => n.endsWith(".json")).length;
3151
2707
  } catch {
3152
2708
  return 0;
3153
2709
  }
@@ -3188,12 +2744,12 @@ function appendUnidentified(root, event, nowIso, payload) {
3188
2744
  unidentified: true,
3189
2745
  // The agent TYPE is an id-shaped enum and may still be present; it is
3190
2746
  // the only thing salvageable from a payload with no id.
3191
- agent_type: str2(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
2747
+ agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3192
2748
  reason: "payload carried no agent id \u2014 nothing to correlate start with stop"
3193
2749
  });
3194
2750
  }
3195
2751
  function handleStart(root, payload, nowIso, sessionId) {
3196
- const agentId = str2(payload, "agent_id", "agentId");
2752
+ const agentId = str(payload, "agent_id", "agentId");
3197
2753
  if (agentId === null) {
3198
2754
  appendUnidentified(root, "subagent_start", nowIso, payload);
3199
2755
  return;
@@ -3204,7 +2760,7 @@ function handleStart(root, payload, nowIso, sessionId) {
3204
2760
  const { depth, depth_basis } = resolveDepth(parentRef, readOpenRecords(root));
3205
2761
  const rec = {
3206
2762
  ref,
3207
- agent_type: str2(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
2763
+ agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3208
2764
  started_at: nowIso,
3209
2765
  parent_ref: parentRef,
3210
2766
  depth,
@@ -3231,7 +2787,7 @@ function handleStart(root, payload, nowIso, sessionId) {
3231
2787
  });
3232
2788
  }
3233
2789
  function handleStop(root, payload, nowIso, sessionId) {
3234
- const agentId = str2(payload, "agent_id", "agentId");
2790
+ const agentId = str(payload, "agent_id", "agentId");
3235
2791
  if (agentId === null) {
3236
2792
  appendUnidentified(root, "subagent_stop", nowIso, payload);
3237
2793
  return;
@@ -3243,7 +2799,7 @@ function handleStop(root, payload, nowIso, sessionId) {
3243
2799
  const started = Date.parse(rec.started_at);
3244
2800
  if (Number.isFinite(started)) durationMs = Math.max(0, Date.parse(nowIso) - started);
3245
2801
  }
3246
- const parse2 = classifyEnvelope(str2(payload, "last_assistant_message", "lastAssistantMessage"));
2802
+ const parse2 = classifyEnvelope(str(payload, "last_assistant_message", "lastAssistantMessage"));
3247
2803
  removeOpenRecord(root, ref);
3248
2804
  appendLedgerLine(root, nowIso, {
3249
2805
  event: "subagent_stop",
@@ -3254,7 +2810,7 @@ function handleStop(root, payload, nowIso, sessionId) {
3254
2810
  // relabel a cross-session stop as belonging to the dispatching session
3255
2811
  // and hide exactly the mismatch this field exists to expose.
3256
2812
  session_id: sessionId,
3257
- agent_type: rec?.agent_type ?? str2(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
2813
+ agent_type: rec?.agent_type ?? str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3258
2814
  depth: rec?.depth ?? null,
3259
2815
  depth_basis: rec?.depth_basis ?? null,
3260
2816
  // `null` = the matching start was never seen. That is a finding about
@@ -3274,12 +2830,12 @@ function handleStop(root, payload, nowIso, sessionId) {
3274
2830
  }
3275
2831
  function processEnvelope(envelope, consumerRoot) {
3276
2832
  try {
3277
- if (!isObject2(envelope)) return EXIT_ALLOW3;
2833
+ if (!isObject(envelope)) return EXIT_ALLOW3;
3278
2834
  const event = envelope["event"];
3279
2835
  if (event !== "subagent_start" && event !== "subagent_stop") return EXIT_ALLOW3;
3280
2836
  const payload = unwrapPayload(envelope);
3281
2837
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
3282
- const sessionId = str2(payload, "session_id", "sessionId") ?? str2(envelope, "session_id", "sessionId");
2838
+ const sessionId = str(payload, "session_id", "sessionId") ?? str(envelope, "session_id", "sessionId");
3283
2839
  reapStaleOpenRecords(consumerRoot, nowIso);
3284
2840
  if (event === "subagent_start") handleStart(consumerRoot, payload, nowIso, sessionId);
3285
2841
  else handleStop(consumerRoot, payload, nowIso, sessionId);
@@ -3289,43 +2845,600 @@ function processEnvelope(envelope, consumerRoot) {
3289
2845
  return EXIT_ALLOW3;
3290
2846
  }
3291
2847
  function resolveConsumerRoot(envelope) {
3292
- if (isObject2(envelope)) {
3293
- const pr = str2(envelope, "workspace_root", "project_root");
2848
+ if (isObject(envelope)) {
2849
+ const pr = str(envelope, "workspace_root", "project_root");
3294
2850
  if (pr) return pr;
3295
2851
  const payload = unwrapPayload(envelope);
3296
- const cwd = str2(payload, "cwd");
2852
+ const cwd = str(payload, "cwd");
3297
2853
  if (cwd) return cwd;
3298
2854
  }
3299
- return process.cwd();
2855
+ return process.cwd();
2856
+ }
2857
+ function main4() {
2858
+ const raw = readHookStdin();
2859
+ let envelope = {};
2860
+ try {
2861
+ envelope = raw.trim() ? JSON.parse(raw) : {};
2862
+ } catch {
2863
+ return EXIT_ALLOW3;
2864
+ }
2865
+ return processEnvelope(envelope, resolveConsumerRoot(envelope));
2866
+ }
2867
+ function _isCliEntry4() {
2868
+ if (true) {
2869
+ return false;
2870
+ }
2871
+ if (process.argv[1] === void 0) return false;
2872
+ const argvUrl = pathToFileURL4(path4.resolve(process.argv[1])).href;
2873
+ if (import.meta.url === argvUrl) return true;
2874
+ try {
2875
+ return fs4.realpathSync(fileURLToPath4(import.meta.url)) === fs4.realpathSync(path4.resolve(process.argv[1]));
2876
+ } catch {
2877
+ return false;
2878
+ }
2879
+ }
2880
+ if (_isCliEntry4()) process.exit(main4());
2881
+
2882
+ // src/scripts/_lib/turn_end_transcript.ts
2883
+ import * as fs7 from "node:fs";
2884
+
2885
+ // src/scripts/hooks/end_review_nudge_hook.ts
2886
+ import { spawnSync as spawnSync2 } from "node:child_process";
2887
+ import * as crypto2 from "node:crypto";
2888
+ import * as fs6 from "node:fs";
2889
+ import * as os from "node:os";
2890
+ import * as path6 from "node:path";
2891
+ import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
2892
+
2893
+ // src/scripts/_lib/review_baseline.ts
2894
+ import { spawnSync } from "node:child_process";
2895
+ import * as fs5 from "node:fs";
2896
+ import * as path5 from "node:path";
2897
+ function currentHeadSha(cwd) {
2898
+ try {
2899
+ const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
2900
+ if (r.status !== 0) return null;
2901
+ const out = (r.stdout ?? "").trim();
2902
+ return out === "" ? null : out;
2903
+ } catch {
2904
+ return null;
2905
+ }
2906
+ }
2907
+ var BASELINE_STATE_REL = path5.join("agents", "runtime", "state", "review-baseline");
2908
+ function baselineStateDir(workspaceRoot) {
2909
+ return path5.join(workspaceRoot, BASELINE_STATE_REL);
2910
+ }
2911
+ function baselineStateFile(workspaceRoot, sessionKey) {
2912
+ return path5.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
2913
+ }
2914
+ function parseBaseline(raw) {
2915
+ let decoded;
2916
+ try {
2917
+ decoded = JSON.parse(raw);
2918
+ } catch {
2919
+ return null;
2920
+ }
2921
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
2922
+ const o = decoded;
2923
+ const lines = o["baseline_lines"];
2924
+ if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
2925
+ const written = o["written_at"];
2926
+ if (typeof written !== "string") return null;
2927
+ const head = o["head_sha"];
2928
+ const measure = o["measure"];
2929
+ return {
2930
+ head_sha: typeof head === "string" && head !== "" ? head : null,
2931
+ baseline_lines: lines,
2932
+ ...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
2933
+ written_at: written
2934
+ };
2935
+ }
2936
+ function readBaseline(workspaceRoot, sessionKey) {
2937
+ const file = baselineStateFile(workspaceRoot, sessionKey);
2938
+ let raw;
2939
+ try {
2940
+ raw = fs5.readFileSync(file, "utf-8");
2941
+ } catch (exc) {
2942
+ const code = exc?.code;
2943
+ return code === "ENOENT" ? "absent" : "unreadable";
2944
+ }
2945
+ return parseBaseline(raw) ?? "unreadable";
2946
+ }
2947
+ function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
2948
+ if (baseline === "absent" || baseline === "unreadable") {
2949
+ return { applied: false, fallback: baseline, lines: measuredLines };
2950
+ }
2951
+ if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
2952
+ return { applied: false, fallback: "mixed-measure", lines: measuredLines };
2953
+ }
2954
+ if (baseline.head_sha !== headSha()) {
2955
+ return { applied: false, fallback: "head-moved", lines: measuredLines };
2956
+ }
2957
+ return {
2958
+ applied: true,
2959
+ baseline,
2960
+ lines: Math.max(0, measuredLines - baseline.baseline_lines)
2961
+ };
2962
+ }
2963
+
2964
+ // src/scripts/_lib/audit_field_provenance.ts
2965
+ var MAX_APPLIED_IDS = 32;
2966
+ function appliedIds(values) {
2967
+ const out = [];
2968
+ for (const raw of values ?? []) {
2969
+ const v = String(raw).trim();
2970
+ if (v === "" || out.includes(v)) continue;
2971
+ out.push(v);
2972
+ if (out.length >= MAX_APPLIED_IDS) break;
2973
+ }
2974
+ return out;
2975
+ }
2976
+
2977
+ // src/scripts/_lib/review_skipped_record.ts
2978
+ var BASELINE_APPLICATIONS = [
2979
+ "applied",
2980
+ "absent",
2981
+ "unreadable",
2982
+ "head_moved",
2983
+ "mixed_measure"
2984
+ ];
2985
+ var PRODUCER_PRIVACY_CLASS = "ids-only";
2986
+ function isNonNegInt(n) {
2987
+ return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
2988
+ }
2989
+ var HIGH_RISK_DIFF_LINES = 200;
2990
+ function buildReviewSkippedLine(input) {
2991
+ const errors = [];
2992
+ if (!isNonNegInt(input.diff_lines)) {
2993
+ errors.push("diff_lines must be a non-negative integer");
2994
+ }
2995
+ if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
2996
+ errors.push('mutation_measure must be "exact" or "capped_approximation"');
2997
+ }
2998
+ if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
2999
+ if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
3000
+ if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
3001
+ errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
3002
+ }
3003
+ if (errors.length) return { line: null, errors };
3004
+ const line = {
3005
+ schema_version: 1,
3006
+ id: input.id,
3007
+ ts: input.ts,
3008
+ work_id: input.work_id ?? `review-skipped-${input.ts}`,
3009
+ phase: "report",
3010
+ outcome: "skipped",
3011
+ confidence_band: "medium",
3012
+ risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
3013
+ memory: { asks: 0, hits: 0 },
3014
+ verify: { claims: 0, first_try_passes: 0 },
3015
+ // COMPUTED, never a producer constant.
3016
+ rules_applied: appliedIds(input.rules_applied),
3017
+ // MANDATORY on every audit line, same as the orchestration producer.
3018
+ privacy_class: PRODUCER_PRIVACY_CLASS,
3019
+ // `skills_applied` is OMITTED here, deliberately and not by oversight.
3020
+ // audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
3021
+ // ("recorded, none applied"), and this writer observes a review that
3022
+ // did not happen -- it has no skill observation to offer in either
3023
+ // direction. Emitting `[]` would assert "no skills applied" on evidence
3024
+ // this producer never gathered, which is the exact fabrication the
3025
+ // absent/empty split exists to make impossible.
3026
+ persona: null,
3027
+ input_kind: "prompt",
3028
+ type: "note",
3029
+ review_skipped: {
3030
+ diff_lines: input.diff_lines,
3031
+ mutation_measure: input.mutation_measure,
3032
+ // Omitted, not defaulted, when the producer recorded nothing —
3033
+ // audit-log-v1's absent-versus-present split is the same one
3034
+ // `skills_applied` uses above, for the same reason.
3035
+ ...input.baseline === void 0 ? {} : { baseline: input.baseline }
3036
+ }
3037
+ };
3038
+ return { line, errors: [] };
3039
+ }
3040
+
3041
+ // src/scripts/hooks/end_review_nudge_hook.ts
3042
+ var MUTATION_LINE_THRESHOLD = 50;
3043
+ var UNTRACKED_FILE_CAP = 20;
3044
+ var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
3045
+ function isSafeTranscriptPath(rawPath, opts = {}) {
3046
+ if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
3047
+ const homeDir = opts.homeDir ?? os.homedir();
3048
+ const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
3049
+ try {
3050
+ const realHome = fs6.realpathSync(homeDir);
3051
+ const realPath = fs6.realpathSync(rawPath);
3052
+ const relative3 = path6.relative(realHome, realPath);
3053
+ if (relative3 === "" || relative3.startsWith("..") || path6.isAbsolute(relative3)) {
3054
+ return false;
3055
+ }
3056
+ const { size } = fs6.statSync(realPath);
3057
+ return size <= maxBytes;
3058
+ } catch {
3059
+ return false;
3060
+ }
3061
+ }
3062
+ function parseNumstat(raw) {
3063
+ const rows = [];
3064
+ for (const line of raw.split("\n")) {
3065
+ const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
3066
+ if (!m) continue;
3067
+ const addedStr = m[1];
3068
+ const deletedStr = m[2];
3069
+ rows.push({
3070
+ added: addedStr === "-" ? 0 : Number(addedStr),
3071
+ deleted: deletedStr === "-" ? 0 : Number(deletedStr),
3072
+ path: m[3]
3073
+ });
3074
+ }
3075
+ return rows;
3076
+ }
3077
+ var DOC_PATH_RE = /\.md$/i;
3078
+ function isDocPath(p) {
3079
+ return DOC_PATH_RE.test(p);
3080
+ }
3081
+ function nonDocMutatedLines(rows) {
3082
+ return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
3083
+ }
3084
+ function gitNumstatRows(cwd) {
3085
+ try {
3086
+ const r = spawnSync2("git", ["diff", "--numstat", "HEAD"], {
3087
+ cwd,
3088
+ encoding: "utf8",
3089
+ maxBuffer: 16 * 1024 * 1024
3090
+ });
3091
+ if (r.status !== 0) return [];
3092
+ return parseNumstat(r.stdout ?? "");
3093
+ } catch {
3094
+ return [];
3095
+ }
3096
+ }
3097
+ function untrackedNonDocFiles(cwd) {
3098
+ try {
3099
+ const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
3100
+ cwd,
3101
+ encoding: "utf8",
3102
+ maxBuffer: 16 * 1024 * 1024
3103
+ });
3104
+ if (r.status !== 0) return [];
3105
+ return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
3106
+ } catch {
3107
+ return [];
3108
+ }
3109
+ }
3110
+ var BINARY_PROBE_BYTES = 8e3;
3111
+ var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
3112
+ function untrackedFileLineCount(cwd, relPath) {
3113
+ try {
3114
+ const abs = path6.resolve(cwd, relPath);
3115
+ const size = fs6.statSync(abs).size;
3116
+ if (size === 0) return 0;
3117
+ if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
3118
+ return MUTATION_LINE_THRESHOLD + 1;
3119
+ }
3120
+ const buf = fs6.readFileSync(abs);
3121
+ if (buf.length === 0) return 0;
3122
+ const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
3123
+ if (probe.includes(0)) return 0;
3124
+ let lines = 0;
3125
+ for (const byte of buf) {
3126
+ if (byte === 10) lines += 1;
3127
+ }
3128
+ if (buf[buf.length - 1] !== 10) lines += 1;
3129
+ return lines;
3130
+ } catch {
3131
+ return 0;
3132
+ }
3133
+ }
3134
+ function totalNonDocMutatedLinesWithMeasure(cwd) {
3135
+ const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
3136
+ const untracked = untrackedNonDocFiles(cwd);
3137
+ if (untracked.length > UNTRACKED_FILE_CAP) {
3138
+ return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
3139
+ }
3140
+ const untrackedLines = untracked.reduce(
3141
+ (sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
3142
+ 0
3143
+ );
3144
+ return { lines: trackedLines + untrackedLines, measure: "exact" };
3145
+ }
3146
+ function isObject2(v) {
3147
+ return typeof v === "object" && v !== null && !Array.isArray(v);
3148
+ }
3149
+ function str2(v) {
3150
+ return typeof v === "string" ? v : "";
3151
+ }
3152
+ var DISPATCH_TOOL_RE = /^(agent|task)$/i;
3153
+ var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
3154
+ var REVIEW_SHAPED_PROMPT_RE = /\b(code\s+review|neutral\s+review|adversarial|judge|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
3155
+ var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
3156
+ function scanTranscriptForReviewer(transcriptText) {
3157
+ for (const raw of transcriptText.split("\n")) {
3158
+ const line = raw.trim();
3159
+ if (!line) continue;
3160
+ let obj;
3161
+ try {
3162
+ obj = JSON.parse(line);
3163
+ } catch {
3164
+ continue;
3165
+ }
3166
+ if (!isObject2(obj) || obj["type"] !== "assistant") continue;
3167
+ const msg = obj["message"];
3168
+ if (!isObject2(msg)) continue;
3169
+ const content = msg["content"];
3170
+ if (!Array.isArray(content)) continue;
3171
+ for (const blk of content) {
3172
+ if (!isObject2(blk) || blk["type"] !== "tool_use") continue;
3173
+ const name = str2(blk["name"]);
3174
+ const input = blk["input"];
3175
+ const inputObj = isObject2(input) ? input : {};
3176
+ if (DISPATCH_TOOL_RE.test(name)) {
3177
+ const label = [
3178
+ str2(inputObj["description"]),
3179
+ str2(inputObj["subagent_type"])
3180
+ ].filter(Boolean).join(" ");
3181
+ if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
3182
+ const prompt = str2(inputObj["prompt"]);
3183
+ if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
3184
+ continue;
3185
+ }
3186
+ if (/^skill$/i.test(name)) {
3187
+ const skillName = str2(inputObj["skill"]) || str2(inputObj["name"]) || str2(inputObj["command"]);
3188
+ if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
3189
+ continue;
3190
+ }
3191
+ if (REVIEW_SKILL_NAME_RE.test(name)) return true;
3192
+ }
3193
+ }
3194
+ return false;
3195
+ }
3196
+ function buildAdvisoryLine(diffLines) {
3197
+ return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
3198
+ }
3199
+ function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
3200
+ if (is_replay_mode()) return;
3201
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
3202
+ const { line, errors } = buildReviewSkippedLine({
3203
+ diff_lines: diffLines,
3204
+ mutation_measure: mutationMeasure,
3205
+ ts,
3206
+ id: crypto2.randomUUID(),
3207
+ baseline
3208
+ });
3209
+ if (errors.length || line === null) return;
3210
+ try {
3211
+ const dir = path6.join(workspaceRoot, "agents", "runtime", "state", "audit");
3212
+ fs6.mkdirSync(dir, { recursive: true });
3213
+ const file = path6.join(dir, `${ts.slice(0, 7)}.jsonl`);
3214
+ fs6.appendFileSync(file, `${JSON.stringify(line)}
3215
+ `, "utf8");
3216
+ } catch {
3217
+ }
3218
+ }
3219
+ function sessionStateDir(workspaceRoot) {
3220
+ return path6.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
3221
+ }
3222
+ function sessionStateFile(workspaceRoot, sessionKey) {
3223
+ return path6.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
3224
+ }
3225
+ function deriveSessionKey2(envelope, payload) {
3226
+ const raw = str2(envelope["session_id"]) || str2(payload["transcript_path"] ?? payload["transcriptPath"]);
3227
+ const material = raw || "unknown-session";
3228
+ return crypto2.createHash("sha256").update(material).digest("hex");
3229
+ }
3230
+ function hasFiredThisSession(workspaceRoot, sessionKey) {
3231
+ try {
3232
+ return fs6.existsSync(sessionStateFile(workspaceRoot, sessionKey));
3233
+ } catch {
3234
+ return false;
3235
+ }
3236
+ }
3237
+ function markFiredThisSession(workspaceRoot, sessionKey, ts) {
3238
+ try {
3239
+ atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
3240
+ } catch {
3241
+ }
3242
+ }
3243
+ function _read_stdin() {
3244
+ return readHookStdin();
3245
+ }
3246
+ var FALLBACK_TO_TELEMETRY = {
3247
+ absent: "absent",
3248
+ unreadable: "unreadable",
3249
+ "head-moved": "head_moved",
3250
+ "mixed-measure": "mixed_measure"
3251
+ };
3252
+ function main5() {
3253
+ const [envelope, payload] = unwrap(_read_stdin(), "claude");
3254
+ const event = String(envelope["event"] ?? "");
3255
+ if (event !== "" && event !== "stop") {
3256
+ return 0;
3257
+ }
3258
+ const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
3259
+ let measuredLines;
3260
+ let mutationMeasure;
3261
+ try {
3262
+ const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
3263
+ measuredLines = measured.lines;
3264
+ mutationMeasure = measured.measure;
3265
+ } catch {
3266
+ return 0;
3267
+ }
3268
+ const sessionKey = deriveSessionKey2(envelope, payload);
3269
+ const baselineOutcome = applyBaseline(
3270
+ measuredLines,
3271
+ readBaseline(workspace_root, sessionKey),
3272
+ // A THUNK, not a value: on the `absent` and `unreadable` branches the
3273
+ // result is discarded, and those two are the steady state on every host
3274
+ // without this nudge and on every session before its first baseline
3275
+ // write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
3276
+ // each of those stops for nothing.
3277
+ () => currentHeadSha(workspace_root),
3278
+ mutationMeasure
3279
+ );
3280
+ const diffLines = baselineOutcome.lines;
3281
+ const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
3282
+ if (diffLines <= MUTATION_LINE_THRESHOLD) {
3283
+ return 0;
3284
+ }
3285
+ if (hasFiredThisSession(workspace_root, sessionKey)) {
3286
+ return 0;
3287
+ }
3288
+ const transcriptPath = str2(
3289
+ payload["transcript_path"] ?? payload["transcriptPath"]
3290
+ ).trim();
3291
+ let reviewerRan = false;
3292
+ if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
3293
+ try {
3294
+ reviewerRan = scanTranscriptForReviewer(fs6.readFileSync(transcriptPath, "utf-8"));
3295
+ } catch {
3296
+ reviewerRan = false;
3297
+ }
3298
+ }
3299
+ if (reviewerRan) {
3300
+ return 0;
3301
+ }
3302
+ markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
3303
+ try {
3304
+ appendReviewSkippedTelemetry(
3305
+ workspace_root,
3306
+ diffLines,
3307
+ mutationMeasure,
3308
+ baselineApplication
3309
+ );
3310
+ } catch {
3311
+ }
3312
+ process.stdout.write(
3313
+ `${JSON.stringify({
3314
+ decision: "warn",
3315
+ reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
3316
+ additional_context: buildAdvisoryLine(diffLines)
3317
+ })}
3318
+ `
3319
+ );
3320
+ return EXIT_WARN2;
3321
+ }
3322
+ function _isCliEntry5() {
3323
+ if (true) {
3324
+ return false;
3325
+ }
3326
+ if (process.argv[1] === void 0) {
3327
+ return false;
3328
+ }
3329
+ const argvUrl = pathToFileURL5(path6.resolve(process.argv[1])).href;
3330
+ if (import.meta.url === argvUrl) {
3331
+ return true;
3332
+ }
3333
+ try {
3334
+ const here = fs6.realpathSync(fileURLToPath5(import.meta.url));
3335
+ const argv = fs6.realpathSync(path6.resolve(process.argv[1]));
3336
+ return here === argv;
3337
+ } catch {
3338
+ return false;
3339
+ }
3340
+ }
3341
+ if (_isCliEntry5()) {
3342
+ process.exit(main5());
3343
+ }
3344
+
3345
+ // src/scripts/_lib/turn_end_transcript.ts
3346
+ function readTranscriptTail(transcriptPath, opts = {}) {
3347
+ const empty = {
3348
+ lastAssistant: "",
3349
+ turnOrdinal: 0,
3350
+ toolCalls: [],
3351
+ assistantTurnTexts: []
3352
+ };
3353
+ if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
3354
+ let lines;
3355
+ try {
3356
+ if (opts.maxBytes !== void 0 && fs7.statSync(transcriptPath).size > opts.maxBytes) {
3357
+ return empty;
3358
+ }
3359
+ lines = fs7.readFileSync(transcriptPath, "utf-8").split("\n");
3360
+ } catch {
3361
+ return empty;
3362
+ }
3363
+ let lastAssistant = "";
3364
+ let turnOrdinal = 0;
3365
+ let toolCalls = [];
3366
+ let assistantTurnTexts = [];
3367
+ for (const rawLine of lines) {
3368
+ const line = rawLine.trim();
3369
+ if (!line) continue;
3370
+ let obj;
3371
+ try {
3372
+ obj = JSON.parse(line);
3373
+ } catch {
3374
+ continue;
3375
+ }
3376
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
3377
+ const entry = obj;
3378
+ if (entry["isSidechain"] === true) continue;
3379
+ const role = entry["type"];
3380
+ if (role !== "assistant" && role !== "user") continue;
3381
+ const msg = entry["message"];
3382
+ if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
3383
+ const content = msg["content"];
3384
+ if (role === "assistant") {
3385
+ toolCalls.push(...extractToolCalls(content));
3386
+ }
3387
+ const text = _messageText(content);
3388
+ if (text === null) continue;
3389
+ if (role === "assistant") {
3390
+ lastAssistant = text;
3391
+ assistantTurnTexts.push(text);
3392
+ } else if (!isSyntheticPrompt(text)) {
3393
+ turnOrdinal += 1;
3394
+ toolCalls = [];
3395
+ assistantTurnTexts = [];
3396
+ }
3397
+ }
3398
+ return {
3399
+ lastAssistant: lastAssistant.trim(),
3400
+ turnOrdinal,
3401
+ toolCalls,
3402
+ assistantTurnTexts
3403
+ };
3300
3404
  }
3301
- function main5() {
3302
- const raw = readHookStdin();
3303
- let envelope = {};
3304
- try {
3305
- envelope = raw.trim() ? JSON.parse(raw) : {};
3306
- } catch {
3307
- return EXIT_ALLOW3;
3405
+ function extractToolCalls(content) {
3406
+ if (!Array.isArray(content)) return [];
3407
+ const out = [];
3408
+ for (const blk of content) {
3409
+ if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
3410
+ const b = blk;
3411
+ if (b["type"] !== "tool_use") continue;
3412
+ const name = b["name"];
3413
+ if (typeof name !== "string") continue;
3414
+ const input = b["input"];
3415
+ const call = { name };
3416
+ if (typeof input === "object" && input !== null && !Array.isArray(input)) {
3417
+ const inp = input;
3418
+ const cmd = inp["command"];
3419
+ if (typeof cmd === "string") call.command = cmd;
3420
+ const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
3421
+ if (typeof p === "string") call.path = p;
3422
+ }
3423
+ out.push(call);
3308
3424
  }
3309
- return processEnvelope(envelope, resolveConsumerRoot(envelope));
3425
+ return out;
3310
3426
  }
3311
- function _isCliEntry5() {
3312
- if (true) {
3313
- return false;
3314
- }
3315
- if (process.argv[1] === void 0) return false;
3316
- const argvUrl = pathToFileURL5(path6.resolve(process.argv[1])).href;
3317
- if (import.meta.url === argvUrl) return true;
3318
- try {
3319
- return fs6.realpathSync(fileURLToPath5(import.meta.url)) === fs6.realpathSync(path6.resolve(process.argv[1]));
3320
- } catch {
3321
- return false;
3427
+ function _messageText(content) {
3428
+ if (typeof content === "string") return content;
3429
+ if (!Array.isArray(content)) return null;
3430
+ const parts = [];
3431
+ for (const blk of content) {
3432
+ if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
3433
+ const b = blk;
3434
+ if (b["type"] !== "text") continue;
3435
+ const t = b["text"];
3436
+ if (typeof t === "string") parts.push(t);
3322
3437
  }
3438
+ return parts.length > 0 ? parts.join("\n") : null;
3323
3439
  }
3324
- if (_isCliEntry5()) process.exit(main5());
3325
3440
 
3326
3441
  // src/scripts/hooks/turn_end_gate_hook.ts
3327
- var EXIT_BLOCK = 1;
3328
- var EXIT_ALLOW4 = 0;
3329
3442
  var TRANSCRIPT_READ_MAX_BYTES = 8 * 1024 * 1024;
3330
3443
  function stripFencedBlocks(text) {
3331
3444
  const out = [];
@@ -3430,7 +3543,7 @@ function detectPromissory(reply) {
3430
3543
  function readCiSettled(workspaceRoot, session_id) {
3431
3544
  if (!hasStableSessionId(session_id)) return { seen: false, settled: false };
3432
3545
  try {
3433
- const raw = fs7.readFileSync(path7.join(workspaceRoot, statePathFor(session_id)), "utf-8");
3546
+ const raw = fs8.readFileSync(path7.join(workspaceRoot, statePathFor(session_id)), "utf-8");
3434
3547
  const decoded = JSON.parse(raw);
3435
3548
  if (!owns_session_state(decoded, session_id)) return { seen: false, settled: false };
3436
3549
  if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
@@ -3466,7 +3579,7 @@ function detectCompletionClaim(reply, ci) {
3466
3579
  function readLanguagePin(workspaceRoot, session_id) {
3467
3580
  if (!hasStableSessionId(session_id)) return "und";
3468
3581
  try {
3469
- const raw = fs7.readFileSync(
3582
+ const raw = fs8.readFileSync(
3470
3583
  path7.join(workspaceRoot, statePathFor2(session_id)),
3471
3584
  "utf-8"
3472
3585
  );
@@ -3493,29 +3606,38 @@ function detectLanguage(reply, pinned) {
3493
3606
  };
3494
3607
  }
3495
3608
  var _EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
3496
- var _VERIFY_RE = /\b(test|tests|vitest|jest|pytest|phpunit|pest|tsc|eslint|ruff|mypy|pyright|clippy|phpstan|typecheck|lint[-_:a-z]*|build|ci|preflight|smoke[-_:a-z]*|check[-_:a-z]*|validate[-_:a-z]*)\b|\b(task|npm|pnpm|yarn|composer|cargo|go|make|php artisan|bun)\s+(run\s+)?\S*(test|check|lint|build|ci|typecheck)/i;
3497
- function isVerificationCommand(command) {
3498
- return _VERIFY_RE.test(command);
3499
- }
3500
- function detectUnverifiedEdit(toolCalls) {
3609
+ function detectUnverifiedEdit(toolCalls, runState = null) {
3501
3610
  let lastEdit = -1;
3502
3611
  for (let i = 0; i < toolCalls.length; i += 1) {
3503
3612
  if (_EDIT_TOOLS.has(toolCalls[i].name)) lastEdit = i;
3504
3613
  }
3505
3614
  if (lastEdit === -1) return null;
3615
+ const edited = toolCalls[lastEdit].path ?? toolCalls[lastEdit].name;
3616
+ if (runState !== null) {
3617
+ const reading = readRunEvidence(runState);
3618
+ if (reading.passed) return null;
3619
+ if (!reading.instrumentGap) {
3620
+ return {
3621
+ detector: "verification",
3622
+ evidence: edited,
3623
+ reason: describeRecordFinding(reading.failed, reading.reasons),
3624
+ mode: "record"
3625
+ };
3626
+ }
3627
+ }
3506
3628
  for (let i = lastEdit + 1; i < toolCalls.length; i += 1) {
3507
3629
  const c = toolCalls[i];
3508
3630
  if (c.name === "Bash" && c.command !== void 0 && isVerificationCommand(c.command)) {
3509
3631
  return null;
3510
3632
  }
3511
3633
  }
3512
- const edited = toolCalls[lastEdit].path;
3513
3634
  return {
3514
3635
  detector: "verification",
3515
3636
  // The path, never the diff: the evidence span is quoted into a refusal,
3516
3637
  // and `ToolCall` is shaped so a file body cannot reach it.
3517
- evidence: edited ?? toolCalls[lastEdit].name,
3518
- reason: "this turn changed a file and then ran no verification command \u2014 no test, type-check, lint or build call follows the last edit (verify-before-complete: a claim without a fresh run is unverified)"
3638
+ evidence: edited,
3639
+ reason: "this turn changed a file and then ran no verification command \u2014 no test, type-check, lint or build call follows the last edit (verify-before-complete: a claim without a fresh run is unverified)",
3640
+ mode: "transcript"
3519
3641
  };
3520
3642
  }
3521
3643
  var _SOURCE_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|vue|svelte|php|py|rb|go|rs|java|kt|swift|cs|scala|ex|exs)$/i;
@@ -3527,18 +3649,37 @@ function _isTestPath(p) {
3527
3649
  function _isProductionSource(p) {
3528
3650
  return _SOURCE_RE.test(p) && !_isTestPath(p);
3529
3651
  }
3530
- function detectUntestedChange(reply, toolCalls) {
3652
+ function detectUntestedChange(reply, toolCalls, runState = null) {
3531
3653
  const edited = toolCalls.filter((c) => _EDIT_TOOLS.has(c.name) && c.path !== void 0).map((c) => c.path);
3532
3654
  const source = edited.filter(_isProductionSource);
3533
3655
  if (source.length === 0) return null;
3534
- if (edited.some(_isTestPath)) return null;
3656
+ const wroteNewTest = toolCalls.some(
3657
+ (c) => c.name === "Write" && c.path !== void 0 && _isTestPath(c.path)
3658
+ );
3659
+ let noRedEvidence = false;
3660
+ if (edited.some(_isTestPath)) {
3661
+ if (!wroteNewTest) return null;
3662
+ if (runState === null) return null;
3663
+ if (hasRedThenGreen(runState)) return null;
3664
+ if (readRunEvidence(runState).instrumentGap) return null;
3665
+ noRedEvidence = true;
3666
+ }
3535
3667
  const prose = visibleProse(reply);
3536
3668
  const m = _COMPLETION_RE.exec(prose);
3537
3669
  if (!m) return null;
3538
3670
  if (_NEGATED_CLAIM_RE.test(_lineAround(prose, m.index))) return null;
3539
3671
  const shown = source.slice(0, 3).join(", ");
3672
+ if (noRedEvidence) {
3673
+ return {
3674
+ detector: "untested",
3675
+ evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
3676
+ mode: "record",
3677
+ reason: NO_RED_EVIDENCE_REASON
3678
+ };
3679
+ }
3540
3680
  return {
3541
3681
  detector: "untested",
3682
+ mode: "transcript",
3542
3683
  evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
3543
3684
  reason: "a completion claim over production code this turn changed, with NO test file touched anywhere in the turn \u2014 running a linter is not evidence the change works. Write the test that would have caught the failure, and enumerate the states the change actually has (every view, every CRUD path, the empty and error states) rather than the happy one. If a test genuinely does not belong here, say which file carries the coverage instead"
3544
3685
  };
@@ -3548,7 +3689,7 @@ function sessionStateFile2(workspaceRoot, sessionKey) {
3548
3689
  }
3549
3690
  function alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal) {
3550
3691
  try {
3551
- const raw = fs7.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
3692
+ const raw = fs8.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
3552
3693
  const decoded = JSON.parse(raw);
3553
3694
  if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
3554
3695
  return decoded["refused_turn"] === turnOrdinal;
@@ -3562,7 +3703,7 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
3562
3703
  const file = sessionStateFile2(workspaceRoot, sessionKey);
3563
3704
  let prev = null;
3564
3705
  try {
3565
- prev = parseRecord(fs7.readFileSync(file, "utf-8"));
3706
+ prev = parseRecord(fs8.readFileSync(file, "utf-8"));
3566
3707
  } catch {
3567
3708
  }
3568
3709
  let version;
@@ -3585,112 +3726,10 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
3585
3726
  } catch {
3586
3727
  }
3587
3728
  }
3588
- function readTranscriptTail(transcriptPath, opts = {}) {
3589
- const empty = {
3590
- lastAssistant: "",
3591
- turnOrdinal: 0,
3592
- toolCalls: [],
3593
- assistantTurnTexts: []
3594
- };
3595
- if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
3596
- let lines;
3597
- try {
3598
- if (opts.maxBytes !== void 0 && fs7.statSync(transcriptPath).size > opts.maxBytes) {
3599
- return empty;
3600
- }
3601
- lines = fs7.readFileSync(transcriptPath, "utf-8").split("\n");
3602
- } catch {
3603
- return empty;
3604
- }
3605
- let lastAssistant = "";
3606
- let turnOrdinal = 0;
3607
- let toolCalls = [];
3608
- let assistantTurnTexts = [];
3609
- for (const rawLine of lines) {
3610
- const line = rawLine.trim();
3611
- if (!line) continue;
3612
- let obj;
3613
- try {
3614
- obj = JSON.parse(line);
3615
- } catch {
3616
- continue;
3617
- }
3618
- if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
3619
- const entry = obj;
3620
- if (entry["isSidechain"] === true) continue;
3621
- const role = entry["type"];
3622
- if (role !== "assistant" && role !== "user") continue;
3623
- const msg = entry["message"];
3624
- if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
3625
- const content = msg["content"];
3626
- if (role === "assistant") {
3627
- toolCalls.push(...extractToolCalls(content));
3628
- }
3629
- const text = _messageText(content);
3630
- if (text === null) continue;
3631
- if (role === "assistant") {
3632
- lastAssistant = text;
3633
- assistantTurnTexts.push(text);
3634
- } else if (!isSyntheticPrompt(text)) {
3635
- turnOrdinal += 1;
3636
- toolCalls = [];
3637
- assistantTurnTexts = [];
3638
- }
3639
- }
3640
- return {
3641
- lastAssistant: lastAssistant.trim(),
3642
- turnOrdinal,
3643
- toolCalls,
3644
- assistantTurnTexts
3645
- };
3646
- }
3647
- function extractToolCalls(content) {
3648
- if (!Array.isArray(content)) return [];
3649
- const out = [];
3650
- for (const blk of content) {
3651
- if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
3652
- const b = blk;
3653
- if (b["type"] !== "tool_use") continue;
3654
- const name = b["name"];
3655
- if (typeof name !== "string") continue;
3656
- const input = b["input"];
3657
- const call = { name };
3658
- if (typeof input === "object" && input !== null && !Array.isArray(input)) {
3659
- const inp = input;
3660
- const cmd = inp["command"];
3661
- if (typeof cmd === "string") call.command = cmd;
3662
- const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
3663
- if (typeof p === "string") call.path = p;
3664
- }
3665
- out.push(call);
3666
- }
3667
- return out;
3668
- }
3669
- function _messageText(content) {
3670
- if (typeof content === "string") return content;
3671
- if (!Array.isArray(content)) return null;
3672
- const parts = [];
3673
- for (const blk of content) {
3674
- if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
3675
- const b = blk;
3676
- if (b["type"] !== "text") continue;
3677
- const t = b["text"];
3678
- if (typeof t === "string") parts.push(t);
3679
- }
3680
- return parts.length > 0 ? parts.join("\n") : null;
3681
- }
3682
3729
  function str3(v) {
3683
3730
  return typeof v === "string" ? v : "";
3684
3731
  }
3685
- function main6() {
3686
- let envelope;
3687
- let payload;
3688
- try {
3689
- [envelope, payload] = unwrap(readHookStdin(), "claude");
3690
- } catch {
3691
- return EXIT_ALLOW4;
3692
- }
3693
- if (payload["stop_hook_active"] === true) return EXIT_ALLOW4;
3732
+ function assembleDetectorInputs(envelope, payload) {
3694
3733
  const workspaceRoot = str3(envelope["workspace_root"]) || process.cwd();
3695
3734
  let dispatchOpen = false;
3696
3735
  try {
@@ -3704,18 +3743,32 @@ function main6() {
3704
3743
  transcriptPath,
3705
3744
  { maxBytes: TRANSCRIPT_READ_MAX_BYTES }
3706
3745
  );
3707
- if (!lastAssistant) return EXIT_ALLOW4;
3746
+ if (!lastAssistant) return null;
3708
3747
  const payloadClosing = str3(
3709
3748
  payload["last_assistant_message"] ?? payload["lastAssistantMessage"]
3710
3749
  );
3711
3750
  const closingFromPayload = payloadClosing !== "" && !assistantTurnTexts.includes(payloadClosing);
3712
3751
  const turnTexts = closingFromPayload ? [...assistantTurnTexts, payloadClosing] : assistantTurnTexts;
3713
- const promptId = str3(
3714
- payload["prompt_id"] ?? payload["promptId"]
3715
- );
3752
+ const promptId = str3(payload["prompt_id"] ?? payload["promptId"]);
3716
3753
  const rawSessionId = str3(envelope["session_id"]) || "";
3717
3754
  const sessionKey = deriveSessionKey(rawSessionId || "unknown-session");
3718
- if (alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal)) return EXIT_ALLOW4;
3755
+ const runState = readTurnRunState(workspaceRoot, rawSessionId);
3756
+ return {
3757
+ workspaceRoot,
3758
+ rawSessionId,
3759
+ sessionKey,
3760
+ turnOrdinal,
3761
+ promptId,
3762
+ lastAssistant,
3763
+ toolCalls,
3764
+ turnTexts,
3765
+ closingFromPayload,
3766
+ dispatchOpen,
3767
+ runState
3768
+ };
3769
+ }
3770
+ function runDetectors(inputs) {
3771
+ const { dispatchOpen, lastAssistant, workspaceRoot, rawSessionId, toolCalls, runState } = inputs;
3719
3772
  const findings = [];
3720
3773
  for (const f of [
3721
3774
  // A and D are the completion-adjacent pair a pending dispatch excuses
@@ -3723,17 +3776,14 @@ function main6() {
3723
3776
  // excused by anything about a dispatch and run unchanged.
3724
3777
  dispatchOpen ? null : detectPromissory(lastAssistant),
3725
3778
  detectLanguage(lastAssistant, readLanguagePin(workspaceRoot, rawSessionId)),
3726
- detectUnverifiedEdit(toolCalls),
3727
- // Round 7 § Phase 1 — detector D. It is NOT unconditional, and this
3728
- // comment said it was while sitting one line above the `dispatchOpen`
3729
- // ternary that conditions it: A and D are both excused by an open
3730
- // dispatch, per the note at the top of this loop. Corrected 2026-08-18.
3731
- // It shipped with its own settings flag one commit earlier
3732
- // and that flag is gone: `hooks.turn_end_gate.*` was deleted on
3733
- // 2026-08-12 because a default-off safety gate is an absent one. Its
3734
- // gating is where the comment above says gating belongs — inside the
3735
- // detector: no CI observed, or a settled read, or no completion claim
3736
- // ⇒ no finding.
3779
+ detectUnverifiedEdit(toolCalls, runState),
3780
+ // Round 7 § Phase 1 — detector D. It is NOT unconditional: A and D are
3781
+ // both excused by an open dispatch, per the note above. It shipped with
3782
+ // its own settings flag one commit earlier and that flag is gone:
3783
+ // `hooks.turn_end_gate.*` was deleted on 2026-08-12 because a
3784
+ // default-off safety gate is an absent one. Its gating is where the
3785
+ // note above says gating belongs — inside the detector: no CI observed,
3786
+ // or a settled read, or no completion claim ⇒ no finding.
3737
3787
  dispatchOpen ? null : detectCompletionClaim(lastAssistant, readCiSettled(workspaceRoot, rawSessionId)),
3738
3788
  // Detector E is UNCONDITIONAL, and that is the opposite choice from A
3739
3789
  // and D one line above rather than an oversight. An open dispatch
@@ -3743,24 +3793,71 @@ function main6() {
3743
3793
  // that produced the measured failure — IS the continuation that drops
3744
3794
  // the decision, so narrowing E the same way would silence it in exactly
3745
3795
  // the case it exists for.
3746
- detectDroppedDecision(turnTexts, closingFromPayload),
3796
+ detectDroppedDecision(inputs.turnTexts, inputs.closingFromPayload),
3747
3797
  // Detector F takes the OTHER side of that same question, and the two
3748
3798
  // sitting adjacent is the clearest place to say why. E fires on a
3749
3799
  // question already asked, which an open dispatch cannot excuse. F fires
3750
3800
  // on a completion CLAIM, and a turn waiting on a subagent has not
3751
3801
  // finished — so its closing is not the claim F is about. Same slot,
3752
3802
  // opposite trigger, opposite treatment of `dispatchOpen`.
3753
- dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls)
3803
+ dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls, runState)
3754
3804
  ]) {
3755
3805
  if (f) findings.push(f);
3756
3806
  }
3757
- if (findings.length === 0) return EXIT_ALLOW4;
3807
+ return findings;
3808
+ }
3809
+ function recordShadow(inputs, layer) {
3810
+ if (is_replay_mode()) return;
3811
+ try {
3812
+ const findings = runDetectors(inputs);
3813
+ const file = sessionShadowFile(inputs.workspaceRoot, inputs.sessionKey);
3814
+ let prev = null;
3815
+ try {
3816
+ prev = parseShadowRecord(fs8.readFileSync(file, "utf-8"));
3817
+ } catch {
3818
+ }
3819
+ atomic_write_json(
3820
+ file,
3821
+ foldShadow(prev, {
3822
+ detectors: findings.map((f) => f.detector),
3823
+ turnOrdinal: inputs.turnOrdinal,
3824
+ at: (/* @__PURE__ */ new Date()).toISOString(),
3825
+ layer
3826
+ })
3827
+ );
3828
+ } catch {
3829
+ }
3830
+ }
3831
+ function main6() {
3832
+ let envelope;
3833
+ let payload;
3834
+ try {
3835
+ [envelope, payload] = unwrap(readHookStdin(), "claude");
3836
+ } catch {
3837
+ return EXIT_ALLOW3;
3838
+ }
3839
+ if (payload["stop_hook_active"] === true) {
3840
+ try {
3841
+ const retryInputs = assembleDetectorInputs(envelope, payload);
3842
+ if (retryInputs !== null) recordShadow(retryInputs, "stop_hook_active");
3843
+ } catch {
3844
+ }
3845
+ return EXIT_ALLOW3;
3846
+ }
3847
+ const inputs = assembleDetectorInputs(envelope, payload);
3848
+ if (inputs === null) return EXIT_ALLOW3;
3849
+ if (alreadyRefusedTurn(inputs.workspaceRoot, inputs.sessionKey, inputs.turnOrdinal)) {
3850
+ recordShadow(inputs, "refused_turn");
3851
+ return EXIT_ALLOW3;
3852
+ }
3853
+ const findings = runDetectors(inputs);
3854
+ if (findings.length === 0) return EXIT_ALLOW3;
3758
3855
  markRefusedTurn(
3759
- workspaceRoot,
3760
- sessionKey,
3761
- turnOrdinal,
3856
+ inputs.workspaceRoot,
3857
+ inputs.sessionKey,
3858
+ inputs.turnOrdinal,
3762
3859
  findings.map((f) => f.detector),
3763
- promptId
3860
+ inputs.promptId
3764
3861
  );
3765
3862
  const lines = findings.map(
3766
3863
  (f) => ` \xB7 ${f.detector}: ${f.reason}
@@ -3787,8 +3884,8 @@ function _isCliEntry6() {
3787
3884
  return true;
3788
3885
  }
3789
3886
  try {
3790
- const here = fs7.realpathSync(fileURLToPath6(import.meta.url));
3791
- const argv = fs7.realpathSync(path7.resolve(process.argv[1]));
3887
+ const here = fs8.realpathSync(fileURLToPath6(import.meta.url));
3888
+ const argv = fs8.realpathSync(path7.resolve(process.argv[1]));
3792
3889
  return here === argv;
3793
3890
  } catch {
3794
3891
  return false;
@@ -3799,12 +3896,28 @@ if (_isCliEntry6()) {
3799
3896
  }
3800
3897
 
3801
3898
  // src/scripts/hooks/evidence_independence.ts
3899
+ import { spawnSync as spawnSync3 } from "node:child_process";
3802
3900
  import crypto3 from "node:crypto";
3803
- import fs8 from "node:fs";
3901
+ import fs9 from "node:fs";
3804
3902
  import path8 from "node:path";
3805
3903
  import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
3806
- var EXIT_ALLOW5 = 0;
3807
- var EXIT_BLOCK2 = 1;
3904
+
3905
+ // src/scripts/_lib/test_delta.ts
3906
+ function isCodePath(p) {
3907
+ const f = p.replace(/\\/g, "/");
3908
+ if (isTestPath(f)) return false;
3909
+ if (!/\.(ts|tsx|js|mjs|cjs)$/.test(f)) return false;
3910
+ if (f.endsWith(".d.ts")) return false;
3911
+ if (/^(dist|\.augment|\.claude|\.cursor|node_modules)\//.test(f)) return false;
3912
+ return /^(src|scripts)\//.test(f);
3913
+ }
3914
+ function isTestPath(p) {
3915
+ const f = p.replace(/\\/g, "/");
3916
+ if (/^tests\//.test(f)) return true;
3917
+ return /\.(test|spec)\.(ts|tsx|js|mjs|cjs)$/.test(f);
3918
+ }
3919
+
3920
+ // src/scripts/hooks/evidence_independence.ts
3808
3921
  var STATE_FILE3 = path8.join("agents", "state", "evidence-dispatch.json");
3809
3922
  function _isObject5(v) {
3810
3923
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -3873,7 +3986,7 @@ function extractDispatch(envelope) {
3873
3986
  }
3874
3987
  function _ledgerStamp(consumer_root) {
3875
3988
  try {
3876
- const raw = fs8.readFileSync(
3989
+ const raw = fs9.readFileSync(
3877
3990
  path8.join(consumer_root, "agents", "state", "git-authorization.json"),
3878
3991
  "utf8"
3879
3992
  );
@@ -3885,7 +3998,7 @@ function _ledgerStamp(consumer_root) {
3885
3998
  }
3886
3999
  function _load(target) {
3887
4000
  try {
3888
- const decoded = JSON.parse(fs8.readFileSync(target, "utf8"));
4001
+ const decoded = JSON.parse(fs9.readFileSync(target, "utf8"));
3889
4002
  if (_isObject5(decoded) && Array.isArray(decoded["evaluations"])) {
3890
4003
  return decoded;
3891
4004
  }
@@ -3896,12 +4009,12 @@ function _load(target) {
3896
4009
  var WARN_TEXT = "First evaluation dispatch this turn. Two shapes fabricated a binding honest-null in the audited sessions \u2014 check your prompt for both before it goes out: (1) a verdict pre-loaded into the prompt, and (2) a scope narrowed to files you chose. A review you commissioned on your own work is admissible as gate evidence only when the prompt is recorded alongside the verdict.";
3897
4010
  function decide(tool, prompt, priorEvaluations) {
3898
4011
  if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
3899
- return { exit: EXIT_ALLOW5, stdout: "", stderr: "", evaluations: priorEvaluations };
4012
+ return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
3900
4013
  }
3901
4014
  const preloaded = preloadedVerdict(prompt);
3902
4015
  if (preloaded !== null) {
3903
4016
  return {
3904
- exit: EXIT_BLOCK2,
4017
+ exit: EXIT_BLOCK,
3905
4018
  stdout: "",
3906
4019
  stderr: `Blocked: this evaluation prompt pre-loads its verdict ("${preloaded}"). That is the construct that produced a fabricated NO-FINDINGS committed as binding gate evidence, over a delta an unsteered pass then found a live critical in. Remove the expectation from the prompt and let the evaluator reach its own verdict.
3907
4020
  `,
@@ -3909,11 +4022,11 @@ function decide(tool, prompt, priorEvaluations) {
3909
4022
  };
3910
4023
  }
3911
4024
  if (!isSelfScoped(prompt)) {
3912
- return { exit: EXIT_ALLOW5, stdout: "", stderr: "", evaluations: priorEvaluations };
4025
+ return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
3913
4026
  }
3914
4027
  if (priorEvaluations >= 1) {
3915
4028
  return {
3916
- exit: EXIT_ALLOW5,
4029
+ exit: EXIT_ALLOW3,
3917
4030
  stdout: `${JSON.stringify({
3918
4031
  decision: "warn",
3919
4032
  reason: `Second evaluation dispatch of your own work in this turn. If both passes judge the SAME subject, that is verdict shopping \u2014 commissioning another with a different prompt or scope selects the answer instead of measuring it. Report what the first pass returned, then re-plan. If this is an implementation fan-out rather than a second review, carry on: this check reads prose and cannot tell the two apart with certainty.`
@@ -3924,13 +4037,39 @@ function decide(tool, prompt, priorEvaluations) {
3924
4037
  };
3925
4038
  }
3926
4039
  return {
3927
- exit: EXIT_ALLOW5,
4040
+ exit: EXIT_ALLOW3,
3928
4041
  stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}
3929
4042
  `,
3930
4043
  stderr: "",
3931
4044
  evaluations: priorEvaluations + 1
3932
4045
  };
3933
4046
  }
4047
+ function sameSessionTestFlag(command) {
4048
+ if (command === null || !/\bgit\b[^|;&]*\bcommit\b/.test(command)) return null;
4049
+ return SAME_SESSION_TEXT;
4050
+ }
4051
+ var SAME_SESSION_TEXT = "This commit stages both a test and code it could cover \u2014 the shape L0 takes in a diff, where the implementer authored the evaluator. This check reads the staged set only: it cannot tell who wrote the test, so it is a prompt to STATE the level, not a finding that you are at L0. L0 is a permitted FALLBACK, not the default: where a second session, a second model or a second provider was available, the test should have come from one of them. Say which in the commit or the PR body \u2014 a diff looks identical either way. See evaluator-independence \xA7 Tests are evaluators.";
4052
+ function touchesTestAndCode(paths) {
4053
+ return paths.some(isTestPath) && paths.some(isCodePath);
4054
+ }
4055
+ function _commandOf(envelope) {
4056
+ const payload = _isObject5(envelope["payload"]) ? envelope["payload"] : envelope;
4057
+ const input = _isObject5(payload["tool_input"]) ? payload["tool_input"] : payload;
4058
+ for (const key of ["command", "cmd", "shell_command"]) {
4059
+ const v = input[key];
4060
+ if (typeof v === "string" && v !== "") return v;
4061
+ }
4062
+ return null;
4063
+ }
4064
+ function _stagedPaths(consumer_root) {
4065
+ const r = spawnSync3("git", ["diff", "--cached", "--name-only"], {
4066
+ cwd: consumer_root,
4067
+ encoding: "utf8"
4068
+ });
4069
+ if (r.status !== 0) return null;
4070
+ const out = r.stdout.split("\n").map((s) => s.trim()).filter((s) => s !== "");
4071
+ return out.length === 0 ? null : out;
4072
+ }
3934
4073
  function run3(stdin_text, options) {
3935
4074
  let envelope = {};
3936
4075
  if (stdin_text.trim()) {
@@ -3940,7 +4079,7 @@ function run3(stdin_text, options) {
3940
4079
  envelope = decoded;
3941
4080
  }
3942
4081
  } catch {
3943
- return EXIT_ALLOW5;
4082
+ return EXIT_ALLOW3;
3944
4083
  }
3945
4084
  }
3946
4085
  const target = path8.join(options.consumer_root, STATE_FILE3);
@@ -3953,6 +4092,15 @@ function run3(stdin_text, options) {
3953
4092
  state.evaluations = [];
3954
4093
  }
3955
4094
  const [tool, prompt] = extractDispatch(envelope);
4095
+ const flag = sameSessionTestFlag(_commandOf(envelope));
4096
+ if (flag !== null) {
4097
+ const staged = _stagedPaths(options.consumer_root);
4098
+ if (staged !== null && touchesTestAndCode(staged)) {
4099
+ process.stdout.write(`${JSON.stringify({ decision: "warn", reason: flag })}
4100
+ `);
4101
+ return EXIT_ALLOW3;
4102
+ }
4103
+ }
3956
4104
  const decision = decide(tool, prompt, state.evaluations.length);
3957
4105
  if (decision.evaluations > state.evaluations.length) {
3958
4106
  state.evaluations.push({
@@ -4007,8 +4155,8 @@ function _isCliEntry7() {
4007
4155
  return true;
4008
4156
  }
4009
4157
  try {
4010
- const here = fs8.realpathSync(fileURLToPath7(import.meta.url));
4011
- const argv = fs8.realpathSync(path8.resolve(process.argv[1]));
4158
+ const here = fs9.realpathSync(fileURLToPath7(import.meta.url));
4159
+ const argv = fs9.realpathSync(path8.resolve(process.argv[1]));
4012
4160
  return here === argv;
4013
4161
  } catch {
4014
4162
  return false;
@@ -4019,7 +4167,7 @@ if (_isCliEntry7()) {
4019
4167
  }
4020
4168
 
4021
4169
  // src/scripts/hooks/delegation_nudge_hook.ts
4022
- import fs9 from "node:fs";
4170
+ import fs10 from "node:fs";
4023
4171
  import path9 from "node:path";
4024
4172
  import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
4025
4173
 
@@ -4301,8 +4449,6 @@ function resolveSubagentRouting(inp) {
4301
4449
  }
4302
4450
 
4303
4451
  // src/scripts/hooks/delegation_nudge_hook.ts
4304
- var EXIT_ALLOW6 = 0;
4305
- var EXIT_WARN3 = 2;
4306
4452
  function _isObject6(v) {
4307
4453
  return typeof v === "object" && v !== null && !Array.isArray(v);
4308
4454
  }
@@ -4482,28 +4628,28 @@ function main8() {
4482
4628
  const raw = readHookStdin();
4483
4629
  envelope = raw.trim() ? JSON.parse(raw) : {};
4484
4630
  } catch {
4485
- return EXIT_ALLOW6;
4631
+ return EXIT_ALLOW3;
4486
4632
  }
4487
4633
  const env = _isObject6(envelope) ? envelope : {};
4488
4634
  const event = env["event"];
4489
4635
  const slot = typeof event === "string" && event !== "" ? event : "user_prompt_submit";
4490
4636
  if (slot !== "user_prompt_submit") {
4491
- return EXIT_ALLOW6;
4637
+ return EXIT_ALLOW3;
4492
4638
  }
4493
4639
  const payload = _isObject6(env["payload"]) ? env["payload"] : env;
4494
4640
  const prompt = _extractPrompt2(payload);
4495
4641
  if (!prompt) {
4496
- return EXIT_ALLOW6;
4642
+ return EXIT_ALLOW3;
4497
4643
  }
4498
4644
  if (isSyntheticPrompt(prompt)) {
4499
- return EXIT_ALLOW6;
4645
+ return EXIT_ALLOW3;
4500
4646
  }
4501
4647
  const root = _workspaceRoot(env);
4502
4648
  const platform = env["platform"];
4503
4649
  const hostId = typeof platform === "string" && platform ? platform : null;
4504
4650
  const result = classifyPrompt(prompt, root, hostId);
4505
4651
  if (!result) {
4506
- return EXIT_ALLOW6;
4652
+ return EXIT_ALLOW3;
4507
4653
  }
4508
4654
  process.stdout.write(
4509
4655
  `${JSON.stringify({
@@ -4513,7 +4659,7 @@ function main8() {
4513
4659
  })}
4514
4660
  `
4515
4661
  );
4516
- return EXIT_WARN3;
4662
+ return EXIT_WARN2;
4517
4663
  }
4518
4664
  function _isCliEntry8() {
4519
4665
  if (true) {
@@ -4527,8 +4673,8 @@ function _isCliEntry8() {
4527
4673
  return true;
4528
4674
  }
4529
4675
  try {
4530
- const here = fs9.realpathSync(fileURLToPath8(import.meta.url));
4531
- const argv = fs9.realpathSync(path9.resolve(process.argv[1]));
4676
+ const here = fs10.realpathSync(fileURLToPath8(import.meta.url));
4677
+ const argv = fs10.realpathSync(path9.resolve(process.argv[1]));
4532
4678
  return here === argv;
4533
4679
  } catch {
4534
4680
  return false;
@@ -4925,13 +5071,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
4925
5071
  return {
4926
5072
  project: {
4927
5073
  dir: projectRulesDir,
4928
- present: fs10.existsSync(projectRulesDir),
5074
+ present: fs11.existsSync(projectRulesDir),
4929
5075
  files: p.files,
4930
5076
  tokens: _tokens(p.chars)
4931
5077
  },
4932
5078
  global: {
4933
5079
  dir: globalRulesDir,
4934
- present: fs10.existsSync(globalRulesDir),
5080
+ present: fs11.existsSync(globalRulesDir),
4935
5081
  files: g.files,
4936
5082
  tokens: _tokens(g.chars)
4937
5083
  },
@@ -4942,10 +5088,10 @@ function storeKey(store) {
4942
5088
  return createHash3("sha256").update(path10.resolve(store)).digest("hex").slice(0, 12);
4943
5089
  }
4944
5090
  function scanStore(store, limit) {
4945
- const files = fs10.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs10.statSync(path10.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
5091
+ const files = fs11.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs11.statSync(path10.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
4946
5092
  const per_session = [];
4947
5093
  for (const f of files) {
4948
- const lines = fs10.readFileSync(path10.join(store, f), "utf8").split("\n");
5094
+ const lines = fs11.readFileSync(path10.join(store, f), "utf8").split("\n");
4949
5095
  const r = scanSession(f.replace(/\.jsonl$/, "").slice(0, 8), lines);
4950
5096
  if (r.assistant_turns > 0) {
4951
5097
  per_session.push(r);
@@ -5006,8 +5152,8 @@ function scanStore(store, limit) {
5006
5152
  };
5007
5153
  }
5008
5154
  function recordRate(file, scanned_at, rate) {
5009
- fs10.mkdirSync(path10.dirname(file), { recursive: true });
5010
- fs10.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
5155
+ fs11.mkdirSync(path10.dirname(file), { recursive: true });
5156
+ fs11.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
5011
5157
  `, "utf8");
5012
5158
  }
5013
5159
  function render(report) {
@@ -5138,14 +5284,14 @@ function main9(argv) {
5138
5284
  return 2;
5139
5285
  }
5140
5286
  const resolved = store ?? defaultStore(process.cwd());
5141
- if (!fs10.existsSync(resolved)) {
5287
+ if (!fs11.existsSync(resolved)) {
5142
5288
  process.stderr.write(`conformance:behavior: no transcript store at ${resolved}
5143
5289
  `);
5144
5290
  return 1;
5145
5291
  }
5146
5292
  const report = scanStore(resolved, Number.isFinite(limit) && limit > 0 ? limit : 30);
5147
5293
  if (out) {
5148
- fs10.writeFileSync(out, `${JSON.stringify(report, null, 2)}
5294
+ fs11.writeFileSync(out, `${JSON.stringify(report, null, 2)}
5149
5295
  `, "utf8");
5150
5296
  }
5151
5297
  if (record) {
@@ -5153,7 +5299,7 @@ function main9(argv) {
5153
5299
  }
5154
5300
  if (why !== null) {
5155
5301
  const id = why;
5156
- fs10.writeSync(
5302
+ fs11.writeSync(
5157
5303
  1,
5158
5304
  json ? `${JSON.stringify(
5159
5305
  {
@@ -5173,7 +5319,7 @@ function main9(argv) {
5173
5319
  );
5174
5320
  return 0;
5175
5321
  }
5176
- fs10.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
5322
+ fs11.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
5177
5323
  ` : `${render(report)}
5178
5324
  `);
5179
5325
  return 0;
@@ -5190,8 +5336,8 @@ function _isCliEntry9() {
5190
5336
  return true;
5191
5337
  }
5192
5338
  try {
5193
- const here = fs10.realpathSync(fileURLToPath9(import.meta.url));
5194
- const argv = fs10.realpathSync(path10.resolve(process.argv[1]));
5339
+ const here = fs11.realpathSync(fileURLToPath9(import.meta.url));
5340
+ const argv = fs11.realpathSync(path10.resolve(process.argv[1]));
5195
5341
  return here === argv;
5196
5342
  } catch {
5197
5343
  return false;
@@ -5202,7 +5348,7 @@ if (_isCliEntry9()) {
5202
5348
  }
5203
5349
 
5204
5350
  // src/scripts/report_carrier_divergence.ts
5205
- import * as fs11 from "node:fs";
5351
+ import * as fs12 from "node:fs";
5206
5352
  import * as os3 from "node:os";
5207
5353
  import * as path11 from "node:path";
5208
5354
  import process2 from "node:process";
@@ -5214,7 +5360,7 @@ var PROJECTION_SOURCE = path11.join("dist", "agent-src", "rules");
5214
5360
  var GLOBAL_RULES = path11.join(".claude", "rules");
5215
5361
  function _listRules(dir) {
5216
5362
  try {
5217
- return fs11.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
5363
+ return fs12.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
5218
5364
  } catch {
5219
5365
  return [];
5220
5366
  }
@@ -5222,7 +5368,7 @@ function _listRules(dir) {
5222
5368
  function _ruleType(file) {
5223
5369
  let text;
5224
5370
  try {
5225
- text = fs11.readFileSync(file, "utf-8");
5371
+ text = fs12.readFileSync(file, "utf-8");
5226
5372
  } catch {
5227
5373
  return "";
5228
5374
  }
@@ -5233,8 +5379,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
5233
5379
  const out = {
5234
5380
  projectDir,
5235
5381
  globalDir,
5236
- globalPresent: fs11.existsSync(globalDir),
5237
- projectPresent: fs11.existsSync(projectDir),
5382
+ globalPresent: fs12.existsSync(globalDir),
5383
+ projectPresent: fs12.existsSync(projectDir),
5238
5384
  shared: 0,
5239
5385
  identical: [],
5240
5386
  provenanceOnly: [],
@@ -5267,8 +5413,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
5267
5413
  let projectCopy;
5268
5414
  let globalCopy;
5269
5415
  try {
5270
- projectCopy = fs11.readFileSync(path11.join(projectDir, rule));
5271
- globalCopy = fs11.readFileSync(path11.join(globalDir, rule));
5416
+ projectCopy = fs12.readFileSync(path11.join(projectDir, rule));
5417
+ globalCopy = fs12.readFileSync(path11.join(globalDir, rule));
5272
5418
  verdict = comparePair(projectCopy, globalCopy);
5273
5419
  } catch {
5274
5420
  out.shared -= 1;
@@ -5455,7 +5601,7 @@ function _isCliEntry10() {
5455
5601
  if (process2.argv[1] === void 0) return false;
5456
5602
  if (import.meta.url === pathToFileURL10(path11.resolve(process2.argv[1])).href) return true;
5457
5603
  try {
5458
- return fs11.realpathSync(_HERE2) === fs11.realpathSync(path11.resolve(process2.argv[1]));
5604
+ return fs12.realpathSync(_HERE2) === fs12.realpathSync(path11.resolve(process2.argv[1]));
5459
5605
  } catch {
5460
5606
  return false;
5461
5607
  }
@@ -5465,7 +5611,7 @@ if (_isCliEntry10()) {
5465
5611
  }
5466
5612
 
5467
5613
  // src/scripts/report_skill_activation.ts
5468
- import * as fs12 from "node:fs";
5614
+ import * as fs13 from "node:fs";
5469
5615
  import * as os4 from "node:os";
5470
5616
  import * as path12 from "node:path";
5471
5617
  import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
@@ -5481,25 +5627,25 @@ function censusSkills(root) {
5481
5627
  withTriggerCorpus: [],
5482
5628
  withDeterministicObligation: []
5483
5629
  };
5484
- if (!fs12.existsSync(root)) {
5630
+ if (!fs13.existsSync(root)) {
5485
5631
  return out;
5486
5632
  }
5487
- for (const entry of fs12.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
5633
+ for (const entry of fs13.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
5488
5634
  if (!entry.isDirectory()) {
5489
5635
  continue;
5490
5636
  }
5491
5637
  const file = path12.join(root, entry.name, "SKILL.md");
5492
- if (!fs12.existsSync(file)) {
5638
+ if (!fs13.existsSync(file)) {
5493
5639
  continue;
5494
5640
  }
5495
5641
  out.total += 1;
5496
- const text = fs12.readFileSync(file, "utf8");
5642
+ const text = fs13.readFileSync(file, "utf8");
5497
5643
  const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
5498
5644
  const fm = fmEnd === -1 ? "" : text.slice(0, fmEnd);
5499
5645
  if (TRIGGER_KEYS.some((k) => new RegExp(`^${k}`, "m").test(fm))) {
5500
5646
  out.withTriggerKey.push(entry.name);
5501
5647
  }
5502
- if (fs12.existsSync(path12.join(root, entry.name, "evals", "triggers.json"))) {
5648
+ if (fs13.existsSync(path12.join(root, entry.name, "evals", "triggers.json"))) {
5503
5649
  out.withTriggerCorpus.push(entry.name);
5504
5650
  }
5505
5651
  const body = fmEnd === -1 ? text : text.slice(fmEnd + 4);
@@ -5514,13 +5660,13 @@ function defaultStore2(cwd) {
5514
5660
  }
5515
5661
  function measureUsage(store, limit) {
5516
5662
  const rep = { store, sessions: 0, assistantTurns: 0, invocations: 0, bySkill: {} };
5517
- if (!fs12.existsSync(store)) {
5663
+ if (!fs13.existsSync(store)) {
5518
5664
  return rep;
5519
5665
  }
5520
- const files = fs12.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path12.join(store, f)).sort((a, b) => fs12.statSync(b).mtimeMs - fs12.statSync(a).mtimeMs).slice(0, limit);
5666
+ const files = fs13.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path12.join(store, f)).sort((a, b) => fs13.statSync(b).mtimeMs - fs13.statSync(a).mtimeMs).slice(0, limit);
5521
5667
  rep.sessions = files.length;
5522
5668
  for (const file of files) {
5523
- for (const line of fs12.readFileSync(file, "utf8").split("\n")) {
5669
+ for (const line of fs13.readFileSync(file, "utf8").split("\n")) {
5524
5670
  if (!line.trim()) {
5525
5671
  continue;
5526
5672
  }
@@ -5598,10 +5744,10 @@ function buildRecord(census, usage, today) {
5598
5744
  }
5599
5745
  function readRecord(repoRoot) {
5600
5746
  const file = path12.join(repoRoot, RECORD_PATH);
5601
- if (!fs12.existsSync(file)) {
5747
+ if (!fs13.existsSync(file)) {
5602
5748
  return null;
5603
5749
  }
5604
- return JSON.parse(fs12.readFileSync(file, "utf8"));
5750
+ return JSON.parse(fs13.readFileSync(file, "utf8"));
5605
5751
  }
5606
5752
  function render3(census, usage, published = null) {
5607
5753
  const lines = [];
@@ -5762,8 +5908,8 @@ function main11(argv) {
5762
5908
  }
5763
5909
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5764
5910
  const file = path12.join(root, RECORD_PATH);
5765
- fs12.mkdirSync(path12.dirname(file), { recursive: true });
5766
- fs12.writeFileSync(file, `${JSON.stringify(buildRecord(census, usage, today), null, 2)}
5911
+ fs13.mkdirSync(path12.dirname(file), { recursive: true });
5912
+ fs13.writeFileSync(file, `${JSON.stringify(buildRecord(census, usage, today), null, 2)}
5767
5913
  `);
5768
5914
  process.stdout.write(`
5769
5915
  wrote ${RECORD_PATH} (measured ${today})
@@ -5775,7 +5921,7 @@ if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL11(path12.res
5775
5921
  }
5776
5922
 
5777
5923
  // src/scripts/report_skill_obligation_violations.ts
5778
- import * as fs13 from "node:fs";
5924
+ import * as fs14 from "node:fs";
5779
5925
  import * as os5 from "node:os";
5780
5926
  import * as path13 from "node:path";
5781
5927
  import process3 from "node:process";
@@ -5833,7 +5979,7 @@ function extractObligations(repoRoot) {
5833
5979
  for (const skill of census.withDeterministicObligation) {
5834
5980
  let text;
5835
5981
  try {
5836
- text = fs13.readFileSync(path13.join(skillsRoot, skill, "SKILL.md"), "utf-8");
5982
+ text = fs14.readFileSync(path13.join(skillsRoot, skill, "SKILL.md"), "utf-8");
5837
5983
  } catch {
5838
5984
  continue;
5839
5985
  }
@@ -5962,13 +6108,13 @@ function scanStore2(repoRoot, store, limit) {
5962
6108
  };
5963
6109
  let files;
5964
6110
  try {
5965
- files = fs13.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path13.join(store, f), m: fs13.statSync(path13.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
6111
+ files = fs14.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path13.join(store, f), m: fs14.statSync(path13.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
5966
6112
  } catch {
5967
6113
  return report;
5968
6114
  }
5969
6115
  report.sessions = files.length;
5970
6116
  for (const file of files) {
5971
- const lines = fs13.readFileSync(file, "utf-8").split("\n");
6117
+ const lines = fs14.readFileSync(file, "utf-8").split("\n");
5972
6118
  const id = path13.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
5973
6119
  const entries = _parseSession(lines);
5974
6120
  if (loadedSkillsFrom(entries).size > 0) report.sessionsWithASkill += 1;
@@ -6096,7 +6242,7 @@ function _isCliEntry11() {
6096
6242
  if (process3.argv[1] === void 0) return false;
6097
6243
  if (import.meta.url === pathToFileURL12(path13.resolve(process3.argv[1])).href) return true;
6098
6244
  try {
6099
- return fs13.realpathSync(_HERE4) === fs13.realpathSync(path13.resolve(process3.argv[1]));
6245
+ return fs14.realpathSync(_HERE4) === fs14.realpathSync(path13.resolve(process3.argv[1]));
6100
6246
  } catch {
6101
6247
  return false;
6102
6248
  }
@@ -6120,7 +6266,7 @@ function buildFunnel(opts = {}) {
6120
6266
  divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
6121
6267
  skills: censusSkills(path14.join(repoRoot, SKILLS_ROOT)),
6122
6268
  usage: measureUsage(store, limit),
6123
- storePresent: fs14.existsSync(store),
6269
+ storePresent: fs15.existsSync(store),
6124
6270
  sk2: scanStore2(repoRoot, store, limit)
6125
6271
  };
6126
6272
  }
@@ -6295,7 +6441,7 @@ function _isCliEntry12() {
6295
6441
  if (process4.argv[1] === void 0) return false;
6296
6442
  if (import.meta.url === pathToFileURL13(path14.resolve(process4.argv[1])).href) return true;
6297
6443
  try {
6298
- return fs14.realpathSync(_HERE5) === fs14.realpathSync(path14.resolve(process4.argv[1]));
6444
+ return fs15.realpathSync(_HERE5) === fs15.realpathSync(path14.resolve(process4.argv[1]));
6299
6445
  } catch {
6300
6446
  return false;
6301
6447
  }
@@ -6315,6 +6461,10 @@ var CONFORMANCE_CHECK_IDS = [
6315
6461
  "host-manifest"
6316
6462
  ];
6317
6463
  var THIN_STUB_MARKER = "Routed rule \u2014 load the body on trigger-match";
6464
+ var CONFORMANCE_STATUS_SYMBOLS = {
6465
+ ...STATUS_SYMBOLS,
6466
+ unknown: "\u2754"
6467
+ };
6318
6468
  function print(s = "") {
6319
6469
  process5.stdout.write(`${s}
6320
6470
  `);
@@ -6337,21 +6487,24 @@ function installLogPath() {
6337
6487
  }
6338
6488
  return path15.join(os7.homedir(), ".event4u", "agent-config", "install-log.jsonl");
6339
6489
  }
6340
- function _check_txlog_clean(logPath = installLogPath()) {
6341
- if (!fs15.existsSync(logPath)) {
6342
- return {
6343
- id: "txlog-clean",
6344
- status: "ok",
6345
- message: "no install transaction log \u2014 nothing to recover",
6346
- remedy: ""
6347
- };
6348
- }
6349
- const entries = readRecentEntries(logPath);
6490
+ function _installLogExpected(projectRoot) {
6491
+ return fs16.existsSync(manifest_path(projectRoot));
6492
+ }
6493
+ function _check_txlog_clean(logPath = installLogPath(), projectRoot = null) {
6494
+ const entries = fs16.existsSync(logPath) ? readRecentEntries(logPath) : [];
6350
6495
  if (entries.length === 0) {
6496
+ if (projectRoot !== null && _installLogExpected(projectRoot)) {
6497
+ return {
6498
+ id: "txlog-clean",
6499
+ status: "unknown",
6500
+ message: "install manifest present, no usable install transaction log \u2014 nothing recorded this install, so the last run cannot be confirmed complete",
6501
+ remedy: "no install path writes this log today; run `agent-config doctor` for the drift answer this check cannot give"
6502
+ };
6503
+ }
6351
6504
  return {
6352
6505
  id: "txlog-clean",
6353
6506
  status: "ok",
6354
- message: "install transaction log is empty",
6507
+ message: "no usable install transaction log and no install manifest \u2014 nothing was installed here",
6355
6508
  remedy: ""
6356
6509
  };
6357
6510
  }
@@ -6361,7 +6514,7 @@ function _check_txlog_clean(logPath = installLogPath()) {
6361
6514
  id: "txlog-clean",
6362
6515
  status: "fail",
6363
6516
  message: `install log tail is an abandoned run (abort at ${last.ts}${last.note ? `: ${last.note}` : ""})`,
6364
- remedy: "re-run `agent-config init` (recovery reverse-applies the aborted tail)"
6517
+ remedy: "re-run `agent-config init` \u2014 it re-applies the plan over the partial tail; the aborted run's writes stay on disk and are replaced only where the plan covers them"
6365
6518
  };
6366
6519
  }
6367
6520
  return {
@@ -6376,7 +6529,7 @@ function routeTargetPaths(target) {
6376
6529
  }
6377
6530
  function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6378
6531
  const routerPath = path15.join(packageRoot, "dist", "router.json");
6379
- if (!fs15.existsSync(routerPath)) {
6532
+ if (!fs16.existsSync(routerPath)) {
6380
6533
  return {
6381
6534
  id: "router-pointers",
6382
6535
  status: "fail",
@@ -6386,7 +6539,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6386
6539
  }
6387
6540
  let router;
6388
6541
  try {
6389
- router = JSON.parse(fs15.readFileSync(routerPath, "utf8"));
6542
+ router = JSON.parse(fs16.readFileSync(routerPath, "utf8"));
6390
6543
  } catch (exc) {
6391
6544
  return {
6392
6545
  id: "router-pointers",
@@ -6401,7 +6554,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6401
6554
  let targets = 0;
6402
6555
  for (const kernelId of router["kernel"] ?? []) {
6403
6556
  ids += 1;
6404
- if (!fs15.existsSync(path15.join(rulesDir, `${kernelId}.md`))) {
6557
+ if (!fs16.existsSync(path15.join(rulesDir, `${kernelId}.md`))) {
6405
6558
  broken.push(`kernel:${kernelId} \u2192 rules/${kernelId}.md missing`);
6406
6559
  }
6407
6560
  }
@@ -6409,7 +6562,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6409
6562
  for (const entry of router[tier] ?? []) {
6410
6563
  const ruleId = String(entry["id"] ?? "");
6411
6564
  ids += 1;
6412
- if (!fs15.existsSync(path15.join(rulesDir, `${ruleId}.md`))) {
6565
+ if (!fs16.existsSync(path15.join(rulesDir, `${ruleId}.md`))) {
6413
6566
  broken.push(`${tier}:${ruleId} \u2192 rules/${ruleId}.md missing`);
6414
6567
  }
6415
6568
  for (const target of entry["routes_to"] ?? []) {
@@ -6419,7 +6572,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6419
6572
  broken.push(`${ruleId} \u2192 unparseable routes_to ${JSON.stringify(target)}`);
6420
6573
  continue;
6421
6574
  }
6422
- if (!candidates.some((rel) => fs15.existsSync(path15.join(packageRoot, rel)))) {
6575
+ if (!candidates.some((rel) => fs16.existsSync(path15.join(packageRoot, rel)))) {
6423
6576
  broken.push(`${ruleId} \u2192 ${target} (${candidates[0]} missing)`);
6424
6577
  }
6425
6578
  }
@@ -6453,7 +6606,7 @@ function detectSmokePlatform(projectRoot) {
6453
6606
  function dispatcherRunner(packageRoot) {
6454
6607
  const localTsx = path15.join(packageRoot, "node_modules", ".bin", "tsx");
6455
6608
  const script = path15.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
6456
- if (fs15.existsSync(localTsx)) {
6609
+ if (fs16.existsSync(localTsx)) {
6457
6610
  return [localTsx, script];
6458
6611
  }
6459
6612
  return ["npx", "tsx", script];
@@ -6470,7 +6623,7 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
6470
6623
  ...event === "stop" ? { stop_reason: "conformance-smoke" } : { source: "startup" }
6471
6624
  });
6472
6625
  const argv = [...runner, "--platform", platform, "--event", event];
6473
- const proc = spawnSync3(argv[0], argv.slice(1), {
6626
+ const proc = spawnSync4(argv[0], argv.slice(1), {
6474
6627
  input: payload,
6475
6628
  cwd: projectRoot,
6476
6629
  encoding: "utf8",
@@ -6504,11 +6657,11 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
6504
6657
  }
6505
6658
  function readSettingsMode(projectRoot) {
6506
6659
  const settingsPath = path15.join(projectRoot, ".agent-settings.yml");
6507
- if (!fs15.existsSync(settingsPath)) {
6660
+ if (!fs16.existsSync(settingsPath)) {
6508
6661
  return "eager-all";
6509
6662
  }
6510
6663
  try {
6511
- const parsed = YAML.parse(fs15.readFileSync(settingsPath, "utf8"));
6664
+ const parsed = YAML.parse(fs16.readFileSync(settingsPath, "utf8"));
6512
6665
  const lean = parsed?.["lean_projection"] ?? null;
6513
6666
  const mode = lean?.["mode"];
6514
6667
  return typeof mode === "string" && mode.length > 0 ? mode : "eager-all";
@@ -6519,7 +6672,7 @@ function readSettingsMode(projectRoot) {
6519
6672
  function kernelIdsFromRouter(packageRoot) {
6520
6673
  try {
6521
6674
  const router = JSON.parse(
6522
- fs15.readFileSync(path15.join(packageRoot, "dist", "router.json"), "utf8")
6675
+ fs16.readFileSync(path15.join(packageRoot, "dist", "router.json"), "utf8")
6523
6676
  );
6524
6677
  return new Set((router["kernel"] ?? []).map(String));
6525
6678
  } catch {
@@ -6537,7 +6690,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
6537
6690
  };
6538
6691
  }
6539
6692
  const rulesDir = projectedRulesDir ?? path15.join(projectRoot, ".augment", "rules");
6540
- if (!fs15.existsSync(rulesDir)) {
6693
+ if (!fs16.existsSync(rulesDir)) {
6541
6694
  return {
6542
6695
  id: "lean-projection",
6543
6696
  status: "skipped",
@@ -6546,7 +6699,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
6546
6699
  };
6547
6700
  }
6548
6701
  const kernel = kernelIdsFromRouter(packageRoot);
6549
- const files = fs15.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
6702
+ const files = fs16.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
6550
6703
  const nonKernel = files.filter((f) => !kernel.has(f.replace(/\.md$/, "")));
6551
6704
  if (nonKernel.length === 0) {
6552
6705
  return {
@@ -6560,7 +6713,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
6560
6713
  for (const f of nonKernel) {
6561
6714
  let text = "";
6562
6715
  try {
6563
- text = fs15.readFileSync(path15.join(rulesDir, f), "utf8");
6716
+ text = fs16.readFileSync(path15.join(rulesDir, f), "utf8");
6564
6717
  } catch {
6565
6718
  continue;
6566
6719
  }
@@ -6592,7 +6745,7 @@ function _check_host_manifest(projectRoot) {
6592
6745
  const detected = Object.entries(detectToolPresence(projectRoot)).filter(([, v]) => v === true).map(([k]) => k);
6593
6746
  const detectedNote = detected.length > 0 ? `detected surfaces: ${detected.join(", ")}` : "no surfaces detected";
6594
6747
  const settingsPath = path15.join(projectRoot, ".agent-settings.yml");
6595
- if (!fs15.existsSync(settingsPath)) {
6748
+ if (!fs16.existsSync(settingsPath)) {
6596
6749
  return {
6597
6750
  id: "host-manifest",
6598
6751
  status: "ok",
@@ -6602,7 +6755,7 @@ function _check_host_manifest(projectRoot) {
6602
6755
  }
6603
6756
  let parsed = null;
6604
6757
  try {
6605
- parsed = YAML.parse(fs15.readFileSync(settingsPath, "utf8"));
6758
+ parsed = YAML.parse(fs16.readFileSync(settingsPath, "utf8"));
6606
6759
  } catch (exc) {
6607
6760
  return {
6608
6761
  id: "host-manifest",
@@ -6632,7 +6785,7 @@ function runConformanceChecks(opts = {}) {
6632
6785
  const projectRoot = opts.projectRoot ?? process5.cwd();
6633
6786
  const packageRoot = opts.packageRoot ?? PACKAGE_ROOT;
6634
6787
  const checks = [];
6635
- checks.push(_check_txlog_clean());
6788
+ checks.push(_check_txlog_clean(installLogPath(), projectRoot));
6636
6789
  checks.push(_check_router_pointers(packageRoot));
6637
6790
  if (opts.skipDispatcher === true) {
6638
6791
  checks.push({
@@ -6756,7 +6909,7 @@ function main14(argv = null) {
6756
6909
  print(` \u{1F4CD} project_root: ${project_root}`);
6757
6910
  print("conformance:");
6758
6911
  for (const c of allChecks) {
6759
- const sym = STATUS_SYMBOLS[c["status"]] ?? "?";
6912
+ const sym = CONFORMANCE_STATUS_SYMBOLS[c["status"]] ?? "?";
6760
6913
  print(` ${sym} ${c["id"]}: ${c["message"]}`);
6761
6914
  if (c["status"] !== "ok" && c["remedy"]) {
6762
6915
  print(` fix: ${c["remedy"]}`);
@@ -6781,8 +6934,8 @@ function _isCliEntry13() {
6781
6934
  return true;
6782
6935
  }
6783
6936
  try {
6784
- const here = fs15.realpathSync(fileURLToPath14(import.meta.url));
6785
- const argv = fs15.realpathSync(path15.resolve(process5.argv[1]));
6937
+ const here = fs16.realpathSync(fileURLToPath14(import.meta.url));
6938
+ const argv = fs16.realpathSync(path15.resolve(process5.argv[1]));
6786
6939
  return here === argv;
6787
6940
  } catch {
6788
6941
  return false;
@@ -6808,6 +6961,7 @@ export {
6808
6961
  _check_lean_projection,
6809
6962
  _check_router_pointers,
6810
6963
  _check_txlog_clean,
6964
+ _installLogExpected,
6811
6965
  _parse,
6812
6966
  appendConformanceReport,
6813
6967
  conformanceLogPath,