@event4u/agent-config 16.1.0 → 16.3.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 (467) 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 +98 -257
  5. package/README.md +14 -12
  6. package/dist/agent-src/commands/analyze/inbox.md +1 -1
  7. package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
  8. package/dist/agent-src/commands/bug/fix.md +17 -2
  9. package/dist/agent-src/commands/feature/plan.md +1 -1
  10. package/dist/agent-src/commands/feature/roadmap.md +1 -1
  11. package/dist/agent-src/commands/fix/ci.md +1 -1
  12. package/dist/agent-src/commands/implement-ticket.md +1 -1
  13. package/dist/agent-src/commands/jira-ticket.md +1 -1
  14. package/dist/agent-src/commands/roadmap/create.md +1 -1
  15. package/dist/agent-src/commands/roadmap/materialize.md +1 -1
  16. package/dist/agent-src/commands/roadmap/process-full.md +5 -5
  17. package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
  18. package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
  19. package/dist/agent-src/commands/roadmap.md +18 -4
  20. package/dist/agent-src/commands/tests/create.md +17 -3
  21. package/dist/agent-src/commands/tests/execute.md +22 -5
  22. package/dist/agent-src/commands/work.md +4 -0
  23. package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  24. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  25. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
  26. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
  27. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
  28. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  29. package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
  30. package/dist/agent-src/rules/active-remediation.md +1 -1
  31. package/dist/agent-src/rules/autonomous-execution.md +1 -0
  32. package/dist/agent-src/rules/broken-access-control.md +1 -0
  33. package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
  34. package/dist/agent-src/rules/context-hygiene.md +1 -1
  35. package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
  36. package/dist/agent-src/rules/design-fidelity.md +13 -16
  37. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
  38. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
  39. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  40. package/dist/agent-src/rules/domain-safety-retention.md +1 -0
  41. package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
  42. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  43. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  44. package/dist/agent-src/rules/finance-safety-floor.md +1 -0
  45. package/dist/agent-src/rules/git-history-discipline.md +1 -0
  46. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  47. package/dist/agent-src/rules/legal-safety-floor.md +3 -2
  48. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  49. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  50. package/dist/agent-src/rules/media-governance-routing.md +14 -17
  51. package/dist/agent-src/rules/persona-governance.md +2 -2
  52. package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
  53. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  54. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  55. package/dist/agent-src/rules/runtime-safety.md +1 -0
  56. package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
  57. package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
  58. package/dist/agent-src/rules/session-canary.md +8 -62
  59. package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
  60. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  61. package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
  62. package/dist/agent-src/rules/tool-safety.md +1 -0
  63. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  64. package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
  65. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  66. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  67. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  68. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  69. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  70. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
  71. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  72. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  73. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  74. package/dist/agent-src/skills/design-review/SKILL.md +30 -4
  75. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  76. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  77. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
  78. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  79. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  80. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  81. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  82. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  83. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  84. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  85. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  86. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  87. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  88. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  89. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  90. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  91. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  92. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  93. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  94. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  95. package/dist/agent-src/skills/ui-component-architect/SKILL.md +25 -2
  96. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  97. package/dist/agent-src/templates/roadmaps.md +30 -0
  98. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  99. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  100. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  101. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  102. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  103. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  104. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  105. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  106. package/dist/cli/python/knowledge_ingest.js +1 -1
  107. package/dist/cli/python/workspace_drive.js +1 -1
  108. package/dist/cli/registry.js +1 -1
  109. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  110. package/dist/cli-delegate/check_update_banner.js +12 -1
  111. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  112. package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
  113. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  114. package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
  115. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  116. package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
  117. package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
  118. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  119. package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -0
  120. package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
  121. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  122. package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
  123. package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
  124. package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
  125. package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
  126. package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
  127. package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
  128. package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
  129. package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
  130. package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
  131. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
  132. package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
  133. package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
  134. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  135. package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
  136. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  137. package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
  138. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  139. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  140. package/dist/cli-delegate/cmd_conformance.js +1862 -809
  141. package/dist/cli-delegate/cmd_doctor.js +13 -6
  142. package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
  143. package/dist/cli-delegate/cmd_explain.js +5 -5
  144. package/dist/cli-delegate/cmd_export.js +11 -8
  145. package/dist/cli-delegate/cmd_fleet.js +3 -3
  146. package/dist/cli-delegate/cmd_handoff.js +10 -7
  147. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  148. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  149. package/dist/cli-delegate/cmd_migrate.js +2 -2
  150. package/dist/cli-delegate/cmd_packs_active.js +3 -3
  151. package/dist/cli-delegate/cmd_preflight.js +6 -6
  152. package/dist/cli-delegate/cmd_prune.js +11 -8
  153. package/dist/cli-delegate/cmd_refresh.js +2 -1
  154. package/dist/cli-delegate/cmd_route_audit.js +8 -6
  155. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  156. package/dist/cli-delegate/cmd_session_recycle.js +4 -3
  157. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  158. package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
  159. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  160. package/dist/cli-delegate/cmd_sync.js +11 -8
  161. package/dist/cli-delegate/cmd_uninstall.js +11 -8
  162. package/dist/cli-delegate/cmd_update.js +1 -1
  163. package/dist/cli-delegate/cmd_upgrade.js +3 -2
  164. package/dist/cli-delegate/cmd_validate.js +11 -8
  165. package/dist/cli-delegate/cmd_versions.js +1 -1
  166. package/dist/cli-delegate/deferral_carry.js +16 -0
  167. package/dist/cli-delegate/gate_execute.js +2 -2
  168. package/dist/cli-delegate/roadmap_gates.js +5 -5
  169. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  170. package/dist/discovery/deprecation-report.md +1 -1
  171. package/dist/discovery/discovery-manifest.json +132 -80
  172. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  173. package/dist/discovery/discovery-manifest.summary.md +3 -3
  174. package/dist/discovery/dormancy-report.md +1 -1
  175. package/dist/discovery/orphan-report.md +1 -1
  176. package/dist/discovery/packs.json +7 -5
  177. package/dist/discovery/trust-report.md +2 -2
  178. package/dist/discovery/workspaces.json +6 -4
  179. package/dist/hooks/dispatch.js +7972 -5186
  180. package/dist/hooks/dispatch.sha256 +1 -0
  181. package/dist/install/claudePathsPlan.js +47 -6
  182. package/dist/install/claudeRuleRewrite.js +7 -0
  183. package/dist/install/emit_host_rules_cli.js +39 -36
  184. package/dist/install/globalRuleScope.js +87 -0
  185. package/dist/install/host_rule_ownership.js +160 -0
  186. package/dist/install/install.mjs +2226 -1055
  187. package/dist/install/installThinLayer.js +261 -0
  188. package/dist/install/installedRuleLinks.js +176 -0
  189. package/dist/install/wizard-plan.js +24 -0
  190. package/dist/mcp/registry-manifest.json +3 -3
  191. package/dist/mcp/server.json +2 -2
  192. package/dist/mcp/server.mjs +40913 -34865
  193. package/dist/scripts/_lib/agent_settings.js +11 -0
  194. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  195. package/dist/scripts/_lib/installed_layer.js +378 -0
  196. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  197. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  198. package/dist/scripts/_lib/rule_injection.js +67 -10
  199. package/dist/scripts/_lib/rule_law_section.js +122 -0
  200. package/dist/scripts/_lib/thin_rules.js +458 -0
  201. package/dist/scripts/_lib/windsurf_render.js +60 -0
  202. package/dist/scripts/condense.js +14 -26
  203. package/dist/scripts/project_thin_rules.js +14 -294
  204. package/dist/server/schemas/settings.js +29 -0
  205. package/dist/shared/skillRanking.js +74 -13
  206. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  207. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  208. package/dist/ui/index.html +2 -2
  209. package/docs/contracts/settings-classes.md +10 -6
  210. package/package.json +4 -4
  211. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  212. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  213. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  214. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  215. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  216. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  217. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  218. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  219. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  220. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  221. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  222. package/src/config/agent-settings.template.yml +91 -22
  223. package/src/config/ci-local-parity.yml +18 -0
  224. package/src/config/continuity-surface.json +9 -2
  225. package/src/config/evaluator-budgets.json +4 -4
  226. package/src/config/gate-coverage.yml +327 -3
  227. package/src/config/gate-violation-baselines.json +21 -9
  228. package/src/config/hook-bundle-budget.json +25 -0
  229. package/src/config/hook-latency-budget.json +21 -0
  230. package/src/config/hook-token-budget.json +8 -3
  231. package/src/config/host-instruction-limits.json +33 -0
  232. package/src/config/metric-registry.yml +114 -0
  233. package/src/config/placeholder-drift-budget.json +10 -0
  234. package/src/config/routing-coverage-seed.json +38 -3
  235. package/src/config/rule-activation-census.json +7 -3
  236. package/src/config/rule-consequence-class.json +161 -0
  237. package/src/config/rule-law-ceilings.json +99 -0
  238. package/src/install/claudePathsPlan.ts +46 -4
  239. package/src/install/claudeRuleRewrite.ts +10 -1
  240. package/src/install/emit_host_rules_cli.ts +43 -34
  241. package/src/install/globalRuleScope.ts +109 -0
  242. package/src/install/host_rule_ownership.ts +170 -0
  243. package/src/install/installThinLayer.ts +312 -0
  244. package/src/install/installedRuleLinks.ts +220 -0
  245. package/src/install/wizard-plan.ts +24 -0
  246. package/src/scripts/_cli/cmd_doctor.ts +28 -90
  247. package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
  248. package/src/scripts/_cli/doctor_execution.ts +100 -17
  249. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  250. package/src/scripts/_dispatch.bash +16 -2
  251. package/src/scripts/_lib/agent_settings.ts +11 -0
  252. package/src/scripts/_lib/body_portable.ts +102 -0
  253. package/src/scripts/_lib/cc_transcript.ts +132 -1
  254. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  255. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  256. package/src/scripts/_lib/estate_offsets.ts +241 -0
  257. package/src/scripts/_lib/exemption_shape.ts +143 -0
  258. package/src/scripts/_lib/fnv.ts +23 -0
  259. package/src/scripts/_lib/foreign_scan.ts +106 -0
  260. package/src/scripts/_lib/forge_protection.ts +114 -0
  261. package/src/scripts/_lib/forge_reader.ts +558 -0
  262. package/src/scripts/_lib/gitignore_block.ts +314 -0
  263. package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
  264. package/src/scripts/_lib/graph_feeder_record.ts +214 -0
  265. package/src/scripts/_lib/hook_settings.ts +73 -0
  266. package/src/scripts/_lib/host_capability.ts +40 -19
  267. package/src/scripts/_lib/host_env_write.ts +111 -0
  268. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  269. package/src/scripts/_lib/installed_layer.ts +538 -0
  270. package/src/scripts/_lib/json_merge.ts +82 -0
  271. package/src/scripts/_lib/json_pointers.ts +93 -11
  272. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  273. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  274. package/src/scripts/_lib/machine_wake.ts +16 -3
  275. package/src/scripts/_lib/neighbour_census.ts +760 -0
  276. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  277. package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
  278. package/src/scripts/_lib/obligations.ts +119 -8
  279. package/src/scripts/_lib/probe_inputs.ts +271 -0
  280. package/src/scripts/_lib/prompt_shape.ts +46 -0
  281. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  282. package/src/scripts/_lib/release_highlights.ts +2 -1
  283. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  284. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  285. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  286. package/src/scripts/_lib/review_coverage.ts +134 -0
  287. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  288. package/src/scripts/_lib/rule_injection.ts +130 -10
  289. package/src/scripts/_lib/rule_law_section.ts +147 -0
  290. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  291. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  292. package/src/scripts/_lib/skill_origin.ts +254 -0
  293. package/src/scripts/_lib/structural_hiding.ts +622 -0
  294. package/src/scripts/_lib/structured_ask.ts +64 -21
  295. package/src/scripts/_lib/thin_rules.ts +540 -0
  296. package/src/scripts/_lib/touched_file_quality.ts +434 -0
  297. package/src/scripts/_lib/turn_end_refusals.ts +600 -4
  298. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  299. package/src/scripts/_lib/verification_command.ts +324 -0
  300. package/src/scripts/_lib/verification_evidence.ts +647 -0
  301. package/src/scripts/_lib/verify_clause.ts +280 -0
  302. package/src/scripts/_lib/windsurf_render.ts +63 -0
  303. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  304. package/src/scripts/ai_council/clients.ts +26 -57
  305. package/src/scripts/ai_council/config.ts +1 -1
  306. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  307. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  308. package/src/scripts/audit_skill_overlap.ts +261 -0
  309. package/src/scripts/before_complete_hook.ts +573 -25
  310. package/src/scripts/bench_hook_latency.ts +412 -5
  311. package/src/scripts/bench_touched_file_quality.ts +238 -0
  312. package/src/scripts/cache_realization_report.ts +61 -1
  313. package/src/scripts/check_argument_hint.ts +287 -0
  314. package/src/scripts/check_claims.ts +125 -1
  315. package/src/scripts/check_enforcement_coverage.ts +38 -9
  316. package/src/scripts/check_enforcement_matrix.ts +621 -22
  317. package/src/scripts/check_estate_count.ts +130 -153
  318. package/src/scripts/check_finding_dispositions.ts +138 -7
  319. package/src/scripts/check_generator_sync.ts +71 -0
  320. package/src/scripts/check_hook_bundle_composition.ts +370 -0
  321. package/src/scripts/check_host_docs_digest.ts +786 -0
  322. package/src/scripts/check_host_format_column.ts +559 -0
  323. package/src/scripts/check_input_references.ts +228 -0
  324. package/src/scripts/check_inputs_parity.ts +235 -0
  325. package/src/scripts/check_kernel_edit_ratified.ts +89 -9
  326. package/src/scripts/check_kill_switch_table.ts +294 -0
  327. package/src/scripts/check_memory.ts +7 -1
  328. package/src/scripts/check_placeholder_drift.ts +221 -0
  329. package/src/scripts/check_read_surface_coverage.ts +515 -0
  330. package/src/scripts/check_release_highlights.ts +10 -0
  331. package/src/scripts/check_release_holds.ts +2 -1
  332. package/src/scripts/check_release_pr_shape.ts +38 -15
  333. package/src/scripts/check_routing_coverage.ts +378 -11
  334. package/src/scripts/check_standing_rule_delivery.ts +33 -13
  335. package/src/scripts/check_static_parity.ts +17 -0
  336. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  337. package/src/scripts/ci_settle.ts +145 -19
  338. package/src/scripts/closure_scan.ts +115 -1
  339. package/src/scripts/code_graph/detect.ts +127 -7
  340. package/src/scripts/code_graph/foreign.ts +194 -0
  341. package/src/scripts/code_graph/query.ts +217 -4
  342. package/src/scripts/code_graph/types.ts +26 -2
  343. package/src/scripts/code_graph/validate.ts +3 -1
  344. package/src/scripts/code_graph/verbs.ts +103 -0
  345. package/src/scripts/compile_hook_manifest.ts +90 -14
  346. package/src/scripts/condense.ts +14 -27
  347. package/src/scripts/estate_exemption_shape.ts +202 -0
  348. package/src/scripts/external_sources_denylist.json +29 -4
  349. package/src/scripts/generate_host_cost_table.ts +1 -1
  350. package/src/scripts/hook_manifest.json +1 -1
  351. package/src/scripts/hook_manifest.yaml +192 -14
  352. package/src/scripts/hooks/block_config_weakening.ts +294 -4
  353. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  354. package/src/scripts/hooks/block_plumbing_writes.ts +342 -0
  355. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  356. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  357. package/src/scripts/hooks/code_graph_context_hook.ts +253 -25
  358. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  359. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  360. package/src/scripts/hooks/concern_registry.ts +2 -0
  361. package/src/scripts/hooks/concern_timings.ts +94 -0
  362. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  363. package/src/scripts/hooks/design_pass_hook.ts +100 -9
  364. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  365. package/src/scripts/hooks/dispatch_hook.ts +77 -71
  366. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  367. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  368. package/src/scripts/hooks/evidence_independence.ts +1 -2
  369. package/src/scripts/hooks/exit_codes.ts +129 -0
  370. package/src/scripts/hooks/host_lowering.json +1 -0
  371. package/src/scripts/hooks/host_lowering.ts +146 -3
  372. package/src/scripts/hooks/host_lowering.yaml +262 -46
  373. package/src/scripts/hooks/host_semantics.ts +30 -2
  374. package/src/scripts/hooks/injection_budget.ts +9 -2
  375. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  376. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  377. package/src/scripts/hooks/memory_recall_hook.ts +1 -2
  378. package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
  379. package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
  380. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  381. package/src/scripts/hooks/payload_stub.ts +10 -1
  382. package/src/scripts/hooks/permission_gate.ts +1 -2
  383. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  384. package/src/scripts/hooks/replay_hook.ts +2 -1
  385. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  386. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  387. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  388. package/src/scripts/hooks/rule_inject_hook.ts +765 -68
  389. package/src/scripts/hooks/run_continuation_hook.ts +13 -15
  390. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  391. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  392. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  393. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  394. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  395. package/src/scripts/hooks/table_fingerprint.ts +70 -0
  396. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  397. package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
  398. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  399. package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
  400. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  401. package/src/scripts/hooks_status.ts +112 -0
  402. package/src/scripts/injection_scan_hook.ts +7 -0
  403. package/src/scripts/install.ts +75 -189
  404. package/src/scripts/installed_layer_report.ts +103 -0
  405. package/src/scripts/language_mirror_hook.ts +15 -6
  406. package/src/scripts/learning_sidecar.ts +119 -8
  407. package/src/scripts/lint_deny_text.ts +419 -0
  408. package/src/scripts/lint_exit_codes.ts +262 -0
  409. package/src/scripts/lint_hook_manifest.ts +108 -0
  410. package/src/scripts/lint_major_migration_sections.ts +156 -26
  411. package/src/scripts/lint_pack_boundaries.ts +63 -5
  412. package/src/scripts/lint_roadmap_blockers.ts +78 -12
  413. package/src/scripts/lint_rule_law_section.ts +517 -0
  414. package/src/scripts/lint_skill_descriptions.ts +1 -1
  415. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  416. package/src/scripts/lint_workflow_security.ts +161 -5
  417. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  418. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  419. package/src/scripts/mcp_server/prompts.ts +142 -1
  420. package/src/scripts/measure_routing_signal.ts +54 -16
  421. package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
  422. package/src/scripts/measure_turn_end_gate.ts +193 -0
  423. package/src/scripts/memory_learn_hook.ts +91 -6
  424. package/src/scripts/memory_lookup.ts +1 -1
  425. package/src/scripts/model_rule_injection.ts +52 -5
  426. package/src/scripts/prepack-check.mjs +53 -0
  427. package/src/scripts/project_thin_rules.ts +34 -327
  428. package/src/scripts/release_publication.ts +13 -1
  429. package/src/scripts/report_evidence_temperature.ts +657 -0
  430. package/src/scripts/report_installed_rule_links.ts +157 -0
  431. package/src/scripts/report_invocation_surface.ts +383 -0
  432. package/src/scripts/report_label_agreement.ts +233 -0
  433. package/src/scripts/report_obligation_carriers.ts +699 -0
  434. package/src/scripts/report_obligation_mechanism.ts +230 -0
  435. package/src/scripts/report_obligation_writer_split.ts +236 -0
  436. package/src/scripts/report_single_token_triggers.ts +277 -0
  437. package/src/scripts/report_skill_menu_census.ts +208 -0
  438. package/src/scripts/report_skill_ranker_confusion.ts +442 -0
  439. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  440. package/src/scripts/report_verification_record_coverage.ts +306 -0
  441. package/src/scripts/roadmap_verify_share.ts +213 -0
  442. package/src/scripts/rule_activation_census.ts +5 -2
  443. package/src/scripts/rule_trigger_eval.ts +9 -9
  444. package/src/scripts/schemas/command.schema.json +44 -0
  445. package/src/scripts/schemas/review-findings.schema.json +7 -2
  446. package/src/scripts/schemas/rule.schema.json +5 -0
  447. package/src/scripts/schemas/skill.schema.json +44 -0
  448. package/src/scripts/self_review_gate.ts +421 -17
  449. package/src/scripts/semantic_parity_census.ts +314 -0
  450. package/src/scripts/session_eol_report.ts +54 -1
  451. package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
  452. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  453. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  454. package/src/scripts/sync_gitignore.ts +29 -235
  455. package/src/scripts/trigger_eval_grandfather.json +0 -7
  456. package/src/scripts/trigger_eval_rotation.ts +122 -27
  457. package/src/scripts/ui_conformance_probe.ts +156 -4
  458. package/src/scripts/update_prices.ts +85 -3
  459. package/src/scripts/write_bundle_digest.mjs +74 -0
  460. package/src/server/schemas/settings.ts +41 -0
  461. package/src/shared/skillRanking.ts +117 -11
  462. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  463. package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
  464. package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
  465. package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
  466. package/dist/ui/assets/index-DH1xc62v.js +0 -29
  467. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
@@ -1,24 +1,43 @@
1
1
  import { createRequire as __acCreateRequire } from 'node:module'; const require = globalThis.require ?? __acCreateRequire(import.meta.url);
2
2
  import {
3
+ readRecentEntries
4
+ } from "./chunk-NYX3L2PZ.js";
5
+ import {
6
+ NO_RED_EVIDENCE_REASON,
3
7
  deriveSessionKey,
8
+ describeRecordFinding,
4
9
  foldRefusal,
10
+ foldShadow,
11
+ hasRedThenGreen,
5
12
  isCiPoll,
6
13
  isVacuousOutput,
14
+ isVerificationCommand,
7
15
  parseRecord,
16
+ parseShadowRecord,
8
17
  pendingCount,
9
18
  readInstallBoundary,
19
+ readRunEvidence,
20
+ readTurnRunState,
10
21
  sessionRefusalFile,
22
+ sessionShadowFile,
23
+ shadowSetBy,
11
24
  statePathFor,
12
25
  validateAssumption
13
- } from "./chunk-QJWAUMJW.js";
26
+ } from "./chunk-NQ7WT43P.js";
14
27
  import {
15
28
  readHookStdin
16
29
  } from "./chunk-F7DQWYR5.js";
17
30
  import {
18
- DeadScopeError,
19
- assertScanned,
20
- reportScanned
21
- } from "./chunk-I3EIN2YG.js";
31
+ LexicalIndex,
32
+ isSqliteAvailableSync,
33
+ loadSqliteSync,
34
+ readUserVersion,
35
+ stampUserVersion,
36
+ tokenize
37
+ } from "./chunk-ZBZV2G5R.js";
38
+ import {
39
+ detectToolPresence
40
+ } from "./chunk-A4QJ6ENT.js";
22
41
  import {
23
42
  ArgparseExit,
24
43
  STATUS_SYMBOLS,
@@ -35,16 +54,22 @@ import {
35
54
  comparePair,
36
55
  frontmatterPaths,
37
56
  proseEqual,
57
+ resolveSessionRole,
38
58
  splitFrontmatter
39
- } from "./chunk-GJCBB2UG.js";
59
+ } from "./chunk-TRI6DQZ6.js";
40
60
  import "./chunk-FTAISGDQ.js";
41
- import "./chunk-M2DPGFZ4.js";
61
+ import "./chunk-6V6TA25H.js";
42
62
  import "./chunk-ROYYTEZU.js";
43
63
  import "./chunk-BTWUYDGN.js";
44
64
  import {
45
- projectStoreSlug,
46
- resolveSessionRole
47
- } from "./chunk-FXYUYU4N.js";
65
+ atomicAppendLine,
66
+ probeHostCapabilities
67
+ } from "./chunk-S4YWNRKO.js";
68
+ import "./chunk-ULQRUR23.js";
69
+ import {
70
+ hardenedSpawnEnv,
71
+ projectStoreSlug
72
+ } from "./chunk-EEHDXSYD.js";
48
73
  import {
49
74
  atomic_write_json,
50
75
  atomic_write_text,
@@ -55,26 +80,22 @@ import {
55
80
  prune_stale_session_states,
56
81
  session_state_file
57
82
  } from "./chunk-E4XIZDIB.js";
58
- import {
59
- artefact_roots
60
- } from "./chunk-NUHLI2RX.js";
61
- import {
62
- readRecentEntries
63
- } from "./chunk-5R7HOLAF.js";
64
- import {
65
- detectToolPresence
66
- } from "./chunk-A4QJ6ENT.js";
67
- import {
68
- atomicAppendLine,
69
- probeHostCapabilities
70
- } from "./chunk-6KM6ORRY.js";
71
83
  import "./chunk-T7L2QK3K.js";
72
84
  import "./chunk-DMSZHXZS.js";
85
+ import "./chunk-NXEILF2K.js";
86
+ import "./chunk-DEIV7XP7.js";
87
+ import "./chunk-KB5FZ655.js";
88
+ import "./chunk-JQOM7TTM.js";
73
89
  import {
74
90
  manifest_path,
75
91
  read_manifest
76
- } from "./chunk-4E2W63DA.js";
92
+ } from "./chunk-OKJUIAPS.js";
77
93
  import "./chunk-MWRD5YOH.js";
94
+ import {
95
+ DeadScopeError,
96
+ assertScanned,
97
+ reportScanned
98
+ } from "./chunk-I3EIN2YG.js";
78
99
  import {
79
100
  require_dist
80
101
  } from "./chunk-OY53J6II.js";
@@ -83,17 +104,20 @@ import {
83
104
  } from "./chunk-MCJYCQPF.js";
84
105
  import {
85
106
  load_agent_settings
86
- } from "./chunk-FC75PGJE.js";
107
+ } from "./chunk-JY2I2DXV.js";
87
108
  import "./chunk-NDJLNC6Q.js";
109
+ import {
110
+ artefact_roots
111
+ } from "./chunk-YCU3TIXH.js";
88
112
  import {
89
113
  __toESM
90
114
  } from "./chunk-J55WI3RR.js";
91
115
 
92
116
  // src/scripts/_cli/cmd_conformance.ts
93
117
  var YAML = __toESM(require_dist(), 1);
94
- import * as fs15 from "node:fs";
118
+ import * as fs20 from "node:fs";
95
119
  import * as os7 from "node:os";
96
- import * as path15 from "node:path";
120
+ import * as path18 from "node:path";
97
121
  import process5 from "node:process";
98
122
  import { spawnSync as spawnSync4 } from "node:child_process";
99
123
  import { fileURLToPath as fileURLToPath14, pathToFileURL as pathToFileURL14 } from "node:url";
@@ -109,10 +133,10 @@ var ROUTE_TARGET_TEMPLATES = Object.freeze({
109
133
  ])
110
134
  });
111
135
  function parseRouteTarget(target) {
112
- const sep2 = target.indexOf(":");
113
- if (sep2 <= 0) return null;
114
- const kind = target.slice(0, sep2);
115
- const id = target.slice(sep2 + 1);
136
+ const sep3 = target.indexOf(":");
137
+ if (sep3 <= 0) return null;
138
+ const kind = target.slice(0, sep3);
139
+ const id = target.slice(sep3 + 1);
116
140
  if (!id) return null;
117
141
  if (!Object.prototype.hasOwnProperty.call(ROUTE_TARGET_TEMPLATES, kind)) return null;
118
142
  return { kind, id };
@@ -124,17 +148,17 @@ function routeTargetPathsPosix(target) {
124
148
  }
125
149
 
126
150
  // src/scripts/report_conformance_funnel.ts
127
- import * as fs14 from "node:fs";
151
+ import * as fs19 from "node:fs";
128
152
  import * as os6 from "node:os";
129
- import * as path14 from "node:path";
153
+ import * as path17 from "node:path";
130
154
  import process4 from "node:process";
131
155
  import { fileURLToPath as fileURLToPath13, pathToFileURL as pathToFileURL13 } from "node:url";
132
156
 
133
157
  // src/scripts/conformance_scan.ts
134
- import { createHash as createHash3 } from "node:crypto";
135
- import fs10 from "node:fs";
158
+ import { createHash as createHash4 } from "node:crypto";
159
+ import fs15 from "node:fs";
136
160
  import os2 from "node:os";
137
- import path10 from "node:path";
161
+ import path13 from "node:path";
138
162
  import { fileURLToPath as fileURLToPath9, pathToFileURL as pathToFileURL9 } from "node:url";
139
163
 
140
164
  // src/scripts/_lib/transcript_entry.ts
@@ -182,7 +206,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
182
206
  // src/scripts/_lib/prompt_shape.ts
183
207
  function isSyntheticPrompt(prompt) {
184
208
  const head = prompt.trimStart().slice(0, 400);
185
- return head.startsWith("[SYSTEM NOTIFICATION - NOT USER INPUT]") || head.startsWith("<task-notification>") || head.startsWith("<system-reminder>") || head.startsWith("<local-command-caveat>") || /^\[SYSTEM NOTIFICATION\b/.test(head);
209
+ return head.startsWith("[SYSTEM NOTIFICATION - NOT USER INPUT]") || head.startsWith("<task-notification>") || head.startsWith("<system-reminder>") || head.startsWith("<local-command-caveat>") || head.startsWith("<agent-message") || head.startsWith("Your claude.ai usage limit has reset") || /^\[SYSTEM NOTIFICATION\b/.test(head);
186
210
  }
187
211
  var DOCUMENT_HEAD = /^\s*(#{1,6}\s|-{3,}\s*$|={3,}\s*$|<[A-Za-z][\w-]*>\s*$)/;
188
212
  function humanAuthoredLead(text) {
@@ -339,7 +363,7 @@ This is the WEAKER of the two provenances \u2014 it reflects the machine, not th
339
363
  return `<language-pin>
340
364
  The user submitted this turn's prompt in ${name}. That is the mirror target for EVERY user-visible token of your replies this turn \u2014 including the short inter-tool lines between tool calls, headings, table cells, bullets, status lines, and the recommendation label under any numbered-options block.
341
365
 
342
- This pin exists because the trigger is not observable from the transcript alone: slash-command and skill bodies arrive in the user role and can be long and English, which is what drove the 626 wrong-language turns measured in the 30-session audit this hook was built from. Tool output, file contents, and an injected skill body are NOT the trigger \u2014 this pin is.
366
+ Tool output, file contents, and an injected skill body are NOT the trigger \u2014 this pin is.
343
367
  </language-pin>`;
344
368
  }
345
369
  function _loadState(target) {
@@ -707,7 +731,8 @@ import { fileURLToPath as fileURLToPath2, pathToFileURL as pathToFileURL2 } from
707
731
  // src/scripts/_lib/machine_wake.ts
708
732
  var MACHINE_WAKE_PREFIXES = [
709
733
  "<task-notification>",
710
- "<system-reminder>"
734
+ "<system-reminder>",
735
+ "<agent-message"
711
736
  ];
712
737
  function humanTypedThisTurn(prompt) {
713
738
  const head = prompt.trimStart();
@@ -1918,8 +1943,8 @@ var COMMAND_OBJECTS = {
1918
1943
  };
1919
1944
 
1920
1945
  // src/scripts/hooks/turn_end_gate_hook.ts
1921
- import * as fs7 from "node:fs";
1922
- import * as path7 from "node:path";
1946
+ import * as fs12 from "node:fs";
1947
+ import * as path10 from "node:path";
1923
1948
  import { fileURLToPath as fileURLToPath6, pathToFileURL as pathToFileURL6 } from "node:url";
1924
1949
 
1925
1950
  // src/scripts/check_reply_consistency.ts
@@ -2298,526 +2323,950 @@ function detectDroppedDecision(assistantTurnTexts, closingFromPayload = false) {
2298
2323
  return null;
2299
2324
  }
2300
2325
 
2301
- // src/scripts/hooks/end_review_nudge_hook.ts
2302
- import { spawnSync as spawnSync2 } from "node:child_process";
2303
- import * as crypto from "node:crypto";
2304
- import * as fs5 from "node:fs";
2305
- import * as os from "node:os";
2306
- import * as path5 from "node:path";
2307
- import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
2326
+ // src/scripts/_lib/graph_feeder_record.ts
2327
+ import * as fs7 from "node:fs";
2328
+ import * as path6 from "node:path";
2308
2329
 
2309
- // src/scripts/_lib/review_baseline.ts
2310
- import { spawnSync } from "node:child_process";
2330
+ // src/scripts/code_graph/detect.ts
2331
+ import { execFileSync } from "node:child_process";
2311
2332
  import * as fs4 from "node:fs";
2312
2333
  import * as path4 from "node:path";
2313
- function currentHeadSha(cwd) {
2334
+
2335
+ // src/scripts/code_graph/types.ts
2336
+ var GUESS_RESOLVED_VIA = /* @__PURE__ */ new Set([
2337
+ "name-lookup",
2338
+ "dynamic",
2339
+ // The accepted-edge filter is a DENYLIST, so a new value is accepted by
2340
+ // default — which is exactly backwards for this one. `foreign` must be
2341
+ // listed here or a consumer's graph would silently answer the gate verbs
2342
+ // as if this package had resolved its edges.
2343
+ "foreign"
2344
+ ]);
2345
+ function isStatedResolution(resolved_via) {
2346
+ return !GUESS_RESOLVED_VIA.has(resolved_via);
2347
+ }
2348
+ var SCHEMA_VERSION = 4;
2349
+ var EXT_LANG = {
2350
+ ".php": "php",
2351
+ ".ts": "typescript",
2352
+ ".tsx": "typescript",
2353
+ ".mts": "typescript",
2354
+ ".cts": "typescript",
2355
+ ".js": "javascript",
2356
+ ".jsx": "javascript",
2357
+ ".mjs": "javascript",
2358
+ ".cjs": "javascript"
2359
+ };
2360
+
2361
+ // src/scripts/code_graph/validate.ts
2362
+ var RELATIONS = /* @__PURE__ */ new Set(["calls", "imports", "uses", "inherits", "member", "tests"]);
2363
+ var CONFIDENCES = /* @__PURE__ */ new Set(["EXTRACTED", "INFERRED", "AMBIGUOUS"]);
2364
+ var RESOLVED_VIA = /* @__PURE__ */ new Set([
2365
+ "same-file",
2366
+ "import-specifier",
2367
+ "path-alias",
2368
+ "psr4",
2369
+ "route-table",
2370
+ "test-import",
2371
+ "name-lookup",
2372
+ "dynamic",
2373
+ // Adapted from a consumer's own graph file; see `foreign.ts`.
2374
+ "foreign"
2375
+ ]);
2376
+ var PROVIDERS = /* @__PURE__ */ new Set(["native", "foreign"]);
2377
+ var KINDS = /* @__PURE__ */ new Set([
2378
+ "file",
2379
+ "class",
2380
+ "interface",
2381
+ "trait",
2382
+ "function",
2383
+ "method",
2384
+ "constant",
2385
+ "type",
2386
+ "enum",
2387
+ "skipped"
2388
+ ]);
2389
+ function validateGraph(g) {
2390
+ const errors = [];
2391
+ const push = (m) => {
2392
+ if (errors.length < 50) errors.push(m);
2393
+ };
2394
+ if (typeof g !== "object" || g === null) return { ok: false, errors: ["graph is not an object"] };
2395
+ const obj = g;
2396
+ if (typeof obj["schema_version"] !== "number") push("schema_version must be a number");
2397
+ if (typeof obj["source_checksum"] !== "string") push("source_checksum must be a string");
2398
+ if (!Array.isArray(obj["nodes"])) push("nodes must be an array");
2399
+ if (!Array.isArray(obj["edges"])) push("edges must be an array");
2400
+ if (errors.length) return { ok: false, errors };
2401
+ const nodes = obj["nodes"];
2402
+ const ids = /* @__PURE__ */ new Set();
2403
+ nodes.forEach((n, i) => {
2404
+ if (typeof n !== "object" || n === null) return push(`node[${i}] not an object`);
2405
+ const nn = n;
2406
+ if (typeof nn["id"] !== "string") push(`node[${i}].id must be a string`);
2407
+ else ids.add(nn["id"]);
2408
+ if (!KINDS.has(nn["kind"])) push(`node[${i}].kind invalid: ${String(nn["kind"])}`);
2409
+ if (typeof nn["source_file"] !== "string") push(`node[${i}].source_file must be a string`);
2410
+ if (!Array.isArray(nn["source_location"])) push(`node[${i}].source_location must be an array`);
2411
+ });
2412
+ obj["edges"].forEach((e, i) => {
2413
+ if (typeof e !== "object" || e === null) return push(`edge[${i}] not an object`);
2414
+ const ee = e;
2415
+ if (typeof ee["source"] !== "string") push(`edge[${i}].source must be a string`);
2416
+ if (typeof ee["target"] !== "string") push(`edge[${i}].target must be a string`);
2417
+ if (!RELATIONS.has(ee["relation"])) push(`edge[${i}].relation invalid: ${String(ee["relation"])}`);
2418
+ if (!CONFIDENCES.has(ee["confidence"]))
2419
+ push(`edge[${i}].confidence invalid: ${String(ee["confidence"])}`);
2420
+ if (ee["resolved_via"] === void 0) push(`edge[${i}].resolved_via is required`);
2421
+ else if (!RESOLVED_VIA.has(ee["resolved_via"]))
2422
+ push(`edge[${i}].resolved_via invalid: ${String(ee["resolved_via"])}`);
2423
+ if (ee["provider"] === void 0) push(`edge[${i}].provider is required`);
2424
+ else if (!PROVIDERS.has(ee["provider"]))
2425
+ push(`edge[${i}].provider invalid: ${String(ee["provider"])}`);
2426
+ if (typeof ee["source"] === "string" && !ids.has(ee["source"]))
2427
+ push(`edge[${i}].source is not a known node: ${String(ee["source"])}`);
2428
+ if (ee["candidates"] !== void 0 && !Array.isArray(ee["candidates"]))
2429
+ push(`edge[${i}].candidates must be an array when present`);
2430
+ });
2431
+ return { ok: errors.length === 0, errors };
2432
+ }
2433
+
2434
+ // src/scripts/code_graph/detect.ts
2435
+ var CONSUMER_CANDIDATES = ["graph.json", "code-graph.json", ".code-graph/graph.json"];
2436
+ function consumerCandidates(root) {
2437
+ let declared;
2314
2438
  try {
2315
- const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
2316
- if (r.status !== 0) return null;
2317
- const out = (r.stdout ?? "").trim();
2318
- return out === "" ? null : out;
2439
+ const settings = load_agent_settings({ cwd: root }) ?? {};
2440
+ const section = settings["code_graph"];
2441
+ declared = section !== null && typeof section === "object" && !Array.isArray(section) ? section["consumer_index_paths"] : void 0;
2319
2442
  } catch {
2320
- return null;
2443
+ declared = void 0;
2321
2444
  }
2445
+ const extra = Array.isArray(declared) ? declared.filter(
2446
+ (p) => typeof p === "string" && p !== "" && !path4.isAbsolute(p) && !path4.normalize(p).startsWith("..")
2447
+ ) : [];
2448
+ return [...CONSUMER_CANDIDATES, ...extra.filter((p) => !CONSUMER_CANDIDATES.includes(p))];
2322
2449
  }
2323
- var BASELINE_STATE_REL = path4.join("agents", "runtime", "state", "review-baseline");
2324
- function baselineStateDir(workspaceRoot) {
2325
- return path4.join(workspaceRoot, BASELINE_STATE_REL);
2326
- }
2327
- function baselineStateFile(workspaceRoot, sessionKey) {
2328
- return path4.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
2329
- }
2330
- function parseBaseline(raw) {
2331
- let decoded;
2450
+ function gitLastCommitEpoch(root) {
2332
2451
  try {
2333
- decoded = JSON.parse(raw);
2452
+ const out = execFileSync("git", ["-C", root, "log", "-1", "--format=%ct"], {
2453
+ env: hardenedSpawnEnv(),
2454
+ encoding: "utf-8",
2455
+ stdio: ["ignore", "pipe", "ignore"]
2456
+ }).trim();
2457
+ const n = Number(out);
2458
+ return Number.isFinite(n) ? n : null;
2334
2459
  } catch {
2335
2460
  return null;
2336
2461
  }
2337
- if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
2338
- const o = decoded;
2339
- const lines = o["baseline_lines"];
2340
- if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
2341
- const written = o["written_at"];
2342
- if (typeof written !== "string") return null;
2343
- const head = o["head_sha"];
2344
- const measure = o["measure"];
2345
- return {
2346
- head_sha: typeof head === "string" && head !== "" ? head : null,
2347
- baseline_lines: lines,
2348
- ...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
2349
- written_at: written
2350
- };
2351
2462
  }
2352
- function readBaseline(workspaceRoot, sessionKey) {
2353
- const file = baselineStateFile(workspaceRoot, sessionKey);
2354
- let raw;
2463
+ function commitsBehind(root, sha) {
2355
2464
  try {
2356
- raw = fs4.readFileSync(file, "utf-8");
2357
- } catch (exc) {
2358
- const code = exc?.code;
2359
- return code === "ENOENT" ? "absent" : "unreadable";
2465
+ const out = execFileSync("git", ["-C", root, "rev-list", "--count", `${sha}..HEAD`], {
2466
+ env: hardenedSpawnEnv(),
2467
+ encoding: "utf-8",
2468
+ stdio: ["ignore", "pipe", "ignore"]
2469
+ }).trim();
2470
+ const n = Number(out);
2471
+ return Number.isFinite(n) ? n : null;
2472
+ } catch {
2473
+ return null;
2360
2474
  }
2361
- return parseBaseline(raw) ?? "unreadable";
2362
2475
  }
2363
- function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
2364
- if (baseline === "absent" || baseline === "unreadable") {
2365
- return { applied: false, fallback: baseline, lines: measuredLines };
2476
+ function freshness(root, file, embeddedSha) {
2477
+ if (embeddedSha) {
2478
+ const behind = commitsBehind(root, embeddedSha);
2479
+ if (behind !== null) return { stale: behind > 0, commits_behind: behind };
2366
2480
  }
2367
- if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
2368
- return { applied: false, fallback: "mixed-measure", lines: measuredLines };
2481
+ const last = gitLastCommitEpoch(root);
2482
+ if (last === null) return {};
2483
+ try {
2484
+ const mtime = Math.floor(fs4.statSync(file).mtimeMs / 1e3);
2485
+ return { stale: mtime < last };
2486
+ } catch {
2487
+ return {};
2369
2488
  }
2370
- if (baseline.head_sha !== headSha()) {
2371
- return { applied: false, fallback: "head-moved", lines: measuredLines };
2489
+ }
2490
+ function looksLikeGraph(p) {
2491
+ try {
2492
+ const raw = JSON.parse(fs4.readFileSync(p, "utf-8"));
2493
+ if (validateGraph(raw).ok) return { ok: true, sha: raw["head_at_build"] ?? null };
2494
+ const nodes = raw["nodes"];
2495
+ const edges = raw["edges"] ?? raw["links"];
2496
+ const okShape = Array.isArray(nodes) && Array.isArray(edges) && (edges.length === 0 || typeof edges[0] === "object" && edges[0] !== null && "source" in edges[0] && "target" in edges[0]);
2497
+ return { ok: okShape, sha: raw["head_at_build"] ?? null };
2498
+ } catch {
2499
+ return { ok: false, sha: null };
2372
2500
  }
2373
- return {
2374
- applied: true,
2375
- baseline,
2376
- lines: Math.max(0, measuredLines - baseline.baseline_lines)
2377
- };
2378
2501
  }
2379
-
2380
- // src/scripts/_lib/audit_field_provenance.ts
2381
- var MAX_APPLIED_IDS = 32;
2382
- function appliedIds(values) {
2502
+ function detectSources(root, nativeCache) {
2503
+ const abs = path4.resolve(root);
2383
2504
  const out = [];
2384
- for (const raw of values ?? []) {
2385
- const v = String(raw).trim();
2386
- if (v === "" || out.includes(v)) continue;
2387
- out.push(v);
2388
- if (out.length >= MAX_APPLIED_IDS) break;
2505
+ for (const rel of consumerCandidates(abs)) {
2506
+ const p = path4.join(abs, rel);
2507
+ if (fs4.existsSync(p)) {
2508
+ const { ok, sha } = looksLikeGraph(p);
2509
+ if (ok) out.push({ kind: "consumer", path: p, present: true, ...freshness(abs, p, sha) });
2510
+ }
2511
+ }
2512
+ const scipCandidates = [path4.join(abs, "index.scip")];
2513
+ try {
2514
+ for (const e of fs4.readdirSync(abs)) if (e.endsWith(".scip")) scipCandidates.push(path4.join(abs, e));
2515
+ } catch {
2389
2516
  }
2517
+ const scip = scipCandidates.find((p) => fs4.existsSync(p));
2518
+ if (scip) out.push({ kind: "scip", path: scip, present: true, note: "SCIP detected \u2014 peer tooling required (no owned reader)" });
2519
+ if (fs4.existsSync(nativeCache)) out.push({ kind: "native", path: nativeCache, present: true, ...freshness(abs, nativeCache, null) });
2390
2520
  return out;
2391
2521
  }
2392
-
2393
- // src/scripts/_lib/review_skipped_record.ts
2394
- var BASELINE_APPLICATIONS = [
2395
- "applied",
2396
- "absent",
2397
- "unreadable",
2398
- "head_moved",
2399
- "mixed_measure"
2400
- ];
2401
- var PRODUCER_PRIVACY_CLASS = "ids-only";
2402
- function isNonNegInt(n) {
2403
- return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
2522
+ function pickSource(verdicts) {
2523
+ const consumer = verdicts.find((v) => v.kind === "consumer" && v.stale !== true);
2524
+ if (consumer) return consumer;
2525
+ const native = verdicts.find((v) => v.kind === "native");
2526
+ if (native) return native;
2527
+ return verdicts.find((v) => v.kind === "consumer") ?? null;
2404
2528
  }
2405
- var HIGH_RISK_DIFF_LINES = 200;
2406
- function buildReviewSkippedLine(input) {
2407
- const errors = [];
2408
- if (!isNonNegInt(input.diff_lines)) {
2409
- errors.push("diff_lines must be a non-negative integer");
2529
+ var NATIVE_CACHE_REL = path4.join("agents", "runtime", "state", "code-graph-v1.json");
2530
+ function hasUncommittedIndexedEdit(root) {
2531
+ let out;
2532
+ try {
2533
+ out = execFileSync("git", ["-C", root, "status", "--porcelain", "--untracked-files=all"], {
2534
+ env: hardenedSpawnEnv(),
2535
+ encoding: "utf-8",
2536
+ stdio: ["ignore", "pipe", "ignore"],
2537
+ // Both bounds are the review's: Node's 1 MB default would throw
2538
+ // ENOBUFS on a large working tree, the catch below would return
2539
+ // false, and the state would read `fresh` — a second silent
2540
+ // under-report on a probe that runs at PreToolUse and at every stop.
2541
+ // The timeout bounds the other failure: this probe sits in front of
2542
+ // a tool call, so a hung git must not hold one up.
2543
+ maxBuffer: 32 * 1024 * 1024,
2544
+ timeout: 5e3
2545
+ });
2546
+ } catch {
2547
+ return false;
2410
2548
  }
2411
- if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
2412
- errors.push('mutation_measure must be "exact" or "capped_approximation"');
2549
+ for (const raw of out.split("\n")) {
2550
+ if (raw.length < 4) continue;
2551
+ let p = raw.slice(3);
2552
+ const arrow = p.indexOf(" -> ");
2553
+ if (arrow !== -1) p = p.slice(arrow + 4);
2554
+ p = p.trim().replace(/^"|"$/g, "");
2555
+ const dot = p.lastIndexOf(".");
2556
+ if (dot === -1) continue;
2557
+ if (p.slice(dot).toLowerCase() in EXT_LANG) return true;
2413
2558
  }
2414
- if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
2415
- if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
2416
- if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
2417
- errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
2559
+ return false;
2560
+ }
2561
+ function graphState(root, nativeCache) {
2562
+ const picked = pickSource(detectSources(root, nativeCache ?? path4.join(root, NATIVE_CACHE_REL)));
2563
+ if (!picked) return "absent";
2564
+ if (picked.stale === true) {
2565
+ const behind = picked.commits_behind;
2566
+ return `behind:${typeof behind === "number" ? behind : 0}`;
2418
2567
  }
2419
- if (errors.length) return { line: null, errors };
2420
- const line = {
2421
- schema_version: 1,
2422
- id: input.id,
2423
- ts: input.ts,
2424
- work_id: input.work_id ?? `review-skipped-${input.ts}`,
2425
- phase: "report",
2426
- outcome: "skipped",
2427
- confidence_band: "medium",
2428
- risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
2429
- memory: { asks: 0, hits: 0 },
2430
- verify: { claims: 0, first_try_passes: 0 },
2431
- // COMPUTED, never a producer constant.
2432
- rules_applied: appliedIds(input.rules_applied),
2433
- // MANDATORY on every audit line, same as the orchestration producer.
2434
- privacy_class: PRODUCER_PRIVACY_CLASS,
2435
- // `skills_applied` is OMITTED here, deliberately and not by oversight.
2436
- // audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
2437
- // ("recorded, none applied"), and this writer observes a review that
2438
- // did not happen -- it has no skill observation to offer in either
2439
- // direction. Emitting `[]` would assert "no skills applied" on evidence
2440
- // this producer never gathered, which is the exact fabrication the
2441
- // absent/empty split exists to make impossible.
2442
- persona: null,
2443
- input_kind: "prompt",
2444
- type: "note",
2445
- review_skipped: {
2446
- diff_lines: input.diff_lines,
2447
- mutation_measure: input.mutation_measure,
2448
- // Omitted, not defaulted, when the producer recorded nothing —
2449
- // audit-log-v1's absent-versus-present split is the same one
2450
- // `skills_applied` uses above, for the same reason.
2451
- ...input.baseline === void 0 ? {} : { baseline: input.baseline }
2452
- }
2453
- };
2454
- return { line, errors: [] };
2568
+ return hasUncommittedIndexedEdit(root) ? "edited" : "fresh";
2455
2569
  }
2456
2570
 
2457
- // src/scripts/hooks/envelope.ts
2458
- var ENVELOPE_KEYS = [
2459
- "schema_version",
2460
- "platform",
2461
- "event",
2462
- "payload"
2463
- ];
2464
- function _isObject4(obj) {
2465
- return typeof obj === "object" && obj !== null && !Array.isArray(obj);
2466
- }
2467
- function looks_like_envelope(obj) {
2468
- if (!_isObject4(obj)) {
2571
+ // src/scripts/code_graph/query.ts
2572
+ import * as fs6 from "node:fs";
2573
+
2574
+ // src/scripts/code_graph/sqlite_store.ts
2575
+ import * as fs5 from "node:fs";
2576
+ var GRAPH_STORE_VERSION = 4;
2577
+ var INDEXED_READ_MIN_EDGES = 5e4;
2578
+ function sqliteTwinPath(jsonPath) {
2579
+ return jsonPath.endsWith(".json") ? `${jsonPath.slice(0, -5)}.sqlite3` : `${jsonPath}.sqlite3`;
2580
+ }
2581
+ function emitSqliteTwin(graph, serialized, jsonPath) {
2582
+ if (!isSqliteAvailableSync()) return false;
2583
+ const twin = sqliteTwinPath(jsonPath);
2584
+ const tmp = `${twin}.tmp-${process.pid}`;
2585
+ try {
2586
+ const { DatabaseSync } = loadSqliteSync("code_graph.sqlite_store");
2587
+ let stat;
2588
+ try {
2589
+ stat = fs5.statSync(jsonPath);
2590
+ } catch {
2591
+ return false;
2592
+ }
2593
+ fs5.rmSync(tmp, { force: true });
2594
+ const db = new DatabaseSync(tmp);
2595
+ try {
2596
+ db.exec("PRAGMA journal_mode = MEMORY");
2597
+ db.exec(
2598
+ "CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);CREATE TABLE nodes (id TEXT PRIMARY KEY, label TEXT NOT NULL, kind TEXT, source_file TEXT, l0 INTEGER, l1 INTEGER, l2 INTEGER, l3 INTEGER);CREATE TABLE edges (source TEXT NOT NULL, target TEXT NOT NULL, relation TEXT NOT NULL, confidence TEXT NOT NULL, candidates TEXT, ambiguity_reason TEXT, resolved_via TEXT, provider TEXT);CREATE INDEX edges_source ON edges(source);CREATE INDEX edges_target ON edges(target);CREATE INDEX nodes_label ON nodes(label);CREATE INDEX nodes_source_file ON nodes(source_file);"
2599
+ );
2600
+ const meta = db.prepare("INSERT INTO meta (key, value) VALUES (?, ?)");
2601
+ meta.run("source_checksum", graph.source_checksum);
2602
+ meta.run("json_size", String(stat.size));
2603
+ meta.run("json_mtime_ms", String(stat.mtimeMs));
2604
+ meta.run("graph_json", serialized);
2605
+ meta.run("node_count", String(graph.nodes.length));
2606
+ meta.run("edge_count", String(graph.edges.length));
2607
+ const insNode = db.prepare(
2608
+ "INSERT INTO nodes (id, label, kind, source_file, l0, l1, l2, l3) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
2609
+ );
2610
+ for (const n of graph.nodes) {
2611
+ const loc = n.source_location ?? [];
2612
+ insNode.run(
2613
+ n.id,
2614
+ n.label,
2615
+ n.kind ?? null,
2616
+ n.source_file ?? null,
2617
+ loc[0] ?? null,
2618
+ loc[1] ?? null,
2619
+ loc[2] ?? null,
2620
+ loc[3] ?? null
2621
+ );
2622
+ }
2623
+ const insEdge = db.prepare(
2624
+ "INSERT INTO edges (source, target, relation, confidence, candidates, ambiguity_reason, resolved_via, provider) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"
2625
+ );
2626
+ for (const e of graph.edges) {
2627
+ insEdge.run(
2628
+ e.source,
2629
+ e.target,
2630
+ e.relation,
2631
+ e.confidence,
2632
+ e.candidates ? JSON.stringify(e.candidates) : null,
2633
+ e.ambiguity_reason ?? null,
2634
+ e.resolved_via,
2635
+ e.provider
2636
+ );
2637
+ }
2638
+ stampUserVersion(db, GRAPH_STORE_VERSION);
2639
+ } finally {
2640
+ db.close();
2641
+ }
2642
+ fs5.renameSync(tmp, twin);
2643
+ return true;
2644
+ } catch {
2645
+ fs5.rmSync(tmp, { force: true });
2469
2646
  return false;
2470
2647
  }
2471
- return ENVELOPE_KEYS.every((key) => key in obj);
2472
2648
  }
2473
- function unwrap(stdin_text, default_platform = "generic") {
2474
- const text = (stdin_text ?? "").trim();
2475
- if (!text) {
2476
- return [{}, {}, default_platform];
2477
- }
2478
- let decoded;
2649
+ function loadSerializedFromTwin(jsonPath) {
2650
+ if (!isSqliteAvailableSync()) return null;
2651
+ const twin = sqliteTwinPath(jsonPath);
2652
+ if (!fs5.existsSync(twin)) return null;
2653
+ let stat;
2479
2654
  try {
2480
- decoded = JSON.parse(text);
2655
+ stat = fs5.statSync(jsonPath);
2481
2656
  } catch {
2482
- return [{}, {}, default_platform];
2657
+ return null;
2483
2658
  }
2484
- if (looks_like_envelope(decoded)) {
2485
- const env = decoded;
2486
- let payload2 = env["payload"];
2487
- if (!payload2 || !_isObject4(payload2)) {
2488
- payload2 = {};
2659
+ try {
2660
+ const { DatabaseSync } = loadSqliteSync("code_graph.sqlite_store");
2661
+ const db = new DatabaseSync(twin, { readOnly: true });
2662
+ try {
2663
+ if (readUserVersion(db) !== GRAPH_STORE_VERSION) return null;
2664
+ const rows = db.prepare("SELECT key, value FROM meta").all();
2665
+ const meta = new Map(rows.map((r) => [r.key, r.value]));
2666
+ if (meta.get("json_size") !== String(stat.size)) return null;
2667
+ if (meta.get("json_mtime_ms") !== String(stat.mtimeMs)) return null;
2668
+ const serialized = meta.get("graph_json");
2669
+ return serialized && serialized.length > 0 ? serialized : null;
2670
+ } finally {
2671
+ db.close();
2489
2672
  }
2490
- const platRaw = env["platform"];
2491
- const platform = String(platRaw || default_platform);
2492
- return [env, payload2, platform];
2673
+ } catch {
2674
+ fs5.rmSync(twin, { force: true });
2675
+ return null;
2493
2676
  }
2494
- const payload = _isObject4(decoded) ? decoded : {};
2495
- return [
2496
- {
2497
- schema_version: 1,
2498
- platform: default_platform,
2499
- event: "",
2500
- native_event: "",
2501
- session_id: "",
2502
- workspace_root: "",
2503
- payload,
2504
- settings: {}
2505
- },
2506
- payload,
2507
- default_platform
2508
- ];
2509
2677
  }
2510
-
2511
- // src/scripts/hooks/end_review_nudge_hook.ts
2512
- var MUTATION_LINE_THRESHOLD = 50;
2513
- var UNTRACKED_FILE_CAP = 20;
2514
- var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
2515
- function isSafeTranscriptPath(rawPath, opts = {}) {
2516
- if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
2517
- const homeDir = opts.homeDir ?? os.homedir();
2518
- const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
2519
- try {
2520
- const realHome = fs5.realpathSync(homeDir);
2521
- const realPath = fs5.realpathSync(rawPath);
2522
- const relative3 = path5.relative(realHome, realPath);
2523
- if (relative3 === "" || relative3.startsWith("..") || path5.isAbsolute(relative3)) {
2524
- return false;
2525
- }
2526
- const { size } = fs5.statSync(realPath);
2527
- return size <= maxBytes;
2528
- } catch {
2529
- return false;
2530
- }
2678
+ function toNode(r) {
2679
+ const loc = [r.l0, r.l1, r.l2, r.l3].filter((v) => typeof v === "number");
2680
+ return {
2681
+ id: r.id,
2682
+ label: r.label,
2683
+ kind: r.kind ?? "file",
2684
+ source_file: r.source_file ?? "",
2685
+ source_location: loc
2686
+ };
2531
2687
  }
2532
- var EXIT_WARN2 = 2;
2533
- function parseNumstat(raw) {
2534
- const rows = [];
2535
- for (const line of raw.split("\n")) {
2536
- const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
2537
- if (!m) continue;
2538
- const addedStr = m[1];
2539
- const deletedStr = m[2];
2540
- rows.push({
2541
- added: addedStr === "-" ? 0 : Number(addedStr),
2542
- deleted: deletedStr === "-" ? 0 : Number(deletedStr),
2543
- path: m[3]
2544
- });
2688
+ function toEdge(r) {
2689
+ const e = {
2690
+ source: r.source,
2691
+ target: r.target,
2692
+ relation: r.relation,
2693
+ confidence: r.confidence,
2694
+ // The SQL columns are nullable, the TypeScript fields are not. A null
2695
+ // can only come from a twin emitted before these fields existed, which
2696
+ // GRAPH_STORE_VERSION already refuses — so this coalesce is unreachable
2697
+ // in a healthy tree and is written to fail READABLE rather than to
2698
+ // fabricate: `name-lookup` is the weakest mechanism, so a mislabelled
2699
+ // edge is under-trusted rather than over-trusted by Phase 3's filter.
2700
+ resolved_via: r.resolved_via ?? "name-lookup",
2701
+ provider: r.provider ?? "native"
2702
+ };
2703
+ if (r.candidates) e.candidates = JSON.parse(r.candidates);
2704
+ if (r.ambiguity_reason === "receiver-unknown" || r.ambiguity_reason === "hierarchy-unresolved") {
2705
+ e.ambiguity_reason = r.ambiguity_reason;
2545
2706
  }
2546
- return rows;
2707
+ return e;
2547
2708
  }
2548
- var DOC_PATH_RE = /\.md$/i;
2549
- function isDocPath(p) {
2550
- return DOC_PATH_RE.test(p);
2551
- }
2552
- function nonDocMutatedLines(rows) {
2553
- return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
2554
- }
2555
- function gitNumstatRows(cwd) {
2709
+ function openGraphIndex(jsonPath) {
2710
+ if (!isSqliteAvailableSync()) return null;
2711
+ const twin = sqliteTwinPath(jsonPath);
2712
+ if (!fs5.existsSync(twin)) return null;
2713
+ let stat;
2556
2714
  try {
2557
- const r = spawnSync2("git", ["diff", "--numstat", "HEAD"], {
2558
- cwd,
2559
- encoding: "utf8",
2560
- maxBuffer: 16 * 1024 * 1024
2561
- });
2562
- if (r.status !== 0) return [];
2563
- return parseNumstat(r.stdout ?? "");
2715
+ stat = fs5.statSync(jsonPath);
2564
2716
  } catch {
2565
- return [];
2717
+ return null;
2566
2718
  }
2567
- }
2568
- function untrackedNonDocFiles(cwd) {
2569
2719
  try {
2570
- const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
2571
- cwd,
2572
- encoding: "utf8",
2573
- maxBuffer: 16 * 1024 * 1024
2574
- });
2575
- if (r.status !== 0) return [];
2576
- return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
2720
+ const { DatabaseSync } = loadSqliteSync("code_graph.sqlite_store");
2721
+ const db = new DatabaseSync(twin, { readOnly: true });
2722
+ try {
2723
+ if (readUserVersion(db) !== GRAPH_STORE_VERSION) {
2724
+ db.close();
2725
+ return null;
2726
+ }
2727
+ const rows = db.prepare("SELECT key, value FROM meta").all();
2728
+ const meta = new Map(rows.map((r) => [r.key, r.value]));
2729
+ if (meta.get("json_size") !== String(stat.size) || meta.get("json_mtime_ms") !== String(stat.mtimeMs)) {
2730
+ db.close();
2731
+ return null;
2732
+ }
2733
+ const nodeCount = Number(meta.get("node_count") ?? NaN);
2734
+ const edgeCount = Number(meta.get("edge_count") ?? NaN);
2735
+ if (!Number.isFinite(nodeCount) || !Number.isFinite(edgeCount)) {
2736
+ db.close();
2737
+ return null;
2738
+ }
2739
+ const qNode = db.prepare("SELECT * FROM nodes WHERE id = ?");
2740
+ const qFrom = db.prepare("SELECT * FROM edges WHERE source = ?");
2741
+ const qTo = db.prepare("SELECT * FROM edges WHERE target = ?");
2742
+ const qLabel = db.prepare("SELECT id FROM nodes WHERE label = ? LIMIT ?");
2743
+ return {
2744
+ nodeCount,
2745
+ edgeCount,
2746
+ sourceChecksum: meta.get("source_checksum") ?? "",
2747
+ getNode(id) {
2748
+ const r = qNode.get(id);
2749
+ return r ? toNode(r) : void 0;
2750
+ },
2751
+ hasNode(id) {
2752
+ return qNode.get(id) !== void 0;
2753
+ },
2754
+ edgesFrom(id) {
2755
+ return qFrom.all(id).map(toEdge);
2756
+ },
2757
+ edgesTo(id) {
2758
+ return qTo.all(id).map(toEdge);
2759
+ },
2760
+ idsByLabel(labelText, limit) {
2761
+ return qLabel.all(labelText, limit).map((r) => r.id);
2762
+ },
2763
+ idsInFiles(files) {
2764
+ if (files.length === 0) return [];
2765
+ const out = [];
2766
+ const CHUNK = 400;
2767
+ for (let i = 0; i < files.length; i += CHUNK) {
2768
+ const slice = files.slice(i, i + CHUNK);
2769
+ const marks = slice.map(() => "?").join(",");
2770
+ const stmt = db.prepare(
2771
+ `SELECT id FROM nodes WHERE kind != 'file' AND source_file IN (${marks})`
2772
+ );
2773
+ for (const r of stmt.all(...slice)) out.push(r.id);
2774
+ }
2775
+ return out;
2776
+ },
2777
+ allNodeIds() {
2778
+ return db.prepare("SELECT id FROM nodes ORDER BY id").all().map(
2779
+ (r) => r.id
2780
+ );
2781
+ },
2782
+ lexicalCorpus() {
2783
+ return db.prepare("SELECT id, label FROM nodes").all().map(
2784
+ (r) => ({ id: r.id, text: `${r.label} ${r.id}` })
2785
+ );
2786
+ },
2787
+ close() {
2788
+ db.close();
2789
+ }
2790
+ };
2791
+ } catch (err) {
2792
+ db.close();
2793
+ throw err;
2794
+ }
2577
2795
  } catch {
2578
- return [];
2796
+ return null;
2579
2797
  }
2580
2798
  }
2581
- var BINARY_PROBE_BYTES = 8e3;
2582
- var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
2583
- function untrackedFileLineCount(cwd, relPath) {
2584
- try {
2585
- const abs = path5.resolve(cwd, relPath);
2586
- const size = fs5.statSync(abs).size;
2587
- if (size === 0) return 0;
2588
- if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
2589
- return MUTATION_LINE_THRESHOLD + 1;
2799
+
2800
+ // src/scripts/code_graph/foreign.ts
2801
+ import { createHash } from "node:crypto";
2802
+ var RELATION_MAP = {
2803
+ calls: "calls",
2804
+ call: "calls",
2805
+ invokes: "calls",
2806
+ imports: "imports",
2807
+ import: "imports",
2808
+ requires: "imports",
2809
+ uses: "uses",
2810
+ use: "uses",
2811
+ references: "uses",
2812
+ inherits: "inherits",
2813
+ extends: "inherits",
2814
+ implements: "inherits",
2815
+ member: "member",
2816
+ contains: "member",
2817
+ tests: "tests",
2818
+ test: "tests"
2819
+ };
2820
+ var KINDS2 = /* @__PURE__ */ new Set([
2821
+ "file",
2822
+ "class",
2823
+ "interface",
2824
+ "trait",
2825
+ "function",
2826
+ "method",
2827
+ "constant",
2828
+ "type",
2829
+ "enum",
2830
+ "skipped"
2831
+ ]);
2832
+ function isObject(v) {
2833
+ return typeof v === "object" && v !== null && !Array.isArray(v);
2834
+ }
2835
+ function looksForeign(doc) {
2836
+ if (!isObject(doc)) return false;
2837
+ if (!Array.isArray(doc["nodes"])) return false;
2838
+ if (Array.isArray(doc["links"])) return true;
2839
+ return Array.isArray(doc["edges"]) && typeof doc["schema_version"] !== "number";
2840
+ }
2841
+ function adaptForeignGraph(doc) {
2842
+ if (!isObject(doc)) return null;
2843
+ const rawNodes = doc["nodes"];
2844
+ const rawEdges = doc["links"] ?? doc["edges"];
2845
+ if (!Array.isArray(rawNodes) || !Array.isArray(rawEdges)) return null;
2846
+ const nodes = [];
2847
+ const ids = /* @__PURE__ */ new Set();
2848
+ for (const n of rawNodes) {
2849
+ if (!isObject(n)) continue;
2850
+ const id = n["id"];
2851
+ if (typeof id !== "string" || id === "") continue;
2852
+ const kind = typeof n["kind"] === "string" && KINDS2.has(n["kind"]) ? n["kind"] : "skipped";
2853
+ const sourceFile = typeof n["source_file"] === "string" ? n["source_file"] : typeof n["file"] === "string" ? n["file"] : "";
2854
+ nodes.push({
2855
+ id,
2856
+ label: typeof n["label"] === "string" ? n["label"] : id,
2857
+ kind,
2858
+ source_file: sourceFile,
2859
+ // A foreign node rarely carries a span. An empty one is honest —
2860
+ // it says "no location recorded", where a fabricated [1,1] would
2861
+ // point a reader at the top of a file for no reason.
2862
+ source_location: Array.isArray(n["source_location"]) ? n["source_location"] : []
2863
+ });
2864
+ ids.add(id);
2865
+ }
2866
+ const edges = [];
2867
+ const unmapped = {};
2868
+ let malformed = 0;
2869
+ for (const e of rawEdges) {
2870
+ if (!isObject(e)) {
2871
+ malformed += 1;
2872
+ continue;
2590
2873
  }
2591
- const buf = fs5.readFileSync(abs);
2592
- if (buf.length === 0) return 0;
2593
- const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
2594
- if (probe.includes(0)) return 0;
2595
- let lines = 0;
2596
- for (const byte of buf) {
2597
- if (byte === 10) lines += 1;
2874
+ const source = e["source"];
2875
+ const target = e["target"];
2876
+ if (typeof source !== "string" || typeof target !== "string" || !ids.has(source)) {
2877
+ malformed += 1;
2878
+ continue;
2598
2879
  }
2599
- if (buf[buf.length - 1] !== 10) lines += 1;
2600
- return lines;
2601
- } catch {
2602
- return 0;
2880
+ const rawRelation = typeof e["relation"] === "string" ? e["relation"] : "";
2881
+ const relation = RELATION_MAP[rawRelation.toLowerCase()];
2882
+ if (relation === void 0) {
2883
+ const key = rawRelation === "" ? "(none)" : rawRelation;
2884
+ unmapped[key] = (unmapped[key] ?? 0) + 1;
2885
+ continue;
2886
+ }
2887
+ edges.push({
2888
+ source,
2889
+ target,
2890
+ relation,
2891
+ // Not EXTRACTED: this package did not extract it and cannot say
2892
+ // how the tool that did arrived at it.
2893
+ confidence: "AMBIGUOUS",
2894
+ resolved_via: "foreign",
2895
+ provider: "foreign"
2896
+ });
2603
2897
  }
2898
+ const counts = { EXTRACTED: 0, INFERRED: 0, AMBIGUOUS: edges.length };
2899
+ const graph = {
2900
+ schema_version: SCHEMA_VERSION,
2901
+ source_checksum: createHash("sha256").update(JSON.stringify(doc), "utf-8").digest("hex"),
2902
+ languages: [],
2903
+ grammar_abi: 0,
2904
+ edge_confidence_counts: counts,
2905
+ suppressed_edge_counts: { dynamic_no_candidate: 0 },
2906
+ nodes,
2907
+ edges
2908
+ };
2909
+ return { graph, unmapped_relations: unmapped, malformed_edges: malformed };
2604
2910
  }
2605
- function totalNonDocMutatedLinesWithMeasure(cwd) {
2606
- const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
2607
- const untracked = untrackedNonDocFiles(cwd);
2608
- if (untracked.length > UNTRACKED_FILE_CAP) {
2609
- return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
2911
+
2912
+ // src/scripts/code_graph/query.ts
2913
+ function indexBackedGraph(index, source, answeredBy) {
2914
+ const nodeCache = /* @__PURE__ */ new Map();
2915
+ const readNode = (id) => {
2916
+ if (nodeCache.has(id)) return nodeCache.get(id);
2917
+ const n = index.getNode(id);
2918
+ nodeCache.set(id, n);
2919
+ return n;
2920
+ };
2921
+ let lexical = null;
2922
+ return {
2923
+ graph: null,
2924
+ source,
2925
+ answeredBy,
2926
+ byId: { get: readNode, has: (id) => readNode(id) !== void 0 },
2927
+ out: { get: (id) => index.edgesFrom(id) },
2928
+ in: { get: (id) => index.edgesTo(id) },
2929
+ idsByLabel: (labelText, limit) => index.idsByLabel(labelText, limit),
2930
+ idsInFiles: (files) => index.idsInFiles(files),
2931
+ allNodeIds: () => index.allNodeIds(),
2932
+ lex: () => {
2933
+ lexical ??= new LexicalIndex(index.lexicalCorpus());
2934
+ return lexical;
2935
+ },
2936
+ close: () => index.close()
2937
+ };
2938
+ }
2939
+ function loadGraph(graphPath, source = graphPath) {
2940
+ const index = openGraphIndex(graphPath);
2941
+ if (index) {
2942
+ if (index.edgeCount >= INDEXED_READ_MIN_EDGES) return indexBackedGraph(index, source, graphPath);
2943
+ index.close();
2944
+ }
2945
+ const fromTwin = loadSerializedFromTwin(graphPath);
2946
+ const raw = fromTwin ?? fs6.readFileSync(graphPath, "utf-8");
2947
+ const parsed = JSON.parse(raw);
2948
+ let foreignAdapted = false;
2949
+ let candidate = parsed;
2950
+ if (looksForeign(parsed)) {
2951
+ const adapted = adaptForeignGraph(parsed);
2952
+ if (adapted !== null) {
2953
+ candidate = adapted.graph;
2954
+ foreignAdapted = true;
2955
+ }
2956
+ }
2957
+ const v = validateGraph(candidate);
2958
+ if (!v.ok) throw new Error(`invalid graph at ${graphPath}: ${v.errors.slice(0, 3).join("; ")}`);
2959
+ const graph = candidate;
2960
+ if (fromTwin === null && !foreignAdapted) {
2961
+ emitSqliteTwin(graph, raw, graphPath);
2962
+ }
2963
+ const byId = new Map(graph.nodes.map((n) => [n.id, n]));
2964
+ const outM = /* @__PURE__ */ new Map();
2965
+ const inM = /* @__PURE__ */ new Map();
2966
+ const add = (m, k, e) => {
2967
+ const arr = m.get(k);
2968
+ if (arr) arr.push(e);
2969
+ else m.set(k, [e]);
2970
+ };
2971
+ for (const e of graph.edges) {
2972
+ add(outM, e.source, e);
2973
+ add(inM, e.target, e);
2610
2974
  }
2611
- const untrackedLines = untracked.reduce(
2612
- (sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
2613
- 0
2614
- );
2615
- return { lines: trackedLines + untrackedLines, measure: "exact" };
2975
+ const lex = new LexicalIndex(graph.nodes.map((n) => ({ id: n.id, text: `${n.label} ${n.id}` })));
2976
+ return {
2977
+ graph,
2978
+ source,
2979
+ answeredBy: graphPath,
2980
+ byId,
2981
+ out: outM,
2982
+ in: inM,
2983
+ idsByLabel: (labelText, limit) => graph.nodes.filter((n) => n.label === labelText).slice(0, limit).map((n) => n.id),
2984
+ idsInFiles: (files) => {
2985
+ const set = new Set(files);
2986
+ return graph.nodes.filter((n) => set.has(n.source_file) && n.kind !== "file").map((n) => n.id);
2987
+ },
2988
+ allNodeIds: () => graph.nodes.map((n) => n.id).sort((a, b) => a < b ? -1 : a > b ? 1 : 0),
2989
+ lex: () => lex,
2990
+ close: () => {
2991
+ }
2992
+ };
2616
2993
  }
2617
- function isObject(v) {
2618
- return typeof v === "object" && v !== null && !Array.isArray(v);
2994
+ function resolveSeedsTiered(g, seed, limit = 5) {
2995
+ if (g.byId.has(seed)) return { ids: [seed], weak: false };
2996
+ const exactLabel = g.idsByLabel(seed, limit);
2997
+ if (exactLabel.length) return { ids: exactLabel, weak: false };
2998
+ const ranked = g.lex().rank(tokenize(seed)).filter((r) => r.score > 0);
2999
+ return { ids: ranked.slice(0, limit).map((r) => r.id), weak: true };
2619
3000
  }
2620
- function str(v) {
2621
- return typeof v === "string" ? v : "";
3001
+ function resolveSeeds(g, seed, limit = 5) {
3002
+ return resolveSeedsTiered(g, seed, limit).ids;
2622
3003
  }
2623
- var DISPATCH_TOOL_RE = /^(agent|task)$/i;
2624
- var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
2625
- 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;
2626
- var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
2627
- function scanTranscriptForReviewer(transcriptText) {
2628
- for (const raw of transcriptText.split("\n")) {
2629
- const line = raw.trim();
2630
- if (!line) continue;
2631
- let obj;
2632
- try {
2633
- obj = JSON.parse(line);
2634
- } catch {
3004
+
3005
+ // src/scripts/code_graph/verbs.ts
3006
+ import * as path5 from "node:path";
3007
+ function isAcceptedEdge(e) {
3008
+ return isStatedResolution(e.resolved_via);
3009
+ }
3010
+ function foreignRefusal(g) {
3011
+ const edges = g.graph?.edges ?? [];
3012
+ if (edges.length === 0) return null;
3013
+ if (!edges.every((e) => e.resolved_via === "foreign")) return null;
3014
+ return 'every edge in this graph was read from a file this package did not build, so all of them are guesses and none is gate-grade. A gate verb would accept no edge and report an empty answer, which reads as "nothing found" rather than "nothing trusted". Build a native graph (`agent-config graph build`) to use this verb, or use `query` and `explain`, which answer from foreign edges and say so.';
3015
+ }
3016
+ function viaHistogram(edges) {
3017
+ const counts = /* @__PURE__ */ new Map();
3018
+ for (const e of edges) counts.set(e.resolved_via, (counts.get(e.resolved_via) ?? 0) + 1);
3019
+ if (counts.size === 0) return "(none)";
3020
+ return [...counts.entries()].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)).map(([via, n]) => `${via} ${n}`).join(" \xB7 ");
3021
+ }
3022
+ function readFor(g, id) {
3023
+ const n = g.byId.get(id);
3024
+ if (!n) return null;
3025
+ const loc = n.source_location;
3026
+ return { path: n.source_file, lines: loc.length >= 3 ? [loc[0], loc[2]] : null };
3027
+ }
3028
+ function dedupeReads(reads) {
3029
+ const m = /* @__PURE__ */ new Map();
3030
+ for (const r of reads) {
3031
+ if (!r) continue;
3032
+ m.set(r.lines ? `${r.path}:${r.lines[0]}-${r.lines[1]}` : r.path, r);
3033
+ }
3034
+ return [...m.values()];
3035
+ }
3036
+ function seedsForFiles(g, files) {
3037
+ const symbols = g.idsInFiles(files);
3038
+ const seeds = new Set(symbols);
3039
+ const unresolved = [];
3040
+ const notIndexed = [];
3041
+ for (const f of files) {
3042
+ if (g.byId.has(f)) {
3043
+ seeds.add(f);
2635
3044
  continue;
2636
3045
  }
2637
- if (!isObject(obj) || obj["type"] !== "assistant") continue;
2638
- const msg = obj["message"];
2639
- if (!isObject(msg)) continue;
2640
- const content = msg["content"];
2641
- if (!Array.isArray(content)) continue;
2642
- for (const blk of content) {
2643
- if (!isObject(blk) || blk["type"] !== "tool_use") continue;
2644
- const name = str(blk["name"]);
2645
- const input = blk["input"];
2646
- const inputObj = isObject(input) ? input : {};
2647
- if (DISPATCH_TOOL_RE.test(name)) {
2648
- const label = [
2649
- str(inputObj["description"]),
2650
- str(inputObj["subagent_type"])
2651
- ].filter(Boolean).join(" ");
2652
- if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
2653
- const prompt = str(inputObj["prompt"]);
2654
- if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
2655
- continue;
2656
- }
2657
- if (/^skill$/i.test(name)) {
2658
- const skillName = str(inputObj["skill"]) || str(inputObj["name"]) || str(inputObj["command"]);
2659
- if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
3046
+ if (symbols.some((s) => s.startsWith(`${f}#`))) continue;
3047
+ if (EXT_LANG[path5.extname(f).toLowerCase()] === void 0) notIndexed.push(f);
3048
+ else unresolved.push(f);
3049
+ }
3050
+ return {
3051
+ seeds: [...seeds].sort(),
3052
+ unresolved: unresolved.sort(),
3053
+ not_indexed: notIndexed.sort()
3054
+ };
3055
+ }
3056
+ function testsFor(g, symbol, state) {
3057
+ const seeds = resolveSeeds(g, symbol);
3058
+ const targets = new Set(seeds);
3059
+ for (const s of seeds) {
3060
+ const f = g.byId.get(s)?.source_file;
3061
+ if (f && g.byId.has(f)) targets.add(f);
3062
+ }
3063
+ const accepted = [];
3064
+ const rejected = [];
3065
+ const tests = /* @__PURE__ */ new Set();
3066
+ for (const t of targets) {
3067
+ for (const e of g.in.get(t) ?? []) {
3068
+ if (e.relation !== "tests") continue;
3069
+ if (!isAcceptedEdge(e)) {
3070
+ rejected.push(e);
2660
3071
  continue;
2661
3072
  }
2662
- if (REVIEW_SKILL_NAME_RE.test(name)) return true;
3073
+ accepted.push(e);
3074
+ tests.add(e.source);
2663
3075
  }
2664
3076
  }
2665
- return false;
3077
+ const list = [...tests].sort();
3078
+ return {
3079
+ source: g.source,
3080
+ state,
3081
+ accepted_via: viaHistogram(accepted),
3082
+ rejected_via: viaHistogram(rejected),
3083
+ seeds,
3084
+ tests: list,
3085
+ accepted_edges: accepted,
3086
+ rejected_edges: rejected,
3087
+ lines: list,
3088
+ recommended_reads: dedupeReads(list.map((t) => readFor(g, t)))
3089
+ };
2666
3090
  }
2667
- function buildAdvisoryLine(diffLines) {
2668
- return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
3091
+ function untested(g, files, state) {
3092
+ const foreign = foreignRefusal(g);
3093
+ if (foreign !== null) {
3094
+ return {
3095
+ source: g.source,
3096
+ state,
3097
+ accepted_via: "(none)",
3098
+ rejected_via: "(none)",
3099
+ refusal: foreign,
3100
+ refusal_reason: "foreign-edges-not-accepted",
3101
+ seeds: [],
3102
+ unresolved_files: [],
3103
+ not_indexed_files: [],
3104
+ untested: [],
3105
+ tested: [],
3106
+ lines: [],
3107
+ recommended_reads: []
3108
+ };
3109
+ }
3110
+ const { seeds, unresolved, not_indexed } = seedsForFiles(g, files);
3111
+ const accepted = [];
3112
+ const rejected = [];
3113
+ const tested = [];
3114
+ const bare = [];
3115
+ for (const s of seeds) {
3116
+ if (g.byId.get(s)?.kind === "file") continue;
3117
+ const r = testsFor(g, s, state);
3118
+ accepted.push(...r.accepted_edges);
3119
+ rejected.push(...r.rejected_edges);
3120
+ if (r.tests.length) tested.push(s);
3121
+ else bare.push(s);
3122
+ }
3123
+ return {
3124
+ source: g.source,
3125
+ state,
3126
+ accepted_via: viaHistogram(accepted),
3127
+ rejected_via: viaHistogram(rejected),
3128
+ refusal: null,
3129
+ refusal_reason: null,
3130
+ seeds,
3131
+ unresolved_files: unresolved,
3132
+ not_indexed_files: not_indexed,
3133
+ untested: bare.sort(),
3134
+ tested: tested.sort(),
3135
+ lines: bare.sort(),
3136
+ recommended_reads: dedupeReads(bare.map((b) => readFor(g, b)))
3137
+ };
2669
3138
  }
2670
- function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
2671
- if (is_replay_mode()) return;
2672
- const ts = (/* @__PURE__ */ new Date()).toISOString();
2673
- const { line, errors } = buildReviewSkippedLine({
2674
- diff_lines: diffLines,
2675
- mutation_measure: mutationMeasure,
2676
- ts,
2677
- id: crypto.randomUUID(),
2678
- baseline
2679
- });
2680
- if (errors.length || line === null) return;
3139
+
3140
+ // src/scripts/_lib/graph_feeder_record.ts
3141
+ var MAX_PATHS = 5;
3142
+ var MAX_ROWS_PER_SESSION = 200;
3143
+ function feederFile(workspaceRoot, sessionKey) {
3144
+ return path6.join(workspaceRoot, "agents", "state", "graph-feeder", `${sessionKey}.jsonl`);
3145
+ }
3146
+ function graphUntestedVerdict(root, state, paths) {
3147
+ const none = { verdict: null, untested: 0, tested: 0 };
3148
+ if (state !== "fresh" && state !== "edited") return none;
3149
+ if (paths.length === 0) return none;
3150
+ let picked;
2681
3151
  try {
2682
- const dir = path5.join(workspaceRoot, "agents", "runtime", "state", "audit");
2683
- fs5.mkdirSync(dir, { recursive: true });
2684
- const file = path5.join(dir, `${ts.slice(0, 7)}.jsonl`);
2685
- fs5.appendFileSync(file, `${JSON.stringify(line)}
2686
- `, "utf8");
3152
+ picked = pickSource(detectSources(root, path6.join(root, NATIVE_CACHE_REL)));
2687
3153
  } catch {
3154
+ return none;
2688
3155
  }
2689
- }
2690
- function sessionStateDir(workspaceRoot) {
2691
- return path5.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
2692
- }
2693
- function sessionStateFile(workspaceRoot, sessionKey) {
2694
- return path5.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
2695
- }
2696
- function deriveSessionKey2(envelope, payload) {
2697
- const raw = str(envelope["session_id"]) || str(payload["transcript_path"] ?? payload["transcriptPath"]);
2698
- const material = raw || "unknown-session";
2699
- return crypto.createHash("sha256").update(material).digest("hex");
2700
- }
2701
- function hasFiredThisSession(workspaceRoot, sessionKey) {
3156
+ if (!picked || picked.kind === "scip") return none;
3157
+ let g;
2702
3158
  try {
2703
- return fs5.existsSync(sessionStateFile(workspaceRoot, sessionKey));
3159
+ g = loadGraph(picked.path, `${picked.kind}:${path6.relative(root, picked.path)}`);
2704
3160
  } catch {
2705
- return false;
3161
+ return none;
2706
3162
  }
2707
- }
2708
- function markFiredThisSession(workspaceRoot, sessionKey, ts) {
2709
3163
  try {
2710
- atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
3164
+ const r = untested(g, paths, state);
3165
+ if (r.seeds.length === 0) return { verdict: "no-seeds", untested: 0, tested: 0 };
3166
+ return {
3167
+ verdict: r.untested.length > 0 ? "untested" : "tested",
3168
+ untested: r.untested.length,
3169
+ tested: r.tested.length
3170
+ };
2711
3171
  } catch {
3172
+ return none;
3173
+ } finally {
3174
+ try {
3175
+ g.close();
3176
+ } catch {
3177
+ }
2712
3178
  }
2713
3179
  }
2714
- function _read_stdin() {
2715
- return readHookStdin();
2716
- }
2717
- var FALLBACK_TO_TELEMETRY = {
2718
- absent: "absent",
2719
- unreadable: "unreadable",
2720
- "head-moved": "head_moved",
2721
- "mixed-measure": "mixed_measure"
2722
- };
2723
- function main4() {
2724
- const [envelope, payload] = unwrap(_read_stdin(), "claude");
2725
- const event = String(envelope["event"] ?? "");
2726
- if (event !== "" && event !== "stop") {
2727
- return 0;
2728
- }
2729
- const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
2730
- let measuredLines;
2731
- let mutationMeasure;
3180
+ function appendFeederRow(workspaceRoot, sessionKey, row) {
2732
3181
  try {
2733
- const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
2734
- measuredLines = measured.lines;
2735
- mutationMeasure = measured.measure;
3182
+ const file = feederFile(workspaceRoot, sessionKey);
3183
+ let existing = 0;
3184
+ try {
3185
+ existing = fs7.readFileSync(file, "utf-8").split("\n").filter((l) => l.trim() !== "").length;
3186
+ } catch {
3187
+ }
3188
+ if (existing >= MAX_ROWS_PER_SESSION) return;
3189
+ fs7.mkdirSync(path6.dirname(file), { recursive: true });
3190
+ fs7.appendFileSync(file, `${JSON.stringify(row)}
3191
+ `);
2736
3192
  } catch {
2737
- return 0;
2738
- }
2739
- const sessionKey = deriveSessionKey2(envelope, payload);
2740
- const baselineOutcome = applyBaseline(
2741
- measuredLines,
2742
- readBaseline(workspace_root, sessionKey),
2743
- // A THUNK, not a value: on the `absent` and `unreadable` branches the
2744
- // result is discarded, and those two are the steady state on every host
2745
- // without this nudge and on every session before its first baseline
2746
- // write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
2747
- // each of those stops for nothing.
2748
- () => currentHeadSha(workspace_root),
2749
- mutationMeasure
2750
- );
2751
- const diffLines = baselineOutcome.lines;
2752
- const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
2753
- if (diffLines <= MUTATION_LINE_THRESHOLD) {
2754
- return 0;
2755
3193
  }
2756
- if (hasFiredThisSession(workspace_root, sessionKey)) {
2757
- return 0;
2758
- }
2759
- const transcriptPath = str(
2760
- payload["transcript_path"] ?? payload["transcriptPath"]
2761
- ).trim();
2762
- let reviewerRan = false;
2763
- if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
2764
- try {
2765
- reviewerRan = scanTranscriptForReviewer(fs5.readFileSync(transcriptPath, "utf-8"));
2766
- } catch {
2767
- reviewerRan = false;
2768
- }
2769
- }
2770
- if (reviewerRan) {
2771
- return 0;
2772
- }
2773
- markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
2774
- try {
2775
- appendReviewSkippedTelemetry(
2776
- workspace_root,
2777
- diffLines,
2778
- mutationMeasure,
2779
- baselineApplication
2780
- );
2781
- } catch {
2782
- }
2783
- process.stdout.write(
2784
- `${JSON.stringify({
2785
- decision: "warn",
2786
- reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
2787
- additional_context: buildAdvisoryLine(diffLines)
2788
- })}
2789
- `
2790
- );
2791
- return EXIT_WARN2;
2792
3194
  }
2793
- function _isCliEntry4() {
2794
- if (true) {
2795
- return false;
2796
- }
2797
- if (process.argv[1] === void 0) {
3195
+ function buildFeederRow(input) {
3196
+ return {
3197
+ at: input.at ?? (/* @__PURE__ */ new Date()).toISOString(),
3198
+ turn: input.turn,
3199
+ layer: input.layer,
3200
+ graph_state: input.state,
3201
+ f: input.fFired,
3202
+ f_mode: input.fMode,
3203
+ graph: input.graph.verdict,
3204
+ graph_untested: input.graph.untested,
3205
+ graph_tested: input.graph.tested,
3206
+ paths: input.paths.slice(0, MAX_PATHS).map((p) => p),
3207
+ path_count: input.paths.length
3208
+ };
3209
+ }
3210
+
3211
+ // src/scripts/hooks/envelope.ts
3212
+ var ENVELOPE_KEYS = [
3213
+ "schema_version",
3214
+ "platform",
3215
+ "event",
3216
+ "payload"
3217
+ ];
3218
+ function _isObject4(obj) {
3219
+ return typeof obj === "object" && obj !== null && !Array.isArray(obj);
3220
+ }
3221
+ function looks_like_envelope(obj) {
3222
+ if (!_isObject4(obj)) {
2798
3223
  return false;
2799
3224
  }
2800
- const argvUrl = pathToFileURL4(path5.resolve(process.argv[1])).href;
2801
- if (import.meta.url === argvUrl) {
2802
- return true;
3225
+ return ENVELOPE_KEYS.every((key) => key in obj);
3226
+ }
3227
+ function unwrap(stdin_text, default_platform = "generic") {
3228
+ const text = (stdin_text ?? "").trim();
3229
+ if (!text) {
3230
+ return [{}, {}, default_platform];
2803
3231
  }
3232
+ let decoded;
2804
3233
  try {
2805
- const here = fs5.realpathSync(fileURLToPath4(import.meta.url));
2806
- const argv = fs5.realpathSync(path5.resolve(process.argv[1]));
2807
- return here === argv;
3234
+ decoded = JSON.parse(text);
2808
3235
  } catch {
2809
- return false;
3236
+ return [{}, {}, default_platform];
2810
3237
  }
2811
- }
2812
- if (_isCliEntry4()) {
2813
- process.exit(main4());
3238
+ if (looks_like_envelope(decoded)) {
3239
+ const env = decoded;
3240
+ let payload2 = env["payload"];
3241
+ if (!payload2 || !_isObject4(payload2)) {
3242
+ payload2 = {};
3243
+ }
3244
+ const platRaw = env["platform"];
3245
+ const platform = String(platRaw || default_platform);
3246
+ return [env, payload2, platform];
3247
+ }
3248
+ const payload = _isObject4(decoded) ? decoded : {};
3249
+ return [
3250
+ {
3251
+ schema_version: 1,
3252
+ platform: default_platform,
3253
+ event: "",
3254
+ native_event: "",
3255
+ session_id: "",
3256
+ workspace_root: "",
3257
+ payload,
3258
+ settings: {}
3259
+ },
3260
+ payload,
3261
+ default_platform
3262
+ ];
2814
3263
  }
2815
3264
 
2816
3265
  // src/scripts/hooks/subagent_ledger_hook.ts
2817
- import * as crypto2 from "node:crypto";
2818
- import * as fs6 from "node:fs";
2819
- import * as path6 from "node:path";
2820
- import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
3266
+ import * as crypto from "node:crypto";
3267
+ import * as fs8 from "node:fs";
3268
+ import * as path7 from "node:path";
3269
+ import { fileURLToPath as fileURLToPath4, pathToFileURL as pathToFileURL4 } from "node:url";
2821
3270
 
2822
3271
  // src/scripts/_lib/subagent_response.ts
2823
3272
  var MAX_SUMMARY_CHARS = 2e3;
@@ -2918,22 +3367,57 @@ function validateResponse(input) {
2918
3367
  return { valid: errors.length === 0, errors };
2919
3368
  }
2920
3369
 
2921
- // src/scripts/hooks/subagent_ledger_hook.ts
3370
+ // src/scripts/hooks/exit_codes.ts
2922
3371
  var EXIT_ALLOW3 = 0;
3372
+ var EXIT_BLOCK = 1;
3373
+ var EXIT_WARN2 = 2;
3374
+ var EXIT_ERROR = 3;
3375
+ var EXIT_CODES = Object.freeze([
3376
+ Object.freeze({
3377
+ code: EXIT_ALLOW3,
3378
+ name: "allow",
3379
+ meaning: "no objection; the dispatcher passes the call through",
3380
+ owner: "every concern \u2014 the default verdict",
3381
+ authorizedBy: "nothing; allowing needs no authority"
3382
+ }),
3383
+ Object.freeze({
3384
+ code: EXIT_BLOCK,
3385
+ name: "block",
3386
+ meaning: "refuse the call; the dispatcher lowers this onto the host deny channel",
3387
+ owner: "a concern declared `severity: blocking` in hook_manifest.yaml",
3388
+ 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"
3389
+ }),
3390
+ Object.freeze({
3391
+ code: EXIT_WARN2,
3392
+ name: "warn",
3393
+ meaning: "advisory; the dispatcher exits 0 and surfaces the reason",
3394
+ owner: "any concern",
3395
+ authorizedBy: "nothing; warning is the ceiling for an advisory concern and is always available to a blocking one"
3396
+ }),
3397
+ Object.freeze({
3398
+ code: EXIT_ERROR,
3399
+ name: "error",
3400
+ 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",
3401
+ owner: "no concern emits this on purpose; the runtime does",
3402
+ authorizedBy: "hook_manifest.yaml `severity` alone \u2014 blocking refuses, anything else fails open; `fail_closed` is no longer read on this branch. See docs/contracts/hook-architecture-v1.md \xA7 Exit-code semantics"
3403
+ })
3404
+ ]);
3405
+
3406
+ // src/scripts/hooks/subagent_ledger_hook.ts
2923
3407
  function isObject2(v) {
2924
3408
  return typeof v === "object" && v !== null && !Array.isArray(v);
2925
3409
  }
2926
- var LEDGER_DIR = path6.join("agents", "runtime", "state", "subagent-ledger");
3410
+ var LEDGER_DIR = path7.join("agents", "runtime", "state", "subagent-ledger");
2927
3411
  var OPEN_SUBDIR = "open";
2928
3412
  var OPEN_RECORD_TTL_MS = 24 * 60 * 60 * 1e3;
2929
3413
  function refFor(agentId) {
2930
- return crypto2.createHash("sha256").update(agentId, "utf8").digest("hex").slice(0, 12);
3414
+ return crypto.createHash("sha256").update(agentId, "utf8").digest("hex").slice(0, 12);
2931
3415
  }
2932
3416
  function unwrapPayload(envelope) {
2933
3417
  const inner = envelope["payload"];
2934
3418
  return isObject2(inner) ? inner : envelope;
2935
3419
  }
2936
- function str2(payload, ...keys) {
3420
+ function str(payload, ...keys) {
2937
3421
  for (const k of keys) {
2938
3422
  const v = payload[k];
2939
3423
  if (typeof v === "string" && v) return v;
@@ -2941,11 +3425,11 @@ function str2(payload, ...keys) {
2941
3425
  return null;
2942
3426
  }
2943
3427
  function extractParentId(payload, ownId) {
2944
- const explicit = str2(payload, "parent_agent_id", "parentAgentId");
3428
+ const explicit = str(payload, "parent_agent_id", "parentAgentId");
2945
3429
  if (explicit) return explicit;
2946
3430
  const session = payload["session"];
2947
3431
  if (isObject2(session)) {
2948
- const sessionAgent = str2(session, "agent_id", "agentId");
3432
+ const sessionAgent = str(session, "agent_id", "agentId");
2949
3433
  if (sessionAgent && sessionAgent !== ownId) return sessionAgent;
2950
3434
  }
2951
3435
  return null;
@@ -3013,10 +3497,10 @@ function _jsonObjectCandidates(message) {
3013
3497
  return out;
3014
3498
  }
3015
3499
  function openDir(root) {
3016
- return path6.join(root, LEDGER_DIR, OPEN_SUBDIR);
3500
+ return path7.join(root, LEDGER_DIR, OPEN_SUBDIR);
3017
3501
  }
3018
3502
  function openFile(root, ref) {
3019
- return path6.join(openDir(root), `${ref}.json`);
3503
+ return path7.join(openDir(root), `${ref}.json`);
3020
3504
  }
3021
3505
  var DEPTH_BASES = /* @__PURE__ */ new Set(["observed", "asserted-parent", "assumed-root"]);
3022
3506
  function _asOpenRecord(value, expectedRef) {
@@ -3043,7 +3527,7 @@ function readOpenRecords(root) {
3043
3527
  const out = /* @__PURE__ */ new Map();
3044
3528
  let names;
3045
3529
  try {
3046
- names = fs6.readdirSync(openDir(root));
3530
+ names = fs8.readdirSync(openDir(root));
3047
3531
  } catch {
3048
3532
  return out;
3049
3533
  }
@@ -3051,7 +3535,7 @@ function readOpenRecords(root) {
3051
3535
  if (!name.endsWith(".json")) continue;
3052
3536
  const expectedRef = name.slice(0, -".json".length);
3053
3537
  try {
3054
- const raw = fs6.readFileSync(path6.join(openDir(root), name), "utf8");
3538
+ const raw = fs8.readFileSync(path7.join(openDir(root), name), "utf8");
3055
3539
  const rec = _asOpenRecord(JSON.parse(raw), expectedRef);
3056
3540
  if (rec) out.set(rec.ref, rec);
3057
3541
  } catch {
@@ -3066,13 +3550,13 @@ function resolveDepth(parentRef, open) {
3066
3550
  return { depth: parent.depth + 1, depth_basis: "observed" };
3067
3551
  }
3068
3552
  function ledgerFileFor2(root, ts) {
3069
- return path6.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
3553
+ return path7.join(root, LEDGER_DIR, `${ts.slice(0, 7)}.jsonl`);
3070
3554
  }
3071
3555
  function appendLedgerLine(root, ts, line) {
3072
3556
  if (is_replay_mode()) return;
3073
3557
  const file = ledgerFileFor2(root, ts);
3074
- fs6.mkdirSync(path6.dirname(file), { recursive: true });
3075
- fs6.appendFileSync(file, `${JSON.stringify(line)}
3558
+ fs8.mkdirSync(path7.dirname(file), { recursive: true });
3559
+ fs8.appendFileSync(file, `${JSON.stringify(line)}
3076
3560
  `, "utf8");
3077
3561
  }
3078
3562
  var WALL_CLOCK_ARMS_MS = [
@@ -3086,7 +3570,7 @@ function armsExceeded(elapsedMs) {
3086
3570
  return WALL_CLOCK_ARMS_MS.filter((a) => elapsedMs >= a.ms).map((a) => a.label);
3087
3571
  }
3088
3572
  function openRecordStats(root, nowMs = Date.now()) {
3089
- const ledgerPresent = fs6.existsSync(path6.join(root, LEDGER_DIR));
3573
+ const ledgerPresent = fs8.existsSync(path7.join(root, LEDGER_DIR));
3090
3574
  const open = readOpenRecords(root);
3091
3575
  let maxDepth = 0;
3092
3576
  let counted = 0;
@@ -3114,13 +3598,13 @@ function writeOpenRecord(root, rec) {
3114
3598
  function removeOpenRecord(root, ref) {
3115
3599
  if (is_replay_mode()) return;
3116
3600
  try {
3117
- fs6.unlinkSync(openFile(root, ref));
3601
+ fs8.unlinkSync(openFile(root, ref));
3118
3602
  } catch {
3119
3603
  }
3120
3604
  }
3121
3605
  function countOpen(root) {
3122
3606
  try {
3123
- return fs6.readdirSync(openDir(root)).filter((n) => n.endsWith(".json")).length;
3607
+ return fs8.readdirSync(openDir(root)).filter((n) => n.endsWith(".json")).length;
3124
3608
  } catch {
3125
3609
  return 0;
3126
3610
  }
@@ -3161,12 +3645,12 @@ function appendUnidentified(root, event, nowIso, payload) {
3161
3645
  unidentified: true,
3162
3646
  // The agent TYPE is an id-shaped enum and may still be present; it is
3163
3647
  // the only thing salvageable from a payload with no id.
3164
- agent_type: str2(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3648
+ agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3165
3649
  reason: "payload carried no agent id \u2014 nothing to correlate start with stop"
3166
3650
  });
3167
3651
  }
3168
3652
  function handleStart(root, payload, nowIso, sessionId) {
3169
- const agentId = str2(payload, "agent_id", "agentId");
3653
+ const agentId = str(payload, "agent_id", "agentId");
3170
3654
  if (agentId === null) {
3171
3655
  appendUnidentified(root, "subagent_start", nowIso, payload);
3172
3656
  return;
@@ -3177,7 +3661,7 @@ function handleStart(root, payload, nowIso, sessionId) {
3177
3661
  const { depth, depth_basis } = resolveDepth(parentRef, readOpenRecords(root));
3178
3662
  const rec = {
3179
3663
  ref,
3180
- agent_type: str2(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3664
+ agent_type: str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3181
3665
  started_at: nowIso,
3182
3666
  parent_ref: parentRef,
3183
3667
  depth,
@@ -3204,7 +3688,7 @@ function handleStart(root, payload, nowIso, sessionId) {
3204
3688
  });
3205
3689
  }
3206
3690
  function handleStop(root, payload, nowIso, sessionId) {
3207
- const agentId = str2(payload, "agent_id", "agentId");
3691
+ const agentId = str(payload, "agent_id", "agentId");
3208
3692
  if (agentId === null) {
3209
3693
  appendUnidentified(root, "subagent_stop", nowIso, payload);
3210
3694
  return;
@@ -3216,7 +3700,7 @@ function handleStop(root, payload, nowIso, sessionId) {
3216
3700
  const started = Date.parse(rec.started_at);
3217
3701
  if (Number.isFinite(started)) durationMs = Math.max(0, Date.parse(nowIso) - started);
3218
3702
  }
3219
- const parse2 = classifyEnvelope(str2(payload, "last_assistant_message", "lastAssistantMessage"));
3703
+ const parse2 = classifyEnvelope(str(payload, "last_assistant_message", "lastAssistantMessage"));
3220
3704
  removeOpenRecord(root, ref);
3221
3705
  appendLedgerLine(root, nowIso, {
3222
3706
  event: "subagent_stop",
@@ -3227,7 +3711,7 @@ function handleStop(root, payload, nowIso, sessionId) {
3227
3711
  // relabel a cross-session stop as belonging to the dispatching session
3228
3712
  // and hide exactly the mismatch this field exists to expose.
3229
3713
  session_id: sessionId,
3230
- agent_type: rec?.agent_type ?? str2(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3714
+ agent_type: rec?.agent_type ?? str(payload, "agent_type", "agentType", "subagent_type", "subagentType"),
3231
3715
  depth: rec?.depth ?? null,
3232
3716
  depth_basis: rec?.depth_basis ?? null,
3233
3717
  // `null` = the matching start was never seen. That is a finding about
@@ -3252,7 +3736,7 @@ function processEnvelope(envelope, consumerRoot) {
3252
3736
  if (event !== "subagent_start" && event !== "subagent_stop") return EXIT_ALLOW3;
3253
3737
  const payload = unwrapPayload(envelope);
3254
3738
  const nowIso = (/* @__PURE__ */ new Date()).toISOString();
3255
- const sessionId = str2(payload, "session_id", "sessionId") ?? str2(envelope, "session_id", "sessionId");
3739
+ const sessionId = str(payload, "session_id", "sessionId") ?? str(envelope, "session_id", "sessionId");
3256
3740
  reapStaleOpenRecords(consumerRoot, nowIso);
3257
3741
  if (event === "subagent_start") handleStart(consumerRoot, payload, nowIso, sessionId);
3258
3742
  else handleStop(consumerRoot, payload, nowIso, sessionId);
@@ -3263,42 +3747,599 @@ function processEnvelope(envelope, consumerRoot) {
3263
3747
  }
3264
3748
  function resolveConsumerRoot(envelope) {
3265
3749
  if (isObject2(envelope)) {
3266
- const pr = str2(envelope, "workspace_root", "project_root");
3750
+ const pr = str(envelope, "workspace_root", "project_root");
3267
3751
  if (pr) return pr;
3268
3752
  const payload = unwrapPayload(envelope);
3269
- const cwd = str2(payload, "cwd");
3753
+ const cwd = str(payload, "cwd");
3270
3754
  if (cwd) return cwd;
3271
3755
  }
3272
- return process.cwd();
3756
+ return process.cwd();
3757
+ }
3758
+ function main4() {
3759
+ const raw = readHookStdin();
3760
+ let envelope = {};
3761
+ try {
3762
+ envelope = raw.trim() ? JSON.parse(raw) : {};
3763
+ } catch {
3764
+ return EXIT_ALLOW3;
3765
+ }
3766
+ return processEnvelope(envelope, resolveConsumerRoot(envelope));
3767
+ }
3768
+ function _isCliEntry4() {
3769
+ if (true) {
3770
+ return false;
3771
+ }
3772
+ if (process.argv[1] === void 0) return false;
3773
+ const argvUrl = pathToFileURL4(path7.resolve(process.argv[1])).href;
3774
+ if (import.meta.url === argvUrl) return true;
3775
+ try {
3776
+ return fs8.realpathSync(fileURLToPath4(import.meta.url)) === fs8.realpathSync(path7.resolve(process.argv[1]));
3777
+ } catch {
3778
+ return false;
3779
+ }
3780
+ }
3781
+ if (_isCliEntry4()) process.exit(main4());
3782
+
3783
+ // src/scripts/_lib/turn_end_transcript.ts
3784
+ import * as fs11 from "node:fs";
3785
+
3786
+ // src/scripts/hooks/end_review_nudge_hook.ts
3787
+ import { spawnSync as spawnSync2 } from "node:child_process";
3788
+ import * as crypto2 from "node:crypto";
3789
+ import * as fs10 from "node:fs";
3790
+ import * as os from "node:os";
3791
+ import * as path9 from "node:path";
3792
+ import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL5 } from "node:url";
3793
+
3794
+ // src/scripts/_lib/review_baseline.ts
3795
+ import { spawnSync } from "node:child_process";
3796
+ import * as fs9 from "node:fs";
3797
+ import * as path8 from "node:path";
3798
+ function currentHeadSha(cwd) {
3799
+ try {
3800
+ const r = spawnSync("git", ["rev-parse", "HEAD"], { cwd, encoding: "utf8" });
3801
+ if (r.status !== 0) return null;
3802
+ const out = (r.stdout ?? "").trim();
3803
+ return out === "" ? null : out;
3804
+ } catch {
3805
+ return null;
3806
+ }
3807
+ }
3808
+ var BASELINE_STATE_REL = path8.join("agents", "runtime", "state", "review-baseline");
3809
+ function baselineStateDir(workspaceRoot) {
3810
+ return path8.join(workspaceRoot, BASELINE_STATE_REL);
3811
+ }
3812
+ function baselineStateFile(workspaceRoot, sessionKey) {
3813
+ return path8.join(baselineStateDir(workspaceRoot), `${sessionKey}.json`);
3814
+ }
3815
+ function parseBaseline(raw) {
3816
+ let decoded;
3817
+ try {
3818
+ decoded = JSON.parse(raw);
3819
+ } catch {
3820
+ return null;
3821
+ }
3822
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
3823
+ const o = decoded;
3824
+ const lines = o["baseline_lines"];
3825
+ if (typeof lines !== "number" || !Number.isFinite(lines) || lines < 0) return null;
3826
+ const written = o["written_at"];
3827
+ if (typeof written !== "string") return null;
3828
+ const head = o["head_sha"];
3829
+ const measure = o["measure"];
3830
+ return {
3831
+ head_sha: typeof head === "string" && head !== "" ? head : null,
3832
+ baseline_lines: lines,
3833
+ ...measure === "exact" || measure === "capped_approximation" ? { measure } : {},
3834
+ written_at: written
3835
+ };
3836
+ }
3837
+ function readBaseline(workspaceRoot, sessionKey) {
3838
+ const file = baselineStateFile(workspaceRoot, sessionKey);
3839
+ let raw;
3840
+ try {
3841
+ raw = fs9.readFileSync(file, "utf-8");
3842
+ } catch (exc) {
3843
+ const code = exc?.code;
3844
+ return code === "ENOENT" ? "absent" : "unreadable";
3845
+ }
3846
+ return parseBaseline(raw) ?? "unreadable";
3847
+ }
3848
+ function applyBaseline(measuredLines, baseline, headSha, measuredMeasure = "exact") {
3849
+ if (baseline === "absent" || baseline === "unreadable") {
3850
+ return { applied: false, fallback: baseline, lines: measuredLines };
3851
+ }
3852
+ if (baseline.measure !== "exact" || measuredMeasure !== "exact") {
3853
+ return { applied: false, fallback: "mixed-measure", lines: measuredLines };
3854
+ }
3855
+ if (baseline.head_sha !== headSha()) {
3856
+ return { applied: false, fallback: "head-moved", lines: measuredLines };
3857
+ }
3858
+ return {
3859
+ applied: true,
3860
+ baseline,
3861
+ lines: Math.max(0, measuredLines - baseline.baseline_lines)
3862
+ };
3863
+ }
3864
+
3865
+ // src/scripts/_lib/audit_field_provenance.ts
3866
+ var MAX_APPLIED_IDS = 32;
3867
+ function appliedIds(values) {
3868
+ const out = [];
3869
+ for (const raw of values ?? []) {
3870
+ const v = String(raw).trim();
3871
+ if (v === "" || out.includes(v)) continue;
3872
+ out.push(v);
3873
+ if (out.length >= MAX_APPLIED_IDS) break;
3874
+ }
3875
+ return out;
3876
+ }
3877
+
3878
+ // src/scripts/_lib/review_skipped_record.ts
3879
+ var BASELINE_APPLICATIONS = [
3880
+ "applied",
3881
+ "absent",
3882
+ "unreadable",
3883
+ "head_moved",
3884
+ "mixed_measure"
3885
+ ];
3886
+ var PRODUCER_PRIVACY_CLASS = "ids-only";
3887
+ function isNonNegInt(n) {
3888
+ return typeof n === "number" && Number.isFinite(n) && Number.isInteger(n) && n >= 0;
3889
+ }
3890
+ var HIGH_RISK_DIFF_LINES = 200;
3891
+ function buildReviewSkippedLine(input) {
3892
+ const errors = [];
3893
+ if (!isNonNegInt(input.diff_lines)) {
3894
+ errors.push("diff_lines must be a non-negative integer");
3895
+ }
3896
+ if (input.mutation_measure !== "exact" && input.mutation_measure !== "capped_approximation") {
3897
+ errors.push('mutation_measure must be "exact" or "capped_approximation"');
3898
+ }
3899
+ if (!input.ts) errors.push("ts (ISO-8601 UTC) is required");
3900
+ if (!input.id) errors.push("id (ULID, UUID, or content hash) is required");
3901
+ if (input.baseline !== void 0 && !BASELINE_APPLICATIONS.includes(input.baseline)) {
3902
+ errors.push(`baseline must be one of ${BASELINE_APPLICATIONS.join(", ")}`);
3903
+ }
3904
+ if (errors.length) return { line: null, errors };
3905
+ const line = {
3906
+ schema_version: 1,
3907
+ id: input.id,
3908
+ ts: input.ts,
3909
+ work_id: input.work_id ?? `review-skipped-${input.ts}`,
3910
+ phase: "report",
3911
+ outcome: "skipped",
3912
+ confidence_band: "medium",
3913
+ risk_class: input.diff_lines >= HIGH_RISK_DIFF_LINES ? "high" : "medium",
3914
+ memory: { asks: 0, hits: 0 },
3915
+ verify: { claims: 0, first_try_passes: 0 },
3916
+ // COMPUTED, never a producer constant.
3917
+ rules_applied: appliedIds(input.rules_applied),
3918
+ // MANDATORY on every audit line, same as the orchestration producer.
3919
+ privacy_class: PRODUCER_PRIVACY_CLASS,
3920
+ // `skills_applied` is OMITTED here, deliberately and not by oversight.
3921
+ // audit-log-v1 distinguishes an absent key ("not recorded") from `[]`
3922
+ // ("recorded, none applied"), and this writer observes a review that
3923
+ // did not happen -- it has no skill observation to offer in either
3924
+ // direction. Emitting `[]` would assert "no skills applied" on evidence
3925
+ // this producer never gathered, which is the exact fabrication the
3926
+ // absent/empty split exists to make impossible.
3927
+ persona: null,
3928
+ input_kind: "prompt",
3929
+ type: "note",
3930
+ review_skipped: {
3931
+ diff_lines: input.diff_lines,
3932
+ mutation_measure: input.mutation_measure,
3933
+ // Omitted, not defaulted, when the producer recorded nothing —
3934
+ // audit-log-v1's absent-versus-present split is the same one
3935
+ // `skills_applied` uses above, for the same reason.
3936
+ ...input.baseline === void 0 ? {} : { baseline: input.baseline }
3937
+ }
3938
+ };
3939
+ return { line, errors: [] };
3940
+ }
3941
+
3942
+ // src/scripts/hooks/end_review_nudge_hook.ts
3943
+ var MUTATION_LINE_THRESHOLD = 50;
3944
+ var UNTRACKED_FILE_CAP = 20;
3945
+ var TRANSCRIPT_SCAN_MAX_BYTES = 50 * 1024 * 1024;
3946
+ function isSafeTranscriptPath(rawPath, opts = {}) {
3947
+ if (!rawPath || !/\.jsonl$/i.test(rawPath)) return false;
3948
+ const homeDir = opts.homeDir ?? os.homedir();
3949
+ const maxBytes = opts.maxBytes ?? TRANSCRIPT_SCAN_MAX_BYTES;
3950
+ try {
3951
+ const realHome = fs10.realpathSync(homeDir);
3952
+ const realPath = fs10.realpathSync(rawPath);
3953
+ const relative5 = path9.relative(realHome, realPath);
3954
+ if (relative5 === "" || relative5.startsWith("..") || path9.isAbsolute(relative5)) {
3955
+ return false;
3956
+ }
3957
+ const { size } = fs10.statSync(realPath);
3958
+ return size <= maxBytes;
3959
+ } catch {
3960
+ return false;
3961
+ }
3962
+ }
3963
+ function parseNumstat(raw) {
3964
+ const rows = [];
3965
+ for (const line of raw.split("\n")) {
3966
+ const m = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
3967
+ if (!m) continue;
3968
+ const addedStr = m[1];
3969
+ const deletedStr = m[2];
3970
+ rows.push({
3971
+ added: addedStr === "-" ? 0 : Number(addedStr),
3972
+ deleted: deletedStr === "-" ? 0 : Number(deletedStr),
3973
+ path: m[3]
3974
+ });
3975
+ }
3976
+ return rows;
3977
+ }
3978
+ var DOC_PATH_RE = /\.md$/i;
3979
+ function isDocPath(p) {
3980
+ return DOC_PATH_RE.test(p);
3981
+ }
3982
+ function nonDocMutatedLines(rows) {
3983
+ return rows.filter((r) => !isDocPath(r.path)).reduce((sum, r) => sum + r.added + r.deleted, 0);
3984
+ }
3985
+ function gitNumstatRows(cwd) {
3986
+ try {
3987
+ const r = spawnSync2("git", ["diff", "--numstat", "HEAD"], {
3988
+ cwd,
3989
+ encoding: "utf8",
3990
+ maxBuffer: 16 * 1024 * 1024
3991
+ });
3992
+ if (r.status !== 0) return [];
3993
+ return parseNumstat(r.stdout ?? "");
3994
+ } catch {
3995
+ return [];
3996
+ }
3997
+ }
3998
+ function untrackedNonDocFiles(cwd) {
3999
+ try {
4000
+ const r = spawnSync2("git", ["ls-files", "--others", "--exclude-standard"], {
4001
+ cwd,
4002
+ encoding: "utf8",
4003
+ maxBuffer: 16 * 1024 * 1024
4004
+ });
4005
+ if (r.status !== 0) return [];
4006
+ return (r.stdout ?? "").split("\n").map((l) => l.trim()).filter((l) => l.length > 0 && !isDocPath(l));
4007
+ } catch {
4008
+ return [];
4009
+ }
4010
+ }
4011
+ var BINARY_PROBE_BYTES = 8e3;
4012
+ var UNTRACKED_FILE_READ_CAP_BYTES = 8 * 1024 * 1024;
4013
+ function untrackedFileLineCount(cwd, relPath) {
4014
+ try {
4015
+ const abs = path9.resolve(cwd, relPath);
4016
+ const size = fs10.statSync(abs).size;
4017
+ if (size === 0) return 0;
4018
+ if (size > UNTRACKED_FILE_READ_CAP_BYTES) {
4019
+ return MUTATION_LINE_THRESHOLD + 1;
4020
+ }
4021
+ const buf = fs10.readFileSync(abs);
4022
+ if (buf.length === 0) return 0;
4023
+ const probe = buf.subarray(0, Math.min(buf.length, BINARY_PROBE_BYTES));
4024
+ if (probe.includes(0)) return 0;
4025
+ let lines = 0;
4026
+ for (const byte of buf) {
4027
+ if (byte === 10) lines += 1;
4028
+ }
4029
+ if (buf[buf.length - 1] !== 10) lines += 1;
4030
+ return lines;
4031
+ } catch {
4032
+ return 0;
4033
+ }
4034
+ }
4035
+ function totalNonDocMutatedLinesWithMeasure(cwd) {
4036
+ const trackedLines = nonDocMutatedLines(gitNumstatRows(cwd));
4037
+ const untracked = untrackedNonDocFiles(cwd);
4038
+ if (untracked.length > UNTRACKED_FILE_CAP) {
4039
+ return { lines: MUTATION_LINE_THRESHOLD + 1 + trackedLines, measure: "capped_approximation" };
4040
+ }
4041
+ const untrackedLines = untracked.reduce(
4042
+ (sum, relPath) => sum + untrackedFileLineCount(cwd, relPath),
4043
+ 0
4044
+ );
4045
+ return { lines: trackedLines + untrackedLines, measure: "exact" };
4046
+ }
4047
+ function isObject3(v) {
4048
+ return typeof v === "object" && v !== null && !Array.isArray(v);
4049
+ }
4050
+ function str2(v) {
4051
+ return typeof v === "string" ? v : "";
4052
+ }
4053
+ var DISPATCH_TOOL_RE = /^(agent|task)$/i;
4054
+ var REVIEW_SHAPED_LABEL_RE = /\b(review|judge|adversarial|critique|second[- ]opinion|blind\s+(pass|review)|cross-model)\b/i;
4055
+ 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;
4056
+ var REVIEW_SKILL_NAME_RE = /^(review[-:]changes|judge[-:][a-z0-9-]+|adversarial-review|code-review)$/i;
4057
+ function scanTranscriptForReviewer(transcriptText) {
4058
+ for (const raw of transcriptText.split("\n")) {
4059
+ const line = raw.trim();
4060
+ if (!line) continue;
4061
+ let obj;
4062
+ try {
4063
+ obj = JSON.parse(line);
4064
+ } catch {
4065
+ continue;
4066
+ }
4067
+ if (!isObject3(obj) || obj["type"] !== "assistant") continue;
4068
+ const msg = obj["message"];
4069
+ if (!isObject3(msg)) continue;
4070
+ const content = msg["content"];
4071
+ if (!Array.isArray(content)) continue;
4072
+ for (const blk of content) {
4073
+ if (!isObject3(blk) || blk["type"] !== "tool_use") continue;
4074
+ const name = str2(blk["name"]);
4075
+ const input = blk["input"];
4076
+ const inputObj = isObject3(input) ? input : {};
4077
+ if (DISPATCH_TOOL_RE.test(name)) {
4078
+ const label = [
4079
+ str2(inputObj["description"]),
4080
+ str2(inputObj["subagent_type"])
4081
+ ].filter(Boolean).join(" ");
4082
+ if (REVIEW_SHAPED_LABEL_RE.test(label)) return true;
4083
+ const prompt = str2(inputObj["prompt"]);
4084
+ if (REVIEW_SHAPED_PROMPT_RE.test(prompt)) return true;
4085
+ continue;
4086
+ }
4087
+ if (/^skill$/i.test(name)) {
4088
+ const skillName = str2(inputObj["skill"]) || str2(inputObj["name"]) || str2(inputObj["command"]);
4089
+ if (REVIEW_SKILL_NAME_RE.test(skillName)) return true;
4090
+ continue;
4091
+ }
4092
+ if (REVIEW_SKILL_NAME_RE.test(name)) return true;
4093
+ }
4094
+ }
4095
+ return false;
4096
+ }
4097
+ function buildAdvisoryLine(diffLines) {
4098
+ return `this session mutated ${diffLines} lines without a neutral review; spawn a cross-model reviewer before claiming done (delegation-policy / verify-budget)`;
4099
+ }
4100
+ function appendReviewSkippedTelemetry(workspaceRoot, diffLines, mutationMeasure, baseline) {
4101
+ if (is_replay_mode()) return;
4102
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
4103
+ const { line, errors } = buildReviewSkippedLine({
4104
+ diff_lines: diffLines,
4105
+ mutation_measure: mutationMeasure,
4106
+ ts,
4107
+ id: crypto2.randomUUID(),
4108
+ baseline
4109
+ });
4110
+ if (errors.length || line === null) return;
4111
+ try {
4112
+ const dir = path9.join(workspaceRoot, "agents", "runtime", "state", "audit");
4113
+ fs10.mkdirSync(dir, { recursive: true });
4114
+ const file = path9.join(dir, `${ts.slice(0, 7)}.jsonl`);
4115
+ fs10.appendFileSync(file, `${JSON.stringify(line)}
4116
+ `, "utf8");
4117
+ } catch {
4118
+ }
4119
+ }
4120
+ function sessionStateDir(workspaceRoot) {
4121
+ return path9.join(workspaceRoot, "agents", "runtime", "state", "end-review-nudge");
4122
+ }
4123
+ function sessionStateFile(workspaceRoot, sessionKey) {
4124
+ return path9.join(sessionStateDir(workspaceRoot), `${sessionKey}.json`);
4125
+ }
4126
+ function deriveSessionKey2(envelope, payload) {
4127
+ const raw = str2(envelope["session_id"]) || str2(payload["transcript_path"] ?? payload["transcriptPath"]);
4128
+ const material = raw || "unknown-session";
4129
+ return crypto2.createHash("sha256").update(material).digest("hex");
4130
+ }
4131
+ function hasFiredThisSession(workspaceRoot, sessionKey) {
4132
+ try {
4133
+ return fs10.existsSync(sessionStateFile(workspaceRoot, sessionKey));
4134
+ } catch {
4135
+ return false;
4136
+ }
4137
+ }
4138
+ function markFiredThisSession(workspaceRoot, sessionKey, ts) {
4139
+ try {
4140
+ atomic_write_json(sessionStateFile(workspaceRoot, sessionKey), { fired_at: ts });
4141
+ } catch {
4142
+ }
4143
+ }
4144
+ function _read_stdin() {
4145
+ return readHookStdin();
4146
+ }
4147
+ var FALLBACK_TO_TELEMETRY = {
4148
+ absent: "absent",
4149
+ unreadable: "unreadable",
4150
+ "head-moved": "head_moved",
4151
+ "mixed-measure": "mixed_measure"
4152
+ };
4153
+ function main5() {
4154
+ const [envelope, payload] = unwrap(_read_stdin(), "claude");
4155
+ const event = String(envelope["event"] ?? "");
4156
+ if (event !== "" && event !== "stop") {
4157
+ return 0;
4158
+ }
4159
+ const workspace_root = String(envelope["workspace_root"] ?? "").trim() || process.cwd();
4160
+ let measuredLines;
4161
+ let mutationMeasure;
4162
+ try {
4163
+ const measured = totalNonDocMutatedLinesWithMeasure(workspace_root);
4164
+ measuredLines = measured.lines;
4165
+ mutationMeasure = measured.measure;
4166
+ } catch {
4167
+ return 0;
4168
+ }
4169
+ const sessionKey = deriveSessionKey2(envelope, payload);
4170
+ const baselineOutcome = applyBaseline(
4171
+ measuredLines,
4172
+ readBaseline(workspace_root, sessionKey),
4173
+ // A THUNK, not a value: on the `absent` and `unreadable` branches the
4174
+ // result is discarded, and those two are the steady state on every host
4175
+ // without this nudge and on every session before its first baseline
4176
+ // write. Passing the value eagerly spent an 11-14 ms `git rev-parse` on
4177
+ // each of those stops for nothing.
4178
+ () => currentHeadSha(workspace_root),
4179
+ mutationMeasure
4180
+ );
4181
+ const diffLines = baselineOutcome.lines;
4182
+ const baselineApplication = baselineOutcome.applied ? "applied" : FALLBACK_TO_TELEMETRY[baselineOutcome.fallback];
4183
+ if (diffLines <= MUTATION_LINE_THRESHOLD) {
4184
+ return 0;
4185
+ }
4186
+ if (hasFiredThisSession(workspace_root, sessionKey)) {
4187
+ return 0;
4188
+ }
4189
+ const transcriptPath = str2(
4190
+ payload["transcript_path"] ?? payload["transcriptPath"]
4191
+ ).trim();
4192
+ let reviewerRan = false;
4193
+ if (transcriptPath && isSafeTranscriptPath(transcriptPath)) {
4194
+ try {
4195
+ reviewerRan = scanTranscriptForReviewer(fs10.readFileSync(transcriptPath, "utf-8"));
4196
+ } catch {
4197
+ reviewerRan = false;
4198
+ }
4199
+ }
4200
+ if (reviewerRan) {
4201
+ return 0;
4202
+ }
4203
+ markFiredThisSession(workspace_root, sessionKey, (/* @__PURE__ */ new Date()).toISOString());
4204
+ try {
4205
+ appendReviewSkippedTelemetry(
4206
+ workspace_root,
4207
+ diffLines,
4208
+ mutationMeasure,
4209
+ baselineApplication
4210
+ );
4211
+ } catch {
4212
+ }
4213
+ process.stdout.write(
4214
+ `${JSON.stringify({
4215
+ decision: "warn",
4216
+ reason: `end-review-nudge: session mutated ${diffLines} non-doc lines without a neutral review`,
4217
+ additional_context: buildAdvisoryLine(diffLines)
4218
+ })}
4219
+ `
4220
+ );
4221
+ return EXIT_WARN2;
4222
+ }
4223
+ function _isCliEntry5() {
4224
+ if (true) {
4225
+ return false;
4226
+ }
4227
+ if (process.argv[1] === void 0) {
4228
+ return false;
4229
+ }
4230
+ const argvUrl = pathToFileURL5(path9.resolve(process.argv[1])).href;
4231
+ if (import.meta.url === argvUrl) {
4232
+ return true;
4233
+ }
4234
+ try {
4235
+ const here = fs10.realpathSync(fileURLToPath5(import.meta.url));
4236
+ const argv = fs10.realpathSync(path9.resolve(process.argv[1]));
4237
+ return here === argv;
4238
+ } catch {
4239
+ return false;
4240
+ }
4241
+ }
4242
+ if (_isCliEntry5()) {
4243
+ process.exit(main5());
4244
+ }
4245
+
4246
+ // src/scripts/_lib/turn_end_transcript.ts
4247
+ function readTranscriptTail(transcriptPath, opts = {}) {
4248
+ const empty = {
4249
+ lastAssistant: "",
4250
+ turnOrdinal: 0,
4251
+ toolCalls: [],
4252
+ assistantTurnTexts: []
4253
+ };
4254
+ if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
4255
+ let lines;
4256
+ try {
4257
+ if (opts.maxBytes !== void 0 && fs11.statSync(transcriptPath).size > opts.maxBytes) {
4258
+ return empty;
4259
+ }
4260
+ lines = fs11.readFileSync(transcriptPath, "utf-8").split("\n");
4261
+ } catch {
4262
+ return empty;
4263
+ }
4264
+ let lastAssistant = "";
4265
+ let turnOrdinal = 0;
4266
+ let toolCalls = [];
4267
+ let assistantTurnTexts = [];
4268
+ for (const rawLine of lines) {
4269
+ const line = rawLine.trim();
4270
+ if (!line) continue;
4271
+ let obj;
4272
+ try {
4273
+ obj = JSON.parse(line);
4274
+ } catch {
4275
+ continue;
4276
+ }
4277
+ if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
4278
+ const entry = obj;
4279
+ if (entry["isSidechain"] === true) continue;
4280
+ const role = entry["type"];
4281
+ if (role !== "assistant" && role !== "user") continue;
4282
+ const msg = entry["message"];
4283
+ if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
4284
+ const content = msg["content"];
4285
+ if (role === "assistant") {
4286
+ toolCalls.push(...extractToolCalls(content));
4287
+ }
4288
+ const text = _messageText(content);
4289
+ if (text === null) continue;
4290
+ if (role === "assistant") {
4291
+ lastAssistant = text;
4292
+ assistantTurnTexts.push(text);
4293
+ } else if (!isSyntheticPrompt(text)) {
4294
+ turnOrdinal += 1;
4295
+ toolCalls = [];
4296
+ assistantTurnTexts = [];
4297
+ }
4298
+ }
4299
+ return {
4300
+ lastAssistant: lastAssistant.trim(),
4301
+ turnOrdinal,
4302
+ toolCalls,
4303
+ assistantTurnTexts
4304
+ };
3273
4305
  }
3274
- function main5() {
3275
- const raw = readHookStdin();
3276
- let envelope = {};
3277
- try {
3278
- envelope = raw.trim() ? JSON.parse(raw) : {};
3279
- } catch {
3280
- return EXIT_ALLOW3;
4306
+ function extractToolCalls(content) {
4307
+ if (!Array.isArray(content)) return [];
4308
+ const out = [];
4309
+ for (const blk of content) {
4310
+ if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
4311
+ const b = blk;
4312
+ if (b["type"] !== "tool_use") continue;
4313
+ const name = b["name"];
4314
+ if (typeof name !== "string") continue;
4315
+ const input = b["input"];
4316
+ const call = { name };
4317
+ if (typeof input === "object" && input !== null && !Array.isArray(input)) {
4318
+ const inp = input;
4319
+ const cmd = inp["command"];
4320
+ if (typeof cmd === "string") call.command = cmd;
4321
+ const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
4322
+ if (typeof p === "string") call.path = p;
4323
+ }
4324
+ out.push(call);
3281
4325
  }
3282
- return processEnvelope(envelope, resolveConsumerRoot(envelope));
4326
+ return out;
3283
4327
  }
3284
- function _isCliEntry5() {
3285
- if (true) {
3286
- return false;
3287
- }
3288
- if (process.argv[1] === void 0) return false;
3289
- const argvUrl = pathToFileURL5(path6.resolve(process.argv[1])).href;
3290
- if (import.meta.url === argvUrl) return true;
3291
- try {
3292
- return fs6.realpathSync(fileURLToPath5(import.meta.url)) === fs6.realpathSync(path6.resolve(process.argv[1]));
3293
- } catch {
3294
- return false;
4328
+ function _messageText(content) {
4329
+ if (typeof content === "string") return content;
4330
+ if (!Array.isArray(content)) return null;
4331
+ const parts = [];
4332
+ for (const blk of content) {
4333
+ if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
4334
+ const b = blk;
4335
+ if (b["type"] !== "text") continue;
4336
+ const t = b["text"];
4337
+ if (typeof t === "string") parts.push(t);
3295
4338
  }
4339
+ return parts.length > 0 ? parts.join("\n") : null;
3296
4340
  }
3297
- if (_isCliEntry5()) process.exit(main5());
3298
4341
 
3299
4342
  // src/scripts/hooks/turn_end_gate_hook.ts
3300
- var EXIT_BLOCK = 1;
3301
- var EXIT_ALLOW4 = 0;
3302
4343
  var TRANSCRIPT_READ_MAX_BYTES = 8 * 1024 * 1024;
3303
4344
  function stripFencedBlocks(text) {
3304
4345
  const out = [];
@@ -3403,7 +4444,7 @@ function detectPromissory(reply) {
3403
4444
  function readCiSettled(workspaceRoot, session_id) {
3404
4445
  if (!hasStableSessionId(session_id)) return { seen: false, settled: false };
3405
4446
  try {
3406
- const raw = fs7.readFileSync(path7.join(workspaceRoot, statePathFor(session_id)), "utf-8");
4447
+ const raw = fs12.readFileSync(path10.join(workspaceRoot, statePathFor(session_id)), "utf-8");
3407
4448
  const decoded = JSON.parse(raw);
3408
4449
  if (!owns_session_state(decoded, session_id)) return { seen: false, settled: false };
3409
4450
  if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
@@ -3439,8 +4480,8 @@ function detectCompletionClaim(reply, ci) {
3439
4480
  function readLanguagePin(workspaceRoot, session_id) {
3440
4481
  if (!hasStableSessionId(session_id)) return "und";
3441
4482
  try {
3442
- const raw = fs7.readFileSync(
3443
- path7.join(workspaceRoot, statePathFor2(session_id)),
4483
+ const raw = fs12.readFileSync(
4484
+ path10.join(workspaceRoot, statePathFor2(session_id)),
3444
4485
  "utf-8"
3445
4486
  );
3446
4487
  const decoded = JSON.parse(raw);
@@ -3466,29 +4507,38 @@ function detectLanguage(reply, pinned) {
3466
4507
  };
3467
4508
  }
3468
4509
  var _EDIT_TOOLS = /* @__PURE__ */ new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
3469
- 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;
3470
- function isVerificationCommand(command) {
3471
- return _VERIFY_RE.test(command);
3472
- }
3473
- function detectUnverifiedEdit(toolCalls) {
4510
+ function detectUnverifiedEdit(toolCalls, runState = null) {
3474
4511
  let lastEdit = -1;
3475
4512
  for (let i = 0; i < toolCalls.length; i += 1) {
3476
4513
  if (_EDIT_TOOLS.has(toolCalls[i].name)) lastEdit = i;
3477
4514
  }
3478
4515
  if (lastEdit === -1) return null;
4516
+ const edited = toolCalls[lastEdit].path ?? toolCalls[lastEdit].name;
4517
+ if (runState !== null) {
4518
+ const reading = readRunEvidence(runState);
4519
+ if (reading.passed) return null;
4520
+ if (!reading.instrumentGap) {
4521
+ return {
4522
+ detector: "verification",
4523
+ evidence: edited,
4524
+ reason: describeRecordFinding(reading.failed, reading.reasons),
4525
+ mode: "record"
4526
+ };
4527
+ }
4528
+ }
3479
4529
  for (let i = lastEdit + 1; i < toolCalls.length; i += 1) {
3480
4530
  const c = toolCalls[i];
3481
4531
  if (c.name === "Bash" && c.command !== void 0 && isVerificationCommand(c.command)) {
3482
4532
  return null;
3483
4533
  }
3484
4534
  }
3485
- const edited = toolCalls[lastEdit].path;
3486
4535
  return {
3487
4536
  detector: "verification",
3488
4537
  // The path, never the diff: the evidence span is quoted into a refusal,
3489
4538
  // and `ToolCall` is shaped so a file body cannot reach it.
3490
- evidence: edited ?? toolCalls[lastEdit].name,
3491
- 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)"
4539
+ evidence: edited,
4540
+ 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)",
4541
+ mode: "transcript"
3492
4542
  };
3493
4543
  }
3494
4544
  var _SOURCE_RE = /\.(?:ts|tsx|js|jsx|mjs|cjs|vue|svelte|php|py|rb|go|rs|java|kt|swift|cs|scala|ex|exs)$/i;
@@ -3500,18 +4550,37 @@ function _isTestPath(p) {
3500
4550
  function _isProductionSource(p) {
3501
4551
  return _SOURCE_RE.test(p) && !_isTestPath(p);
3502
4552
  }
3503
- function detectUntestedChange(reply, toolCalls) {
4553
+ function detectUntestedChange(reply, toolCalls, runState = null) {
3504
4554
  const edited = toolCalls.filter((c) => _EDIT_TOOLS.has(c.name) && c.path !== void 0).map((c) => c.path);
3505
4555
  const source = edited.filter(_isProductionSource);
3506
4556
  if (source.length === 0) return null;
3507
- if (edited.some(_isTestPath)) return null;
4557
+ const wroteNewTest = toolCalls.some(
4558
+ (c) => c.name === "Write" && c.path !== void 0 && _isTestPath(c.path)
4559
+ );
4560
+ let noRedEvidence = false;
4561
+ if (edited.some(_isTestPath)) {
4562
+ if (!wroteNewTest) return null;
4563
+ if (runState === null) return null;
4564
+ if (hasRedThenGreen(runState)) return null;
4565
+ if (readRunEvidence(runState).instrumentGap) return null;
4566
+ noRedEvidence = true;
4567
+ }
3508
4568
  const prose = visibleProse(reply);
3509
4569
  const m = _COMPLETION_RE.exec(prose);
3510
4570
  if (!m) return null;
3511
4571
  if (_NEGATED_CLAIM_RE.test(_lineAround(prose, m.index))) return null;
3512
4572
  const shown = source.slice(0, 3).join(", ");
4573
+ if (noRedEvidence) {
4574
+ return {
4575
+ detector: "untested",
4576
+ evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
4577
+ mode: "record",
4578
+ reason: NO_RED_EVIDENCE_REASON
4579
+ };
4580
+ }
3513
4581
  return {
3514
4582
  detector: "untested",
4583
+ mode: "transcript",
3515
4584
  evidence: shown + (source.length > 3 ? ` (+${String(source.length - 3)} more)` : ""),
3516
4585
  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"
3517
4586
  };
@@ -3521,7 +4590,7 @@ function sessionStateFile2(workspaceRoot, sessionKey) {
3521
4590
  }
3522
4591
  function alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal) {
3523
4592
  try {
3524
- const raw = fs7.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
4593
+ const raw = fs12.readFileSync(sessionStateFile2(workspaceRoot, sessionKey), "utf-8");
3525
4594
  const decoded = JSON.parse(raw);
3526
4595
  if (typeof decoded === "object" && decoded !== null && !Array.isArray(decoded)) {
3527
4596
  return decoded["refused_turn"] === turnOrdinal;
@@ -3535,7 +4604,7 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
3535
4604
  const file = sessionStateFile2(workspaceRoot, sessionKey);
3536
4605
  let prev = null;
3537
4606
  try {
3538
- prev = parseRecord(fs7.readFileSync(file, "utf-8"));
4607
+ prev = parseRecord(fs12.readFileSync(file, "utf-8"));
3539
4608
  } catch {
3540
4609
  }
3541
4610
  let version;
@@ -3558,112 +4627,10 @@ function markRefusedTurn(workspaceRoot, sessionKey, turnOrdinal, detectors, prom
3558
4627
  } catch {
3559
4628
  }
3560
4629
  }
3561
- function readTranscriptTail(transcriptPath, opts = {}) {
3562
- const empty = {
3563
- lastAssistant: "",
3564
- turnOrdinal: 0,
3565
- toolCalls: [],
3566
- assistantTurnTexts: []
3567
- };
3568
- if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
3569
- let lines;
3570
- try {
3571
- if (opts.maxBytes !== void 0 && fs7.statSync(transcriptPath).size > opts.maxBytes) {
3572
- return empty;
3573
- }
3574
- lines = fs7.readFileSync(transcriptPath, "utf-8").split("\n");
3575
- } catch {
3576
- return empty;
3577
- }
3578
- let lastAssistant = "";
3579
- let turnOrdinal = 0;
3580
- let toolCalls = [];
3581
- let assistantTurnTexts = [];
3582
- for (const rawLine of lines) {
3583
- const line = rawLine.trim();
3584
- if (!line) continue;
3585
- let obj;
3586
- try {
3587
- obj = JSON.parse(line);
3588
- } catch {
3589
- continue;
3590
- }
3591
- if (typeof obj !== "object" || obj === null || Array.isArray(obj)) continue;
3592
- const entry = obj;
3593
- if (entry["isSidechain"] === true) continue;
3594
- const role = entry["type"];
3595
- if (role !== "assistant" && role !== "user") continue;
3596
- const msg = entry["message"];
3597
- if (typeof msg !== "object" || msg === null || Array.isArray(msg)) continue;
3598
- const content = msg["content"];
3599
- if (role === "assistant") {
3600
- toolCalls.push(...extractToolCalls(content));
3601
- }
3602
- const text = _messageText(content);
3603
- if (text === null) continue;
3604
- if (role === "assistant") {
3605
- lastAssistant = text;
3606
- assistantTurnTexts.push(text);
3607
- } else if (!isSyntheticPrompt(text)) {
3608
- turnOrdinal += 1;
3609
- toolCalls = [];
3610
- assistantTurnTexts = [];
3611
- }
3612
- }
3613
- return {
3614
- lastAssistant: lastAssistant.trim(),
3615
- turnOrdinal,
3616
- toolCalls,
3617
- assistantTurnTexts
3618
- };
3619
- }
3620
- function extractToolCalls(content) {
3621
- if (!Array.isArray(content)) return [];
3622
- const out = [];
3623
- for (const blk of content) {
3624
- if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
3625
- const b = blk;
3626
- if (b["type"] !== "tool_use") continue;
3627
- const name = b["name"];
3628
- if (typeof name !== "string") continue;
3629
- const input = b["input"];
3630
- const call = { name };
3631
- if (typeof input === "object" && input !== null && !Array.isArray(input)) {
3632
- const inp = input;
3633
- const cmd = inp["command"];
3634
- if (typeof cmd === "string") call.command = cmd;
3635
- const p = inp["file_path"] ?? inp["path"] ?? inp["notebook_path"];
3636
- if (typeof p === "string") call.path = p;
3637
- }
3638
- out.push(call);
3639
- }
3640
- return out;
3641
- }
3642
- function _messageText(content) {
3643
- if (typeof content === "string") return content;
3644
- if (!Array.isArray(content)) return null;
3645
- const parts = [];
3646
- for (const blk of content) {
3647
- if (typeof blk !== "object" || blk === null || Array.isArray(blk)) continue;
3648
- const b = blk;
3649
- if (b["type"] !== "text") continue;
3650
- const t = b["text"];
3651
- if (typeof t === "string") parts.push(t);
3652
- }
3653
- return parts.length > 0 ? parts.join("\n") : null;
3654
- }
3655
4630
  function str3(v) {
3656
4631
  return typeof v === "string" ? v : "";
3657
4632
  }
3658
- function main6() {
3659
- let envelope;
3660
- let payload;
3661
- try {
3662
- [envelope, payload] = unwrap(readHookStdin(), "claude");
3663
- } catch {
3664
- return EXIT_ALLOW4;
3665
- }
3666
- if (payload["stop_hook_active"] === true) return EXIT_ALLOW4;
4633
+ function assembleDetectorInputs(envelope, payload) {
3667
4634
  const workspaceRoot = str3(envelope["workspace_root"]) || process.cwd();
3668
4635
  let dispatchOpen = false;
3669
4636
  try {
@@ -3677,18 +4644,32 @@ function main6() {
3677
4644
  transcriptPath,
3678
4645
  { maxBytes: TRANSCRIPT_READ_MAX_BYTES }
3679
4646
  );
3680
- if (!lastAssistant) return EXIT_ALLOW4;
4647
+ if (!lastAssistant) return null;
3681
4648
  const payloadClosing = str3(
3682
4649
  payload["last_assistant_message"] ?? payload["lastAssistantMessage"]
3683
4650
  );
3684
4651
  const closingFromPayload = payloadClosing !== "" && !assistantTurnTexts.includes(payloadClosing);
3685
4652
  const turnTexts = closingFromPayload ? [...assistantTurnTexts, payloadClosing] : assistantTurnTexts;
3686
- const promptId = str3(
3687
- payload["prompt_id"] ?? payload["promptId"]
3688
- );
4653
+ const promptId = str3(payload["prompt_id"] ?? payload["promptId"]);
3689
4654
  const rawSessionId = str3(envelope["session_id"]) || "";
3690
4655
  const sessionKey = deriveSessionKey(rawSessionId || "unknown-session");
3691
- if (alreadyRefusedTurn(workspaceRoot, sessionKey, turnOrdinal)) return EXIT_ALLOW4;
4656
+ const runState = readTurnRunState(workspaceRoot, rawSessionId);
4657
+ return {
4658
+ workspaceRoot,
4659
+ rawSessionId,
4660
+ sessionKey,
4661
+ turnOrdinal,
4662
+ promptId,
4663
+ lastAssistant,
4664
+ toolCalls,
4665
+ turnTexts,
4666
+ closingFromPayload,
4667
+ dispatchOpen,
4668
+ runState
4669
+ };
4670
+ }
4671
+ function runDetectors(inputs) {
4672
+ const { dispatchOpen, lastAssistant, workspaceRoot, rawSessionId, toolCalls, runState } = inputs;
3692
4673
  const findings = [];
3693
4674
  for (const f of [
3694
4675
  // A and D are the completion-adjacent pair a pending dispatch excuses
@@ -3696,17 +4677,14 @@ function main6() {
3696
4677
  // excused by anything about a dispatch and run unchanged.
3697
4678
  dispatchOpen ? null : detectPromissory(lastAssistant),
3698
4679
  detectLanguage(lastAssistant, readLanguagePin(workspaceRoot, rawSessionId)),
3699
- detectUnverifiedEdit(toolCalls),
3700
- // Round 7 § Phase 1 — detector D. It is NOT unconditional, and this
3701
- // comment said it was while sitting one line above the `dispatchOpen`
3702
- // ternary that conditions it: A and D are both excused by an open
3703
- // dispatch, per the note at the top of this loop. Corrected 2026-08-18.
3704
- // It shipped with its own settings flag one commit earlier
3705
- // and that flag is gone: `hooks.turn_end_gate.*` was deleted on
3706
- // 2026-08-12 because a default-off safety gate is an absent one. Its
3707
- // gating is where the comment above says gating belongs — inside the
3708
- // detector: no CI observed, or a settled read, or no completion claim
3709
- // ⇒ no finding.
4680
+ detectUnverifiedEdit(toolCalls, runState),
4681
+ // Round 7 § Phase 1 — detector D. It is NOT unconditional: A and D are
4682
+ // both excused by an open dispatch, per the note above. It shipped with
4683
+ // its own settings flag one commit earlier and that flag is gone:
4684
+ // `hooks.turn_end_gate.*` was deleted on 2026-08-12 because a
4685
+ // default-off safety gate is an absent one. Its gating is where the
4686
+ // note above says gating belongs — inside the detector: no CI observed,
4687
+ // or a settled read, or no completion claim ⇒ no finding.
3710
4688
  dispatchOpen ? null : detectCompletionClaim(lastAssistant, readCiSettled(workspaceRoot, rawSessionId)),
3711
4689
  // Detector E is UNCONDITIONAL, and that is the opposite choice from A
3712
4690
  // and D one line above rather than an oversight. An open dispatch
@@ -3716,24 +4694,103 @@ function main6() {
3716
4694
  // that produced the measured failure — IS the continuation that drops
3717
4695
  // the decision, so narrowing E the same way would silence it in exactly
3718
4696
  // the case it exists for.
3719
- detectDroppedDecision(turnTexts, closingFromPayload),
4697
+ detectDroppedDecision(inputs.turnTexts, inputs.closingFromPayload),
3720
4698
  // Detector F takes the OTHER side of that same question, and the two
3721
4699
  // sitting adjacent is the clearest place to say why. E fires on a
3722
4700
  // question already asked, which an open dispatch cannot excuse. F fires
3723
4701
  // on a completion CLAIM, and a turn waiting on a subagent has not
3724
4702
  // finished — so its closing is not the claim F is about. Same slot,
3725
4703
  // opposite trigger, opposite treatment of `dispatchOpen`.
3726
- dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls)
4704
+ dispatchOpen ? null : detectUntestedChange(lastAssistant, toolCalls, runState)
3727
4705
  ]) {
3728
4706
  if (f) findings.push(f);
3729
4707
  }
3730
- if (findings.length === 0) return EXIT_ALLOW4;
4708
+ return findings;
4709
+ }
4710
+ function recordGraphFeeder(inputs, findings, layer) {
4711
+ if (is_replay_mode()) return;
4712
+ try {
4713
+ let state;
4714
+ try {
4715
+ state = graphState(inputs.workspaceRoot);
4716
+ } catch {
4717
+ return;
4718
+ }
4719
+ if (state === "absent") return;
4720
+ const paths = inputs.toolCalls.filter((c) => _EDIT_TOOLS.has(c.name) && c.path !== void 0).map((c) => c.path).filter(_isProductionSource);
4721
+ const f = findings.find((x) => x.detector === "untested");
4722
+ appendFeederRow(
4723
+ inputs.workspaceRoot,
4724
+ inputs.sessionKey,
4725
+ buildFeederRow({
4726
+ turn: inputs.turnOrdinal,
4727
+ layer,
4728
+ state,
4729
+ fFired: f !== void 0,
4730
+ fMode: f?.mode ?? null,
4731
+ paths,
4732
+ graph: graphUntestedVerdict(inputs.workspaceRoot, state, paths)
4733
+ })
4734
+ );
4735
+ } catch {
4736
+ }
4737
+ }
4738
+ function recordShadow(inputs, layer) {
4739
+ if (is_replay_mode()) return;
4740
+ try {
4741
+ const findings = runDetectors(inputs);
4742
+ const setBy = layer === "refused_turn" ? "ours" : shadowSetBy(inputs.workspaceRoot, inputs.sessionKey);
4743
+ recordGraphFeeder(inputs, findings, layer);
4744
+ const file = sessionShadowFile(inputs.workspaceRoot, inputs.sessionKey);
4745
+ let prev = null;
4746
+ try {
4747
+ prev = parseShadowRecord(fs12.readFileSync(file, "utf-8"));
4748
+ } catch {
4749
+ }
4750
+ atomic_write_json(
4751
+ file,
4752
+ foldShadow(prev, {
4753
+ detectors: findings.map((f) => f.detector),
4754
+ turnOrdinal: inputs.turnOrdinal,
4755
+ at: (/* @__PURE__ */ new Date()).toISOString(),
4756
+ layer,
4757
+ setBy
4758
+ })
4759
+ );
4760
+ } catch {
4761
+ }
4762
+ }
4763
+ function main6() {
4764
+ let envelope;
4765
+ let payload;
4766
+ try {
4767
+ [envelope, payload] = unwrap(readHookStdin(), "claude");
4768
+ } catch {
4769
+ return EXIT_ALLOW3;
4770
+ }
4771
+ if (payload["stop_hook_active"] === true) {
4772
+ try {
4773
+ const retryInputs = assembleDetectorInputs(envelope, payload);
4774
+ if (retryInputs !== null) recordShadow(retryInputs, "stop_hook_active");
4775
+ } catch {
4776
+ }
4777
+ return EXIT_ALLOW3;
4778
+ }
4779
+ const inputs = assembleDetectorInputs(envelope, payload);
4780
+ if (inputs === null) return EXIT_ALLOW3;
4781
+ if (alreadyRefusedTurn(inputs.workspaceRoot, inputs.sessionKey, inputs.turnOrdinal)) {
4782
+ recordShadow(inputs, "refused_turn");
4783
+ return EXIT_ALLOW3;
4784
+ }
4785
+ const findings = runDetectors(inputs);
4786
+ recordGraphFeeder(inputs, findings, "live");
4787
+ if (findings.length === 0) return EXIT_ALLOW3;
3731
4788
  markRefusedTurn(
3732
- workspaceRoot,
3733
- sessionKey,
3734
- turnOrdinal,
4789
+ inputs.workspaceRoot,
4790
+ inputs.sessionKey,
4791
+ inputs.turnOrdinal,
3735
4792
  findings.map((f) => f.detector),
3736
- promptId
4793
+ inputs.promptId
3737
4794
  );
3738
4795
  const lines = findings.map(
3739
4796
  (f) => ` \xB7 ${f.detector}: ${f.reason}
@@ -3755,13 +4812,13 @@ function _isCliEntry6() {
3755
4812
  if (process.argv[1] === void 0) {
3756
4813
  return false;
3757
4814
  }
3758
- const argvUrl = pathToFileURL6(path7.resolve(process.argv[1])).href;
4815
+ const argvUrl = pathToFileURL6(path10.resolve(process.argv[1])).href;
3759
4816
  if (import.meta.url === argvUrl) {
3760
4817
  return true;
3761
4818
  }
3762
4819
  try {
3763
- const here = fs7.realpathSync(fileURLToPath6(import.meta.url));
3764
- const argv = fs7.realpathSync(path7.resolve(process.argv[1]));
4820
+ const here = fs12.realpathSync(fileURLToPath6(import.meta.url));
4821
+ const argv = fs12.realpathSync(path10.resolve(process.argv[1]));
3765
4822
  return here === argv;
3766
4823
  } catch {
3767
4824
  return false;
@@ -3774,8 +4831,8 @@ if (_isCliEntry6()) {
3774
4831
  // src/scripts/hooks/evidence_independence.ts
3775
4832
  import { spawnSync as spawnSync3 } from "node:child_process";
3776
4833
  import crypto3 from "node:crypto";
3777
- import fs8 from "node:fs";
3778
- import path8 from "node:path";
4834
+ import fs13 from "node:fs";
4835
+ import path11 from "node:path";
3779
4836
  import { fileURLToPath as fileURLToPath7, pathToFileURL as pathToFileURL7 } from "node:url";
3780
4837
 
3781
4838
  // src/scripts/_lib/test_delta.ts
@@ -3794,9 +4851,7 @@ function isTestPath(p) {
3794
4851
  }
3795
4852
 
3796
4853
  // src/scripts/hooks/evidence_independence.ts
3797
- var EXIT_ALLOW5 = 0;
3798
- var EXIT_BLOCK2 = 1;
3799
- var STATE_FILE3 = path8.join("agents", "state", "evidence-dispatch.json");
4854
+ var STATE_FILE3 = path11.join("agents", "state", "evidence-dispatch.json");
3800
4855
  function _isObject5(v) {
3801
4856
  return typeof v === "object" && v !== null && !Array.isArray(v);
3802
4857
  }
@@ -3864,8 +4919,8 @@ function extractDispatch(envelope) {
3864
4919
  }
3865
4920
  function _ledgerStamp(consumer_root) {
3866
4921
  try {
3867
- const raw = fs8.readFileSync(
3868
- path8.join(consumer_root, "agents", "state", "git-authorization.json"),
4922
+ const raw = fs13.readFileSync(
4923
+ path11.join(consumer_root, "agents", "state", "git-authorization.json"),
3869
4924
  "utf8"
3870
4925
  );
3871
4926
  const d = JSON.parse(raw);
@@ -3876,7 +4931,7 @@ function _ledgerStamp(consumer_root) {
3876
4931
  }
3877
4932
  function _load(target) {
3878
4933
  try {
3879
- const decoded = JSON.parse(fs8.readFileSync(target, "utf8"));
4934
+ const decoded = JSON.parse(fs13.readFileSync(target, "utf8"));
3880
4935
  if (_isObject5(decoded) && Array.isArray(decoded["evaluations"])) {
3881
4936
  return decoded;
3882
4937
  }
@@ -3887,12 +4942,12 @@ function _load(target) {
3887
4942
  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.";
3888
4943
  function decide(tool, prompt, priorEvaluations) {
3889
4944
  if (!isDispatchTool(tool) || !isEvaluationPrompt(prompt)) {
3890
- return { exit: EXIT_ALLOW5, stdout: "", stderr: "", evaluations: priorEvaluations };
4945
+ return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
3891
4946
  }
3892
4947
  const preloaded = preloadedVerdict(prompt);
3893
4948
  if (preloaded !== null) {
3894
4949
  return {
3895
- exit: EXIT_BLOCK2,
4950
+ exit: EXIT_BLOCK,
3896
4951
  stdout: "",
3897
4952
  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.
3898
4953
  `,
@@ -3900,11 +4955,11 @@ function decide(tool, prompt, priorEvaluations) {
3900
4955
  };
3901
4956
  }
3902
4957
  if (!isSelfScoped(prompt)) {
3903
- return { exit: EXIT_ALLOW5, stdout: "", stderr: "", evaluations: priorEvaluations };
4958
+ return { exit: EXIT_ALLOW3, stdout: "", stderr: "", evaluations: priorEvaluations };
3904
4959
  }
3905
4960
  if (priorEvaluations >= 1) {
3906
4961
  return {
3907
- exit: EXIT_ALLOW5,
4962
+ exit: EXIT_ALLOW3,
3908
4963
  stdout: `${JSON.stringify({
3909
4964
  decision: "warn",
3910
4965
  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.`
@@ -3915,7 +4970,7 @@ function decide(tool, prompt, priorEvaluations) {
3915
4970
  };
3916
4971
  }
3917
4972
  return {
3918
- exit: EXIT_ALLOW5,
4973
+ exit: EXIT_ALLOW3,
3919
4974
  stdout: `${JSON.stringify({ decision: "warn", reason: WARN_TEXT })}
3920
4975
  `,
3921
4976
  stderr: "",
@@ -3957,10 +5012,10 @@ function run3(stdin_text, options) {
3957
5012
  envelope = decoded;
3958
5013
  }
3959
5014
  } catch {
3960
- return EXIT_ALLOW5;
5015
+ return EXIT_ALLOW3;
3961
5016
  }
3962
5017
  }
3963
- const target = path8.join(options.consumer_root, STATE_FILE3);
5018
+ const target = path11.join(options.consumer_root, STATE_FILE3);
3964
5019
  const state = _load(target);
3965
5020
  const session_id = typeof envelope["session_id"] === "string" ? envelope["session_id"] : "";
3966
5021
  const turnMarker = `${session_id}:${_ledgerStamp(options.consumer_root)}`;
@@ -3976,7 +5031,7 @@ function run3(stdin_text, options) {
3976
5031
  if (staged !== null && touchesTestAndCode(staged)) {
3977
5032
  process.stdout.write(`${JSON.stringify({ decision: "warn", reason: flag })}
3978
5033
  `);
3979
- return EXIT_ALLOW5;
5034
+ return EXIT_ALLOW3;
3980
5035
  }
3981
5036
  }
3982
5037
  const decision = decide(tool, prompt, state.evaluations.length);
@@ -4028,13 +5083,13 @@ function _isCliEntry7() {
4028
5083
  if (process.argv[1] === void 0) {
4029
5084
  return false;
4030
5085
  }
4031
- const argvUrl = pathToFileURL7(path8.resolve(process.argv[1])).href;
5086
+ const argvUrl = pathToFileURL7(path11.resolve(process.argv[1])).href;
4032
5087
  if (import.meta.url === argvUrl) {
4033
5088
  return true;
4034
5089
  }
4035
5090
  try {
4036
- const here = fs8.realpathSync(fileURLToPath7(import.meta.url));
4037
- const argv = fs8.realpathSync(path8.resolve(process.argv[1]));
5091
+ const here = fs13.realpathSync(fileURLToPath7(import.meta.url));
5092
+ const argv = fs13.realpathSync(path11.resolve(process.argv[1]));
4038
5093
  return here === argv;
4039
5094
  } catch {
4040
5095
  return false;
@@ -4045,8 +5100,8 @@ if (_isCliEntry7()) {
4045
5100
  }
4046
5101
 
4047
5102
  // src/scripts/hooks/delegation_nudge_hook.ts
4048
- import fs9 from "node:fs";
4049
- import path9 from "node:path";
5103
+ import fs14 from "node:fs";
5104
+ import path12 from "node:path";
4050
5105
  import { fileURLToPath as fileURLToPath8, pathToFileURL as pathToFileURL8 } from "node:url";
4051
5106
 
4052
5107
  // src/scripts/_lib/auto_dispatch.ts
@@ -4327,8 +5382,6 @@ function resolveSubagentRouting(inp) {
4327
5382
  }
4328
5383
 
4329
5384
  // src/scripts/hooks/delegation_nudge_hook.ts
4330
- var EXIT_ALLOW6 = 0;
4331
- var EXIT_WARN3 = 2;
4332
5385
  function _isObject6(v) {
4333
5386
  return typeof v === "object" && v !== null && !Array.isArray(v);
4334
5387
  }
@@ -4508,28 +5561,28 @@ function main8() {
4508
5561
  const raw = readHookStdin();
4509
5562
  envelope = raw.trim() ? JSON.parse(raw) : {};
4510
5563
  } catch {
4511
- return EXIT_ALLOW6;
5564
+ return EXIT_ALLOW3;
4512
5565
  }
4513
5566
  const env = _isObject6(envelope) ? envelope : {};
4514
5567
  const event = env["event"];
4515
5568
  const slot = typeof event === "string" && event !== "" ? event : "user_prompt_submit";
4516
5569
  if (slot !== "user_prompt_submit") {
4517
- return EXIT_ALLOW6;
5570
+ return EXIT_ALLOW3;
4518
5571
  }
4519
5572
  const payload = _isObject6(env["payload"]) ? env["payload"] : env;
4520
5573
  const prompt = _extractPrompt2(payload);
4521
5574
  if (!prompt) {
4522
- return EXIT_ALLOW6;
5575
+ return EXIT_ALLOW3;
4523
5576
  }
4524
5577
  if (isSyntheticPrompt(prompt)) {
4525
- return EXIT_ALLOW6;
5578
+ return EXIT_ALLOW3;
4526
5579
  }
4527
5580
  const root = _workspaceRoot(env);
4528
5581
  const platform = env["platform"];
4529
5582
  const hostId = typeof platform === "string" && platform ? platform : null;
4530
5583
  const result = classifyPrompt(prompt, root, hostId);
4531
5584
  if (!result) {
4532
- return EXIT_ALLOW6;
5585
+ return EXIT_ALLOW3;
4533
5586
  }
4534
5587
  process.stdout.write(
4535
5588
  `${JSON.stringify({
@@ -4539,7 +5592,7 @@ function main8() {
4539
5592
  })}
4540
5593
  `
4541
5594
  );
4542
- return EXIT_WARN3;
5595
+ return EXIT_WARN2;
4543
5596
  }
4544
5597
  function _isCliEntry8() {
4545
5598
  if (true) {
@@ -4548,13 +5601,13 @@ function _isCliEntry8() {
4548
5601
  if (process.argv[1] === void 0) {
4549
5602
  return false;
4550
5603
  }
4551
- const argvUrl = pathToFileURL8(path9.resolve(process.argv[1])).href;
5604
+ const argvUrl = pathToFileURL8(path12.resolve(process.argv[1])).href;
4552
5605
  if (import.meta.url === argvUrl) {
4553
5606
  return true;
4554
5607
  }
4555
5608
  try {
4556
- const here = fs9.realpathSync(fileURLToPath8(import.meta.url));
4557
- const argv = fs9.realpathSync(path9.resolve(process.argv[1]));
5609
+ const here = fs14.realpathSync(fileURLToPath8(import.meta.url));
5610
+ const argv = fs14.realpathSync(path12.resolve(process.argv[1]));
4558
5611
  return here === argv;
4559
5612
  } catch {
4560
5613
  return false;
@@ -4565,7 +5618,7 @@ if (_isCliEntry8()) {
4565
5618
  }
4566
5619
 
4567
5620
  // src/scripts/conformance_scan.ts
4568
- var REPO_ROOT = path10.resolve(path10.dirname(fileURLToPath9(import.meta.url)), "..", "..");
5621
+ var REPO_ROOT = path13.resolve(path13.dirname(fileURLToPath9(import.meta.url)), "..", "..");
4569
5622
  var OBSERVED_BAND = { low: 9.1, high: 39.2 };
4570
5623
  var BAND_MIN_TURNS = 1978;
4571
5624
  var CARRIER_CHANGES = [
@@ -4931,7 +5984,7 @@ function scanSession(sessionId, lines) {
4931
5984
  closeWindow();
4932
5985
  return report;
4933
5986
  }
4934
- var DEFAULT_RATE_SERIES = path10.join(
5987
+ var DEFAULT_RATE_SERIES = path13.join(
4935
5988
  REPO_ROOT,
4936
5989
  "agents",
4937
5990
  "runtime",
@@ -4940,7 +5993,7 @@ var DEFAULT_RATE_SERIES = path10.join(
4940
5993
  );
4941
5994
  function defaultStore(projectDir) {
4942
5995
  const slug = projectStoreSlug(projectDir);
4943
- return path10.join(process.env["HOME"] ?? "", ".claude", "projects", slug);
5996
+ return path13.join(process.env["HOME"] ?? "", ".claude", "projects", slug);
4944
5997
  }
4945
5998
  function _tokens(chars) {
4946
5999
  return Math.round(chars / 4);
@@ -4951,13 +6004,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
4951
6004
  return {
4952
6005
  project: {
4953
6006
  dir: projectRulesDir,
4954
- present: fs10.existsSync(projectRulesDir),
6007
+ present: fs15.existsSync(projectRulesDir),
4955
6008
  files: p.files,
4956
6009
  tokens: _tokens(p.chars)
4957
6010
  },
4958
6011
  global: {
4959
6012
  dir: globalRulesDir,
4960
- present: fs10.existsSync(globalRulesDir),
6013
+ present: fs15.existsSync(globalRulesDir),
4961
6014
  files: g.files,
4962
6015
  tokens: _tokens(g.chars)
4963
6016
  },
@@ -4965,13 +6018,13 @@ function measureDelivered(projectRulesDir, globalRulesDir) {
4965
6018
  };
4966
6019
  }
4967
6020
  function storeKey(store) {
4968
- return createHash3("sha256").update(path10.resolve(store)).digest("hex").slice(0, 12);
6021
+ return createHash4("sha256").update(path13.resolve(store)).digest("hex").slice(0, 12);
4969
6022
  }
4970
6023
  function scanStore(store, limit) {
4971
- 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);
6024
+ const files = fs15.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ f, m: fs15.statSync(path13.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.f);
4972
6025
  const per_session = [];
4973
6026
  for (const f of files) {
4974
- const lines = fs10.readFileSync(path10.join(store, f), "utf8").split("\n");
6027
+ const lines = fs15.readFileSync(path13.join(store, f), "utf8").split("\n");
4975
6028
  const r = scanSession(f.replace(/\.jsonl$/, "").slice(0, 8), lines);
4976
6029
  if (r.assistant_turns > 0) {
4977
6030
  per_session.push(r);
@@ -4999,10 +6052,10 @@ function scanStore(store, limit) {
4999
6052
  const task_completeness_rate_pct = completeness_windows === 0 ? 0 : Number((100 * task_completeness / completeness_windows).toFixed(1));
5000
6053
  const era = spansCarrierChange(per_session.map((s) => s.first_at));
5001
6054
  const delivered = measureDelivered(
5002
- path10.join(REPO_ROOT, ".claude", "rules"),
5003
- path10.join(process.env["HOME"] ?? os2.homedir(), ".claude", "rules")
6055
+ path13.join(REPO_ROOT, ".claude", "rules"),
6056
+ path13.join(process.env["HOME"] ?? os2.homedir(), ".claude", "rules")
5004
6057
  );
5005
- const ownStore = path10.resolve(store) === path10.resolve(defaultStore(REPO_ROOT));
6058
+ const ownStore = path13.resolve(store) === path13.resolve(defaultStore(REPO_ROOT));
5006
6059
  return {
5007
6060
  scanned_at: (/* @__PURE__ */ new Date()).toISOString(),
5008
6061
  store,
@@ -5032,8 +6085,8 @@ function scanStore(store, limit) {
5032
6085
  };
5033
6086
  }
5034
6087
  function recordRate(file, scanned_at, rate) {
5035
- fs10.mkdirSync(path10.dirname(file), { recursive: true });
5036
- fs10.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
6088
+ fs15.mkdirSync(path13.dirname(file), { recursive: true });
6089
+ fs15.appendFileSync(file, `${JSON.stringify({ measured_at: scanned_at, ...rate })}
5037
6090
  `, "utf8");
5038
6091
  }
5039
6092
  function render(report) {
@@ -5164,14 +6217,14 @@ function main9(argv) {
5164
6217
  return 2;
5165
6218
  }
5166
6219
  const resolved = store ?? defaultStore(process.cwd());
5167
- if (!fs10.existsSync(resolved)) {
6220
+ if (!fs15.existsSync(resolved)) {
5168
6221
  process.stderr.write(`conformance:behavior: no transcript store at ${resolved}
5169
6222
  `);
5170
6223
  return 1;
5171
6224
  }
5172
6225
  const report = scanStore(resolved, Number.isFinite(limit) && limit > 0 ? limit : 30);
5173
6226
  if (out) {
5174
- fs10.writeFileSync(out, `${JSON.stringify(report, null, 2)}
6227
+ fs15.writeFileSync(out, `${JSON.stringify(report, null, 2)}
5175
6228
  `, "utf8");
5176
6229
  }
5177
6230
  if (record) {
@@ -5179,7 +6232,7 @@ function main9(argv) {
5179
6232
  }
5180
6233
  if (why !== null) {
5181
6234
  const id = why;
5182
- fs10.writeSync(
6235
+ fs15.writeSync(
5183
6236
  1,
5184
6237
  json ? `${JSON.stringify(
5185
6238
  {
@@ -5199,7 +6252,7 @@ function main9(argv) {
5199
6252
  );
5200
6253
  return 0;
5201
6254
  }
5202
- fs10.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
6255
+ fs15.writeSync(1, json ? `${JSON.stringify(report, null, 2)}
5203
6256
  ` : `${render(report)}
5204
6257
  `);
5205
6258
  return 0;
@@ -5211,13 +6264,13 @@ function _isCliEntry9() {
5211
6264
  if (process.argv[1] === void 0) {
5212
6265
  return false;
5213
6266
  }
5214
- const argvUrl = pathToFileURL9(path10.resolve(process.argv[1])).href;
6267
+ const argvUrl = pathToFileURL9(path13.resolve(process.argv[1])).href;
5215
6268
  if (import.meta.url === argvUrl) {
5216
6269
  return true;
5217
6270
  }
5218
6271
  try {
5219
- const here = fs10.realpathSync(fileURLToPath9(import.meta.url));
5220
- const argv = fs10.realpathSync(path10.resolve(process.argv[1]));
6272
+ const here = fs15.realpathSync(fileURLToPath9(import.meta.url));
6273
+ const argv = fs15.realpathSync(path13.resolve(process.argv[1]));
5221
6274
  return here === argv;
5222
6275
  } catch {
5223
6276
  return false;
@@ -5228,19 +6281,19 @@ if (_isCliEntry9()) {
5228
6281
  }
5229
6282
 
5230
6283
  // src/scripts/report_carrier_divergence.ts
5231
- import * as fs11 from "node:fs";
6284
+ import * as fs16 from "node:fs";
5232
6285
  import * as os3 from "node:os";
5233
- import * as path11 from "node:path";
6286
+ import * as path14 from "node:path";
5234
6287
  import process2 from "node:process";
5235
6288
  import { fileURLToPath as fileURLToPath10, pathToFileURL as pathToFileURL10 } from "node:url";
5236
6289
  var _HERE2 = fileURLToPath10(import.meta.url);
5237
- var REPO_ROOT2 = path11.resolve(path11.dirname(_HERE2), "..", "..");
5238
- var PROJECT_RULES = path11.join(".claude", "rules");
5239
- var PROJECTION_SOURCE = path11.join("dist", "agent-src", "rules");
5240
- var GLOBAL_RULES = path11.join(".claude", "rules");
6290
+ var REPO_ROOT2 = path14.resolve(path14.dirname(_HERE2), "..", "..");
6291
+ var PROJECT_RULES = path14.join(".claude", "rules");
6292
+ var PROJECTION_SOURCE = path14.join("dist", "agent-src", "rules");
6293
+ var GLOBAL_RULES = path14.join(".claude", "rules");
5241
6294
  function _listRules(dir) {
5242
6295
  try {
5243
- return fs11.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
6296
+ return fs16.readdirSync(dir).filter((f) => f.endsWith(".md")).sort();
5244
6297
  } catch {
5245
6298
  return [];
5246
6299
  }
@@ -5248,7 +6301,7 @@ function _listRules(dir) {
5248
6301
  function _ruleType(file) {
5249
6302
  let text;
5250
6303
  try {
5251
- text = fs11.readFileSync(file, "utf-8");
6304
+ text = fs16.readFileSync(file, "utf-8");
5252
6305
  } catch {
5253
6306
  return "";
5254
6307
  }
@@ -5259,8 +6312,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
5259
6312
  const out = {
5260
6313
  projectDir,
5261
6314
  globalDir,
5262
- globalPresent: fs11.existsSync(globalDir),
5263
- projectPresent: fs11.existsSync(projectDir),
6315
+ globalPresent: fs16.existsSync(globalDir),
6316
+ projectPresent: fs16.existsSync(projectDir),
5264
6317
  shared: 0,
5265
6318
  identical: [],
5266
6319
  provenanceOnly: [],
@@ -5283,7 +6336,7 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
5283
6336
  }
5284
6337
  if (!inP && inG) {
5285
6338
  out.globalOnly.push(rule);
5286
- if (sourceDir !== void 0 && _ruleType(path11.join(sourceDir, rule)) === "manual") {
6339
+ if (sourceDir !== void 0 && _ruleType(path14.join(sourceDir, rule)) === "manual") {
5287
6340
  out.manualOnlyGlobal.push(rule);
5288
6341
  }
5289
6342
  continue;
@@ -5293,8 +6346,8 @@ function compareCarriers(projectDir, globalDir, sourceDir) {
5293
6346
  let projectCopy;
5294
6347
  let globalCopy;
5295
6348
  try {
5296
- projectCopy = fs11.readFileSync(path11.join(projectDir, rule));
5297
- globalCopy = fs11.readFileSync(path11.join(globalDir, rule));
6349
+ projectCopy = fs16.readFileSync(path14.join(projectDir, rule));
6350
+ globalCopy = fs16.readFileSync(path14.join(globalDir, rule));
5298
6351
  verdict = comparePair(projectCopy, globalCopy);
5299
6352
  } catch {
5300
6353
  out.shared -= 1;
@@ -5441,9 +6494,9 @@ function render2(d) {
5441
6494
  }
5442
6495
  function main10(argv) {
5443
6496
  const args = argv ?? process2.argv.slice(2);
5444
- let projectDir = path11.join(REPO_ROOT2, PROJECT_RULES);
5445
- let globalDir = path11.join(os3.homedir(), GLOBAL_RULES);
5446
- let sourceDir = path11.join(REPO_ROOT2, PROJECTION_SOURCE);
6497
+ let projectDir = path14.join(REPO_ROOT2, PROJECT_RULES);
6498
+ let globalDir = path14.join(os3.homedir(), GLOBAL_RULES);
6499
+ let sourceDir = path14.join(REPO_ROOT2, PROJECTION_SOURCE);
5447
6500
  const value = (i) => {
5448
6501
  const v = args[i + 1];
5449
6502
  return v === void 0 || v.startsWith("-") ? null : v;
@@ -5457,7 +6510,7 @@ function main10(argv) {
5457
6510
  `);
5458
6511
  return 1;
5459
6512
  }
5460
- const resolved = path11.resolve(v);
6513
+ const resolved = path14.resolve(v);
5461
6514
  if (a === "--project") projectDir = resolved;
5462
6515
  else if (a === "--global") globalDir = resolved;
5463
6516
  else sourceDir = resolved;
@@ -5479,9 +6532,9 @@ function main10(argv) {
5479
6532
  }
5480
6533
  function _isCliEntry10() {
5481
6534
  if (process2.argv[1] === void 0) return false;
5482
- if (import.meta.url === pathToFileURL10(path11.resolve(process2.argv[1])).href) return true;
6535
+ if (import.meta.url === pathToFileURL10(path14.resolve(process2.argv[1])).href) return true;
5483
6536
  try {
5484
- return fs11.realpathSync(_HERE2) === fs11.realpathSync(path11.resolve(process2.argv[1]));
6537
+ return fs16.realpathSync(_HERE2) === fs16.realpathSync(path14.resolve(process2.argv[1]));
5485
6538
  } catch {
5486
6539
  return false;
5487
6540
  }
@@ -5491,12 +6544,12 @@ if (_isCliEntry10()) {
5491
6544
  }
5492
6545
 
5493
6546
  // src/scripts/report_skill_activation.ts
5494
- import * as fs12 from "node:fs";
6547
+ import * as fs17 from "node:fs";
5495
6548
  import * as os4 from "node:os";
5496
- import * as path12 from "node:path";
6549
+ import * as path15 from "node:path";
5497
6550
  import { fileURLToPath as fileURLToPath11, pathToFileURL as pathToFileURL11 } from "node:url";
5498
6551
  var _HERE3 = fileURLToPath11(import.meta.url);
5499
- var REPO_ROOT3 = path12.resolve(path12.dirname(_HERE3), "..", "..");
6552
+ var REPO_ROOT3 = path15.resolve(path15.dirname(_HERE3), "..", "..");
5500
6553
  var SKILLS_ROOT = "src/skills";
5501
6554
  var TRIGGER_KEYS = ["triggers:", "trigger_description:", "file_pattern:", "path_prefix:"];
5502
6555
  var DETERMINISTIC_RE = /^\s*(?:[-*]\s*)?(?:\*\*)?(MUST|NEVER|ALWAYS)\b/m;
@@ -5507,25 +6560,25 @@ function censusSkills(root) {
5507
6560
  withTriggerCorpus: [],
5508
6561
  withDeterministicObligation: []
5509
6562
  };
5510
- if (!fs12.existsSync(root)) {
6563
+ if (!fs17.existsSync(root)) {
5511
6564
  return out;
5512
6565
  }
5513
- for (const entry of fs12.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
6566
+ for (const entry of fs17.readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
5514
6567
  if (!entry.isDirectory()) {
5515
6568
  continue;
5516
6569
  }
5517
- const file = path12.join(root, entry.name, "SKILL.md");
5518
- if (!fs12.existsSync(file)) {
6570
+ const file = path15.join(root, entry.name, "SKILL.md");
6571
+ if (!fs17.existsSync(file)) {
5519
6572
  continue;
5520
6573
  }
5521
6574
  out.total += 1;
5522
- const text = fs12.readFileSync(file, "utf8");
6575
+ const text = fs17.readFileSync(file, "utf8");
5523
6576
  const fmEnd = text.startsWith("---") ? text.indexOf("\n---", 3) : -1;
5524
6577
  const fm = fmEnd === -1 ? "" : text.slice(0, fmEnd);
5525
6578
  if (TRIGGER_KEYS.some((k) => new RegExp(`^${k}`, "m").test(fm))) {
5526
6579
  out.withTriggerKey.push(entry.name);
5527
6580
  }
5528
- if (fs12.existsSync(path12.join(root, entry.name, "evals", "triggers.json"))) {
6581
+ if (fs17.existsSync(path15.join(root, entry.name, "evals", "triggers.json"))) {
5529
6582
  out.withTriggerCorpus.push(entry.name);
5530
6583
  }
5531
6584
  const body = fmEnd === -1 ? text : text.slice(fmEnd + 4);
@@ -5536,17 +6589,17 @@ function censusSkills(root) {
5536
6589
  return out;
5537
6590
  }
5538
6591
  function defaultStore2(cwd) {
5539
- return path12.join(os4.homedir(), ".claude", "projects", projectStoreSlug(cwd));
6592
+ return path15.join(os4.homedir(), ".claude", "projects", projectStoreSlug(cwd));
5540
6593
  }
5541
6594
  function measureUsage(store, limit) {
5542
6595
  const rep = { store, sessions: 0, assistantTurns: 0, invocations: 0, bySkill: {} };
5543
- if (!fs12.existsSync(store)) {
6596
+ if (!fs17.existsSync(store)) {
5544
6597
  return rep;
5545
6598
  }
5546
- 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);
6599
+ const files = fs17.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => path15.join(store, f)).sort((a, b) => fs17.statSync(b).mtimeMs - fs17.statSync(a).mtimeMs).slice(0, limit);
5547
6600
  rep.sessions = files.length;
5548
6601
  for (const file of files) {
5549
- for (const line of fs12.readFileSync(file, "utf8").split("\n")) {
6602
+ for (const line of fs17.readFileSync(file, "utf8").split("\n")) {
5550
6603
  if (!line.trim()) {
5551
6604
  continue;
5552
6605
  }
@@ -5619,15 +6672,15 @@ function buildRecord(census, usage, today) {
5619
6672
  assistant_turns: usage.reduce((a, u) => a + u.assistantTurns, 0),
5620
6673
  invocations: usage.reduce((a, u) => a + u.invocations, 0),
5621
6674
  distinct_skills_invoked: distinct.size,
5622
- stores: usage.map((u) => path12.basename(u.store))
6675
+ stores: usage.map((u) => path15.basename(u.store))
5623
6676
  };
5624
6677
  }
5625
6678
  function readRecord(repoRoot) {
5626
- const file = path12.join(repoRoot, RECORD_PATH);
5627
- if (!fs12.existsSync(file)) {
6679
+ const file = path15.join(repoRoot, RECORD_PATH);
6680
+ if (!fs17.existsSync(file)) {
5628
6681
  return null;
5629
6682
  }
5630
- return JSON.parse(fs12.readFileSync(file, "utf8"));
6683
+ return JSON.parse(fs17.readFileSync(file, "utf8"));
5631
6684
  }
5632
6685
  function render3(census, usage, published = null) {
5633
6686
  const lines = [];
@@ -5662,7 +6715,7 @@ function render3(census, usage, published = null) {
5662
6715
  }
5663
6716
  totalInv += u.invocations;
5664
6717
  lines.push(
5665
- ` ${path12.basename(u.store).slice(-38).padEnd(38)} sessions=${String(u.sessions).padStart(3)} asst=${String(u.assistantTurns).padStart(6)} Skill calls=${String(u.invocations).padStart(3)}`
6718
+ ` ${path15.basename(u.store).slice(-38).padEnd(38)} sessions=${String(u.sessions).padStart(3)} asst=${String(u.assistantTurns).padStart(6)} Skill calls=${String(u.invocations).padStart(3)}`
5666
6719
  );
5667
6720
  }
5668
6721
  lines.push("");
@@ -5766,7 +6819,7 @@ function main11(argv) {
5766
6819
  if (stores.length === 0) {
5767
6820
  stores.push(defaultStore2(REPO_ROOT3));
5768
6821
  }
5769
- const census = censusSkills(path12.join(root, SKILLS_ROOT));
6822
+ const census = censusSkills(path15.join(root, SKILLS_ROOT));
5770
6823
  const usage = stores.map((s) => measureUsage(s, limit));
5771
6824
  process.stdout.write(`${render3(census, usage, readRecord(root))}
5772
6825
  `);
@@ -5787,27 +6840,27 @@ function main11(argv) {
5787
6840
  return 1;
5788
6841
  }
5789
6842
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
5790
- const file = path12.join(root, RECORD_PATH);
5791
- fs12.mkdirSync(path12.dirname(file), { recursive: true });
5792
- fs12.writeFileSync(file, `${JSON.stringify(buildRecord(census, usage, today), null, 2)}
6843
+ const file = path15.join(root, RECORD_PATH);
6844
+ fs17.mkdirSync(path15.dirname(file), { recursive: true });
6845
+ fs17.writeFileSync(file, `${JSON.stringify(buildRecord(census, usage, today), null, 2)}
5793
6846
  `);
5794
6847
  process.stdout.write(`
5795
6848
  wrote ${RECORD_PATH} (measured ${today})
5796
6849
  `);
5797
6850
  return 0;
5798
6851
  }
5799
- if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL11(path12.resolve(process.argv[1])).href) {
6852
+ if (process.argv[1] !== void 0 && import.meta.url === pathToFileURL11(path15.resolve(process.argv[1])).href) {
5800
6853
  process.exit(main11(process.argv.slice(2)));
5801
6854
  }
5802
6855
 
5803
6856
  // src/scripts/report_skill_obligation_violations.ts
5804
- import * as fs13 from "node:fs";
6857
+ import * as fs18 from "node:fs";
5805
6858
  import * as os5 from "node:os";
5806
- import * as path13 from "node:path";
6859
+ import * as path16 from "node:path";
5807
6860
  import process3 from "node:process";
5808
6861
  import { fileURLToPath as fileURLToPath12, pathToFileURL as pathToFileURL12 } from "node:url";
5809
6862
  var _HERE4 = fileURLToPath12(import.meta.url);
5810
- var REPO_ROOT4 = path13.resolve(path13.dirname(_HERE4), "..", "..");
6863
+ var REPO_ROOT4 = path16.resolve(path16.dirname(_HERE4), "..", "..");
5811
6864
  var OBLIGATION_LINE_RE = new RegExp(`${DETERMINISTIC_RE.source}.*`, "gm");
5812
6865
  var PATH_RE = /`([A-Za-z0-9_./-]*\/[A-Za-z0-9_.-]+\.[a-z]{2,6})`/g;
5813
6866
  var CMD_RE = /`([a-z][a-z0-9-]*(?: [a-z][a-z0-9:._-]*){1,3})`/g;
@@ -5845,7 +6898,7 @@ function _artefacts(line) {
5845
6898
  return out;
5846
6899
  }
5847
6900
  function extractObligations(repoRoot) {
5848
- const skillsRoot = path13.join(repoRoot, SKILLS_ROOT);
6901
+ const skillsRoot = path16.join(repoRoot, SKILLS_ROOT);
5849
6902
  const census = censusSkills(skillsRoot);
5850
6903
  const out = {
5851
6904
  skills: census.withDeterministicObligation,
@@ -5859,7 +6912,7 @@ function extractObligations(repoRoot) {
5859
6912
  for (const skill of census.withDeterministicObligation) {
5860
6913
  let text;
5861
6914
  try {
5862
- text = fs13.readFileSync(path13.join(skillsRoot, skill, "SKILL.md"), "utf-8");
6915
+ text = fs18.readFileSync(path16.join(skillsRoot, skill, "SKILL.md"), "utf-8");
5863
6916
  } catch {
5864
6917
  continue;
5865
6918
  }
@@ -5975,7 +7028,7 @@ function scanSessionForViolations(session, lines, forbidden, parsed) {
5975
7028
  return flags;
5976
7029
  }
5977
7030
  function defaultStore3(cwd) {
5978
- return path13.join(os5.homedir(), ".claude", "projects", projectStoreSlug(cwd));
7031
+ return path16.join(os5.homedir(), ".claude", "projects", projectStoreSlug(cwd));
5979
7032
  }
5980
7033
  function scanStore2(repoRoot, store, limit) {
5981
7034
  const census = extractObligations(repoRoot);
@@ -5988,14 +7041,14 @@ function scanStore2(repoRoot, store, limit) {
5988
7041
  };
5989
7042
  let files;
5990
7043
  try {
5991
- 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);
7044
+ files = fs18.readdirSync(store).filter((f) => f.endsWith(".jsonl")).map((f) => ({ p: path16.join(store, f), m: fs18.statSync(path16.join(store, f)).mtimeMs })).sort((a, b) => b.m - a.m).slice(0, limit).map((x) => x.p);
5992
7045
  } catch {
5993
7046
  return report;
5994
7047
  }
5995
7048
  report.sessions = files.length;
5996
7049
  for (const file of files) {
5997
- const lines = fs13.readFileSync(file, "utf-8").split("\n");
5998
- const id = path13.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
7050
+ const lines = fs18.readFileSync(file, "utf-8").split("\n");
7051
+ const id = path16.basename(file).replace(/\.jsonl$/, "").slice(0, 8);
5999
7052
  const entries = _parseSession(lines);
6000
7053
  if (loadedSkillsFrom(entries).size > 0) report.sessionsWithASkill += 1;
6001
7054
  report.flags.push(...scanSessionForViolations(id, lines, census.forbidden, entries));
@@ -6120,9 +7173,9 @@ function main12(argv) {
6120
7173
  }
6121
7174
  function _isCliEntry11() {
6122
7175
  if (process3.argv[1] === void 0) return false;
6123
- if (import.meta.url === pathToFileURL12(path13.resolve(process3.argv[1])).href) return true;
7176
+ if (import.meta.url === pathToFileURL12(path16.resolve(process3.argv[1])).href) return true;
6124
7177
  try {
6125
- return fs13.realpathSync(_HERE4) === fs13.realpathSync(path13.resolve(process3.argv[1]));
7178
+ return fs18.realpathSync(_HERE4) === fs18.realpathSync(path16.resolve(process3.argv[1]));
6126
7179
  } catch {
6127
7180
  return false;
6128
7181
  }
@@ -6133,20 +7186,20 @@ if (_isCliEntry11()) {
6133
7186
 
6134
7187
  // src/scripts/report_conformance_funnel.ts
6135
7188
  var _HERE5 = fileURLToPath13(import.meta.url);
6136
- var REPO_ROOT5 = path14.resolve(path14.dirname(_HERE5), "..", "..");
7189
+ var REPO_ROOT5 = path17.resolve(path17.dirname(_HERE5), "..", "..");
6137
7190
  function buildFunnel(opts = {}) {
6138
7191
  const repoRoot = opts.repoRoot ?? REPO_ROOT5;
6139
- const projectRulesDir = opts.projectRulesDir ?? path14.join(repoRoot, PROJECT_RULES);
6140
- const globalRulesDir = opts.globalRulesDir ?? path14.join(os6.homedir(), GLOBAL_RULES);
6141
- const sourceDir = opts.sourceDir ?? path14.join(repoRoot, PROJECTION_SOURCE);
7192
+ const projectRulesDir = opts.projectRulesDir ?? path17.join(repoRoot, PROJECT_RULES);
7193
+ const globalRulesDir = opts.globalRulesDir ?? path17.join(os6.homedir(), GLOBAL_RULES);
7194
+ const sourceDir = opts.sourceDir ?? path17.join(repoRoot, PROJECTION_SOURCE);
6142
7195
  const store = opts.store ?? defaultStore(repoRoot);
6143
7196
  const limit = opts.limit ?? 30;
6144
7197
  return {
6145
7198
  delivered: measureDelivered(projectRulesDir, globalRulesDir),
6146
7199
  divergence: compareCarriers(projectRulesDir, globalRulesDir, sourceDir),
6147
- skills: censusSkills(path14.join(repoRoot, SKILLS_ROOT)),
7200
+ skills: censusSkills(path17.join(repoRoot, SKILLS_ROOT)),
6148
7201
  usage: measureUsage(store, limit),
6149
- storePresent: fs14.existsSync(store),
7202
+ storePresent: fs19.existsSync(store),
6150
7203
  sk2: scanStore2(repoRoot, store, limit)
6151
7204
  };
6152
7205
  }
@@ -6287,7 +7340,7 @@ function main13(argv) {
6287
7340
  `);
6288
7341
  return 1;
6289
7342
  }
6290
- const resolved = path14.resolve(v);
7343
+ const resolved = path17.resolve(v);
6291
7344
  if (a === "--store") opts.store = resolved;
6292
7345
  else if (a === "--project") opts.projectRulesDir = resolved;
6293
7346
  else if (a === "--global") opts.globalRulesDir = resolved;
@@ -6319,9 +7372,9 @@ function main13(argv) {
6319
7372
  }
6320
7373
  function _isCliEntry12() {
6321
7374
  if (process4.argv[1] === void 0) return false;
6322
- if (import.meta.url === pathToFileURL13(path14.resolve(process4.argv[1])).href) return true;
7375
+ if (import.meta.url === pathToFileURL13(path17.resolve(process4.argv[1])).href) return true;
6323
7376
  try {
6324
- return fs14.realpathSync(_HERE5) === fs14.realpathSync(path14.resolve(process4.argv[1]));
7377
+ return fs19.realpathSync(_HERE5) === fs19.realpathSync(path17.resolve(process4.argv[1]));
6325
7378
  } catch {
6326
7379
  return false;
6327
7380
  }
@@ -6358,20 +7411,20 @@ function conformanceLogPath() {
6358
7411
  if (override && override.trim().length > 0) {
6359
7412
  return override;
6360
7413
  }
6361
- return path15.join(os7.homedir(), ".event4u", "agent-config", "conformance-log.jsonl");
7414
+ return path18.join(os7.homedir(), ".event4u", "agent-config", "conformance-log.jsonl");
6362
7415
  }
6363
7416
  function installLogPath() {
6364
7417
  const override = process5.env["AGENT_CONFIG_INSTALL_LOG"];
6365
7418
  if (override && override.trim().length > 0) {
6366
7419
  return override;
6367
7420
  }
6368
- return path15.join(os7.homedir(), ".event4u", "agent-config", "install-log.jsonl");
7421
+ return path18.join(os7.homedir(), ".event4u", "agent-config", "install-log.jsonl");
6369
7422
  }
6370
7423
  function _installLogExpected(projectRoot) {
6371
- return fs15.existsSync(manifest_path(projectRoot));
7424
+ return fs20.existsSync(manifest_path(projectRoot));
6372
7425
  }
6373
7426
  function _check_txlog_clean(logPath = installLogPath(), projectRoot = null) {
6374
- const entries = fs15.existsSync(logPath) ? readRecentEntries(logPath) : [];
7427
+ const entries = fs20.existsSync(logPath) ? readRecentEntries(logPath) : [];
6375
7428
  if (entries.length === 0) {
6376
7429
  if (projectRoot !== null && _installLogExpected(projectRoot)) {
6377
7430
  return {
@@ -6405,11 +7458,11 @@ function _check_txlog_clean(logPath = installLogPath(), projectRoot = null) {
6405
7458
  };
6406
7459
  }
6407
7460
  function routeTargetPaths(target) {
6408
- return routeTargetPathsPosix(target).map((rel) => path15.join(...rel.split("/")));
7461
+ return routeTargetPathsPosix(target).map((rel) => path18.join(...rel.split("/")));
6409
7462
  }
6410
7463
  function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6411
- const routerPath = path15.join(packageRoot, "dist", "router.json");
6412
- if (!fs15.existsSync(routerPath)) {
7464
+ const routerPath = path18.join(packageRoot, "dist", "router.json");
7465
+ if (!fs20.existsSync(routerPath)) {
6413
7466
  return {
6414
7467
  id: "router-pointers",
6415
7468
  status: "fail",
@@ -6419,7 +7472,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6419
7472
  }
6420
7473
  let router;
6421
7474
  try {
6422
- router = JSON.parse(fs15.readFileSync(routerPath, "utf8"));
7475
+ router = JSON.parse(fs20.readFileSync(routerPath, "utf8"));
6423
7476
  } catch (exc) {
6424
7477
  return {
6425
7478
  id: "router-pointers",
@@ -6428,13 +7481,13 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6428
7481
  remedy: "reinstall the package \u2014 dist/router.json is corrupt"
6429
7482
  };
6430
7483
  }
6431
- const rulesDir = path15.join(packageRoot, "dist", "agent-src", "rules");
7484
+ const rulesDir = path18.join(packageRoot, "dist", "agent-src", "rules");
6432
7485
  const broken = [];
6433
7486
  let ids = 0;
6434
7487
  let targets = 0;
6435
7488
  for (const kernelId of router["kernel"] ?? []) {
6436
7489
  ids += 1;
6437
- if (!fs15.existsSync(path15.join(rulesDir, `${kernelId}.md`))) {
7490
+ if (!fs20.existsSync(path18.join(rulesDir, `${kernelId}.md`))) {
6438
7491
  broken.push(`kernel:${kernelId} \u2192 rules/${kernelId}.md missing`);
6439
7492
  }
6440
7493
  }
@@ -6442,7 +7495,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6442
7495
  for (const entry of router[tier] ?? []) {
6443
7496
  const ruleId = String(entry["id"] ?? "");
6444
7497
  ids += 1;
6445
- if (!fs15.existsSync(path15.join(rulesDir, `${ruleId}.md`))) {
7498
+ if (!fs20.existsSync(path18.join(rulesDir, `${ruleId}.md`))) {
6446
7499
  broken.push(`${tier}:${ruleId} \u2192 rules/${ruleId}.md missing`);
6447
7500
  }
6448
7501
  for (const target of entry["routes_to"] ?? []) {
@@ -6452,7 +7505,7 @@ function _check_router_pointers(packageRoot = PACKAGE_ROOT) {
6452
7505
  broken.push(`${ruleId} \u2192 unparseable routes_to ${JSON.stringify(target)}`);
6453
7506
  continue;
6454
7507
  }
6455
- if (!candidates.some((rel) => fs15.existsSync(path15.join(packageRoot, rel)))) {
7508
+ if (!candidates.some((rel) => fs20.existsSync(path18.join(packageRoot, rel)))) {
6456
7509
  broken.push(`${ruleId} \u2192 ${target} (${candidates[0]} missing)`);
6457
7510
  }
6458
7511
  }
@@ -6484,9 +7537,9 @@ function detectSmokePlatform(projectRoot) {
6484
7537
  return "claude";
6485
7538
  }
6486
7539
  function dispatcherRunner(packageRoot) {
6487
- const localTsx = path15.join(packageRoot, "node_modules", ".bin", "tsx");
6488
- const script = path15.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
6489
- if (fs15.existsSync(localTsx)) {
7540
+ const localTsx = path18.join(packageRoot, "node_modules", ".bin", "tsx");
7541
+ const script = path18.join(packageRoot, "src", "scripts", "hooks", "dispatch_hook.ts");
7542
+ if (fs20.existsSync(localTsx)) {
6490
7543
  return [localTsx, script];
6491
7544
  }
6492
7545
  return ["npx", "tsx", script];
@@ -6536,12 +7589,12 @@ function _check_hook_dispatcher(projectRoot, packageRoot = PACKAGE_ROOT, opts =
6536
7589
  };
6537
7590
  }
6538
7591
  function readSettingsMode(projectRoot) {
6539
- const settingsPath = path15.join(projectRoot, ".agent-settings.yml");
6540
- if (!fs15.existsSync(settingsPath)) {
7592
+ const settingsPath = path18.join(projectRoot, ".agent-settings.yml");
7593
+ if (!fs20.existsSync(settingsPath)) {
6541
7594
  return "eager-all";
6542
7595
  }
6543
7596
  try {
6544
- const parsed = YAML.parse(fs15.readFileSync(settingsPath, "utf8"));
7597
+ const parsed = YAML.parse(fs20.readFileSync(settingsPath, "utf8"));
6545
7598
  const lean = parsed?.["lean_projection"] ?? null;
6546
7599
  const mode = lean?.["mode"];
6547
7600
  return typeof mode === "string" && mode.length > 0 ? mode : "eager-all";
@@ -6552,7 +7605,7 @@ function readSettingsMode(projectRoot) {
6552
7605
  function kernelIdsFromRouter(packageRoot) {
6553
7606
  try {
6554
7607
  const router = JSON.parse(
6555
- fs15.readFileSync(path15.join(packageRoot, "dist", "router.json"), "utf8")
7608
+ fs20.readFileSync(path18.join(packageRoot, "dist", "router.json"), "utf8")
6556
7609
  );
6557
7610
  return new Set((router["kernel"] ?? []).map(String));
6558
7611
  } catch {
@@ -6569,8 +7622,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
6569
7622
  remedy: "set lean_projection.mode to 'eager-all' or 'thin' in .agent-settings.yml"
6570
7623
  };
6571
7624
  }
6572
- const rulesDir = projectedRulesDir ?? path15.join(projectRoot, ".augment", "rules");
6573
- if (!fs15.existsSync(rulesDir)) {
7625
+ const rulesDir = projectedRulesDir ?? path18.join(projectRoot, ".augment", "rules");
7626
+ if (!fs20.existsSync(rulesDir)) {
6574
7627
  return {
6575
7628
  id: "lean-projection",
6576
7629
  status: "skipped",
@@ -6579,7 +7632,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
6579
7632
  };
6580
7633
  }
6581
7634
  const kernel = kernelIdsFromRouter(packageRoot);
6582
- const files = fs15.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
7635
+ const files = fs20.readdirSync(rulesDir).filter((f) => f.endsWith(".md") && f !== "INDEX.md").sort();
6583
7636
  const nonKernel = files.filter((f) => !kernel.has(f.replace(/\.md$/, "")));
6584
7637
  if (nonKernel.length === 0) {
6585
7638
  return {
@@ -6593,7 +7646,7 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
6593
7646
  for (const f of nonKernel) {
6594
7647
  let text = "";
6595
7648
  try {
6596
- text = fs15.readFileSync(path15.join(rulesDir, f), "utf8");
7649
+ text = fs20.readFileSync(path18.join(rulesDir, f), "utf8");
6597
7650
  } catch {
6598
7651
  continue;
6599
7652
  }
@@ -6624,8 +7677,8 @@ function _check_lean_projection(projectRoot, packageRoot = PACKAGE_ROOT, project
6624
7677
  function _check_host_manifest(projectRoot) {
6625
7678
  const detected = Object.entries(detectToolPresence(projectRoot)).filter(([, v]) => v === true).map(([k]) => k);
6626
7679
  const detectedNote = detected.length > 0 ? `detected surfaces: ${detected.join(", ")}` : "no surfaces detected";
6627
- const settingsPath = path15.join(projectRoot, ".agent-settings.yml");
6628
- if (!fs15.existsSync(settingsPath)) {
7680
+ const settingsPath = path18.join(projectRoot, ".agent-settings.yml");
7681
+ if (!fs20.existsSync(settingsPath)) {
6629
7682
  return {
6630
7683
  id: "host-manifest",
6631
7684
  status: "ok",
@@ -6635,7 +7688,7 @@ function _check_host_manifest(projectRoot) {
6635
7688
  }
6636
7689
  let parsed = null;
6637
7690
  try {
6638
- parsed = YAML.parse(fs15.readFileSync(settingsPath, "utf8"));
7691
+ parsed = YAML.parse(fs20.readFileSync(settingsPath, "utf8"));
6639
7692
  } catch (exc) {
6640
7693
  return {
6641
7694
  id: "host-manifest",
@@ -6809,13 +7862,13 @@ function _isCliEntry13() {
6809
7862
  if (process5.argv[1] === void 0) {
6810
7863
  return false;
6811
7864
  }
6812
- const argvUrl = pathToFileURL14(path15.resolve(process5.argv[1])).href;
7865
+ const argvUrl = pathToFileURL14(path18.resolve(process5.argv[1])).href;
6813
7866
  if (import.meta.url === argvUrl) {
6814
7867
  return true;
6815
7868
  }
6816
7869
  try {
6817
- const here = fs15.realpathSync(fileURLToPath14(import.meta.url));
6818
- const argv = fs15.realpathSync(path15.resolve(process5.argv[1]));
7870
+ const here = fs20.realpathSync(fileURLToPath14(import.meta.url));
7871
+ const argv = fs20.realpathSync(path18.resolve(process5.argv[1]));
6819
7872
  return here === argv;
6820
7873
  } catch {
6821
7874
  return false;