@event4u/agent-config 16.2.0 → 16.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +89 -237
  5. package/README.md +14 -12
  6. package/dist/agent-src/commands/analyze/inbox.md +1 -1
  7. package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
  8. package/dist/agent-src/commands/bug/fix.md +17 -2
  9. package/dist/agent-src/commands/feature/plan.md +1 -1
  10. package/dist/agent-src/commands/feature/roadmap.md +1 -1
  11. package/dist/agent-src/commands/fix/ci.md +1 -1
  12. package/dist/agent-src/commands/implement-ticket.md +1 -1
  13. package/dist/agent-src/commands/jira-ticket.md +1 -1
  14. package/dist/agent-src/commands/roadmap/create.md +1 -1
  15. package/dist/agent-src/commands/roadmap/materialize.md +1 -1
  16. package/dist/agent-src/commands/roadmap/process-full.md +4 -4
  17. package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
  18. package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
  19. package/dist/agent-src/commands/roadmap.md +18 -4
  20. package/dist/agent-src/commands/tests/create.md +17 -3
  21. package/dist/agent-src/commands/tests/execute.md +22 -5
  22. package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  23. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  24. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
  25. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
  26. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  27. package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
  28. package/dist/agent-src/rules/active-remediation.md +1 -1
  29. package/dist/agent-src/rules/autonomous-execution.md +1 -0
  30. package/dist/agent-src/rules/broken-access-control.md +1 -0
  31. package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
  32. package/dist/agent-src/rules/context-hygiene.md +1 -1
  33. package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
  34. package/dist/agent-src/rules/design-fidelity.md +13 -16
  35. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
  36. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
  37. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  38. package/dist/agent-src/rules/domain-safety-retention.md +1 -0
  39. package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
  40. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  41. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  42. package/dist/agent-src/rules/finance-safety-floor.md +1 -0
  43. package/dist/agent-src/rules/git-history-discipline.md +1 -0
  44. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  45. package/dist/agent-src/rules/legal-safety-floor.md +3 -2
  46. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  47. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  48. package/dist/agent-src/rules/media-governance-routing.md +14 -17
  49. package/dist/agent-src/rules/persona-governance.md +2 -2
  50. package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
  51. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  52. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  53. package/dist/agent-src/rules/runtime-safety.md +1 -0
  54. package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
  55. package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
  56. package/dist/agent-src/rules/session-canary.md +8 -62
  57. package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
  58. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  59. package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
  60. package/dist/agent-src/rules/tool-safety.md +1 -0
  61. package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
  62. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  63. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  64. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  65. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  66. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  67. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  68. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  69. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  70. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +2 -2
  71. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  72. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  73. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  74. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  75. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  76. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  77. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  78. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  79. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  80. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  81. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  82. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  83. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  84. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  85. package/dist/agent-src/skills/ui-component-architect/SKILL.md +2 -2
  86. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  87. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
  88. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  89. package/dist/cli/registry.js +1 -1
  90. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  91. package/dist/cli-delegate/check_update_banner.js +12 -1
  92. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  93. package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
  94. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  95. package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
  96. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  97. package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
  98. package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
  99. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  100. package/dist/cli-delegate/{chunk-IGD6ERX6.js → chunk-JQVYKIZA.js} +10 -0
  101. package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
  102. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  103. package/dist/cli-delegate/{chunk-7RQP4UDH.js → chunk-KR6EM23A.js} +235 -1455
  104. package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
  105. package/dist/cli-delegate/{chunk-YJ72UIAK.js → chunk-NQ7WT43P.js} +1186 -244
  106. package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
  107. package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
  108. package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
  109. package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
  110. package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
  111. package/dist/cli-delegate/{chunk-7XGBX6ZH.js → chunk-RQNNW7IF.js} +1314 -560
  112. package/dist/cli-delegate/{chunk-4XFP2Y3E.js → chunk-S4UMZNES.js} +1 -1
  113. package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
  114. package/dist/cli-delegate/{chunk-FP36SWJD.js → chunk-TRI6DQZ6.js} +2781 -340
  115. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  116. package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
  117. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  118. package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
  119. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  120. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  121. package/dist/cli-delegate/cmd_conformance.js +1167 -234
  122. package/dist/cli-delegate/cmd_doctor.js +13 -6
  123. package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
  124. package/dist/cli-delegate/cmd_explain.js +5 -5
  125. package/dist/cli-delegate/cmd_export.js +11 -8
  126. package/dist/cli-delegate/cmd_fleet.js +3 -3
  127. package/dist/cli-delegate/cmd_handoff.js +10 -7
  128. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  129. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  130. package/dist/cli-delegate/cmd_migrate.js +2 -2
  131. package/dist/cli-delegate/cmd_packs_active.js +3 -3
  132. package/dist/cli-delegate/cmd_preflight.js +6 -6
  133. package/dist/cli-delegate/cmd_prune.js +11 -8
  134. package/dist/cli-delegate/cmd_refresh.js +2 -1
  135. package/dist/cli-delegate/cmd_route_audit.js +8 -6
  136. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  137. package/dist/cli-delegate/cmd_session_recycle.js +4 -3
  138. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  139. package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
  140. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  141. package/dist/cli-delegate/cmd_sync.js +11 -8
  142. package/dist/cli-delegate/cmd_uninstall.js +11 -8
  143. package/dist/cli-delegate/cmd_update.js +1 -1
  144. package/dist/cli-delegate/cmd_upgrade.js +3 -2
  145. package/dist/cli-delegate/cmd_validate.js +11 -8
  146. package/dist/cli-delegate/cmd_versions.js +1 -1
  147. package/dist/cli-delegate/deferral_carry.js +16 -0
  148. package/dist/cli-delegate/gate_execute.js +2 -2
  149. package/dist/cli-delegate/roadmap_gates.js +5 -5
  150. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  151. package/dist/discovery/deprecation-report.md +1 -1
  152. package/dist/discovery/discovery-manifest.json +127 -75
  153. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  154. package/dist/discovery/discovery-manifest.summary.md +3 -3
  155. package/dist/discovery/dormancy-report.md +1 -1
  156. package/dist/discovery/orphan-report.md +1 -1
  157. package/dist/discovery/packs.json +7 -5
  158. package/dist/discovery/trust-report.md +2 -2
  159. package/dist/discovery/workspaces.json +6 -4
  160. package/dist/hooks/dispatch.js +5357 -6656
  161. package/dist/hooks/dispatch.sha256 +1 -1
  162. package/dist/install/claudePathsPlan.js +47 -6
  163. package/dist/install/claudeRuleRewrite.js +7 -0
  164. package/dist/install/emit_host_rules_cli.js +39 -36
  165. package/dist/install/globalRuleScope.js +87 -0
  166. package/dist/install/host_rule_ownership.js +160 -0
  167. package/dist/install/install.mjs +2080 -1024
  168. package/dist/install/installThinLayer.js +261 -0
  169. package/dist/install/installedRuleLinks.js +176 -0
  170. package/dist/install/wizard-plan.js +24 -0
  171. package/dist/mcp/registry-manifest.json +3 -3
  172. package/dist/mcp/server.json +2 -2
  173. package/dist/mcp/server.mjs +31965 -27154
  174. package/dist/scripts/_lib/agent_settings.js +11 -0
  175. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  176. package/dist/scripts/_lib/installed_layer.js +378 -0
  177. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  178. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  179. package/dist/scripts/_lib/rule_injection.js +67 -10
  180. package/dist/scripts/_lib/rule_law_section.js +122 -0
  181. package/dist/scripts/_lib/thin_rules.js +458 -0
  182. package/dist/scripts/_lib/windsurf_render.js +60 -0
  183. package/dist/scripts/condense.js +14 -26
  184. package/dist/scripts/project_thin_rules.js +14 -294
  185. package/dist/server/schemas/settings.js +6 -0
  186. package/dist/shared/skillRanking.js +74 -13
  187. package/docs/contracts/settings-classes.md +7 -5
  188. package/package.json +1 -1
  189. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  190. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  191. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  192. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
  193. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  194. package/src/config/agent-settings.template.yml +62 -22
  195. package/src/config/continuity-surface.json +7 -0
  196. package/src/config/evaluator-budgets.json +4 -4
  197. package/src/config/gate-coverage.yml +96 -3
  198. package/src/config/gate-violation-baselines.json +17 -9
  199. package/src/config/hook-bundle-budget.json +25 -0
  200. package/src/config/hook-token-budget.json +6 -1
  201. package/src/config/host-instruction-limits.json +33 -0
  202. package/src/config/routing-coverage-seed.json +24 -3
  203. package/src/config/rule-activation-census.json +7 -3
  204. package/src/config/rule-consequence-class.json +161 -0
  205. package/src/config/rule-law-ceilings.json +99 -0
  206. package/src/install/claudePathsPlan.ts +46 -4
  207. package/src/install/claudeRuleRewrite.ts +10 -1
  208. package/src/install/emit_host_rules_cli.ts +43 -34
  209. package/src/install/globalRuleScope.ts +109 -0
  210. package/src/install/host_rule_ownership.ts +170 -0
  211. package/src/install/installThinLayer.ts +312 -0
  212. package/src/install/installedRuleLinks.ts +220 -0
  213. package/src/install/wizard-plan.ts +24 -0
  214. package/src/scripts/_cli/cmd_doctor.ts +23 -71
  215. package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
  216. package/src/scripts/_dispatch.bash +16 -2
  217. package/src/scripts/_lib/agent_settings.ts +11 -0
  218. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  219. package/src/scripts/_lib/estate_offsets.ts +241 -0
  220. package/src/scripts/_lib/exemption_shape.ts +143 -0
  221. package/src/scripts/_lib/fnv.ts +23 -0
  222. package/src/scripts/_lib/foreign_scan.ts +106 -0
  223. package/src/scripts/_lib/gitignore_block.ts +314 -0
  224. package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
  225. package/src/scripts/_lib/graph_feeder_record.ts +214 -0
  226. package/src/scripts/_lib/hook_settings.ts +73 -0
  227. package/src/scripts/_lib/host_capability.ts +40 -19
  228. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  229. package/src/scripts/_lib/installed_layer.ts +538 -0
  230. package/src/scripts/_lib/json_merge.ts +82 -0
  231. package/src/scripts/_lib/json_pointers.ts +93 -11
  232. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  233. package/src/scripts/_lib/machine_wake.ts +16 -3
  234. package/src/scripts/_lib/neighbour_census.ts +760 -0
  235. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  236. package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
  237. package/src/scripts/_lib/prompt_shape.ts +46 -0
  238. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  239. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  240. package/src/scripts/_lib/review_coverage.ts +134 -0
  241. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  242. package/src/scripts/_lib/rule_injection.ts +130 -10
  243. package/src/scripts/_lib/rule_law_section.ts +147 -0
  244. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  245. package/src/scripts/_lib/skill_origin.ts +254 -0
  246. package/src/scripts/_lib/structured_ask.ts +64 -21
  247. package/src/scripts/_lib/thin_rules.ts +540 -0
  248. package/src/scripts/_lib/touched_file_quality.ts +434 -0
  249. package/src/scripts/_lib/turn_end_refusals.ts +77 -2
  250. package/src/scripts/_lib/windsurf_render.ts +63 -0
  251. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  252. package/src/scripts/ai_council/clients.ts +26 -57
  253. package/src/scripts/ai_council/config.ts +1 -1
  254. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  255. package/src/scripts/audit_skill_overlap.ts +261 -0
  256. package/src/scripts/before_complete_hook.ts +243 -12
  257. package/src/scripts/bench_touched_file_quality.ts +238 -0
  258. package/src/scripts/check_enforcement_matrix.ts +377 -20
  259. package/src/scripts/check_estate_count.ts +130 -153
  260. package/src/scripts/check_finding_dispositions.ts +116 -4
  261. package/src/scripts/check_hook_bundle_composition.ts +370 -0
  262. package/src/scripts/check_kernel_edit_ratified.ts +49 -11
  263. package/src/scripts/check_memory.ts +7 -1
  264. package/src/scripts/check_release_highlights.ts +10 -0
  265. package/src/scripts/check_release_pr_shape.ts +38 -15
  266. package/src/scripts/check_standing_rule_delivery.ts +33 -13
  267. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  268. package/src/scripts/code_graph/detect.ts +127 -7
  269. package/src/scripts/code_graph/foreign.ts +194 -0
  270. package/src/scripts/code_graph/query.ts +217 -4
  271. package/src/scripts/code_graph/types.ts +26 -2
  272. package/src/scripts/code_graph/validate.ts +3 -1
  273. package/src/scripts/code_graph/verbs.ts +103 -0
  274. package/src/scripts/compile_hook_manifest.ts +33 -1
  275. package/src/scripts/condense.ts +14 -27
  276. package/src/scripts/estate_exemption_shape.ts +202 -0
  277. package/src/scripts/generate_host_cost_table.ts +1 -1
  278. package/src/scripts/hook_manifest.json +1 -1
  279. package/src/scripts/hook_manifest.yaml +150 -9
  280. package/src/scripts/hooks/block_config_weakening.ts +67 -2
  281. package/src/scripts/hooks/block_plumbing_writes.ts +29 -1
  282. package/src/scripts/hooks/code_graph_context_hook.ts +252 -23
  283. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  284. package/src/scripts/hooks/dispatch_hook.ts +52 -52
  285. package/src/scripts/hooks/exit_codes.ts +2 -1
  286. package/src/scripts/hooks/host_lowering.json +1 -1
  287. package/src/scripts/hooks/host_lowering.ts +19 -1
  288. package/src/scripts/hooks/one_question_per_ask_hook.ts +41 -25
  289. package/src/scripts/hooks/payload_stub.ts +10 -1
  290. package/src/scripts/hooks/rule_inject_hook.ts +757 -63
  291. package/src/scripts/hooks/table_fingerprint.ts +21 -0
  292. package/src/scripts/hooks/telemetry_usage_hook.ts +64 -8
  293. package/src/scripts/hooks/turn_end_gate_hook.ts +84 -0
  294. package/src/scripts/hooks_status.ts +112 -0
  295. package/src/scripts/injection_scan_hook.ts +7 -0
  296. package/src/scripts/install.ts +72 -185
  297. package/src/scripts/installed_layer_report.ts +103 -0
  298. package/src/scripts/language_mirror_hook.ts +15 -6
  299. package/src/scripts/learning_sidecar.ts +119 -8
  300. package/src/scripts/lint_hook_manifest.ts +53 -0
  301. package/src/scripts/lint_major_migration_sections.ts +156 -26
  302. package/src/scripts/lint_pack_boundaries.ts +63 -5
  303. package/src/scripts/lint_rule_law_section.ts +517 -0
  304. package/src/scripts/lint_workflow_security.ts +161 -5
  305. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  306. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  307. package/src/scripts/measure_routing_signal.ts +54 -16
  308. package/src/scripts/measure_skill_ranker_baseline.ts +216 -8
  309. package/src/scripts/memory_learn_hook.ts +91 -6
  310. package/src/scripts/model_rule_injection.ts +52 -5
  311. package/src/scripts/project_thin_rules.ts +34 -327
  312. package/src/scripts/report_evidence_temperature.ts +657 -0
  313. package/src/scripts/report_installed_rule_links.ts +157 -0
  314. package/src/scripts/report_label_agreement.ts +233 -0
  315. package/src/scripts/report_obligation_mechanism.ts +230 -0
  316. package/src/scripts/report_single_token_triggers.ts +277 -0
  317. package/src/scripts/report_skill_ranker_confusion.ts +442 -0
  318. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  319. package/src/scripts/report_verification_record_coverage.ts +306 -0
  320. package/src/scripts/rule_activation_census.ts +5 -2
  321. package/src/scripts/rule_trigger_eval.ts +9 -9
  322. package/src/scripts/schemas/review-findings.schema.json +3 -2
  323. package/src/scripts/schemas/rule.schema.json +5 -0
  324. package/src/scripts/self_review_gate.ts +62 -7
  325. package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
  326. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  327. package/src/scripts/sync_gitignore.ts +29 -235
  328. package/src/scripts/trigger_eval_grandfather.json +0 -5
  329. package/src/server/schemas/settings.ts +10 -0
  330. package/src/shared/skillRanking.ts +117 -11
  331. package/dist/cli-delegate/chunk-JNMVLWLR.js +0 -4457
  332. package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
@@ -54,6 +54,11 @@ import {
54
54
  type SlotFireSizes,
55
55
  } from './_lib/activation_payload.js';
56
56
  import { loadRouter, pathOnlyRuleIds } from './_lib/rule_injection.js';
57
+ // The windsurf emitter itself, not a second renderer beside it — 3.1 of
58
+ // `road-to-a-hook-bundle-with-one-yaml-reader`, whose Risk 3 is precisely a
59
+ // census that measures a file nobody receives. `condense.generate_windsurfrules`
60
+ // writes with this same function.
61
+ import { render_windsurfrules } from './_lib/windsurf_render.js';
57
62
  import { CAP_BYTES } from './hooks/rule_inject_hook.js';
58
63
 
59
64
  // Re-exported rather than redefined: this module was their only home until the
@@ -99,8 +104,47 @@ export interface HostSurface {
99
104
  */
100
105
  readonly anchor: string;
101
106
  readonly note: string;
107
+ /**
108
+ * Where a SINGLE-FILE row's byte figure comes from. Absent on per-rule-tree
109
+ * rows, which read the projection source like every other row.
110
+ *
111
+ * `road-to-a-hook-bundle-with-one-yaml-reader` 3.1. Until this field existed
112
+ * every single-file row read its surface off disk with `fs.readFileSync`,
113
+ * and three of the four surfaces are UNTRACKED generated projections. So the
114
+ * published windsurf figure was a function of when the checkout last ran
115
+ * `task generate-tools` — `absent` on a fresh worktree, some older byte count
116
+ * on a stale one, and nothing in the artifact said which. A census whose
117
+ * number moves with the measurer's housekeeping is not a measurement.
118
+ *
119
+ * Three kinds, and the rule that picks between them is reproducibility, not
120
+ * convenience: `render` recomputes the surface from the emitter itself;
121
+ * `tracked` reads a file that is IN GIT, so its bytes are the repository's;
122
+ * `unrenderable` refuses with a named reason rather than publishing a figure
123
+ * it cannot stand behind. Reading an untracked file is not one of the three.
124
+ */
125
+ readonly bytes?: ByteSource;
102
126
  }
103
127
 
128
+ /** How a single-file surface's byte figure is obtained. See {@link HostSurface.bytes}. */
129
+ export type ByteSource =
130
+ | {
131
+ /** Recomputed in memory by the emitter that writes the surface. */
132
+ readonly kind: 'render';
133
+ readonly detail: string;
134
+ }
135
+ | {
136
+ /** Read from a file tracked in git, so the figure is the repository's. */
137
+ readonly kind: 'tracked';
138
+ /** Repo-relative path actually read — not always `surface`. */
139
+ readonly path: string;
140
+ readonly detail: string;
141
+ }
142
+ | {
143
+ /** No in-memory emitter and no tracked file: the row reports a refusal. */
144
+ readonly kind: 'unrenderable';
145
+ readonly reason: string;
146
+ };
147
+
104
148
  /**
105
149
  * The host axis, taken from `docs/enforcement-by-host.md:18-28` rather than
106
150
  * invented here, plus the writer each surface has in this tree.
@@ -108,19 +152,19 @@ export interface HostSurface {
108
152
  export const HOST_SURFACES: readonly HostSurface[] = [
109
153
  {
110
154
  host: 'augment', surface: '.augment/rules', perRuleTree: true,
111
- writer: 'src/scripts/condense.ts:2495',
155
+ writer: 'src/scripts/condense.ts:2482',
112
156
  anchor: '\'.augment/rules\',',
113
157
  note: 'copies by default; symlinks under `augment.rules_use_symlinks`',
114
158
  },
115
159
  {
116
160
  host: 'claude-code', surface: '.claude/rules', perRuleTree: true,
117
- writer: 'src/scripts/condense.ts:1188',
161
+ writer: 'src/scripts/condense.ts:1181',
118
162
  anchor: '_emit_claude_rule(',
119
163
  note: '`_emit_claude_rule` rewrites frontmatter to the host\'s own `paths:` key',
120
164
  },
121
165
  {
122
166
  host: 'cline', surface: '.clinerules', perRuleTree: true,
123
- writer: 'src/scripts/condense.ts:1190',
167
+ writer: 'src/scripts/condense.ts:1183',
124
168
  anchor: 'fs.symlinkSync(',
125
169
  note: 'symlink per rule into the projection',
126
170
  },
@@ -129,6 +173,16 @@ export const HOST_SURFACES: readonly HostSurface[] = [
129
173
  writer: 'src/scripts/generate_capability_matrix.ts:138',
130
174
  anchor: '_INSTALL_TIME_CELLS',
131
175
  note: 'NOT written by `condense`: the installer aggregates it from `src/agent-src/templates/copilot-instructions.md`, which is why the capability matrix marks this cell `adapter` and footnotes it as install-time',
176
+ bytes: {
177
+ kind: 'tracked',
178
+ path: '.github/copilot-instructions.md',
179
+ detail:
180
+ 'the surface is COMMITTED in this repository, so its bytes are the ' +
181
+ 'repository\'s rather than this checkout\'s. The installer aggregates a ' +
182
+ 'consumer\'s copy from a template at install time and there is no in-memory ' +
183
+ 'emitter to call here; a tracked read is reproducible on any clone, which is ' +
184
+ 'the property the figure needs.',
185
+ },
132
186
  },
133
187
  {
134
188
  host: 'cowork', surface: null, perRuleTree: false,
@@ -138,25 +192,44 @@ export const HOST_SURFACES: readonly HostSurface[] = [
138
192
  },
139
193
  {
140
194
  host: 'cursor', surface: '.cursor/rules', perRuleTree: true,
141
- writer: 'src/scripts/condense.ts:1190',
195
+ writer: 'src/scripts/condense.ts:1183',
142
196
  anchor: 'fs.symlinkSync(',
143
- note: 'symlink per rule, plus `.mdc` companions at `condense.ts:1418`',
197
+ note: 'symlink per rule, plus `.mdc` companions at `condense.ts:1405`',
144
198
  },
145
199
  {
146
200
  host: 'gemini', surface: 'GEMINI.md', perRuleTree: false,
147
- writer: 'src/scripts/condense.ts:1494',
201
+ writer: 'src/scripts/condense.ts:1481',
148
202
  anchor: '\'GEMINI.md\'',
149
- note: 'single file',
203
+ note: 'single file — a symlink to the tracked `AGENTS.md`, so the bytes are that file\'s',
204
+ bytes: {
205
+ kind: 'tracked',
206
+ path: 'AGENTS.md',
207
+ detail:
208
+ '`generate_gemini_md` writes a SYMLINK to `AGENTS.md` and no content of its ' +
209
+ 'own, so the emitter\'s output IS that file and reading it is calling the ' +
210
+ 'emitter. `GEMINI.md` itself is untracked and is never read for the figure — ' +
211
+ 'on a checkout that has not generated it, the row is unchanged.',
212
+ },
150
213
  },
151
214
  {
152
215
  host: 'windsurf', surface: '.windsurfrules', perRuleTree: false,
153
- writer: 'src/scripts/condense.ts:1238',
216
+ writer: 'src/scripts/condense.ts:1225',
154
217
  anchor: '\'.windsurfrules\'',
155
- note: 'single concatenated file',
218
+ note: 'single concatenated file, rendered here by `condense.render_windsurfrules` rather than read off disk',
219
+ bytes: {
220
+ kind: 'render',
221
+ detail:
222
+ 'rendered in memory by `condense.render_windsurfrules`, the function ' +
223
+ '`generate_windsurfrules` writes with — never a second renderer. It is fed the ' +
224
+ 'SAME unscoped, non-manual rule set every other row uses, not the install-local ' +
225
+ 'scoped-and-deduplicated set the writer passes, so it reports this table\'s ' +
226
+ 'stated unit: the upper bound a consumer install receives. Reading `.windsurfrules` ' +
227
+ 'off disk reported instead when the checkout last ran `task generate-tools`.',
228
+ },
156
229
  },
157
230
  {
158
231
  host: 'codex', surface: '.codex/agent-config.md', perRuleTree: false,
159
- writer: 'src/scripts/install.ts:1431',
232
+ writer: 'src/scripts/install.ts:1382',
160
233
  // Scoped to `.codex` rather than the bare filename: nine other lines in
161
234
  // `install.ts` write an `agent-config.md` for some other host, so the
162
235
  // unscoped anchor would have accepted a drift onto any of them — the
@@ -164,6 +237,16 @@ export const HOST_SURFACES: readonly HostSurface[] = [
164
237
  // to close, one level down.
165
238
  anchor: '\'.codex\', \'agent-config.md\'',
166
239
  note: 'written by the installer, not by `condense`; absent in this checkout',
240
+ bytes: {
241
+ kind: 'unrenderable',
242
+ reason:
243
+ 'written by the installer into a consumer project, not by any emitter this ' +
244
+ 'repository can call in memory, and not committed here. There is no byte count ' +
245
+ 'this tree can stand behind, so the row refuses rather than publishing one. It ' +
246
+ 'read `absent` before this change too — the difference is that `absent` then ' +
247
+ 'meant "not on this disk right now" and could have become a number on a ' +
248
+ 'checkout that happened to have run an install.',
249
+ },
167
250
  },
168
251
  ];
169
252
 
@@ -214,7 +297,17 @@ export function manualCorpusChars(root: string): number {
214
297
  return chars;
215
298
  }
216
299
 
217
- /** Bytes of a single-file surface, or `null` when it is absent from this checkout. */
300
+ /**
301
+ * Bytes of a file at `rel`, or `null` when it cannot be read.
302
+ *
303
+ * ONLY LEGITIMATE BEHIND A TRACKEDNESS CHECK, and `generate_host_cost_table` is
304
+ * its one remaining caller — it tests `isTracked` first and reports
305
+ * `unverifiable` otherwise, so what it reads is always in the committed tree.
306
+ * THIS CENSUS NO LONGER USES IT: an unguarded read of an untracked generated
307
+ * projection is the 3.1 defect, and `singleFileBytes` below is what replaced it
308
+ * here. Kept rather than deleted because the guarded caller is correct as it
309
+ * stands and rewriting it would change a different generated table.
310
+ */
218
311
  export function singleFileChars(root: string, rel: string): number | null {
219
312
  try {
220
313
  return Buffer.byteLength(fs.readFileSync(path.join(root, rel), 'utf-8'), 'utf-8');
@@ -223,6 +316,73 @@ export function singleFileChars(root: string, rel: string): number | null {
223
316
  }
224
317
  }
225
318
 
319
+ /** The non-manual rules a per-tool tree receives — the unit every row in this table reports. */
320
+ export function projectedRuleBasenames(root: string): string[] {
321
+ const dir = path.join(root, RULES_DIST_REL);
322
+ return fs
323
+ .readdirSync(dir)
324
+ .filter((f) => f.endsWith('.md'))
325
+ .filter((f) => !isManualRule(fs.readFileSync(path.join(dir, f), 'utf-8')))
326
+ .sort();
327
+ }
328
+
329
+ /** A single-file row's figure, with how it was obtained — or why there is none. */
330
+ export interface SingleFileMeasure {
331
+ /** Bytes, or `null` when the row refuses. */
332
+ readonly bytes: number | null;
333
+ /** `render` · `tracked:<path>` · `refused` — printed in the artifact. */
334
+ readonly how: string;
335
+ /** Set when `bytes` is null. */
336
+ readonly reason?: string;
337
+ }
338
+
339
+ /**
340
+ * Bytes of a single-file surface, computed from what the tree GENERATES rather
341
+ * than from what happens to sit on this disk.
342
+ *
343
+ * `road-to-a-hook-bundle-with-one-yaml-reader` 3.1. The predecessor
344
+ * (`singleFileChars`) read the surface with `fs.readFileSync`, and three of the
345
+ * four single-file surfaces are untracked generated projections — so the figure
346
+ * it published was a reading of the measurer's housekeeping. This function never
347
+ * reads an untracked file: it renders, reads a tracked one, or refuses.
348
+ *
349
+ * A row with no `bytes` declaration is a programming error rather than a
350
+ * fallback to the old behaviour, and it throws. A silent fallback is how the
351
+ * defect would come back one row at a time.
352
+ */
353
+ export function singleFileBytes(root: string, h: HostSurface): SingleFileMeasure {
354
+ const src = h.bytes;
355
+ if (src === undefined) {
356
+ throw new Error(
357
+ `${h.host}: single-file surface with no \`bytes\` source declared. Declare render, ` +
358
+ 'tracked or unrenderable — reading the surface off disk is not an option here.',
359
+ );
360
+ }
361
+ if (src.kind === 'unrenderable') {
362
+ return { bytes: null, how: 'refused', reason: src.reason };
363
+ }
364
+ if (src.kind === 'tracked') {
365
+ try {
366
+ const text = fs.readFileSync(path.join(root, src.path), 'utf-8');
367
+ return { bytes: Buffer.byteLength(text, 'utf-8'), how: `tracked:${src.path}` };
368
+ } catch {
369
+ return {
370
+ bytes: null,
371
+ how: 'refused',
372
+ reason: `${src.path} is tracked but could not be read from this root`,
373
+ };
374
+ }
375
+ }
376
+ // `render` — windsurf is the only one, and the renderer is named rather than
377
+ // dispatched on, because a table of host → renderer with one row is a lookup
378
+ // pretending to be a mechanism.
379
+ if (h.host !== 'windsurf') {
380
+ throw new Error(`${h.host}: declares a render source but no renderer is wired for it`);
381
+ }
382
+ const text = render_windsurfrules(path.join(root, RULES_DIST_REL), projectedRuleBasenames(root));
383
+ return { bytes: Buffer.byteLength(text, 'utf-8'), how: 'render' };
384
+ }
385
+
226
386
  /**
227
387
  * Every `writer` citation resolves to a real line.
228
388
  *
@@ -365,16 +525,29 @@ export function renderArtifact(root: string, pin: string): string {
365
525
  );
366
526
  continue;
367
527
  }
368
- const chars = singleFileChars(root, h.surface);
369
- const bytes = chars === null ? 'absent' : String(chars);
370
- const toks = chars === null ? 'absent' : String(tokensChars4(chars));
371
- L.push(`| \`${h.host}\` | \`${h.surface}\` | single file | ${bytes} | ${toks} | \`${h.writer}\` |`);
528
+ const m = singleFileBytes(root, h);
529
+ const bytes = m.bytes === null ? 'refused' : String(m.bytes);
530
+ const toks = m.bytes === null ? 'refused' : String(tokensChars4(m.bytes));
531
+ L.push(
532
+ `| \`${h.host}\` | \`${h.surface}\` | single file (${m.how}) | ${bytes} | ${toks} | \`${h.writer}\` |`,
533
+ );
372
534
  }
373
535
  L.push('');
536
+ L.push('**Where the single-file figures come from.** Each is RENDERED by the emitter that writes');
537
+ L.push('the surface, or read from a file tracked in git — never read off an untracked generated');
538
+ L.push('projection, which is what the `(…)` in the Shape column names. Three of the four');
539
+ L.push('single-file surfaces are untracked, so the earlier read-off-disk figure reported when');
540
+ L.push('this checkout last ran `task generate-tools` rather than anything about the tree. A row');
541
+ L.push('with neither an emitter nor a tracked file reads `refused` and says why below.');
542
+ L.push('');
374
543
  L.push('Notes per host:');
375
544
  L.push('');
376
545
  for (const h of HOST_SURFACES) {
377
546
  L.push(`- \`${h.host}\` — ${h.note}`);
547
+ if (h.bytes !== undefined) {
548
+ const detail = h.bytes.kind === 'unrenderable' ? h.bytes.reason : h.bytes.detail;
549
+ L.push(` - bytes (\`${h.bytes.kind}\`): ${detail}`);
550
+ }
378
551
  }
379
552
  L.push('');
380
553
  L.push('## The two units, recorded once');
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env tsx
2
+ /**
3
+ * How often a verification command leaves no usable record — per host.
4
+ *
5
+ * The question this answers is not "did the checks pass". It is whether the
6
+ * INSTRUMENT that the turn-end gate reads is working on a given host at all. A
7
+ * host whose records never carry an exit code gives the gate nothing to refuse
8
+ * on, and that failure is silent in both directions: the gate does not complain,
9
+ * and the records look like a clean run of passes.
10
+ *
11
+ * Named `report_` and not `check_` deliberately. `gate_population` classifies on
12
+ * a `lint|check|audit|skill|verify_` prefix, and this gates nothing — it exits 0
13
+ * whatever it finds. A high instrument-gap rate is answered by repairing the
14
+ * carrier, never by refusing more often.
15
+ *
16
+ * What it can see, and the one thing it cannot.
17
+ *
18
+ * Its corpus is the witness files the recorder writes, one per session. From
19
+ * those it reads, per host: sessions, records written, how many carry an exit
20
+ * code, each record's verdict class, and the share of verdicts that are
21
+ * instrument gaps rather than statements about the work.
22
+ *
23
+ * It CANNOT see a verification command that was never recorded. A candidate and
24
+ * a record are the same event here — the recorder writes a record for every
25
+ * command its selector admits — so the two can never differ in this corpus, and
26
+ * a column reporting both would invite exactly the reading that nothing was
27
+ * dropped. The failure that motivated this whole measurement was of that shape:
28
+ * a failing command fired an event bound to nothing, so it left no record, and
29
+ * no reading over records could have found it. That blind spot is printed with
30
+ * the table rather than left for a reader to infer.
31
+ *
32
+ * A host is `unobserved` when it binds the recorder but no witness file names
33
+ * it. That is not the same as a host with no gaps, and the output never lets the
34
+ * two share a row.
35
+ *
36
+ * Usage:
37
+ * report_verification_record_coverage [--root DIR] [--json] [--quiet]
38
+ *
39
+ * Exit 0 always — except a dead scan scope, which throws via `assertScanned`:
40
+ * a reader that read nothing has measured nothing, and printing 0 % gaps over an
41
+ * empty corpus is the most misleading output this script could produce.
42
+ */
43
+
44
+ import * as fs from 'node:fs';
45
+ import * as path from 'node:path';
46
+ import { fileURLToPath, pathToFileURL } from 'node:url';
47
+
48
+ import { parse as parseYaml } from 'yaml';
49
+
50
+ import { assertScanned } from './_lib/scan_scope.js';
51
+ import {
52
+ classifyRun,
53
+ isInstrumentGap,
54
+ type VerificationVerdict,
55
+ } from './_lib/verification_evidence.js';
56
+
57
+ const _HERE = fileURLToPath(import.meta.url);
58
+ const REPO_ROOT = path.resolve(path.dirname(_HERE), '..', '..');
59
+
60
+ /** Where the recorder writes one witness per session. */
61
+ export const WITNESS_DIR = path.join('agents', 'state', 'verify-before-complete');
62
+
63
+ /** The concern whose binding decides whether a host can produce a record at all. */
64
+ const RECORDER_CONCERN = 'verify-before-complete';
65
+
66
+ /** A witness file written before the producer recorded its host. */
67
+ export const UNATTRIBUTED = 'unattributed';
68
+
69
+ export interface HostCoverage {
70
+ host: string;
71
+ /** The host binds the recorder on a tool event, so a record is possible there. */
72
+ binds_recorder: boolean;
73
+ sessions: number;
74
+ records: number;
75
+ /** Records whose `exit_code` is a number — the gate's only hard signal. */
76
+ exit_code_available: number;
77
+ /** Verdict kind (or `INVALID_RUN:<reason>`) → count. */
78
+ verdicts: Record<string, number>;
79
+ /** Verdicts that describe the instrument rather than the work. */
80
+ instrument_gaps: number;
81
+ /** `instrument_gaps / records`, or null when there is nothing to divide. */
82
+ instrument_gap_share: number | null;
83
+ }
84
+
85
+ export interface CoverageReport {
86
+ root: string;
87
+ files_scanned: number;
88
+ hosts: HostCoverage[];
89
+ /** Hosts that bind the recorder and left no witness at all. */
90
+ unobserved: string[];
91
+ }
92
+
93
+ function _verdictKey(v: VerificationVerdict): string {
94
+ return v.kind === 'INVALID_RUN' ? `INVALID_RUN:${v.reason}` : v.kind;
95
+ }
96
+
97
+ /**
98
+ * Hosts whose manifest binds the recorder on a tool event.
99
+ *
100
+ * Read from the manifest rather than hardcoded, so a host that gains or loses
101
+ * the binding changes this report without anybody remembering to edit it. A
102
+ * manifest that cannot be read yields an empty set, which makes every observed
103
+ * host report `binds_recorder: false` — visibly wrong rather than quietly
104
+ * plausible.
105
+ */
106
+ export function hostsBindingRecorder(manifestText: string): Set<string> {
107
+ const out = new Set<string>();
108
+ let manifest: Record<string, unknown>;
109
+ try {
110
+ manifest = (parseYaml(manifestText) ?? {}) as Record<string, unknown>;
111
+ } catch {
112
+ return out;
113
+ }
114
+ const platforms = (manifest['platforms'] ?? {}) as Record<string, unknown>;
115
+ for (const [host, spec] of Object.entries(platforms)) {
116
+ if (typeof spec !== 'object' || spec === null) continue;
117
+ for (const [event, concerns] of Object.entries(spec as Record<string, unknown>)) {
118
+ if (!event.endsWith('tool_use')) continue;
119
+ if (Array.isArray(concerns) && concerns.includes(RECORDER_CONCERN)) out.add(host);
120
+ }
121
+ }
122
+ return out;
123
+ }
124
+
125
+ export function computeCoverage(
126
+ files: ReadonlyArray<{ path: string; text: string }>,
127
+ binding: ReadonlySet<string>,
128
+ root: string,
129
+ ): CoverageReport {
130
+ const byHost = new Map<string, HostCoverage>();
131
+ const ensure = (host: string): HostCoverage => {
132
+ let h = byHost.get(host);
133
+ if (h === undefined) {
134
+ h = {
135
+ host,
136
+ binds_recorder: binding.has(host),
137
+ sessions: 0,
138
+ records: 0,
139
+ exit_code_available: 0,
140
+ verdicts: {},
141
+ instrument_gaps: 0,
142
+ instrument_gap_share: null,
143
+ };
144
+ byHost.set(host, h);
145
+ }
146
+ return h;
147
+ };
148
+
149
+ for (const f of files) {
150
+ let state: Record<string, unknown>;
151
+ try {
152
+ state = JSON.parse(f.text) as Record<string, unknown>;
153
+ } catch {
154
+ // A torn or half-written witness is counted under the host that
155
+ // cannot be read from it, never dropped: a file the reader could not
156
+ // parse is evidence about the instrument too.
157
+ ensure(UNATTRIBUTED).sessions += 1;
158
+ continue;
159
+ }
160
+ const platform = state['platform'];
161
+ const host = typeof platform === 'string' && platform ? platform : UNATTRIBUTED;
162
+ const h = ensure(host);
163
+ h.sessions += 1;
164
+
165
+ const runs = state['verification_runs'];
166
+ if (!Array.isArray(runs)) continue;
167
+ for (const rec of runs) {
168
+ h.records += 1;
169
+ const code = (rec as Record<string, unknown> | null)?.['exit_code'];
170
+ if (typeof code === 'number') h.exit_code_available += 1;
171
+ const verdict = classifyRun(rec);
172
+ const key = _verdictKey(verdict);
173
+ h.verdicts[key] = (h.verdicts[key] ?? 0) + 1;
174
+ if (isInstrumentGap(verdict)) h.instrument_gaps += 1;
175
+ }
176
+ }
177
+
178
+ for (const h of byHost.values()) {
179
+ h.instrument_gap_share = h.records > 0 ? h.instrument_gaps / h.records : null;
180
+ }
181
+
182
+ const hosts = [...byHost.values()].sort((a, b) => a.host.localeCompare(b.host));
183
+ const unobserved = [...binding].filter((x) => !byHost.has(x)).sort();
184
+ return { root, files_scanned: files.length, hosts, unobserved };
185
+ }
186
+
187
+ function _pct(x: number | null): string {
188
+ return x === null ? '—' : `${(x * 100).toFixed(1)}%`;
189
+ }
190
+
191
+ export function renderText(r: CoverageReport): string {
192
+ const lines: string[] = [];
193
+ lines.push(`verification record coverage — ${r.files_scanned} witness file(s) under ${r.root}`);
194
+ lines.push('');
195
+ lines.push('| host | binds recorder | sessions | records | exit_code available | instrument gaps | gap share |');
196
+ lines.push('|---|---|---|---|---|---|---|');
197
+ for (const h of r.hosts) {
198
+ lines.push(
199
+ `| ${h.host} | ${h.binds_recorder ? 'yes' : 'no'} | ${String(h.sessions)} | ` +
200
+ `${String(h.records)} | ${String(h.exit_code_available)} | ` +
201
+ `${String(h.instrument_gaps)} | ${_pct(h.instrument_gap_share)} |`,
202
+ );
203
+ }
204
+ for (const host of r.unobserved) {
205
+ lines.push(`| ${host} | yes | 0 | 0 | 0 | 0 | unobserved |`);
206
+ }
207
+ lines.push('');
208
+ lines.push('verdict classes');
209
+ for (const h of r.hosts) {
210
+ const keys = Object.keys(h.verdicts).sort();
211
+ if (keys.length === 0) {
212
+ lines.push(` ${h.host}: no records`);
213
+ continue;
214
+ }
215
+ lines.push(` ${h.host}: ${keys.map((k) => `${k}=${String(h.verdicts[k])}`).join(' · ')}`);
216
+ }
217
+ lines.push('');
218
+ if (r.unobserved.length > 0) {
219
+ lines.push(
220
+ `unobserved (binds the recorder, left no witness here): ${r.unobserved.join(', ')} — ` +
221
+ 'this is an absence of measurement, not an absence of gaps.',
222
+ );
223
+ }
224
+ lines.push(
225
+ 'blind spot: a verification command that left no record at all is invisible to this ' +
226
+ 'reading. Records are the corpus, so a command the recorder never saw cannot appear ' +
227
+ 'as a gap — it appears as nothing.',
228
+ );
229
+ return lines.join('\n');
230
+ }
231
+
232
+ export function main(argv: readonly string[] = process.argv.slice(2)): number {
233
+ let root = path.join(REPO_ROOT, WITNESS_DIR);
234
+ let asJson = false;
235
+ let quiet = false;
236
+ for (let i = 0; i < argv.length; i += 1) {
237
+ const a = argv[i] as string;
238
+ if (a === '--root') {
239
+ const next = argv[i + 1];
240
+ if (next === undefined) {
241
+ process.stderr.write('report_verification_record_coverage: --root needs a path\n');
242
+ return 2;
243
+ }
244
+ root = next;
245
+ i += 1;
246
+ } else if (a === '--json') {
247
+ asJson = true;
248
+ } else if (a === '--quiet') {
249
+ quiet = true;
250
+ } else {
251
+ process.stderr.write(
252
+ 'usage: report_verification_record_coverage [--root DIR] [--json] [--quiet]\n',
253
+ );
254
+ return 2;
255
+ }
256
+ }
257
+
258
+ const files: Array<{ path: string; text: string }> = [];
259
+ if (fs.existsSync(root)) {
260
+ for (const name of fs.readdirSync(root).sort()) {
261
+ if (!name.endsWith('.json')) continue;
262
+ const p = path.join(root, name);
263
+ try {
264
+ files.push({ path: p, text: fs.readFileSync(p, 'utf8') });
265
+ } catch {
266
+ // Unreadable on this filesystem — skipped, and the file count
267
+ // below reflects what was actually read rather than what was listed.
268
+ }
269
+ }
270
+ }
271
+
272
+ // A dead scope is the one outcome this report must refuse. Everything else
273
+ // it has to say is a number; "0 % gaps" over nothing is not a number, it is
274
+ // a sentence about an empty directory wearing a measurement's clothes.
275
+ assertScanned({
276
+ gate: 'report_verification_record_coverage',
277
+ scanned: files.length,
278
+ units: 'witness files',
279
+ roots: [root],
280
+ });
281
+
282
+ let manifestText = '';
283
+ try {
284
+ manifestText = fs.readFileSync(
285
+ path.join(REPO_ROOT, 'src', 'scripts', 'hook_manifest.yaml'),
286
+ 'utf8',
287
+ );
288
+ } catch {
289
+ manifestText = '';
290
+ }
291
+
292
+ const report = computeCoverage(files, hostsBindingRecorder(manifestText), root);
293
+ if (!quiet) {
294
+ process.stdout.write(asJson ? `${JSON.stringify(report, null, 2)}\n` : `${renderText(report)}\n`);
295
+ }
296
+ return 0;
297
+ }
298
+
299
+ if (
300
+ process.argv[1] !== undefined &&
301
+ import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href
302
+ ) {
303
+ process.exit(main());
304
+ }
305
+
306
+ export { _HERE };
@@ -131,8 +131,11 @@ export function classify_rule(
131
131
  const type = typeof meta['type'] === 'string' ? (meta['type'] as string) : 'unknown';
132
132
  const always = Boolean(meta['alwaysApply'] || type === 'always');
133
133
 
134
- // `_has_non_path_trigger` is the guard's own predicate and is deliberately
135
- // narrower than `has_text_trigger` looks: it ignores `command:` triggers.
134
+ // `_has_non_path_trigger` is the guard's own predicate, and it is BROADER
135
+ // than `has_text_trigger`: it counts every match key that is not
136
+ // path-shaped, so `command:` triggers count too. It used to enumerate
137
+ // `keyword` and `phrase` and therefore missed them — `roadmap-progress-sync`
138
+ // read as path-only here while losing three command routes on Claude.
136
139
  // Reported separately rather than conflated, because a rule that is mixed by
137
140
  // the guard's definition is the one that flipped.
138
141
  const mixed = has_path_trigger && _has_non_path_trigger(meta);
@@ -51,6 +51,7 @@ import {
51
51
  } from './skill_trigger_eval.js';
52
52
  import { AnthropicFetchRouter, loadKeyFromFile } from './_lib/trigger_routers.js';
53
53
  import { RULES_MODE_FLOOR } from './_lib/trigger_eval_floors.js';
54
+ import { fnv1a } from './_lib/fnv.js';
54
55
 
55
56
  const _HERE = path.dirname(fileURLToPath(import.meta.url));
56
57
  const REPO_ROOT = path.resolve(_HERE, '..', '..');
@@ -144,15 +145,14 @@ export interface RuleCase {
144
145
  expected: boolean;
145
146
  }
146
147
 
147
- /** FNV-1a 32-bit — deterministic sampling key (no Math.random). */
148
- export function fnv1a(s: string): number {
149
- let h = 0x811c9dc5;
150
- for (let i = 0; i < s.length; i += 1) {
151
- h ^= s.charCodeAt(i);
152
- h = Math.imul(h, 0x01000193) >>> 0;
153
- }
154
- return h >>> 0;
155
- }
148
+ /**
149
+ * FNV-1a 32-bit — deterministic sampling key (no Math.random).
150
+ *
151
+ * Moved to `_lib/fnv.ts` when the held-out partition in
152
+ * `measure_skill_ranker_baseline` became its second caller; re-exported here
153
+ * because this module's own suite imports the name from this file.
154
+ */
155
+ export { fnv1a };
156
156
 
157
157
  /**
158
158
  * Derive the eval suite from the routing-matrix fixtures: positive →
@@ -111,9 +111,10 @@
111
111
  "open",
112
112
  "fixed",
113
113
  "accepted_risk",
114
- "false_positive"
114
+ "false_positive",
115
+ "still_open"
115
116
  ],
116
- "description": "Disposition. `fixed` / `false_positive` / `accepted_risk` are the three terminal states check_finding_dispositions accepts; `open` is the initial state. NOTE the underscore spelling — the Markdown track writes `accepted-risk` with a hyphen in its Status cell, and the two vocabularies are deliberately not unified here: renaming either would break committed artefacts on that track."
117
+ "description": "Disposition. `fixed` / `false_positive` / `accepted_risk` are the three terminal states check_finding_dispositions accepts ON A BLOCKING ROW; `open` is the initial state, meaning nobody has looked yet. `still_open` is a fourth TERMINAL state: a row somebody re-read against a named head, confirmed as a real unfixed issue, and recorded as such. It is deliberately distinct from `open`, which the two look identical to a reader checking only for a non-empty field while being opposites — nobody looked, versus somebody looked and this is the answer. `check_finding_dispositions.TERMINAL_STATUSES` carries all four and `DISPOSITION_STATUSES` only the first three, so a BLOCKING row marked `still_open` reports as an unknown status and keeps the gate red, which is the behaviour a blocking finding that is still open should have. NOTE the underscore spelling — the Markdown track writes `accepted-risk` with a hyphen in its Status cell, and the two vocabularies are deliberately not unified here: renaming either would break committed artefacts on that track."
117
118
  },
118
119
  "rationale": {
119
120
  "type": "string",