@event4u/agent-config 16.0.0 → 16.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +289 -140
  5. package/CONTRIBUTING.md +9 -0
  6. package/README.md +2 -2
  7. package/dist/agent-src/commands/analyze/inbox.md +26 -0
  8. package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
  9. package/dist/agent-src/commands/challenge-me/closure.md +178 -0
  10. package/dist/agent-src/commands/challenge-me.md +9 -2
  11. package/dist/agent-src/commands/council/default.md +25 -5
  12. package/dist/agent-src/commands/feature/plan.md +28 -1
  13. package/dist/agent-src/commands/feature/roadmap.md +28 -1
  14. package/dist/agent-src/commands/implement-ticket.md +27 -0
  15. package/dist/agent-src/commands/jira-ticket.md +27 -0
  16. package/dist/agent-src/commands/pr/create.md +29 -0
  17. package/dist/agent-src/commands/pr/merge.md +60 -9
  18. package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
  19. package/dist/agent-src/commands/roadmap/create.md +74 -27
  20. package/dist/agent-src/commands/roadmap/materialize.md +27 -0
  21. package/dist/agent-src/commands/roadmap/process-full.md +123 -18
  22. package/dist/agent-src/commands/team.md +22 -0
  23. package/dist/agent-src/commands/work.md +4 -0
  24. package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
  25. package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
  26. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
  27. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +19 -23
  28. package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
  29. package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
  30. package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
  31. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
  32. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  33. package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  34. package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
  35. package/dist/agent-src/rules/autonomous-execution.md +15 -8
  36. package/dist/agent-src/rules/delegation-policy.md +23 -45
  37. package/dist/agent-src/rules/design-fidelity.md +32 -37
  38. package/dist/agent-src/rules/evaluator-independence.md +43 -49
  39. package/dist/agent-src/rules/test-first.md +41 -0
  40. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  41. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  42. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  43. package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
  44. package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
  45. package/dist/agent-src/skills/database/data/manifest.json +4 -4
  46. package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
  47. package/dist/agent-src/skills/design-review/SKILL.md +80 -4
  48. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  49. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -5
  50. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  51. package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
  52. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  53. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  54. package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
  55. package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
  56. package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
  57. package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
  58. package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
  59. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
  60. package/dist/agent-src/templates/agent-settings.md +1 -1
  61. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  62. package/dist/agent-src/templates/roadmaps.md +218 -0
  63. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  64. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  65. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  66. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  67. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  68. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  69. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  70. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  71. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  72. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  73. package/dist/cli/python/knowledge_ingest.js +1 -1
  74. package/dist/cli/python/workspace_drive.js +1 -1
  75. package/dist/cli-delegate/check_update_banner.js +40 -2
  76. package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
  77. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
  78. package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
  79. package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
  80. package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
  81. package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-7XGBX6ZH.js} +973 -555
  82. package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
  83. package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-FP36SWJD.js} +587 -141
  84. package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-IGD6ERX6.js} +62 -7
  85. package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-JNMVLWLR.js} +99 -14
  86. package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
  87. package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
  88. package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
  89. package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
  90. package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
  91. package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
  92. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  93. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  94. package/dist/cli-delegate/cmd_conformance.js +985 -831
  95. package/dist/cli-delegate/cmd_doctor.js +7 -5
  96. package/dist/cli-delegate/cmd_explain.js +2 -2
  97. package/dist/cli-delegate/cmd_export.js +9 -7
  98. package/dist/cli-delegate/cmd_fleet.js +4 -2
  99. package/dist/cli-delegate/cmd_handoff.js +7 -7
  100. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  101. package/dist/cli-delegate/cmd_memory_get.js +1 -1
  102. package/dist/cli-delegate/cmd_migrate.js +2 -2
  103. package/dist/cli-delegate/cmd_packs_active.js +2 -2
  104. package/dist/cli-delegate/cmd_preflight.js +4 -2
  105. package/dist/cli-delegate/cmd_prune.js +10 -8
  106. package/dist/cli-delegate/cmd_route_audit.js +3 -3
  107. package/dist/cli-delegate/cmd_route_explain.js +1 -1
  108. package/dist/cli-delegate/cmd_session_recycle.js +4 -4
  109. package/dist/cli-delegate/cmd_settings_get.js +28 -2
  110. package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
  111. package/dist/cli-delegate/cmd_settings_set.js +1 -1
  112. package/dist/cli-delegate/cmd_sync.js +10 -8
  113. package/dist/cli-delegate/cmd_uninstall.js +16 -13
  114. package/dist/cli-delegate/cmd_update.js +1 -1
  115. package/dist/cli-delegate/cmd_upgrade.js +1 -1
  116. package/dist/cli-delegate/cmd_validate.js +12 -10
  117. package/dist/cli-delegate/cmd_versions.js +1 -1
  118. package/dist/discovery/deprecation-report.md +1 -1
  119. package/dist/discovery/discovery-manifest.json +90 -43
  120. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  121. package/dist/discovery/discovery-manifest.summary.md +4 -4
  122. package/dist/discovery/dormancy-report.md +1 -1
  123. package/dist/discovery/orphan-report.md +1 -1
  124. package/dist/discovery/packs.json +10 -8
  125. package/dist/discovery/trust-report.md +3 -3
  126. package/dist/discovery/workspaces.json +8 -6
  127. package/dist/hooks/dispatch.js +7682 -2772
  128. package/dist/hooks/dispatch.sha256 +1 -0
  129. package/dist/install/claudeRuleRewrite.js +14 -3
  130. package/dist/install/conflict.js +60 -10
  131. package/dist/install/conflictTracker.js +218 -0
  132. package/dist/install/fsPrimitives.js +54 -0
  133. package/dist/install/install.mjs +2200 -1559
  134. package/dist/install/manifestFiles.js +259 -0
  135. package/dist/install/preflight.js +14 -2
  136. package/dist/install/preserve.js +157 -0
  137. package/dist/install/recordedOwnership.js +132 -0
  138. package/dist/install/txlog.js +11 -2
  139. package/dist/mcp/registry-manifest.json +3 -3
  140. package/dist/mcp/server.json +2 -2
  141. package/dist/mcp/server.mjs +3066 -1470
  142. package/dist/router.json +1 -1
  143. package/dist/scripts/_lib/agent_settings.js +3 -2
  144. package/dist/scripts/_lib/fs_atomic.js +167 -0
  145. package/dist/scripts/_lib/installed_tools.js +460 -0
  146. package/dist/scripts/_lib/lean_projection_mode.js +3 -2
  147. package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
  148. package/dist/server/routes/install.js +8 -0
  149. package/dist/server/routes/wizard.js +9 -3
  150. package/dist/server/schemas/settings.js +40 -5
  151. package/dist/shared/missionExecution.js +97 -0
  152. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  153. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  154. package/dist/ui/index.html +2 -2
  155. package/docs/MIGRATION.md +103 -0
  156. package/docs/contracts/settings-classes.md +28 -7
  157. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
  158. package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
  159. package/docs/guidelines/design-fidelity-routing.md +29 -0
  160. package/package.json +4 -4
  161. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
  162. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
  163. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
  164. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  165. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
  166. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  167. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  168. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  169. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  170. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  171. package/src/config/agent-settings.template.yml +168 -27
  172. package/src/config/auto-rule-budget.json +16 -0
  173. package/src/config/ci-local-parity.yml +36 -5
  174. package/src/config/continuity-surface.json +23 -2
  175. package/src/config/gate-coverage.yml +377 -13
  176. package/src/config/gate-violation-baselines.json +10 -6
  177. package/src/config/hook-latency-budget.json +21 -0
  178. package/src/config/hook-token-budget.json +4 -2
  179. package/src/config/loop-surfaces.yaml +215 -0
  180. package/src/config/metric-registry.yml +114 -0
  181. package/src/config/placeholder-drift-budget.json +10 -0
  182. package/src/config/platform-anchor.json +3 -2
  183. package/src/config/release-gate-locality.yml +11 -0
  184. package/src/config/routing-coverage-seed.json +33 -5
  185. package/src/config/rule-enforcement-baseline.json +0 -1
  186. package/src/install/claudeRuleRewrite.ts +16 -2
  187. package/src/install/conflict.ts +65 -9
  188. package/src/install/conflictTracker.ts +299 -0
  189. package/src/install/fsPrimitives.ts +54 -0
  190. package/src/install/manifestFiles.ts +312 -0
  191. package/src/install/preflight.ts +16 -2
  192. package/src/install/preserve.ts +205 -0
  193. package/src/install/recordedOwnership.ts +149 -0
  194. package/src/install/txlog.ts +11 -2
  195. package/src/install/types.ts +19 -4
  196. package/src/scripts/_cli/cmd_conformance.ts +88 -15
  197. package/src/scripts/_cli/cmd_doctor.ts +7 -20
  198. package/src/scripts/_cli/cmd_route_explain.ts +9 -2
  199. package/src/scripts/_cli/cmd_settings_get.ts +44 -0
  200. package/src/scripts/_cli/doctor_execution.ts +173 -0
  201. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  202. package/src/scripts/_lib/activation_payload.ts +247 -0
  203. package/src/scripts/_lib/agent_settings.ts +4 -2
  204. package/src/scripts/_lib/authority_path.ts +102 -0
  205. package/src/scripts/_lib/body_portable.ts +102 -0
  206. package/src/scripts/_lib/cascade_base.ts +94 -0
  207. package/src/scripts/_lib/cc_transcript.ts +132 -1
  208. package/src/scripts/_lib/changelog_eras.ts +7 -2
  209. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  210. package/src/scripts/_lib/continuation_ladder.ts +149 -2
  211. package/src/scripts/_lib/council_transport.ts +129 -0
  212. package/src/scripts/_lib/delivery_ready.ts +112 -0
  213. package/src/scripts/_lib/forge_protection.ts +381 -0
  214. package/src/scripts/_lib/forge_reader.ts +558 -0
  215. package/src/scripts/_lib/hook_platform_keys.ts +27 -0
  216. package/src/scripts/_lib/host_env_write.ts +111 -0
  217. package/src/scripts/_lib/json_python_parity.ts +127 -0
  218. package/src/scripts/_lib/lean_projection_mode.ts +14 -3
  219. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  220. package/src/scripts/_lib/loop_surfaces.ts +382 -0
  221. package/src/scripts/_lib/mission_record.ts +190 -0
  222. package/src/scripts/_lib/obligation_frequency.ts +217 -0
  223. package/src/scripts/_lib/obligations.ts +513 -0
  224. package/src/scripts/_lib/orchestration_record.ts +11 -20
  225. package/src/scripts/_lib/probe_inputs.ts +271 -0
  226. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  227. package/src/scripts/_lib/release_highlights.ts +201 -11
  228. package/src/scripts/_lib/release_holds.ts +493 -0
  229. package/src/scripts/_lib/release_migration_gate.ts +184 -0
  230. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  231. package/src/scripts/_lib/rides_along.ts +102 -0
  232. package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
  233. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  234. package/src/scripts/_lib/structural_hiding.ts +622 -0
  235. package/src/scripts/_lib/test_delta.ts +166 -0
  236. package/src/scripts/_lib/test_provenance.ts +171 -0
  237. package/src/scripts/_lib/turn_end_refusals.ts +525 -4
  238. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  239. package/src/scripts/_lib/typed_op_grant.ts +246 -0
  240. package/src/scripts/_lib/typed_op_watch.ts +185 -0
  241. package/src/scripts/_lib/verification_command.ts +324 -0
  242. package/src/scripts/_lib/verification_evidence.ts +647 -0
  243. package/src/scripts/_lib/verify_clause.ts +280 -0
  244. package/src/scripts/ai_council/config.ts +8 -11
  245. package/src/scripts/ai_council/fallback_config.ts +5 -3
  246. package/src/scripts/ai_council/ownership_classes.ts +118 -0
  247. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  248. package/src/scripts/ask_block_census.ts +285 -15
  249. package/src/scripts/before_complete_hook.ts +340 -23
  250. package/src/scripts/bench_hook_latency.ts +412 -5
  251. package/src/scripts/cache_realization_report.ts +61 -1
  252. package/src/scripts/check_argument_hint.ts +287 -0
  253. package/src/scripts/check_claims.ts +125 -1
  254. package/src/scripts/check_enforcement_coverage.ts +38 -9
  255. package/src/scripts/check_enforcement_matrix.ts +866 -0
  256. package/src/scripts/check_finding_dispositions.ts +22 -3
  257. package/src/scripts/check_gate_reachability.ts +173 -10
  258. package/src/scripts/check_generator_sync.ts +1025 -0
  259. package/src/scripts/check_host_docs_digest.ts +786 -0
  260. package/src/scripts/check_host_format_column.ts +559 -0
  261. package/src/scripts/check_input_references.ts +228 -0
  262. package/src/scripts/check_inputs_parity.ts +235 -0
  263. package/src/scripts/check_kernel_edit_ratified.ts +47 -5
  264. package/src/scripts/check_kill_switch_table.ts +294 -0
  265. package/src/scripts/check_placeholder_drift.ts +221 -0
  266. package/src/scripts/check_preamble_payload_budget.ts +75 -2
  267. package/src/scripts/check_read_surface_coverage.ts +515 -0
  268. package/src/scripts/check_release_highlights.ts +33 -1
  269. package/src/scripts/check_release_holds.ts +568 -0
  270. package/src/scripts/check_routing_coverage.ts +378 -11
  271. package/src/scripts/check_rule_projection_integrity.ts +22 -12
  272. package/src/scripts/check_static_parity.ts +17 -0
  273. package/src/scripts/check_test_delta.ts +294 -0
  274. package/src/scripts/check_test_weakening.ts +299 -0
  275. package/src/scripts/ci_settle.ts +145 -19
  276. package/src/scripts/closure_scan.ts +447 -0
  277. package/src/scripts/compile_hook_manifest.ts +102 -18
  278. package/src/scripts/council_record_shape.ts +209 -0
  279. package/src/scripts/external_sources_denylist.json +29 -4
  280. package/src/scripts/hook_manifest.json +1 -1
  281. package/src/scripts/hook_manifest.yaml +127 -11
  282. package/src/scripts/hooks/block_config_weakening.ts +229 -4
  283. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  284. package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
  285. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  286. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  287. package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
  288. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  289. package/src/scripts/hooks/concern_registry.ts +6 -0
  290. package/src/scripts/hooks/concern_timings.ts +94 -0
  291. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  292. package/src/scripts/hooks/design_pass_hook.ts +221 -5
  293. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  294. package/src/scripts/hooks/dispatch_hook.ts +25 -19
  295. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  296. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  297. package/src/scripts/hooks/evidence_independence.ts +97 -2
  298. package/src/scripts/hooks/exit_codes.ts +128 -0
  299. package/src/scripts/hooks/host_lowering.json +1 -0
  300. package/src/scripts/hooks/host_lowering.ts +128 -3
  301. package/src/scripts/hooks/host_lowering.yaml +262 -46
  302. package/src/scripts/hooks/host_semantics.ts +30 -2
  303. package/src/scripts/hooks/injection_budget.ts +9 -2
  304. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  305. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  306. package/src/scripts/hooks/memory_recall_hook.ts +539 -0
  307. package/src/scripts/hooks/obligation_settle_hook.ts +328 -0
  308. package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
  309. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  310. package/src/scripts/hooks/permission_gate.ts +1 -2
  311. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  312. package/src/scripts/hooks/replay_hook.ts +2 -1
  313. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  314. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  315. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  316. package/src/scripts/hooks/rule_inject_hook.ts +55 -4
  317. package/src/scripts/hooks/run_continuation_hook.ts +30 -35
  318. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  319. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  320. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  321. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  322. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  323. package/src/scripts/hooks/table_fingerprint.ts +49 -0
  324. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  325. package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
  326. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  327. package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
  328. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  329. package/src/scripts/hooks_doctor.ts +85 -0
  330. package/src/scripts/hooks_status.ts +19 -0
  331. package/src/scripts/install.ts +109 -210
  332. package/src/scripts/janitor.ts +7 -0
  333. package/src/scripts/lint_decision_classes.ts +464 -0
  334. package/src/scripts/lint_deny_text.ts +419 -0
  335. package/src/scripts/lint_exit_codes.ts +262 -0
  336. package/src/scripts/lint_hook_manifest.ts +66 -0
  337. package/src/scripts/lint_major_migration_sections.ts +450 -0
  338. package/src/scripts/lint_plan_risk_register.ts +47 -3
  339. package/src/scripts/lint_roadmap_blockers.ts +139 -12
  340. package/src/scripts/lint_roadmap_producers.ts +202 -0
  341. package/src/scripts/lint_skill_descriptions.ts +1 -1
  342. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  343. package/src/scripts/mcp_server/prompts.ts +142 -1
  344. package/src/scripts/measure_rule_budget.ts +101 -5
  345. package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
  346. package/src/scripts/measure_turn_end_gate.ts +193 -0
  347. package/src/scripts/memory_lookup.ts +1 -1
  348. package/src/scripts/new_roadmap.ts +37 -0
  349. package/src/scripts/orchestration_record.ts +0 -5
  350. package/src/scripts/prepack-check.mjs +53 -0
  351. package/src/scripts/release.ts +14 -98
  352. package/src/scripts/release_drill.ts +267 -1
  353. package/src/scripts/release_publication.ts +64 -1
  354. package/src/scripts/report_enforcement_drift.ts +566 -0
  355. package/src/scripts/report_host_injection_effect.ts +3 -11
  356. package/src/scripts/report_invocation_surface.ts +383 -0
  357. package/src/scripts/report_obligation_carriers.ts +699 -0
  358. package/src/scripts/report_obligation_writer_split.ts +236 -0
  359. package/src/scripts/report_skill_menu_census.ts +208 -0
  360. package/src/scripts/report_standing_payload_by_host.ts +26 -152
  361. package/src/scripts/roadmap_verify_share.ts +213 -0
  362. package/src/scripts/schemas/agent-settings.schema.json +23 -0
  363. package/src/scripts/schemas/command.schema.json +48 -0
  364. package/src/scripts/schemas/review-findings.schema.json +4 -0
  365. package/src/scripts/schemas/skill.schema.json +44 -0
  366. package/src/scripts/scope_growth.ts +219 -0
  367. package/src/scripts/self_review_gate.ts +359 -10
  368. package/src/scripts/semantic_parity_census.ts +314 -0
  369. package/src/scripts/session_eol_report.ts +54 -1
  370. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  371. package/src/scripts/trigger_eval_grandfather.json +0 -3
  372. package/src/scripts/trigger_eval_rotation.ts +122 -27
  373. package/src/scripts/ui_conformance_probe.ts +774 -0
  374. package/src/scripts/update_prices.ts +85 -3
  375. package/src/scripts/write_bundle_digest.mjs +74 -0
  376. package/src/server/schemas/settings.ts +62 -7
  377. package/src/shared/missionExecution.ts +126 -0
  378. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  379. package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
  380. package/dist/ui/assets/index-D0jaCRPM.js +0 -29
  381. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
  382. /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
@@ -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.
@@ -0,0 +1,447 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * The closure detector — finds the open decisions in a plan, and classifies
4
+ * each one by OWNERSHIP. It never resolves one.
5
+ *
6
+ * That boundary is the whole design. A decision dispatcher was killed on
7
+ * purpose: no TypeScript path routes a decision, and this script adds none.
8
+ * What it adds is the half that can be mechanical — *which questions are open,
9
+ * and whose they are* — so the claim "this plan reached execution with twelve
10
+ * open technical decisions and asked the owner none of them" is falsifiable
11
+ * rather than a feeling.
12
+ *
13
+ * Detector families, each a phrase an author writes when they KNOW something
14
+ * is open. Deliberately narrow: a detector that fires on ordinary plan prose
15
+ * turns every producer red at once, and the cheapest repair is then to weaken
16
+ * it until it finds nothing.
17
+ *
18
+ * `tbd` TBD / to be decided / decide later
19
+ * `unpicked-alternative` "either X or Y", "option A … option B"
20
+ * `unchecked-assumption` "assuming", "presumably", "probably fine"
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
24
+ * `ambiguous-acceptance` a vague acceptance criterion (improve / clean up
25
+ * / best practices / handle errors properly)
26
+ * `contradictory` "contradicts", "conflicts with", "but also"
27
+ * `product-semantics` two valid user-visible outcomes
28
+ * `typed-op` an operation from the typed vocabulary
29
+ * `spend` a ceiling or quota question
30
+ *
31
+ * Ownership classification is the output that matters. Six families classify
32
+ * to a technical class the agent, an independent session, a council or a team
33
+ * resolves; exactly two reach the owner (`product-semantics` → `product-owned`,
34
+ * `typed-op` → `destructive-owned`); `spend` reaches nobody — it pauses and
35
+ * reports (ADR-268 § 8).
36
+ *
37
+ * Exit codes: 0 no open decisions · 1 open decisions found · 2 bad argv.
38
+ * Finding open decisions is the POINT, so `--strict` is what makes exit 1 a
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.
58
+ */
59
+
60
+ import * as fs from 'node:fs';
61
+ import * as path from 'node:path';
62
+ import process from 'node:process';
63
+ import { fileURLToPath } from 'node:url';
64
+
65
+ import { parseVerifyClause, type VerifyClause } from './_lib/verify_clause.js';
66
+
67
+ const _HERE = fileURLToPath(import.meta.url);
68
+ const _PROG = 'closure_scan';
69
+
70
+ export type Kind =
71
+ | 'tbd'
72
+ | 'unpicked-alternative'
73
+ | 'unchecked-assumption'
74
+ | 'missing-verify'
75
+ | 'unfalsifiable-verify'
76
+ | 'ambiguous-acceptance'
77
+ | 'contradictory'
78
+ | 'product-semantics'
79
+ | 'typed-op'
80
+ | 'spend';
81
+
82
+ export interface Finding {
83
+ readonly id: string;
84
+ readonly line: number;
85
+ readonly kind: Kind;
86
+ readonly ownership: string;
87
+ readonly excerpt: string;
88
+ }
89
+
90
+ /**
91
+ * Kind → ownership class. The two owner-routed rows are the whole cost of
92
+ * closure: everything else is resolved without reaching a person, which is
93
+ * what "zero owner questions for twelve technical ambiguities" means.
94
+ */
95
+ export const OWNERSHIP_BY_KIND: Readonly<Record<Kind, string>> = {
96
+ tbd: 'contested-technical',
97
+ 'unpicked-alternative': 'contested-technical',
98
+ 'unchecked-assumption': 'reversible-technical',
99
+ 'missing-verify': 'deterministic',
100
+ 'unfalsifiable-verify': 'deterministic',
101
+ 'ambiguous-acceptance': 'reversible-technical',
102
+ contradictory: 'contested-technical',
103
+ 'product-semantics': 'product-owned',
104
+ 'typed-op': 'destructive-owned',
105
+ spend: 'spend-exhaustion',
106
+ };
107
+
108
+ /** Classes that reach a person. Everything else closes without an ask. */
109
+ export const OWNER_ROUTED: ReadonlySet<string> = new Set([
110
+ 'product-owned',
111
+ 'business-owned',
112
+ 'destructive-owned',
113
+ ]);
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
+
182
+ interface Pattern {
183
+ readonly kind: Kind;
184
+ readonly re: RegExp;
185
+ }
186
+
187
+ const LINE_PATTERNS: readonly Pattern[] = [
188
+ { kind: 'tbd', re: /\bTBD\b/ },
189
+ { kind: 'tbd', re: /\bto be decided\b/i },
190
+ { kind: 'tbd', re: /\bdecide later\b/i },
191
+ { kind: 'unpicked-alternative', re: /\beither\b[^.]{3,80}\bor\b/i },
192
+ { kind: 'unpicked-alternative', re: /\boption A\b[^.]{0,120}\boption B\b/i },
193
+ { kind: 'unpicked-alternative', re: /\bwe could (?:either )?\w+[^.]{0,80}\bor\b/i },
194
+ // "two equal technical strategies" is the canonical unpicked alternative,
195
+ // and the phrasing an author reaches for when the choice is genuinely open
196
+ // — it carries no `either`, no `or` and no `option A`.
197
+ { kind: 'unpicked-alternative', re: /\btwo (?:equal|competing)\b[^.]{0,60}\b(?:strateg|approach|design|option)/i },
198
+ { kind: 'unchecked-assumption', re: /\bassuming\b/i },
199
+ { kind: 'unchecked-assumption', re: /\bpresumably\b/i },
200
+ { kind: 'unchecked-assumption', re: /\bprobably (?:fine|works|ok)\b/i },
201
+ { kind: 'ambiguous-acceptance', re: /\b(?:improve|optimi[sz]e)\b(?![^.]*\bby\b)/i },
202
+ { kind: 'ambiguous-acceptance', re: /\bmake it (?:better|cleaner|nicer)\b/i },
203
+ { kind: 'ambiguous-acceptance', re: /\buse best practices\b/i },
204
+ { kind: 'ambiguous-acceptance', re: /\bhandle errors properly\b/i },
205
+ { kind: 'contradictory', re: /\bcontradicts\b/i },
206
+ { kind: 'contradictory', re: /\bconflicts with\b/i },
207
+ { kind: 'product-semantics', re: /\buser-visible\b[^.]{0,80}\b(?:or|versus|vs\.?)\b/i },
208
+ { kind: 'product-semantics', re: /\bwhat the user sees\b/i },
209
+ { kind: 'product-semantics', re: /\btwo valid (?:user|product) semantics\b/i },
210
+ { kind: 'typed-op', re: /\b(?:merge|force-push|deploy|publish|purchase|rotate the secret)\b/i },
211
+ { kind: 'spend', re: /\b(?:over|above|crosses?) the (?:configured )?(?:spend )?ceiling\b/i },
212
+ { kind: 'spend', re: /\bquota (?:is )?(?:exhausted|gone)\b/i },
213
+ ];
214
+
215
+ const STEP_RE = /^\s*-\s\[[ x~-]\]\s/;
216
+ const OPEN_STEP_RE = /^\s*-\s\[ \]\s/;
217
+ const H2_RE = /^##\s+/;
218
+ const DISCHARGE_H2_RE = /^##\s+(Decisions|Blockers|Kill register|Risk Register)\s*$/;
219
+ const ACCEPTANCE_H2_RE = /^##\s+Acceptance Criteria\s*$/;
220
+
221
+ function _isFence(line: string): boolean {
222
+ return /^\s*(```|~~~)/.test(line);
223
+ }
224
+
225
+ /**
226
+ * A scan unit: one logical statement, which is NOT one physical line.
227
+ *
228
+ * A roadmap step wraps over several lines, and the ambiguity it carries
229
+ * routinely straddles the wrap ("either newline-delimited JSON / or a single
230
+ * array"). Matching per line missed exactly those, which are the ones an
231
+ * author is least likely to notice. A step block is therefore ONE unit, joined
232
+ * and matched once — which also means one finding per step at most, so a step
233
+ * carrying two hedges is one open decision rather than two.
234
+ */
235
+ export interface Unit {
236
+ /** 1-based line of the unit's first line. */
237
+ readonly line: number;
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;
250
+ readonly inAcceptance: boolean;
251
+ readonly isOpenStep: boolean;
252
+ readonly hasVerify: boolean;
253
+ }
254
+
255
+ export function units(lines: readonly string[]): Unit[] {
256
+ const out: Unit[] = [];
257
+ let inFence = false;
258
+ let inDischarge = false;
259
+ let inAcceptance = false;
260
+ let i = 0;
261
+ while (i < lines.length) {
262
+ const line = lines[i] as string;
263
+ if (_isFence(line)) {
264
+ inFence = !inFence;
265
+ i += 1;
266
+ continue;
267
+ }
268
+ if (inFence) {
269
+ i += 1;
270
+ continue;
271
+ }
272
+ if (H2_RE.test(line)) {
273
+ inDischarge = DISCHARGE_H2_RE.test(line);
274
+ inAcceptance = ACCEPTANCE_H2_RE.test(line);
275
+ }
276
+ if (inDischarge) {
277
+ i += 1;
278
+ continue;
279
+ }
280
+ if (STEP_RE.test(line)) {
281
+ let j = i + 1;
282
+ while (j < lines.length && !STEP_RE.test(lines[j] as string) && !H2_RE.test(lines[j] as string)) {
283
+ j += 1;
284
+ }
285
+ const block = lines.slice(i, j);
286
+ out.push({
287
+ line: i + 1,
288
+ // Whitespace-normalised: a wrapped step joined with a single
289
+ // space still carries the next line's indent, so a pattern
290
+ // written with single spaces stops matching across the wrap —
291
+ // which is the case this unit exists to catch.
292
+ text: block.join(' ').replace(/\s+/g, ' '),
293
+ blockText: block.join('\n'),
294
+ inAcceptance,
295
+ isOpenStep: OPEN_STEP_RE.test(line),
296
+ hasVerify: /(?:^|\s)verify:/m.test(block.join('\n')),
297
+ });
298
+ i = j;
299
+ continue;
300
+ }
301
+ out.push({
302
+ line: i + 1,
303
+ text: line,
304
+ blockText: line,
305
+ inAcceptance,
306
+ isOpenStep: false,
307
+ hasVerify: false,
308
+ });
309
+ i += 1;
310
+ }
311
+ return out;
312
+ }
313
+
314
+ export function scan(text: string): Finding[] {
315
+ const lines = text.split('\n');
316
+ const findings: Finding[] = [];
317
+
318
+ for (const u of units(lines)) {
319
+ if (/<!--\s*closure:\s*ignore\s*-->/.test(u.text)) continue;
320
+ let matched: Kind | null = null;
321
+ for (const { kind, re } of LINE_PATTERNS) {
322
+ if (re.test(u.text)) {
323
+ matched = kind;
324
+ break;
325
+ }
326
+ }
327
+ // An open step carries a `verify:` line — a step whose success cannot
328
+ // be checked is a trigger for a question, not for work. Acceptance
329
+ // criteria are criteria, not steps: they have their own contract and
330
+ // carry no verify line by design.
331
+ if (matched === null && u.isOpenStep && !u.hasVerify && !u.inAcceptance) {
332
+ matched = 'missing-verify';
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
+ }
342
+ if (matched === null) continue;
343
+ findings.push({
344
+ id: '',
345
+ line: u.line,
346
+ kind: matched,
347
+ ownership: OWNERSHIP_BY_KIND[matched],
348
+ excerpt: u.text.trim().replace(/\s+/g, ' ').slice(0, 120),
349
+ });
350
+ }
351
+
352
+ findings.sort((a, b) => a.line - b.line);
353
+ return findings.map((f, idx) => ({ ...f, id: `C${String(idx + 1)}` }));
354
+ }
355
+
356
+ /** The `## Decisions` skeleton — one row per finding, resolution left open. */
357
+ export function renderRows(findings: readonly Finding[]): string {
358
+ const header = '| ID | ownership | resolved by | decision | evidence | revisit if |';
359
+ const sep = '|---|---|---|---|---|---|';
360
+ const rows = findings.map(
361
+ (f) => `| ${f.id} | ${f.ownership} | <resolver> | <decision> | ${f.kind}:${String(f.line)} | <condition> |`,
362
+ );
363
+ return [header, sep, ...rows].join('\n');
364
+ }
365
+
366
+ export function ownerQuestionCount(findings: readonly Finding[]): number {
367
+ return findings.filter((f) => OWNER_ROUTED.has(f.ownership)).length;
368
+ }
369
+
370
+ // --------------------------------------------------------------------------
371
+ // CLI.
372
+ // --------------------------------------------------------------------------
373
+
374
+ function _usage(): string {
375
+ return (
376
+ `usage: ${_PROG} [-h] [--json] [--rows] [--strict] <roadmap.md>\n\n` +
377
+ `Find and classify the open decisions in a plan. Resolves nothing.\n`
378
+ );
379
+ }
380
+
381
+ export function main(argv?: readonly string[]): number {
382
+ const args = argv ?? process.argv.slice(2);
383
+ let json = false;
384
+ let rows = false;
385
+ let strict = false;
386
+ const paths: string[] = [];
387
+ for (const a of args) {
388
+ if (a === '-h' || a === '--help') {
389
+ process.stdout.write(_usage());
390
+ return 0;
391
+ } else if (a === '--json') json = true;
392
+ else if (a === '--rows') rows = true;
393
+ else if (a === '--strict') strict = true;
394
+ else if (a.startsWith('-')) {
395
+ process.stderr.write(`${_usage()}${_PROG}: error: unrecognized arguments: ${a}\n`);
396
+ return 2;
397
+ } else paths.push(a);
398
+ }
399
+ if (paths.length !== 1) {
400
+ process.stderr.write(`${_usage()}${_PROG}: error: exactly one roadmap path is required\n`);
401
+ return 2;
402
+ }
403
+ const target = path.resolve(paths[0] as string);
404
+ let text: string;
405
+ try {
406
+ text = fs.readFileSync(target, 'utf-8');
407
+ } catch {
408
+ process.stderr.write(`${_PROG}: error: cannot read ${paths[0] as string}\n`);
409
+ return 2;
410
+ }
411
+
412
+ const findings = scan(text);
413
+ const owner = ownerQuestionCount(findings);
414
+
415
+ if (json) {
416
+ process.stdout.write(
417
+ JSON.stringify({ findings, open: findings.length, owner_questions: owner }, null, 2) + '\n',
418
+ );
419
+ } else if (rows) {
420
+ process.stdout.write(renderRows(findings) + '\n');
421
+ } else {
422
+ process.stdout.write(`open decisions: ${String(findings.length)}\n`);
423
+ process.stdout.write(`owner questions: ${String(owner)}\n`);
424
+ for (const f of findings) {
425
+ process.stdout.write(
426
+ ` ${f.id} ${String(f.line).padStart(4)} ${f.kind.padEnd(20)} ${f.ownership.padEnd(22)} ${f.excerpt}\n`,
427
+ );
428
+ }
429
+ }
430
+ if (!findings.length) return 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;
434
+ }
435
+
436
+ function _isCliEntry(): boolean {
437
+ if (!process.argv[1]) return false;
438
+ try {
439
+ return fs.realpathSync(_HERE) === fs.realpathSync(path.resolve(process.argv[1]));
440
+ } catch {
441
+ return false;
442
+ }
443
+ }
444
+
445
+ if (_isCliEntry() || process.argv[1] === _HERE) {
446
+ process.exitCode = main();
447
+ }