@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
@@ -238,7 +238,11 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
238
238
  roadmap:set-step Flip ONE checkbox glyph safely (lock + line anchor +
239
239
  live invariant + survival check)
240
240
  roadmap:archive Archive completed roadmaps (branch-touched by default;
241
- --all for every complete one; --dry-run to preview)
241
+ --all for every complete one; --dry-run to preview;
242
+ bare [~] steps are carried to a follow-up first,
243
+ --no-carry to leave them blocking; owner-blocked
244
+ steps ask first; --owner-decision later --only <x>
245
+ parks that one roadmap)
242
246
  stubs:due Read-only: roadmap stubs past their `review_by:`
243
247
  date, plus the decisions routed to the owner.
244
248
  Writes nothing. Flags: --json, --counts,
@@ -342,7 +346,9 @@ Tier 2 — maintenance / internal (hooks, MCP, memory, telemetry):
342
346
  memory:check-proposal Run the admission gate on a memory proposal
343
347
  memory:learn Aggregate memory intake signals into the local learning
344
348
  sidecar (read-only; --write to emit). Usage: memory:learn [--intake-dir DIR]
345
- [--out-dir DIR] [--now ISO] [--write] [--format text|json]
349
+ [--out-dir DIR] [--now ISO] [--write] [--format text|json|status]
350
+ --format status prints the one-screen human view (verdict counts,
351
+ top preferred lessons, the hand-off); it refuses --write.
346
352
  analytics Local-only workspace analytics (emit|show|prune|migrate).
347
353
  Usage: analytics show [--window 30d|7d|24h] · analytics prune
348
354
  knowledge Global knowledge-card store (list|show|trace|forget|promote|
@@ -1456,7 +1462,15 @@ cmd_prune() {
1456
1462
  # `agent-config doctor` — read-only drift report against the manifest.
1457
1463
  # Surfaces missing / modified / foreign files. Exit 0 clean, 1 drift,
1458
1464
  # 2 manifest-absent. See scripts/_cli/cmd_doctor.ts.
1465
+ # `doctor neighbours` routes to its own entry point rather than a branch inside
1466
+ # cmd_doctor.ts, which is 2,000 lines over the source-size ceiling and cannot
1467
+ # grow. Routed here because the dispatcher is where verbs are already resolved.
1459
1468
  cmd_doctor() {
1469
+ if [[ "${1:-}" == "neighbours" ]]; then
1470
+ shift
1471
+ exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_doctor_neighbours.ts" "$@"
1472
+ return
1473
+ fi
1460
1474
  exec_ts "$PACKAGE_ROOT/src/scripts/_cli/cmd_doctor.ts" "$@"
1461
1475
  }
1462
1476
 
@@ -305,6 +305,17 @@ export const MERGEABLE_KEYS: readonly string[] = [
305
305
  // GUI; whitelisting decides only whether the value SURVIVES the merge
306
306
  // (ADR-271, road-to-design-intent-conformance 2.3).
307
307
  'design.fidelity_mode',
308
+ // `lean_projection.*` is a per-MACHINE setting and the install it governs is
309
+ // a user-scope one: the layer being thinned is `~/.claude/rules`, which
310
+ // belongs to the machine and not to any checkout. Without these two rows a
311
+ // consumer who wrote the opt-in into the user-global file got no error, no
312
+ // warning and no effect — the same silent-filter defect the two entries
313
+ // above repair, reaching a third key, and unsatisfiable-by-construction on
314
+ // an ADR-020 global-only install, where no project layer exists to carry it.
315
+ // Whitelisting decides only whether a value SURVIVES the merge: it sets
316
+ // nothing, and `installerThinsHost` still refuses a template-only value.
317
+ 'lean_projection.mode',
318
+ 'lean_projection.hosts',
308
319
  ];
309
320
 
310
321
  /** The shipped template, relative to the package root. */
@@ -98,16 +98,18 @@ export function build_claude_hook_matrix(manifest_path: string): ClaudeHookMatri
98
98
  const aliasesAll = (manifest['native_event_aliases'] ?? {}) as Record<string, unknown>;
99
99
  const aliases = ((aliasesAll['claude'] ?? {}) as Record<string, unknown>) || {};
100
100
 
101
- const ac_to_native: Record<string, string> = {};
102
- for (const [native, ac] of Object.entries(aliases)) {
103
- ac_to_native[String(ac)] = native;
104
- }
105
-
101
+ // Iterating the ALIAS map, not the event map, because the relation is
102
+ // many-natives-to-one-event and the inversion this replaced silently lost
103
+ // every native but the last. `PostToolUse` and `PostToolUseFailure` both
104
+ // lower to `post_tool_use`; building `ac_to_native` would have kept one of
105
+ // them and UNBOUND the other, turning a routing fix into a routing
106
+ // regression that no existing test could see. Pinned by
107
+ // `before_complete_failure_envelope.test.ts` ("both natives stay bound").
106
108
  const matrix: ClaudeHookMatrix = {};
107
- for (const [ac_event, concerns] of Object.entries(claude_events)) {
109
+ for (const [native, ac_raw] of Object.entries(aliases)) {
110
+ const ac_event = String(ac_raw);
111
+ const concerns = claude_events[ac_event];
108
112
  if (!concerns || (Array.isArray(concerns) && concerns.length === 0)) continue;
109
- const native = ac_to_native[ac_event];
110
- if (native === undefined) continue;
111
113
  // Bundle fast path (road-to-hook-latency-repair Phase 2): invoke the
112
114
  // precompiled dispatcher bundle directly — no bash shim, no CLI boot
113
115
  // (measured ~450-500 ms CLI vs ~110 ms bundle per event on a 1-vCPU
@@ -0,0 +1,241 @@
1
+ /**
2
+ * How a CHANGE touches the roadmap estate — the diff half, read once.
3
+ *
4
+ * `check_estate_count` answers two different questions with two different
5
+ * inputs. The count half reads the TREE and ratchets it against the base ref's
6
+ * tree. This module is the other half: it reads the DIFF and says what the
7
+ * change did — which roadmaps entered the active top level, which left it, which
8
+ * of the arrivals declared an exemption, and what that exemption says.
9
+ *
10
+ * WHY IT LIVES HERE AND NOT IN THE GATE. `check_source_size_budget` scores
11
+ * `Σ max(0, lines(f) − 1500)` over `src/**` and the gate crossed the ceiling
12
+ * when the exemption-shape work landed. That gate does not object to new code;
13
+ * it objects to new code in the file that is already too big to read, and the
14
+ * ratchet was right about which file that was. The seam it forced is the one a
15
+ * reader would draw anyway — tree-reading on one side of it, diff-reading on the
16
+ * other — so the extraction is a correction rather than a concession.
17
+ *
18
+ * `exemptionReason` sits here rather than beside the vocabulary it feeds
19
+ * (`exemption_shape.ts`) because this is the module that READS a change; that
20
+ * one is the module that JUDGES a reason. The split is read-then-judge, and
21
+ * keeping it means the vocabulary module imports no filesystem and no git.
22
+ */
23
+ import * as path from 'node:path';
24
+
25
+ import {
26
+ is_roadmap_candidate as isRoadmapCandidate,
27
+ parse_frontmatter as parseFrontmatter,
28
+ } from '../../agent-src/scripts/update_roadmap_progress.js';
29
+
30
+ /** How a change touched the active roadmap tree, as git reports it. */
31
+ export interface OffsetLedger {
32
+ /** Files that entered the active top level: new files, and un-parked ones. */
33
+ added: string[];
34
+ /** Files that left it: deleted, archived, parked or merged away. */
35
+ offsets: string[];
36
+ /** Added files carrying an `estate_offset_exempt:` reason, with the reason. */
37
+ exempt: Array<{ file: string; reason: string }>;
38
+ /**
39
+ * The subset of `offsets` that went to `later/`.
40
+ *
41
+ * Tracked separately because parking is the one offset that RAISES another
42
+ * gated count: active falls by one and later rises by one, which under an
43
+ * exact floor is growth in `later_roadmaps` unless this allowance exists.
44
+ * Kept out of a general "any offset raises any allowance" rule on purpose —
45
+ * an archived roadmap must not buy a new `later/` file.
46
+ */
47
+ parked: string[];
48
+ }
49
+
50
+ /** An `estate_growth_exempt:` reason ADDED to a roadmap in this change. */
51
+ export interface GrowthClaim {
52
+ /** The roadmap the claim was added to, as git reports the path. */
53
+ file: string;
54
+ reason: string;
55
+ }
56
+
57
+ /** `agents/roadmaps/<name>.md` — the active top level. code-comment-allow provenance-comment -- operand, not provenance */
58
+ function isActiveTopLevel(rel: string): boolean {
59
+ const norm = rel.split(path.sep).join('/');
60
+ if (!norm.startsWith('agents/roadmaps/') || !norm.endsWith('.md')) {
61
+ return false;
62
+ }
63
+ const tail = norm.slice('agents/roadmaps/'.length);
64
+ return !tail.includes('/') && isRoadmapCandidate(norm);
65
+ }
66
+
67
+ /**
68
+ * A disposition directory — where an offset sends a roadmap.
69
+ *
70
+ * `stubs/` is in the set, and it was missing from the first version. Un-stubbing
71
+ * is the documented promotion path, so a stub moved to the top level is an
72
+ * ADDITION that T3 must charge, and a roadmap demoted to a stub is an offset.
73
+ * With `stubs/` unrecognised, a promotion was classified as neither and the lint
74
+ * could never charge it — the one hole that let an active roadmap arrive for free.
75
+ */
76
+ function isDisposed(rel: string): boolean {
77
+ const norm = rel.split(path.sep).join('/');
78
+ return /^agents\/roadmaps\/(archive|later|skipped|stubs)\//.test(norm);
79
+ }
80
+
81
+ /** A roadmap parked for later — the one disposition that grows another count. */
82
+ function isParked(rel: string): boolean {
83
+ return rel.split(path.sep).join('/').startsWith('agents/roadmaps/later/');
84
+ }
85
+
86
+ /**
87
+ * Read the exemption reason a newly added roadmap declares, if any.
88
+ *
89
+ * The key lives in the file's own frontmatter rather than in a config or a
90
+ * commit trailer, for the reason `RATCHET_RESET_KEY` gives for living inside the
91
+ * baseline JSON: the claim then shows up in the diff of the change that makes
92
+ * it, and a reviewer sees it without being told to look.
93
+ *
94
+ * THE BLOCK SCALAR IS READ HERE AND NOT BY `parseFrontmatter`, which is a flat
95
+ * line parser with no YAML block support: it returns the value as everything
96
+ * after the first colon. Measured 2026-10-01 across the three roadmap trees,
97
+ * **38 of 221 exemptions are written `estate_offset_exempt: >-` with the reason
98
+ * indented beneath**, and for every one of them this function used to return
99
+ * the literal two-character string `>-`. That is non-empty, so the gate
100
+ * accepted it — a key whose whole purpose is that a reviewer sees the claim was
101
+ * being checked against a YAML punctuation mark on one file in six. The fix
102
+ * belongs here rather than in the shared parser because every other caller of
103
+ * that parser reads single-token values (`status`, `complexity`) where block
104
+ * support buys nothing and a rewrite risks the dashboard.
105
+ */
106
+ export function exemptionReason(text: string): string | null {
107
+ const block = blockScalar(text, 'estate_offset_exempt');
108
+ if (block !== null) return block === '' ? null : block;
109
+ const fm = parseFrontmatter(text);
110
+ const raw = (fm as Record<string, unknown>)['estate_offset_exempt'];
111
+ if (typeof raw !== 'string') {
112
+ return null;
113
+ }
114
+ const reason = raw.trim().replace(/^["']|["']$/g, '').trim();
115
+ return reason === '' ? null : reason;
116
+ }
117
+
118
+ /**
119
+ * The folded body of `<key>: >-` / `<key>: |`, or `null` when not that form.
120
+ *
121
+ * Returns `''` — not `null` — for a block header with no body, so the caller can
122
+ * tell "declared and empty" from "not a block scalar". An empty block is the
123
+ * blank-reason case the key already refuses, and collapsing it into `null` here
124
+ * would send it back to the flat parser, which would hand back `>-` and accept it.
125
+ */
126
+ function blockScalar(text: string, key: string): string | null {
127
+ const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
128
+ if (m === null) return null;
129
+ const lines = (m[1] as string).split(/\r?\n/);
130
+ const head = new RegExp(`^${key}:[ \\t]*(.*)$`);
131
+ for (let i = 0; i < lines.length; i += 1) {
132
+ const hm = head.exec(lines[i] as string);
133
+ if (hm === null) continue;
134
+ // Only the block indicators. A quoted or bare value is the flat form and
135
+ // stays with the flat parser, which already handles it.
136
+ //
137
+ // THE TRAILING COMMENT IS PART OF THE HEADER, and omitting it was a
138
+ // BYPASS rather than a parse gap. `estate_offset_exempt: >- # offset`
139
+ // failed this test, fell through to the flat parser, and came back as
140
+ // the string `">- # offset"` — which the shape rule then searched for a
141
+ // disposition word and found one, IN THE COMMENT, over a body it never
142
+ // read. The indicator may also be written `>2-` as well as `>-2`, so
143
+ // both orders are accepted; an indentation indicator is never `0`.
144
+ if (!/^[>|](?:[1-9][-+]?|[-+]?[1-9]?)(?:[ \t]+#.*)?$/.test((hm[1] as string).trim())) return null;
145
+ const body: string[] = [];
146
+ for (let j = i + 1; j < lines.length; j += 1) {
147
+ const line = lines[j] as string;
148
+ if (line.trim() === '') continue;
149
+ // The first unindented line ends the block — the next key.
150
+ if (!/^[ \t]/.test(line)) break;
151
+ body.push(line.trim());
152
+ }
153
+ return body.join(' ').replace(/\s+/g, ' ').trim();
154
+ }
155
+ return null;
156
+ }
157
+
158
+ /**
159
+ * Classify the change's effect on the active roadmap tree.
160
+ *
161
+ * Renames carry information a name-only diff loses: `road-to-x.md` →
162
+ * `archive/road-to-x.md` is the wanted direction and counts as an offset, while
163
+ * `later/road-to-x.md` → `road-to-x.md` is an un-parking and counts as an
164
+ * addition. A top-level-to-top-level rename is neither.
165
+ */
166
+ export function classifyDiff(
167
+ nameStatus: string,
168
+ readFile: (rel: string) => string | null,
169
+ ): OffsetLedger {
170
+ const added: string[] = [];
171
+ const offsets: string[] = [];
172
+ const exempt: Array<{ file: string; reason: string }> = [];
173
+ const parked: string[] = [];
174
+ for (const line of nameStatus.split('\n')) {
175
+ if (line.trim() === '') continue;
176
+ const cols = line.split('\t');
177
+ const status = (cols[0] ?? '').trim();
178
+ if (status.startsWith('R') || status.startsWith('C')) {
179
+ const from = cols[1] ?? '';
180
+ const to = cols[2] ?? '';
181
+ if (isActiveTopLevel(from) && isDisposed(to)) {
182
+ offsets.push(from);
183
+ if (isParked(to)) parked.push(to);
184
+ } else if (isDisposed(from) && isActiveTopLevel(to)) {
185
+ added.push(to);
186
+ }
187
+ continue;
188
+ }
189
+ const file = cols[1] ?? '';
190
+ if (!isActiveTopLevel(file)) continue;
191
+ if (status === 'A') {
192
+ added.push(file);
193
+ } else if (status === 'D') {
194
+ offsets.push(file);
195
+ }
196
+ }
197
+ for (const file of added) {
198
+ const text = readFile(file);
199
+ if (text === null) continue;
200
+ const reason = exemptionReason(text);
201
+ if (reason !== null) {
202
+ exempt.push({ file, reason });
203
+ }
204
+ }
205
+ return { added, offsets, exempt, parked };
206
+ }
207
+
208
+ /**
209
+ * The `estate_growth_exempt:` reasons this change ADDS, read from the patch.
210
+ *
211
+ * Read from the diff rather than from the file, and that is the point: a claim
212
+ * sitting in a roadmap authorises nothing on a later change, so an exemption
213
+ * cannot be banked the way surplus in a stored baseline could. It also means a
214
+ * newly added roadmap and an edited one need no separate handling — in a
215
+ * `base...HEAD` patch both arrive as `+` lines.
216
+ *
217
+ * `--unified=0` keeps context lines out, so a claim that merely sits NEAR an
218
+ * edited line is not read as added. Deliberately tolerant of leading whitespace
219
+ * and of quoted values, matching `exemptionReason`; deliberately NOT tolerant of
220
+ * an empty reason, because an exemption whose reason is blank is the silent
221
+ * exception the key exists to replace.
222
+ */
223
+ export function growthClaims(patch: string): GrowthClaim[] {
224
+ const out: GrowthClaim[] = [];
225
+ let file = '';
226
+ for (const line of patch.split('\n')) {
227
+ // `+++ b/<path>` names the file the following `+` lines belong to. The
228
+ // `/dev/null` form is a deletion, which cannot carry a claim.
229
+ const head = /^\+\+\+ b\/(.+)$/.exec(line);
230
+ if (head !== null) {
231
+ file = (head[1] ?? '').trim();
232
+ continue;
233
+ }
234
+ if (!line.startsWith('+') || line.startsWith('+++')) continue;
235
+ const m = /^\+\s*estate_growth_exempt:\s*(.+?)\s*$/.exec(line);
236
+ if (m === null) continue;
237
+ const reason = (m[1] ?? '').trim().replace(/^["']|["']$/g, '').trim();
238
+ if (reason !== '') out.push({ file, reason });
239
+ }
240
+ return out;
241
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * What an `estate_offset_exempt` reason has to SAY — one vocabulary, two readers.
3
+ *
4
+ * THE DEFECT THIS EXISTS TO NAME. The one-in-one-out rule charges every added
5
+ * active roadmap against a disposed one, and `estate_offset_exempt: <reason>`
6
+ * is the escape hatch that costs "one reviewable line instead of a silent
7
+ * exception". `exemptionReason` accepted any non-empty string, so the line was
8
+ * reviewable in the sense that it existed. Measured 2026-10-01 over the active,
9
+ * `later/` and `archive/` trees: 221 files carry the key, and 43 of them name
10
+ * no alternative disposition at all under the three-lemma set the roadmap named
11
+ * from memory, and 6 under the measured one below — including three that say only
12
+ * `lane N of <parent>`. The escape hatch was free.
13
+ *
14
+ * WHAT A SHAPED REASON SAYS. It names at least one disposition the author
15
+ * considered instead of adding a file: archiving something, parking it in
16
+ * `later/`, merging into an existing roadmap, or any of the words this tree
17
+ * actually uses for those three. The requirement is deliberately NOT "explain
18
+ * yourself well" — that is unjudgeable — but "name what you rejected", which is
19
+ * a word that is either present or absent.
20
+ *
21
+ * WHY THE VOCABULARY IS MEASURED AND NOT CHOSEN. The commissioning roadmap
22
+ * named three lemmas, archive / park / merge, from memory. Over the real
23
+ * population those three cover 178 of 221; the measured ten below cover 215.
24
+ * The 37-file gap is not boilerplate — "Offsetting it individually is not
25
+ * possible without splitting the set" and "no completed roadmap to retire
26
+ * against this addition" both name a rejected alternative in words the
27
+ * three-lemma set does not carry. A refusal vocabulary that reds one added
28
+ * roadmap in five on the house style gets weakened until it finds nothing, so
29
+ * the set here is the one the tree writes. Every number above carries its
30
+ * producing command: `./scripts-run src/scripts/estate_exemption_shape`.
31
+ *
32
+ * `close` is excluded on purpose, and it is the most common verb in these
33
+ * reasons: it almost always refers to closing WORK, not to disposing of a file,
34
+ * so accepting it would make the check pass on a sentence that named nothing.
35
+ *
36
+ * WHAT THIS DOES NOT DO, stated because a reviewer called it HIGH and the
37
+ * disagreement is worth recording rather than resolving silently. The cheapest
38
+ * string that passes is the single word `park`, and `park 1` / `park 2` are two
39
+ * distinct reasons to the duplicate rule. So this verifies that a disposition
40
+ * word APPEARS; it does not verify that the author thought about one, and no
41
+ * check can. The claim the gate's message makes is therefore "name what you
42
+ * rejected", which is what a word either does or does not do — not "explain
43
+ * yourself well", which is unjudgeable. A reason gamed down to one word is
44
+ * visible to a human reviewer in the diff, which is where the key's own stated
45
+ * purpose puts the real control; what this removes is the case where there was
46
+ * nothing to look at because the reason was free text nobody had read.
47
+ *
48
+ * WHY ONE MODULE. Two readers consume this — the estate gate, which refuses,
49
+ * and the measurement script, which reports. A second copy of the lemma list is
50
+ * how a gate and its own evidence drift apart, and a drifted vocabulary fails
51
+ * silently: the report says the tree conforms while the gate refuses it.
52
+ */
53
+
54
+ /**
55
+ * The dispositions a reason may name, as source strings.
56
+ *
57
+ * Exported individually rather than as one baked regex so the measurement
58
+ * script can report per-lemma counts against the same list the gate refuses on.
59
+ */
60
+ export const DISPOSITION_LEMMAS: readonly { readonly name: string; readonly source: string }[] = [
61
+ { name: 'archive', source: String.raw`archiv\w*` },
62
+ // `park` is the one stem with a common false friend, and a reviewer named
63
+ // it: `park\w*` matches `parkour` at a word boundary, so a reason saying
64
+ // nothing about dispositions passes on a word that is not one. Enumerated
65
+ // instead of stemmed — the inflections this tree writes, and no more. Every
66
+ // other lemma keeps its stem; none of them has a false friend in English
67
+ // that could plausibly appear in a sentence about roadmap dispositions.
68
+ { name: 'park', source: String.raw`unpark(?:ed|ing|s)?\b|park(?:ed|ing|s)?\b` },
69
+ { name: 'merge', source: String.raw`merg\w*` },
70
+ { name: 'later', source: String.raw`later\/` },
71
+ { name: 'offset', source: String.raw`offset\w*` },
72
+ { name: 'defer', source: String.raw`defer\w*` },
73
+ { name: 'fold', source: String.raw`fold\w*` },
74
+ { name: 'consolidate', source: String.raw`consolidat\w*` },
75
+ { name: 'absorb', source: String.raw`absorb\w*` },
76
+ { name: 'retire', source: String.raw`retir\w*` },
77
+ ];
78
+
79
+ /** The whole vocabulary as one alternation — the gate's own test. */
80
+ export const DISPOSITION_SOURCE = `\\b(?:${DISPOSITION_LEMMAS.map((l) => l.source).join('|')})`;
81
+
82
+ const DISPOSITION_RE = new RegExp(DISPOSITION_SOURCE, 'i');
83
+
84
+ /** Does this reason name at least one rejected alternative? */
85
+ export function namesDisposition(reason: string): boolean {
86
+ return DISPOSITION_RE.test(reason);
87
+ }
88
+
89
+ /** Which lemmas a reason names, for the reader's per-file column. */
90
+ export function lemmasNamed(reason: string): string[] {
91
+ return DISPOSITION_LEMMAS.filter((l) => new RegExp(`\\b(?:${l.source})`, 'i').test(reason)).map((l) => l.name);
92
+ }
93
+
94
+ /**
95
+ * The comparison key for "two added files say the same thing".
96
+ *
97
+ * Case and whitespace are folded because a reason pasted into a second file and
98
+ * re-wrapped by an editor is the same reason. Nothing else is folded: a reason
99
+ * that differs by one clause is a different reason, and a normaliser clever
100
+ * enough to call those equal would start refusing reasons that genuinely differ.
101
+ */
102
+ export function reasonKey(reason: string): string {
103
+ return reason.toLowerCase().replace(/\s+/g, ' ').trim();
104
+ }
105
+
106
+ /** One added file whose exemption reason does not hold up. */
107
+ export interface ExemptionFinding {
108
+ readonly file: string;
109
+ readonly kind: 'shapeless' | 'duplicate';
110
+ /** For `duplicate`, the other added file carrying the same reason. */
111
+ readonly twin: string | null;
112
+ readonly reason: string;
113
+ }
114
+
115
+ /**
116
+ * Judge the exemptions a single change ADDS — never the ones already in the tree.
117
+ *
118
+ * Grandfathering is load-bearing rather than polite. 43 of the 221 existing
119
+ * reasons name no disposition; re-reading them would land this check red on a
120
+ * fifth of the corpus, which is a backlog wearing a control's clothes. The
121
+ * input is `classifyDiff`'s exempt ledger, which by construction lists only the
122
+ * files this change added.
123
+ */
124
+ export function exemptionFindings(
125
+ exempt: readonly { readonly file: string; readonly reason: string }[],
126
+ ): ExemptionFinding[] {
127
+ const out: ExemptionFinding[] = [];
128
+ const seen = new Map<string, string>();
129
+ for (const e of exempt) {
130
+ if (!namesDisposition(e.reason)) {
131
+ out.push({ file: e.file, kind: 'shapeless', twin: null, reason: e.reason });
132
+ continue;
133
+ }
134
+ const key = reasonKey(e.reason);
135
+ const twin = seen.get(key);
136
+ if (twin === undefined) {
137
+ seen.set(key, e.file);
138
+ continue;
139
+ }
140
+ out.push({ file: e.file, kind: 'duplicate', twin, reason: e.reason });
141
+ }
142
+ return out;
143
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * FNV-1a, 32-bit — the one definition in this tree.
3
+ *
4
+ * Extracted from `rule_trigger_eval.ts`, which still re-exports it under its
5
+ * own name so its suite keeps importing the symbol it always did. The reason
6
+ * for the extraction is the reason this file carries a doc block at all: a
7
+ * second consumer arrived (`measure_skill_ranker_baseline`'s held-out
8
+ * partition), and two copies of a hash that decides which rows a measurement
9
+ * reads is the shape in which a partition quietly stops being the same
10
+ * partition. One definition, two callers.
11
+ *
12
+ * Pure — no imports, no I/O, no clock.
13
+ */
14
+
15
+ /** FNV-1a over UTF-16 code units, returned as an unsigned 32-bit integer. */
16
+ export function fnv1a(s: string): number {
17
+ let h = 0x811c9dc5;
18
+ for (let i = 0; i < s.length; i += 1) {
19
+ h ^= s.charCodeAt(i);
20
+ h = Math.imul(h, 0x01000193) >>> 0;
21
+ }
22
+ return h >>> 0;
23
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * The unclaimed-file scan under this package's own deploy roots.
3
+ *
4
+ * Moved verbatim out of `src/scripts/_cli/cmd_doctor.ts`
5
+ * (road-to-a-tree-that-keeps-its-neighbours Phase 2.6). The behaviour is
6
+ * unchanged — this is a module boundary, not a rewrite.
7
+ *
8
+ * Two reasons it earns its own file. `cmd_doctor.ts` is 2,100 lines above the
9
+ * `check_source_size_budget` ceiling, so the census line that now links this
10
+ * listing is paid for by a MOVE rather than by raising the tree-wide excess.
11
+ * And the neighbour census answers a strictly larger question than this scan
12
+ * does — this one lists FILES under paths this package deploys to, the census
13
+ * lists ARTEFACTS wherever a host keeps them — so the two belong beside each
14
+ * other rather than nested inside a doctor renderer.
15
+ *
16
+ * What this scan does NOT do, stated because the two listings look alike: it
17
+ * never leaves a deploy root, so a neighbour's skill in `~/.claude/skills` or
18
+ * hook entry in `.cursor/hooks.json` is invisible to it. That gap is the
19
+ * census's subject, and the census links back here rather than repeating the
20
+ * paths this one already prints.
21
+ */
22
+
23
+ import * as fs from 'node:fs';
24
+
25
+ /**
26
+ * `Path.rglob("*")` — every descendant path (dirs + files), recursively.
27
+ *
28
+ * Order is not relied upon by callers (they sort the matched set), but the
29
+ * walk is deterministic (sorted per directory) to keep behaviour stable.
30
+ */
31
+ export function rglob(root: string, isDir: (p: string) => boolean, join: (a: string, b: string) => string): string[] {
32
+ const out: string[] = [];
33
+ const walk = (dir: string): void => {
34
+ let entries: fs.Dirent[];
35
+ try {
36
+ entries = fs.readdirSync(dir, { withFileTypes: true });
37
+ } catch {
38
+ return;
39
+ }
40
+ entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
41
+ for (const ent of entries) {
42
+ const full = join(dir, ent.name);
43
+ out.push(full);
44
+ let dirLike = ent.isDirectory();
45
+ if (ent.isSymbolicLink()) {
46
+ // pathlib rglob follows into symlinked dirs.
47
+ dirLike = isDir(full);
48
+ }
49
+ if (dirLike) {
50
+ walk(full);
51
+ }
52
+ }
53
+ };
54
+ walk(root);
55
+ return out;
56
+ }
57
+
58
+ export interface ForeignScanDeps {
59
+ pathExists: (p: string) => boolean;
60
+ isDir: (p: string) => boolean;
61
+ isFile: (p: string) => boolean;
62
+ resolvePath: (p: string) => string;
63
+ join: (a: string, b: string) => string;
64
+ /** Resolve one declared deploy root against the consumer root. */
65
+ resolveRoot: (projectRoot: string, rel: string) => string;
66
+ defaultRoots: readonly string[];
67
+ }
68
+
69
+ /**
70
+ * Walk every declared deploy root and surface unclaimed files. Only regular
71
+ * files under `deploy_roots` count; bookkeeping is on the resolved final path.
72
+ */
73
+ export function scanForeign(
74
+ projectRoot: string,
75
+ manifest: Record<string, unknown>,
76
+ known: Set<string>,
77
+ deps: ForeignScanDeps,
78
+ ): string[] {
79
+ const roots = (manifest['deploy_roots'] as unknown[] | undefined) || Array.from(deps.defaultRoots);
80
+ const foreign: string[] = [];
81
+ const seen = new Set<string>();
82
+ for (const root_rel of roots) {
83
+ const root = deps.resolveRoot(projectRoot, String(root_rel));
84
+ if (!deps.pathExists(root) || !deps.isDir(root)) {
85
+ continue;
86
+ }
87
+ for (const child of rglob(root, deps.isDir, deps.join)) {
88
+ if (!deps.isFile(child)) {
89
+ continue;
90
+ }
91
+ let resolved: string;
92
+ try {
93
+ resolved = deps.resolvePath(child);
94
+ } catch {
95
+ resolved = child;
96
+ }
97
+ if (known.has(resolved) || seen.has(resolved)) {
98
+ continue;
99
+ }
100
+ seen.add(resolved);
101
+ foreign.push(child);
102
+ }
103
+ }
104
+ foreign.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
105
+ return foreign;
106
+ }