@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
@@ -0,0 +1,538 @@
1
+ /**
2
+ * What a host actually loads, measured in the host's own unit (step 0.1 of
3
+ * `road-to-a-rule-carrier-that-works-outside-the-repo`).
4
+ *
5
+ * THE QUESTION NOTHING IN THIS TREE COULD ANSWER. Every existing figure for the
6
+ * installed layer counts raw bytes of whole files: `censusRuleDir` sums
7
+ * `statSync().size`, `charsAtRoot` does the same for a directory, and
8
+ * `check_preamble_payload_budget` reads both. A host does not load frontmatter
9
+ * as instruction — it is the routing surface, which the router has already
10
+ * read — and it does not render HTML comments, which are authoring scaffolding.
11
+ * So the number a reader wants when they ask "how much instruction is this
12
+ * consumer under" has never been computed here.
13
+ *
14
+ * THE NUMBER IS THEREFORE NOT COMPARABLE WITH ANY PUBLISHED BASELINE, and that
15
+ * is stated here rather than discovered by someone holding the two side by
16
+ * side. `chars` below is strictly smaller than the byte figures in
17
+ * `budgets.yml`, `rule-activation-census.json` or the payload ratchet, and the
18
+ * gap is not a constant — it is whatever that particular corpus spends on
19
+ * frontmatter and comments.
20
+ *
21
+ * ONE LIBRARY, FOUR CONSUMERS. The report CLI is the only one wired in this
22
+ * change (Phase 0 makes no behavior change), and the shape is the reason the
23
+ * other three named in step 0.1 — CI, `doctor`, the upgrade receipt — can use
24
+ * it without a second reader: every input is a PARAMETER. `home`, `projectRoot`
25
+ * and `manifestPath` are supplied by the caller, never read off the process, so
26
+ * a test, a temporary `HOME` and a real machine are the same call with
27
+ * different arguments. That is also what makes the step's "install into a
28
+ * temporary HOME" reachable at all, and it is the fact that refutes
29
+ * `check_standing_rule_delivery`'s header: a user-scope install IS observable
30
+ * from CI, because the installer takes `HOME` from the environment.
31
+ *
32
+ * WHAT `package_owned` AND `foreign` MEAN, because the honest answer is three
33
+ * valued and the report has two columns. `recordedOwnership` distinguishes
34
+ * `recorded-unchanged`, `recorded-modified` and `unknown`, where `unknown` is
35
+ * explicitly "this tree cannot say" rather than "foreign". The report folds the
36
+ * first two into `package_owned` — both are files the manifest claims — and
37
+ * reports the third as `foreign`, which therefore reads "not claimed by this
38
+ * install", never "written by someone else". A machine with no manifest reports
39
+ * every file foreign, which is the correct reading of no evidence and is why
40
+ * {@link InstalledLayerReport.manifest_present} is in the output.
41
+ */
42
+
43
+ import * as fs from 'node:fs';
44
+ import * as path from 'node:path';
45
+ import { fileURLToPath } from 'node:url';
46
+
47
+ import {
48
+ GLOBAL_RULE_DIRS,
49
+ PROJECT_RULE_DIRS,
50
+ globalRuleLayerPath,
51
+ } from '../../install/globalRuleLayers.js';
52
+ import { NO_RECORDED_HASHES, readRecordedHashes } from '../../install/recordedOwnership.js';
53
+ import { ruleBody } from './rule_law_section.js';
54
+
55
+ /** How many files the per-directory ranking carries. Step 0.1 fixes it at 20. */
56
+ export const TOP_FILES = 20;
57
+
58
+ /** Which layer a directory belongs to — the two the host unions when it loads. */
59
+ export type LayerScope = 'global' | 'project';
60
+
61
+ /** One rule file, measured the way the host loads it. */
62
+ export interface RuleFileReading {
63
+ /** Basename, which is what a reader opening the directory sees. */
64
+ readonly file: string;
65
+ /** Characters AFTER the frontmatter and block-comment strip. */
66
+ readonly chars: number;
67
+ /** False when the file carries a `paths:` key — then the host loads it on a path match. */
68
+ readonly unconditional: boolean;
69
+ /** True when the installed-tools manifest records this path, in either state. */
70
+ readonly package_owned: boolean;
71
+ }
72
+
73
+ /** One host rule directory, which is the unit step 0.1 asks the report to be in. */
74
+ export interface LayerReading {
75
+ /** Tool id, as `GLOBAL_RULE_DIRS` spells it. */
76
+ readonly host: string;
77
+ readonly scope: LayerScope;
78
+ readonly dir: string;
79
+ /** False when the directory does not exist — a real answer, never an error. */
80
+ readonly present: boolean;
81
+ readonly files: number;
82
+ readonly unconditional: number;
83
+ readonly chars: number;
84
+ readonly package_owned: number;
85
+ readonly foreign: number;
86
+ /**
87
+ * The same split as `package_owned` / `foreign`, in CHARACTERS.
88
+ *
89
+ * Both axes are kept because they answer different questions and the file
90
+ * counts alone cannot answer the one step 1.4 asks. "How many of the files
91
+ * does this install claim" is a reaping question; "how much of what the
92
+ * host loads does this install account for" is a budget question, and one
93
+ * 30,000-character rule against twenty 200-character ones makes the two
94
+ * readings disagree by an order of magnitude. The limit rows below are
95
+ * built from the character axis for exactly that reason.
96
+ */
97
+ readonly package_owned_chars: number;
98
+ readonly foreign_chars: number;
99
+ /** The {@link TOP_FILES} largest, by the SAME measure as `chars`. */
100
+ readonly top: readonly RuleFileReading[];
101
+ }
102
+
103
+ /**
104
+ * The fraction of a published limit at which the report warns.
105
+ *
106
+ * 0.8 is step 1.4's number, not a tuned one. It is a WARNING threshold and
107
+ * never a gate: crossing it changes what the report says and nothing else, so
108
+ * no install fails here.
109
+ */
110
+ export const LIMIT_WARN_FRACTION = 0.8;
111
+
112
+ /** What this tree can say about a host's published instruction budget. */
113
+ export type HostLimitKind = 'vendor-published' | 'reported' | 'unpublished';
114
+
115
+ /**
116
+ * One host, measured against its published limit — step 3.5's per-host row.
117
+ *
118
+ * The row is per HOST and not per directory, because the limit applies to what
119
+ * the host loads and the host unions its global and project layers. A host
120
+ * whose two directories are each comfortably under the limit can still be over
121
+ * it together, and a per-directory row would never show that.
122
+ */
123
+ export interface HostLimitReading {
124
+ readonly host: string;
125
+ /** `null` when this tree records no limit — which fires no warning. */
126
+ readonly limit: number | null;
127
+ readonly kind: HostLimitKind;
128
+ /** Every scope for this host, summed. */
129
+ readonly chars: number;
130
+ readonly package_owned_chars: number;
131
+ readonly foreign_chars: number;
132
+ /** `chars / limit`, or `null` when there is no limit to be a fraction of. */
133
+ readonly fraction: number | null;
134
+ /**
135
+ * True only when a limit EXISTS and the combined total reaches
136
+ * {@link LIMIT_WARN_FRACTION} of it — 80 %, not 100 %. The field is an
137
+ * early warning, which is the whole point of a fraction below 1.
138
+ *
139
+ * An absent limit never warns. That is the conservative direction and it is
140
+ * deliberate: a warning derived from a limit nobody recorded would be a
141
+ * fabricated finding, which is worse than a silent row a reader can see is
142
+ * unmeasured.
143
+ */
144
+ readonly warn: boolean;
145
+ }
146
+
147
+ export interface InstalledLayerReport {
148
+ readonly home: string;
149
+ readonly project_root: string;
150
+ /** `claude --version`, or `unknown`. Never fabricated. */
151
+ readonly host_version: string;
152
+ /** False when no installed-tools manifest resolved — then every file reads foreign. */
153
+ readonly manifest_present: boolean;
154
+ readonly layers: readonly LayerReading[];
155
+ /** One row per host, against its published limit. Step 3.5. */
156
+ readonly limits: readonly HostLimitReading[];
157
+ /** Every layer summed, which is what the host loads when it unions the two scopes. */
158
+ readonly totals: {
159
+ readonly files: number;
160
+ readonly unconditional: number;
161
+ readonly chars: number;
162
+ readonly package_owned: number;
163
+ readonly foreign: number;
164
+ readonly package_owned_chars: number;
165
+ readonly foreign_chars: number;
166
+ };
167
+ }
168
+
169
+ /**
170
+ * Whether the host loads this file on every session rather than on a path match.
171
+ *
172
+ * `paths:` at the start of a line is the whole test, and it is the host's own
173
+ * convention rather than this package's: `claudeRuleRewrite.ts` states it from
174
+ * the emitting side — "with it a rule loads on a path match, without it the
175
+ * rule loads unconditionally". `rule_activation_census.projection_reading` asks
176
+ * the installed tree the same question with the same regex, so a file counted
177
+ * unconditional here is counted unconditional there.
178
+ *
179
+ * SOURCE-SIDE CLASSIFICATION IS A DIFFERENT QUESTION and deliberately not used.
180
+ * `classify_rule` reads `alwaysApply` and `type: always` to tell `always` from
181
+ * `unconditional`, which matters when you are deciding what to emit. This
182
+ * reads what WAS emitted, where the distinction has already been spent: an
183
+ * Iron-Law rule and an untriggered tier-2 rule both arrive without `paths:` and
184
+ * both load every session, which is the only property the host acts on.
185
+ */
186
+ export function isUnconditional(text: string): boolean {
187
+ return !/^paths:/m.test(text);
188
+ }
189
+
190
+ /**
191
+ * Measure one directory.
192
+ *
193
+ * `.md` only, matching `censusRuleDir` and `topRulesByCost`, so the FILE count
194
+ * is comparable with the existing byte censuses even though the character count
195
+ * is not. Cursor's `.mdc` tree is therefore read as zero files, which is the
196
+ * same blind spot every other census in this tree has and is not fixed here —
197
+ * fixing it in one reader and not the others would make two counts that
198
+ * disagree, which is worse than one that is openly partial.
199
+ */
200
+ export function readLayer(
201
+ host: string,
202
+ scope: LayerScope,
203
+ dir: string,
204
+ recorded: ReadonlyMap<string, string | null>,
205
+ ): LayerReading {
206
+ const empty = {
207
+ files: 0,
208
+ unconditional: 0,
209
+ chars: 0,
210
+ package_owned: 0,
211
+ foreign: 0,
212
+ package_owned_chars: 0,
213
+ foreign_chars: 0,
214
+ top: [],
215
+ };
216
+ let names: string[];
217
+ try {
218
+ names = fs.readdirSync(dir);
219
+ } catch {
220
+ return { host, scope, dir, present: false, ...empty };
221
+ }
222
+ const readings: RuleFileReading[] = [];
223
+ for (const name of names.sort()) {
224
+ if (!name.endsWith('.md')) continue;
225
+ const abs = path.join(dir, name);
226
+ let text: string;
227
+ try {
228
+ const st = fs.statSync(abs);
229
+ if (!st.isFile()) continue;
230
+ text = fs.readFileSync(abs, 'utf-8');
231
+ } catch {
232
+ continue; // a file that cannot be read is not a file the host loads
233
+ }
234
+ // RECORDED-BUT-MODIFIED IS STILL RECORDED. The report's two columns
235
+ // answer "does this install claim the file"; `classifyOwnership` answers
236
+ // the finer question the upgrade path needs — whether the bytes still
237
+ // match — and folding that in here would give a column three meanings.
238
+ const owned = recorded.has(abs);
239
+ readings.push({
240
+ file: name,
241
+ // THE STRIP IS THE PROJECTOR'S OWN. `ruleBody` is what
242
+ // `project_thin_rules` and the delivery carrier both call, so "what
243
+ // a host loads" and "what a delivery carries" cannot drift into two
244
+ // spellings of the same measurement.
245
+ chars: ruleBody(text).length,
246
+ unconditional: isUnconditional(text),
247
+ package_owned: owned,
248
+ });
249
+ }
250
+ const top = [...readings]
251
+ // RANKED BY THE SAME MEASURE AS THE TOTAL, which is why
252
+ // `preamble_byte_census.topRulesByCost` is not reused here: it ranks by
253
+ // `statSync().size`, so its list and this total would be in different
254
+ // units and the largest file by one could be absent from the other.
255
+ .sort((a, b) => b.chars - a.chars || a.file.localeCompare(b.file))
256
+ .slice(0, TOP_FILES);
257
+ return {
258
+ host,
259
+ scope,
260
+ dir,
261
+ present: true,
262
+ files: readings.length,
263
+ unconditional: readings.filter((r) => r.unconditional).length,
264
+ chars: readings.reduce((n, r) => n + r.chars, 0),
265
+ package_owned: readings.filter((r) => r.package_owned).length,
266
+ foreign: readings.filter((r) => !r.package_owned).length,
267
+ package_owned_chars: readings.reduce((n, r) => n + (r.package_owned ? r.chars : 0), 0),
268
+ foreign_chars: readings.reduce((n, r) => n + (r.package_owned ? 0 : r.chars), 0),
269
+ top,
270
+ };
271
+ }
272
+
273
+ /** The shape of one entry in `src/config/host-instruction-limits.json`. */
274
+ interface RawHostLimit {
275
+ readonly limit?: unknown;
276
+ readonly kind?: unknown;
277
+ }
278
+
279
+ /**
280
+ * Where the limits live, derived from this module rather than from `cwd`.
281
+ *
282
+ * The two `..` hops climb from `src/scripts/_lib` to `src/`, which is where
283
+ * `config/` lives — NOT to the package root, though the comment said so until
284
+ * 2026-10-05. The result is correct and the arithmetic stated for it was not,
285
+ * which is the half a future reader would trust when changing the hops.
286
+ *
287
+ * `fileURLToPath`, NOT `new URL(...).pathname` — the claimed parity with
288
+ * `default_template_path()` was only half true until 2026-10-05. A `pathname`
289
+ * percent-encodes, so a package under a directory with a space resolved to
290
+ * `/Users/x/My%20Projects/...`, and on Windows it yields a leading-slash drive
291
+ * path. Either way the read fails, `loadHostInstructionLimits` swallows it and
292
+ * returns an empty map, and EVERY host then reads `unpublished` with `warn`
293
+ * false — the budget measurement degrading to "not measured" with no signal,
294
+ * which is the one failure mode this file's own limit table was written to
295
+ * avoid.
296
+ */
297
+ export function defaultHostLimitsPath(): string {
298
+ return path.join(
299
+ path.dirname(fileURLToPath(import.meta.url)),
300
+ '..',
301
+ '..',
302
+ 'config',
303
+ 'host-instruction-limits.json',
304
+ );
305
+ }
306
+
307
+ /**
308
+ * Read the published-limit table.
309
+ *
310
+ * TOLERANT IN ONE DIRECTION ONLY. An unreadable or malformed file yields an
311
+ * EMPTY table, so every host reads `unpublished` and nothing warns — a reading
312
+ * this tree cannot stand behind must never become a warning it can. A present
313
+ * entry whose `limit` is not a positive finite number is treated the same way,
314
+ * because a `0` or a string would otherwise make every install look saturated.
315
+ */
316
+ export function loadHostInstructionLimits(
317
+ file: string = defaultHostLimitsPath(),
318
+ ): ReadonlyMap<string, { limit: number | null; kind: HostLimitKind }> {
319
+ const out = new Map<string, { limit: number | null; kind: HostLimitKind }>();
320
+ let raw: unknown;
321
+ try {
322
+ raw = JSON.parse(fs.readFileSync(file, 'utf-8'));
323
+ } catch {
324
+ return out; // no table ⇒ no limits ⇒ no warnings
325
+ }
326
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return out;
327
+ for (const [host, v] of Object.entries(raw as Record<string, unknown>)) {
328
+ if (host.startsWith('_')) continue; // `_comment` and friends are not hosts
329
+ if (typeof v !== 'object' || v === null || Array.isArray(v)) continue;
330
+ const e = v as RawHostLimit;
331
+ const n = e.limit;
332
+ const limit = typeof n === 'number' && Number.isFinite(n) && n > 0 ? n : null;
333
+ const k = e.kind;
334
+ const kind: HostLimitKind =
335
+ limit === null
336
+ ? 'unpublished'
337
+ : k === 'vendor-published' || k === 'reported'
338
+ ? k
339
+ : 'reported'; // a cited figure with no stated kind is the weaker one
340
+ out.set(host, { limit, kind });
341
+ }
342
+ return out;
343
+ }
344
+
345
+ /**
346
+ * Fold the per-directory readings into one row per host, against its limit.
347
+ *
348
+ * Exported so a caller holding layers it built itself — the install receipt,
349
+ * which measures before and after — can produce the same rows without
350
+ * re-reading the tree.
351
+ */
352
+ export function buildHostLimitRows(
353
+ layers: readonly LayerReading[],
354
+ limits: ReadonlyMap<string, { limit: number | null; kind: HostLimitKind }>,
355
+ ): HostLimitReading[] {
356
+ const byHost = new Map<string, { chars: number; owned: number; foreign: number }>();
357
+ for (const l of layers) {
358
+ if (!l.present) continue;
359
+ const acc = byHost.get(l.host) ?? { chars: 0, owned: 0, foreign: 0 };
360
+ acc.chars += l.chars;
361
+ acc.owned += l.package_owned_chars;
362
+ acc.foreign += l.foreign_chars;
363
+ byHost.set(l.host, acc);
364
+ }
365
+ // EVERY host that has a directory gets a row, present or not — a host whose
366
+ // layer is absent reads 0 against its limit, which is a real answer and the
367
+ // one step 3.5 asks for ("one row per host with a published limit").
368
+ for (const l of layers) if (!byHost.has(l.host)) byHost.set(l.host, { chars: 0, owned: 0, foreign: 0 });
369
+ // AND every host the LIMITS table knows, whether or not a layer reached it.
370
+ // Rows were layer-driven while the requirement is limit-driven, so a limit
371
+ // recorded for a host the layer map does not list disappeared silently —
372
+ // the two sources agree today, which means they can only diverge in the
373
+ // direction the code did not cover.
374
+ for (const [host, e] of limits) if (e.limit !== null && !byHost.has(host)) {
375
+ byHost.set(host, { chars: 0, owned: 0, foreign: 0 });
376
+ }
377
+ const rows: HostLimitReading[] = [];
378
+ for (const host of [...byHost.keys()].sort()) {
379
+ const a = byHost.get(host) as { chars: number; owned: number; foreign: number };
380
+ const e = limits.get(host) ?? { limit: null, kind: 'unpublished' as HostLimitKind };
381
+ const fraction = e.limit === null ? null : a.chars / e.limit;
382
+ rows.push({
383
+ host,
384
+ limit: e.limit,
385
+ kind: e.kind,
386
+ chars: a.chars,
387
+ package_owned_chars: a.owned,
388
+ foreign_chars: a.foreign,
389
+ fraction,
390
+ warn: fraction !== null && fraction >= LIMIT_WARN_FRACTION,
391
+ });
392
+ }
393
+ return rows;
394
+ }
395
+
396
+ export interface ReportOptions {
397
+ readonly home: string;
398
+ readonly projectRoot: string;
399
+ /** The installed-tools manifest. Absent or unreadable means no ownership evidence. */
400
+ readonly manifestPath?: string | null;
401
+ /** Supplied rather than shelled, so a fixture is not at the mercy of a CLI being on PATH. */
402
+ readonly hostVersion?: string;
403
+ /**
404
+ * The published-limit table. A parameter for the same reason `home` is: a
405
+ * fixture must be able to state the limit it is measuring against instead
406
+ * of inheriting whatever the shipped config happens to say today.
407
+ */
408
+ readonly hostLimitsPath?: string | null;
409
+ }
410
+
411
+ /**
412
+ * Read every host rule directory in both scopes.
413
+ *
414
+ * BOTH SCOPES, BECAUSE THE HOST UNIONS THEM. A reading of the global layer
415
+ * alone would understate a consumer whose project carries its own rules, and
416
+ * the carrier's own scope filter reads the union for the same reason
417
+ * (`rule_layer_overlap.hostRuleLayerIds`).
418
+ */
419
+ export function buildInstalledLayerReport(opts: ReportOptions): InstalledLayerReport {
420
+ const manifestPath = opts.manifestPath ?? null;
421
+ const recorded =
422
+ manifestPath === null
423
+ ? NO_RECORDED_HASHES
424
+ : readRecordedHashes(manifestPath, opts.projectRoot);
425
+ const layers: LayerReading[] = [];
426
+ for (const host of Object.keys(GLOBAL_RULE_DIRS).sort()) {
427
+ const dir = globalRuleLayerPath(host, opts.home);
428
+ if (dir === null) continue;
429
+ layers.push(readLayer(host, 'global', dir, recorded));
430
+ }
431
+ for (const rel of Object.keys(PROJECT_RULE_DIRS).sort()) {
432
+ const host = PROJECT_RULE_DIRS[rel] as string;
433
+ layers.push(readLayer(host, 'project', path.join(opts.projectRoot, rel), recorded));
434
+ }
435
+ const totals = layers.reduce(
436
+ (acc, l) => ({
437
+ files: acc.files + l.files,
438
+ unconditional: acc.unconditional + l.unconditional,
439
+ chars: acc.chars + l.chars,
440
+ package_owned: acc.package_owned + l.package_owned,
441
+ foreign: acc.foreign + l.foreign,
442
+ package_owned_chars: acc.package_owned_chars + l.package_owned_chars,
443
+ foreign_chars: acc.foreign_chars + l.foreign_chars,
444
+ }),
445
+ {
446
+ files: 0,
447
+ unconditional: 0,
448
+ chars: 0,
449
+ package_owned: 0,
450
+ foreign: 0,
451
+ package_owned_chars: 0,
452
+ foreign_chars: 0,
453
+ },
454
+ );
455
+ const limits = buildHostLimitRows(
456
+ layers,
457
+ loadHostInstructionLimits(opts.hostLimitsPath ?? defaultHostLimitsPath()),
458
+ );
459
+ return {
460
+ home: opts.home,
461
+ project_root: opts.projectRoot,
462
+ host_version: opts.hostVersion ?? 'unknown',
463
+ manifest_present: manifestPath !== null && fs.existsSync(manifestPath),
464
+ layers,
465
+ limits,
466
+ totals,
467
+ };
468
+ }
469
+
470
+ /** The report as lines, for a terminal. One directory per block. */
471
+ export function renderInstalledLayerReport(report: InstalledLayerReport): string[] {
472
+ const out: string[] = [];
473
+ out.push(`installed layer — host ${report.host_version}, HOME ${report.home}`);
474
+ out.push(`project ${report.project_root}`);
475
+ out.push(
476
+ report.manifest_present
477
+ ? 'ownership: read from the installed-tools manifest'
478
+ : 'ownership: NO manifest resolved — every file reads foreign, which is no evidence rather than a finding',
479
+ );
480
+ for (const l of report.layers) {
481
+ if (!l.present) {
482
+ out.push(` ${l.host} (${l.scope}) — absent: ${l.dir}`);
483
+ continue;
484
+ }
485
+ out.push(
486
+ ` ${l.host} (${l.scope}) — ${String(l.files)} files, ` +
487
+ `${String(l.unconditional)} unconditional, ${String(l.chars)} chars, ` +
488
+ `${String(l.package_owned)} package-owned / ${String(l.foreign)} foreign`,
489
+ );
490
+ for (const f of l.top) {
491
+ out.push(
492
+ ` ${String(f.chars).padStart(7)} ${f.unconditional ? 'uncond' : 'scoped'} ${f.file}`,
493
+ );
494
+ }
495
+ }
496
+ const t = report.totals;
497
+ out.push(
498
+ ` TOTAL — ${String(t.files)} files, ${String(t.unconditional)} unconditional, ` +
499
+ `${String(t.chars)} chars, ${String(t.package_owned)} package-owned / ${String(t.foreign)} foreign`,
500
+ );
501
+ out.push(...renderHostLimitRows(report.limits));
502
+ return out;
503
+ }
504
+
505
+ /**
506
+ * The budget block — one row per host, against its published limit.
507
+ *
508
+ * Extracted so the REPORT and the INSTALL RECEIPT print the same bytes. They
509
+ * answer the same question at two moments, and a consumer comparing a receipt
510
+ * against a later report must not have to decide whether a wording difference
511
+ * means a measurement difference.
512
+ */
513
+ export function renderHostLimitRows(rows: readonly HostLimitReading[]): string[] {
514
+ // ONE ROW PER HOST, against its published limit — steps 1.4 and 3.5. The
515
+ // combined figure is what the row is about: a consumer's own instruction
516
+ // files sit in the same budget as this package's, so a package-owned number
517
+ // alone would understate what the host is actually holding.
518
+ const out: string[] = [
519
+ 'host instruction budgets — package-owned + foreign against the published limit',
520
+ ];
521
+ for (const r of rows) {
522
+ const split =
523
+ `${String(r.package_owned_chars)} package-owned + ` +
524
+ `${String(r.foreign_chars)} foreign = ${String(r.chars)} chars`;
525
+ if (r.limit === null) {
526
+ out.push(` ${r.host} — ${split}; no published limit recorded — not measured`);
527
+ continue;
528
+ }
529
+ const pct = ((r.fraction as number) * 100).toFixed(1);
530
+ out.push(
531
+ ` ${r.host} — ${split}; ${pct}% of ${String(r.limit)} (${r.kind})` +
532
+ (r.warn
533
+ ? ` — WARNING: at or past ${String(Math.round(LIMIT_WARN_FRACTION * 100))}% of the published limit`
534
+ : ''),
535
+ );
536
+ }
537
+ return out;
538
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * JSON-shaped merge primitives shared by the installer's host-config writers.
3
+ *
4
+ * Moved verbatim out of `src/scripts/install.ts` (road-to-a-tree-that-keeps-its-
5
+ * neighbours Phase 1.1). The behaviour is unchanged — this is a module boundary,
6
+ * not a rewrite. Two reasons it earns its own file:
7
+ *
8
+ * - `install.ts` sits far above the `check_source_size_budget` ceiling, so every
9
+ * line added there raises the tree-wide excess. The hook-coexistence work needs
10
+ * to touch seven call sites in that file; paying for it with a MOVE keeps the
11
+ * ratchet green and leaves the file shorter than it was.
12
+ * - `host_hook_merge.ts` needs `deepcopy` and `jsonEqual` too, and importing them
13
+ * from the installer would be a cycle.
14
+ *
15
+ * `deepMerge` REPLACES arrays wholesale. That is correct for scalar and object
16
+ * keys and wrong for a host's `hooks.<event>` array, which is a shared list other
17
+ * packages also append to — that case is `host_hook_merge.mergeHookEvents`, and
18
+ * the installer routes `hooks` through it before calling this.
19
+ */
20
+
21
+ /** True for a JSON object (not null, not an array). */
22
+ export function isPlainObject(v: unknown): v is Record<string, unknown> {
23
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
24
+ }
25
+
26
+ /** `copy.deepcopy` for JSON-shaped values. */
27
+ export function deepcopy<T>(v: T): T {
28
+ if (v === null || typeof v !== 'object') return v;
29
+ if (Array.isArray(v)) return v.map((x) => deepcopy(x)) as unknown as T;
30
+ const out: Record<string, unknown> = {};
31
+ for (const k of Object.keys(v as Record<string, unknown>)) {
32
+ out[k] = deepcopy((v as Record<string, unknown>)[k]);
33
+ }
34
+ return out as unknown as T;
35
+ }
36
+
37
+ /**
38
+ * Recursive object merge; `overlay` wins on every leaf.
39
+ *
40
+ * Arrays are replaced, never concatenated — see the module header for the one
41
+ * case where that is the wrong answer and who handles it instead.
42
+ */
43
+ export function deepMerge(
44
+ base: Record<string, unknown>,
45
+ overlay: Record<string, unknown>,
46
+ ): Record<string, unknown> {
47
+ const result = deepcopy(base);
48
+ for (const key of Object.keys(overlay)) {
49
+ const value = overlay[key];
50
+ if (
51
+ Object.prototype.hasOwnProperty.call(result, key) &&
52
+ isPlainObject(result[key]) &&
53
+ isPlainObject(value)
54
+ ) {
55
+ result[key] = deepMerge(result[key] as Record<string, unknown>, value as Record<string, unknown>);
56
+ } else {
57
+ result[key] = deepcopy(value);
58
+ }
59
+ }
60
+ return result;
61
+ }
62
+
63
+ /** Deep structural equality for JSON-shaped values (Python dict `==`). */
64
+ export function jsonEqual(a: unknown, b: unknown): boolean {
65
+ if (a === b) return true;
66
+ if (typeof a !== typeof b) return false;
67
+ if (Array.isArray(a) || Array.isArray(b)) {
68
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
69
+ return a.every((v, i) => jsonEqual(v, b[i]));
70
+ }
71
+ if (isPlainObject(a) && isPlainObject(b)) {
72
+ const ka = Object.keys(a);
73
+ const kb = Object.keys(b);
74
+ if (ka.length !== kb.length) return false;
75
+ return ka.every(
76
+ (k) =>
77
+ Object.prototype.hasOwnProperty.call(b, k) &&
78
+ jsonEqual(a[k], (b as Record<string, unknown>)[k]),
79
+ );
80
+ }
81
+ return false;
82
+ }