@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,122 @@
1
+ /**
2
+ * The shape scan a neighbour skill body passes before any of it may be indexed.
3
+ *
4
+ * SCAN BEFORE INJECT. A foreign `SKILL.md` is a file another package put on
5
+ * disk, and the ranker reaches it through the catalogue union. Nothing about
6
+ * being in `~/.claude/skills` makes its prose safe to fold into a term index
7
+ * whose output names skills to a model — the body is untrusted content in the
8
+ * sense `untrusted-input-defense` means it, and the floor here is the same one
9
+ * that rule states: it is data until something has looked at it.
10
+ *
11
+ * THE SHAPES ARE THE ONES THIS SUITE ALREADY SCANS ITS OWN CORPUS WITH. Four
12
+ * `security_lint` checks, reused as functions rather than re-implemented
13
+ * (decision D3 of the lane): instruction smuggling, hidden Unicode, mixed-script
14
+ * confusables and dangerous frontmatter. Re-declaring their patterns here would
15
+ * create a second copy to drift against the first, and the first is the one CI
16
+ * keeps honest.
17
+ *
18
+ * ANY FINDING REFUSES THE BODY — severity is NOT the discriminator. In the
19
+ * corpus linters, `HIGH` fails a build and `MED` warns, because the corpus is
20
+ * this package's own prose and a warning there is a maintainer's to weigh. A
21
+ * neighbour body is nobody's to weigh at rank time, and the cost of refusing is
22
+ * known and small: the skill still RANKS, by its name, with the finding kind
23
+ * printed. Risk-register row 4 is exactly this trade and resolves the same way —
24
+ * a shell snippet can be an installer skill's whole point, so the skill is never
25
+ * hidden, only un-indexed.
26
+ *
27
+ * WHY THIS IS NOT IMPORTED BY THE RANKER. It pulls four linter modules and,
28
+ * through one of them, `node:child_process`. The ranker runs inside the shared
29
+ * hook bundle on `user_prompt_submit`, where every byte is paid by every concern
30
+ * on every dispatch and `check_hook_bundle_composition` holds the ceiling. So
31
+ * the scan runs in the census CLI and its verdict reaches the ranker as a cached
32
+ * record — see `neighbour_census.scanVerdict`, where a missing record is a
33
+ * refusal rather than a pass.
34
+ */
35
+ import * as fs from 'node:fs';
36
+ import * as path from 'node:path';
37
+
38
+ import * as sl from './security_lint.js';
39
+ import { fileDigest, type ScanCache, type ScanRecord } from './skill_origin.js';
40
+ import { _scan as scanSmuggling } from '../lint_instruction_smuggling.js';
41
+ import { _scan as scanHiddenUnicode } from '../lint_hidden_unicode.js';
42
+ import { _scan as scanConfusables } from '../lint_confusables.js';
43
+ import { _scan as scanFrontmatter } from '../lint_skill_frontmatter_safety.js';
44
+
45
+ /** The shape checks, in the order their findings are reported. */
46
+ const SHAPES: ReadonlyArray<readonly [string, (sf: sl.ScannedFile) => sl.Finding[]]> = [
47
+ ['instruction-smuggling', scanSmuggling],
48
+ ['dangerous-frontmatter', scanFrontmatter],
49
+ ['hidden-unicode', scanHiddenUnicode],
50
+ ['mixed-script-confusable', scanConfusables],
51
+ ];
52
+
53
+ export interface ScanResult {
54
+ /** `null` when every shape passed; otherwise the first finding's check id. */
55
+ kind: string | null;
56
+ /** Every finding, for a report that wants more than the first kind. */
57
+ findings: sl.Finding[];
58
+ }
59
+
60
+ /**
61
+ * Scan one `SKILL.md`.
62
+ *
63
+ * An unreadable file is a refusal, not a pass (`unreadable`): a body nobody
64
+ * could open is in exactly the state this gate exists to keep out of the index,
65
+ * and returning `kind: null` for it would make an IO error look like a clean
66
+ * scan. A THROWING check is reported under its own id rather than swallowed, for
67
+ * the same reason.
68
+ */
69
+ export function scanNeighbourBody(skillMdPath: string): ScanResult {
70
+ let sf: sl.ScannedFile;
71
+ try {
72
+ sf = sl.scan_file(skillMdPath);
73
+ } catch {
74
+ return { kind: 'unreadable', findings: [] };
75
+ }
76
+ const findings: sl.Finding[] = [];
77
+ for (const [id, scan] of SHAPES) {
78
+ try {
79
+ findings.push(...scan(sf));
80
+ } catch {
81
+ findings.push(
82
+ new sl.Finding(sf.rel, 0, id, 'HIGH', 'shape check threw on this body', 1.0),
83
+ );
84
+ }
85
+ }
86
+ return { kind: findings.length === 0 ? null : (findings[0] as sl.Finding).check, findings };
87
+ }
88
+
89
+ /**
90
+ * Record what the scan found, for the ranker to read on the hook path.
91
+ *
92
+ * The digest is the FILE's, not the finding's: the ranker re-digests the body it
93
+ * is about to index and compares, so a body edited after its scan reads as
94
+ * `digest-changed` and goes back to name-only ranking until the census runs
95
+ * again. That compare is the whole mechanism behind "a changed digest rescans
96
+ * first" — without it a one-time pass would keep clearing a file forever.
97
+ *
98
+ * A write failure is reported to the caller rather than thrown: the scan is
99
+ * correct either way, and a read-only report directory must not fail a census.
100
+ */
101
+ export function writeScanCache(file: string, entries: readonly ScanRecord[]): boolean {
102
+ const payload: ScanCache = {
103
+ schema_version: 1,
104
+ entries: [...entries].sort((a, b) => (a.qualified < b.qualified ? -1 : 1)),
105
+ };
106
+ try {
107
+ fs.mkdirSync(path.dirname(file), { recursive: true });
108
+ fs.writeFileSync(file, `${JSON.stringify(payload, null, 2)}\n`, 'utf-8');
109
+ return true;
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ /** A `ScanRecord` for one file, digest included. */
116
+ export function scanRecordFor(qualified: string, skillMdPath: string): ScanRecord {
117
+ return {
118
+ qualified,
119
+ digest: fileDigest(skillMdPath) ?? '',
120
+ unscanned: scanNeighbourBody(skillMdPath).kind,
121
+ };
122
+ }
@@ -0,0 +1,189 @@
1
+ /**
2
+ * The foreign-MCP-tool-name store: one writer on the post-tool hook path, one
3
+ * reader in the neighbour census.
4
+ *
5
+ * WHY A MODULE OF ITS OWN, AND NOT A CONSTANT IN EITHER SIDE. The writer lives
6
+ * in `hooks/telemetry_usage_hook.ts`, which is inlined into the shared hook
7
+ * bundle `check_hook_bundle_composition` caps; the reader lives in
8
+ * `_lib/neighbour_census.ts`, which is not. Putting the pair here lets esbuild
9
+ * tree-shake {@link toolsUsedByServer} out of the hook bundle while both sides
10
+ * still name one path — a constant copied into two files is a store that
11
+ * silently splits in two the first time one copy is edited.
12
+ *
13
+ * WHAT IS RECORDED, AND WHAT IS NOT. One map of `mcp__<server>__<tool>` to the
14
+ * ISO date it was last seen. No arguments, no responses, no session id, no
15
+ * project path beyond the file's own location. The names recorded are names the
16
+ * consumer's own `.mcp.json` already lists, and the file is written under
17
+ * `agents/runtime/`, which is gitignored and local-only — nothing here is sent
18
+ * anywhere, and no transport reads it.
19
+ *
20
+ * NOT GATED ON THE TELEMETRY OPT-IN, DELIBERATELY. `telemetry_usage_hook`'s
21
+ * Class-A capture is default-off because it feeds an outbound spool. This store
22
+ * feeds `doctor neighbours`, a local report the consumer runs on their own tree.
23
+ * Gating it on an org telemetry switch would make the census print `0` on every
24
+ * install that never enabled one — a number that looks like a measurement and is
25
+ * an instrumentation artifact, which is the exact failure the usage hook's own
26
+ * header records from the collector it replaced.
27
+ *
28
+ * GROWTH, DECLARED (scale-discipline R-A7). The store is a map keyed by tool
29
+ * NAME, not an append log: one entry per distinct `mcp__…` name the consumer
30
+ * has ever called, each rewritten in place at most once per day. The bound is
31
+ * therefore the size of the neighbour's tool surface — tens of entries, not a
32
+ * series — so it needs no TTL job. The reader applies the window; entries older
33
+ * than it stay on disk and stop counting, which is why a stale entry is inert
34
+ * rather than wrong.
35
+ */
36
+ import * as fs from 'node:fs';
37
+ import * as path from 'node:path';
38
+
39
+ /** Host-side prefix of every MCP tool name: `mcp__<server>__<tool>`. */
40
+ export const MCP_TOOL_PREFIX = 'mcp__';
41
+
42
+ /**
43
+ * The store, relative to the project root. Gitignored; see the header.
44
+ *
45
+ * A literal with forward slashes rather than `path.join`, and the reason is
46
+ * measured rather than stylistic: `path.join` here is a top-level call, so the
47
+ * `node:path` namespace import survives into the hook bundle even though every
48
+ * function in this module that needs it is tree-shaken out of that bundle.
49
+ * `path.join(root, REL)` at each call site normalises the separator on Windows,
50
+ * so the only thing lost is the authoring convention.
51
+ */
52
+ export const FOREIGN_TOOL_USE_REL = 'agents/runtime/neighbour-tool-use.json';
53
+
54
+ /** The census window. 30 days, matching the step that asked for the number. */
55
+ export const TOOL_USE_WINDOW_DAYS = 30;
56
+
57
+ /** The filename the writer's root is the directory of. */
58
+ const SETTINGS_FILENAME = '.agent-settings.yml';
59
+
60
+ /** `YYYY-MM-DD` in UTC — the granularity the store keys on. */
61
+ export function usageDay(now: Date): string {
62
+ return now.toISOString().slice(0, 10);
63
+ }
64
+
65
+ const DAY_RE = /^\d{4}-\d{2}-\d{2}$/u;
66
+
67
+ /**
68
+ * Is this store value a day the window comparison can be trusted on?
69
+ *
70
+ * The comparison is lexicographic, which is exactly right for `YYYY-MM-DD` and
71
+ * quietly wrong for anything else: `'TODO'`, `'unknown'` and `'hand edited'`
72
+ * all sort ABOVE a real cutoff, so without this check a hand-edited or
73
+ * half-written store inflates the count instead of being ignored. The reader's
74
+ * promise is that a malformed store yields nothing, and a malformed VALUE is
75
+ * the likelier half of that.
76
+ */
77
+ export function isUsageDay(value: unknown): value is string {
78
+ return typeof value === 'string' && DAY_RE.test(value);
79
+ }
80
+
81
+ /**
82
+ * The `.mcp.json` key as it appears inside a tool name.
83
+ *
84
+ * Hosts sanitise the key before embedding it — a server registered as
85
+ * `claude.ai Claude Docs` arrives as `mcp__claude_ai_Claude_Docs__…`. Normalising
86
+ * BOTH sides the same way is what keeps a matched count from quietly being a
87
+ * zero: an exact-key match would report `0` for every server whose name carries
88
+ * a dot or a space, which reads as "never used" rather than as "never matched".
89
+ */
90
+ export function serverSegment(key: string): string {
91
+ return key.replace(/[^A-Za-z0-9_-]/gu, '_');
92
+ }
93
+
94
+ /**
95
+ * Does `tool` belong to the server whose sanitised key is `segment`?
96
+ *
97
+ * Driven by the KNOWN segment rather than by parsing the tool name, and that
98
+ * direction is the whole correctness argument. Parsing — split at the first
99
+ * `__` after the prefix — is ambiguous exactly where {@link serverSegment} is
100
+ * lossy: `Acme Inc. Tools` sanitises to `Acme_Inc__Tools`, whose own name
101
+ * contains the separator, so a parser reads the server as `Acme_Inc` and the
102
+ * census reports `0` for a server in daily use. That is the same never-matched
103
+ * zero `serverSegment` exists to prevent, reintroduced one layer down. Matching
104
+ * against the keys `.mcp.json` actually lists cannot be ambiguous, because the
105
+ * candidate set is finite and known.
106
+ */
107
+ export function toolBelongsTo(tool: string, segment: string): boolean {
108
+ if (!tool.startsWith(MCP_TOOL_PREFIX)) return false;
109
+ const rest = tool.slice(MCP_TOOL_PREFIX.length);
110
+ return rest === segment || rest.startsWith(`${segment}__`);
111
+ }
112
+
113
+ /**
114
+ * The directory the store lives under, from any directory inside the project.
115
+ *
116
+ * The WRITER roots the store at the directory holding `.agent-settings.yml`
117
+ * (`telemetry_usage_hook.readSettingsFor`), because a session started in a
118
+ * subdirectory would otherwise scatter one store per directory it happened to
119
+ * start in. The reader has to resolve the same way or it looks in the wrong
120
+ * place: a monorepo with settings at the repo root and `.mcp.json` under
121
+ * `packages/web` would report `0` for a server in daily use. The two walks are
122
+ * pinned equal by a fixture rather than by assertion.
123
+ *
124
+ * No settings file on any ancestor → `start`, matching the writer's own
125
+ * fallback.
126
+ */
127
+ export function resolveStoreRoot(start: string): string {
128
+ let dir = path.resolve(start);
129
+ for (;;) {
130
+ try {
131
+ if (fs.statSync(path.join(dir, SETTINGS_FILENAME)).isFile()) return dir;
132
+ } catch {
133
+ // Not here — keep walking.
134
+ }
135
+ const parent = path.dirname(dir);
136
+ if (parent === dir) return path.resolve(start);
137
+ dir = parent;
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Distinct tools seen per server inside the window, keyed by server segment.
143
+ *
144
+ * `segments` is the closed candidate set — the sanitised `.mcp.json` keys. Every
145
+ * one of them appears in the result, so a server with no sightings reads `0`
146
+ * rather than being absent. Longest segment wins where one is a prefix of
147
+ * another, so a tool is counted once.
148
+ *
149
+ * A missing, unreadable or malformed store yields all-zero — true of what was
150
+ * observed. It does not mean the server was unused, and the census says so next
151
+ * to the number.
152
+ */
153
+ export function toolsUsedByServer(
154
+ projectRoot: string,
155
+ now: Date,
156
+ segments: Iterable<string>,
157
+ ): Map<string, number> {
158
+ const known = [...new Set(segments)].sort((a, b) => b.length - a.length);
159
+ const counts = new Map<string, number>(known.map((s) => [s, 0]));
160
+ if (known.length === 0) return counts;
161
+
162
+ let decoded: unknown;
163
+ try {
164
+ const root = resolveStoreRoot(projectRoot);
165
+ decoded = JSON.parse(fs.readFileSync(path.join(root, FOREIGN_TOOL_USE_REL), 'utf-8'));
166
+ } catch {
167
+ return counts;
168
+ }
169
+ if (typeof decoded !== 'object' || decoded === null || Array.isArray(decoded)) return counts;
170
+
171
+ const today = usageDay(now);
172
+ // Inclusive of both ends, so the span is WINDOW days and not WINDOW+1: a
173
+ // floor computed at `now - 30d` would count 31 distinct days under a field
174
+ // named `tools_used_30d`.
175
+ const floor = usageDay(new Date(now.getTime() - (TOOL_USE_WINDOW_DAYS - 1) * 86_400_000));
176
+
177
+ for (const [tool, seen] of Object.entries(decoded as Record<string, unknown>)) {
178
+ // A future date is as much a sign of a bad write as a malformed one,
179
+ // and without the upper bound a single `9999-01-01` counts forever.
180
+ if (!isUsageDay(seen) || seen < floor || seen > today) continue;
181
+ for (const segment of known) {
182
+ if (toolBelongsTo(tool, segment)) {
183
+ counts.set(segment, (counts.get(segment) ?? 0) + 1);
184
+ break;
185
+ }
186
+ }
187
+ }
188
+ return counts;
189
+ }
@@ -44,7 +44,9 @@ import {
44
44
  session_state_file,
45
45
  update_json_under_lock,
46
46
  } from '../hooks/state_io.js';
47
+ import { manifest_path } from './installed_tools.js';
47
48
  import { type EnforcementClass, is_enforcement_class } from './obligation_frequency.js';
49
+ import { hasSentinel } from './repo_root.js';
48
50
 
49
51
  /**
50
52
  * Directory holding one ledger file per session.
@@ -61,6 +63,77 @@ export function statePathFor(session_id: string): string {
61
63
  return session_state_file(OBLIGATION_STATE_DIR, session_id);
62
64
  }
63
65
 
66
+ /**
67
+ * WHICH KIND OF TREE WROTE A ROW — the whole vocabulary, and nothing else.
68
+ *
69
+ * `obligation-settle-shadow-bar` is read off `agents/runtime/state/obligations/`,
70
+ * which is one machine's gitignored runtime state. That scope bound lives in the
71
+ * claim's prose and in a roadmap paragraph, and in nothing a reader of the rows
72
+ * can compute. A reading that clears the pre-registered floor would therefore be
73
+ * quotable as evidence about a population nobody separated. This field makes the
74
+ * separation a property of the DATA, so the split is computable from the ledger
75
+ * alone rather than asserted beside it.
76
+ *
77
+ * THREE VALUES, CLOSED, AND A UNION OF LITERALS ON PURPOSE. The record gains one
78
+ * enumerated field and no `payload`, `notes` or `extra` slot, so the privacy
79
+ * property is the SHAPE and not a scrubber somebody has to keep correct — the
80
+ * same by-construction posture `artifact-engagement-recording` states for the
81
+ * telemetry event. A type that cannot hold a path cannot leak one.
82
+ *
83
+ * WHAT IT DELIBERATELY IS NOT. Not a path, not a username, not a hostname, not a
84
+ * machine id, not a git remote. The obvious implementation of "which tree wrote
85
+ * this" is the resolved root's filesystem path, which on a developer machine
86
+ * carries an account name — and because the bar is read by QUOTING rows, one such
87
+ * field would make every row already written unquotable rather than scrubbable.
88
+ * So the field carries a ROLE: what kind of tree this is, never which one.
89
+ */
90
+ export type WriterRole =
91
+ /** The tree carries this package's own `package.json` sentinel — a maintainer checkout. */
92
+ | 'package'
93
+ /** Not the package, but an install manifest is present — a consumer project. */
94
+ | 'consumer'
95
+ /** Neither marker resolved. An honest absence, never a default. */
96
+ | 'unknown';
97
+
98
+ /** The closed set, in one place, so a reader and a writer cannot disagree. */
99
+ export const WRITER_ROLES: readonly WriterRole[] = ['package', 'consumer', 'unknown'];
100
+
101
+ /** Narrowing guard for a value read back off disk. */
102
+ export function isWriterRole(value: unknown): value is WriterRole {
103
+ return typeof value === 'string' && (WRITER_ROLES as readonly string[]).includes(value);
104
+ }
105
+
106
+ /**
107
+ * Classify the root this ledger lives under.
108
+ *
109
+ * ORDER IS THE CONTRACT. `package` is tested FIRST, because a maintainer checkout
110
+ * routinely also carries the install artifacts a consumer has — this repository
111
+ * installs itself — and a consumer-first test would label the maintainer tree
112
+ * `consumer` on exactly the machine the bar is being read from. The sentinel is
113
+ * `hasSentinel` from `repo_root.ts` rather than a second `package.json` reader:
114
+ * the sentinel name lives in one place or it drifts.
115
+ *
116
+ * `consumer` keys on the INSTALL MANIFEST (`agents/installed-tools.lock`, via the
117
+ * canonical `manifest_path` so the `AGENT_CONFIG_INSTALLED_TOOLS` override is
118
+ * honoured) and not on `.agent-settings.yml`. The settings file is something a
119
+ * human writes by hand; the lock is written by the installer and by nothing else.
120
+ * A weaker marker would buy coverage by guessing, which is the one thing this
121
+ * field may not do.
122
+ *
123
+ * NEVER THROWS. A probe that cannot stat returns `unknown`, which is a real
124
+ * answer — the ledger's contract is that an instrument missing a reading is
125
+ * cheaper than a refused turn, and a classifier is no exception.
126
+ */
127
+ export function resolveWriterRole(root: string): WriterRole {
128
+ try {
129
+ if (hasSentinel(root)) return 'package';
130
+ if (fs.existsSync(manifest_path(root))) return 'consumer';
131
+ } catch {
132
+ return 'unknown';
133
+ }
134
+ return 'unknown';
135
+ }
136
+
64
137
  /**
65
138
  * One rule delivered into this session, with the class its frontmatter declares.
66
139
  *
@@ -75,8 +148,21 @@ export interface DeliveredRow {
75
148
  readonly cls: EnforcementClass;
76
149
  /** ISO-8601, second precision. */
77
150
  readonly at: string;
151
+ /** Which kind of tree wrote this. Stamped here, never supplied — see {@link WriterInput}. */
152
+ readonly writer: WriterRole;
78
153
  }
79
154
 
155
+ /**
156
+ * What a CALLER offers, for every row type: the stored shape minus its writer.
157
+ *
158
+ * The writer is stamped by the append functions from the `root` they were
159
+ * already given, and is deliberately not part of any caller's input. A caller
160
+ * that could supply it could supply the wrong one — and a provenance field a
161
+ * caller can set is a provenance field, not provenance. Making it unsettable is
162
+ * cheaper than any check that it was set correctly.
163
+ */
164
+ export type WriterInput<T> = Omit<T, 'writer'>;
165
+
80
166
  /**
81
167
  * One obligation discharged this session.
82
168
  *
@@ -92,6 +178,8 @@ export interface DischargeRow {
92
178
  /** What observed it — a concern name, so a reader can go and check. */
93
179
  readonly by: string;
94
180
  readonly at: string;
181
+ /** Which kind of tree wrote this. Stamped, never supplied. */
182
+ readonly writer: WriterRole;
95
183
  }
96
184
 
97
185
  /**
@@ -109,6 +197,14 @@ export interface ShadowRow {
109
197
  readonly attempt: number;
110
198
  /** Always true today: a row is only written when the detector would refuse. */
111
199
  readonly would_refuse: boolean;
200
+ /**
201
+ * Which kind of tree wrote this. Stamped, never supplied.
202
+ *
203
+ * The shadow array is the one the pre-registered bar actually counts, so
204
+ * this is the field that turns "the corpus is one machine" from a sentence
205
+ * in a claim into something a reader can compute off the rows.
206
+ */
207
+ readonly writer: WriterRole;
112
208
  }
113
209
 
114
210
  /** The on-disk shape of one session's ledger. */
@@ -125,10 +221,11 @@ interface SerialisedRow {
125
221
  rule: string;
126
222
  class: string;
127
223
  at: string;
224
+ writer: WriterRole;
128
225
  }
129
226
 
130
227
  function serialise(row: DeliveredRow): SerialisedRow {
131
- return { rule: row.rule, class: row.cls, at: row.at };
228
+ return { rule: row.rule, class: row.cls, at: row.at, writer: row.writer };
132
229
  }
133
230
 
134
231
  /**
@@ -139,6 +236,16 @@ function serialise(row: DeliveredRow): SerialisedRow {
139
236
  * whose class is outside the closed set must not enter the ledger wearing a
140
237
  * value the vocabulary denies — `is_enforcement_class` is the gate, so a
141
238
  * future widening of the schema cannot leak in through the state file.
239
+ *
240
+ * THE WRITER IS TREATED THE OTHER WAY ROUND, and the asymmetry is deliberate. A
241
+ * row whose CLASS is outside the vocabulary is dropped, because the class is the
242
+ * row's subject matter and a row wearing a denied class is a false statement
243
+ * about what was delivered. A row whose WRITER is absent or unrecognised is KEPT
244
+ * and reads `unknown`, because the writer is provenance ABOUT a delivery that
245
+ * genuinely happened — dropping the row would destroy a real record to punish a
246
+ * missing label, and every row written before this field existed is exactly that
247
+ * case. `unknown` is the honest reading of both, which is why one value covers
248
+ * absent and unresolvable alike.
142
249
  */
143
250
  export function parseRows(loaded: unknown): DeliveredRow[] {
144
251
  if (typeof loaded !== 'object' || loaded === null) return [];
@@ -154,7 +261,8 @@ export function parseRows(loaded: unknown): DeliveredRow[] {
154
261
  if (typeof rule !== 'string' || rule === '') continue;
155
262
  if (typeof at !== 'string' || at === '') continue;
156
263
  if (!is_enforcement_class(cls)) continue;
157
- out.push({ rule, cls, at });
264
+ const writer = e['writer'];
265
+ out.push({ rule, cls, at, writer: isWriterRole(writer) ? writer : 'unknown' });
158
266
  }
159
267
  return out;
160
268
  }
@@ -185,13 +293,14 @@ export function stamp(now: Date = new Date()): string {
185
293
  export function appendDelivered(
186
294
  root: string,
187
295
  session_id: string,
188
- rows: readonly DeliveredRow[],
296
+ rows: readonly WriterInput<DeliveredRow>[],
189
297
  ): number {
190
298
  if (rows.length === 0) return 0;
191
299
  if (is_replay_mode()) return 0;
192
300
  if (session_id.trim() === '') return 0;
193
301
 
194
302
  const target = path.join(root, statePathFor(session_id));
303
+ const writer = resolveWriterRole(root);
195
304
  let added = 0;
196
305
  try {
197
306
  update_json_under_lock<LedgerFile>(target, (loaded) => {
@@ -201,7 +310,7 @@ export function appendDelivered(
201
310
  for (const row of rows) {
202
311
  if (seen.has(row.rule)) continue;
203
312
  seen.add(row.rule);
204
- merged.push(serialise(row));
313
+ merged.push(serialise({ ...row, writer }));
205
314
  added += 1;
206
315
  }
207
316
  if (added === 0) return null; // deliberate no-write
@@ -245,24 +354,25 @@ function carryOver(loaded: Partial<LedgerFile>): Pick<LedgerFile, 'discharged' |
245
354
  export function appendDischarge(
246
355
  root: string,
247
356
  session_id: string,
248
- rows: readonly DischargeRow[],
357
+ rows: readonly WriterInput<DischargeRow>[],
249
358
  ): number {
250
359
  if (rows.length === 0) return 0;
251
360
  if (is_replay_mode()) return 0;
252
361
  if (session_id.trim() === '') return 0;
253
362
 
254
363
  const target = path.join(root, statePathFor(session_id));
364
+ const writer = resolveWriterRole(root);
255
365
  let added = 0;
256
366
  try {
257
367
  update_json_under_lock<LedgerFile>(target, (loaded) => {
258
368
  const existing = Array.isArray(loaded.discharged) ? loaded.discharged : [];
259
- const key = (r: DischargeRow): string => `${r.rule}::${r.by}`;
369
+ const key = (r: WriterInput<DischargeRow>): string => `${r.rule}::${r.by}`;
260
370
  const seen = new Set(existing.map(key));
261
371
  const merged = [...existing];
262
372
  for (const row of rows) {
263
373
  if (seen.has(key(row))) continue;
264
374
  seen.add(key(row));
265
- merged.push(row);
375
+ merged.push({ ...row, writer });
266
376
  added += 1;
267
377
  }
268
378
  if (added === 0) return null;
@@ -320,6 +430,7 @@ export function appendShadow(
320
430
 
321
431
  const fingerprint = [...missing].sort().join('::');
322
432
  const target = path.join(root, statePathFor(session_id));
433
+ const writer = resolveWriterRole(root);
323
434
  let attempt = 0;
324
435
  try {
325
436
  update_json_under_lock<LedgerFile>(target, (loaded) => {
@@ -334,7 +445,7 @@ export function appendShadow(
334
445
  discharged: Array.isArray(loaded.discharged) ? loaded.discharged : [],
335
446
  shadow: [
336
447
  ...existing,
337
- { at: now, missing: [...missing], attempt, would_refuse: true },
448
+ { at: now, missing: [...missing], attempt, would_refuse: true, writer },
338
449
  ],
339
450
  };
340
451
  });