@event4u/agent-config 16.2.0 → 16.4.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 (651) 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 +337 -234
  5. package/README.md +15 -13
  6. package/agents/templates/.ai-council.yml.example +26 -8
  7. package/dist/agent-src/commands/analyze/inbox.md +11 -8
  8. package/dist/agent-src/commands/analyze/roadmap-repos.md +1 -1
  9. package/dist/agent-src/commands/bug/fix.md +17 -2
  10. package/dist/agent-src/commands/bug/investigate.md +1 -1
  11. package/dist/agent-src/commands/commit/in-chunks.md +40 -31
  12. package/dist/agent-src/commands/commit.md +72 -33
  13. package/dist/agent-src/commands/condense.md +2 -8
  14. package/dist/agent-src/commands/council/debate.md +11 -7
  15. package/dist/agent-src/commands/council/default.md +10 -9
  16. package/dist/agent-src/commands/council.md +1 -1
  17. package/dist/agent-src/commands/estimate-ticket.md +1 -1
  18. package/dist/agent-src/commands/feature/explore.md +3 -3
  19. package/dist/agent-src/commands/feature/plan.md +2 -2
  20. package/dist/agent-src/commands/feature/roadmap.md +1 -1
  21. package/dist/agent-src/commands/fix/ci.md +18 -4
  22. package/dist/agent-src/commands/fix/commit-messages.md +48 -23
  23. package/dist/agent-src/commands/fix/pr-comments.md +3 -6
  24. package/dist/agent-src/commands/implement-ticket.md +4 -4
  25. package/dist/agent-src/commands/jira-ticket.md +1 -1
  26. package/dist/agent-src/commands/memory/add.md +4 -16
  27. package/dist/agent-src/commands/memory/load.md +5 -6
  28. package/dist/agent-src/commands/memory/promote.md +3 -16
  29. package/dist/agent-src/commands/mission/upgrade.md +3 -2
  30. package/dist/agent-src/commands/optimize/agents-dir.md +2 -2
  31. package/dist/agent-src/commands/optimize/deep.md +4 -2
  32. package/dist/agent-src/commands/optimize/rtk.md +4 -26
  33. package/dist/agent-src/commands/pr/create/description-only.md +18 -15
  34. package/dist/agent-src/commands/pr/create.md +42 -34
  35. package/dist/agent-src/commands/pr/merge.md +136 -9
  36. package/dist/agent-src/commands/prepare-for-review.md +13 -11
  37. package/dist/agent-src/commands/project/analyze.md +7 -35
  38. package/dist/agent-src/commands/refine-ticket.md +1 -1
  39. package/dist/agent-src/commands/review/changes.md +14 -19
  40. package/dist/agent-src/commands/roadmap/create.md +1 -1
  41. package/dist/agent-src/commands/roadmap/materialize.md +1 -1
  42. package/dist/agent-src/commands/roadmap/next.md +10 -1
  43. package/dist/agent-src/commands/roadmap/process-full.md +56 -42
  44. package/dist/agent-src/commands/roadmap/resolve-blockers.md +411 -0
  45. package/dist/agent-src/commands/roadmap/triage-parked.md +325 -0
  46. package/dist/agent-src/commands/roadmap.md +18 -4
  47. package/dist/agent-src/commands/tests/create.md +17 -3
  48. package/dist/agent-src/commands/tests/execute.md +22 -5
  49. package/dist/agent-src/commands/worktree/create.md +7 -3
  50. package/dist/agent-src/commands/worktree/verify.md +2 -1
  51. package/dist/agent-src/contexts/authority/decision-revisit-gate-enforcement.md +43 -0
  52. package/dist/agent-src/contexts/communication/rules-auto/design-fidelity-enforcement.md +24 -0
  53. package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +5 -3
  54. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +7 -9
  55. package/dist/agent-src/contexts/contracts/command-suggestion-flow.md +8 -6
  56. package/dist/agent-src/contexts/contracts/frugality-charter.md +8 -5
  57. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +7 -7
  58. package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +0 -1
  59. package/dist/agent-src/contexts/execution/evidence-discipline.md +2 -2
  60. package/dist/agent-src/contexts/execution/non-interactive-contract.md +5 -8
  61. package/dist/agent-src/contexts/execution/rdp-gate.md +4 -4
  62. package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +9 -8
  63. package/dist/agent-src/contexts/execution/roadmap-process-loop.md +99 -54
  64. package/dist/agent-src/contexts/execution/session-canary-enforcement-history.md +67 -0
  65. package/dist/agent-src/contexts/execution/subagent-routing.md +8 -8
  66. package/dist/agent-src/contexts/execution/subagent-steering.md +6 -4
  67. package/dist/agent-src/contexts/execution/terminal-states.md +2 -2
  68. package/dist/agent-src/contexts/execution/toolchain-resolver.md +95 -7
  69. package/dist/agent-src/contexts/subagent-configuration.md +1 -1
  70. package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +39 -0
  71. package/dist/agent-src/guidelines/agent-infra/context-hygiene-mechanics.md +1 -1
  72. package/dist/agent-src/guidelines/agent-infra/cross-source-consistency-mechanics.md +8 -10
  73. package/dist/agent-src/guidelines/agent-infra/frontier-reasoning-operating-profile.md +3 -2
  74. package/dist/agent-src/guidelines/agent-infra/layered-settings.md +13 -1
  75. package/dist/agent-src/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  76. package/dist/agent-src/guidelines/agent-infra/self-improvement-pipeline.md +10 -3
  77. package/dist/agent-src/guidelines/agent-infra/size-and-scope.md +36 -0
  78. package/dist/agent-src/guidelines/php/git.md +15 -2
  79. package/dist/agent-src/rules/active-remediation.md +1 -2
  80. package/dist/agent-src/rules/analysis-skill-routing.md +0 -1
  81. package/dist/agent-src/rules/architecture.md +0 -1
  82. package/dist/agent-src/rules/artifact-drafting-protocol.md +0 -1
  83. package/dist/agent-src/rules/artifact-engagement-recording.md +0 -1
  84. package/dist/agent-src/rules/augment-edit-discipline.md +0 -1
  85. package/dist/agent-src/rules/autonomous-execution.md +1 -1
  86. package/dist/agent-src/rules/brand-consistency.md +0 -1
  87. package/dist/agent-src/rules/brand-source-of-truth.md +0 -1
  88. package/dist/agent-src/rules/broken-access-control.md +1 -1
  89. package/dist/agent-src/rules/cli-output-handling.md +0 -1
  90. package/dist/agent-src/rules/code-comment-discipline.md +0 -1
  91. package/dist/agent-src/rules/code-provenance.md +0 -1
  92. package/dist/agent-src/rules/command-suggestion-policy.md +1 -1
  93. package/dist/agent-src/rules/commit-conventions.md +1 -2
  94. package/dist/agent-src/rules/communication-through-line.md +1 -2
  95. package/dist/agent-src/rules/content-quoting-floor.md +1 -2
  96. package/dist/agent-src/rules/context-hygiene.md +6 -6
  97. package/dist/agent-src/rules/copilot-routing.md +1 -2
  98. package/dist/agent-src/rules/council-availability.md +4 -4
  99. package/dist/agent-src/rules/cross-source-consistency.md +4 -6
  100. package/dist/agent-src/rules/decision-revisit-gate.md +17 -35
  101. package/dist/agent-src/rules/delegation-policy.md +2 -3
  102. package/dist/agent-src/rules/design-fidelity.md +12 -16
  103. package/dist/agent-src/rules/design-review-after-ui-write.md +3 -3
  104. package/dist/agent-src/rules/devcontainer-routing.md +1 -2
  105. package/dist/agent-src/rules/doc-screenshot-hygiene.md +1 -1
  106. package/dist/agent-src/rules/docker-commands.md +0 -1
  107. package/dist/agent-src/rules/domain-adoption-policy.md +0 -1
  108. package/dist/agent-src/rules/domain-safety-disclaimer.md +1 -1
  109. package/dist/agent-src/rules/domain-safety-pii.md +1 -1
  110. package/dist/agent-src/rules/domain-safety-retention.md +1 -1
  111. package/dist/agent-src/rules/downstream-changes.md +0 -1
  112. package/dist/agent-src/rules/engineering-safety-floor.md +1 -1
  113. package/dist/agent-src/rules/evaluator-independence.md +3 -2
  114. package/dist/agent-src/rules/external-code-graph-interop.md +0 -1
  115. package/dist/agent-src/rules/external-reference-deep-dive.md +0 -1
  116. package/dist/agent-src/rules/fast-path-marker-visibility.md +2 -2
  117. package/dist/agent-src/rules/finance-safety-floor.md +1 -1
  118. package/dist/agent-src/rules/fix-what-you-see.md +6 -32
  119. package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +0 -1
  120. package/dist/agent-src/rules/git-history-discipline.md +2 -2
  121. package/dist/agent-src/rules/guidelines.md +0 -1
  122. package/dist/agent-src/rules/history-discipline.md +0 -1
  123. package/dist/agent-src/rules/icon-consistency.md +0 -1
  124. package/dist/agent-src/rules/image-likeness-and-rights.md +1 -1
  125. package/dist/agent-src/rules/improve-before-implement.md +0 -1
  126. package/dist/agent-src/rules/invite-challenge.md +0 -1
  127. package/dist/agent-src/rules/laravel-routing.md +1 -2
  128. package/dist/agent-src/rules/laravel-translations.md +0 -1
  129. package/dist/agent-src/rules/legal-safety-floor.md +3 -3
  130. package/dist/agent-src/rules/lethal-trifecta-guard.md +1 -1
  131. package/dist/agent-src/rules/linked-projects-onboarding-gate.md +0 -1
  132. package/dist/agent-src/rules/low-impact-corpus-privacy-floor.md +1 -1
  133. package/dist/agent-src/rules/markdown-safe-codeblocks.md +0 -1
  134. package/dist/agent-src/rules/media-governance-routing.md +13 -17
  135. package/dist/agent-src/rules/media-sync-ground-truth.md +0 -1
  136. package/dist/agent-src/rules/minimal-safe-diff.md +0 -1
  137. package/dist/agent-src/rules/missing-skill-recovery.md +0 -1
  138. package/dist/agent-src/rules/missing-tool-handling.md +0 -1
  139. package/dist/agent-src/rules/model-recommendation.md +0 -1
  140. package/dist/agent-src/rules/neighbour-precedence.md +64 -0
  141. package/dist/agent-src/rules/no-attribution-footers.md +0 -1
  142. package/dist/agent-src/rules/no-decorative-emojis-in-git-surfaces.md +0 -1
  143. package/dist/agent-src/rules/no-pr-progress-comments.md +0 -1
  144. package/dist/agent-src/rules/no-roadmap-references.md +0 -1
  145. package/dist/agent-src/rules/notes-first-reasoning.md +0 -1
  146. package/dist/agent-src/rules/onboarding-gate.md +0 -1
  147. package/dist/agent-src/rules/output-discipline.md +0 -1
  148. package/dist/agent-src/rules/package-ci-checks.md +0 -1
  149. package/dist/agent-src/rules/persona-governance.md +4 -5
  150. package/dist/agent-src/rules/php-coding.md +0 -1
  151. package/dist/agent-src/rules/prefer-enums-over-literals.md +0 -1
  152. package/dist/agent-src/rules/preservation-guard.md +0 -1
  153. package/dist/agent-src/rules/provider-lifecycle-discipline.md +3 -4
  154. package/dist/agent-src/rules/question-not-instruction.md +1 -1
  155. package/dist/agent-src/rules/reviewer-awareness.md +0 -1
  156. package/dist/agent-src/rules/roadmap-ci-steps-policy.md +0 -1
  157. package/dist/agent-src/rules/roadmap-progress-sync.md +4 -7
  158. package/dist/agent-src/rules/role-mode-adherence.md +0 -1
  159. package/dist/agent-src/rules/rule-type-governance.md +0 -1
  160. package/dist/agent-src/rules/runtime-safety.md +1 -1
  161. package/dist/agent-src/rules/scale-discipline.md +0 -1
  162. package/dist/agent-src/rules/secret-vcs-guard.md +1 -1
  163. package/dist/agent-src/rules/security-sensitive-stop.md +3 -3
  164. package/dist/agent-src/rules/self-repair-loop.md +0 -1
  165. package/dist/agent-src/rules/senior-engineering-discipline.md +0 -1
  166. package/dist/agent-src/rules/session-canary.md +7 -62
  167. package/dist/agent-src/rules/settings-ask-protocol.md +6 -6
  168. package/dist/agent-src/rules/size-enforcement.md +0 -1
  169. package/dist/agent-src/rules/skill-improvement-trigger.md +2 -3
  170. package/dist/agent-src/rules/skill-quality.md +0 -1
  171. package/dist/agent-src/rules/slash-command-routing-policy.md +0 -1
  172. package/dist/agent-src/rules/source-confidentiality.md +1 -1
  173. package/dist/agent-src/rules/source-discovery-gate.md +0 -1
  174. package/dist/agent-src/rules/source-of-truth.md +0 -1
  175. package/dist/agent-src/rules/spreadsheet-source-quality.md +1 -2
  176. package/dist/agent-src/rules/strategy-safety-floor.md +1 -1
  177. package/dist/agent-src/rules/symfony-routing.md +1 -2
  178. package/dist/agent-src/rules/think-before-action.md +0 -1
  179. package/dist/agent-src/rules/token-budget-discipline.md +12 -46
  180. package/dist/agent-src/rules/token-efficiency.md +0 -1
  181. package/dist/agent-src/rules/token-optimizer-maintenance.md +0 -1
  182. package/dist/agent-src/rules/tool-safety.md +1 -1
  183. package/dist/agent-src/rules/ui-audit-gate.md +1 -2
  184. package/dist/agent-src/rules/untrusted-input-defense.md +1 -1
  185. package/dist/agent-src/rules/upstream-proposal.md +0 -1
  186. package/dist/agent-src/rules/user-interaction.md +0 -1
  187. package/dist/agent-src/rules/user-interrupt-priority.md +1 -1
  188. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  189. package/dist/agent-src/scripts/deferral_carry.ts +339 -0
  190. package/dist/agent-src/scripts/gate_budget.ts +47 -23
  191. package/dist/agent-src/scripts/gate_execute.ts +12 -4
  192. package/dist/agent-src/scripts/update_roadmap_progress.ts +4 -1
  193. package/dist/agent-src/skills/ai-council/SKILL.md +7 -4
  194. package/dist/agent-src/skills/ai-council/references/cost-and-redaction.md +21 -11
  195. package/dist/agent-src/skills/ai-council/references/procedure.md +12 -6
  196. package/dist/agent-src/skills/analysis-autonomous-mode/SKILL.md +1 -1
  197. package/dist/agent-src/skills/api-design/data/manifest.json +2 -2
  198. package/dist/agent-src/skills/api-testing/SKILL.md +17 -0
  199. package/dist/agent-src/skills/bug-analyzer/SKILL.md +1 -1
  200. package/dist/agent-src/skills/code-intelligence/SKILL.md +14 -2
  201. package/dist/agent-src/skills/command-routing/SKILL.md +3 -4
  202. package/dist/agent-src/skills/conventional-commits-writing/SKILL.md +127 -139
  203. package/dist/agent-src/skills/corpus-grounding/ATTRIBUTION.md +39 -0
  204. package/dist/agent-src/skills/corpus-grounding/SKILL.md +8 -2
  205. package/dist/agent-src/skills/database/data/manifest.json +2 -2
  206. package/dist/agent-src/skills/database/data/query-tuning.csv +13 -13
  207. package/dist/agent-src/skills/design-system-capture/SKILL.md +0 -3
  208. package/dist/agent-src/skills/design-system-capture/references/import-procedure.md +1 -1
  209. package/dist/agent-src/skills/estimate-ticket/SKILL.md +1 -1
  210. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +2 -2
  211. package/dist/agent-src/skills/fe-design/SKILL.md +1 -1
  212. package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
  213. package/dist/agent-src/skills/git-workflow/SKILL.md +44 -44
  214. package/dist/agent-src/skills/git-workflow/references/branch-update.md +312 -0
  215. package/dist/agent-src/skills/git-workflow/references/commit-subject.md +205 -0
  216. package/dist/agent-src/skills/iconography/SKILL.md +1 -1
  217. package/dist/agent-src/skills/jira-integration/SKILL.md +3 -3
  218. package/dist/agent-src/skills/js-library-packaging/SKILL.md +2 -2
  219. package/dist/agent-src/skills/merge-conflicts/SKILL.md +10 -4
  220. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
  221. package/dist/agent-src/skills/po-discovery/SKILL.md +1 -1
  222. package/dist/agent-src/skills/quality-tools/SKILL.md +11 -1
  223. package/dist/agent-src/skills/quality-tools/references/go-tools.md +76 -0
  224. package/dist/agent-src/skills/quality-tools/references/python-tools.md +104 -0
  225. package/dist/agent-src/skills/refine-ticket/SKILL.md +4 -4
  226. package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
  227. package/dist/agent-src/skills/roadmap-writing/SKILL.md +2 -2
  228. package/dist/agent-src/skills/skill-improvement-pipeline/SKILL.md +5 -3
  229. package/dist/agent-src/skills/source-discovery/SKILL.md +1 -1
  230. package/dist/agent-src/skills/storybook-workshop/SKILL.md +3 -3
  231. package/dist/agent-src/skills/tailwind-engineer/SKILL.md +4 -2
  232. package/dist/agent-src/skills/test-case-discovery/SKILL.md +7 -0
  233. package/dist/agent-src/skills/test-driven-development/SKILL.md +17 -0
  234. package/dist/agent-src/skills/test-performance/SKILL.md +19 -1
  235. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
  236. package/dist/agent-src/skills/ui-component-architect/SKILL.md +2 -2
  237. package/dist/agent-src/skills/using-git-worktrees/SKILL.md +1 -1
  238. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +2 -2
  239. package/dist/agent-src/templates/AGENTS.md +1 -1
  240. package/dist/agent-src/templates/agent-settings.md +46 -129
  241. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +3 -3
  242. package/dist/agent-src/templates/command.md +2 -2
  243. package/dist/agent-src/templates/roadmaps.md +6 -1
  244. package/dist/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -6
  245. package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +11 -7
  246. package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
  247. package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +3 -2
  248. package/dist/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +2 -1
  249. package/dist/agent-src/templates/scripts/work_engine/hooks/settings.ts +0 -6
  250. package/dist/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +9 -12
  251. package/dist/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +2 -1
  252. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  253. package/dist/agent-src/templates/skill.md +1 -1
  254. package/dist/cli/registry.js +2 -1
  255. package/dist/cli-delegate/archive_completed_roadmaps.js +92 -12
  256. package/dist/cli-delegate/check_update_banner.js +207 -120
  257. package/dist/cli-delegate/chunk-523HCFYO.js +90 -0
  258. package/dist/cli-delegate/{chunk-FP36SWJD.js → chunk-5GPULQBE.js} +2797 -338
  259. package/dist/cli-delegate/{chunk-IGD6ERX6.js → chunk-6SRJTYJS.js} +42 -176
  260. package/dist/cli-delegate/chunk-6V5FMA3P.js +186 -0
  261. package/dist/cli-delegate/{chunk-M2DPGFZ4.js → chunk-6V6TA25H.js} +22 -152
  262. package/dist/cli-delegate/chunk-6YQC2HLF.js +415 -0
  263. package/dist/cli-delegate/{chunk-E4XIZDIB.js → chunk-7HKRE54K.js} +1 -64
  264. package/dist/cli-delegate/chunk-7IQE3D5S.js +191 -0
  265. package/dist/cli-delegate/{chunk-YJ72UIAK.js → chunk-AED2VQGM.js} +1245 -249
  266. package/dist/cli-delegate/{chunk-7HQ52DHU.js → chunk-AL3RZ22D.js} +1 -1
  267. package/dist/cli-delegate/chunk-DEIV7XP7.js +108 -0
  268. package/dist/cli-delegate/{chunk-DBH4AHDK.js → chunk-ED7KCMLB.js} +9 -11
  269. package/dist/cli-delegate/chunk-F3QDG2GT.js +2569 -0
  270. package/dist/cli-delegate/{chunk-6KFEC3G7.js → chunk-G5VVIZRC.js} +3 -3
  271. package/dist/cli-delegate/{chunk-FC75PGJE.js → chunk-H76PHZBI.js} +149 -61
  272. package/dist/cli-delegate/chunk-IAYTIIQW.js +67 -0
  273. package/dist/cli-delegate/chunk-JQOM7TTM.js +1201 -0
  274. package/dist/cli-delegate/chunk-KB5FZ655.js +208 -0
  275. package/dist/cli-delegate/{chunk-7RQP4UDH.js → chunk-KR6EM23A.js} +235 -1455
  276. package/dist/cli-delegate/{chunk-LTYVIY76.js → chunk-KXXWJTDO.js} +6 -11
  277. package/dist/cli-delegate/{chunk-T7L2QK3K.js → chunk-LQOCRFWT.js} +1 -1
  278. package/dist/cli-delegate/{chunk-6KM6ORRY.js → chunk-MDPPKQCQ.js} +58 -9
  279. package/dist/cli-delegate/{chunk-4XFP2Y3E.js → chunk-MSUL5NYP.js} +5 -5
  280. package/dist/cli-delegate/chunk-OAFSJBHH.js +272 -0
  281. package/dist/cli-delegate/{chunk-4E2W63DA.js → chunk-OKJUIAPS.js} +62 -1
  282. package/dist/cli-delegate/{chunk-QAJS5YWY.js → chunk-PHD7OOLE.js} +2 -1
  283. package/dist/cli-delegate/{chunk-P6ZHP5W2.js → chunk-PWX7CKMD.js} +4 -4
  284. package/dist/cli-delegate/{chunk-NDJLNC6Q.js → chunk-T6BP34WI.js} +1 -0
  285. package/dist/cli-delegate/{chunk-7XGBX6ZH.js → chunk-T7XSJSUQ.js} +1144 -589
  286. package/dist/cli-delegate/chunk-ULQRUR23.js +208 -0
  287. package/dist/cli-delegate/chunk-WQWXGIWB.js +1779 -0
  288. package/dist/cli-delegate/{chunk-RGNIWTEA.js → chunk-XQTPXKFA.js} +1 -1
  289. package/dist/cli-delegate/{chunk-NUHLI2RX.js → chunk-YCU3TIXH.js} +2 -0
  290. package/dist/cli-delegate/chunk-ZBZV2G5R.js +210 -0
  291. package/dist/cli-delegate/{chunk-WGXTVKCQ.js → chunk-ZUZKZYCN.js} +5 -3
  292. package/dist/cli-delegate/cmd_analyze_session.js +223 -43
  293. package/dist/cli-delegate/cmd_brand_status.js +3 -2
  294. package/dist/cli-delegate/cmd_conformance.js +1252 -249
  295. package/dist/cli-delegate/cmd_converge.js +3 -3
  296. package/dist/cli-delegate/cmd_doctor.js +19 -9
  297. package/dist/cli-delegate/cmd_doctor_neighbours.js +30 -0
  298. package/dist/cli-delegate/cmd_explain.js +45 -21
  299. package/dist/cli-delegate/cmd_export.js +13 -10
  300. package/dist/cli-delegate/cmd_fleet.js +3 -3
  301. package/dist/cli-delegate/cmd_git_convention.js +1851 -0
  302. package/dist/cli-delegate/cmd_handoff.js +44 -22
  303. package/dist/cli-delegate/cmd_mcp_available.js +3 -2
  304. package/dist/cli-delegate/cmd_memory_get.js +3 -1
  305. package/dist/cli-delegate/cmd_migrate.js +3 -3
  306. package/dist/cli-delegate/cmd_packs_active.js +5 -4
  307. package/dist/cli-delegate/cmd_preflight.js +6 -6
  308. package/dist/cli-delegate/cmd_prune.js +14 -11
  309. package/dist/cli-delegate/cmd_refresh.js +2 -1
  310. package/dist/cli-delegate/cmd_route_audit.js +10 -8
  311. package/dist/cli-delegate/cmd_route_explain.js +4 -4
  312. package/dist/cli-delegate/cmd_session_recycle.js +10 -7
  313. package/dist/cli-delegate/cmd_settings_check.js +54 -7
  314. package/dist/cli-delegate/cmd_settings_get.js +18 -8
  315. package/dist/cli-delegate/cmd_settings_migrate.js +13 -10
  316. package/dist/cli-delegate/cmd_settings_set.js +4 -4
  317. package/dist/cli-delegate/cmd_sync.js +14 -11
  318. package/dist/cli-delegate/cmd_uninstall.js +15 -12
  319. package/dist/cli-delegate/cmd_update.js +7 -7
  320. package/dist/cli-delegate/cmd_upgrade.js +9 -8
  321. package/dist/cli-delegate/cmd_validate.js +15 -12
  322. package/dist/cli-delegate/cmd_versions.js +5 -5
  323. package/dist/cli-delegate/deferral_carry.js +16 -0
  324. package/dist/cli-delegate/gate_budget.js +1 -1
  325. package/dist/cli-delegate/gate_execute.js +3 -3
  326. package/dist/cli-delegate/roadmap_gates.js +6 -6
  327. package/dist/cli-delegate/update_roadmap_progress.js +1 -1
  328. package/dist/discovery/deprecation-report.md +1 -1
  329. package/dist/discovery/discovery-manifest.json +232 -141
  330. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  331. package/dist/discovery/discovery-manifest.summary.md +3 -3
  332. package/dist/discovery/dormancy-report.md +1 -1
  333. package/dist/discovery/orphan-report.md +1 -1
  334. package/dist/discovery/packs.json +26 -9
  335. package/dist/discovery/trust-report.md +3 -3
  336. package/dist/discovery/workspaces.json +7 -4
  337. package/dist/hooks/dispatch.js +5655 -6623
  338. package/dist/hooks/dispatch.sha256 +1 -1
  339. package/dist/install/claudePathsPlan.js +47 -6
  340. package/dist/install/claudeRuleRewrite.js +7 -0
  341. package/dist/install/emit_host_rules_cli.js +39 -36
  342. package/dist/install/globalRuleScope.js +87 -0
  343. package/dist/install/host_rule_ownership.js +160 -0
  344. package/dist/install/install.mjs +2336 -1249
  345. package/dist/install/installThinLayer.js +289 -0
  346. package/dist/install/installedRuleLinks.js +262 -0
  347. package/dist/install/wizard-plan.js +24 -0
  348. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  349. package/dist/mcp/mcp-cloudflare-catalogue.json +2 -2
  350. package/dist/mcp/registry-manifest.json +5 -5
  351. package/dist/mcp/server.json +2 -2
  352. package/dist/mcp/server.mjs +36504 -30986
  353. package/dist/router.json +1 -1
  354. package/dist/scripts/_lib/agent_settings.js +26 -120
  355. package/dist/scripts/_lib/claude_settings_hooks.js +10 -8
  356. package/dist/scripts/_lib/compile_time_toggles.js +12 -21
  357. package/dist/scripts/_lib/git_convention.js +232 -0
  358. package/dist/scripts/_lib/global_deploy_inventory.js +730 -0
  359. package/dist/scripts/_lib/installed_layer.js +704 -0
  360. package/dist/scripts/_lib/lean_projection_mode.js +218 -15
  361. package/dist/scripts/_lib/package_root.js +68 -0
  362. package/dist/scripts/_lib/rule_consequence_class.js +45 -0
  363. package/dist/scripts/_lib/rule_injection.js +67 -10
  364. package/dist/scripts/_lib/rule_law_section.js +122 -0
  365. package/dist/scripts/_lib/settings_removed_keys.js +168 -0
  366. package/dist/scripts/_lib/thin_rules.js +535 -0
  367. package/dist/scripts/_lib/windsurf_render.js +60 -0
  368. package/dist/scripts/condense.js +14 -26
  369. package/dist/scripts/project_thin_rules.js +14 -294
  370. package/dist/server/app.js +2 -1
  371. package/dist/server/gitKeysGate.js +105 -0
  372. package/dist/server/routes/settings.js +67 -8
  373. package/dist/server/routes/wizard.js +4 -0
  374. package/dist/server/schemas/settings.js +29 -81
  375. package/dist/shared/skillRanking.js +103 -16
  376. package/dist/ui/assets/{index-Cwq4d0hX.js → index-C3djEJah.js} +2 -2
  377. package/dist/ui/index.html +1 -1
  378. package/docs/MIGRATION.md +72 -1
  379. package/docs/contracts/settings-classes.md +39 -67
  380. package/docs/guidelines/agent-infra/active-remediation-mechanics.md +39 -0
  381. package/docs/guidelines/agent-infra/context-hygiene-mechanics.md +1 -1
  382. package/docs/guidelines/agent-infra/cross-source-consistency-mechanics.md +8 -10
  383. package/docs/guidelines/agent-infra/frontier-reasoning-operating-profile.md +3 -2
  384. package/docs/guidelines/agent-infra/layered-settings.md +13 -1
  385. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
  386. package/docs/guidelines/agent-infra/self-improvement-pipeline.md +10 -3
  387. package/docs/guidelines/agent-infra/size-and-scope.md +36 -0
  388. package/docs/guidelines/php/git.md +15 -2
  389. package/package.json +1 -1
  390. package/src/agent-src/scripts/archive_completed_roadmaps.ts +115 -20
  391. package/src/agent-src/scripts/deferral_carry.ts +339 -0
  392. package/src/agent-src/scripts/gate_budget.ts +47 -23
  393. package/src/agent-src/scripts/gate_execute.ts +12 -4
  394. package/src/agent-src/scripts/update_roadmap_progress.ts +4 -1
  395. package/src/agent-src/templates/AGENTS.md +1 -1
  396. package/src/agent-src/templates/scripts/work_engine/_lib/agent_settings.ts +0 -6
  397. package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +11 -7
  398. package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +48 -6
  399. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/decision_trace.ts +3 -2
  400. package/src/agent-src/templates/scripts/work_engine/hooks/builtin/memory_visibility.ts +2 -1
  401. package/src/agent-src/templates/scripts/work_engine/hooks/settings.ts +0 -6
  402. package/src/agent-src/templates/scripts/work_engine/scoring/decision_engine.ts +9 -12
  403. package/src/agent-src/templates/scripts/work_engine/scoring/memory_visibility.ts +2 -1
  404. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +774 -109
  405. package/src/config/agent-settings.template.yml +141 -334
  406. package/src/config/agents-paths.yml +16 -0
  407. package/src/config/assurance-capability-registry.json +7 -3
  408. package/src/config/ci-local-parity.yml +3 -0
  409. package/src/config/continuity-surface.json +10 -3
  410. package/src/config/discovery/packs.yml +17 -1
  411. package/src/config/evaluator-budgets.json +23 -10
  412. package/src/config/gate-coverage.yml +138 -3
  413. package/src/config/gate-violation-baselines.json +64 -16
  414. package/src/config/gitignore-block.txt +19 -0
  415. package/src/config/hook-bundle-budget.json +25 -0
  416. package/src/config/hook-token-budget.json +6 -1
  417. package/src/config/host-instruction-limits.json +33 -0
  418. package/src/config/release-gate-locality.yml +2 -1
  419. package/src/config/routing-coverage-seed.json +67 -4
  420. package/src/config/rule-activation-census.json +7 -3
  421. package/src/config/rule-consequence-class.json +161 -0
  422. package/src/config/rule-law-ceilings.json +105 -0
  423. package/src/config/rule-obligations.json +833 -0
  424. package/src/install/claudePathsPlan.ts +46 -4
  425. package/src/install/claudeRuleRewrite.ts +10 -1
  426. package/src/install/emit_host_rules_cli.ts +43 -34
  427. package/src/install/globalRuleScope.ts +109 -0
  428. package/src/install/host_rule_ownership.ts +170 -0
  429. package/src/install/installThinLayer.ts +378 -0
  430. package/src/install/installedRuleLinks.ts +308 -0
  431. package/src/install/wizard-plan.ts +24 -0
  432. package/src/scripts/_cli/cmd_analyze_session.ts +9 -15
  433. package/src/scripts/_cli/cmd_conformance.ts +13 -2
  434. package/src/scripts/_cli/cmd_doctor.ts +33 -81
  435. package/src/scripts/_cli/cmd_doctor_neighbours.ts +426 -0
  436. package/src/scripts/_cli/cmd_explain.ts +72 -14
  437. package/src/scripts/_cli/cmd_git_convention.ts +637 -0
  438. package/src/scripts/_cli/cmd_settings_check.ts +66 -8
  439. package/src/scripts/_cli/cmd_settings_get.ts +18 -3
  440. package/src/scripts/_cli/doctor_execution.ts +72 -1
  441. package/src/scripts/_dispatch.bash +53 -4
  442. package/src/scripts/_lib/agent_settings.ts +26 -124
  443. package/src/scripts/_lib/blocked_by_marker.ts +1 -1
  444. package/src/scripts/_lib/claude_settings_hooks.ts +10 -8
  445. package/src/scripts/_lib/compile_time_toggles.ts +12 -21
  446. package/src/scripts/_lib/dispatch_import_closure.ts +163 -0
  447. package/src/scripts/_lib/estate_offsets.ts +241 -0
  448. package/src/scripts/_lib/exemption_shape.ts +143 -0
  449. package/src/scripts/_lib/fnv.ts +23 -0
  450. package/src/scripts/_lib/foreign_scan.ts +106 -0
  451. package/src/scripts/_lib/git_base_ref.ts +92 -0
  452. package/src/scripts/_lib/git_convention.ts +291 -0
  453. package/src/scripts/_lib/git_convention_carrier.ts +389 -0
  454. package/src/scripts/_lib/git_convention_grammar.ts +246 -0
  455. package/src/scripts/_lib/git_convention_measure.ts +344 -0
  456. package/src/scripts/_lib/gitignore_block.ts +314 -0
  457. package/src/scripts/_lib/global_deploy_inventory.ts +106 -0
  458. package/src/scripts/_lib/graph_feeder_record.ts +437 -0
  459. package/src/scripts/_lib/hook_settings.ts +73 -0
  460. package/src/scripts/_lib/host_capability.ts +40 -19
  461. package/src/scripts/_lib/host_hook_merge.ts +165 -0
  462. package/src/scripts/_lib/installed_layer.ts +963 -0
  463. package/src/scripts/_lib/json_merge.ts +82 -0
  464. package/src/scripts/_lib/json_pointers.ts +93 -11
  465. package/src/scripts/_lib/knowledge_global.ts +14 -1
  466. package/src/scripts/_lib/lean_projection_mode.ts +275 -15
  467. package/src/scripts/_lib/machine_wake.ts +16 -3
  468. package/src/scripts/_lib/module_reach.ts +456 -0
  469. package/src/scripts/_lib/neighbour_census.ts +760 -0
  470. package/src/scripts/_lib/neighbour_contradictions.ts +236 -0
  471. package/src/scripts/_lib/neighbour_scan.ts +122 -0
  472. package/src/scripts/_lib/neighbour_tool_use.ts +187 -0
  473. package/src/scripts/_lib/prompt_shape.ts +46 -0
  474. package/src/scripts/_lib/ratification_artifact.ts +220 -1
  475. package/src/scripts/_lib/ratification_subject.ts +92 -0
  476. package/src/scripts/_lib/release_findings_ingest.ts +15 -7
  477. package/src/scripts/_lib/release_highlights.ts +5 -0
  478. package/src/scripts/_lib/release_migration_gate.ts +19 -3
  479. package/src/scripts/_lib/reserved_name_sweep.ts +73 -0
  480. package/src/scripts/_lib/review_coverage.ts +134 -0
  481. package/src/scripts/_lib/rule_consequence_class.ts +71 -0
  482. package/src/scripts/_lib/rule_injection.ts +130 -10
  483. package/src/scripts/_lib/rule_law_section.ts +147 -0
  484. package/src/scripts/_lib/rule_layer_overlap.ts +58 -0
  485. package/src/scripts/_lib/rule_obligations.ts +205 -0
  486. package/src/scripts/_lib/self_repair.ts +1 -1
  487. package/src/scripts/_lib/settings_removed_keys.ts +173 -0
  488. package/src/scripts/_lib/skill_origin.ts +254 -0
  489. package/src/scripts/_lib/structured_ask.ts +64 -21
  490. package/src/scripts/_lib/test_red_state.ts +5 -0
  491. package/src/scripts/_lib/thin_rules.ts +620 -0
  492. package/src/scripts/_lib/touched_file_quality.ts +570 -0
  493. package/src/scripts/_lib/turn_end_refusals.ts +77 -2
  494. package/src/scripts/_lib/windsurf_render.ts +63 -0
  495. package/src/scripts/_lib/worker_budget.ts +1 -1
  496. package/src/scripts/adr/regenerate_index.ts +16 -1
  497. package/src/scripts/ai_council/cli_call_budget.ts +13 -5
  498. package/src/scripts/ai_council/clients.ts +26 -57
  499. package/src/scripts/ai_council/config.ts +8 -8
  500. package/src/scripts/ai_council/modification_review.ts +100 -0
  501. package/src/scripts/ai_council/modification_review_questions.ts +90 -0
  502. package/src/scripts/ai_council/orchestrator.ts +5 -5
  503. package/src/scripts/ai_council/spend_gate.ts +18 -7
  504. package/src/scripts/ai_council/vendor_defaults.ts +45 -0
  505. package/src/scripts/ai_team/config.ts +11 -16
  506. package/src/scripts/audit_skill_overlap.ts +261 -0
  507. package/src/scripts/audit_user_type_axis.ts +20 -8
  508. package/src/scripts/before_complete_hook.ts +243 -12
  509. package/src/scripts/bench_ab_clone.ts +1 -2
  510. package/src/scripts/bench_concern_spawn_path.ts +129 -0
  511. package/src/scripts/bench_graph_feeder_latency.ts +584 -0
  512. package/src/scripts/bench_touched_file_quality.ts +238 -0
  513. package/src/scripts/check_agent_artifact_location.ts +36 -0
  514. package/src/scripts/check_branch_freshness.ts +147 -24
  515. package/src/scripts/check_enforcement_coverage.ts +130 -0
  516. package/src/scripts/check_enforcement_matrix.ts +377 -20
  517. package/src/scripts/check_estate_count.ts +130 -153
  518. package/src/scripts/check_evaluator_budgets.ts +4 -0
  519. package/src/scripts/check_finding_dispositions.ts +196 -21
  520. package/src/scripts/check_gate_coverage.ts +4 -3
  521. package/src/scripts/check_gate_reachability.ts +48 -1
  522. package/src/scripts/check_generator_sync.ts +48 -0
  523. package/src/scripts/check_hook_bundle_composition.ts +477 -0
  524. package/src/scripts/check_kernel_edit_ratified.ts +132 -14
  525. package/src/scripts/check_memory.ts +7 -1
  526. package/src/scripts/check_pack_size.ts +1 -1
  527. package/src/scripts/check_release_highlights.ts +10 -0
  528. package/src/scripts/check_release_manifest_content.ts +189 -0
  529. package/src/scripts/check_release_pr_shape.ts +38 -15
  530. package/src/scripts/check_standing_rule_delivery.ts +129 -13
  531. package/src/scripts/check_suppression_hygiene.ts +2 -5
  532. package/src/scripts/check_verify_expectation_delta.ts +521 -0
  533. package/src/scripts/ci_settle.ts +42 -5
  534. package/src/scripts/classify_merge_risk.ts +320 -0
  535. package/src/scripts/closure_scan.ts +91 -1
  536. package/src/scripts/code_graph/detect.ts +127 -7
  537. package/src/scripts/code_graph/foreign.ts +194 -0
  538. package/src/scripts/code_graph/query.ts +217 -4
  539. package/src/scripts/code_graph/types.ts +26 -2
  540. package/src/scripts/code_graph/validate.ts +3 -1
  541. package/src/scripts/code_graph/verbs.ts +103 -0
  542. package/src/scripts/command_suggester/settings.ts +8 -108
  543. package/src/scripts/command_suggester/types.ts +4 -5
  544. package/src/scripts/compile_hook_manifest.ts +33 -1
  545. package/src/scripts/condense.ts +14 -27
  546. package/src/scripts/consumer_matrix.ts +102 -0
  547. package/src/scripts/context_hygiene_hook.ts +13 -23
  548. package/src/scripts/cost/preflight.mjs +12 -1
  549. package/src/scripts/council_cli.ts +7 -7
  550. package/src/scripts/dispatch_r2_reviewer.ts +24 -11
  551. package/src/scripts/estate_exemption_shape.ts +202 -0
  552. package/src/scripts/explain_run.ts +8 -13
  553. package/src/scripts/generate_git_convention_grammar.ts +127 -0
  554. package/src/scripts/generate_host_cost_table.ts +21 -4
  555. package/src/scripts/handoff_context_hook.ts +86 -37
  556. package/src/scripts/hook_manifest.json +1 -1
  557. package/src/scripts/hook_manifest.yaml +175 -15
  558. package/src/scripts/hooks/block_config_weakening.ts +115 -7
  559. package/src/scripts/hooks/block_plumbing_writes.ts +37 -5
  560. package/src/scripts/hooks/code_graph_context_hook.ts +252 -23
  561. package/src/scripts/hooks/concern_failure_policy.ts +280 -0
  562. package/src/scripts/hooks/concern_registry.ts +2 -0
  563. package/src/scripts/hooks/delegation_nudge_hook.ts +2 -3
  564. package/src/scripts/hooks/dispatch_hook.ts +52 -52
  565. package/src/scripts/hooks/evidence_independence.ts +12 -4
  566. package/src/scripts/hooks/exit_codes.ts +2 -1
  567. package/src/scripts/hooks/host_lowering.json +1 -1
  568. package/src/scripts/hooks/host_lowering.ts +19 -1
  569. package/src/scripts/hooks/mcp_usage_observation_hook.ts +121 -0
  570. package/src/scripts/hooks/one_question_per_ask_hook.ts +41 -25
  571. package/src/scripts/hooks/payload_stub.ts +10 -1
  572. package/src/scripts/hooks/push_settle_hook.ts +4 -3
  573. package/src/scripts/hooks/rule_inject_hook.ts +795 -60
  574. package/src/scripts/hooks/state_io.ts +8 -4
  575. package/src/scripts/hooks/table_fingerprint.ts +21 -0
  576. package/src/scripts/hooks/telemetry_usage_hook.ts +24 -7
  577. package/src/scripts/hooks/turn_end_gate_hook.ts +85 -0
  578. package/src/scripts/hooks_status.ts +112 -0
  579. package/src/scripts/injection_scan_hook.ts +7 -0
  580. package/src/scripts/install-hooks.sh +42 -14
  581. package/src/scripts/install.ts +78 -215
  582. package/src/scripts/installed_layer_report.ts +103 -0
  583. package/src/scripts/language_mirror_hook.ts +15 -6
  584. package/src/scripts/learning_sidecar.ts +119 -8
  585. package/src/scripts/lint_blocker_citations.ts +198 -0
  586. package/src/scripts/lint_evidence_artifacts.ts +9 -0
  587. package/src/scripts/lint_hook_concern_budget.ts +10 -21
  588. package/src/scripts/lint_hook_manifest.ts +53 -0
  589. package/src/scripts/lint_major_migration_sections.ts +156 -26
  590. package/src/scripts/lint_pack_boundaries.ts +465 -29
  591. package/src/scripts/lint_roadmap_blockers.ts +282 -29
  592. package/src/scripts/lint_roadmap_complexity.ts +4 -2
  593. package/src/scripts/lint_rule_law_section.ts +647 -0
  594. package/src/scripts/lint_rule_skill_pack_reach.ts +17 -0
  595. package/src/scripts/lint_settings_classes.ts +1 -1
  596. package/src/scripts/lint_ticked_unmet_criteria.ts +165 -0
  597. package/src/scripts/lint_workflow_security.ts +173 -6
  598. package/src/scripts/mcp_server/consumer_tool_catalog.json +53 -0
  599. package/src/scripts/mcp_server/graph_tools.ts +72 -3
  600. package/src/scripts/measure_routing_signal.ts +54 -16
  601. package/src/scripts/measure_skill_ranker_baseline.ts +221 -8
  602. package/src/scripts/memory_learn_hook.ts +91 -6
  603. package/src/scripts/model_rule_injection.ts +55 -8
  604. package/src/scripts/onboarding_gate_hook.ts +7 -6
  605. package/src/scripts/print_required_checks.ts +15 -3
  606. package/src/scripts/probe_host_compliance.ts +33 -5
  607. package/src/scripts/probe_turnaround.ts +183 -3
  608. package/src/scripts/project_thin_rules.ts +34 -327
  609. package/src/scripts/ratification_header.ts +77 -0
  610. package/src/scripts/refusal_envelope.ts +138 -0
  611. package/src/scripts/release_publication.ts +29 -3
  612. package/src/scripts/report_dispatch_import_closure.ts +76 -0
  613. package/src/scripts/report_evidence_temperature.ts +657 -0
  614. package/src/scripts/report_installed_rule_links.ts +274 -0
  615. package/src/scripts/report_label_agreement.ts +233 -0
  616. package/src/scripts/report_module_reach.ts +212 -0
  617. package/src/scripts/report_obligation_mechanism.ts +228 -0
  618. package/src/scripts/report_parked_blockers.ts +222 -0
  619. package/src/scripts/report_required_checks_drift.ts +196 -0
  620. package/src/scripts/report_single_token_triggers.ts +277 -0
  621. package/src/scripts/report_skill_ranker_confusion.ts +543 -0
  622. package/src/scripts/report_standing_payload_by_host.ts +188 -15
  623. package/src/scripts/report_verification_record_coverage.ts +306 -0
  624. package/src/scripts/routing_doctor.ts +2 -2
  625. package/src/scripts/rule_activation_census.ts +5 -2
  626. package/src/scripts/rule_trigger_eval.ts +9 -9
  627. package/src/scripts/schemas/agent-settings.schema.json +17 -31
  628. package/src/scripts/schemas/command.schema.json +1 -1
  629. package/src/scripts/schemas/review-findings.schema.json +3 -2
  630. package/src/scripts/schemas/rule.schema.json +5 -0
  631. package/src/scripts/schemas/skill.schema.json +1 -1
  632. package/src/scripts/self_review_gate.ts +101 -9
  633. package/src/scripts/skill_tools/score_skill_relevance.ts +314 -40
  634. package/src/scripts/sweep_skill_ranker_signals.ts +129 -0
  635. package/src/scripts/sync_gitignore.ts +29 -235
  636. package/src/scripts/sync_pr_branch.ts +403 -132
  637. package/src/scripts/trigger_eval_grandfather.json +0 -21
  638. package/src/server/schemas/settings.ts +42 -176
  639. package/src/shared/skillRanking.ts +169 -14
  640. package/dist/cli-delegate/chunk-5R7HOLAF.js +0 -99
  641. package/dist/cli-delegate/chunk-JNMVLWLR.js +0 -4457
  642. package/dist/cli-delegate/chunk-YDPILCOL.js +0 -152
  643. package/src/scripts/_lib/config_chain.ts +0 -477
  644. package/src/scripts/_lib/conformance_report.ts +0 -403
  645. package/src/scripts/_lib/eval_discrimination.ts +0 -95
  646. package/src/scripts/_lib/evaluator_promotion.ts +0 -225
  647. package/src/scripts/_lib/file_slicer.ts +0 -117
  648. package/src/scripts/_lib/legacy_boundary_map.ts +0 -228
  649. package/src/scripts/_lib/minimality_tiebreak.ts +0 -168
  650. package/src/scripts/_lib/overbuild_lens_contract.ts +0 -196
  651. package/src/scripts/_lib/role_split.ts +0 -338
@@ -0,0 +1,963 @@
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 os from 'node:os';
45
+ import * as path from 'node:path';
46
+ import { fileURLToPath } from 'node:url';
47
+
48
+ import {
49
+ GLOBAL_RULE_DIRS,
50
+ PROJECT_RULE_DIRS,
51
+ globalRuleLayerPath,
52
+ } from '../../install/globalRuleLayers.js';
53
+ import { NO_RECORDED_HASHES, readRecordedHashes } from '../../install/recordedOwnership.js';
54
+ import {
55
+ INVENTORY_BASENAME,
56
+ inventory_path,
57
+ load_inventory,
58
+ recorded_absolute_files,
59
+ } from './global_deploy_inventory.js';
60
+ import { DEFAULT_EVENT4U_ROOT_RELATIVE } from './user_global_paths.js';
61
+ import { ruleBody, splitFrontmatter } from './rule_law_section.js';
62
+
63
+ /** How many files the per-directory ranking carries. Step 0.1 fixes it at 20. */
64
+ export const TOP_FILES = 20;
65
+
66
+ /** Which layer a directory belongs to — the two the host unions when it loads. */
67
+ export type LayerScope = 'global' | 'project';
68
+
69
+ /** One rule file, measured the way the host loads it. */
70
+ export interface RuleFileReading {
71
+ /** Basename, which is what a reader opening the directory sees. */
72
+ readonly file: string;
73
+ /** Characters AFTER the frontmatter and block-comment strip. */
74
+ readonly chars: number;
75
+ /** False when the file carries a `paths:` key — then the host loads it on a path match. */
76
+ readonly unconditional: boolean;
77
+ /** True when the installed-tools manifest records this path, in either state. */
78
+ readonly package_owned: boolean;
79
+ }
80
+
81
+ /** One host rule directory, which is the unit step 0.1 asks the report to be in. */
82
+ export interface LayerReading {
83
+ /** Tool id, as `GLOBAL_RULE_DIRS` spells it. */
84
+ readonly host: string;
85
+ readonly scope: LayerScope;
86
+ readonly dir: string;
87
+ /** False when the directory does not exist — a real answer, never an error. */
88
+ readonly present: boolean;
89
+ readonly files: number;
90
+ readonly unconditional: number;
91
+ readonly chars: number;
92
+ readonly package_owned: number;
93
+ readonly foreign: number;
94
+ /**
95
+ * The same split as `package_owned` / `foreign`, in CHARACTERS.
96
+ *
97
+ * Both axes are kept because they answer different questions and the file
98
+ * counts alone cannot answer the one step 1.4 asks. "How many of the files
99
+ * does this install claim" is a reaping question; "how much of what the
100
+ * host loads does this install account for" is a budget question, and one
101
+ * 30,000-character rule against twenty 200-character ones makes the two
102
+ * readings disagree by an order of magnitude. The limit rows below are
103
+ * built from the character axis for exactly that reason.
104
+ */
105
+ readonly package_owned_chars: number;
106
+ readonly foreign_chars: number;
107
+ /**
108
+ * `chars`, split by WHEN the host loads the file — the second axis.
109
+ *
110
+ * `unconditional` already counts the FILES the host loads every session;
111
+ * these two count their characters, and the pair is what a ceiling can
112
+ * actually be read against. A layer of 100 files can be 90 % path-scoped by
113
+ * file count and 90 % unconditional by character count, so a ceiling stated
114
+ * in characters cannot be answered from the file counts at all — which is
115
+ * the gap step 1.1 closes.
116
+ *
117
+ * `unconditional_chars + scoped_chars === chars` holds by construction:
118
+ * {@link isUnconditional} is a total predicate over the same readings the
119
+ * total sums, so no file lands in both buckets or in neither.
120
+ *
121
+ * NOT the same split as `package_owned_chars` / `foreign_chars`. That pair
122
+ * answers "whose file is this"; this pair answers "when does the host load
123
+ * it". A file can be package-owned and path-scoped at once, and the two
124
+ * splits are therefore independent rather than refinements of each other.
125
+ */
126
+ readonly unconditional_chars: number;
127
+ readonly scoped_chars: number;
128
+ /** The {@link TOP_FILES} largest, by the SAME measure as `chars`. */
129
+ readonly top: readonly RuleFileReading[];
130
+ }
131
+
132
+ /**
133
+ * The fraction of a published limit at which the report warns.
134
+ *
135
+ * 0.8 is step 1.4's number, not a tuned one. It is a WARNING threshold and
136
+ * never a gate: crossing it changes what the report says and nothing else, so
137
+ * no install fails here.
138
+ */
139
+ export const LIMIT_WARN_FRACTION = 0.8;
140
+
141
+ /** What this tree can say about a host's published instruction budget. */
142
+ export type HostLimitKind = 'vendor-published' | 'reported' | 'unpublished';
143
+
144
+ /**
145
+ * One host, measured against its published limit — step 3.5's per-host row.
146
+ *
147
+ * The row is per HOST and not per directory, because the limit applies to what
148
+ * the host loads and the host unions its global and project layers. A host
149
+ * whose two directories are each comfortably under the limit can still be over
150
+ * it together, and a per-directory row would never show that.
151
+ */
152
+ export interface HostLimitReading {
153
+ readonly host: string;
154
+ /** `null` when this tree records no limit — which fires no warning. */
155
+ readonly limit: number | null;
156
+ readonly kind: HostLimitKind;
157
+ /** Every scope for this host, summed. */
158
+ readonly chars: number;
159
+ readonly package_owned_chars: number;
160
+ readonly foreign_chars: number;
161
+ /** `chars / limit`, or `null` when there is no limit to be a fraction of. */
162
+ readonly fraction: number | null;
163
+ /**
164
+ * True only when a limit EXISTS and the combined total reaches
165
+ * {@link LIMIT_WARN_FRACTION} of it — 80 %, not 100 %. The field is an
166
+ * early warning, which is the whole point of a fraction below 1.
167
+ *
168
+ * An absent limit never warns. That is the conservative direction and it is
169
+ * deliberate: a warning derived from a limit nobody recorded would be a
170
+ * fabricated finding, which is worse than a silent row a reader can see is
171
+ * unmeasured.
172
+ */
173
+ readonly warn: boolean;
174
+ }
175
+
176
+ export interface InstalledLayerReport {
177
+ readonly home: string;
178
+ readonly project_root: string;
179
+ /** `claude --version`, or `unknown`. Never fabricated. */
180
+ readonly host_version: string;
181
+ /**
182
+ * False when no installed-tools MANIFEST resolved.
183
+ *
184
+ * No longer the same question as "is there ownership evidence" — read
185
+ * {@link ownership_source} for that. A global-only install has no manifest
186
+ * and full evidence; keeping this field answers the narrower question a
187
+ * project-scoped reader still asks.
188
+ */
189
+ readonly manifest_present: boolean;
190
+ /** Which evidence answered ownership. `none` means no evidence, never "not ours". */
191
+ readonly ownership_source: OwnershipSource;
192
+ /** Every contributor, because the layers span two path spaces. See {@link OwnershipResolution.sources}. */
193
+ readonly ownership_sources: readonly OwnershipSource[];
194
+ readonly layers: readonly LayerReading[];
195
+ /** One row per host, against its published limit. Step 3.5. */
196
+ readonly limits: readonly HostLimitReading[];
197
+ /** Every layer summed, which is what the host loads when it unions the two scopes. */
198
+ readonly totals: {
199
+ readonly files: number;
200
+ readonly unconditional: number;
201
+ readonly chars: number;
202
+ readonly package_owned: number;
203
+ readonly foreign: number;
204
+ readonly package_owned_chars: number;
205
+ readonly foreign_chars: number;
206
+ /** The when-loaded split, summed. See {@link LayerReading.unconditional_chars}. */
207
+ readonly unconditional_chars: number;
208
+ readonly scoped_chars: number;
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Whether the host loads this file on every session rather than on a path match.
214
+ *
215
+ * `paths:` at the start of a line IN THE FRONTMATTER is the whole test, and it
216
+ * is the host's own convention rather than this package's: `claudeRuleRewrite`
217
+ * states it from the emitting side — "with it a rule loads on a path match,
218
+ * without it the rule loads unconditionally".
219
+ *
220
+ * SCOPED TO THE FRONTMATTER, which it was not until 2026-10-06. The predicate
221
+ * tested the whole unstripped file while the characters it partitions come
222
+ * from `ruleBody` — predicate and measure read different strings. That was
223
+ * survivable while it only moved a file COUNT; since the when-loaded character
224
+ * split it decides which bucket a whole file's characters land in, and
225
+ * `unconditional_chars` is the figure the ceiling argument is read in. One body
226
+ * line beginning `paths:` — a YAML trigger example quoted inside a kernel or
227
+ * `no_stub` rule, which is ordinary prose — would have moved that rule's entire
228
+ * character count from standing to path-scoped, silently.
229
+ *
230
+ * Measured before the change: of the 105 files on a real opted-in install, two
231
+ * are path-scoped by either reading and none carries a body-only mention, so
232
+ * the fix moves no published figure. It is hardening against a case that does
233
+ * not exist yet rather than a correction of one that does, and it is worth
234
+ * making precisely because the failure would be silent.
235
+ *
236
+ * `check_single_delivery` already slices the frontmatter before this test, so
237
+ * this now AGREES with it. `rule_activation_census.projection_reading` still
238
+ * scans the whole file; that reader counts files rather than characters, so the
239
+ * divergence costs it nothing today, and changing a census's reading belongs to
240
+ * that census rather than here. Named so the next reader finds a known
241
+ * difference instead of discovering an unexplained one.
242
+ *
243
+ * SOURCE-SIDE CLASSIFICATION IS A DIFFERENT QUESTION and deliberately not used.
244
+ * `classify_rule` reads `alwaysApply` and `type: always` to tell `always` from
245
+ * `unconditional`, which matters when you are deciding what to emit. This
246
+ * reads what WAS emitted, where the distinction has already been spent: an
247
+ * Iron-Law rule and an untriggered tier-2 rule both arrive without `paths:` and
248
+ * both load every session, which is the only property the host acts on.
249
+ */
250
+ export function isUnconditional(text: string): boolean {
251
+ // THE SAME FUNCTION that produces the characters being partitioned, not the
252
+ // same arithmetic re-implemented. An earlier version scanned for
253
+ // `'\n---'` while `ruleBody` -> `splitFrontmatter` scans for `'\n---\n'`,
254
+ // so the two disagreed about where the frontmatter stops, and the
255
+ // predicate's early stop could hide a later `paths:` key.
256
+ const [frontmatter] = splitFrontmatter(text);
257
+ // AND THE BLIND SPOT THAT SCOPING OPENS, CLOSED HERE. `splitFrontmatter`
258
+ // returns `''` for anything that does not begin exactly `---\n` — a BOM, a
259
+ // leading blank line, CRLF, a block that never closes — and scoping to `''`
260
+ // reads every such file as unconditional even when its frontmatter carries
261
+ // `paths:`. `readLayer` measures every `.md` in a host directory, foreign
262
+ // user-authored rules included, so that would inflate `unconditional_chars`
263
+ // silently, which is the figure the whole ceiling argument is read in.
264
+ //
265
+ // A file that CLAIMS a block `splitFrontmatter` could not parse falls back
266
+ // to the whole-text test. The claim test is the one `installThinLayer`
267
+ // already uses for the same blind spot, and is a regex rather than
268
+ // `startsWith('---')` for the reason stated there.
269
+ //
270
+ // WHICH WAY THE FALLBACK ERRS, SAID PLAINLY RATHER THAN CALLED
271
+ // "CONSERVATIVE". It can only move characters OUT of the standing bucket,
272
+ // never into it. That is conservative about the claim "this rule stands
273
+ // every session" — it never asserts one it cannot support. It is the
274
+ // FLATTERING direction for the ceiling argument, which reads the standing
275
+ // figure against a limit: a misfire here makes the layer look smaller than
276
+ // it is. Both halves are true and the second is the one a reader checking
277
+ // the overage needs, so it is stated rather than left to the word.
278
+ //
279
+ // AND THE STATE THAT IS NOT AN UNPARSEABLE BLOCK. `splitFrontmatter`
280
+ // returns '' for a WELL-FORMED EMPTY block too — `---\n---\n` has no
281
+ // `\n---\n` after index 4, so its own parse fails — and such a file has no
282
+ // `paths:` key by construction. Without the second test it fell to the
283
+ // whole-text scan and a body line beginning `paths:` (a quoted trigger
284
+ // example, which is exactly what a rule about triggers contains) moved the
285
+ // file's entire character count out of the standing figure. Measured
286
+ // before the exclusion: `---\n---\npaths: not a key, just prose\n` read
287
+ // path-scoped.
288
+ const emptyBlock = /^---\n---(\n|$)/.test(text);
289
+ if (frontmatter === '' && !emptyBlock && /^\uFEFF?[\s]*---/.test(text)) {
290
+ return !/^paths:/m.test(text);
291
+ }
292
+ return !/^paths:/m.test(frontmatter);
293
+ }
294
+
295
+
296
+ /**
297
+ * Measure one directory.
298
+ *
299
+ * `.md` only, matching `censusRuleDir` and `topRulesByCost`, so the FILE count
300
+ * is comparable with the existing byte censuses even though the character count
301
+ * is not. Cursor's `.mdc` tree is therefore read as zero files, which is the
302
+ * same blind spot every other census in this tree has and is not fixed here —
303
+ * fixing it in one reader and not the others would make two counts that
304
+ * disagree, which is worse than one that is openly partial.
305
+ */
306
+ export function readLayer(
307
+ host: string,
308
+ scope: LayerScope,
309
+ dir: string,
310
+ recorded: ReadonlyMap<string, string | null>,
311
+ ): LayerReading {
312
+ const empty = {
313
+ files: 0,
314
+ unconditional: 0,
315
+ chars: 0,
316
+ package_owned: 0,
317
+ foreign: 0,
318
+ package_owned_chars: 0,
319
+ foreign_chars: 0,
320
+ unconditional_chars: 0,
321
+ scoped_chars: 0,
322
+ top: [],
323
+ };
324
+ let names: string[];
325
+ try {
326
+ names = fs.readdirSync(dir);
327
+ } catch {
328
+ return { host, scope, dir, present: false, ...empty };
329
+ }
330
+ const readings: RuleFileReading[] = [];
331
+ for (const name of names.sort()) {
332
+ if (!name.endsWith('.md')) continue;
333
+ const abs = path.join(dir, name);
334
+ let text: string;
335
+ try {
336
+ const st = fs.statSync(abs);
337
+ if (!st.isFile()) continue;
338
+ text = fs.readFileSync(abs, 'utf-8');
339
+ } catch {
340
+ continue; // a file that cannot be read is not a file the host loads
341
+ }
342
+ // RECORDED-BUT-MODIFIED IS STILL RECORDED. The report's two columns
343
+ // answer "does this install claim the file"; `classifyOwnership` answers
344
+ // the finer question the upgrade path needs — whether the bytes still
345
+ // match — and folding that in here would give a column three meanings.
346
+ const owned = recorded.has(abs);
347
+ readings.push({
348
+ file: name,
349
+ // THE STRIP IS THE PROJECTOR'S OWN. `ruleBody` is what
350
+ // `project_thin_rules` and the delivery carrier both call, so "what
351
+ // a host loads" and "what a delivery carries" cannot drift into two
352
+ // spellings of the same measurement.
353
+ chars: ruleBody(text).length,
354
+ unconditional: isUnconditional(text),
355
+ package_owned: owned,
356
+ });
357
+ }
358
+ const top = [...readings]
359
+ // RANKED BY THE SAME MEASURE AS THE TOTAL, which is why
360
+ // `preamble_byte_census.topRulesByCost` is not reused here: it ranks by
361
+ // `statSync().size`, so its list and this total would be in different
362
+ // units and the largest file by one could be absent from the other.
363
+ .sort((a, b) => b.chars - a.chars || a.file.localeCompare(b.file))
364
+ .slice(0, TOP_FILES);
365
+ return {
366
+ host,
367
+ scope,
368
+ dir,
369
+ present: true,
370
+ files: readings.length,
371
+ unconditional: readings.filter((r) => r.unconditional).length,
372
+ chars: readings.reduce((n, r) => n + r.chars, 0),
373
+ package_owned: readings.filter((r) => r.package_owned).length,
374
+ foreign: readings.filter((r) => !r.package_owned).length,
375
+ package_owned_chars: readings.reduce((n, r) => n + (r.package_owned ? r.chars : 0), 0),
376
+ foreign_chars: readings.reduce((n, r) => n + (r.package_owned ? 0 : r.chars), 0),
377
+ unconditional_chars: readings.reduce((n, r) => n + (r.unconditional ? r.chars : 0), 0),
378
+ scoped_chars: readings.reduce((n, r) => n + (r.unconditional ? 0 : r.chars), 0),
379
+ top,
380
+ };
381
+ }
382
+
383
+ /** The shape of one entry in `src/config/host-instruction-limits.json`. */
384
+ interface RawHostLimit {
385
+ readonly limit?: unknown;
386
+ readonly kind?: unknown;
387
+ }
388
+
389
+ /**
390
+ * Where the limits live, derived from this module rather than from `cwd`.
391
+ *
392
+ * The two `..` hops climb from `src/scripts/_lib` to `src/`, which is where
393
+ * `config/` lives — NOT to the package root, though the comment said so until
394
+ * 2026-10-05. The result is correct and the arithmetic stated for it was not,
395
+ * which is the half a future reader would trust when changing the hops.
396
+ *
397
+ * `fileURLToPath`, NOT `new URL(...).pathname` — the claimed parity with
398
+ * `default_template_path()` was only half true until 2026-10-05. A `pathname`
399
+ * percent-encodes, so a package under a directory with a space resolved to
400
+ * `/Users/x/My%20Projects/...`, and on Windows it yields a leading-slash drive
401
+ * path. Either way the read fails, `loadHostInstructionLimits` swallows it and
402
+ * returns an empty map, and EVERY host then reads `unpublished` with `warn`
403
+ * false — the budget measurement degrading to "not measured" with no signal,
404
+ * which is the one failure mode this file's own limit table was written to
405
+ * avoid.
406
+ */
407
+ export function defaultHostLimitsPath(): string {
408
+ return path.join(
409
+ path.dirname(fileURLToPath(import.meta.url)),
410
+ '..',
411
+ '..',
412
+ 'config',
413
+ 'host-instruction-limits.json',
414
+ );
415
+ }
416
+
417
+ /**
418
+ * Read the published-limit table.
419
+ *
420
+ * TOLERANT IN ONE DIRECTION ONLY. An unreadable or malformed file yields an
421
+ * EMPTY table, so every host reads `unpublished` and nothing warns — a reading
422
+ * this tree cannot stand behind must never become a warning it can. A present
423
+ * entry whose `limit` is not a positive finite number is treated the same way,
424
+ * because a `0` or a string would otherwise make every install look saturated.
425
+ */
426
+ export function loadHostInstructionLimits(
427
+ file: string = defaultHostLimitsPath(),
428
+ ): ReadonlyMap<string, { limit: number | null; kind: HostLimitKind }> {
429
+ const out = new Map<string, { limit: number | null; kind: HostLimitKind }>();
430
+ let raw: unknown;
431
+ try {
432
+ raw = JSON.parse(fs.readFileSync(file, 'utf-8'));
433
+ } catch {
434
+ return out; // no table ⇒ no limits ⇒ no warnings
435
+ }
436
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) return out;
437
+ for (const [host, v] of Object.entries(raw as Record<string, unknown>)) {
438
+ if (host.startsWith('_')) continue; // `_comment` and friends are not hosts
439
+ if (typeof v !== 'object' || v === null || Array.isArray(v)) continue;
440
+ const e = v as RawHostLimit;
441
+ const n = e.limit;
442
+ const limit = typeof n === 'number' && Number.isFinite(n) && n > 0 ? n : null;
443
+ const k = e.kind;
444
+ const kind: HostLimitKind =
445
+ limit === null
446
+ ? 'unpublished'
447
+ : k === 'vendor-published' || k === 'reported'
448
+ ? k
449
+ : 'reported'; // a cited figure with no stated kind is the weaker one
450
+ out.set(host, { limit, kind });
451
+ }
452
+ return out;
453
+ }
454
+
455
+ /**
456
+ * Fold the per-directory readings into one row per host, against its limit.
457
+ *
458
+ * Exported so a caller holding layers it built itself — the install receipt,
459
+ * which measures before and after — can produce the same rows without
460
+ * re-reading the tree.
461
+ */
462
+ export function buildHostLimitRows(
463
+ layers: readonly LayerReading[],
464
+ limits: ReadonlyMap<string, { limit: number | null; kind: HostLimitKind }>,
465
+ ): HostLimitReading[] {
466
+ const byHost = new Map<string, { chars: number; owned: number; foreign: number }>();
467
+ for (const l of layers) {
468
+ if (!l.present) continue;
469
+ const acc = byHost.get(l.host) ?? { chars: 0, owned: 0, foreign: 0 };
470
+ acc.chars += l.chars;
471
+ acc.owned += l.package_owned_chars;
472
+ acc.foreign += l.foreign_chars;
473
+ byHost.set(l.host, acc);
474
+ }
475
+ // EVERY host that has a directory gets a row, present or not — a host whose
476
+ // layer is absent reads 0 against its limit, which is a real answer and the
477
+ // one step 3.5 asks for ("one row per host with a published limit").
478
+ for (const l of layers) if (!byHost.has(l.host)) byHost.set(l.host, { chars: 0, owned: 0, foreign: 0 });
479
+ // AND every host the LIMITS table knows, whether or not a layer reached it.
480
+ // Rows were layer-driven while the requirement is limit-driven, so a limit
481
+ // recorded for a host the layer map does not list disappeared silently —
482
+ // the two sources agree today, which means they can only diverge in the
483
+ // direction the code did not cover.
484
+ for (const [host, e] of limits) if (e.limit !== null && !byHost.has(host)) {
485
+ byHost.set(host, { chars: 0, owned: 0, foreign: 0 });
486
+ }
487
+ const rows: HostLimitReading[] = [];
488
+ for (const host of [...byHost.keys()].sort()) {
489
+ const a = byHost.get(host) as { chars: number; owned: number; foreign: number };
490
+ const e = limits.get(host) ?? { limit: null, kind: 'unpublished' as HostLimitKind };
491
+ const fraction = e.limit === null ? null : a.chars / e.limit;
492
+ rows.push({
493
+ host,
494
+ limit: e.limit,
495
+ kind: e.kind,
496
+ chars: a.chars,
497
+ package_owned_chars: a.owned,
498
+ foreign_chars: a.foreign,
499
+ fraction,
500
+ warn: fraction !== null && fraction >= LIMIT_WARN_FRACTION,
501
+ });
502
+ }
503
+ return rows;
504
+ }
505
+
506
+ /** Which evidence answered "is this file ours". Never a guess — see {@link resolveLayerOwnership}. */
507
+ export type OwnershipSource = 'manifest' | 'global-inventory' | 'this-deploy' | 'none';
508
+
509
+ /**
510
+ * Where `deployed-files.json` lives FOR A STATED HOME.
511
+ *
512
+ * `global_deploy_inventory.inventory_path()` answers for the process asking,
513
+ * honouring `AGENT_CONFIG_DEPLOY_INVENTORY` and `EVENT4U_CONFIG_HOME`. That is
514
+ * right for the installer, which is running the install it is recording, and
515
+ * WRONG for a reader measuring some other home: a report about a staged install
516
+ * under a temporary HOME would read the maintainer's own inventory and claim
517
+ * ownership of files that install never wrote.
518
+ *
519
+ * SO AN EXPLICITLY SUPPLIED HOME WINS, AND THE ENV OVERRIDES DECIDE ONLY THE
520
+ * HOME-LESS CASE. A caller that says which home it is measuring has already
521
+ * answered the question those variables would answer for it, and letting the
522
+ * asking process's environment override that is exactly the hazard named above.
523
+ * An intermediate draft of this function had the precedence the other way round
524
+ * while this paragraph said what it says now; the code is what changed.
525
+ *
526
+ * THE CONSEQUENCE, STATED RATHER THAN IMPLIED: `ReportOptions.home` is
527
+ * REQUIRED, so every caller of {@link buildInstalledLayerReport} supplies one
528
+ * and neither env override reaches this reader through that path. That is
529
+ * correct — those variables describe the asking process's own install, which is
530
+ * not what a report about another home is measuring — but it means "honours the
531
+ * env override" is not a true sentence about the report, and saying so here is
532
+ * cheaper than leaving the next reader to discover it.
533
+ *
534
+ * The layout is COMPOSED from the modules that own it rather than re-spelled,
535
+ * so a layout change moves this reader with it instead of leaving it looking in
536
+ * a directory nobody writes.
537
+ */
538
+ export function defaultInventoryPath(home?: string | null): string {
539
+ // NO HOME STATED — the asking process is the subject, so the env-aware
540
+ // resolver is the right answer.
541
+ if (home === undefined || home === null || home === '') return inventory_path();
542
+ // THE STATED HOME IS THE ASKING PROCESS'S OWN. `ReportOptions.home` is
543
+ // required, so `buildInstalledLayerReport` ALWAYS states one — including
544
+ // when it is simply measuring the machine it is running on. Treating that
545
+ // as "a different home" sent it to the hardcoded layout, found nothing
546
+ // under `EVENT4U_CONFIG_HOME` or `AGENT_CONFIG_DEPLOY_INVENTORY`, and
547
+ // printed "NO manifest and NO deploy inventory resolved" — the
548
+ // `0 package-owned` reading step 1.2 exists to remove, and it put the
549
+ // report and the receipt in disagreement about one install.
550
+ //
551
+ // The precedence argument only ever covered a home that is NOT this
552
+ // process's, so that is the case the branch now tests for.
553
+ try {
554
+ // BOTH SPELLINGS, for the reason `recorded_absolute_files` states at
555
+ // length: a lexical compare alone calls a symlinked or alternate
556
+ // spelling of the SAME home "a different home", takes the hardcoded
557
+ // layout, and reproduces the 0 package-owned reading this branch
558
+ // exists to prevent. macOS `/var` -> `/private/var` is the standing
559
+ // example.
560
+ const real = (q: string): string => {
561
+ try {
562
+ return fs.realpathSync(q);
563
+ } catch {
564
+ return path.resolve(q);
565
+ }
566
+ };
567
+ const mine = os.homedir();
568
+ if (path.resolve(home) === path.resolve(mine) || real(home) === real(mine)) {
569
+ return inventory_path();
570
+ }
571
+ } catch {
572
+ // `homedir()` can throw on an exotic environment; fall through to the
573
+ // layout, which is the answer for a home that is not ours anyway.
574
+ }
575
+ return path.join(home, DEFAULT_EVENT4U_ROOT_RELATIVE, INVENTORY_BASENAME);
576
+ }
577
+
578
+
579
+ /** The ownership evidence and the name of where it came from. */
580
+ export interface OwnershipResolution {
581
+ /** Absolute path → recorded hash (or `null` when the source records no hash). */
582
+ readonly recorded: ReadonlyMap<string, string | null>;
583
+ /**
584
+ * EVERY source that contributed at least one path, in resolution order.
585
+ *
586
+ * Plural because the sources describe disjoint path spaces — a manifest
587
+ * speaks for a project tree, an inventory for the user's home — so a
588
+ * reading that covers both layers can legitimately have two, and naming
589
+ * only one is wrong about the files the other accounts for.
590
+ */
591
+ readonly sources: readonly OwnershipSource[];
592
+ /** The most specific contributor, for a reader that wants one word. `none` when empty. */
593
+ readonly source: OwnershipSource;
594
+ }
595
+
596
+ /** What {@link resolveLayerOwnership} may read. Every input is a parameter. */
597
+ export interface OwnershipOptions {
598
+ /** Where the project manifest would be. Absent file → fall through, never an error. */
599
+ readonly manifestPath?: string | null;
600
+ /** Needed only to resolve the manifest's relative entries. */
601
+ readonly projectRoot: string;
602
+ /**
603
+ * The home the install being measured lives under.
604
+ *
605
+ * The inventory records its anchor tilde-relative (`~/.claude/`), so a
606
+ * reader measuring a staged install under a temporary HOME must say which
607
+ * home, or the recorded paths resolve against the asking process's own and
608
+ * claim nothing. Omitted → `os.homedir()`.
609
+ */
610
+ readonly home?: string | null | undefined;
611
+ /** `deployed-files.json`. `undefined` reads the user-global default; `null` reads none. */
612
+ readonly inventoryPath?: string | null | undefined;
613
+ /**
614
+ * Anchor → anchor-relative paths THIS run just wrote.
615
+ *
616
+ * The first-install case, and the reason it needs its own input: the
617
+ * install receipt prints before `record_deploy`, so on a first install the
618
+ * inventory on disk is still empty and the only evidence that the files are
619
+ * ours is the set the installer is holding. Passing it is counting this
620
+ * deploy's own file set — not a claim about any earlier one.
621
+ *
622
+ * CLOSED FOR ONE ANCHOR OF FIVE, stated rather than implied. The only
623
+ * production caller (`install.ts`, inside the `claude-code` branch) passes
624
+ * a single-entry map for that anchor, while `installReceiptBudgetLines`
625
+ * reads all five `GLOBAL_RULE_DIRS`. So on a genuine first install the
626
+ * cursor, augment, windsurf and cline global layers still read foreign in
627
+ * the receipt. That is narrower than "the first install is covered", which
628
+ * an earlier version of this paragraph said, and it is the honest scope:
629
+ * the receipt is printed from the one branch that has a file set to offer.
630
+ */
631
+ readonly thisDeploy?: ReadonlyMap<string, Iterable<string>> | null | undefined;
632
+ }
633
+
634
+ /**
635
+ * The ONE place that answers "which of these installed files are ours".
636
+ *
637
+ * Two readers ask it — the installed-layer report and the install receipt's
638
+ * budget lines — and until step 1.2 they asked different things. Both called
639
+ * `manifest_path(...)` and read a project-scoped `agents/installed-tools.lock`.
640
+ * A global-only install writes nothing into a project, so on exactly the
641
+ * install shape the thinned-layer roadmap's AC-1 is about, both reported
642
+ * `0 package-owned / N foreign` and AC-1's figure could not be produced at all.
643
+ *
644
+ * The global install DOES record what it wrote: `deployed-files.json` lists,
645
+ * per tool, the anchor and every file the installer maintains there —
646
+ * "the ownership proof that makes reaping safe". This reads it.
647
+ *
648
+ * IT IS A UNION, AND THE PATH SPACES ARE WHY. An earlier shape let the project
649
+ * manifest win OUTRIGHT whenever its file existed, on the reasoning that it
650
+ * carries hashes an inventory does not. That reasoning does not survive the
651
+ * observation that the two sources describe DISJOINT sets of paths: the
652
+ * manifest records project-relative entries resolved against `projectRoot`,
653
+ * the inventory records paths under the user's home. Exclusivity therefore
654
+ * bought nothing and cost everything — any `agents/installed-tools.lock` on
655
+ * disk, a gitignored file a single project-scoped install creates, restored
656
+ * the exact `0 package-owned` reading for the global layer that step 1.2
657
+ * exists to fix, while the report printed "read from the installed-tools
658
+ * manifest" with no evidence behind it for those paths.
659
+ *
660
+ * So every source contributes, and the MANIFEST IS MERGED LAST: on the paths
661
+ * it names its hash overwrites a hashless entry, which preserves the
662
+ * `recorded-unchanged` / `recorded-modified` distinction that motivated the
663
+ * precedence in the first place, without discarding evidence about paths it
664
+ * says nothing about.
665
+ *
666
+ * {@link OwnershipResolution.sources} lists every source that contributed at
667
+ * least one path, because one word cannot describe two path spaces — a report
668
+ * reading global and project layers together may have manifest evidence for
669
+ * one and inventory evidence for the other, and saying only one of them is
670
+ * wrong about half the files it is describing.
671
+ *
672
+ * A FILE THE USER EDITED AND THE INSTALLER PRESERVED STILL COUNTS AS
673
+ * MAINTAINED. The inventory records what the installer MAINTAINS at a path, not
674
+ * what the bytes currently are, so a preserved rule stays package-owned. That
675
+ * is the correct reading — the package is still responsible for the path — and
676
+ * it is why this function returns `null` hashes rather than fabricating one.
677
+ */
678
+ export function resolveLayerOwnership(opts: OwnershipOptions): OwnershipResolution {
679
+ const recorded = new Map<string, string | null>();
680
+ const sources: OwnershipSource[] = [];
681
+
682
+ // HASHLESS SOURCES FIRST, so the manifest's hashes overwrite a `null` on
683
+ // any path both happen to name rather than the other way round.
684
+ if (opts.inventoryPath !== null) {
685
+ let n = 0;
686
+ try {
687
+ const inv = load_inventory(opts.inventoryPath ?? defaultInventoryPath(opts.home));
688
+ for (const abs of recorded_absolute_files(inv, opts.home ?? null)) {
689
+ // COUNT PATHS NEWLY CONTRIBUTED, not entries read, so a
690
+ // source that added nothing is not named as evidence.
691
+ //
692
+ // WHAT THIS GUARD DOES NOT DO, since an earlier comment here
693
+ // claimed it did: it does not collapse the two spellings
694
+ // `recorded_absolute_files` emits per file. The lexical and the
695
+ // realpath spelling are distinct Map keys, so wherever they
696
+ // differ — every path under macOS `/var` — each is newly
697
+ // contributed and `n` still rises twice for one file. Harmless
698
+ // only because `n` is read as `> 0` and never as a count; a
699
+ // future reader wanting a count must not take it from here.
700
+ if (!recorded.has(abs)) n += 1;
701
+ recorded.set(abs, null);
702
+ }
703
+ } catch {
704
+ // Defence in depth, and NOT where the documented behaviour lives.
705
+ // "A corrupt inventory is no evidence, never a failure" is real but
706
+ // is carried one module down: `load_inventory` swallows its own
707
+ // read and parse failures and returns an empty shell, and
708
+ // `recorded_absolute_files` type-guards every field. So this catch
709
+ // is unreachable for that reason and the fixture case named for it
710
+ // exercises `load_inventory`, not this guard. Kept because a future
711
+ // change to either could make it reachable; labelled so it is not
712
+ // mistaken for tested coverage.
713
+ }
714
+ if (n > 0) sources.push('global-inventory');
715
+ }
716
+
717
+ let fromDeploy = 0;
718
+ for (const [anchor, rels] of opts.thisDeploy ?? new Map()) {
719
+ for (const rel of rels) {
720
+ const abs = path.resolve(anchor, rel);
721
+ // Same rule as the inventory branch: a source is named only when it
722
+ // contributes a path nothing else had. On every install after the
723
+ // first, `thisDeploy`'s paths are already in the inventory, and
724
+ // naming it there printed "the file set this install just wrote"
725
+ // for a source that added nothing.
726
+ if (!recorded.has(abs)) fromDeploy += 1;
727
+ recorded.set(abs, null);
728
+ // Both spellings, for the reason `recorded_absolute_files` states:
729
+ // the reader compares against a lexically joined path, and a HOME
730
+ // behind a symlink makes the two disagree.
731
+ try {
732
+ recorded.set(fs.realpathSync(abs), null);
733
+ } catch {
734
+ // Not on disk — the lexical spelling is all there is.
735
+ }
736
+ }
737
+ }
738
+ if (fromDeploy > 0) sources.push('this-deploy');
739
+
740
+ const manifestPath = opts.manifestPath ?? null;
741
+ if (manifestPath !== null && fs.existsSync(manifestPath)) {
742
+ let n = 0;
743
+ // DEGRADE LIKE THE INVENTORY BRANCH, which this function documents
744
+ // twice. The code this replaced wrapped exactly this call, so a corrupt
745
+ // `installed-tools.lock` used to read as "no manifest evidence" and the
746
+ // budget block still printed. Unguarded it threw into the receipt's
747
+ // outer catch and suppressed the WHOLE block — ownership line, per-host
748
+ // rows and the 80 % warning — and out of `buildInstalledLayerReport`,
749
+ // which has no outer catch at all. An unreadable manifest is no
750
+ // evidence, never a failure; the asymmetry was undeclared.
751
+ let recordedHashes: ReadonlyMap<string, string | null>;
752
+ try {
753
+ recordedHashes = readRecordedHashes(manifestPath, opts.projectRoot);
754
+ } catch {
755
+ recordedHashes = NO_RECORDED_HASHES;
756
+ }
757
+ for (const [abs, hash] of recordedHashes) {
758
+ // The manifest contributes whenever it names a path at all: it
759
+ // supplies the HASH even where another source already had the path,
760
+ // which is evidence the others cannot give.
761
+ n += 1;
762
+ recorded.set(abs, hash);
763
+ }
764
+ if (n > 0) sources.push('manifest');
765
+ }
766
+
767
+ if (recorded.size === 0) {
768
+ return { recorded: NO_RECORDED_HASHES, sources: [], source: 'none' };
769
+ }
770
+ // `source` keeps a single word for readers that want one. The manifest is
771
+ // the most specific evidence, so it leads when it contributed; otherwise
772
+ // the inventory, otherwise this deploy's own set.
773
+ const primary =
774
+ sources.includes('manifest')
775
+ ? 'manifest'
776
+ : sources.includes('global-inventory')
777
+ ? 'global-inventory'
778
+ : 'this-deploy';
779
+ return { recorded, sources, source: primary };
780
+ }
781
+
782
+ export interface ReportOptions {
783
+ readonly home: string;
784
+ readonly projectRoot: string;
785
+ /** The installed-tools manifest. Absent or unreadable falls through to the global inventory. */
786
+ readonly manifestPath?: string | null;
787
+ /** `deployed-files.json`. `undefined` reads the user-global default; `null` reads none. */
788
+ readonly inventoryPath?: string | null | undefined;
789
+ /** Supplied rather than shelled, so a fixture is not at the mercy of a CLI being on PATH. */
790
+ readonly hostVersion?: string;
791
+ /**
792
+ * The published-limit table. A parameter for the same reason `home` is: a
793
+ * fixture must be able to state the limit it is measuring against instead
794
+ * of inheriting whatever the shipped config happens to say today.
795
+ */
796
+ readonly hostLimitsPath?: string | null;
797
+ }
798
+
799
+ /**
800
+ * Read every host rule directory in both scopes.
801
+ *
802
+ * BOTH SCOPES, BECAUSE THE HOST UNIONS THEM. A reading of the global layer
803
+ * alone would understate a consumer whose project carries its own rules, and
804
+ * the carrier's own scope filter reads the union for the same reason
805
+ * (`rule_layer_overlap.hostRuleLayerIds`).
806
+ */
807
+ export function buildInstalledLayerReport(opts: ReportOptions): InstalledLayerReport {
808
+ const manifestPath = opts.manifestPath ?? null;
809
+ const { recorded, source: ownership_source, sources: ownership_sources } = resolveLayerOwnership({
810
+ manifestPath,
811
+ projectRoot: opts.projectRoot,
812
+ home: opts.home,
813
+ inventoryPath: opts.inventoryPath,
814
+ });
815
+ const layers: LayerReading[] = [];
816
+ for (const host of Object.keys(GLOBAL_RULE_DIRS).sort()) {
817
+ const dir = globalRuleLayerPath(host, opts.home);
818
+ if (dir === null) continue;
819
+ layers.push(readLayer(host, 'global', dir, recorded));
820
+ }
821
+ for (const rel of Object.keys(PROJECT_RULE_DIRS).sort()) {
822
+ const host = PROJECT_RULE_DIRS[rel] as string;
823
+ layers.push(readLayer(host, 'project', path.join(opts.projectRoot, rel), recorded));
824
+ }
825
+ const totals = layers.reduce(
826
+ (acc, l) => ({
827
+ files: acc.files + l.files,
828
+ unconditional: acc.unconditional + l.unconditional,
829
+ chars: acc.chars + l.chars,
830
+ package_owned: acc.package_owned + l.package_owned,
831
+ foreign: acc.foreign + l.foreign,
832
+ package_owned_chars: acc.package_owned_chars + l.package_owned_chars,
833
+ foreign_chars: acc.foreign_chars + l.foreign_chars,
834
+ unconditional_chars: acc.unconditional_chars + l.unconditional_chars,
835
+ scoped_chars: acc.scoped_chars + l.scoped_chars,
836
+ }),
837
+ {
838
+ files: 0,
839
+ unconditional: 0,
840
+ chars: 0,
841
+ package_owned: 0,
842
+ foreign: 0,
843
+ package_owned_chars: 0,
844
+ foreign_chars: 0,
845
+ unconditional_chars: 0,
846
+ scoped_chars: 0,
847
+ },
848
+ );
849
+ const limits = buildHostLimitRows(
850
+ layers,
851
+ loadHostInstructionLimits(opts.hostLimitsPath ?? defaultHostLimitsPath()),
852
+ );
853
+ return {
854
+ home: opts.home,
855
+ project_root: opts.projectRoot,
856
+ host_version: opts.hostVersion ?? 'unknown',
857
+ manifest_present: manifestPath !== null && fs.existsSync(manifestPath),
858
+ ownership_source,
859
+ ownership_sources,
860
+ layers,
861
+ limits,
862
+ totals,
863
+ };
864
+ }
865
+
866
+ /**
867
+ * One line naming WHICH evidence answered ownership.
868
+ *
869
+ * Exported so the report and the install receipt say the same sentence about
870
+ * the same source — a consumer comparing a receipt against a later report must
871
+ * not have to decide whether a wording difference means a different reading.
872
+ */
873
+ export function ownershipLine(sources: OwnershipSource | readonly OwnershipSource[]): string {
874
+ const list = (Array.isArray(sources) ? sources : [sources as OwnershipSource]).filter(
875
+ (s) => s !== 'none',
876
+ );
877
+ if (list.length === 0) {
878
+ return 'ownership: NO manifest and NO deploy inventory resolved — every file reads foreign, which is no evidence rather than a finding';
879
+ }
880
+ // EVERY contributor is named, never just one. The sources cover disjoint
881
+ // path spaces, so a line naming one of two is a true sentence about half
882
+ // the files and a false one about the rest.
883
+ const phrases: Record<Exclude<OwnershipSource, 'none'>, string> = {
884
+ manifest: 'the installed-tools manifest',
885
+ 'global-inventory': 'the global deploy inventory (deployed-files.json)',
886
+ 'this-deploy': 'the file set this install just wrote',
887
+ };
888
+ const named = list.map((s) => phrases[s as Exclude<OwnershipSource, 'none'>]);
889
+ const joined =
890
+ named.length === 1
891
+ ? (named[0] as string)
892
+ : `${named.slice(0, -1).join(', ')} and ${named[named.length - 1] as string}`;
893
+ return `ownership: read from ${joined}`;
894
+ }
895
+
896
+ /** The report as lines, for a terminal. One directory per block. */
897
+ export function renderInstalledLayerReport(report: InstalledLayerReport): string[] {
898
+ const out: string[] = [];
899
+ out.push(`installed layer — host ${report.host_version}, HOME ${report.home}`);
900
+ out.push(`project ${report.project_root}`);
901
+ out.push(ownershipLine(report.ownership_sources));
902
+ for (const l of report.layers) {
903
+ if (!l.present) {
904
+ out.push(` ${l.host} (${l.scope}) — absent: ${l.dir}`);
905
+ continue;
906
+ }
907
+ out.push(
908
+ ` ${l.host} (${l.scope}) — ${String(l.files)} files ` +
909
+ `(${String(l.unconditional)} unconditional), ${String(l.chars)} chars ` +
910
+ `(${String(l.unconditional_chars)} standing + ${String(l.scoped_chars)} path-scoped), ` +
911
+ `${String(l.package_owned)} package-owned / ${String(l.foreign)} foreign`,
912
+ );
913
+ for (const f of l.top) {
914
+ out.push(
915
+ ` ${String(f.chars).padStart(7)} ${f.unconditional ? 'uncond' : 'scoped'} ${f.file}`,
916
+ );
917
+ }
918
+ }
919
+ const t = report.totals;
920
+ out.push(
921
+ ` TOTAL — ${String(t.files)} files (${String(t.unconditional)} unconditional), ` +
922
+ `${String(t.chars)} chars ` +
923
+ `(${String(t.unconditional_chars)} standing + ${String(t.scoped_chars)} path-scoped), ` +
924
+ `${String(t.package_owned)} package-owned / ${String(t.foreign)} foreign`,
925
+ );
926
+ out.push(...renderHostLimitRows(report.limits));
927
+ return out;
928
+ }
929
+
930
+ /**
931
+ * The budget block — one row per host, against its published limit.
932
+ *
933
+ * Extracted so the REPORT and the INSTALL RECEIPT print the same bytes. They
934
+ * answer the same question at two moments, and a consumer comparing a receipt
935
+ * against a later report must not have to decide whether a wording difference
936
+ * means a measurement difference.
937
+ */
938
+ export function renderHostLimitRows(rows: readonly HostLimitReading[]): string[] {
939
+ // ONE ROW PER HOST, against its published limit — steps 1.4 and 3.5. The
940
+ // combined figure is what the row is about: a consumer's own instruction
941
+ // files sit in the same budget as this package's, so a package-owned number
942
+ // alone would understate what the host is actually holding.
943
+ const out: string[] = [
944
+ 'host instruction budgets — package-owned + foreign against the published limit',
945
+ ];
946
+ for (const r of rows) {
947
+ const split =
948
+ `${String(r.package_owned_chars)} package-owned + ` +
949
+ `${String(r.foreign_chars)} foreign = ${String(r.chars)} chars`;
950
+ if (r.limit === null) {
951
+ out.push(` ${r.host} — ${split}; no published limit recorded — not measured`);
952
+ continue;
953
+ }
954
+ const pct = ((r.fraction as number) * 100).toFixed(1);
955
+ out.push(
956
+ ` ${r.host} — ${split}; ${pct}% of ${String(r.limit)} (${r.kind})` +
957
+ (r.warn
958
+ ? ` — WARNING: at or past ${String(Math.round(LIMIT_WARN_FRACTION * 100))}% of the published limit`
959
+ : ''),
960
+ );
961
+ }
962
+ return out;
963
+ }