@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
@@ -237,9 +237,30 @@ export function classifyPoll(stdout: string, stderr: string, status: number | nu
237
237
  return { kind: 'pending', total: rows.length, done };
238
238
  }
239
239
  const bad = new Set(['FAILURE', 'TIMED_OUT', 'CANCELLED', 'ACTION_REQUIRED', 'STARTUP_FAILURE']);
240
- const failing = rows
241
- .filter((r) => bad.has(String(r.conclusion).toUpperCase()))
242
- .map((r) => String(r.name ?? '?'));
240
+ // A concurrency group that supersedes its own earlier run leaves TWO rows
241
+ // under one name: the superseded one CANCELLED, the one that counts
242
+ // SUCCESS. The forge reports that check green; this classifier reported the
243
+ // whole PR red, because CANCELLED sat in `bad` and nothing looked at the
244
+ // twin. Measured 2026-10-01 on PR #2135 (`lint commit subjects`), where the
245
+ // false red cost a run a diagnosis it did not owe.
246
+ //
247
+ // The reprieve is deliberately the narrowest one that fixes it: CANCELLED
248
+ // only, and only when a row of the SAME NAME concluded SUCCESS. A genuine
249
+ // cancellation — a human stopping a run, a timeout killing one — has no
250
+ // successful twin and stays failing. Widening this to every bad conclusion,
251
+ // or to CANCELLED unconditionally, would trade a false red for a false
252
+ // green, which is the worse error in a tool whose output is acted on.
253
+ const nameOf = (r: (typeof rows)[number]): string => String(r.name ?? '?');
254
+ const concl = (r: (typeof rows)[number]): string => String(r.conclusion).toUpperCase();
255
+ const succeeded = new Set(rows.filter((r) => concl(r) === 'SUCCESS').map(nameOf));
256
+ const failing = [
257
+ ...new Set(
258
+ rows
259
+ .filter((r) => bad.has(concl(r)))
260
+ .filter((r) => !(concl(r) === 'CANCELLED' && succeeded.has(nameOf(r))))
261
+ .map(nameOf),
262
+ ),
263
+ ];
243
264
  return { kind: 'settled', failing, total: rows.length };
244
265
  }
245
266
 
@@ -293,25 +314,131 @@ function sleepSync(seconds: number): void {
293
314
  });
294
315
  }
295
316
 
296
- export function main(argv: readonly string[]): number {
297
- const positional = argv.filter((a) => !a.startsWith('--'));
298
- const pr = positional[0];
299
- if (pr === undefined) {
300
- process.stderr.write(
301
- 'usage: ci_settle <pr> [--timeout-min N] [--interval-sec N]\n' +
302
- ` default --timeout-min is ${String(FOREGROUND_CEILING_MIN)}, which fits inside one foreground Bash call (600 s cap).\n` +
303
- ' a longer wait is a BACKGROUND job, not a bigger number — a foreground call past the cap is killed and reports nothing.\n',
317
+ /** The only flags this waiter honours. Anything else is a caller mistake. */
318
+ const KNOWN_FLAGS = ['--timeout-min', '--interval-sec'] as const;
319
+
320
+ const USAGE =
321
+ 'usage: ci_settle <pr> [--timeout-min N] [--interval-sec N]\n' +
322
+ ` default --timeout-min is ${String(FOREGROUND_CEILING_MIN)}, which fits inside one foreground Bash call (600 s cap).\n` +
323
+ ' a longer wait is a BACKGROUND job, not a bigger number — a foreground call past the cap is killed and reports nothing.\n';
324
+
325
+ export type ParsedArgs =
326
+ | { kind: 'ok'; pr: string; timeoutMin: number; intervalSec: number }
327
+ | { kind: 'usage'; message: string };
328
+
329
+ /**
330
+ * Parse argv, REFUSING anything this waiter would not honour.
331
+ *
332
+ * Refusal rather than a default is the whole point. The previous parser read
333
+ * its two knobs with `argv.indexOf(flag)` and filtered positionals on a leading
334
+ * `--`, so a caller who wrote the near-universal `--timeout 1700` got two
335
+ * silent substitutions at once: the unknown flag was ignored, and its value —
336
+ * carrying no dashes — survived as a second positional and was dropped. The
337
+ * wait then ran for the 9-minute default while the caller believed they had
338
+ * asked for 28, and the resulting `DID NOT SETTLE` read as a slow CI rather
339
+ * than as their own typo.
340
+ *
341
+ * That is the one failure a waiter must not have. Its entire product is a
342
+ * verdict somebody will act on, and a verdict produced under arguments the
343
+ * caller did not actually give is worse than no verdict, because it looks like
344
+ * one. So every unrecognised flag, every unusable value and every extra
345
+ * positional is a usage refusal, exit 2 — the code this tool already reserves
346
+ * for "no verdict is claimed".
347
+ *
348
+ * `--flag=value` is accepted beside `--flag value` because it is the other
349
+ * spelling a caller reaches for, and refusing it would trade one silent
350
+ * surprise for a noisy one with no safety gained.
351
+ */
352
+ export function parseArgs(argv: readonly string[]): ParsedArgs {
353
+ const refuse = (why: string): ParsedArgs => ({ kind: 'usage', message: `ci_settle: ${why}\n${USAGE}` });
354
+
355
+ const positional: string[] = [];
356
+ const given = new Map<string, string>();
357
+
358
+ for (let i = 0; i < argv.length; i++) {
359
+ const a = argv[i] as string;
360
+ if (!a.startsWith('--')) {
361
+ positional.push(a);
362
+ continue;
363
+ }
364
+ const eq = a.indexOf('=');
365
+ const name = eq === -1 ? a : a.slice(0, eq);
366
+ if (!(KNOWN_FLAGS as readonly string[]).includes(name)) {
367
+ return refuse(
368
+ `unknown flag ${name}. This waiter spells its deadline --timeout-min (in MINUTES), not --timeout`,
369
+ );
370
+ }
371
+ if (given.has(name)) return refuse(`${name} given twice`);
372
+ if (eq !== -1) {
373
+ given.set(name, a.slice(eq + 1));
374
+ continue;
375
+ }
376
+ const v = argv[i + 1];
377
+ if (v === undefined || v.startsWith('--')) return refuse(`${name} needs a value`);
378
+ given.set(name, v);
379
+ i++;
380
+ }
381
+
382
+ const raw = positional[0];
383
+ if (raw === undefined) return refuse('no PR number given');
384
+ if (positional.length > 1) {
385
+ return refuse(
386
+ `unexpected argument ${JSON.stringify(positional[1])} — this tool takes exactly one positional, the PR number; ` +
387
+ 'a flag value must follow its own flag',
304
388
  );
305
- return 2;
306
389
  }
307
- const num = (flag: string, dflt: number): number => {
308
- const i = argv.indexOf(flag);
309
- if (i === -1) return dflt;
310
- const v = argv[i + 1];
311
- const n = v === undefined ? NaN : parseInt(v, 10);
312
- return Number.isFinite(n) && n > 0 ? n : dflt;
390
+
391
+ // Finding 1 of the 2026-10-01 R2 review: the first version of this guard
392
+ // validated every OPTIONAL argument and waved the mandatory one through, so
393
+ // the failure it exists to remove stayed reachable through the front door.
394
+ // `ci_settle PR-2130` parsed as ok; `gh pr view PR-2130` then exits non-zero
395
+ // on every poll, each one classifies as unreadable, no streak caps the loop,
396
+ // and the run spends its entire budget to arrive at `DID NOT SETTLE` — which
397
+ // is read as a slow CI rather than as the typo it is. A guard that refuses
398
+ // the arguments a caller may omit and accepts the one they cannot is not a
399
+ // guard. `#123` and a PR URL are admitted because both are what a caller
400
+ // copies out of the forge; a branch name is NOT, because this tool's whole
401
+ // verdict is bound to one head and a branch makes that binding ambiguous.
402
+ const pr = /^#?[0-9]+$/.test(raw)
403
+ ? raw.replace(/^#/, '')
404
+ : (/^https?:\/\/[^\s]*\/pull\/([0-9]+)(?:[/?#].*)?$/.exec(raw)?.[1] ?? null);
405
+ if (pr === null || pr === '0' || /^0+$/.test(pr)) {
406
+ return refuse(
407
+ `${JSON.stringify(raw)} is not a PR number — give the number itself (2130), #2130, or the pull-request URL`,
408
+ );
409
+ }
410
+
411
+ const num = (flag: string, dflt: number): number | string => {
412
+ const v = given.get(flag);
413
+ if (v === undefined) return dflt;
414
+ const unit = flag === '--timeout-min' ? 'minutes' : 'seconds';
415
+ if (!/^[0-9]+$/.test(v)) return `${flag} wants a whole number of ${unit}, got ${JSON.stringify(v)}`;
416
+ const n = parseInt(v, 10);
417
+ // Finding 2: the parser this replaced guarded with `Number.isFinite`, and
418
+ // the digits-only test that replaced it does not. A long enough run of
419
+ // digits parses to Infinity, clears `n > 0`, and makes `deadline`
420
+ // unreachable — an unbounded wait in a tool whose contract is that it
421
+ // always returns a stated verdict or a stated non-verdict.
422
+ if (!Number.isFinite(n)) return `${flag} is too large to be a number of ${unit}`;
423
+ if (n <= 0) return `${flag} wants a positive number, got ${v}`;
424
+ return n;
313
425
  };
426
+
314
427
  const timeoutMin = num('--timeout-min', FOREGROUND_CEILING_MIN);
428
+ if (typeof timeoutMin === 'string') return refuse(timeoutMin);
429
+ const intervalSec = num('--interval-sec', 60);
430
+ if (typeof intervalSec === 'string') return refuse(intervalSec);
431
+
432
+ return { kind: 'ok', pr, timeoutMin, intervalSec };
433
+ }
434
+
435
+ export function main(argv: readonly string[]): number {
436
+ const parsed = parseArgs(argv);
437
+ if (parsed.kind === 'usage') {
438
+ process.stderr.write(parsed.message);
439
+ return 2;
440
+ }
441
+ const { pr, timeoutMin, intervalSec } = parsed;
315
442
  if (timeoutMin > FOREGROUND_CEILING_MIN) {
316
443
  // Said once, up front, rather than discovered when the call is killed:
317
444
  // a truncated wait produces no line at all, so the warning has to come
@@ -322,7 +449,6 @@ export function main(argv: readonly string[]): number {
322
449
  'the deadline and will report nothing — run it as a background job for a wait this long.\n',
323
450
  );
324
451
  }
325
- const intervalSec = num('--interval-sec', 60);
326
452
 
327
453
  // Up front, because waiting nine minutes to learn the PR was merged before
328
454
  // the wait began is the expensive way to find out.
@@ -19,6 +19,8 @@
19
19
  * `unpicked-alternative` "either X or Y", "option A … option B"
20
20
  * `unchecked-assumption` "assuming", "presumably", "probably fine"
21
21
  * `missing-verify` an open step with no `verify:` line
22
+ * `unfalsifiable-verify` a step WITH a `verify:` line whose oracle
23
+ * cannot say no
22
24
  * `ambiguous-acceptance` a vague acceptance criterion (improve / clean up
23
25
  * / best practices / handle errors properly)
24
26
  * `contradictory` "contradicts", "conflicts with", "but also"
@@ -35,6 +37,24 @@
35
37
  * Exit codes: 0 no open decisions · 1 open decisions found · 2 bad argv.
36
38
  * Finding open decisions is the POINT, so `--strict` is what makes exit 1 a
37
39
  * failure for a caller that wants one; the default is a report.
40
+ *
41
+ * ONE FAMILY IS A LISTING AND NEVER A GATE. `unfalsifiable-verify` exits 0
42
+ * unconditionally — under `--strict` too — and that is a property of the
43
+ * family, not a flag a caller can flip. The reason is the one this script's
44
+ * own header already records about a detector that fires on ordinary prose,
45
+ * sharpened by a measurement: on 2026-09-29 the active roadmap tree carried
46
+ * 230 clauses and 6 machine-decidable expectations — 97.4% of them state no
47
+ * oracle — so a family promoted to a gate would red all but a handful of
48
+ * roadmaps at once, and the cheapest repair for a gate that reds on
49
+ * everything is to weaken it until it finds nothing. The exemption is
50
+ * narrow: a `tbd` in the same corpus still goes red under `--strict`.
51
+ *
52
+ * The figure is the MEASURED one, not the commissioning roadmap's. That
53
+ * roadmap asserted zero expectations tree-wide; a reading refuted it — six
54
+ * exist, all six written before the grammar was legal, and three of them are
55
+ * that roadmap's own verify lines. The producing
56
+ * command is `./scripts-run src/scripts/roadmap_verify_share`, so the number
57
+ * above is re-derivable rather than remembered.
38
58
  */
39
59
 
40
60
  import * as fs from 'node:fs';
@@ -42,6 +62,8 @@ import * as path from 'node:path';
42
62
  import process from 'node:process';
43
63
  import { fileURLToPath } from 'node:url';
44
64
 
65
+ import { parseVerifyClause, type VerifyClause } from './_lib/verify_clause.js';
66
+
45
67
  const _HERE = fileURLToPath(import.meta.url);
46
68
  const _PROG = 'closure_scan';
47
69
 
@@ -50,6 +72,7 @@ export type Kind =
50
72
  | 'unpicked-alternative'
51
73
  | 'unchecked-assumption'
52
74
  | 'missing-verify'
75
+ | 'unfalsifiable-verify'
53
76
  | 'ambiguous-acceptance'
54
77
  | 'contradictory'
55
78
  | 'product-semantics'
@@ -74,6 +97,7 @@ export const OWNERSHIP_BY_KIND: Readonly<Record<Kind, string>> = {
74
97
  'unpicked-alternative': 'contested-technical',
75
98
  'unchecked-assumption': 'reversible-technical',
76
99
  'missing-verify': 'deterministic',
100
+ 'unfalsifiable-verify': 'deterministic',
77
101
  'ambiguous-acceptance': 'reversible-technical',
78
102
  contradictory: 'contested-technical',
79
103
  'product-semantics': 'product-owned',
@@ -88,6 +112,73 @@ export const OWNER_ROUTED: ReadonlySet<string> = new Set([
88
112
  'destructive-owned',
89
113
  ]);
90
114
 
115
+ /**
116
+ * Families that report and never gate — `--strict` cannot make these exit 1.
117
+ *
118
+ * A set rather than a flag, so the exemption is a property of the family that a
119
+ * caller cannot accidentally promote. See the header for the measurement that
120
+ * makes promotion a bad trade today.
121
+ */
122
+ export const NON_BLOCKING_KINDS: ReadonlySet<Kind> = new Set<Kind>(['unfalsifiable-verify']);
123
+
124
+ /**
125
+ * Command heads whose exit status is decided by something other than the
126
+ * property a step is claiming.
127
+ *
128
+ * Deliberately short. `echo`, `true` and `printf` cannot fail at all; `cat`,
129
+ * `ls`, `pwd` and `date` fail only on a missing path or a broken clock, never
130
+ * on the CONTENT a step asserts. Anything whose exit code already tracks the
131
+ * claim — `grep` (1 on no match), a linter, a test runner — is absent on
132
+ * purpose: including it would turn this listing into a census of every verify
133
+ * line in the tree, which is step 2.2's instrument, not this one.
134
+ */
135
+ const FIXED_OUTPUT_HEADS: ReadonlySet<string> = new Set(['echo', 'true', 'printf', 'cat', 'ls', 'pwd', 'date']);
136
+
137
+ /**
138
+ * Why this clause's oracle cannot say no — or `null` when it can.
139
+ *
140
+ * Three shapes, each one a way of carrying a `verify:` line that reads as
141
+ * verification and decides nothing.
142
+ *
143
+ * THE COUNT IS A LOWER BOUND, and saying so is the point. `scan()` assigns at
144
+ * most one kind per unit and the line patterns run first, so a step that also
145
+ * says "assuming", "merge" or "deploy" is claimed by the broader family and
146
+ * never reaches this one. Measured on 2026-09-29: 12 of 50 candidates were
147
+ * shadowed that way, so the published listing under-reports by roughly a
148
+ * quarter. A reader told a number and not told it is a floor would read the
149
+ * remainder as clean, which is the same mistake as reporting a share off a
150
+ * corpus nobody scanned.
151
+ */
152
+ export function unfalsifiableReason(clause: VerifyClause): string | null {
153
+ if (clause.command === null) {
154
+ // A MANUAL clause asserting a QUANTITY nothing produced. Narrowed to a
155
+ // multi-digit number or an explicit `N of`/`N%` shape: a lone digit is
156
+ // usually a step id or an exit code being discussed, and firing on
157
+ // those would bury the real ones.
158
+ const quantity = /\b\d{2,}\b/.test(clause.raw) || /\b\d+\s*(?:%|of\b)/.test(clause.raw);
159
+ return quantity ? 'a manual clause asserts a quantity nothing here produces' : null;
160
+ }
161
+
162
+ const head = (clause.command.trim().split(/\s+/)[0] ?? '').split('/').pop() ?? '';
163
+ if (FIXED_OUTPUT_HEADS.has(head) && clause.expect?.kind !== 'regex') {
164
+ // A regex expectation rescues a fixed-output head: `cat f -> /x/` does
165
+ // decide something. An exit expectation does not — naming exit 0 on a
166
+ // command that cannot exit anything else adds a symbol, not an oracle.
167
+ return `the command head \`${head}\` produces a fixed result, so the clause cannot fail`;
168
+ }
169
+
170
+ if (clause.expect?.kind === 'regex') {
171
+ try {
172
+ if (new RegExp(clause.expect.source).test(clause.command)) {
173
+ return 'the expectation matches the command text, so an echo of the command satisfies it';
174
+ }
175
+ } catch {
176
+ // An expectation that cannot compile never became one upstream.
177
+ }
178
+ }
179
+ return null;
180
+ }
181
+
91
182
  interface Pattern {
92
183
  readonly kind: Kind;
93
184
  readonly re: RegExp;
@@ -145,6 +236,17 @@ export interface Unit {
145
236
  /** 1-based line of the unit's first line. */
146
237
  readonly line: number;
147
238
  readonly text: string;
239
+ /**
240
+ * The same block with its NEWLINES intact.
241
+ *
242
+ * `text` is joined with spaces so a wrapped pattern still matches across
243
+ * the wrap, and that is right for the line patterns. It is wrong for the
244
+ * `verify:` clause: with no newline left, "to end of line" means "to end of
245
+ * the step", so a manual clause inherited every digit in the evidence block
246
+ * written under it. Measured before this field existed: 46 of 50
247
+ * `unfalsifiable-verify` hits fired on a quantity the clause never wrote.
248
+ */
249
+ readonly blockText: string;
148
250
  readonly inAcceptance: boolean;
149
251
  readonly isOpenStep: boolean;
150
252
  readonly hasVerify: boolean;
@@ -188,6 +290,7 @@ export function units(lines: readonly string[]): Unit[] {
188
290
  // written with single spaces stops matching across the wrap —
189
291
  // which is the case this unit exists to catch.
190
292
  text: block.join(' ').replace(/\s+/g, ' '),
293
+ blockText: block.join('\n'),
191
294
  inAcceptance,
192
295
  isOpenStep: OPEN_STEP_RE.test(line),
193
296
  hasVerify: /(?:^|\s)verify:/m.test(block.join('\n')),
@@ -198,6 +301,7 @@ export function units(lines: readonly string[]): Unit[] {
198
301
  out.push({
199
302
  line: i + 1,
200
303
  text: line,
304
+ blockText: line,
201
305
  inAcceptance,
202
306
  isOpenStep: false,
203
307
  hasVerify: false,
@@ -227,6 +331,14 @@ export function scan(text: string): Finding[] {
227
331
  if (matched === null && u.isOpenStep && !u.hasVerify && !u.inAcceptance) {
228
332
  matched = 'missing-verify';
229
333
  }
334
+ // A step that HAS a verify line, whose oracle still cannot say no.
335
+ // Checked on closed steps too — a `[x]` flipped on a command that
336
+ // could not fail is the defect in its completed form, and restricting
337
+ // this to open steps would hide every instance that already landed.
338
+ if (matched === null && u.hasVerify && !u.inAcceptance) {
339
+ const clause = parseVerifyClause(u.blockText);
340
+ if (clause !== null && unfalsifiableReason(clause) !== null) matched = 'unfalsifiable-verify';
341
+ }
230
342
  if (matched === null) continue;
231
343
  findings.push({
232
344
  id: '',
@@ -316,7 +428,9 @@ export function main(argv?: readonly string[]): number {
316
428
  }
317
429
  }
318
430
  if (!findings.length) return 0;
319
- return strict ? 1 : 0;
431
+ // `--strict` goes red on the families that gate, never on the listing.
432
+ const blocking = findings.filter((f) => !NON_BLOCKING_KINDS.has(f.kind));
433
+ return strict && blocking.length > 0 ? 1 : 0;
320
434
  }
321
435
 
322
436
  function _isCliEntry(): boolean {
@@ -17,7 +17,9 @@ import { execFileSync } from 'node:child_process';
17
17
  import * as fs from 'node:fs';
18
18
  import * as path from 'node:path';
19
19
 
20
+ import { load_agent_settings } from '../_lib/agent_settings.js';
20
21
  import { hardenedSpawnEnv } from '../_lib/spawn_env.js';
22
+ import { EXT_LANG } from './types.js';
21
23
  import { validateGraph } from './validate.js';
22
24
 
23
25
  export type SourceKind = 'consumer' | 'scip' | 'native';
@@ -33,6 +35,44 @@ export interface SourceVerdict {
33
35
 
34
36
  const CONSUMER_CANDIDATES = ['graph.json', 'code-graph.json', '.code-graph/graph.json'];
35
37
 
38
+ /**
39
+ * Extra graph paths the CONSUMER declares, from `code_graph.consumer_index_paths`.
40
+ *
41
+ * The default list above stays generic on purpose — three filenames, none of
42
+ * them any particular tool's. A tool that writes its index somewhere else is
43
+ * reachable by the consumer naming the path, never by this package shipping a
44
+ * table of vendor locations: such a table is wrong the day a tool moves its
45
+ * file and wrong forever about one nobody thought of, and
46
+ * `check_no_external_sources` would fail the build for carrying it.
47
+ *
48
+ * Absolute paths and anything escaping the project root are dropped. A setting
49
+ * that could point the walk at `/etc` would be a path-traversal surface in a
50
+ * read a consumer did not ask for.
51
+ */
52
+ export function consumerCandidates(root: string): string[] {
53
+ let declared: unknown;
54
+ try {
55
+ const settings = load_agent_settings({ cwd: root }) ?? {};
56
+ const section = (settings as Record<string, unknown>)['code_graph'];
57
+ declared =
58
+ section !== null && typeof section === 'object' && !Array.isArray(section)
59
+ ? (section as Record<string, unknown>)['consumer_index_paths']
60
+ : undefined;
61
+ } catch {
62
+ declared = undefined;
63
+ }
64
+ const extra = Array.isArray(declared)
65
+ ? declared.filter(
66
+ (p): p is string =>
67
+ typeof p === 'string' &&
68
+ p !== '' &&
69
+ !path.isAbsolute(p) &&
70
+ !path.normalize(p).startsWith('..'),
71
+ )
72
+ : [];
73
+ return [...CONSUMER_CANDIDATES, ...extra.filter((p) => !CONSUMER_CANDIDATES.includes(p))];
74
+ }
75
+
36
76
  function gitLastCommitEpoch(root: string): number | null {
37
77
  try {
38
78
  const out = execFileSync('git', ['-C', root, 'log', '-1', '--format=%ct'], {
@@ -100,7 +140,7 @@ export function detectSources(root: string, nativeCache: string): SourceVerdict[
100
140
  const abs = path.resolve(root);
101
141
  const out: SourceVerdict[] = [];
102
142
 
103
- for (const rel of CONSUMER_CANDIDATES) {
143
+ for (const rel of consumerCandidates(abs)) {
104
144
  const p = path.join(abs, rel);
105
145
  if (fs.existsSync(p)) {
106
146
  const { ok, sha } = looksLikeGraph(p);
@@ -189,7 +229,12 @@ export function computeVerdict(root: string, nativeCache: string): VerdictJSON {
189
229
  export const NATIVE_CACHE_REL = path.join('agents', 'runtime', 'state', 'code-graph-v1.json');
190
230
 
191
231
  /**
192
- * The three-state staleness token every consumer of the graph reports.
232
+ * The four-state staleness token every consumer of the graph reports.
233
+ *
234
+ * `edited` is the fourth and newest (`road-to-a-graph-that-feeds-the-gate` 2.3).
235
+ * Without it an uncommitted change to an indexed file read as `fresh` — the one
236
+ * state that says "nothing stands between this answer and the tree" — which is
237
+ * the reading most likely to be wrong while someone is editing.
193
238
  *
194
239
  * Lives HERE rather than in the PreToolUse hook that first needed it
195
240
  * (`hooks/code_graph_context_hook.ts`, step 1.2). Phase 3's verbs must each
@@ -198,7 +243,73 @@ export const NATIVE_CACHE_REL = path.join('agents', 'runtime', 'state', 'code-gr
198
243
  * the engine, never the other way round. The hook re-exports this so its own
199
244
  * consumers are unaffected.
200
245
  */
201
- export type GraphState = 'absent' | 'fresh' | `behind:${number}`;
246
+ export type GraphState = 'absent' | 'fresh' | 'edited' | `behind:${number}`;
247
+
248
+ /**
249
+ * The working tree carries an uncommitted change to a file the extractor would
250
+ * index.
251
+ *
252
+ * WHY POINT-IN-TIME PORCELAIN AND NOT THE ARTIFACT'S MTIME. An mtime comparison
253
+ * looks cheaper and is wrong in the one direction that matters: restoring a
254
+ * stashed or checked-out file rewrites its mtime, so a tree put back exactly as
255
+ * the graph saw it would read stale forever and never return to `fresh`. The
256
+ * porcelain result describes a CONTENT difference, so a revert is visible as a
257
+ * revert.
258
+ *
259
+ * "Indexed paths" is read as *paths of the kind the extractor indexes* —
260
+ * `EXT_LANG`'s extensions — not as the node set of a loaded graph. Loading the
261
+ * graph to answer a freshness question would mean opening the index on every
262
+ * PreToolUse call this now runs on, which is the latency the Risk Register
263
+ * names; the extension filter answers the same question with no read. It can
264
+ * over-report (a `.ts` file the build excluded) and cannot under-report, and
265
+ * over-reporting `edited` only ever makes an answer look worth less than it is.
266
+ *
267
+ * Untracked files count. A source file that exists only in the working tree is
268
+ * precisely something the index cannot know about.
269
+ *
270
+ * `--untracked-files=all` IS LOAD-BEARING, and the default was wrong. Plain
271
+ * porcelain COLLAPSES an untracked directory to one entry — `?? src/` — which
272
+ * carries no extension, so the filter below skipped it and an entire new source
273
+ * tree read as `fresh`. A completion review reproduced exactly that, and it is
274
+ * the one direction the paragraph above promises cannot happen. The cost is
275
+ * enumerating untracked files rather than untracked directories; `.gitignore`
276
+ * still applies, so a vendored tree stays out of it.
277
+ */
278
+ function hasUncommittedIndexedEdit(root: string): boolean {
279
+ let out: string;
280
+ try {
281
+ out = execFileSync('git', ['-C', root, 'status', '--porcelain', '--untracked-files=all'], {
282
+ env: hardenedSpawnEnv(),
283
+ encoding: 'utf-8',
284
+ stdio: ['ignore', 'pipe', 'ignore'],
285
+ // Both bounds are the review's: Node's 1 MB default would throw
286
+ // ENOBUFS on a large working tree, the catch below would return
287
+ // false, and the state would read `fresh` — a second silent
288
+ // under-report on a probe that runs at PreToolUse and at every stop.
289
+ // The timeout bounds the other failure: this probe sits in front of
290
+ // a tool call, so a hung git must not hold one up.
291
+ maxBuffer: 32 * 1024 * 1024,
292
+ timeout: 5_000,
293
+ });
294
+ } catch {
295
+ // Not a repository, or the probe failed: nothing is known, and an
296
+ // unknown is reported as the state the caller already had.
297
+ return false;
298
+ }
299
+ for (const raw of out.split('\n')) {
300
+ if (raw.length < 4) continue;
301
+ // `XY <path>`; a rename or copy is `XY <old> -> <new>`, and the NEW path
302
+ // is the one on disk. Quotes appear under core.quotepath for non-ASCII.
303
+ let p = raw.slice(3);
304
+ const arrow = p.indexOf(' -> ');
305
+ if (arrow !== -1) p = p.slice(arrow + 4);
306
+ p = p.trim().replace(/^"|"$/g, '');
307
+ const dot = p.lastIndexOf('.');
308
+ if (dot === -1) continue;
309
+ if (p.slice(dot).toLowerCase() in EXT_LANG) return true;
310
+ }
311
+ return false;
312
+ }
202
313
 
203
314
  /**
204
315
  * Resolve the graph's state for `root`.
@@ -206,7 +317,10 @@ export type GraphState = 'absent' | 'fresh' | `behind:${number}`;
206
317
  * `absent` means no source at all — the silent case. A picked source whose
207
318
  * staleness is UNKNOWN reads as `fresh`, mirroring {@link computeVerdict}'s own
208
319
  * `picked.stale ? STALE : FRESH`: unknown is not treated as stale, because
209
- * inventing a commit count would be worse than reporting none.
320
+ * inventing a commit count would be worse than reporting none. A source level
321
+ * with HEAD then reads `edited` instead of `fresh` while the working tree
322
+ * carries an uncommitted change to an indexable file — see
323
+ * {@link hasUncommittedIndexedEdit} for why that probe is porcelain, not mtime.
210
324
  *
211
325
  * `nativeCache` overrides where the native graph is looked for. It exists
212
326
  * because a verb invoked with an explicit `--graph <path>` would otherwise
@@ -218,7 +332,13 @@ export type GraphState = 'absent' | 'fresh' | `behind:${number}`;
218
332
  export function graphState(root: string, nativeCache?: string): GraphState {
219
333
  const picked = pickSource(detectSources(root, nativeCache ?? path.join(root, NATIVE_CACHE_REL)));
220
334
  if (!picked) return 'absent';
221
- if (picked.stale !== true) return 'fresh';
222
- const behind = picked.commits_behind;
223
- return `behind:${typeof behind === 'number' ? behind : 0}`;
335
+ if (picked.stale === true) {
336
+ const behind = picked.commits_behind;
337
+ return `behind:${typeof behind === 'number' ? behind : 0}`;
338
+ }
339
+ // The commit count comes FIRST and wins: `behind:N` already says the index
340
+ // predates committed work, and reporting `edited` instead would hide the
341
+ // larger gap behind the smaller one. Only a graph level with HEAD has an
342
+ // uncommitted edit as its whole remaining distance.
343
+ return hasUncommittedIndexedEdit(root) ? 'edited' : 'fresh';
224
344
  }