@event4u/agent-config 16.1.0 → 16.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (467) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +98 -257
  5. package/README.md +14 -12
  6. package/dist/agent-src/commands/analyze/inbox.md +1 -1
  7. package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
  8. package/dist/agent-src/commands/bug/fix.md +17 -2
  9. package/dist/agent-src/commands/feature/plan.md +1 -1
  10. package/dist/agent-src/commands/feature/roadmap.md +1 -1
  11. package/dist/agent-src/commands/fix/ci.md +1 -1
  12. package/dist/agent-src/commands/implement-ticket.md +1 -1
  13. package/dist/agent-src/commands/jira-ticket.md +1 -1
  14. package/dist/agent-src/commands/roadmap/create.md +1 -1
  15. package/dist/agent-src/commands/roadmap/materialize.md +1 -1
  16. package/dist/agent-src/commands/roadmap/process-full.md +5 -5
  17. package/dist/agent-src/commands/roadmap/resolve-blockers.md +361 -0
  18. package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
  19. package/dist/agent-src/commands/roadmap.md +18 -4
  20. package/dist/agent-src/commands/tests/create.md +17 -3
  21. package/dist/agent-src/commands/tests/execute.md +22 -5
  22. package/dist/agent-src/commands/work.md +4 -0
  23. package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  24. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  25. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +6 -6
  26. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +4 -8
  27. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +32 -2
  28. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  29. package/dist/agent-src/contexts/execution/toolchain-resolver.md +101 -7
  30. package/dist/agent-src/rules/active-remediation.md +1 -1
  31. package/dist/agent-src/rules/autonomous-execution.md +1 -0
  32. package/dist/agent-src/rules/broken-access-control.md +1 -0
  33. package/dist/agent-src/rules/command-suggestion-policy.md +1 -0
  34. package/dist/agent-src/rules/context-hygiene.md +1 -1
  35. package/dist/agent-src/rules/decision-revisit-gate.md +16 -33
  36. package/dist/agent-src/rules/design-fidelity.md +13 -16
  37. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -0
  38. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -0
  39. package/dist/agent-src/rules/domain-safety-pii.md +1 -0
  40. package/dist/agent-src/rules/domain-safety-retention.md +1 -0
  41. package/dist/agent-src/rules/engineering-safety-floor.md +1 -0
  42. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  43. package/dist/agent-src/rules/fast-path-marker-visibility.md +1 -0
  44. package/dist/agent-src/rules/finance-safety-floor.md +1 -0
  45. package/dist/agent-src/rules/git-history-discipline.md +1 -0
  46. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -0
  47. package/dist/agent-src/rules/legal-safety-floor.md +3 -2
  48. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -0
  49. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -0
  50. package/dist/agent-src/rules/media-governance-routing.md +14 -17
  51. package/dist/agent-src/rules/persona-governance.md +2 -2
  52. package/dist/agent-src/rules/provider-lifecycle-discipline.md +1 -1
  53. package/dist/agent-src/rules/question-not-instruction.md +1 -0
  54. package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
  55. package/dist/agent-src/rules/runtime-safety.md +1 -0
  56. package/dist/agent-src/rules/secret-vcs-guard.md +1 -0
  57. package/dist/agent-src/rules/security-sensitive-stop.md +3 -2
  58. package/dist/agent-src/rules/session-canary.md +8 -62
  59. package/dist/agent-src/rules/settings-ask-protocol.md +1 -0
  60. package/dist/agent-src/rules/source-confidentiality.md +1 -0
  61. package/dist/agent-src/rules/strategy-safety-floor.md +1 -0
  62. package/dist/agent-src/rules/tool-safety.md +1 -0
  63. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  64. package/dist/agent-src/rules/untrusted-input-defense.md +1 -0
  65. package/dist/agent-src/rules/user-interrupt-priority.md +1 -0
  66. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  67. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  68. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  69. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
  70. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +2 -2
  71. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  72. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  73. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  74. package/dist/agent-src/skills/design-review/SKILL.md +30 -4
  75. package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
  76. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  77. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +18 -3
  78. package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
  79. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  80. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  81. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  82. package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
  83. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  84. package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
  85. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  86. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  87. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  88. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  89. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +1 -1
  90. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  91. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +1 -1
  92. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  93. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  94. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  95. package/dist/agent-src/skills/ui-component-architect/SKILL.md +25 -2
  96. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  97. package/dist/agent-src/templates/roadmaps.md +30 -0
  98. package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  99. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  100. package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  101. package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  102. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  103. package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
  104. package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  105. package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  106. package/dist/cli/python/knowledge_ingest.js +1 -1
  107. package/dist/cli/python/workspace_drive.js +1 -1
  108. package/dist/cli/registry.js +1 -1
  109. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  110. package/dist/cli-delegate/check_update_banner.js +12 -1
  111. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  112. package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
  113. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  114. package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-CQWX336L.js} +1 -1
  115. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  116. package/dist/cli-delegate/chunk-EEHDXSYD.js +2040 -0
  117. package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-GKE7ZVFA.js} +4 -4
  118. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  119. package/dist/cli-delegate/{chunk-UNCRJBT3.js → chunk-JQVYKIZA.js} +41 -0
  120. package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-JY2I2DXV.js} +12 -1
  121. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  122. package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-KR6EM23A.js} +235 -1455
  123. package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-LIYTLH6O.js} +1 -1
  124. package/dist/cli-delegate/chunk-NQ7WT43P.js +2729 -0
  125. package/dist/cli-delegate/chunk-NXEILF2K.js +2160 -0
  126. package/dist/cli-delegate/{chunk-5R7HOLAF.js → chunk-NYX3L2PZ.js} +1 -1
  127. package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
  128. package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
  129. package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-QLDHIPWR.js} +1 -1
  130. package/dist/cli-delegate/{chunk-HDVVGPQW.js → chunk-RQNNW7IF.js} +1405 -567
  131. package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-S4UMZNES.js} +1 -1
  132. package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-S4YWNRKO.js} +24 -8
  133. package/dist/cli-delegate/{chunk-GJCBB2UG.js → chunk-TRI6DQZ6.js} +3054 -325
  134. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  135. package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
  136. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  137. package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
  138. package/dist/cli-delegate/cmd_analyze_session.js +1 -1
  139. package/dist/cli-delegate/cmd_brand_status.js +1 -1
  140. package/dist/cli-delegate/cmd_conformance.js +1862 -809
  141. package/dist/cli-delegate/cmd_doctor.js +13 -6
  142. package/dist/cli-delegate/cmd_doctor_neighbours.js +23 -0
  143. package/dist/cli-delegate/cmd_explain.js +5 -5
  144. package/dist/cli-delegate/cmd_export.js +11 -8
  145. package/dist/cli-delegate/cmd_fleet.js +3 -3
  146. package/dist/cli-delegate/cmd_handoff.js +10 -7
  147. package/dist/cli-delegate/cmd_mcp_available.js +1 -1
  148. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  149. package/dist/cli-delegate/cmd_migrate.js +2 -2
  150. package/dist/cli-delegate/cmd_packs_active.js +3 -3
  151. package/dist/cli-delegate/cmd_preflight.js +6 -6
  152. package/dist/cli-delegate/cmd_prune.js +11 -8
  153. package/dist/cli-delegate/cmd_refresh.js +2 -1
  154. package/dist/cli-delegate/cmd_route_audit.js +8 -6
  155. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  156. package/dist/cli-delegate/cmd_session_recycle.js +4 -3
  157. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  158. package/dist/cli-delegate/cmd_settings_migrate.js +11 -8
  159. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  160. package/dist/cli-delegate/cmd_sync.js +11 -8
  161. package/dist/cli-delegate/cmd_uninstall.js +11 -8
  162. package/dist/cli-delegate/cmd_update.js +1 -1
  163. package/dist/cli-delegate/cmd_upgrade.js +3 -2
  164. package/dist/cli-delegate/cmd_validate.js +11 -8
  165. package/dist/cli-delegate/cmd_versions.js +1 -1
  166. package/dist/cli-delegate/deferral_carry.js +16 -0
  167. package/dist/cli-delegate/gate_execute.js +2 -2
  168. package/dist/cli-delegate/roadmap_gates.js +5 -5
  169. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  170. package/dist/discovery/deprecation-report.md +1 -1
  171. package/dist/discovery/discovery-manifest.json +132 -80
  172. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  173. package/dist/discovery/discovery-manifest.summary.md +3 -3
  174. package/dist/discovery/dormancy-report.md +1 -1
  175. package/dist/discovery/orphan-report.md +1 -1
  176. package/dist/discovery/packs.json +7 -5
  177. package/dist/discovery/trust-report.md +2 -2
  178. package/dist/discovery/workspaces.json +6 -4
  179. package/dist/hooks/dispatch.js +7972 -5186
  180. package/dist/hooks/dispatch.sha256 +1 -0
  181. package/dist/install/claudePathsPlan.js +47 -6
  182. package/dist/install/claudeRuleRewrite.js +7 -0
  183. package/dist/install/emit_host_rules_cli.js +39 -36
  184. package/dist/install/globalRuleScope.js +87 -0
  185. package/dist/install/host_rule_ownership.js +160 -0
  186. package/dist/install/install.mjs +2226 -1055
  187. package/dist/install/installThinLayer.js +261 -0
  188. package/dist/install/installedRuleLinks.js +176 -0
  189. package/dist/install/wizard-plan.js +24 -0
  190. package/dist/mcp/registry-manifest.json +3 -3
  191. package/dist/mcp/server.json +2 -2
  192. package/dist/mcp/server.mjs +40913 -34865
  193. package/dist/scripts/_lib/agent_settings.js +11 -0
  194. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  195. package/dist/scripts/_lib/installed_layer.js +378 -0
  196. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  197. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  198. package/dist/scripts/_lib/rule_injection.js +67 -10
  199. package/dist/scripts/_lib/rule_law_section.js +122 -0
  200. package/dist/scripts/_lib/thin_rules.js +458 -0
  201. package/dist/scripts/_lib/windsurf_render.js +60 -0
  202. package/dist/scripts/condense.js +14 -26
  203. package/dist/scripts/project_thin_rules.js +14 -294
  204. package/dist/server/schemas/settings.js +29 -0
  205. package/dist/shared/skillRanking.js +74 -13
  206. package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
  207. package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
  208. package/dist/ui/index.html +2 -2
  209. package/docs/contracts/settings-classes.md +10 -6
  210. package/package.json +4 -4
  211. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  212. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  213. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  214. package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
  215. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +302 -21
  216. package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
  217. package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
  218. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  219. package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
  220. package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
  221. package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
  222. package/src/config/agent-settings.template.yml +91 -22
  223. package/src/config/ci-local-parity.yml +18 -0
  224. package/src/config/continuity-surface.json +9 -2
  225. package/src/config/evaluator-budgets.json +4 -4
  226. package/src/config/gate-coverage.yml +327 -3
  227. package/src/config/gate-violation-baselines.json +21 -9
  228. package/src/config/hook-bundle-budget.json +25 -0
  229. package/src/config/hook-latency-budget.json +21 -0
  230. package/src/config/hook-token-budget.json +8 -3
  231. package/src/config/host-instruction-limits.json +33 -0
  232. package/src/config/metric-registry.yml +114 -0
  233. package/src/config/placeholder-drift-budget.json +10 -0
  234. package/src/config/routing-coverage-seed.json +38 -3
  235. package/src/config/rule-activation-census.json +7 -3
  236. package/src/config/rule-consequence-class.json +161 -0
  237. package/src/config/rule-law-ceilings.json +99 -0
  238. package/src/install/claudePathsPlan.ts +46 -4
  239. package/src/install/claudeRuleRewrite.ts +10 -1
  240. package/src/install/emit_host_rules_cli.ts +43 -34
  241. package/src/install/globalRuleScope.ts +109 -0
  242. package/src/install/host_rule_ownership.ts +170 -0
  243. package/src/install/installThinLayer.ts +312 -0
  244. package/src/install/installedRuleLinks.ts +220 -0
  245. package/src/install/wizard-plan.ts +24 -0
  246. package/src/scripts/_cli/cmd_doctor.ts +28 -90
  247. package/src/scripts/_cli/cmd_doctor_neighbours.ts +293 -0
  248. package/src/scripts/_cli/doctor_execution.ts +100 -17
  249. package/src/scripts/_cli/doctor_network_posture.ts +226 -0
  250. package/src/scripts/_dispatch.bash +16 -2
  251. package/src/scripts/_lib/agent_settings.ts +11 -0
  252. package/src/scripts/_lib/body_portable.ts +102 -0
  253. package/src/scripts/_lib/cc_transcript.ts +132 -1
  254. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  255. package/src/scripts/_lib/concern_sla_window.ts +200 -0
  256. package/src/scripts/_lib/estate_offsets.ts +241 -0
  257. package/src/scripts/_lib/exemption_shape.ts +143 -0
  258. package/src/scripts/_lib/fnv.ts +23 -0
  259. package/src/scripts/_lib/foreign_scan.ts +106 -0
  260. package/src/scripts/_lib/forge_protection.ts +114 -0
  261. package/src/scripts/_lib/forge_reader.ts +558 -0
  262. package/src/scripts/_lib/gitignore_block.ts +314 -0
  263. package/src/scripts/_lib/global_deploy_inventory.ts +32 -0
  264. package/src/scripts/_lib/graph_feeder_record.ts +214 -0
  265. package/src/scripts/_lib/hook_settings.ts +73 -0
  266. package/src/scripts/_lib/host_capability.ts +40 -19
  267. package/src/scripts/_lib/host_env_write.ts +111 -0
  268. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  269. package/src/scripts/_lib/installed_layer.ts +538 -0
  270. package/src/scripts/_lib/json_merge.ts +82 -0
  271. package/src/scripts/_lib/json_pointers.ts +93 -11
  272. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  273. package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
  274. package/src/scripts/_lib/machine_wake.ts +16 -3
  275. package/src/scripts/_lib/neighbour_census.ts +760 -0
  276. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  277. package/src/scripts/_lib/neighbour_tool_use.ts +189 -0
  278. package/src/scripts/_lib/obligations.ts +119 -8
  279. package/src/scripts/_lib/probe_inputs.ts +271 -0
  280. package/src/scripts/_lib/prompt_shape.ts +46 -0
  281. package/src/scripts/_lib/read_surface_scan.ts +322 -0
  282. package/src/scripts/_lib/release_highlights.ts +2 -1
  283. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  284. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  285. package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
  286. package/src/scripts/_lib/review_coverage.ts +134 -0
  287. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  288. package/src/scripts/_lib/rule_injection.ts +130 -10
  289. package/src/scripts/_lib/rule_law_section.ts +147 -0
  290. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  291. package/src/scripts/_lib/shell_write_shapes.ts +108 -0
  292. package/src/scripts/_lib/skill_origin.ts +254 -0
  293. package/src/scripts/_lib/structural_hiding.ts +622 -0
  294. package/src/scripts/_lib/structured_ask.ts +64 -21
  295. package/src/scripts/_lib/thin_rules.ts +540 -0
  296. package/src/scripts/_lib/touched_file_quality.ts +434 -0
  297. package/src/scripts/_lib/turn_end_refusals.ts +600 -4
  298. package/src/scripts/_lib/turn_end_transcript.ts +216 -0
  299. package/src/scripts/_lib/verification_command.ts +324 -0
  300. package/src/scripts/_lib/verification_evidence.ts +647 -0
  301. package/src/scripts/_lib/verify_clause.ts +280 -0
  302. package/src/scripts/_lib/windsurf_render.ts +63 -0
  303. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  304. package/src/scripts/ai_council/clients.ts +26 -57
  305. package/src/scripts/ai_council/config.ts +1 -1
  306. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  307. package/src/scripts/ai_team/team_dispatch.ts +4 -0
  308. package/src/scripts/audit_skill_overlap.ts +261 -0
  309. package/src/scripts/before_complete_hook.ts +573 -25
  310. package/src/scripts/bench_hook_latency.ts +412 -5
  311. package/src/scripts/bench_touched_file_quality.ts +238 -0
  312. package/src/scripts/cache_realization_report.ts +61 -1
  313. package/src/scripts/check_argument_hint.ts +287 -0
  314. package/src/scripts/check_claims.ts +125 -1
  315. package/src/scripts/check_enforcement_coverage.ts +38 -9
  316. package/src/scripts/check_enforcement_matrix.ts +621 -22
  317. package/src/scripts/check_estate_count.ts +130 -153
  318. package/src/scripts/check_finding_dispositions.ts +138 -7
  319. package/src/scripts/check_generator_sync.ts +71 -0
  320. package/src/scripts/check_hook_bundle_composition.ts +370 -0
  321. package/src/scripts/check_host_docs_digest.ts +786 -0
  322. package/src/scripts/check_host_format_column.ts +559 -0
  323. package/src/scripts/check_input_references.ts +228 -0
  324. package/src/scripts/check_inputs_parity.ts +235 -0
  325. package/src/scripts/check_kernel_edit_ratified.ts +89 -9
  326. package/src/scripts/check_kill_switch_table.ts +294 -0
  327. package/src/scripts/check_memory.ts +7 -1
  328. package/src/scripts/check_placeholder_drift.ts +221 -0
  329. package/src/scripts/check_read_surface_coverage.ts +515 -0
  330. package/src/scripts/check_release_highlights.ts +10 -0
  331. package/src/scripts/check_release_holds.ts +2 -1
  332. package/src/scripts/check_release_pr_shape.ts +38 -15
  333. package/src/scripts/check_routing_coverage.ts +378 -11
  334. package/src/scripts/check_standing_rule_delivery.ts +33 -13
  335. package/src/scripts/check_static_parity.ts +17 -0
  336. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  337. package/src/scripts/ci_settle.ts +145 -19
  338. package/src/scripts/closure_scan.ts +115 -1
  339. package/src/scripts/code_graph/detect.ts +127 -7
  340. package/src/scripts/code_graph/foreign.ts +194 -0
  341. package/src/scripts/code_graph/query.ts +217 -4
  342. package/src/scripts/code_graph/types.ts +26 -2
  343. package/src/scripts/code_graph/validate.ts +3 -1
  344. package/src/scripts/code_graph/verbs.ts +103 -0
  345. package/src/scripts/compile_hook_manifest.ts +90 -14
  346. package/src/scripts/condense.ts +14 -27
  347. package/src/scripts/estate_exemption_shape.ts +202 -0
  348. package/src/scripts/external_sources_denylist.json +29 -4
  349. package/src/scripts/generate_host_cost_table.ts +1 -1
  350. package/src/scripts/hook_manifest.json +1 -1
  351. package/src/scripts/hook_manifest.yaml +192 -14
  352. package/src/scripts/hooks/block_config_weakening.ts +294 -4
  353. package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
  354. package/src/scripts/hooks/block_plumbing_writes.ts +342 -0
  355. package/src/scripts/hooks/bundle_integrity.ts +291 -0
  356. package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
  357. package/src/scripts/hooks/code_graph_context_hook.ts +253 -25
  358. package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
  359. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  360. package/src/scripts/hooks/concern_registry.ts +2 -0
  361. package/src/scripts/hooks/concern_timings.ts +94 -0
  362. package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
  363. package/src/scripts/hooks/design_pass_hook.ts +100 -9
  364. package/src/scripts/hooks/design_slop_hook.ts +1 -2
  365. package/src/scripts/hooks/dispatch_hook.ts +77 -71
  366. package/src/scripts/hooks/edit_shape_hook.ts +1 -2
  367. package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
  368. package/src/scripts/hooks/evidence_independence.ts +1 -2
  369. package/src/scripts/hooks/exit_codes.ts +129 -0
  370. package/src/scripts/hooks/host_lowering.json +1 -0
  371. package/src/scripts/hooks/host_lowering.ts +146 -3
  372. package/src/scripts/hooks/host_lowering.yaml +262 -46
  373. package/src/scripts/hooks/host_semantics.ts +30 -2
  374. package/src/scripts/hooks/injection_budget.ts +9 -2
  375. package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
  376. package/src/scripts/hooks/journal_record_hook.ts +1 -2
  377. package/src/scripts/hooks/memory_recall_hook.ts +1 -2
  378. package/src/scripts/hooks/obligation_settle_hook.ts +151 -5
  379. package/src/scripts/hooks/one_question_per_ask_hook.ts +43 -6
  380. package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
  381. package/src/scripts/hooks/payload_stub.ts +10 -1
  382. package/src/scripts/hooks/permission_gate.ts +1 -2
  383. package/src/scripts/hooks/push_settle_hook.ts +1 -3
  384. package/src/scripts/hooks/replay_hook.ts +2 -1
  385. package/src/scripts/hooks/reread_guard_hook.ts +1 -2
  386. package/src/scripts/hooks/review_baseline_hook.ts +1 -2
  387. package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
  388. package/src/scripts/hooks/rule_inject_hook.ts +765 -68
  389. package/src/scripts/hooks/run_continuation_hook.ts +13 -15
  390. package/src/scripts/hooks/skill_route_hook.ts +1 -2
  391. package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
  392. package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
  393. package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
  394. package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
  395. package/src/scripts/hooks/table_fingerprint.ts +70 -0
  396. package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
  397. package/src/scripts/hooks/telemetry_usage_hook.ts +65 -10
  398. package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
  399. package/src/scripts/hooks/turn_end_gate_hook.ts +421 -324
  400. package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
  401. package/src/scripts/hooks_status.ts +112 -0
  402. package/src/scripts/injection_scan_hook.ts +7 -0
  403. package/src/scripts/install.ts +75 -189
  404. package/src/scripts/installed_layer_report.ts +103 -0
  405. package/src/scripts/language_mirror_hook.ts +15 -6
  406. package/src/scripts/learning_sidecar.ts +119 -8
  407. package/src/scripts/lint_deny_text.ts +419 -0
  408. package/src/scripts/lint_exit_codes.ts +262 -0
  409. package/src/scripts/lint_hook_manifest.ts +108 -0
  410. package/src/scripts/lint_major_migration_sections.ts +156 -26
  411. package/src/scripts/lint_pack_boundaries.ts +63 -5
  412. package/src/scripts/lint_roadmap_blockers.ts +78 -12
  413. package/src/scripts/lint_rule_law_section.ts +517 -0
  414. package/src/scripts/lint_skill_descriptions.ts +1 -1
  415. package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
  416. package/src/scripts/lint_workflow_security.ts +161 -5
  417. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  418. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  419. package/src/scripts/mcp_server/prompts.ts +142 -1
  420. package/src/scripts/measure_routing_signal.ts +54 -16
  421. package/src/scripts/measure_skill_ranker_baseline.ts +515 -53
  422. package/src/scripts/measure_turn_end_gate.ts +193 -0
  423. package/src/scripts/memory_learn_hook.ts +91 -6
  424. package/src/scripts/memory_lookup.ts +1 -1
  425. package/src/scripts/model_rule_injection.ts +52 -5
  426. package/src/scripts/prepack-check.mjs +53 -0
  427. package/src/scripts/project_thin_rules.ts +34 -327
  428. package/src/scripts/release_publication.ts +13 -1
  429. package/src/scripts/report_evidence_temperature.ts +657 -0
  430. package/src/scripts/report_installed_rule_links.ts +157 -0
  431. package/src/scripts/report_invocation_surface.ts +383 -0
  432. package/src/scripts/report_label_agreement.ts +233 -0
  433. package/src/scripts/report_obligation_carriers.ts +699 -0
  434. package/src/scripts/report_obligation_mechanism.ts +230 -0
  435. package/src/scripts/report_obligation_writer_split.ts +236 -0
  436. package/src/scripts/report_single_token_triggers.ts +277 -0
  437. package/src/scripts/report_skill_menu_census.ts +208 -0
  438. package/src/scripts/report_skill_ranker_confusion.ts +442 -0
  439. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  440. package/src/scripts/report_verification_record_coverage.ts +306 -0
  441. package/src/scripts/roadmap_verify_share.ts +213 -0
  442. package/src/scripts/rule_activation_census.ts +5 -2
  443. package/src/scripts/rule_trigger_eval.ts +9 -9
  444. package/src/scripts/schemas/command.schema.json +44 -0
  445. package/src/scripts/schemas/review-findings.schema.json +7 -2
  446. package/src/scripts/schemas/rule.schema.json +5 -0
  447. package/src/scripts/schemas/skill.schema.json +44 -0
  448. package/src/scripts/self_review_gate.ts +421 -17
  449. package/src/scripts/semantic_parity_census.ts +314 -0
  450. package/src/scripts/session_eol_report.ts +54 -1
  451. package/src/scripts/skill_tools/score_skill_relevance.ts +290 -25
  452. package/src/scripts/sweep_dead_scan_roots.ts +1 -1
  453. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  454. package/src/scripts/sync_gitignore.ts +29 -235
  455. package/src/scripts/trigger_eval_grandfather.json +0 -7
  456. package/src/scripts/trigger_eval_rotation.ts +122 -27
  457. package/src/scripts/ui_conformance_probe.ts +156 -4
  458. package/src/scripts/update_prices.ts +85 -3
  459. package/src/scripts/write_bundle_digest.mjs +74 -0
  460. package/src/server/schemas/settings.ts +41 -0
  461. package/src/shared/skillRanking.ts +117 -11
  462. package/src/templates/consumer-settings/ONBOARDING.md +1 -0
  463. package/dist/cli-delegate/chunk-FXYUYU4N.js +0 -4416
  464. package/dist/cli-delegate/chunk-QJWAUMJW.js +0 -1072
  465. package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
  466. package/dist/ui/assets/index-DH1xc62v.js +0 -29
  467. package/dist/ui/assets/index-jSfMW8_t.css +0 -1
@@ -7,35 +7,51 @@
7
7
  * the corpus's expected skill per line". Building it surfaced two defects in
8
8
  * that premise, and this file is written around them rather than over them.
9
9
  *
10
- * DEFECT A — the 496-line corpus has no expected SKILL. `tests/eval/routing-matrix/`
11
- * is keyed by `rule:` and every prompt is labelled with the RULE it should
12
- * activate. Scoring a skill ranker against it would need a skill label that
13
- * does not exist there, so a "hit rate" over it would be invented.
10
+ * DEFECT A — the routing matrix had no expected SKILL. `tests/eval/routing-matrix/`
11
+ * is keyed by `rule:` and every prompt was labelled with the RULE it should
12
+ * activate. Scoring a skill ranker against it would need a skill label that did
13
+ * not exist there, so a "hit rate" over it would be invented.
14
14
  *
15
- * DEFECT B — 496 is stale. The matrix holds 499 prompts today
16
- * (`grep -h "^\s*- prompt:" tests/eval/routing-matrix/*.yaml | wc -l`), and 496
17
- * is still published in six places including `hook-token-budget.json`.
15
+ * **Defect A is CLOSED as of `road-to-a-menu-whose-precision-is-measured` 1.1.**
16
+ * Every case in the matrix now carries an `expected_skills` list, hand-written
17
+ * by seats that had not read this ranker's scoring (the protocol is in
18
+ * `tests/eval/routing-matrix/README.md`). An EMPTY list is a first-class value
19
+ * meaning "deliberately no skill expectation", and such a row is excluded from
20
+ * the accuracy denominator rather than counted as a miss. So the matrix arm now
21
+ * has ground truth; `--corpus routing-matrix` scores against it.
18
22
  *
19
- * So the baseline is measured against the corpus that DOES carry expected
20
- * skills — `tests/eval/corpus-{dev,non-dev}.yaml`, 26 labelled prompts — and
21
- * the routing matrix is reported alongside it as what it can honestly be: a
22
- * COVERAGE measurement (does the ranker return anything at all, and how
23
- * confident) with no ground truth and therefore no accuracy.
23
+ * DEFECT B — the published size is stale, repeatedly. 496 was published while
24
+ * the matrix held 499, and 499 is still published in several sites while the
25
+ * matrix holds more than that again. The size is therefore MEASURED on every run
26
+ * and printed, never asserted from a constant.
24
27
  *
25
- * Both halves are emitted in one JSON object so a later ranker revision can be
26
- * compared on the same two axes in one run:
28
+ * THE INTERVAL, AND THE REFUSAL TO PRINT A VERDICT UNDER n = 100.
29
+ * A hit rate over 26 prompts
30
+ * moves ~4 points per prompt; quoting it as "61.5 %" invites a comparison the
31
+ * sample cannot carry. Both arms therefore report a 95 % Wilson score interval
32
+ * (`_lib/capture_rate.ts` — Wilson rather than the normal approximation for the
33
+ * same reason stated there: near p = 1 the normal one produces bounds above 1).
27
34
  *
28
- * ./scripts-run src/scripts/measure_skill_ranker_baseline --json
29
- * ./scripts-run src/scripts/measure_skill_ranker_baseline --ranker keyword-v2 --json
35
+ * Below {@link MIN_POWERED_N} labelled rows the arm's `verdict` is
36
+ * `underpowered`. That is not a failure and not an exit code — the point
37
+ * estimate and the interval are still printed, because suppressing them would
38
+ * replace a wide measurement with no measurement. It is a label that travels
39
+ * with the number so a later reader cannot quote the point estimate alone.
30
40
  *
31
- * A 26-prompt denominator is small and is stated as such in the output. It is
32
- * the whole labelled ground truth this repository has.
41
+ * Usage:
42
+ * ./scripts-run src/scripts/measure_skill_ranker_baseline
43
+ * ./scripts-run src/scripts/measure_skill_ranker_baseline --corpus routing-matrix
44
+ * ./scripts-run src/scripts/measure_skill_ranker_baseline --corpus all
45
+ * ./scripts-run src/scripts/measure_skill_ranker_baseline --ranker keyword-v2
46
+ * ./scripts-run src/scripts/measure_skill_ranker_baseline --corpus routing-matrix --slice sealed
33
47
  */
34
48
 
35
49
  import * as fs from 'node:fs';
36
50
  import * as path from 'node:path';
37
51
  import { fileURLToPath } from 'node:url';
38
52
 
53
+ import { wilsonInterval } from './_lib/capture_rate.js';
54
+ import { fnv1a } from './_lib/fnv.js';
39
55
  import { rank } from './skill_tools/score_skill_relevance.js';
40
56
  import type { RankOptions } from '../shared/skillRanking.js';
41
57
 
@@ -45,6 +61,20 @@ const MATRIX_DIR = 'tests/eval/routing-matrix';
45
61
  /** Where the projected skills live in this checkout. */
46
62
  export const SKILLS_DIR = path.join(REPO, 'src', 'skills');
47
63
 
64
+ /**
65
+ * The smallest labelled corpus this report will call powered.
66
+ *
67
+ * 100 is the roadmap's number, not a derived optimum, and it is stated as such:
68
+ * at n = 100 a point estimate near 0.7 carries a 95 % Wilson half-width of
69
+ * roughly ±9 points, which is wide but no longer moves on a single prompt the
70
+ * way a 26-row denominator does. Raise it with a dated reason if a decision
71
+ * ever needs a tighter bound than that.
72
+ */
73
+ export const MIN_POWERED_N = 100;
74
+
75
+ export type CorpusName = 'labelled' | 'routing-matrix' | 'all';
76
+ export const CORPUS_NAMES: readonly CorpusName[] = ['labelled', 'routing-matrix', 'all'];
77
+
48
78
  export interface LabelledPrompt {
49
79
  id: string;
50
80
  corpus: string;
@@ -94,38 +124,275 @@ export function readLabelledPrompts(repo = REPO): LabelledPrompt[] {
94
124
  return out;
95
125
  }
96
126
 
97
- /** Every `- prompt:` string in the rule routing matrix. No skill label exists. */
98
- export function readMatrixPrompts(repo = REPO): string[] {
127
+ export interface MatrixCase {
128
+ rule: string;
129
+ section: 'positives' | 'near_misses';
130
+ ordinal: number;
131
+ prompt: string;
132
+ /** `undefined` = the key is missing (a defect the lint catches); `[]` = deliberately unlabelled. */
133
+ expected?: string[];
134
+ }
135
+
136
+ /**
137
+ * Every case in the rule routing matrix, with its `expected_skills` label when
138
+ * one is present.
139
+ *
140
+ * Line-oriented for the same reason as the reader above, and because a YAML
141
+ * round-trip through this directory would reformat 100+ hand-kept fixtures.
142
+ * A case is `prompt:` plus the keys indented under it up to the next `- `.
143
+ */
144
+ export function readMatrixCases(repo = REPO): MatrixCase[] {
99
145
  const dir = path.join(repo, MATRIX_DIR);
100
146
  if (!fs.existsSync(dir)) return [];
101
- const out: string[] = [];
147
+ const out: MatrixCase[] = [];
102
148
  for (const name of fs.readdirSync(dir).sort()) {
103
149
  if (!name.endsWith('.yaml')) continue;
150
+ const rule = name.replace(/\.yaml$/, '');
151
+ let section: MatrixCase['section'] = 'positives';
152
+ let ordinal = 0;
153
+ let current: MatrixCase | undefined;
104
154
  for (const raw of fs.readFileSync(path.join(dir, name), 'utf8').split('\n')) {
105
- const m = /^\s*-?\s*prompt:\s*"(.*)"\s*$/.exec(raw.replace(/\r$/, ''));
106
- if (m && m[1]) out.push(m[1]);
155
+ const line = raw.replace(/\r$/, '');
156
+ if (/^positives:\s*$/.test(line)) {
157
+ section = 'positives';
158
+ ordinal = 0;
159
+ current = undefined;
160
+ continue;
161
+ }
162
+ if (/^near_misses:\s*$/.test(line)) {
163
+ section = 'near_misses';
164
+ ordinal = 0;
165
+ current = undefined;
166
+ continue;
167
+ }
168
+ const mPrompt = /^\s*-?\s*prompt:\s*"(.*)"\s*$/.exec(line);
169
+ if (mPrompt && mPrompt[1] !== undefined) {
170
+ current = { rule, section, ordinal: ordinal++, prompt: mPrompt[1] };
171
+ out.push(current);
172
+ continue;
173
+ }
174
+ const mExp = /^\s*expected_skills:\s*\[(.*)\]\s*$/.exec(line);
175
+ if (mExp && current) {
176
+ current.expected = mExp[1]!
177
+ .split(',')
178
+ .map((s) => s.trim())
179
+ .filter(Boolean);
180
+ }
107
181
  }
108
182
  }
109
183
  return out;
110
184
  }
111
185
 
186
+ /** Every `- prompt:` string in the rule routing matrix, label or no label. */
187
+ export function readMatrixPrompts(repo = REPO): string[] {
188
+ return readMatrixCases(repo).map((c) => c.prompt);
189
+ }
190
+
191
+ /** The matrix rows that carry a NON-EMPTY label — the accuracy denominator. */
192
+ export function readMatrixLabelledPrompts(repo = REPO): LabelledPrompt[] {
193
+ return readMatrixCases(repo)
194
+ .filter((c) => (c.expected?.length ?? 0) > 0)
195
+ .map((c) => ({
196
+ id: `${c.rule}#${c.section}[${c.ordinal}]`,
197
+ corpus: 'routing-matrix',
198
+ prompt: c.prompt,
199
+ expected: c.expected!,
200
+ }));
201
+ }
202
+
203
+ export function promptsForCorpus(corpus: CorpusName, repo = REPO): LabelledPrompt[] {
204
+ if (corpus === 'labelled') return readLabelledPrompts(repo);
205
+ if (corpus === 'routing-matrix') return readMatrixLabelledPrompts(repo);
206
+ return [...readLabelledPrompts(repo), ...readMatrixLabelledPrompts(repo)];
207
+ }
208
+
209
+ /**
210
+ * Which half of the corpus a row belongs to.
211
+ *
212
+ * `road-to-a-ranker-that-routes` 1.2. Tuning and reading on one corpus of 390
213
+ * overfits it: a signal chosen because it lifted the number on the rows it was
214
+ * chosen from will lift the number on exactly those rows, and the interval
215
+ * printed beside it then describes the fitting rather than the ranker. Every
216
+ * lift claim in Phases 2 and 3 is read on `sealed` only.
217
+ */
218
+ export type SliceName = 'all' | 'tuning' | 'sealed';
219
+ export const SLICE_NAMES: readonly SliceName[] = ['all', 'tuning', 'sealed'];
220
+
221
+ /**
222
+ * One row in five — nominally 20 %, measured 19.2 % (75 of 390) on the live
223
+ * corpus, because a hash is not a shuffle and the exact count is a property of
224
+ * the ids.
225
+ *
226
+ * Stated as a modulus rather than a fraction because the partition has to be a
227
+ * pure function of the id: a share applied to a shuffled list would move every
228
+ * row the moment a prompt is added, and the sealed slice would stop being the
229
+ * same slice between two runs of the same measurement.
230
+ */
231
+ export const SEALED_MODULUS = 5;
232
+
233
+ /**
234
+ * The slice one case id falls in — deterministic and order-independent.
235
+ *
236
+ * STABILITY UNDER CORPUS CHANGE IS PARTIAL, and an earlier version of this block
237
+ * claimed otherwise. The matrix id is `rule#section[ordinal]` and the ordinal is
238
+ * POSITIONAL — it counts `- prompt:` entries within a section. So:
239
+ *
240
+ * - APPENDING a prompt to the end of a section is stable: every existing row
241
+ * keeps its ordinal, its id, and its side of the seal. This is the workflow
242
+ * the matrix README documents, which is why the partition survives the
243
+ * release-to-release growth the `history` array is for.
244
+ * - INSERTING or DELETING mid-section renumbers every later row in THAT
245
+ * section, which re-hashes them and moves roughly one in five across the
246
+ * seal.
247
+ *
248
+ * A content key (`rule|section|prompt`) would close the second case and is the
249
+ * right shape for the next corpus version. It is deliberately NOT applied here:
250
+ * the signals in Phase 2 were chosen by reading the TUNING rows of this
251
+ * partition, so re-drawing the boundary now would move ~20 % of those rows into
252
+ * the sealed slice and silently contaminate the very claims the seal exists to
253
+ * protect. Changing a partition after choosing on it is worse than a partially
254
+ * stable partition, so the key moves when the corpus does, not before.
255
+ */
256
+ export function sliceForId(id: string): Exclude<SliceName, 'all'> {
257
+ return fnv1a(id) % SEALED_MODULUS === 0 ? 'sealed' : 'tuning';
258
+ }
259
+
260
+ /** The rows of `prompts` that fall in `slice`; `all` is the identity. */
261
+ export function partitionBySlice<T extends { id: string }>(
262
+ prompts: readonly T[],
263
+ slice: SliceName,
264
+ ): T[] {
265
+ if (slice === 'all') return [...prompts];
266
+ return prompts.filter((p) => sliceForId(p.id) === slice);
267
+ }
268
+
269
+ /** How the corpus divides, printed on every run so a reader never assumes it. */
270
+ export interface SliceSizes {
271
+ all: number;
272
+ tuning: number;
273
+ sealed: number;
274
+ sealed_modulus: number;
275
+ }
276
+
277
+ export function sliceSizes(prompts: readonly { id: string }[]): SliceSizes {
278
+ const sealed = prompts.filter((p) => sliceForId(p.id) === 'sealed').length;
279
+ return {
280
+ all: prompts.length,
281
+ tuning: prompts.length - sealed,
282
+ sealed,
283
+ sealed_modulus: SEALED_MODULUS,
284
+ };
285
+ }
286
+
287
+ /**
288
+ * The packs a skill declares, read from its own `packs:` frontmatter block.
289
+ *
290
+ * Line-oriented, and deliberately NOT read from `src/packs/*\/pack.yaml`: those
291
+ * manifests are generated FROM this frontmatter, and four of them
292
+ * (`analytics`, `core`, `memory`, `product-reasoning`) carry no skill at all.
293
+ * A pack with no skill can never own a labelled prompt, so a census over the
294
+ * manifest set would state a floor nothing could ever meet.
295
+ */
296
+ export function packsForSkill(skillsDir: string, name: string): string[] {
297
+ const file = path.join(skillsDir, name, 'SKILL.md');
298
+ if (!fs.existsSync(file)) return [];
299
+ const text = fs.readFileSync(file, 'utf8');
300
+ if (!text.startsWith('---')) return [];
301
+ const end = text.indexOf('\n---', 3);
302
+ if (end === -1) return [];
303
+ const lines = text.slice(3, end).split('\n');
304
+ const at = lines.findIndex((l) => /^packs:/u.test(l));
305
+ if (at === -1) return [];
306
+ const out: string[] = [];
307
+ for (let i = at + 1; i < lines.length; i += 1) {
308
+ const m = /^\s+-\s+(\S+)\s*$/u.exec(lines[i] as string);
309
+ if (!m) break;
310
+ out.push(m[1] as string);
311
+ }
312
+ return out;
313
+ }
314
+
315
+ /** Every pack id that at least one shipped skill declares. The census domain. */
316
+ export function allPacks(skillsDir: string): string[] {
317
+ const packs = new Set<string>();
318
+ for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
319
+ if (!entry.isDirectory()) continue;
320
+ for (const p of packsForSkill(skillsDir, entry.name)) packs.add(p);
321
+ }
322
+ return [...packs].sort();
323
+ }
324
+
325
+ /**
326
+ * How many labelled prompts each pack owns, via the packs of its expected skills.
327
+ *
328
+ * A prompt counts ONCE per pack however many of that pack's skills it expects —
329
+ * otherwise a single prompt naming three `laravel` skills would satisfy a
330
+ * three-prompt floor on its own, which is the shape of coverage this census
331
+ * exists to refuse.
332
+ */
333
+ export function packCensus(prompts: readonly LabelledPrompt[], skillsDir: string): Record<string, number> {
334
+ const out: Record<string, number> = {};
335
+ for (const p of allPacks(skillsDir)) out[p] = 0;
336
+ for (const prompt of prompts) {
337
+ const packs = new Set<string>();
338
+ for (const skill of prompt.expected) for (const pack of packsForSkill(skillsDir, skill)) packs.add(pack);
339
+ for (const pack of packs) out[pack] = (out[pack] ?? 0) + 1;
340
+ }
341
+ return out;
342
+ }
343
+
344
+ /** Packs owning fewer than `floor` labelled prompts, sorted by pack id. */
345
+ export function packsBelow(census: Record<string, number>, floor: number): string[] {
346
+ return Object.keys(census)
347
+ .filter((p) => (census[p] ?? 0) < floor)
348
+ .sort();
349
+ }
350
+
351
+ /** The per-pack floor `road-to-a-menu-whose-precision-is-measured` 1.1 states. */
352
+ export const MIN_PROMPTS_PER_PACK = 3;
353
+
354
+ export interface Interval {
355
+ lower: number;
356
+ upper: number;
357
+ }
358
+
112
359
  export interface AccuracyArm {
360
+ corpus: CorpusName;
361
+ /** Which half of the corpus this arm read. `all` is the whole of it. */
362
+ slice: SliceName;
363
+ /** The division of the FULL corpus, printed whichever slice was read. */
364
+ slice_sizes: SliceSizes;
113
365
  corpus_prompts: number;
114
366
  top1: number;
367
+ top1_ci95: Interval;
115
368
  top3: number;
369
+ top3_ci95: Interval;
370
+ /** `underpowered` below MIN_POWERED_N labelled rows; `measured` at or above. */
371
+ verdict: 'underpowered' | 'measured';
372
+ min_powered_n: number;
116
373
  misses: string[];
117
374
  denominator_note: string;
375
+ /** Pack ids at least one shipped skill declares — the census domain. */
376
+ packs_total: number;
377
+ min_prompts_per_pack: number;
378
+ /** Packs this corpus leaves under the floor. Empty is the passing shape. */
379
+ packs_below_floor: string[];
380
+ /** Full census, so a reader can see the distribution and not just the failures. */
381
+ pack_census: Record<string, number>;
118
382
  }
119
383
 
120
384
  export interface CoverageArm {
121
385
  corpus_prompts: number;
386
+ labelled_prompts: number;
387
+ unlabelled_prompts: number;
388
+ missing_label_key: number;
122
389
  prompts_with_any_result: number;
123
390
  mean_top_score: number;
124
391
  note: string;
125
392
  }
126
393
 
127
394
  export interface RankerBaseline {
128
- schema: 1;
395
+ schema: 2;
129
396
  ranker: string;
130
397
  commit: string;
131
398
  skills_dir: string;
@@ -139,28 +406,150 @@ function hitAt(rows: readonly (readonly [string, number, string[]])[], n: number
139
406
  return expected.some((e) => top.includes(e));
140
407
  }
141
408
 
142
- export function measure(opts: { ranker: string; commit: string; skillsDir?: string; repo?: string }): RankerBaseline {
143
- const repo = opts.repo ?? REPO;
144
- const skillsDir = opts.skillsDir ?? SKILLS_DIR;
145
- // `keyword-v2` is Phase 3.1: the same formula with `triggers:` prose folded
146
- // into each skill's term source. Any other label measures v1.
147
- const rankOpts: RankOptions = opts.ranker === 'keyword-v2' ? { includeTriggers: true } : {};
148
- const labelled = readLabelledPrompts(repo);
409
+ function round3(n: number): number {
410
+ return Math.round(n * 1000) / 1000;
411
+ }
412
+
413
+ function ci(successes: number, trials: number): Interval {
414
+ const raw = wilsonInterval(successes, trials);
415
+ return { lower: round3(raw.lower), upper: round3(raw.upper) };
416
+ }
417
+
418
+ const DENOMINATOR_NOTES: Record<CorpusName, string> = {
419
+ labelled:
420
+ 'tests/eval/corpus-dev.yaml + corpus-non-dev.yaml — the two hand-written eval corpora. ' +
421
+ 'Small; a single prompt moves each rate by ~4 points, which is why the interval is ' +
422
+ 'printed beside the point estimate and not instead of it.',
423
+ 'routing-matrix':
424
+ 'tests/eval/routing-matrix/ rows whose expected_skills list is NON-EMPTY. A row with an ' +
425
+ 'empty list is a deliberate "no skill expectation" and is excluded from the ' +
426
+ 'denominator — never counted as a miss.',
427
+ all:
428
+ 'Both labelled sources pooled. The two were written under different protocols, so a ' +
429
+ 'pooled rate is a convenience reading and the per-corpus arms are the citable ones.',
430
+ };
431
+
432
+ export function measureAccuracy(opts: {
433
+ corpus: CorpusName;
434
+ repo: string;
435
+ skillsDir: string;
436
+ rankOpts: RankOptions;
437
+ slice?: SliceName;
438
+ }): AccuracyArm {
439
+ const slice = opts.slice ?? 'all';
440
+ const whole = promptsForCorpus(opts.corpus, opts.repo);
441
+ const labelled = partitionBySlice(whole, slice);
149
442
  let top1 = 0;
150
443
  let top3 = 0;
151
444
  const misses: string[] = [];
152
445
  for (const p of labelled) {
153
- const rows = rank(p.prompt, skillsDir, rankOpts);
446
+ const rows = rank(p.prompt, opts.skillsDir, opts.rankOpts);
154
447
  if (hitAt(rows, 1, p.expected)) top1++;
155
448
  if (hitAt(rows, 3, p.expected)) top3++;
156
449
  else misses.push(p.id);
157
450
  }
451
+ const n = labelled.length;
452
+ const census = packCensus(labelled, opts.skillsDir);
453
+ return {
454
+ corpus: opts.corpus,
455
+ slice,
456
+ slice_sizes: sliceSizes(whole),
457
+ corpus_prompts: n,
458
+ top1: n ? round3(top1 / n) : 0,
459
+ top1_ci95: ci(top1, n),
460
+ top3: n ? round3(top3 / n) : 0,
461
+ top3_ci95: ci(top3, n),
462
+ verdict: n >= MIN_POWERED_N ? 'measured' : 'underpowered',
463
+ min_powered_n: MIN_POWERED_N,
464
+ misses,
465
+ denominator_note: DENOMINATOR_NOTES[opts.corpus],
466
+ packs_total: Object.keys(census).length,
467
+ min_prompts_per_pack: MIN_PROMPTS_PER_PACK,
468
+ packs_below_floor: packsBelow(census, MIN_PROMPTS_PER_PACK),
469
+ pack_census: census,
470
+ };
471
+ }
472
+
473
+ /**
474
+ * The ranker label → the options it means.
475
+ *
476
+ * One table rather than a conditional at each call site, because a measurement
477
+ * harness that resolves `--ranker` differently from the report that quotes it
478
+ * is a comparison of two things under one name. Every entry past `keyword-v1`
479
+ * is a candidate configuration under `road-to-a-ranker-that-routes` Phase 2,
480
+ * each a single flag so it can be measured alone, plus the combinations the
481
+ * same phase names.
482
+ *
483
+ * AN UNKNOWN LABEL THROWS. It used to resolve to `keyword-v1` — the baseline —
484
+ * which is the exact failure this table exists to prevent: every consumer
485
+ * echoes the requested label back into its output, so a typo published a
486
+ * BASELINE number under another configuration's name, in the report's own title
487
+ * and in the regenerate command it prints beside it. The two sibling parsers in
488
+ * this file refuse an unknown value and so does the sweep; the lenient branch
489
+ * was the inconsistent one.
490
+ */
491
+ export const RANKER_LABELS: Readonly<Record<string, RankOptions>> = {
492
+ 'keyword-v1': {},
493
+ 'keyword-v2': { includeTriggers: true },
494
+ 'when-to-use': { includeWhenToUse: true },
495
+ headings: { includeHeadings: true },
496
+ idf: { idfWeighting: true },
497
+ 'idf+when-to-use': { idfWeighting: true, includeWhenToUse: true },
498
+ };
499
+
500
+ /**
501
+ * The message carries NO tool name.
502
+ *
503
+ * Three scripts resolve `--ranker` through this module now, so a hardcoded
504
+ * prefix would print one tool's name when another was the failing command. The
505
+ * caller's own `process.stderr` line is where the command is identified.
506
+ */
507
+ export class UnknownRankerLabel extends Error {
508
+ constructor(label: string) {
509
+ super(
510
+ `unknown --ranker ${label || '(nothing)'}; ` +
511
+ `known: ${Object.keys(RANKER_LABELS).join(' | ')}`,
512
+ );
513
+ this.name = 'UnknownRankerLabel';
514
+ }
515
+ }
516
+
517
+ /** Whether `label` is one of the table's OWN keys — never an inherited one. */
518
+ export function isKnownRanker(label: string): boolean {
519
+ return Object.hasOwn(RANKER_LABELS, label);
520
+ }
521
+
522
+ export function rankOptionsFor(ranker: string): RankOptions {
523
+ // `Object.hasOwn`, not a truthiness check on the lookup: `RANKER_LABELS`
524
+ // is an object literal, so `constructor`, `toString` and `valueOf` resolve
525
+ // to inherited FUNCTIONS rather than to undefined. A plain `=== undefined`
526
+ // guard lets those three through, scores them as the baseline, and echoes
527
+ // the label into the report title — which is the whole defect this guard
528
+ // was added to close, surviving in its own fix.
529
+ if (!isKnownRanker(ranker)) throw new UnknownRankerLabel(ranker);
530
+ return RANKER_LABELS[ranker] as RankOptions;
531
+ }
532
+
533
+ export function measure(opts: {
534
+ ranker: string;
535
+ commit: string;
536
+ corpus?: CorpusName;
537
+ slice?: SliceName;
538
+ skillsDir?: string;
539
+ repo?: string;
540
+ }): RankerBaseline {
541
+ const repo = opts.repo ?? REPO;
542
+ const skillsDir = opts.skillsDir ?? SKILLS_DIR;
543
+ // Resolved through the one table, which throws on a label it does not know.
544
+ const rankOpts: RankOptions = rankOptionsFor(opts.ranker);
545
+ const corpus = opts.corpus ?? 'labelled';
546
+ const accuracy = measureAccuracy({ corpus, repo, skillsDir, rankOpts, slice: opts.slice ?? 'all' });
158
547
 
159
- const matrix = readMatrixPrompts(repo);
548
+ const cases = readMatrixCases(repo);
160
549
  let withResult = 0;
161
550
  let scoreSum = 0;
162
- for (const prompt of matrix) {
163
- const rows = rank(prompt, skillsDir, rankOpts);
551
+ for (const c of cases) {
552
+ const rows = rank(c.prompt, skillsDir, rankOpts);
164
553
  if (rows.length > 0) {
165
554
  withResult++;
166
555
  scoreSum += rows[0]![1];
@@ -172,37 +561,110 @@ export function measure(opts: { ranker: string; commit: string; skillsDir?: stri
172
561
  : 0;
173
562
 
174
563
  return {
175
- schema: 1,
564
+ schema: 2,
176
565
  ranker: opts.ranker,
177
566
  commit: opts.commit,
178
567
  skills_dir: path.relative(repo, skillsDir),
179
568
  skills_indexed: skillsIndexed,
180
- accuracy: {
181
- corpus_prompts: labelled.length,
182
- top1: labelled.length ? Math.round((top1 / labelled.length) * 1000) / 1000 : 0,
183
- top3: labelled.length ? Math.round((top3 / labelled.length) * 1000) / 1000 : 0,
184
- misses,
185
- denominator_note:
186
- 'tests/eval/corpus-dev.yaml + corpus-non-dev.yaml — the only expected-skill ground ' +
187
- 'truth in this tree. Small; a single prompt moves each rate by ~4 points.',
188
- },
569
+ accuracy,
189
570
  matrix_coverage: {
190
- corpus_prompts: matrix.length,
571
+ corpus_prompts: cases.length,
572
+ labelled_prompts: cases.filter((c) => (c.expected?.length ?? 0) > 0).length,
573
+ unlabelled_prompts: cases.filter((c) => c.expected?.length === 0).length,
574
+ missing_label_key: cases.filter((c) => c.expected === undefined).length,
191
575
  prompts_with_any_result: withResult,
192
576
  mean_top_score: withResult ? Math.round((scoreSum / withResult) * 100) / 100 : 0,
193
577
  note:
194
- 'tests/eval/routing-matrix/ is labelled with the expected RULE, never a skill, so ' +
195
- 'this arm carries no accuracy — only whether the ranker answers at all and how ' +
196
- 'confident its top answer is. Its size is measured here, not assumed: the roadmap ' +
197
- 'and five other sites still publish 496.',
578
+ 'Coverage over EVERY matrix case, labelled or not: whether the ranker answers at ' +
579
+ 'all and how confident its top answer is. Accuracy over the labelled subset is ' +
580
+ 'the `accuracy` arm under `--corpus routing-matrix`. Size is measured here, never ' +
581
+ 'asserted: 496 and 499 are both published elsewhere and both stale.',
198
582
  },
199
583
  };
200
584
  }
201
585
 
586
+ function parseCorpus(argv: readonly string[]): CorpusName {
587
+ const i = argv.indexOf('--corpus');
588
+ if (i === -1) return 'labelled';
589
+ const raw = argv[i + 1];
590
+ if (!raw || !CORPUS_NAMES.includes(raw as CorpusName)) {
591
+ throw new Error(
592
+ `measure_skill_ranker_baseline: --corpus expects one of ${CORPUS_NAMES.join(' | ')}, got ${raw ?? '(nothing)'}`,
593
+ );
594
+ }
595
+ return raw as CorpusName;
596
+ }
597
+
598
+ /**
599
+ * The `--ranker` value, or the baseline when the flag is absent.
600
+ *
601
+ * A PRESENT flag with no value after it is an error, not an omission — the
602
+ * sibling `--slice` has always refused that shape, and substituting the
603
+ * baseline for a malformed flag is the same silent-fallback defect one level
604
+ * up from the label table.
605
+ */
606
+ export function parseRanker(argv: readonly string[]): string {
607
+ const i = argv.indexOf('--ranker');
608
+ if (i === -1) return 'keyword-v1';
609
+ const raw = argv[i + 1];
610
+ if (!raw || raw.startsWith('--')) {
611
+ throw new Error(
612
+ `--ranker expects one of ${Object.keys(RANKER_LABELS).join(' | ')}, got ${raw ?? '(nothing)'}`,
613
+ );
614
+ }
615
+ return raw;
616
+ }
617
+
618
+ /**
619
+ * A flag's value, or `fallback` when the flag is absent.
620
+ *
621
+ * A PRESENT flag followed by nothing — or by the next flag — is an error. Both
622
+ * `--commit` and `--date` are echoed verbatim into published artifacts (the
623
+ * baseline's provenance field, the confusion report's own header), so
624
+ * `--commit --ranker idf` silently publishing `--ranker` as the commit is the
625
+ * same silent-substitution class `parseRanker` was written to close, two flags
626
+ * over in the same two functions.
627
+ */
628
+ export function parseFlagValue(argv: readonly string[], flag: string, fallback: string): string {
629
+ const i = argv.indexOf(flag);
630
+ if (i === -1) return fallback;
631
+ const raw = argv[i + 1];
632
+ if (!raw || raw.startsWith('--')) {
633
+ throw new Error(`${flag} expects a value, got ${raw ?? '(nothing)'}`);
634
+ }
635
+ return raw;
636
+ }
637
+
638
+ export function parseSlice(argv: readonly string[]): SliceName {
639
+ const i = argv.indexOf('--slice');
640
+ if (i === -1) return 'all';
641
+ const raw = argv[i + 1];
642
+ if (!raw || !SLICE_NAMES.includes(raw as SliceName)) {
643
+ throw new Error(
644
+ `measure_skill_ranker_baseline: --slice expects one of ${SLICE_NAMES.join(' | ')}, got ${raw ?? '(nothing)'}`,
645
+ );
646
+ }
647
+ return raw as SliceName;
648
+ }
649
+
202
650
  export function main(argv: readonly string[]): number {
203
- const ranker = argv.includes('--ranker') ? (argv[argv.indexOf('--ranker') + 1] ?? 'keyword-v1') : 'keyword-v1';
204
- const commit = argv.includes('--commit') ? (argv[argv.indexOf('--commit') + 1] ?? 'unknown') : 'unknown';
205
- const out = measure({ ranker, commit });
651
+ let corpus: CorpusName;
652
+ let slice: SliceName;
653
+ let ranker: string;
654
+ let commit: string;
655
+ let out: RankerBaseline;
656
+ try {
657
+ corpus = parseCorpus(argv);
658
+ slice = parseSlice(argv);
659
+ ranker = parseRanker(argv);
660
+ commit = parseFlagValue(argv, '--commit', 'unknown');
661
+ // Inside the same guard as the two parsers above: an unknown --ranker is
662
+ // the same class of mistake and now gets the same exit code.
663
+ out = measure({ ranker, commit, corpus, slice });
664
+ } catch (err) {
665
+ process.stderr.write(`${(err as Error).message}\n`);
666
+ return 2;
667
+ }
206
668
  process.stdout.write(`${JSON.stringify(out, null, 2)}\n`);
207
669
  return 0;
208
670
  }