@event4u/agent-config 16.2.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 (332) 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 +89 -237
  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 +4 -4
  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/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  23. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  24. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
  25. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
  26. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  27. package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
  28. package/dist/agent-src/rules/active-remediation.md +1 -1
  29. package/dist/agent-src/rules/autonomous-execution.md +1 -0
  30. package/dist/agent-src/rules/broken-access-control.md +1 -0
  31. package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
  32. package/dist/agent-src/rules/context-hygiene.md +1 -1
  33. package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
  34. package/dist/agent-src/rules/design-fidelity.md +13 -16
  35. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
  36. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
  37. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  38. package/dist/agent-src/rules/domain-safety-retention.md +1 -0
  39. package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
  40. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  41. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  42. package/dist/agent-src/rules/finance-safety-floor.md +1 -0
  43. package/dist/agent-src/rules/git-history-discipline.md +1 -0
  44. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  45. package/dist/agent-src/rules/legal-safety-floor.md +3 -2
  46. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  47. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  48. package/dist/agent-src/rules/media-governance-routing.md +14 -17
  49. package/dist/agent-src/rules/persona-governance.md +2 -2
  50. package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
  51. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  52. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  53. package/dist/agent-src/rules/runtime-safety.md +1 -0
  54. package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
  55. package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
  56. package/dist/agent-src/rules/session-canary.md +8 -62
  57. package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
  58. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  59. package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
  60. package/dist/agent-src/rules/tool-safety.md +1 -0
  61. package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
  62. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  63. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  64. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  65. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  66. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  67. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  68. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  69. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  70. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +2 -2
  71. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  72. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  73. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  74. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  75. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  76. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  77. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  78. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  79. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  80. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  81. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  82. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  83. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  84. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  85. package/dist/agent-src/skills/ui-component-architect/SKILL.md +2 -2
  86. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  87. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
  88. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  89. package/dist/cli/registry.js +1 -1
  90. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  91. package/dist/cli-delegate/check_update_banner.js +12 -1
  92. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  93. package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
  94. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  95. package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
  96. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  97. package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
  98. package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
  99. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  100. package/dist/cli-delegate/{chunk-IGD6ERX6.js → chunk-JQVYKIZA.js} +10 -0
  101. package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
  102. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  103. package/dist/cli-delegate/{chunk-7RQP4UDH.js → chunk-KR6EM23A.js} +235 -1455
  104. package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
  105. package/dist/cli-delegate/{chunk-YJ72UIAK.js → chunk-NQ7WT43P.js} +1186 -244
  106. package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
  107. package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
  108. package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
  109. package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
  110. package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
  111. package/dist/cli-delegate/{chunk-7XGBX6ZH.js → chunk-RQNNW7IF.js} +1314 -560
  112. package/dist/cli-delegate/{chunk-4XFP2Y3E.js → chunk-S4UMZNES.js} +1 -1
  113. package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
  114. package/dist/cli-delegate/{chunk-FP36SWJD.js → chunk-TRI6DQZ6.js} +2781 -340
  115. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  116. package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
  117. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  118. package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
  119. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  120. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  121. package/dist/cli-delegate/cmd_conformance.js +1167 -234
  122. package/dist/cli-delegate/cmd_doctor.js +13 -6
  123. package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
  124. package/dist/cli-delegate/cmd_explain.js +5 -5
  125. package/dist/cli-delegate/cmd_export.js +11 -8
  126. package/dist/cli-delegate/cmd_fleet.js +3 -3
  127. package/dist/cli-delegate/cmd_handoff.js +10 -7
  128. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  129. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  130. package/dist/cli-delegate/cmd_migrate.js +2 -2
  131. package/dist/cli-delegate/cmd_packs_active.js +3 -3
  132. package/dist/cli-delegate/cmd_preflight.js +6 -6
  133. package/dist/cli-delegate/cmd_prune.js +11 -8
  134. package/dist/cli-delegate/cmd_refresh.js +2 -1
  135. package/dist/cli-delegate/cmd_route_audit.js +8 -6
  136. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  137. package/dist/cli-delegate/cmd_session_recycle.js +4 -3
  138. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  139. package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
  140. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  141. package/dist/cli-delegate/cmd_sync.js +11 -8
  142. package/dist/cli-delegate/cmd_uninstall.js +11 -8
  143. package/dist/cli-delegate/cmd_update.js +1 -1
  144. package/dist/cli-delegate/cmd_upgrade.js +3 -2
  145. package/dist/cli-delegate/cmd_validate.js +11 -8
  146. package/dist/cli-delegate/cmd_versions.js +1 -1
  147. package/dist/cli-delegate/deferral_carry.js +16 -0
  148. package/dist/cli-delegate/gate_execute.js +2 -2
  149. package/dist/cli-delegate/roadmap_gates.js +5 -5
  150. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  151. package/dist/discovery/deprecation-report.md +1 -1
  152. package/dist/discovery/discovery-manifest.json +127 -75
  153. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  154. package/dist/discovery/discovery-manifest.summary.md +3 -3
  155. package/dist/discovery/dormancy-report.md +1 -1
  156. package/dist/discovery/orphan-report.md +1 -1
  157. package/dist/discovery/packs.json +7 -5
  158. package/dist/discovery/trust-report.md +2 -2
  159. package/dist/discovery/workspaces.json +6 -4
  160. package/dist/hooks/dispatch.js +5357 -6656
  161. package/dist/hooks/dispatch.sha256 +1 -1
  162. package/dist/install/claudePathsPlan.js +47 -6
  163. package/dist/install/claudeRuleRewrite.js +7 -0
  164. package/dist/install/emit_host_rules_cli.js +39 -36
  165. package/dist/install/globalRuleScope.js +87 -0
  166. package/dist/install/host_rule_ownership.js +160 -0
  167. package/dist/install/install.mjs +2080 -1024
  168. package/dist/install/installThinLayer.js +261 -0
  169. package/dist/install/installedRuleLinks.js +176 -0
  170. package/dist/install/wizard-plan.js +24 -0
  171. package/dist/mcp/registry-manifest.json +3 -3
  172. package/dist/mcp/server.json +2 -2
  173. package/dist/mcp/server.mjs +31965 -27154
  174. package/dist/scripts/_lib/agent_settings.js +11 -0
  175. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  176. package/dist/scripts/_lib/installed_layer.js +378 -0
  177. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  178. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  179. package/dist/scripts/_lib/rule_injection.js +67 -10
  180. package/dist/scripts/_lib/rule_law_section.js +122 -0
  181. package/dist/scripts/_lib/thin_rules.js +458 -0
  182. package/dist/scripts/_lib/windsurf_render.js +60 -0
  183. package/dist/scripts/condense.js +14 -26
  184. package/dist/scripts/project_thin_rules.js +14 -294
  185. package/dist/server/schemas/settings.js +6 -0
  186. package/dist/shared/skillRanking.js +74 -13
  187. package/docs/contracts/settings-classes.md +7 -5
  188. package/package.json +1 -1
  189. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  190. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  191. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  192. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
  193. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  194. package/src/config/agent-settings.template.yml +62 -22
  195. package/src/config/continuity-surface.json +7 -0
  196. package/src/config/evaluator-budgets.json +4 -4
  197. package/src/config/gate-coverage.yml +96 -3
  198. package/src/config/gate-violation-baselines.json +17 -9
  199. package/src/config/hook-bundle-budget.json +25 -0
  200. package/src/config/hook-token-budget.json +6 -1
  201. package/src/config/host-instruction-limits.json +33 -0
  202. package/src/config/routing-coverage-seed.json +24 -3
  203. package/src/config/rule-activation-census.json +7 -3
  204. package/src/config/rule-consequence-class.json +161 -0
  205. package/src/config/rule-law-ceilings.json +99 -0
  206. package/src/install/claudePathsPlan.ts +46 -4
  207. package/src/install/claudeRuleRewrite.ts +10 -1
  208. package/src/install/emit_host_rules_cli.ts +43 -34
  209. package/src/install/globalRuleScope.ts +109 -0
  210. package/src/install/host_rule_ownership.ts +170 -0
  211. package/src/install/installThinLayer.ts +312 -0
  212. package/src/install/installedRuleLinks.ts +220 -0
  213. package/src/install/wizard-plan.ts +24 -0
  214. package/src/scripts/_cli/cmd_doctor.ts +23 -71
  215. package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
  216. package/src/scripts/_dispatch.bash +16 -2
  217. package/src/scripts/_lib/agent_settings.ts +11 -0
  218. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  219. package/src/scripts/_lib/estate_offsets.ts +241 -0
  220. package/src/scripts/_lib/exemption_shape.ts +143 -0
  221. package/src/scripts/_lib/fnv.ts +23 -0
  222. package/src/scripts/_lib/foreign_scan.ts +106 -0
  223. package/src/scripts/_lib/gitignore_block.ts +314 -0
  224. package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
  225. package/src/scripts/_lib/graph_feeder_record.ts +214 -0
  226. package/src/scripts/_lib/hook_settings.ts +73 -0
  227. package/src/scripts/_lib/host_capability.ts +40 -19
  228. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  229. package/src/scripts/_lib/installed_layer.ts +538 -0
  230. package/src/scripts/_lib/json_merge.ts +82 -0
  231. package/src/scripts/_lib/json_pointers.ts +93 -11
  232. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  233. package/src/scripts/_lib/machine_wake.ts +16 -3
  234. package/src/scripts/_lib/neighbour_census.ts +760 -0
  235. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  236. package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
  237. package/src/scripts/_lib/prompt_shape.ts +46 -0
  238. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  239. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  240. package/src/scripts/_lib/review_coverage.ts +134 -0
  241. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  242. package/src/scripts/_lib/rule_injection.ts +130 -10
  243. package/src/scripts/_lib/rule_law_section.ts +147 -0
  244. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  245. package/src/scripts/_lib/skill_origin.ts +254 -0
  246. package/src/scripts/_lib/structured_ask.ts +64 -21
  247. package/src/scripts/_lib/thin_rules.ts +540 -0
  248. package/src/scripts/_lib/touched_file_quality.ts +434 -0
  249. package/src/scripts/_lib/turn_end_refusals.ts +77 -2
  250. package/src/scripts/_lib/windsurf_render.ts +63 -0
  251. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  252. package/src/scripts/ai_council/clients.ts +26 -57
  253. package/src/scripts/ai_council/config.ts +1 -1
  254. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  255. package/src/scripts/audit_skill_overlap.ts +261 -0
  256. package/src/scripts/before_complete_hook.ts +243 -12
  257. package/src/scripts/bench_touched_file_quality.ts +238 -0
  258. package/src/scripts/check_enforcement_matrix.ts +377 -20
  259. package/src/scripts/check_estate_count.ts +130 -153
  260. package/src/scripts/check_finding_dispositions.ts +116 -4
  261. package/src/scripts/check_hook_bundle_composition.ts +370 -0
  262. package/src/scripts/check_kernel_edit_ratified.ts +49 -11
  263. package/src/scripts/check_memory.ts +7 -1
  264. package/src/scripts/check_release_highlights.ts +10 -0
  265. package/src/scripts/check_release_pr_shape.ts +38 -15
  266. package/src/scripts/check_standing_rule_delivery.ts +33 -13
  267. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  268. package/src/scripts/code_graph/detect.ts +127 -7
  269. package/src/scripts/code_graph/foreign.ts +194 -0
  270. package/src/scripts/code_graph/query.ts +217 -4
  271. package/src/scripts/code_graph/types.ts +26 -2
  272. package/src/scripts/code_graph/validate.ts +3 -1
  273. package/src/scripts/code_graph/verbs.ts +103 -0
  274. package/src/scripts/compile_hook_manifest.ts +33 -1
  275. package/src/scripts/condense.ts +14 -27
  276. package/src/scripts/estate_exemption_shape.ts +202 -0
  277. package/src/scripts/generate_host_cost_table.ts +1 -1
  278. package/src/scripts/hook_manifest.json +1 -1
  279. package/src/scripts/hook_manifest.yaml +150 -9
  280. package/src/scripts/hooks/block_config_weakening.ts +67 -2
  281. package/src/scripts/hooks/block_plumbing_writes.ts +29 -1
  282. package/src/scripts/hooks/code_graph_context_hook.ts +252 -23
  283. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  284. package/src/scripts/hooks/dispatch_hook.ts +52 -52
  285. package/src/scripts/hooks/exit_codes.ts +2 -1
  286. package/src/scripts/hooks/host_lowering.json +1 -1
  287. package/src/scripts/hooks/host_lowering.ts +19 -1
  288. package/src/scripts/hooks/one_question_per_ask_hook.ts +41 -25
  289. package/src/scripts/hooks/payload_stub.ts +10 -1
  290. package/src/scripts/hooks/rule_inject_hook.ts +757 -63
  291. package/src/scripts/hooks/table_fingerprint.ts +21 -0
  292. package/src/scripts/hooks/telemetry_usage_hook.ts +64 -8
  293. package/src/scripts/hooks/turn_end_gate_hook.ts +84 -0
  294. package/src/scripts/hooks_status.ts +112 -0
  295. package/src/scripts/injection_scan_hook.ts +7 -0
  296. package/src/scripts/install.ts +72 -185
  297. package/src/scripts/installed_layer_report.ts +103 -0
  298. package/src/scripts/language_mirror_hook.ts +15 -6
  299. package/src/scripts/learning_sidecar.ts +119 -8
  300. package/src/scripts/lint_hook_manifest.ts +53 -0
  301. package/src/scripts/lint_major_migration_sections.ts +156 -26
  302. package/src/scripts/lint_pack_boundaries.ts +63 -5
  303. package/src/scripts/lint_rule_law_section.ts +517 -0
  304. package/src/scripts/lint_workflow_security.ts +161 -5
  305. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  306. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  307. package/src/scripts/measure_routing_signal.ts +54 -16
  308. package/src/scripts/measure_skill_ranker_baseline.ts +216 -8
  309. package/src/scripts/memory_learn_hook.ts +91 -6
  310. package/src/scripts/model_rule_injection.ts +52 -5
  311. package/src/scripts/project_thin_rules.ts +34 -327
  312. package/src/scripts/report_evidence_temperature.ts +657 -0
  313. package/src/scripts/report_installed_rule_links.ts +157 -0
  314. package/src/scripts/report_label_agreement.ts +233 -0
  315. package/src/scripts/report_obligation_mechanism.ts +230 -0
  316. package/src/scripts/report_single_token_triggers.ts +277 -0
  317. package/src/scripts/report_skill_ranker_confusion.ts +442 -0
  318. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  319. package/src/scripts/report_verification_record_coverage.ts +306 -0
  320. package/src/scripts/rule_activation_census.ts +5 -2
  321. package/src/scripts/rule_trigger_eval.ts +9 -9
  322. package/src/scripts/schemas/review-findings.schema.json +3 -2
  323. package/src/scripts/schemas/rule.schema.json +5 -0
  324. package/src/scripts/self_review_gate.ts +62 -7
  325. package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
  326. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  327. package/src/scripts/sync_gitignore.ts +29 -235
  328. package/src/scripts/trigger_eval_grandfather.json +0 -5
  329. package/src/server/schemas/settings.ts +10 -0
  330. package/src/shared/skillRanking.ts +117 -11
  331. package/dist/cli-delegate/chunk-JNMVLWLR.js +0 -4457
  332. package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
@@ -13,209 +13,9 @@ import {
13
13
  import {
14
14
  read_lockfile
15
15
  } from "./chunk-T7L2QK3K.js";
16
-
17
- // src/scripts/_lib/secret_detector.ts
18
- var ENTROPY_MIN_TOKEN_LENGTH = 20;
19
- var BASE64_ENTROPY_THRESHOLD = 4.5;
20
- var HEX_ENTROPY_THRESHOLD = 3;
21
- var RULES = [
22
- { rule: "anthropic-api-key", kind: "anthropic-api-key", regex: /sk-ant-[A-Za-z0-9_-]{20,}/g },
23
- { rule: "anthropic-project-key", kind: "anthropic-api-key", regex: /sk-proj-[A-Za-z0-9_-]{20,}/g },
24
- { rule: "aws-access-key", kind: "aws-access-key", regex: /AKIA[0-9A-Z]{16}/g },
25
- { rule: "google-api-key", kind: "google-api-key", regex: /AIza[0-9A-Za-z_-]{35}/g },
26
- { rule: "github-pat", kind: "github-pat", regex: /ghp_[0-9A-Za-z]{36}/g },
27
- // Fine-grained PATs (github_pat_…) were a measured detector gap: the
28
- // 2026-07-28 adversarial corpus pass flagged them as missed positives.
29
- { rule: "github-fine-grained-pat", kind: "github-pat", regex: /github_pat_[0-9A-Za-z_]{22,}/g },
30
- {
31
- rule: "jwt",
32
- kind: "jwt",
33
- regex: /eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g
34
- },
35
- // Extensions.
36
- { rule: "stripe-secret-key", kind: "stripe-secret-key", regex: /sk_(?:live|test)_[0-9A-Za-z]{16,}/g },
37
- { rule: "slack-token", kind: "slack-token", regex: /xox[baprs]-[0-9A-Za-z-]{10,}/g },
38
- { rule: "pem-private-key", kind: "pem-private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----/g },
39
- {
40
- rule: "db-connection-uri",
41
- kind: "db-connection-uri",
42
- regex: /\b[a-z][a-z0-9+.-]*:\/\/[^\s:/@]+:[^\s:/@]+@/g
43
- },
44
- {
45
- rule: "generic-assignment",
46
- kind: "generic-assignment",
47
- regex: /(?:password|passwd|pwd|secret|api[_-]?key|access[_-]?token|auth[_-]?token)\s*[:=]\s*['"]?([^'"\s]{8,})['"]?/gi,
48
- valueGroup: 1
49
- }
50
- ];
51
- var SECRET_ALLOW = /secret-allow/;
52
- var PLACEHOLDER_PATTERNS = [
53
- /x{4,}/i,
54
- /example/i,
55
- /changeme/i,
56
- /your[_-].*here/i,
57
- /<[^>]+>/,
58
- /\.\.\./,
59
- /^your[_-]/i,
60
- /placeholder/i
61
- ];
62
- var DATA_URI = /data:[\w.+-]+\/[\w.+-]+;base64,/i;
63
- var UUID = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
64
- var KEYWORD_CONTEXT = /password|secret|token|api[_-]?key|credential/i;
65
- var ENTROPY_TOKEN = /[A-Za-z0-9+=_-]{20,}/g;
66
- var IS_HEX = /^[0-9a-fA-F]+$/;
67
- var HASH_HEX_LENGTHS = /* @__PURE__ */ new Set([32, 40, 64]);
68
- function isAllSameChar(value) {
69
- return value.length > 0 && /^(.)\1*$/.test(value);
70
- }
71
- var GENERIC_MIN_LENGTH = 16;
72
- var GENERIC_MIN_ENTROPY = 3.3;
73
- var CODE_EXPR = /[()$`{}<>[\];,]|->|=>|\.\w/;
74
- function looksLikeSecretValue(value) {
75
- if (value.length < GENERIC_MIN_LENGTH) {
76
- return false;
77
- }
78
- if (CODE_EXPR.test(value)) {
79
- return false;
80
- }
81
- const hasDigit = /[0-9]/.test(value);
82
- const mixedCase = /[a-z]/.test(value) && /[A-Z]/.test(value);
83
- if (!hasDigit && !mixedCase) {
84
- return false;
85
- }
86
- return shannonEntropy(value) >= GENERIC_MIN_ENTROPY;
87
- }
88
- function isPlaceholderValue(value, pathHint) {
89
- if (pathHint !== void 0) {
90
- if (pathHint.endsWith(".example") || pathHint.endsWith(".sample") || pathHint.includes(".env.example")) {
91
- return true;
92
- }
93
- }
94
- if (isAllSameChar(value)) {
95
- return true;
96
- }
97
- return PLACEHOLDER_PATTERNS.some((p) => p.test(value));
98
- }
99
- function maskSecret(value) {
100
- const front = value.slice(0, 4);
101
- const back = value.length > 6 ? value.slice(-2) : "";
102
- if (front.length + back.length >= value.length) {
103
- return `${value.slice(0, 1)}\u2026`;
104
- }
105
- return `${front}\u2026${back}`;
106
- }
107
- function shannonEntropy(value) {
108
- const freq = /* @__PURE__ */ new Map();
109
- for (const ch of value) {
110
- freq.set(ch, (freq.get(ch) ?? 0) + 1);
111
- }
112
- const n = value.length;
113
- let entropy = 0;
114
- for (const count of freq.values()) {
115
- const p = count / n;
116
- entropy -= p * Math.log2(p);
117
- }
118
- return entropy;
119
- }
120
- function overlaps(spans, start, end) {
121
- return spans.some(([s, e]) => start < e && s < end);
122
- }
123
- function scanText(text, opts = {}) {
124
- const pathHint = opts.path;
125
- const findings = [];
126
- const lines = text.split(/\r\n|\r|\n/);
127
- for (let i = 0; i < lines.length; i++) {
128
- const line = lines[i];
129
- const lineNo = i + 1;
130
- if (SECRET_ALLOW.test(line)) {
131
- continue;
132
- }
133
- const covered = [];
134
- for (const rule of RULES) {
135
- rule.regex.lastIndex = 0;
136
- let m;
137
- while ((m = rule.regex.exec(line)) !== null) {
138
- const value = rule.valueGroup !== void 0 ? m[rule.valueGroup] : m[0];
139
- if (value === void 0) {
140
- if (m.index === rule.regex.lastIndex) {
141
- rule.regex.lastIndex += 1;
142
- }
143
- continue;
144
- }
145
- if (rule.rule === "generic-assignment" && !looksLikeSecretValue(value)) {
146
- if (m.index === rule.regex.lastIndex) {
147
- rule.regex.lastIndex += 1;
148
- }
149
- continue;
150
- }
151
- const valueStart = m.index + m[0].indexOf(value);
152
- const confidence = isPlaceholderValue(value, pathHint) ? "low" : "high";
153
- findings.push({
154
- rule: rule.rule,
155
- kind: rule.kind,
156
- line: lineNo,
157
- column: valueStart + 1,
158
- masked: maskSecret(value),
159
- confidence
160
- });
161
- covered.push([valueStart, valueStart + value.length]);
162
- if (m.index === rule.regex.lastIndex) {
163
- rule.regex.lastIndex += 1;
164
- }
165
- }
166
- }
167
- if (!DATA_URI.test(line)) {
168
- const hasKeyword = KEYWORD_CONTEXT.test(line);
169
- ENTROPY_TOKEN.lastIndex = 0;
170
- let tm;
171
- while ((tm = ENTROPY_TOKEN.exec(line)) !== null) {
172
- const token = tm[0];
173
- const start = tm.index;
174
- const end = start + token.length;
175
- if (token.length < ENTROPY_MIN_TOKEN_LENGTH) {
176
- continue;
177
- }
178
- if (overlaps(covered, start, end)) {
179
- continue;
180
- }
181
- if (UUID.test(token)) {
182
- continue;
183
- }
184
- if (/^sha(?:256|384|512)-/.test(token)) {
185
- continue;
186
- }
187
- const entropy = shannonEntropy(token);
188
- const isHex = IS_HEX.test(token);
189
- if (isHex && HASH_HEX_LENGTHS.has(token.length)) {
190
- continue;
191
- }
192
- let confidence = null;
193
- if (isHex) {
194
- if (entropy >= HEX_ENTROPY_THRESHOLD && hasKeyword) {
195
- confidence = "high";
196
- }
197
- } else if (entropy >= BASE64_ENTROPY_THRESHOLD) {
198
- confidence = hasKeyword ? "high" : "medium";
199
- }
200
- if (confidence === null) {
201
- continue;
202
- }
203
- if (isPlaceholderValue(token, pathHint)) {
204
- confidence = "low";
205
- }
206
- findings.push({
207
- rule: "entropy",
208
- kind: "high-entropy-string",
209
- line: lineNo,
210
- column: start + 1,
211
- masked: maskSecret(token),
212
- confidence
213
- });
214
- }
215
- }
216
- }
217
- return findings;
218
- }
16
+ import {
17
+ scanText
18
+ } from "./chunk-KB5FZ655.js";
219
19
 
220
20
  // src/scripts/_lib/subagent_capsule.ts
221
21
  var EPISTEMIC_STATES = ["verified", "assumed", "gap"];
@@ -561,10 +361,57 @@ function validateAssumption(input, label = "assumption") {
561
361
  }
562
362
 
563
363
  // src/scripts/before_complete_hook.ts
564
- import fs from "node:fs";
565
- import path from "node:path";
364
+ import fs4 from "node:fs";
365
+ import path4 from "node:path";
566
366
  import { fileURLToPath, pathToFileURL } from "node:url";
567
367
 
368
+ // src/scripts/_lib/hook_settings.ts
369
+ import * as fs from "node:fs";
370
+ import * as path from "node:path";
371
+ var SETTINGS_FILE = ".agent-settings.yml";
372
+ function hookSectionValue(root, section, key) {
373
+ const file = path.join(root, SETTINGS_FILE);
374
+ let text;
375
+ try {
376
+ if (!fs.statSync(file).isFile()) return "";
377
+ text = fs.readFileSync(file, "utf-8");
378
+ } catch {
379
+ return "";
380
+ }
381
+ const esc = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
382
+ const sectionPattern = new RegExp(`^(\\s+)${esc(section)}\\s*:\\s*$`);
383
+ const keyPattern = new RegExp(`^\\s+${esc(key)}\\s*:\\s*(.+)$`);
384
+ let inHooks = false;
385
+ let sectionIndent = null;
386
+ const indentOf = (line) => line.length - line.replace(/^\s+/, "").length;
387
+ for (const raw of text.split(/\r\n|\r|\n/)) {
388
+ const line = raw.replace(/\s+$/, "");
389
+ if (!line || line.replace(/^\s+/, "").startsWith("#")) continue;
390
+ if (!(line.startsWith(" ") || line.startsWith(" "))) {
391
+ inHooks = /^hooks\s*:\s*$/.test(line);
392
+ sectionIndent = null;
393
+ continue;
394
+ }
395
+ if (inHooks) {
396
+ const opened = sectionPattern.exec(line);
397
+ if (opened) {
398
+ sectionIndent = opened[1].length;
399
+ continue;
400
+ }
401
+ if (sectionIndent !== null && indentOf(line) <= sectionIndent) {
402
+ sectionIndent = null;
403
+ }
404
+ }
405
+ if (sectionIndent !== null) {
406
+ const m = keyPattern.exec(line);
407
+ if (m) {
408
+ return (m[1] ?? "").replace(/\s+#.*$/, "").trim().replace(/^["']|["']$/g, "");
409
+ }
410
+ }
411
+ }
412
+ return "";
413
+ }
414
+
568
415
  // src/scripts/_lib/verification_command.ts
569
416
  var VERIFY_BINARIES = /* @__PURE__ */ new Set([
570
417
  "vitest",
@@ -733,6 +580,187 @@ function mightBeVerification(command) {
733
580
  return _WIDE_CANDIDATE_RE.test(command);
734
581
  }
735
582
 
583
+ // src/scripts/_lib/touched_file_quality.ts
584
+ import { spawnSync } from "node:child_process";
585
+ import * as fs2 from "node:fs";
586
+ import * as path2 from "node:path";
587
+ var OUTPUT_HEAD_LINES = 20;
588
+ var DEFAULT_TIMEOUT_MS = 3e4;
589
+ var JS_EXTENSIONS = [".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"];
590
+ var SCOPED_FORMS = /* @__PURE__ */ new Map([
591
+ ["npx eslint .", { argv: ["npx", "eslint"], extensions: JS_EXTENSIONS, check_form: false }],
592
+ [
593
+ "vendor/bin/phpstan analyse",
594
+ { argv: ["vendor/bin/phpstan", "analyse"], extensions: [".php"], check_form: false }
595
+ ],
596
+ // `vendor/bin/pint` REWRITES source. `--test` is its check form: same tool,
597
+ // same rules, reports instead of writing. Risk 3 of the roadmap is that this
598
+ // module reformats the consumer's tree; this row plus the fixture that proves
599
+ // no file changed is the mitigation.
600
+ [
601
+ "vendor/bin/pint",
602
+ { argv: ["vendor/bin/pint", "--test"], extensions: [".php"], check_form: true }
603
+ ],
604
+ ["ruff check", { argv: ["ruff", "check"], extensions: [".py"], check_form: false }],
605
+ ["mypy .", { argv: ["mypy"], extensions: [".py"], check_form: false }]
606
+ ]);
607
+ var MUTATING_WITHOUT_CHECK_FORM = /* @__PURE__ */ new Set([]);
608
+ function extensionOf(file) {
609
+ const base = path2.basename(file);
610
+ const dot = base.lastIndexOf(".");
611
+ return dot <= 0 ? "" : base.slice(dot).toLowerCase();
612
+ }
613
+ function planQualityRuns(commands, files) {
614
+ return commands.map((source_command) => {
615
+ if (MUTATING_WITHOUT_CHECK_FORM.has(source_command)) {
616
+ return { source_command, argv: null, files: [], skipped: "mutating" };
617
+ }
618
+ const spec = SCOPED_FORMS.get(source_command);
619
+ if (spec === void 0) {
620
+ return { source_command, argv: null, files: [], skipped: "unscoped" };
621
+ }
622
+ const matched = spec.extensions.length === 0 ? [...files] : files.filter((f) => spec.extensions.includes(extensionOf(f)));
623
+ if (matched.length === 0) {
624
+ return { source_command, argv: null, files: [], skipped: "no_files" };
625
+ }
626
+ return { source_command, argv: [...spec.argv, ...matched], files: matched, skipped: null };
627
+ });
628
+ }
629
+ function renderCommand(argv) {
630
+ return argv.join(" ");
631
+ }
632
+ function headLines(text, limit) {
633
+ if (text === "") return [];
634
+ return text.split(/\r\n|\r|\n/).filter((line, i, all) => !(line === "" && i === all.length - 1)).slice(0, limit);
635
+ }
636
+ function now() {
637
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/\.\d{3}Z$/, "+00:00");
638
+ }
639
+ function defaultSpawn(argv, cwd, timeout_ms) {
640
+ const [head, ...rest] = argv;
641
+ if (head === void 0) return { status: null, output: "", enoent: true };
642
+ const res = spawnSync(head, rest, {
643
+ cwd,
644
+ timeout: timeout_ms,
645
+ encoding: "utf-8",
646
+ // No shell. The argv comes from a committed table, and a shell would
647
+ // make a file path with a space into an injection surface.
648
+ shell: false,
649
+ maxBuffer: 4 * 1024 * 1024
650
+ });
651
+ const enoent = res.error !== void 0 && res.error.code === "ENOENT";
652
+ const output2 = `${res.stdout ?? ""}${res.stderr ?? ""}`;
653
+ return { status: res.status, output: output2, enoent };
654
+ }
655
+ function runTouchedFileQuality(opts) {
656
+ const timeout_ms = opts.timeout_ms ?? DEFAULT_TIMEOUT_MS;
657
+ const spawn = opts.spawn ?? defaultSpawn;
658
+ const out = [];
659
+ for (const plan of planQualityRuns(opts.commands, opts.files)) {
660
+ if (plan.argv === null) {
661
+ out.push({
662
+ command: plan.source_command,
663
+ source_command: plan.source_command,
664
+ exit_code: null,
665
+ output_head: [],
666
+ skipped: plan.skipped,
667
+ files: [],
668
+ at: now()
669
+ });
670
+ continue;
671
+ }
672
+ let status = null;
673
+ let output2 = "";
674
+ let enoent = false;
675
+ try {
676
+ const res = spawn(plan.argv, opts.root, timeout_ms);
677
+ status = res.status;
678
+ output2 = res.output;
679
+ enoent = res.enoent;
680
+ } catch {
681
+ enoent = true;
682
+ }
683
+ const absent = enoent || status === 127;
684
+ out.push({
685
+ command: renderCommand(plan.argv),
686
+ source_command: plan.source_command,
687
+ exit_code: absent ? null : status,
688
+ output_head: absent ? [] : headLines(output2, OUTPUT_HEAD_LINES),
689
+ skipped: absent ? "absent" : null,
690
+ files: plan.files,
691
+ at: now()
692
+ });
693
+ }
694
+ return out;
695
+ }
696
+ var WARN_LINE_MAX_BYTES = 200;
697
+ function advisoryLine(runs) {
698
+ const failed = runs.find((r) => r.skipped === null && r.exit_code !== null && r.exit_code !== 0);
699
+ if (failed === void 0) return null;
700
+ const file = failed.files[0] ?? "(no file)";
701
+ const more = failed.files.length > 1 ? ` (+${String(failed.files.length - 1)} more)` : "";
702
+ const line = `touched-file quality: ${failed.source_command} exited ${String(failed.exit_code)} on ${file}${more}`;
703
+ return truncateToBytes(line, WARN_LINE_MAX_BYTES);
704
+ }
705
+ function truncateToBytes(text, max) {
706
+ const buf = Buffer.from(text, "utf-8");
707
+ if (buf.length <= max) return text;
708
+ let end = max;
709
+ while (end > 0 && (buf[end] ?? 0) >= 128 && (buf[end] ?? 0) < 192) end -= 1;
710
+ return buf.subarray(0, end).toString("utf-8");
711
+ }
712
+ function touchedFilesAtStop(root, session_id, gitStatus = defaultGitStatus) {
713
+ let recorded;
714
+ try {
715
+ const raw = fs2.readFileSync(
716
+ path2.join(root, "agents", "state", "minimal-safe-diff.json"),
717
+ "utf-8"
718
+ );
719
+ const decoded = JSON.parse(raw);
720
+ if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) {
721
+ return { files: [], source: "none" };
722
+ }
723
+ const state = decoded;
724
+ if (state["session_id"] !== session_id || session_id === "") {
725
+ return { files: [], source: "none" };
726
+ }
727
+ const touched = state["files_touched_this_turn"];
728
+ if (!Array.isArray(touched)) return { files: [], source: "none" };
729
+ recorded = touched.filter((x) => typeof x === "string");
730
+ } catch {
731
+ return { files: [], source: "none" };
732
+ }
733
+ const changed = new Set(gitStatus(root));
734
+ const files = recorded.filter((f) => changed.has(normalizePath(f)));
735
+ return { files, source: "minimal-safe-diff\u2229worktree" };
736
+ }
737
+ function normalizePath(p) {
738
+ return p.replace(/\\/g, "/").replace(/^\.\//, "");
739
+ }
740
+ function defaultGitStatus(root) {
741
+ const res = spawnSync("git", ["status", "--porcelain", "-z"], {
742
+ cwd: root,
743
+ encoding: "utf-8",
744
+ timeout: 1e4,
745
+ shell: false,
746
+ maxBuffer: 4 * 1024 * 1024
747
+ });
748
+ if (res.status !== 0 || typeof res.stdout !== "string") return [];
749
+ return parsePorcelainZ(res.stdout);
750
+ }
751
+ function parsePorcelainZ(stdout) {
752
+ const out = [];
753
+ const parts = stdout.split("\0");
754
+ for (let i = 0; i < parts.length; i += 1) {
755
+ const entry = parts[i];
756
+ if (entry === void 0 || entry.length < 4) continue;
757
+ const status = entry.slice(0, 2);
758
+ out.push(normalizePath(entry.slice(3)));
759
+ if (status.startsWith("R") || status.startsWith("C")) i += 1;
760
+ }
761
+ return out;
762
+ }
763
+
736
764
  // src/scripts/_lib/verification_evidence.ts
737
765
  var INSTRUMENT_GAP_REASONS = /* @__PURE__ */ new Set([
738
766
  "exit_code_unavailable",
@@ -1029,9 +1057,814 @@ function describeRecordFinding(failed, reasons) {
1029
1057
  }
1030
1058
  var NO_RED_EVIDENCE_REASON = "a completion claim over production code this turn changed, with a test file touched but `no_red_evidence` \u2014 no recorded run shows that test target failing and then passing after the last edit. A test never seen red has unknown sensitivity: run it against the unfixed code, watch it fail for the intended reason, then fix and re-run";
1031
1059
 
1060
+ // src/agent-src/templates/scripts/work_engine/stack/runner.ts
1061
+ import * as fs3 from "node:fs";
1062
+ import * as path3 from "node:path";
1063
+ var BEHAVIOR_UNKNOWN = "unknown";
1064
+ var SPEED_FAST = "fast";
1065
+ var SPEED_SLOW = "slow";
1066
+ var SPEED_E2E = "e2e";
1067
+ var HIGH = "HIGH";
1068
+ var MEDIUM = "MEDIUM";
1069
+ var LOW = "LOW";
1070
+ var _MANIFESTS = [
1071
+ "composer.json",
1072
+ "package.json",
1073
+ "pyproject.toml",
1074
+ "go.mod",
1075
+ "Cargo.toml",
1076
+ "Makefile",
1077
+ "Taskfile.yml",
1078
+ "Taskfile.yaml",
1079
+ "Gemfile",
1080
+ "pom.xml",
1081
+ "build.gradle",
1082
+ "build.gradle.kts",
1083
+ // `settings.gradle[.kts]` is a Gradle signal `_jvm_build` acts on, so it
1084
+ // must be in the key: a settings-only multi-project root emits a junit row
1085
+ // and would otherwise report the greenfield `mtime: 0` forever.
1086
+ "settings.gradle",
1087
+ "settings.gradle.kts",
1088
+ "global.json",
1089
+ "Directory.Build.props"
1090
+ ];
1091
+ var _DOTNET_PROJECT_EXTS = [".sln", ".slnx", ".csproj", ".fsproj", ".vbproj"];
1092
+ var _DOTNET_TEST_STACK = /Microsoft\.NET\.Test\.Sdk|xunit|nunit|mstest/i;
1093
+ var _DOTNET_SCAN_DEPTH = 2;
1094
+ var _DOTNET_MAX_PROJECTS = 50;
1095
+ var _DOTNET_SOLUTION_EXTS = [".sln", ".slnx"];
1096
+ function _has_dotnet_solution(dir) {
1097
+ try {
1098
+ return fs3.readdirSync(dir, { withFileTypes: true }).some((e) => e.isFile() && _DOTNET_SOLUTION_EXTS.includes(path3.extname(e.name).toLowerCase()));
1099
+ } catch {
1100
+ return false;
1101
+ }
1102
+ }
1103
+ var _BEHAVIOR_MARKERS = [
1104
+ "pnpm-workspace.yaml",
1105
+ "requirements.txt",
1106
+ "gems.rb",
1107
+ ".rspec",
1108
+ "behat.yml",
1109
+ "behat.yaml",
1110
+ "behat.yml.dist",
1111
+ "behat.dist.yml",
1112
+ "cucumber.js",
1113
+ "cucumber.cjs",
1114
+ "cucumber.mjs",
1115
+ "cucumber.json"
1116
+ ];
1117
+ var RunnerResult = class {
1118
+ ecosystem;
1119
+ runner;
1120
+ command;
1121
+ speed;
1122
+ confidence;
1123
+ basis;
1124
+ constructor(ecosystem, runner, command, speed = SPEED_FAST, confidence = HIGH, basis = "") {
1125
+ this.ecosystem = ecosystem;
1126
+ this.runner = runner;
1127
+ this.command = command;
1128
+ this.speed = speed;
1129
+ this.confidence = confidence;
1130
+ this.basis = basis;
1131
+ }
1132
+ };
1133
+ var BehaviorRunnerResult = class {
1134
+ constructor(ecosystem, runner, command, scope_root, confidence = HIGH, basis = "", conflict = []) {
1135
+ this.ecosystem = ecosystem;
1136
+ this.runner = runner;
1137
+ this.command = command;
1138
+ this.scope_root = scope_root;
1139
+ this.confidence = confidence;
1140
+ this.basis = basis;
1141
+ this.conflict = conflict;
1142
+ }
1143
+ ecosystem;
1144
+ runner;
1145
+ command;
1146
+ scope_root;
1147
+ confidence;
1148
+ basis;
1149
+ conflict;
1150
+ };
1151
+ var ToolchainResult = class {
1152
+ ecosystems;
1153
+ runners;
1154
+ selected;
1155
+ quality;
1156
+ confidence;
1157
+ mtime;
1158
+ /**
1159
+ * Per-scope behavior inventory — detection only, deliberately NOT merged
1160
+ * into `runners` nor reachable from `selected`, so nothing is ever run
1161
+ * BECAUSE this axis detected it.
1162
+ *
1163
+ * That is a claim about reachability, not about execution, and the two
1164
+ * came apart in review: for four of the eight labels the behavior command
1165
+ * is byte-identical to a native command already in `selected` —
1166
+ * `pytest-bdd`/`pytest`, `cucumber-jvm`/`jvm.command`, and both
1167
+ * `reqnroll` and `specflow`/`dotnet test`. Those native rows are
1168
+ * SPEED_FAST, so the suite does run by default; it runs as the native
1169
+ * runner's work, which this axis neither caused nor can prevent. Stating
1170
+ * it as "reported, never run" overreached in exactly the half a reader
1171
+ * would act on.
1172
+ */
1173
+ behavior_runners;
1174
+ constructor(args) {
1175
+ this.ecosystems = args.ecosystems;
1176
+ this.runners = args.runners;
1177
+ this.selected = args.selected;
1178
+ this.quality = args.quality;
1179
+ this.confidence = args.confidence;
1180
+ this.mtime = args.mtime;
1181
+ this.behavior_runners = args.behavior_runners ?? [];
1182
+ }
1183
+ /**
1184
+ * Serialise to the auto-generated project-config shape.
1185
+ *
1186
+ * Written to `agents/runtime/state/toolchain.json` by
1187
+ * {@link write_config} so the per-stack commands are captured once and
1188
+ * re-read cheaply (keyed on `mtime`) instead of re-detected every turn.
1189
+ */
1190
+ to_config() {
1191
+ return {
1192
+ ecosystems: [...this.ecosystems],
1193
+ confidence: this.confidence,
1194
+ // `mtime` is a Python float; wrap so `write_config`'s serialiser
1195
+ // renders an integral value as `N.0` (Python float repr).
1196
+ mtime: pyFloat(this.mtime),
1197
+ runners: this.runners.map((r) => ({
1198
+ ecosystem: r.ecosystem,
1199
+ runner: r.runner,
1200
+ command: r.command,
1201
+ speed: r.speed,
1202
+ confidence: r.confidence,
1203
+ basis: r.basis
1204
+ })),
1205
+ selected: this.selected.map((r) => r.command),
1206
+ quality: [...this.quality],
1207
+ behavior_runners: this.behavior_runners.map((r) => ({
1208
+ ecosystem: r.ecosystem,
1209
+ runner: r.runner,
1210
+ command: r.command,
1211
+ scope_root: r.scope_root,
1212
+ confidence: r.confidence,
1213
+ basis: r.basis,
1214
+ conflict: [...r.conflict]
1215
+ }))
1216
+ };
1217
+ }
1218
+ };
1219
+ function resolve_toolchain(project_root, opts = {}) {
1220
+ const include_slow = opts.include_slow ?? false;
1221
+ const include_e2e = opts.include_e2e ?? false;
1222
+ const php_only = opts.php_only ?? false;
1223
+ const runners = [];
1224
+ const quality = [];
1225
+ const composer = _read_json(path3.join(project_root, "composer.json"));
1226
+ const pkg = _read_json(path3.join(project_root, "package.json"));
1227
+ const has_composer = _is_file(path3.join(project_root, "composer.json"));
1228
+ const has_package = _is_file(path3.join(project_root, "package.json"));
1229
+ const pyproject_text = _read_text(path3.join(project_root, "pyproject.toml"));
1230
+ const has_python = Boolean(pyproject_text) || _is_file(path3.join(project_root, "requirements.txt")) || _is_file(path3.join(project_root, "setup.cfg")) || _is_file(path3.join(project_root, "pytest.ini"));
1231
+ const has_go = _is_file(path3.join(project_root, "go.mod"));
1232
+ const has_cargo = _is_file(path3.join(project_root, "Cargo.toml"));
1233
+ const gemfile_text = _ruby_gemfile_text(project_root);
1234
+ const jvm = _jvm_build(project_root);
1235
+ const dotnet_basis = _dotnet_basis(project_root);
1236
+ const wrappers = _task_runner_wrappers(project_root, pkg);
1237
+ if (has_composer) {
1238
+ runners.push(..._php_runners(project_root, composer, wrappers));
1239
+ quality.push(..._php_quality(project_root, composer, wrappers));
1240
+ }
1241
+ if (has_package) {
1242
+ runners.push(..._js_runners(pkg, wrappers, _package_manager(project_root, pkg)));
1243
+ quality.push(..._js_quality(pkg, wrappers));
1244
+ }
1245
+ if (has_python) {
1246
+ runners.push(..._python_runners(pyproject_text));
1247
+ quality.push(..._python_quality(pyproject_text));
1248
+ }
1249
+ if (has_go) {
1250
+ runners.push(
1251
+ new RunnerResult("go", "go-test", "go test ./...", SPEED_FAST, HIGH, "go.mod present")
1252
+ );
1253
+ quality.push("go vet ./...");
1254
+ }
1255
+ if (has_cargo) {
1256
+ runners.push(
1257
+ new RunnerResult("rust", "cargo-test", "cargo test", SPEED_FAST, HIGH, "Cargo.toml present")
1258
+ );
1259
+ quality.push("cargo clippy");
1260
+ }
1261
+ runners.push(..._ruby_runners(project_root, gemfile_text));
1262
+ if (jvm !== null) {
1263
+ runners.push(..._jvm_runners(jvm));
1264
+ }
1265
+ if (dotnet_basis !== null) {
1266
+ runners.push(..._dotnet_runners(dotnet_basis));
1267
+ }
1268
+ const ecosystems = _dictFromKeys(runners.map((r) => r.ecosystem));
1269
+ const selected = _apply_guard(runners, { include_slow, include_e2e, php_only });
1270
+ const confidence = _overall_confidence(runners);
1271
+ const scopes = _behavior_scopes(project_root);
1272
+ return new ToolchainResult({
1273
+ ecosystems,
1274
+ runners: [...runners],
1275
+ selected: [...selected],
1276
+ quality: _dictFromKeys(quality),
1277
+ confidence,
1278
+ // Computed ONCE and threaded into both consumers: workspace discovery
1279
+ // reads a manifest and lists per glob parent, and both the cache probe
1280
+ // and the behavior axis need the same list.
1281
+ mtime: latest_manifest_mtime(project_root, scopes),
1282
+ behavior_runners: resolve_behavior_runners(project_root, scopes)
1283
+ });
1284
+ }
1285
+ function latest_manifest_mtime(project_root, scopes) {
1286
+ const mtimes = [];
1287
+ const names = [..._MANIFESTS, ..._BEHAVIOR_MARKERS];
1288
+ for (const scope of scopes ?? _behavior_scopes(project_root)) {
1289
+ const dir = scope === "." ? project_root : path3.join(project_root, scope);
1290
+ for (const name of names) {
1291
+ const p = path3.join(dir, name);
1292
+ if (_is_file(p)) {
1293
+ mtimes.push(_stat_mtime(p));
1294
+ }
1295
+ }
1296
+ }
1297
+ return mtimes.length > 0 ? Math.max(...mtimes) : 0;
1298
+ }
1299
+ function _php_runners(root, composer, wrappers) {
1300
+ const deps = _all_dependencies(composer, "require", "require-dev");
1301
+ if ("pestphp/pest" in deps || _is_file(path3.join(root, "vendor", "bin", "pest"))) {
1302
+ const cmd2 = wrappers["php-test"] || "vendor/bin/pest";
1303
+ const basis = "pestphp/pest" in deps ? "pestphp/pest in composer require" : "vendor/bin/pest present";
1304
+ return [new RunnerResult("php", "pest", cmd2, SPEED_FAST, HIGH, basis)];
1305
+ }
1306
+ if (_is_file(path3.join(root, "artisan"))) {
1307
+ const cmd2 = wrappers["php-test"] || "php artisan test";
1308
+ return [new RunnerResult("php", "phpunit", cmd2, SPEED_FAST, HIGH, "artisan present (Laravel)")];
1309
+ }
1310
+ if ("phpunit/phpunit" in deps || _is_file(path3.join(root, "vendor", "bin", "phpunit"))) {
1311
+ const cmd2 = wrappers["php-test"] || "vendor/bin/phpunit";
1312
+ return [new RunnerResult("php", "phpunit", cmd2, SPEED_FAST, HIGH, "phpunit/phpunit detected")];
1313
+ }
1314
+ const cmd = wrappers["php-test"] || "vendor/bin/phpunit";
1315
+ return [
1316
+ new RunnerResult(
1317
+ "php",
1318
+ "phpunit",
1319
+ cmd,
1320
+ SPEED_FAST,
1321
+ MEDIUM,
1322
+ "composer.json present, no explicit runner"
1323
+ )
1324
+ ];
1325
+ }
1326
+ function _js_runners(pkg, wrappers, manager) {
1327
+ const deps = _all_dependencies(
1328
+ pkg,
1329
+ "dependencies",
1330
+ "devDependencies",
1331
+ "peerDependencies",
1332
+ "optionalDependencies"
1333
+ );
1334
+ const scripts = _isDict(pkg.scripts) ? pkg.scripts : {};
1335
+ const pm_test = wrappers["js-test"];
1336
+ const out = [];
1337
+ if ("vitest" in deps) {
1338
+ const cmd = pm_test && _script_uses(scripts, "test", "vitest") ? pm_test : "npx vitest run";
1339
+ out.push(new RunnerResult("js", "vitest", cmd, SPEED_FAST, HIGH, "vitest in package deps"));
1340
+ } else if ("jest" in deps) {
1341
+ const cmd = pm_test && _script_uses(scripts, "test", "jest") ? pm_test : "npx jest";
1342
+ out.push(new RunnerResult("js", "jest", cmd, SPEED_FAST, HIGH, "jest in package deps"));
1343
+ } else if (pm_test) {
1344
+ out.push(
1345
+ new RunnerResult(
1346
+ "js",
1347
+ "jest",
1348
+ pm_test,
1349
+ SPEED_FAST,
1350
+ MEDIUM,
1351
+ "package.json test script, runner unclear"
1352
+ )
1353
+ );
1354
+ }
1355
+ if ("@playwright/test" in deps || "playwright" in deps) {
1356
+ const cmd = _script_command(scripts, ["test:e2e", "e2e", "playwright"], manager) || "npx playwright test";
1357
+ out.push(
1358
+ new RunnerResult("js", "playwright", cmd, SPEED_E2E, HIGH, "@playwright/test in package deps")
1359
+ );
1360
+ } else if ("cypress" in deps) {
1361
+ const cmd = _script_command(scripts, ["test:e2e", "e2e", "cypress"], manager) || "npx cypress run";
1362
+ out.push(new RunnerResult("js", "cypress", cmd, SPEED_E2E, HIGH, "cypress in package deps"));
1363
+ }
1364
+ const slow_cmd = _script_command(scripts, ["test:slow", "test:integration"], manager);
1365
+ if (slow_cmd) {
1366
+ out.push(
1367
+ new RunnerResult(
1368
+ "js",
1369
+ "vitest" in deps ? "vitest" : "jest",
1370
+ slow_cmd,
1371
+ SPEED_SLOW,
1372
+ MEDIUM,
1373
+ "test:slow/integration script"
1374
+ )
1375
+ );
1376
+ }
1377
+ return out;
1378
+ }
1379
+ function _python_runners(pyproject_text) {
1380
+ if (pyproject_text.includes("pytest") || pyproject_text === "") {
1381
+ const conf = pyproject_text.includes("pytest") ? HIGH : MEDIUM;
1382
+ const basis = pyproject_text.includes("pytest") ? "pytest in pyproject" : "python project, no explicit runner";
1383
+ return [new RunnerResult("python", "pytest", "pytest", SPEED_FAST, conf, basis)];
1384
+ }
1385
+ return [
1386
+ new RunnerResult("python", "pytest", "pytest", SPEED_FAST, MEDIUM, "python project, no explicit runner")
1387
+ ];
1388
+ }
1389
+ function _ruby_gemfile_text(dir) {
1390
+ let present = false;
1391
+ for (const name of ["Gemfile", "gems.rb"]) {
1392
+ const full = path3.join(dir, name);
1393
+ present = present || _is_file(full);
1394
+ const text = _read_text(full);
1395
+ if (text !== "") {
1396
+ return { text, file: name, present: true };
1397
+ }
1398
+ }
1399
+ return { text: "", file: "", present };
1400
+ }
1401
+ function _gem_declared(text, gem) {
1402
+ return new RegExp(`^[ \\t]*gem[ \\t]+["']${gem}(-[A-Za-z0-9_-]+)?["']`, "m").test(text);
1403
+ }
1404
+ function _ruby_runners(root, gemfile) {
1405
+ const dot_rspec = _is_file(path3.join(root, ".rspec"));
1406
+ const spec_helper = _is_file(path3.join(root, "spec", "spec_helper.rb"));
1407
+ const in_gemfile = _gem_declared(gemfile.text, "rspec");
1408
+ if (!in_gemfile && !dot_rspec && !spec_helper) {
1409
+ return [];
1410
+ }
1411
+ const basis = in_gemfile ? `rspec in ${gemfile.file}` : dot_rspec ? ".rspec present" : "spec/spec_helper.rb present";
1412
+ const cmd = gemfile.present ? "bundle exec rspec" : "rspec";
1413
+ return [new RunnerResult("ruby", "rspec", cmd, SPEED_FAST, HIGH, basis)];
1414
+ }
1415
+ function _jvm_build(root) {
1416
+ const pom = _read_text(path3.join(root, "pom.xml"));
1417
+ if (pom !== "") {
1418
+ const wrapper = _is_file(path3.join(root, "mvnw"));
1419
+ return { tool: "maven", text: pom, command: wrapper ? "./mvnw test" : "mvn test" };
1420
+ }
1421
+ const gradleFile = ["build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts"].find(
1422
+ (n) => _is_file(path3.join(root, n))
1423
+ );
1424
+ if (gradleFile !== void 0) {
1425
+ const wrapper = _is_file(path3.join(root, "gradlew"));
1426
+ return {
1427
+ tool: "gradle",
1428
+ text: _read_text(path3.join(root, "build.gradle")) + "\n" + _read_text(path3.join(root, "build.gradle.kts")),
1429
+ command: wrapper ? "./gradlew test" : "gradle test"
1430
+ };
1431
+ }
1432
+ return null;
1433
+ }
1434
+ function _jvm_runners(build) {
1435
+ const named = /\bjunit\b/i.test(build.text);
1436
+ const conf = named ? HIGH : MEDIUM;
1437
+ const basis = named ? `junit in the ${build.tool} build file` : `${build.tool} build file present, no explicit runner`;
1438
+ return [new RunnerResult("jvm", "junit", build.command, SPEED_FAST, conf, basis)];
1439
+ }
1440
+ function _dotnet_basis(root) {
1441
+ let names;
1442
+ try {
1443
+ names = fs3.readdirSync(root, { withFileTypes: true }).filter((e) => e.isFile()).map((e) => e.name).sort();
1444
+ } catch {
1445
+ names = [];
1446
+ }
1447
+ const target = names.find((n) => _DOTNET_PROJECT_EXTS.includes(path3.extname(n).toLowerCase()));
1448
+ if (target === void 0) {
1449
+ return null;
1450
+ }
1451
+ return _DOTNET_TEST_STACK.test(_dotnet_project_text(root)) ? { basis: "test stack named in a .NET project file", confidence: HIGH } : { basis: `${target} present, no test stack named`, confidence: MEDIUM };
1452
+ }
1453
+ function _dotnet_runners(found) {
1454
+ return [
1455
+ new RunnerResult(
1456
+ "dotnet",
1457
+ "dotnet-test",
1458
+ "dotnet test",
1459
+ SPEED_FAST,
1460
+ found.confidence,
1461
+ found.basis
1462
+ )
1463
+ ];
1464
+ }
1465
+ function resolve_behavior_runners(project_root, scopes) {
1466
+ const out = [];
1467
+ for (const scope of scopes ?? _behavior_scopes(project_root)) {
1468
+ const dir = scope === "." ? project_root : path3.join(project_root, scope);
1469
+ const rows = _behavior_runners_in_scope(dir, scope);
1470
+ for (const ecosystem of _dictFromKeys(rows.map((r) => r.ecosystem))) {
1471
+ const found = rows.filter((r) => r.ecosystem === ecosystem);
1472
+ const names = [...new Set(found.map((r) => r.runner))].sort();
1473
+ if (names.length <= 1) {
1474
+ out.push(found[0]);
1475
+ continue;
1476
+ }
1477
+ out.push(
1478
+ new BehaviorRunnerResult(
1479
+ ecosystem,
1480
+ BEHAVIOR_UNKNOWN,
1481
+ "",
1482
+ scope,
1483
+ LOW,
1484
+ `two ${ecosystem} behavior runners in one scope (${names.join(", ")}) \u2014 refusing to choose, as the frontend detector refuses between mutually exclusive workspaces`,
1485
+ names
1486
+ )
1487
+ );
1488
+ }
1489
+ }
1490
+ return out;
1491
+ }
1492
+ function _behavior_runners_in_scope(dir, scope) {
1493
+ const out = [];
1494
+ const at = (n) => path3.join(dir, n);
1495
+ const row = (ecosystem, runner, command, basis) => {
1496
+ out.push(new BehaviorRunnerResult(ecosystem, runner, command, scope, HIGH, basis));
1497
+ };
1498
+ const php_deps = _all_dependencies(_read_json(at("composer.json")), "require", "require-dev");
1499
+ const behat_config = ["behat.yml", "behat.yaml", "behat.yml.dist", "behat.dist.yml"].find(
1500
+ (n) => _is_file(at(n))
1501
+ );
1502
+ if ("behat/behat" in php_deps || behat_config !== void 0) {
1503
+ const declared = "behat/behat" in php_deps;
1504
+ const basis = declared ? "behat/behat in the composer manifest" : `${behat_config} present`;
1505
+ row("php", "behat", declared ? "vendor/bin/behat" : "behat", basis);
1506
+ }
1507
+ const js_deps = _all_dependencies(
1508
+ _read_json(at("package.json")),
1509
+ "dependencies",
1510
+ "devDependencies"
1511
+ );
1512
+ const cucumber_config = ["cucumber.js", "cucumber.cjs", "cucumber.mjs", "cucumber.json"].find(
1513
+ (n) => _is_file(at(n))
1514
+ );
1515
+ if ("@cucumber/cucumber" in js_deps || "cucumber" in js_deps || cucumber_config !== void 0) {
1516
+ const dep = "@cucumber/cucumber" in js_deps ? "@cucumber/cucumber" : "cucumber";
1517
+ const declared = dep in js_deps;
1518
+ const basis = declared ? `${dep} in package deps` : `${cucumber_config} present`;
1519
+ row("js", "cucumber-js", declared ? "npx cucumber-js" : "cucumber-js", basis);
1520
+ }
1521
+ const py_text = [_read_text(at("pyproject.toml")), _read_text(at("requirements.txt"))].join("\n");
1522
+ if (_PY_BEHAVE.test(py_text)) {
1523
+ row("python", "behave", "behave", "behave declared as a python dependency");
1524
+ }
1525
+ if (_PY_BDD.test(py_text)) {
1526
+ row("python", "pytest-bdd", "pytest", "pytest-bdd declared as a python dependency");
1527
+ }
1528
+ const gemfile = _ruby_gemfile_text(dir);
1529
+ const cucumber_gem = _gem_declared(gemfile.text, "cucumber");
1530
+ if (cucumber_gem || _is_file(path3.join(dir, "features", "support", "env.rb"))) {
1531
+ const basis = cucumber_gem ? `cucumber in ${gemfile.file}` : "features/support/env.rb present";
1532
+ const cmd = gemfile.present ? "bundle exec cucumber" : "cucumber";
1533
+ row("ruby", "cucumber-ruby", cmd, basis);
1534
+ }
1535
+ const jvm = _jvm_build(dir);
1536
+ if (jvm !== null && /io\.cucumber|cucumber-java|cucumber-junit/i.test(jvm.text)) {
1537
+ row("jvm", "cucumber-jvm", jvm.command, `cucumber in the ${jvm.tool} build file`);
1538
+ }
1539
+ const dotnet_text = _dotnet_project_text(dir);
1540
+ if (/\bReqnroll\b/i.test(dotnet_text)) {
1541
+ row("dotnet", "reqnroll", "dotnet test", "Reqnroll in a project file");
1542
+ }
1543
+ if (/\bSpecFlow\b/i.test(dotnet_text)) {
1544
+ row("dotnet", "specflow", "dotnet test", "SpecFlow in a project file");
1545
+ }
1546
+ return out;
1547
+ }
1548
+ function _dotnet_project_text(dir) {
1549
+ const texts = [];
1550
+ const solution = _has_dotnet_solution(dir);
1551
+ const walk = (at, depth) => {
1552
+ if (texts.length >= _DOTNET_MAX_PROJECTS) {
1553
+ return;
1554
+ }
1555
+ let entries;
1556
+ try {
1557
+ entries = fs3.readdirSync(at, { withFileTypes: true });
1558
+ } catch {
1559
+ return;
1560
+ }
1561
+ entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
1562
+ let hit = false;
1563
+ for (const e of entries) {
1564
+ if (!e.isFile() || !_DOTNET_PROJECT_EXTS.includes(path3.extname(e.name).toLowerCase())) {
1565
+ continue;
1566
+ }
1567
+ if (_DOTNET_SOLUTION_EXTS.includes(path3.extname(e.name).toLowerCase())) {
1568
+ continue;
1569
+ }
1570
+ if (texts.length >= _DOTNET_MAX_PROJECTS) {
1571
+ return;
1572
+ }
1573
+ texts.push(_read_text(path3.join(at, e.name)));
1574
+ hit = true;
1575
+ }
1576
+ if (hit || !solution || depth >= _DOTNET_SCAN_DEPTH) {
1577
+ return;
1578
+ }
1579
+ for (const e of entries) {
1580
+ if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules") {
1581
+ walk(path3.join(at, e.name), depth + 1);
1582
+ }
1583
+ }
1584
+ };
1585
+ walk(dir, 0);
1586
+ return texts.join("\n");
1587
+ }
1588
+ var _MAX_BEHAVIOR_SCOPES = 200;
1589
+ var _PY_BEHAVE = /^[ \t]*["']?behave["']?[ \t]*(?:$|[=<>~!,;[#])/m;
1590
+ var _PY_BDD = /^[ \t]*["']?pytest-bdd["']?[ \t]*(?:$|[=<>~!,;[#])/m;
1591
+ function _pnpm_packages(text) {
1592
+ const out = [];
1593
+ let inPackages = false;
1594
+ const push = (raw) => {
1595
+ const v = raw.replace(/\s+#.*$/, "").trim().replace(/^['"]|['"]$/g, "").trim();
1596
+ if (v !== "") out.push(v);
1597
+ };
1598
+ for (const line of text.split(/\r?\n/)) {
1599
+ if (line.trim() === "" || /^\s*#/.test(line)) {
1600
+ continue;
1601
+ }
1602
+ const key = /^([A-Za-z0-9_-]+)\s*:(.*)$/.exec(line);
1603
+ if (key) {
1604
+ inPackages = key[1] === "packages";
1605
+ const rest = (key[2] ?? "").trim();
1606
+ if (inPackages && rest.startsWith("[")) {
1607
+ for (const part of rest.replace(/^\[|\]$/g, "").split(",")) {
1608
+ push(part);
1609
+ }
1610
+ inPackages = false;
1611
+ }
1612
+ continue;
1613
+ }
1614
+ if (!inPackages) {
1615
+ continue;
1616
+ }
1617
+ const item = /^\s*-\s*(.+?)\s*$/.exec(line);
1618
+ if (item && item[1] !== void 0) {
1619
+ push(item[1]);
1620
+ }
1621
+ }
1622
+ return out;
1623
+ }
1624
+ function _excluded_scope(name) {
1625
+ return name.split("/").some((seg) => seg.startsWith(".") || seg === "node_modules");
1626
+ }
1627
+ function _behavior_scopes(project_root) {
1628
+ const globs = [];
1629
+ const pkg = _read_json(path3.join(project_root, "package.json"));
1630
+ const ws = pkg["workspaces"];
1631
+ if (Array.isArray(ws)) {
1632
+ globs.push(...ws.filter((g) => typeof g === "string"));
1633
+ } else if (_isDict(ws) && Array.isArray(ws["packages"])) {
1634
+ globs.push(...ws["packages"].filter((g) => typeof g === "string"));
1635
+ }
1636
+ globs.push(..._pnpm_packages(_read_text(path3.join(project_root, "pnpm-workspace.yaml"))));
1637
+ const scopes = ["."];
1638
+ for (const raw of globs) {
1639
+ if (scopes.length >= _MAX_BEHAVIOR_SCOPES) {
1640
+ break;
1641
+ }
1642
+ const g = raw.replace(/^\.\//, "").replace(/\/+$/, "");
1643
+ if (g === "" || g.startsWith("!") || g.startsWith("/") || g.includes("..")) {
1644
+ continue;
1645
+ }
1646
+ if (_excluded_scope(g)) {
1647
+ continue;
1648
+ }
1649
+ if (g.endsWith("/*")) {
1650
+ const parent = g.slice(0, -2);
1651
+ let children;
1652
+ try {
1653
+ children = fs3.readdirSync(path3.join(project_root, parent), { withFileTypes: true });
1654
+ } catch {
1655
+ continue;
1656
+ }
1657
+ children.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
1658
+ for (const c of children) {
1659
+ if (scopes.length >= _MAX_BEHAVIOR_SCOPES) {
1660
+ break;
1661
+ }
1662
+ if (c.isDirectory() && !_excluded_scope(c.name)) {
1663
+ scopes.push(`${parent}/${c.name}`);
1664
+ }
1665
+ }
1666
+ } else if (!g.includes("*")) {
1667
+ try {
1668
+ if (fs3.lstatSync(path3.join(project_root, g)).isDirectory()) {
1669
+ scopes.push(g);
1670
+ }
1671
+ } catch {
1672
+ }
1673
+ }
1674
+ }
1675
+ return _dictFromKeys(scopes);
1676
+ }
1677
+ function _php_quality(root, composer, _wrappers) {
1678
+ const deps = _all_dependencies(composer, "require", "require-dev");
1679
+ const out = [];
1680
+ if ("phpstan/phpstan" in deps || _is_file(path3.join(root, "vendor", "bin", "phpstan"))) {
1681
+ out.push("vendor/bin/phpstan analyse");
1682
+ }
1683
+ if ("laravel/pint" in deps || _is_file(path3.join(root, "vendor", "bin", "pint"))) {
1684
+ out.push("vendor/bin/pint");
1685
+ }
1686
+ return out;
1687
+ }
1688
+ function _js_quality(pkg, _wrappers) {
1689
+ const deps = _all_dependencies(
1690
+ pkg,
1691
+ "dependencies",
1692
+ "devDependencies",
1693
+ "peerDependencies",
1694
+ "optionalDependencies"
1695
+ );
1696
+ const out = [];
1697
+ if ("typescript" in deps) {
1698
+ out.push("npx tsc --noEmit");
1699
+ }
1700
+ if ("eslint" in deps) {
1701
+ out.push("npx eslint .");
1702
+ }
1703
+ return out;
1704
+ }
1705
+ function _python_quality(pyproject_text) {
1706
+ const out = [];
1707
+ if (pyproject_text.includes("ruff")) {
1708
+ out.push("ruff check");
1709
+ }
1710
+ if (pyproject_text.includes("mypy")) {
1711
+ out.push("mypy .");
1712
+ }
1713
+ return out;
1714
+ }
1715
+ function _task_runner_wrappers(root, pkg) {
1716
+ const out = {};
1717
+ const makefile = _read_text(path3.join(root, "Makefile"));
1718
+ const taskfile = _read_text(path3.join(root, "Taskfile.yml")) || _read_text(path3.join(root, "Taskfile.yaml"));
1719
+ if (makefile && _reSearch(/^test\s*:/m, makefile)) {
1720
+ out["php-test"] = "make test";
1721
+ } else if (taskfile && _reSearch(/^\s*test\s*:/m, taskfile)) {
1722
+ out["php-test"] = "task test";
1723
+ }
1724
+ const scripts = _isDict(pkg.scripts) ? pkg.scripts : {};
1725
+ if (_isDict(scripts) && "test" in scripts) {
1726
+ out["js-test"] = `${_package_manager(root, pkg)} test`;
1727
+ }
1728
+ return out;
1729
+ }
1730
+ var LOCKFILE_MANAGERS = [
1731
+ ["pnpm-lock.yaml", "pnpm"],
1732
+ ["yarn.lock", "yarn"],
1733
+ ["bun.lock", "bun"],
1734
+ ["bun.lockb", "bun"],
1735
+ ["package-lock.json", "npm"]
1736
+ ];
1737
+ function _resolve_package_manager(root, pkg) {
1738
+ const declared = pkg === void 0 ? void 0 : pkg["packageManager"];
1739
+ if (typeof declared === "string" && declared.trim() !== "") {
1740
+ const name = declared.trim().split("@")[0];
1741
+ if (name !== "") {
1742
+ return { manager: name, via: "packageManager", finding: "" };
1743
+ }
1744
+ }
1745
+ const present = LOCKFILE_MANAGERS.filter(([file]) => _is_file(path3.join(root, file)));
1746
+ const distinct = [...new Set(present.map(([, mgr]) => mgr))];
1747
+ if (distinct.length > 1) {
1748
+ return {
1749
+ manager: null,
1750
+ via: "ambiguous",
1751
+ finding: `two package-manager lockfiles present (${present.map(([f]) => f).join(", ")}) and no \`packageManager\` declaration \u2014 report both and stop, per monorepo-workspace SKILL.md \xA7 1`
1752
+ };
1753
+ }
1754
+ if (distinct.length === 1) {
1755
+ const mgr = distinct[0];
1756
+ return { manager: mgr, via: mgr, finding: "" };
1757
+ }
1758
+ return { manager: "npm", via: "npm", finding: "" };
1759
+ }
1760
+ function _package_manager(root, pkg) {
1761
+ return _resolve_package_manager(root, pkg).manager ?? "npm";
1762
+ }
1763
+ function _apply_guard(runners, opts) {
1764
+ const out = [];
1765
+ for (const r of runners) {
1766
+ if (opts.php_only && r.ecosystem !== "php") {
1767
+ continue;
1768
+ }
1769
+ if (r.speed === SPEED_E2E && !opts.include_e2e) {
1770
+ continue;
1771
+ }
1772
+ if (r.speed === SPEED_SLOW && !opts.include_slow) {
1773
+ continue;
1774
+ }
1775
+ out.push(r);
1776
+ }
1777
+ return out;
1778
+ }
1779
+ function _overall_confidence(runners) {
1780
+ if (runners.length === 0) {
1781
+ return LOW;
1782
+ }
1783
+ if (runners.some((r) => r.confidence === HIGH)) {
1784
+ return HIGH;
1785
+ }
1786
+ return MEDIUM;
1787
+ }
1788
+ function _read_json(p) {
1789
+ if (!_is_file(p)) {
1790
+ return {};
1791
+ }
1792
+ let payload;
1793
+ try {
1794
+ payload = JSON.parse(fs3.readFileSync(p, { encoding: "utf-8" }));
1795
+ } catch {
1796
+ return {};
1797
+ }
1798
+ return _isDict(payload) ? payload : {};
1799
+ }
1800
+ function _read_text(p) {
1801
+ if (!_is_file(p)) {
1802
+ return "";
1803
+ }
1804
+ try {
1805
+ return fs3.readFileSync(p, { encoding: "utf-8" });
1806
+ } catch {
1807
+ return "";
1808
+ }
1809
+ }
1810
+ function _all_dependencies(manifest, ...keys) {
1811
+ const merged = {};
1812
+ for (const key of keys) {
1813
+ const section = manifest[key];
1814
+ if (_isDict(section)) {
1815
+ Object.assign(merged, section);
1816
+ }
1817
+ }
1818
+ return merged;
1819
+ }
1820
+ function _script_uses(scripts, name, tool) {
1821
+ const value = scripts[name];
1822
+ return typeof value === "string" && value.includes(tool);
1823
+ }
1824
+ function _script_command(scripts, names, manager) {
1825
+ for (const name of names) {
1826
+ if (typeof scripts[name] === "string") {
1827
+ return `${manager} run ${name}`;
1828
+ }
1829
+ }
1830
+ return "";
1831
+ }
1832
+ function _is_file(p) {
1833
+ try {
1834
+ return fs3.statSync(p).isFile();
1835
+ } catch {
1836
+ return false;
1837
+ }
1838
+ }
1839
+ function _stat_mtime(p) {
1840
+ try {
1841
+ return Number(fs3.statSync(p, { bigint: true }).mtimeNs) / 1e9;
1842
+ } catch {
1843
+ return 0;
1844
+ }
1845
+ }
1846
+ function _isDict(v) {
1847
+ return typeof v === "object" && v !== null && !Array.isArray(v);
1848
+ }
1849
+ function _reSearch(pattern, text) {
1850
+ return pattern.test(text);
1851
+ }
1852
+ function _dictFromKeys(seq) {
1853
+ return [...new Set(seq)];
1854
+ }
1855
+ var _PyFloat = class {
1856
+ value;
1857
+ constructor(value) {
1858
+ this.value = value;
1859
+ }
1860
+ };
1861
+ function pyFloat(value) {
1862
+ return new _PyFloat(value);
1863
+ }
1864
+
1032
1865
  // src/scripts/before_complete_hook.ts
1033
- var STATE_FILE = path.join("agents", "state", "verify-before-complete.json");
1034
- var STATE_DIR = path.join("agents", "state", "verify-before-complete");
1866
+ var STATE_FILE = path4.join("agents", "state", "verify-before-complete.json");
1867
+ var STATE_DIR = path4.join("agents", "state", "verify-before-complete");
1035
1868
  var STATE_RETENTION_DAYS = 7;
1036
1869
  function statePathFor(session_id) {
1037
1870
  return session_state_file(STATE_DIR, session_id);
@@ -1080,9 +1913,55 @@ function _empty_state() {
1080
1913
  // a reader must never have to distinguish "no runs yet" from "key absent".
1081
1914
  verification_runs: [],
1082
1915
  edits_this_turn: 0,
1916
+ // NOTE — `quality_runs` and `quality_files_source` are deliberately ABSENT
1917
+ // here, against the convention `verification_runs` above follows.
1918
+ //
1919
+ // That convention ("present in the empty state so a reader never has to tell
1920
+ // 'no runs yet' from 'key absent'") is the right default and it loses to a
1921
+ // stronger one: the roadmap's first acceptance criterion is that with
1922
+ // `touched_file_quality: off` the record is BYTE-IDENTICAL to the base ref.
1923
+ // Seeding two keys would break that for every install that never opted in,
1924
+ // and "the default-off feature changed my state file" is exactly the kind of
1925
+ // silent shape drift a consumer cannot audit. The convention's cost here is
1926
+ // zero, because the only reader of these keys is 2.1's analysis, which counts
1927
+ // stops — and a stop with no key is a stop where the pass did not run, which
1928
+ // is a distinction it needs rather than one it must not make.
1083
1929
  checked_at: _now()
1084
1930
  };
1085
1931
  }
1932
+ function readTouchedFileQualityMode(consumer_root) {
1933
+ const raw = hookSectionValue(consumer_root, "verify_before_complete", "touched_file_quality");
1934
+ return raw === "shadow" || raw === "warn" ? raw : "off";
1935
+ }
1936
+ function collectTouchedFileQuality(consumer_root, session_id) {
1937
+ let touched;
1938
+ try {
1939
+ touched = touchedFilesAtStop(consumer_root, session_id);
1940
+ } catch {
1941
+ return null;
1942
+ }
1943
+ if (touched.files.length === 0) {
1944
+ return { runs: [], source: touched.source };
1945
+ }
1946
+ let commands = [];
1947
+ try {
1948
+ commands = resolve_toolchain(consumer_root).quality;
1949
+ } catch {
1950
+ commands = [];
1951
+ }
1952
+ try {
1953
+ return {
1954
+ runs: runTouchedFileQuality({
1955
+ root: consumer_root,
1956
+ commands,
1957
+ files: touched.files
1958
+ }),
1959
+ source: touched.source
1960
+ };
1961
+ } catch {
1962
+ return null;
1963
+ }
1964
+ }
1086
1965
  function _extract_command(payload) {
1087
1966
  const toolRaw = payload["tool_name"] || payload["toolName"] || payload["tool"];
1088
1967
  if (!(typeof toolRaw === "string" && COMMAND_TOOLS.has(toolRaw))) {
@@ -1195,6 +2074,7 @@ function _cap_runs(runs) {
1195
2074
  return [head, ...tail];
1196
2075
  }
1197
2076
  var _ERROR_EXIT_PREFIX = /^Error:\s*Exit code\s*(\d+)/i;
2077
+ var _BARE_EXIT_PREFIX = /^\s*Exit code\s*(\d+)/i;
1198
2078
  var _NO_EXIT = { code: null, source: null, interrupted: false };
1199
2079
  function _numeric_exit_field(obj) {
1200
2080
  for (const key of ["exit_code", "exitCode", "returncode", "returnCode", "status_code"]) {
@@ -1206,26 +2086,46 @@ function _numeric_exit_field(obj) {
1206
2086
  }
1207
2087
  return null;
1208
2088
  }
1209
- function _extract_exit_reading(payload) {
2089
+ var FAILURE_EVENT_NAMES = /* @__PURE__ */ new Set(["PostToolUseFailure"]);
2090
+ function _is_failure_event(payload, envelope) {
2091
+ for (const v of [payload["hook_event_name"], envelope["native_event"]]) {
2092
+ if (typeof v !== "string" || v === "") continue;
2093
+ if (FAILURE_EVENT_NAMES.has(v)) return true;
2094
+ if (/failure$/i.test(v)) return true;
2095
+ }
2096
+ return false;
2097
+ }
2098
+ function _extract_exit_reading(payload, failure_event = false) {
2099
+ const seal = (r) => failure_event && r.code === 0 ? { code: null, source: null, interrupted: r.interrupted } : r;
1210
2100
  const direct = _numeric_exit_field(payload);
1211
- if (direct !== null) return { code: direct, source: "field", interrupted: false };
2101
+ if (direct !== null) return seal({ code: direct, source: "field", interrupted: false });
2102
+ const interrupted_top = payload["is_interrupt"] === true;
2103
+ const err = payload["error"];
2104
+ if (typeof err === "string") {
2105
+ if (interrupted_top) return { code: null, source: null, interrupted: true };
2106
+ const bare = _BARE_EXIT_PREFIX.exec(err) ?? _ERROR_EXIT_PREFIX.exec(err);
2107
+ if (bare?.[1] !== void 0) {
2108
+ return seal({ code: Number(bare[1]), source: "error_prefix", interrupted: false });
2109
+ }
2110
+ }
2111
+ if (interrupted_top) return { code: null, source: null, interrupted: true };
1212
2112
  for (const key of ["tool_response", "toolResponse", "result", "output"]) {
1213
2113
  const v = payload[key];
1214
2114
  if (typeof v === "string") {
1215
2115
  const m = _ERROR_EXIT_PREFIX.exec(v);
1216
2116
  if (m?.[1] !== void 0) {
1217
- return { code: Number(m[1]), source: "error_prefix", interrupted: false };
2117
+ return seal({ code: Number(m[1]), source: "error_prefix", interrupted: false });
1218
2118
  }
1219
2119
  continue;
1220
2120
  }
1221
2121
  if (v === null || typeof v !== "object" || Array.isArray(v)) continue;
1222
2122
  const obj = v;
1223
2123
  const nested = _numeric_exit_field(obj);
1224
- if (nested !== null) return { code: nested, source: "field", interrupted: false };
2124
+ if (nested !== null) return seal({ code: nested, source: "field", interrupted: false });
1225
2125
  if (obj["interrupted"] === true) return { code: null, source: null, interrupted: true };
1226
2126
  const hasStream = typeof obj["stdout"] === "string" || typeof obj["stderr"] === "string";
1227
2127
  if (obj["interrupted"] === false && hasStream) {
1228
- return { code: 0, source: "response_shape", interrupted: false };
2128
+ return seal({ code: 0, source: "response_shape", interrupted: false });
1229
2129
  }
1230
2130
  }
1231
2131
  return _NO_EXIT;
@@ -1247,10 +2147,15 @@ function _extract_run_streams(payload) {
1247
2147
  }
1248
2148
  const topOut = payload["stdout"] ?? payload["output"];
1249
2149
  const topErr = payload["stderr"];
1250
- return {
1251
- stdout: typeof topOut === "string" ? topOut : "",
1252
- stderr: typeof topErr === "string" ? topErr : ""
1253
- };
2150
+ if (typeof topOut === "string" || typeof topErr === "string") {
2151
+ return {
2152
+ stdout: typeof topOut === "string" ? topOut : "",
2153
+ stderr: typeof topErr === "string" ? topErr : ""
2154
+ };
2155
+ }
2156
+ const errText = payload["error"];
2157
+ if (typeof errText === "string") return { stdout: errText, stderr: "" };
2158
+ return { stdout: "", stderr: "" };
1254
2159
  }
1255
2160
  function _reset_turn(state, session_id) {
1256
2161
  state["session_id"] = session_id || state["session_id"] || "";
@@ -1259,6 +2164,8 @@ function _reset_turn(state, session_id) {
1259
2164
  state["verified_this_turn"] = false;
1260
2165
  state["verification_runs"] = [];
1261
2166
  state["edits_this_turn"] = 0;
2167
+ delete state["quality_runs"];
2168
+ delete state["quality_files_source"];
1262
2169
  state["ci_saw_pending"] = false;
1263
2170
  state["nonevidence_this_turn"] = 0;
1264
2171
  return state;
@@ -1273,7 +2180,7 @@ function _asInt(v) {
1273
2180
  const n = Number(v);
1274
2181
  return Number.isFinite(n) ? Math.trunc(n) : 0;
1275
2182
  }
1276
- function _update(state, event, envelope) {
2183
+ function _update(state, event, envelope, quality = null) {
1277
2184
  const session_id = envelope["session_id"] || state["session_id"] || "";
1278
2185
  if (session_id && session_id !== state["session_id"]) {
1279
2186
  state["session_id"] = session_id;
@@ -1281,6 +2188,8 @@ function _update(state, event, envelope) {
1281
2188
  state["ci_last"] = null;
1282
2189
  state = _reset_turn(state, session_id);
1283
2190
  }
2191
+ const platform = envelope["platform"];
2192
+ if (typeof platform === "string" && platform) state["platform"] = platform;
1284
2193
  let payload = envelope["payload"];
1285
2194
  if (!(typeof payload === "object" && payload !== null && !Array.isArray(payload))) {
1286
2195
  payload = {};
@@ -1332,7 +2241,7 @@ function _update(state, event, envelope) {
1332
2241
  }
1333
2242
  if (cmd && mightBeVerification(cmd)) {
1334
2243
  const streams = _extract_run_streams(pl);
1335
- const exit = _extract_exit_reading(pl);
2244
+ const exit = _extract_exit_reading(pl, _is_failure_event(pl, envelope));
1336
2245
  const runs = Array.isArray(state["verification_runs"]) ? [...state["verification_runs"]] : [];
1337
2246
  runs.push({
1338
2247
  command: cmd.slice(0, 512),
@@ -1356,6 +2265,10 @@ function _update(state, event, envelope) {
1356
2265
  }
1357
2266
  } else if (event === "stop") {
1358
2267
  state["last_stop_at"] = _now();
2268
+ if (quality !== null) {
2269
+ state["quality_runs"] = quality.runs;
2270
+ state["quality_files_source"] = quality.source;
2271
+ }
1359
2272
  }
1360
2273
  state["checked_at"] = _now();
1361
2274
  return state;
@@ -1384,10 +2297,12 @@ function run(stdin_text, options) {
1384
2297
  }
1385
2298
  return 0;
1386
2299
  }
1387
- const target = path.join(consumer_root, statePathFor(session_id));
2300
+ const target = path4.join(consumer_root, statePathFor(session_id));
2301
+ const quality_mode = event === "stop" ? readTouchedFileQualityMode(consumer_root) : "off";
2302
+ const quality = quality_mode === "off" ? null : collectTouchedFileQuality(consumer_root, session_id);
1388
2303
  let state = {};
1389
2304
  const outcome = update_json_under_lock(target, (loaded) => {
1390
- state = _update({ ..._empty_state(), ...loaded }, event, envelope);
2305
+ state = _update({ ..._empty_state(), ...loaded }, event, envelope, quality);
1391
2306
  return state;
1392
2307
  });
1393
2308
  if (outcome === "failed") {
@@ -1397,13 +2312,20 @@ function run(stdin_text, options) {
1397
2312
  return 0;
1398
2313
  }
1399
2314
  if (event === "session_start" || event === "user_prompt_submit") {
1400
- prune_legacy_state_file(path.join(consumer_root, STATE_FILE));
2315
+ prune_legacy_state_file(path4.join(consumer_root, STATE_FILE));
1401
2316
  prune_stale_session_states(
1402
- path.join(consumer_root, STATE_DIR),
2317
+ path4.join(consumer_root, STATE_DIR),
1403
2318
  Date.now(),
1404
2319
  STATE_RETENTION_DAYS
1405
2320
  );
1406
2321
  }
2322
+ if (quality_mode === "warn" && quality !== null) {
2323
+ const line = advisoryLine(quality.runs);
2324
+ if (line !== null) {
2325
+ process.stderr.write(`${line}
2326
+ `);
2327
+ }
2328
+ }
1407
2329
  if (verbose) {
1408
2330
  process.stderr.write(
1409
2331
  `verify-before-complete-hook: event=${event} verified_this_turn=${pyRepr(state["verified_this_turn"])} verifications_this_turn=${pyRepr(state["verifications_this_turn"])}
@@ -1447,7 +2369,7 @@ function main(argv) {
1447
2369
  function readTurnRunState(workspaceRoot, session_id) {
1448
2370
  if (!has_stable_session_id(session_id)) return null;
1449
2371
  try {
1450
- const raw = fs.readFileSync(path.join(workspaceRoot, statePathFor(session_id)), "utf-8");
2372
+ const raw = fs4.readFileSync(path4.join(workspaceRoot, statePathFor(session_id)), "utf-8");
1451
2373
  const decoded = JSON.parse(raw);
1452
2374
  if (!owns_session_state(decoded, session_id)) return null;
1453
2375
  if (typeof decoded !== "object" || decoded === null || Array.isArray(decoded)) return null;
@@ -1468,13 +2390,13 @@ function _isCliEntry() {
1468
2390
  if (process.argv[1] === void 0) {
1469
2391
  return false;
1470
2392
  }
1471
- const argvUrl = pathToFileURL(path.resolve(process.argv[1])).href;
2393
+ const argvUrl = pathToFileURL(path4.resolve(process.argv[1])).href;
1472
2394
  if (import.meta.url === argvUrl) {
1473
2395
  return true;
1474
2396
  }
1475
2397
  try {
1476
- const here = fs.realpathSync(fileURLToPath(import.meta.url));
1477
- const argv = fs.realpathSync(path.resolve(process.argv[1]));
2398
+ const here = fs4.realpathSync(fileURLToPath(import.meta.url));
2399
+ const argv = fs4.realpathSync(path4.resolve(process.argv[1]));
1478
2400
  return here === argv;
1479
2401
  } catch {
1480
2402
  return false;
@@ -1487,8 +2409,8 @@ if (isCliEntry) {
1487
2409
 
1488
2410
  // src/scripts/_lib/turn_end_refusals.ts
1489
2411
  import * as crypto from "node:crypto";
1490
- import * as fs2 from "node:fs";
1491
- import * as path2 from "node:path";
2412
+ import * as fs5 from "node:fs";
2413
+ import * as path5 from "node:path";
1492
2414
  var DETECTOR_IDS = [
1493
2415
  "promissory",
1494
2416
  "language",
@@ -1497,19 +2419,19 @@ var DETECTOR_IDS = [
1497
2419
  "pending-decision",
1498
2420
  "untested"
1499
2421
  ];
1500
- var REFUSAL_STATE_REL = path2.join("agents", "runtime", "state", "turn-end-gate");
2422
+ var REFUSAL_STATE_REL = path5.join("agents", "runtime", "state", "turn-end-gate");
1501
2423
  function refusalStateDir(workspaceRoot) {
1502
- return path2.join(workspaceRoot, REFUSAL_STATE_REL);
2424
+ return path5.join(workspaceRoot, REFUSAL_STATE_REL);
1503
2425
  }
1504
2426
  function deriveSessionKey(sessionId) {
1505
2427
  return crypto.createHash("sha256").update(sessionId).digest("hex").slice(0, 32);
1506
2428
  }
1507
2429
  function sessionRefusalFile(workspaceRoot, sessionKey) {
1508
- return path2.join(refusalStateDir(workspaceRoot), `${sessionKey}.json`);
2430
+ return path5.join(refusalStateDir(workspaceRoot), `${sessionKey}.json`);
1509
2431
  }
1510
2432
  function readSessionCounts(workspaceRoot, sessionId) {
1511
2433
  try {
1512
- const raw = fs2.readFileSync(
2434
+ const raw = fs5.readFileSync(
1513
2435
  sessionRefusalFile(workspaceRoot, deriveSessionKey(sessionId)),
1514
2436
  "utf-8"
1515
2437
  );
@@ -1620,7 +2542,7 @@ function emptyRetryCounts() {
1620
2542
  var SHADOW_MAX_ROWS = 200;
1621
2543
  var SHADOW_SUFFIX = ".shadow.json";
1622
2544
  function sessionShadowFile(workspaceRoot, sessionKey) {
1623
- return path2.join(refusalStateDir(workspaceRoot), `${sessionKey}${SHADOW_SUFFIX}`);
2545
+ return path5.join(refusalStateDir(workspaceRoot), `${sessionKey}${SHADOW_SUFFIX}`);
1624
2546
  }
1625
2547
  function parseShadowRecord(raw) {
1626
2548
  let decoded;
@@ -1645,7 +2567,9 @@ function parseShadowRecord(raw) {
1645
2567
  if (typeof turn !== "number" || !Number.isFinite(turn)) continue;
1646
2568
  if (typeof at !== "string") continue;
1647
2569
  if (layer !== "stop_hook_active" && layer !== "refused_turn") continue;
1648
- rows.push({ detector: det, turn, at, layer });
2570
+ const setByRaw = ro["set_by"];
2571
+ const set_by = setByRaw === "ours" || setByRaw === "foreign" || setByRaw === "unknown" ? setByRaw : "unknown";
2572
+ rows.push({ detector: det, turn, at, layer, set_by });
1649
2573
  }
1650
2574
  const num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
1651
2575
  const observed = o["retries_observed"];
@@ -1665,8 +2589,15 @@ function parseShadowRecord(raw) {
1665
2589
  }
1666
2590
  function foldShadow(prev, input) {
1667
2591
  const rows = [...prev?.would_refuse_again ?? []];
2592
+ const set_by = input.setBy ?? "unknown";
1668
2593
  for (const d of input.detectors) {
1669
- rows.push({ detector: d, turn: input.turnOrdinal, at: input.at, layer: input.layer });
2594
+ rows.push({
2595
+ detector: d,
2596
+ turn: input.turnOrdinal,
2597
+ at: input.at,
2598
+ layer: input.layer,
2599
+ set_by
2600
+ });
1670
2601
  }
1671
2602
  let dropped = prev?.dropped ?? 0;
1672
2603
  if (rows.length > SHADOW_MAX_ROWS) {
@@ -1684,27 +2615,37 @@ function foldShadow(prev, input) {
1684
2615
  last_at: input.at
1685
2616
  };
1686
2617
  }
2618
+ function shadowSetBy(workspaceRoot, sessionKey) {
2619
+ let raw;
2620
+ try {
2621
+ raw = fs5.readFileSync(sessionRefusalFile(workspaceRoot, sessionKey), "utf-8");
2622
+ } catch (err) {
2623
+ const code = err?.code;
2624
+ return code === "ENOENT" ? "foreign" : "unknown";
2625
+ }
2626
+ return parseRecord(raw) === null ? "unknown" : "ours";
2627
+ }
1687
2628
  var REFUSAL_STATE_MAX_AGE_DAYS = 90;
1688
2629
  function pruneAgedRefusalState(workspaceRoot, opts = {}) {
1689
2630
  const maxAgeDays = opts.maxAgeDays ?? REFUSAL_STATE_MAX_AGE_DAYS;
1690
- const now = opts.now ?? /* @__PURE__ */ new Date();
2631
+ const now2 = opts.now ?? /* @__PURE__ */ new Date();
1691
2632
  const dir = refusalStateDir(workspaceRoot);
1692
2633
  const result = { scanned: 0, pruned: 0, kept: 0 };
1693
2634
  let entries;
1694
2635
  try {
1695
- entries = fs2.readdirSync(dir);
2636
+ entries = fs5.readdirSync(dir);
1696
2637
  } catch {
1697
2638
  return result;
1698
2639
  }
1699
- const cutoffMs = now.getTime() - maxAgeDays * 24 * 60 * 60 * 1e3;
2640
+ const cutoffMs = now2.getTime() - maxAgeDays * 24 * 60 * 60 * 1e3;
1700
2641
  for (const name of entries) {
1701
2642
  if (!name.endsWith(".json")) continue;
1702
2643
  result.scanned += 1;
1703
- const file = path2.join(dir, name);
2644
+ const file = path5.join(dir, name);
1704
2645
  if (name.endsWith(SHADOW_SUFFIX)) {
1705
2646
  let shadow = null;
1706
2647
  try {
1707
- shadow = parseShadowRecord(fs2.readFileSync(file, "utf-8"));
2648
+ shadow = parseShadowRecord(fs5.readFileSync(file, "utf-8"));
1708
2649
  } catch {
1709
2650
  shadow = null;
1710
2651
  }
@@ -1712,7 +2653,7 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
1712
2653
  if (!Number.isFinite(shadowAt)) {
1713
2654
  let mtime = NaN;
1714
2655
  try {
1715
- mtime = fs2.statSync(file).mtimeMs;
2656
+ mtime = fs5.statSync(file).mtimeMs;
1716
2657
  } catch {
1717
2658
  mtime = NaN;
1718
2659
  }
@@ -1725,7 +2666,7 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
1725
2666
  continue;
1726
2667
  }
1727
2668
  try {
1728
- fs2.unlinkSync(file);
2669
+ fs5.unlinkSync(file);
1729
2670
  result.pruned += 1;
1730
2671
  } catch {
1731
2672
  result.kept += 1;
@@ -1734,7 +2675,7 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
1734
2675
  }
1735
2676
  let rec = null;
1736
2677
  try {
1737
- rec = parseRecord(fs2.readFileSync(file, "utf-8"));
2678
+ rec = parseRecord(fs5.readFileSync(file, "utf-8"));
1738
2679
  } catch {
1739
2680
  rec = null;
1740
2681
  }
@@ -1748,7 +2689,7 @@ function pruneAgedRefusalState(workspaceRoot, opts = {}) {
1748
2689
  continue;
1749
2690
  }
1750
2691
  try {
1751
- fs2.unlinkSync(file);
2692
+ fs5.unlinkSync(file);
1752
2693
  result.pruned += 1;
1753
2694
  } catch {
1754
2695
  result.kept += 1;
@@ -1783,5 +2724,6 @@ export {
1783
2724
  sessionShadowFile,
1784
2725
  parseShadowRecord,
1785
2726
  foldShadow,
2727
+ shadowSetBy,
1786
2728
  pruneAgedRefusalState
1787
2729
  };