@event4u/agent-config 14.12.0 → 14.13.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 (466) 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 +234 -0
  5. package/CONTRIBUTING.md +38 -0
  6. package/CREDITS.md +135 -0
  7. package/NOTICE +13 -0
  8. package/README.md +8 -6
  9. package/dist/agent-src/commands/analyze/inbox.md +109 -2
  10. package/dist/agent-src/commands/feature/dev.md +7 -2
  11. package/dist/agent-src/commands/ghostwriter/write.md +5 -1
  12. package/dist/agent-src/commands/implement-ticket.md +1 -1
  13. package/dist/agent-src/commands/mission/upgrade.md +1 -1
  14. package/dist/agent-src/commands/orchestrate.md +2 -2
  15. package/dist/agent-src/commands/post-as/me.md +5 -1
  16. package/dist/agent-src/commands/pr/merge.md +19 -6
  17. package/dist/agent-src/commands/roadmap/process-full.md +19 -5
  18. package/dist/agent-src/commands/tdd/red.md +12 -2
  19. package/dist/agent-src/commands/work.md +1 -1
  20. package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +40 -0
  21. package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -2
  22. package/dist/agent-src/contexts/execution/autonomy-mechanics.md +21 -0
  23. package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -0
  24. package/dist/agent-src/contexts/execution/subagent-response-contract.md +40 -0
  25. package/dist/agent-src/contexts/execution/subagent-steering.md +13 -1
  26. package/dist/agent-src/contexts/execution/terminal-states.md +96 -0
  27. package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
  28. package/dist/agent-src/rules/broken-access-control.md +4 -0
  29. package/dist/agent-src/rules/code-provenance.md +4 -0
  30. package/dist/agent-src/rules/design-fidelity.md +1 -1
  31. package/dist/agent-src/rules/domain-safety-pii.md +4 -0
  32. package/dist/agent-src/rules/domain-safety-retention.md +4 -0
  33. package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
  34. package/dist/agent-src/rules/external-code-graph-interop.md +14 -12
  35. package/dist/agent-src/rules/improve-before-implement.md +6 -8
  36. package/dist/agent-src/rules/laravel-routing.md +1 -1
  37. package/dist/agent-src/rules/lethal-trifecta-guard.md +4 -0
  38. package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
  39. package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
  40. package/dist/agent-src/rules/source-confidentiality.md +12 -34
  41. package/dist/agent-src/rules/symfony-routing.md +1 -1
  42. package/dist/agent-src/rules/token-efficiency.md +14 -0
  43. package/dist/agent-src/rules/tool-safety.md +4 -0
  44. package/dist/agent-src/rules/untrusted-input-defense.md +4 -0
  45. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +247 -3
  46. package/dist/agent-src/scripts/guarded_baseline.ts +328 -0
  47. package/dist/agent-src/scripts/roadmap_set_step.ts +253 -0
  48. package/dist/agent-src/scripts/stub_queue.ts +82 -0
  49. package/dist/agent-src/scripts/stubs_due.ts +400 -0
  50. package/dist/agent-src/scripts/ticket_bundles.ts +84 -0
  51. package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -4
  52. package/dist/agent-src/skills/brand-asset-generation/SKILL.md +17 -4
  53. package/dist/agent-src/skills/brand-audit/SKILL.md +33 -0
  54. package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -0
  55. package/dist/agent-src/skills/database/SKILL.md +8 -0
  56. package/dist/agent-src/skills/dependency-upgrade/SKILL.md +27 -0
  57. package/dist/agent-src/skills/design-review/SKILL.md +1 -1
  58. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
  59. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +17 -1
  60. package/dist/agent-src/skills/feature-planning/SKILL.md +9 -2
  61. package/dist/agent-src/skills/frontend-render-security/SKILL.md +44 -0
  62. package/dist/agent-src/skills/git-workflow/SKILL.md +1 -1
  63. package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +38 -4
  64. package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +2 -2
  65. package/dist/agent-src/skills/laravel/SKILL.md +35 -0
  66. package/dist/agent-src/skills/laravel-migration/SKILL.md +57 -3
  67. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +26 -0
  68. package/dist/agent-src/skills/playbook-authoring/SKILL.md +28 -1
  69. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
  70. package/dist/agent-src/skills/skill-writing/references/procedure.md +5 -5
  71. package/dist/agent-src/skills/sql-writing/SKILL.md +7 -1
  72. package/dist/agent-src/skills/standards-from-config/SKILL.md +27 -4
  73. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +11 -4
  74. package/dist/agent-src/skills/symfony-workflow/SKILL.md +35 -0
  75. package/dist/agent-src/skills/test-case-discovery/SKILL.md +85 -0
  76. package/dist/agent-src/skills/test-driven-development/SKILL.md +33 -9
  77. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
  78. package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +37 -0
  79. package/dist/agent-src/skills/ui-component-architect/SKILL.md +45 -2
  80. package/dist/agent-src/skills/upstream-contribute/SKILL.md +38 -1
  81. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +9 -4
  82. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  83. package/dist/agent-src/templates/roadmaps.md +97 -3
  84. package/dist/agent-src/templates/scripts/check_memory.ts +258 -17
  85. package/dist/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
  86. package/dist/agent-src/templates/scripts/memory_lookup.ts +86 -0
  87. package/dist/agent-src/templates/scripts/memory_signal.ts +44 -0
  88. package/dist/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
  89. package/dist/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
  90. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
  91. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
  92. package/dist/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
  93. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
  94. package/dist/cli/commands/uiAudit.js +124 -5
  95. package/dist/cli/registry.js +2 -0
  96. package/dist/cli-delegate/{chunk-HYWSRNUX.js → chunk-2DK2VJWG.js} +3 -3
  97. package/dist/cli-delegate/{chunk-JCW7ZLHK.js → chunk-4FS4WRXP.js} +5 -0
  98. package/dist/cli-delegate/{chunk-OMIVVCG2.js → chunk-A4QJ6ENT.js} +24 -0
  99. package/dist/cli-delegate/{chunk-JJA2QEWA.js → chunk-AKJGUQNO.js} +466 -285
  100. package/dist/cli-delegate/{chunk-E46KPN6K.js → chunk-BTWUYDGN.js} +424 -363
  101. package/dist/cli-delegate/{chunk-K4FFODTS.js → chunk-ILMUPLGW.js} +1227 -316
  102. package/dist/cli-delegate/{chunk-FF7WAFMR.js → chunk-KPBWFOPZ.js} +5 -1
  103. package/dist/cli-delegate/cmd_adr_effective.js +102 -0
  104. package/dist/cli-delegate/cmd_conformance.js +42 -10
  105. package/dist/cli-delegate/cmd_doctor.js +3 -3
  106. package/dist/cli-delegate/cmd_export.js +3 -2
  107. package/dist/cli-delegate/cmd_fleet.js +2 -2
  108. package/dist/cli-delegate/cmd_handoff.js +1 -1
  109. package/dist/cli-delegate/cmd_preflight.js +2 -2
  110. package/dist/cli-delegate/cmd_prune.js +3 -2
  111. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  112. package/dist/cli-delegate/cmd_settings_migrate.js +3 -2
  113. package/dist/cli-delegate/cmd_settings_set.js +2 -2
  114. package/dist/cli-delegate/cmd_sync.js +3 -2
  115. package/dist/cli-delegate/cmd_uninstall.js +3 -2
  116. package/dist/cli-delegate/cmd_validate.js +3 -2
  117. package/dist/discovery/deprecation-report.md +1 -1
  118. package/dist/discovery/discovery-manifest.json +57 -57
  119. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  120. package/dist/discovery/discovery-manifest.summary.md +1 -1
  121. package/dist/discovery/dormancy-report.md +1 -1
  122. package/dist/discovery/orphan-report.md +1 -1
  123. package/dist/discovery/packs.json +2 -2
  124. package/dist/discovery/trust-report.md +1 -1
  125. package/dist/discovery/workspaces.json +2 -2
  126. package/dist/hooks/dispatch.js +1836 -712
  127. package/dist/install/claudeRuleRewrite.js +254 -0
  128. package/dist/install/detect.js +29 -16
  129. package/dist/install/detect_php_shape.js +159 -0
  130. package/dist/install/install.mjs +634 -428
  131. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  132. package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
  133. package/dist/mcp/registry-manifest.json +3 -3
  134. package/dist/mcp/server.json +3 -3
  135. package/dist/mcp/server.mjs +4818 -2645
  136. package/dist/server/schemas/settings.js +3 -0
  137. package/dist/ui/assets/index-D0jaCRPM.js +29 -0
  138. package/dist/ui/index.html +1 -1
  139. package/docs/MIGRATION.md +1 -1
  140. package/docs/THIRD-PARTY-NOTICES.md +25 -0
  141. package/docs/contracts/provider-lifecycle.md +1 -1
  142. package/docs/contracts/settings-classes.md +10 -4
  143. package/docs/guidelines/agent-infra/5w2h-analysis.md +3 -3
  144. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -0
  145. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +58 -3
  146. package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +2 -2
  147. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +3 -3
  148. package/docs/guidelines/agent-infra/break-glass-usage.md +7 -7
  149. package/docs/guidelines/agent-infra/carve-out-predicates.md +3 -3
  150. package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
  151. package/docs/guidelines/agent-infra/critical-thinking.md +4 -4
  152. package/docs/guidelines/agent-infra/direct-answers-demos.md +2 -2
  153. package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +1 -1
  154. package/docs/guidelines/agent-infra/engineering-memory-data-format.md +3 -3
  155. package/docs/guidelines/agent-infra/existence-question-verdicts.md +80 -0
  156. package/docs/guidelines/agent-infra/first-principles.md +2 -2
  157. package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +2 -2
  158. package/docs/guidelines/agent-infra/guarded-baseline.md +39 -0
  159. package/docs/guidelines/agent-infra/inversion-thinking.md +5 -5
  160. package/docs/guidelines/agent-infra/language-and-tone-examples.md +1 -1
  161. package/docs/guidelines/agent-infra/layered-settings.md +10 -10
  162. package/docs/guidelines/agent-infra/memory-access.md +1 -1
  163. package/docs/guidelines/agent-infra/mental-models.md +3 -3
  164. package/docs/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
  165. package/docs/guidelines/agent-infra/review-routing-data-format.md +7 -7
  166. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +13 -7
  167. package/docs/guidelines/agent-infra/role-contracts.md +2 -2
  168. package/docs/guidelines/agent-infra/role-mode-router.md +3 -3
  169. package/docs/guidelines/agent-infra/rule-type-governance.md +2 -2
  170. package/docs/guidelines/agent-infra/scqa-framework.md +5 -5
  171. package/docs/guidelines/agent-infra/self-improvement-pipeline.md +6 -6
  172. package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +2 -2
  173. package/docs/guidelines/agent-infra/six-hats.md +3 -3
  174. package/docs/guidelines/agent-infra/skill-quality-checklist.md +6 -6
  175. package/docs/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
  176. package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
  177. package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
  178. package/docs/guidelines/augment-portability-patterns.md +6 -4
  179. package/docs/guidelines/component-oriented-and-oop-development.md +3 -2
  180. package/docs/guidelines/cross-role-handoff.md +2 -2
  181. package/docs/guidelines/design-fidelity-mechanics.md +1 -1
  182. package/docs/guidelines/docs/readme-size-and-splitting.md +3 -3
  183. package/docs/guidelines/php/database.md +90 -7
  184. package/docs/guidelines/prompt-templates.md +7 -7
  185. package/llms.txt +2 -2
  186. package/package.json +5 -2
  187. package/src/agent-src/scripts/archive_completed_roadmaps.ts +247 -3
  188. package/src/agent-src/scripts/guarded_baseline.ts +328 -0
  189. package/src/agent-src/scripts/roadmap_set_step.ts +253 -0
  190. package/src/agent-src/scripts/stub_queue.ts +82 -0
  191. package/src/agent-src/scripts/stubs_due.ts +400 -0
  192. package/src/agent-src/scripts/ticket_bundles.ts +84 -0
  193. package/src/agent-src/scripts/update_roadmap_progress.ts +8 -4
  194. package/src/agent-src/templates/scripts/check_memory.ts +258 -17
  195. package/src/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
  196. package/src/agent-src/templates/scripts/memory_lookup.ts +86 -0
  197. package/src/agent-src/templates/scripts/memory_signal.ts +44 -0
  198. package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
  199. package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
  200. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
  201. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
  202. package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
  203. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
  204. package/src/config/agent-settings.template.yml +32 -3
  205. package/src/config/assurance-capability-registry.json +21 -9
  206. package/src/config/canonical-terms.yml +119 -0
  207. package/src/config/capability-native-outcome-bars.json +81 -0
  208. package/src/config/ci-local-parity.yml +80 -0
  209. package/src/config/drafting-channels.yml +43 -0
  210. package/src/config/estate-count-budget.json +38 -36
  211. package/src/config/evaluator-budgets.json +14 -14
  212. package/src/config/gate-coverage.yml +579 -63
  213. package/src/config/gate-reachability-exemptions.json +21 -0
  214. package/src/config/gate-violation-baselines.json +37 -19
  215. package/src/config/harness-evolution-budget.json +25 -0
  216. package/src/config/lapsed-beta-baseline.json +96 -0
  217. package/src/config/memory-twin-verdicts.yml +129 -0
  218. package/src/config/metric-registry.yml +124 -0
  219. package/src/config/pack-size-budget.json +16 -15
  220. package/src/config/preamble-payload-budget.json +8 -8
  221. package/src/config/profiles/balanced.ini +27 -0
  222. package/src/config/profiles/full.ini +27 -0
  223. package/src/config/profiles/minimal.ini +27 -0
  224. package/src/config/publish-surface.json +9 -2
  225. package/src/config/routing-coverage-seed.json +16 -2
  226. package/src/config/rule-stub-ceilings.json +126 -112
  227. package/src/config/turnaround-budget.json +66 -0
  228. package/src/install/claudeRuleRewrite.ts +288 -0
  229. package/src/install/detect.ts +60 -17
  230. package/src/install/detect_php_shape.ts +191 -0
  231. package/src/scripts/_cli/cmd_doctor.ts +27 -112
  232. package/src/scripts/_dispatch.bash +27 -0
  233. package/src/scripts/_lib/activation_ladder.ts +211 -0
  234. package/src/scripts/_lib/activation_states.ts +88 -0
  235. package/src/scripts/_lib/adherence_detectors.ts +143 -0
  236. package/src/scripts/_lib/arith_claims.ts +181 -0
  237. package/src/scripts/_lib/arm_ranking.ts +62 -0
  238. package/src/scripts/_lib/asset_delivery_ledger.ts +303 -0
  239. package/src/scripts/_lib/audit_field_provenance.ts +54 -0
  240. package/src/scripts/_lib/auto_dispatch.ts +32 -3
  241. package/src/scripts/_lib/bench_ab_size_claim.ts +98 -4
  242. package/src/scripts/_lib/benchmark_cache_fields.ts +158 -0
  243. package/src/scripts/_lib/candidate_proposer.ts +402 -0
  244. package/src/scripts/_lib/candidate_record.ts +601 -0
  245. package/src/scripts/_lib/capture_rate.ts +319 -0
  246. package/src/scripts/_lib/collector_denominator.ts +771 -0
  247. package/src/scripts/_lib/collector_record.ts +340 -0
  248. package/src/scripts/_lib/collector_store.ts +664 -0
  249. package/src/scripts/_lib/collector_supervision.ts +827 -0
  250. package/src/scripts/_lib/concern_estate.ts +77 -0
  251. package/src/scripts/_lib/config_chain.ts +477 -0
  252. package/src/scripts/_lib/config_cost.ts +209 -0
  253. package/src/scripts/_lib/council_settings_block.ts +6 -0
  254. package/src/scripts/_lib/curator_ops.ts +172 -0
  255. package/src/scripts/_lib/delivery_arm_experiment.ts +266 -0
  256. package/src/scripts/_lib/delivery_criticality.ts +126 -0
  257. package/src/scripts/_lib/doctor_runtime_checks.ts +70 -0
  258. package/src/scripts/_lib/empty_cycles.ts +88 -0
  259. package/src/scripts/_lib/eval_publication.ts +354 -0
  260. package/src/scripts/_lib/evaluation_vector.ts +283 -0
  261. package/src/scripts/_lib/evaluator_promotion.ts +225 -0
  262. package/src/scripts/_lib/evidence_basis.ts +118 -0
  263. package/src/scripts/_lib/exec_evidence.ts +8 -0
  264. package/src/scripts/_lib/experience_card.ts +272 -0
  265. package/src/scripts/_lib/experience_report.ts +151 -0
  266. package/src/scripts/_lib/experiment_freeze.ts +112 -0
  267. package/src/scripts/_lib/generated_by.ts +66 -0
  268. package/src/scripts/_lib/harness_evolution_guards.ts +229 -0
  269. package/src/scripts/_lib/hook_effect_probe.ts +141 -0
  270. package/src/scripts/_lib/host_denominator.ts +687 -0
  271. package/src/scripts/_lib/host_projection_reach.ts +157 -0
  272. package/src/scripts/_lib/ignored_blocker.ts +148 -0
  273. package/src/scripts/_lib/install_reach_checks.ts +495 -0
  274. package/src/scripts/_lib/json_duplicate_keys.ts +150 -0
  275. package/src/scripts/_lib/judge_hygiene.ts +228 -0
  276. package/src/scripts/_lib/legacy_boundary_map.ts +228 -0
  277. package/src/scripts/_lib/loop_guards.ts +299 -0
  278. package/src/scripts/_lib/loss_class.ts +129 -0
  279. package/src/scripts/_lib/machine_wake.ts +51 -0
  280. package/src/scripts/_lib/md_prose_lines.ts +177 -0
  281. package/src/scripts/_lib/minimality_tiebreak.ts +168 -0
  282. package/src/scripts/_lib/one_resolver_invariant.ts +449 -0
  283. package/src/scripts/_lib/orchestration_record.ts +253 -5
  284. package/src/scripts/_lib/outcome_envelope.ts +227 -0
  285. package/src/scripts/_lib/outcome_vocabularies.ts +125 -0
  286. package/src/scripts/_lib/paired_stats.ts +245 -0
  287. package/src/scripts/_lib/paired_verdict.ts +226 -0
  288. package/src/scripts/_lib/prefix_stable_surfaces.ts +123 -0
  289. package/src/scripts/_lib/privacy_class.ts +47 -0
  290. package/src/scripts/_lib/regression_neighbourhood.ts +259 -0
  291. package/src/scripts/_lib/repeated_failure.ts +105 -0
  292. package/src/scripts/_lib/repo_root.ts +111 -0
  293. package/src/scripts/_lib/review_skipped_record.ts +37 -0
  294. package/src/scripts/_lib/role_split.ts +338 -0
  295. package/src/scripts/_lib/runtime_journal.ts +1499 -0
  296. package/src/scripts/_lib/runtime_wiring_checks.ts +430 -0
  297. package/src/scripts/_lib/skill_catalogue.ts +11 -1
  298. package/src/scripts/_lib/source_digest.ts +200 -0
  299. package/src/scripts/_lib/source_redact.ts +260 -0
  300. package/src/scripts/_lib/source_shape.ts +502 -0
  301. package/src/scripts/_lib/source_snapshot_dedup.ts +199 -0
  302. package/src/scripts/_lib/strip_comments.d.mts +8 -0
  303. package/src/scripts/_lib/strip_comments.mjs +65 -0
  304. package/src/scripts/_lib/test_red_state.ts +154 -0
  305. package/src/scripts/_lib/trigger_shift.ts +144 -0
  306. package/src/scripts/_lib/untrusted_content.ts +82 -7
  307. package/src/scripts/adoption_report.ts +1 -1
  308. package/src/scripts/adr/evidence_census.ts +1 -1
  309. package/src/scripts/adr/regenerate_index.ts +26 -2
  310. package/src/scripts/adr_cite_check.ts +164 -1
  311. package/src/scripts/ai_council/blind_review.ts +11 -1
  312. package/src/scripts/ai_council/clients.ts +4 -0
  313. package/src/scripts/ai_council/config.ts +3 -0
  314. package/src/scripts/ai_council/consensus.ts +28 -8
  315. package/src/scripts/ai_council/consensus_round.ts +226 -0
  316. package/src/scripts/ai_council/disagreement_signal.ts +338 -0
  317. package/src/scripts/ai_council/inline_findings.ts +340 -0
  318. package/src/scripts/ai_council/judge_position_bias.ts +197 -0
  319. package/src/scripts/ai_council/orchestrator.ts +16 -203
  320. package/src/scripts/ai_council/orchestrator_results.ts +23 -3
  321. package/src/scripts/ai_council/pricing.ts +106 -0
  322. package/src/scripts/ai_council/prompts.ts +74 -7
  323. package/src/scripts/ai_council/provider_leakage_bench.ts +327 -0
  324. package/src/scripts/ai_council/py_format.ts +14 -3
  325. package/src/scripts/ai_council/recouncil_guard.ts +293 -0
  326. package/src/scripts/ai_council/response_record.ts +50 -0
  327. package/src/scripts/ai_council/topology_bench_manifest.ts +860 -0
  328. package/src/scripts/ai_council/topology_vocabulary.ts +175 -0
  329. package/src/scripts/asset_delivery_ledger.ts +157 -0
  330. package/src/scripts/attest_artifact.ts +193 -0
  331. package/src/scripts/audit_auto_rules.ts +2 -1
  332. package/src/scripts/audit_command_surface.ts +1 -1
  333. package/src/scripts/audit_mcp_tools.ts +2 -2
  334. package/src/scripts/audit_skill_overlap_allowlist.json +6 -1
  335. package/src/scripts/audit_user_type_axis.ts +2 -1
  336. package/src/scripts/bench_ab_clone.ts +215 -10
  337. package/src/scripts/bench_ab_integrity.ts +119 -5
  338. package/src/scripts/bench_ab_v2_stats.ts +40 -213
  339. package/src/scripts/build_archive_index.ts +1 -1
  340. package/src/scripts/build_linear_digest.ts +1 -1
  341. package/src/scripts/build_proof.ts +11 -4
  342. package/src/scripts/build_rule_trigger_matrix.ts +1 -1
  343. package/src/scripts/build_source_digests.ts +157 -0
  344. package/src/scripts/cache_realization_report.ts +103 -0
  345. package/src/scripts/check_adr_frontmatter.ts +126 -0
  346. package/src/scripts/check_benchmark_report_fields.ts +207 -0
  347. package/src/scripts/check_beta_review_markers.ts +235 -6
  348. package/src/scripts/check_bundle_path_leakage.ts +34 -1
  349. package/src/scripts/check_claims.ts +576 -6
  350. package/src/scripts/check_composite_arming.ts +35 -0
  351. package/src/scripts/check_concern_admissions.ts +365 -0
  352. package/src/scripts/check_enforcement_coverage.ts +28 -0
  353. package/src/scripts/check_estate_count.ts +142 -1
  354. package/src/scripts/check_gate_reachability.ts +383 -0
  355. package/src/scripts/check_host_loadability.ts +104 -0
  356. package/src/scripts/check_loss_class_declared.ts +308 -0
  357. package/src/scripts/check_md_language.ts +15 -98
  358. package/src/scripts/check_memory_proposal.ts +18 -11
  359. package/src/scripts/check_no_currency_in_cost_surfaces.ts +225 -0
  360. package/src/scripts/check_no_external_sources.ts +281 -6
  361. package/src/scripts/check_pack_conformance_fixture.ts +11 -1
  362. package/src/scripts/check_pack_size.ts +59 -2
  363. package/src/scripts/check_preamble_payload_budget.ts +114 -3
  364. package/src/scripts/check_prefix_stable_mutation.ts +372 -0
  365. package/src/scripts/check_publish_surface.ts +10 -1
  366. package/src/scripts/check_references.ts +24 -1
  367. package/src/scripts/check_rule_evidence_declaration.ts +316 -0
  368. package/src/scripts/check_rule_projection_integrity.ts +14 -4
  369. package/src/scripts/check_score_contract.ts +26 -3
  370. package/src/scripts/check_static_parity.ts +285 -0
  371. package/src/scripts/check_supervision_claim_atomicity.ts +498 -0
  372. package/src/scripts/check_suppression_hygiene.ts +15 -0
  373. package/src/scripts/ci_settle.ts +39 -2
  374. package/src/scripts/code_graph/build.ts +60 -3
  375. package/src/scripts/code_graph/extract.ts +44 -0
  376. package/src/scripts/code_graph/types.ts +24 -0
  377. package/src/scripts/collector_daemon.ts +714 -0
  378. package/src/scripts/config_cost_report.ts +302 -0
  379. package/src/scripts/consumer_matrix.ts +74 -1
  380. package/src/scripts/cost/budget.mjs +10 -3
  381. package/src/scripts/cost/track.mjs +10 -3
  382. package/src/scripts/council_cli.ts +37 -54
  383. package/src/scripts/dispatch_r2_reviewer.ts +46 -5
  384. package/src/scripts/evolution_lab.ts +980 -0
  385. package/src/scripts/external_sources_denylist.json +5 -18
  386. package/src/scripts/extract_audit_patterns.ts +21 -0
  387. package/src/scripts/fold_intake.ts +12 -1
  388. package/src/scripts/gen_discovery_baseline.ts +1 -1
  389. package/src/scripts/generate_capabilities_index.ts +12 -5
  390. package/src/scripts/generate_capability_matrix.ts +3 -3
  391. package/src/scripts/generate_command_flows.ts +2 -2
  392. package/src/scripts/generate_cookbook.ts +2 -2
  393. package/src/scripts/generate_index.ts +8 -1
  394. package/src/scripts/generate_knowledge_index.ts +2 -1
  395. package/src/scripts/generate_ownership_matrix.ts +2 -1
  396. package/src/scripts/generate_pack_manifests.ts +10 -2
  397. package/src/scripts/generate_role_experiences_catalog.ts +2 -2
  398. package/src/scripts/generate_settings_reference.ts +1 -1
  399. package/src/scripts/git_authorization_hook.ts +22 -1
  400. package/src/scripts/grade_target_readiness.ts +48 -5
  401. package/src/scripts/hook_effect_doctor.ts +261 -0
  402. package/src/scripts/hook_manifest.json +1 -1
  403. package/src/scripts/hook_manifest.yaml +98 -11
  404. package/src/scripts/hooks/block_speaking_inbox_dir.ts +271 -0
  405. package/src/scripts/hooks/block_unauthorized_git.ts +18 -0
  406. package/src/scripts/hooks/concern_registry.ts +4 -0
  407. package/src/scripts/hooks/dispatch_hook.ts +32 -0
  408. package/src/scripts/hooks/journal_record_hook.ts +345 -0
  409. package/src/scripts/hooks/prepush_metadata_sources.sh +110 -0
  410. package/src/scripts/hooks/run_continuation_hook.ts +44 -9
  411. package/src/scripts/hooks/shims/install.sh +61 -0
  412. package/src/scripts/hooks/shims/php +101 -0
  413. package/src/scripts/hooks/suggestion_capture_hook.ts +10 -0
  414. package/src/scripts/hot_context_hook.ts +16 -0
  415. package/src/scripts/install-hooks.sh +20 -5
  416. package/src/scripts/install.ts +17 -35
  417. package/src/scripts/inventory_frontmatter.ts +2 -1
  418. package/src/scripts/lint_adr_sweep_routing.ts +12 -0
  419. package/src/scripts/lint_canonical_terms.ts +604 -0
  420. package/src/scripts/lint_composition_review.ts +619 -0
  421. package/src/scripts/lint_consolidation_lineage.ts +666 -0
  422. package/src/scripts/lint_eval_specs.ts +593 -0
  423. package/src/scripts/lint_hook_manifest.ts +19 -0
  424. package/src/scripts/lint_host_portability.ts +373 -0
  425. package/src/scripts/lint_judge_prompt_expectation.ts +27 -1
  426. package/src/scripts/lint_memory_twin_parity.ts +335 -0
  427. package/src/scripts/lint_metric_consumers.ts +355 -0
  428. package/src/scripts/lint_skill_descriptions.ts +18 -1
  429. package/src/scripts/lint_skill_frontmatter_safety.ts +58 -0
  430. package/src/scripts/lint_skill_link_reach.ts +252 -0
  431. package/src/scripts/lint_skill_trigger_corpus.ts +426 -23
  432. package/src/scripts/lint_ticket_buildable.ts +17 -1
  433. package/src/scripts/mcp_server/consumer_tool_catalog.json +1 -1
  434. package/src/scripts/mcp_server/tool_catalog_source.ts +1 -1
  435. package/src/scripts/measure_docs_dead_links.ts +348 -0
  436. package/src/scripts/measure_host_capture.ts +461 -0
  437. package/src/scripts/migration_status.ts +3 -3
  438. package/src/scripts/model_rule_injection.ts +60 -0
  439. package/src/scripts/orchestration_record.ts +14 -0
  440. package/src/scripts/preamble_byte_census.ts +139 -10
  441. package/src/scripts/prepack-check.mjs +4 -1
  442. package/src/scripts/probe_turnaround.ts +384 -0
  443. package/src/scripts/probe_unblocked_ask.ts +279 -0
  444. package/src/scripts/render_benchmark_md.ts +1 -1
  445. package/src/scripts/rule_activation_census.ts +70 -9
  446. package/src/scripts/rule_backlinks.ts +1 -1
  447. package/src/scripts/run_lifecycle_suite.ts +208 -0
  448. package/src/scripts/run_skill_evals.ts +40 -0
  449. package/src/scripts/schemas/delivery-manifest.schema.json +43 -0
  450. package/src/scripts/schemas/drafting-channels.schema.json +43 -0
  451. package/src/scripts/schemas/evals.schema.json +24 -1
  452. package/src/scripts/schemas/rule.schema.json +52 -1
  453. package/src/scripts/schemas/skill.schema.json +17 -2
  454. package/src/scripts/skill_overlap.ts +7 -1
  455. package/src/scripts/skill_tools/run_block_d_eval.ts +8 -1
  456. package/src/scripts/skill_tools/score_skill_relevance.ts +84 -3
  457. package/src/scripts/skill_tools/suggest_skill_for_task.ts +8 -1
  458. package/src/scripts/skill_usage_report.ts +2 -1
  459. package/src/scripts/stubs_due.ts +211 -0
  460. package/src/scripts/sweep_source_surfaces.ts +678 -0
  461. package/src/scripts/sync_pr_branch.ts +3 -3
  462. package/src/scripts/trigger_eval_grandfather.json +202 -222
  463. package/src/scripts/utilization_report.ts +22 -2
  464. package/src/server/schemas/settings.ts +5 -0
  465. package/src/templates/marketing-copy.yml +1 -1
  466. package/dist/ui/assets/index-Cxgy63kz.js +0 -26
@@ -0,0 +1,664 @@
1
+ /**
2
+ * The supervised collector's record store — deletion, opt-out, retention, and
3
+ * the five upgrade transitions.
4
+ *
5
+ * `road-to-supervised-telemetry-collector` Phase 2 steps 2.3 and 2.4 (AC-4,
6
+ * AC-5). `_lib/collector_record.ts` fixed the record's SHAPE; nothing yet held a
7
+ * record, so "deletion and opt-out are implemented" had nowhere to be true.
8
+ * This is that somewhere, and it is deliberately the smallest thing that makes
9
+ * both steps' `verify:` lines executable rather than documentable.
10
+ *
11
+ * **Revision 2, after a blind R2 review found 12 findings (2 high).** The two
12
+ * high ones were evidence-strength failures on steps this roadmap had already
13
+ * flipped: the quarantine growth-budget test was tautological, and transition
14
+ * 2's "migration" was a version stamp with a test that could not detect that.
15
+ * Both are addressed below and each fix names the finding it closes.
16
+ *
17
+ * ## Class A, and provably so — this is NOT the collector
18
+ *
19
+ * Open, write or read, close, return. No resident process, no socket, no timer,
20
+ * no state surviving a call. The supervised process of Phase 4 is a *client* of
21
+ * this module; the module itself has the same shape as
22
+ * `_lib/runtime_journal.ts` and `_lib/test_red_state.ts`, so nothing here
23
+ * consumes the ADR-249 permission for a resident process. Phase 4 does that,
24
+ * with the four governance conditions attached.
25
+ *
26
+ * ## Where it lives — the uniqueness blocker's answer, executed
27
+ *
28
+ * `<user-root>/agent-collector/collector.sqlite`, where `<user-root>` defaults
29
+ * to `~/.event4u/agent-config/`. This is not a fresh choice: the
30
+ * `uniqueness-namespace` blocker resolved **(b) — exactly one collector per OS
31
+ * user** (AI council 2026-08-28, 2/2 convergent), on the ground that the
32
+ * uniqueness scope should match the state's scope and the configuration already
33
+ * lives under the user's home. A per-repository store would have contradicted
34
+ * that resolution while looking like a smaller decision.
35
+ *
36
+ * Two consequences worth naming rather than discovering:
37
+ *
38
+ * - **A repository is never discovered from the filesystem.** The same blocker
39
+ * forbids it explicitly. Records carry `machine_id` / `episode_id` and no
40
+ * path, so attribution survives multiplexing without the store ever holding a
41
+ * location. {@link CollectorRecord} could not hold one if it wanted to.
42
+ * - **The shared-machine case is a documented limitation, not a design.** A
43
+ * per-user store is the wrong boundary for a machine several people share,
44
+ * and the blocker records that as a known gap rather than solving it here.
45
+ *
46
+ * ## Opt-out prevents the WRITE, and that distinction is the whole step
47
+ *
48
+ * 2.3's second clause is *"opt-out prevents the write rather than filtering the
49
+ * read"*, which is a real architectural difference and not a phrasing
50
+ * preference. A read filter leaves the data on disk: the record exists, the
51
+ * consent choice is honoured only by whoever remembers to apply the filter, and
52
+ * a future reader that forgets is a leak. {@link writeRecord} refuses before it
53
+ * touches SQLite, and {@link isOptedOut} is checked there rather than by the
54
+ * caller, so a caller cannot forget.
55
+ *
56
+ * The marker is a FILE, not a settings key, and reachable without the
57
+ * collector's cooperation — the same property the kill switch of 3.3 needs. A
58
+ * user who cannot start the package can still `touch` it.
59
+ *
60
+ * **Cost, recorded rather than left for 3.2 to discover (R2 finding 12):** the
61
+ * check is one `existsSync` per write, i.e. one stat syscall on the write path.
62
+ * That is a deliberate trade: honouring a mid-session opt-out immediately is
63
+ * worth a stat, and caching the marker's value for the life of a handle would
64
+ * mean a user who opts out during a session keeps being recorded until it ends.
65
+ * The syscall is a line item owed to step 3.2's CPU budget, not a defect.
66
+ *
67
+ * ## Deduplication is a READ, and the duplicate stays visible
68
+ *
69
+ * Metric item 4 of the roadmap's own definition: *"Deduplication happens at read
70
+ * time over the store, never at write time, so a duplicate is observable as a
71
+ * defect rather than silently collapsed."* The first implementation used a
72
+ * `PRIMARY KEY` on the dedup key plus `INSERT OR REPLACE`, which satisfies the
73
+ * item's first sentence by violating its second — the retry is absorbed and
74
+ * nobody can see it happened. Self-caught before the review. The table is
75
+ * append-only now, {@link readRecords} collapses repeats with the first
76
+ * occurrence winning, and {@link readSummary} reports `rows`, `unique` and
77
+ * `duplicates` so a non-zero duplicate count is a defect somebody can find.
78
+ *
79
+ * ## Retention — declared, because an append-only table without one is a leak
80
+ *
81
+ * R2 finding 8: `collector_events` grows per event and had no TTL, pruning job,
82
+ * partition rotation or archive path, while this module reasoned about R-A7 for
83
+ * the neighbouring quarantine *directory*. {@link RETENTION_DAYS} is the
84
+ * declared policy and {@link pruneOlderThan} is the job. The number is the
85
+ * metric definition's own hard stop (item 8: the observation window extends to
86
+ * a hard stop at 63 days), so a record that can no longer belong to any live
87
+ * window has no reason to be kept. The DISK CEILING remains step 3.2's, and a
88
+ * retention policy is not a ceiling.
89
+ *
90
+ * ## Deletion is a path, exercised
91
+ *
92
+ * The governance roadmap's first draft required deletion to be *documented*. A
93
+ * documented deletion path that nobody executed is a claim, so
94
+ * {@link deleteMachine} is the supported path and the tests drive it and then
95
+ * assert the store no longer serves those records — including through
96
+ * {@link readRecords} with no filter, because "gone" has to mean gone from the
97
+ * store and not merely absent from one query.
98
+ */
99
+
100
+ import * as crypto from 'node:crypto';
101
+ import * as fs from 'node:fs';
102
+ import * as os from 'node:os';
103
+ import * as path from 'node:path';
104
+ import type { DatabaseSync } from 'node:sqlite';
105
+
106
+ import {
107
+ type CollectorRecord,
108
+ COLLECTOR_SCHEMA_VERSION,
109
+ dedupKey,
110
+ validateRecord,
111
+ } from './collector_record.js';
112
+ import {
113
+ isSqliteAvailableSync,
114
+ loadSqliteSync,
115
+ readUserVersion,
116
+ stampUserVersion,
117
+ } from './sqlite_guard.js';
118
+
119
+ /** Directory name under the user root. */
120
+ export const COLLECTOR_DIR_NAME = 'agent-collector';
121
+ /** The database file. */
122
+ export const COLLECTOR_FILE_NAME = 'collector.sqlite';
123
+ /** The opt-out marker. Its presence is the whole signal; content is ignored. */
124
+ export const OPT_OUT_MARKER_NAME = 'OPT-OUT';
125
+ /** Written while a migration is in flight; its presence means "crashed mid-migration". */
126
+ export const MIGRATION_MARKER_NAME = 'MIGRATION-IN-FLIGHT';
127
+ /** Where a store this revision cannot read is moved. Never deleted. */
128
+ export const QUARANTINE_DIR_NAME = 'quarantine';
129
+
130
+ /**
131
+ * Declared retention for `collector_events`, in days.
132
+ *
133
+ * 63 is the metric definition's hard stop for the observation window (item 8:
134
+ * 21 days, extending in 7-day increments to a hard stop at 63). A record older
135
+ * than that cannot belong to any live window, so keeping it serves nothing.
136
+ */
137
+ export const RETENTION_DAYS = 63;
138
+
139
+ /** Identifies this module in `sqlite_guard`'s unavailability message. */
140
+ const CALLER = 'collector_store';
141
+
142
+ /**
143
+ * Raised when the store cannot be opened at all — today, only because
144
+ * `node:sqlite` is unavailable in the runtime.
145
+ *
146
+ * Named for what it is rather than for what a wider class might one day be:
147
+ * R2 finding 11 caught the previous docstring ("asked to do something the
148
+ * contract forbids") describing a class this error has never had a member of.
149
+ */
150
+ export class CollectorStoreError extends Error {}
151
+
152
+ /**
153
+ * Why a write did not land. A blind zero is what this enum prevents.
154
+ *
155
+ * `sqlite-unavailable` was removed (R2 finding 11): no code path returned it,
156
+ * because unavailability throws from {@link openCollectorStore} before a write
157
+ * is reachable. An unreachable member forces an exhaustive consumer switch to
158
+ * handle a case that cannot occur, while the real failure arrives as an
159
+ * exception it does not handle.
160
+ */
161
+ export type WriteRefusal = 'opted-out' | 'invalid-record' | 'schema-quarantined';
162
+
163
+ export interface WriteOutcome {
164
+ written: boolean;
165
+ /** Set when `written` is false. Never null-and-written. */
166
+ refusal: WriteRefusal | null;
167
+ /** Validation errors, when the refusal is `invalid-record`. */
168
+ errors: readonly string[];
169
+ }
170
+
171
+ export interface StoreLocation {
172
+ /** The user root the store hangs off. Carried so nothing has to re-derive it. */
173
+ readonly userRoot: string;
174
+ readonly root: string;
175
+ readonly path: string;
176
+ readonly optOutMarker: string;
177
+ readonly migrationMarker: string;
178
+ readonly quarantineDir: string;
179
+ }
180
+
181
+ /** The default user root — where configuration already lives. */
182
+ export function defaultUserRoot(): string {
183
+ return path.join(os.homedir(), '.event4u', 'agent-config');
184
+ }
185
+
186
+ /** Resolve every path the store uses. Pure: creates nothing. */
187
+ export function resolveCollectorStore(userRoot: string = defaultUserRoot()): StoreLocation {
188
+ const dir = path.join(userRoot, COLLECTOR_DIR_NAME);
189
+ return {
190
+ userRoot,
191
+ root: dir,
192
+ path: path.join(dir, COLLECTOR_FILE_NAME),
193
+ optOutMarker: path.join(dir, OPT_OUT_MARKER_NAME),
194
+ migrationMarker: path.join(dir, MIGRATION_MARKER_NAME),
195
+ quarantineDir: path.join(dir, QUARANTINE_DIR_NAME),
196
+ };
197
+ }
198
+
199
+ /** True when this user has opted out. Checked at write time, never at read. */
200
+ export function isOptedOut(userRoot: string = defaultUserRoot()): boolean {
201
+ return fs.existsSync(resolveCollectorStore(userRoot).optOutMarker);
202
+ }
203
+
204
+ /** Create the opt-out marker. Idempotent. */
205
+ export function optOut(userRoot: string = defaultUserRoot()): void {
206
+ const loc = resolveCollectorStore(userRoot);
207
+ fs.mkdirSync(loc.root, { recursive: true });
208
+ fs.writeFileSync(
209
+ loc.optOutMarker,
210
+ 'This user has opted out of collector capture. Delete this file to opt back in.\n',
211
+ );
212
+ }
213
+
214
+ /** Remove the opt-out marker. Idempotent. */
215
+ export function optIn(userRoot: string = defaultUserRoot()): void {
216
+ fs.rmSync(resolveCollectorStore(userRoot).optOutMarker, { force: true });
217
+ }
218
+
219
+ export interface StoreHandle {
220
+ readonly db: DatabaseSync;
221
+ readonly location: StoreLocation;
222
+ /**
223
+ * True when **this open** moved a store aside it could not read, and is
224
+ * handing back a fresh one.
225
+ *
226
+ * SESSION-SCOPED BY DESIGN, and named for it (R2 finding 4). The previous
227
+ * name (`quarantined`) implied a durable lockout it never was: the fresh
228
+ * store is stamped at the current version, so the NEXT open over the same
229
+ * on-disk state returns `false` and writes land. That is correct — the
230
+ * incompatible store is preserved and the fallback is usable — but "one-shot
231
+ * signal" and "durable refusal" were indistinguishable from the code, so a
232
+ * test now pins the post-restart behaviour explicitly.
233
+ *
234
+ * Writes through THIS handle are refused so the incompatible-schema event
235
+ * reaches the caller instead of being absorbed by a silent fallback.
236
+ */
237
+ readonly quarantine_performed: boolean;
238
+ close(): void;
239
+ }
240
+
241
+ /**
242
+ * APPEND-ONLY, and `dedup_key` is deliberately NOT unique.
243
+ *
244
+ * Metric item 4 requires read-time dedup so a duplicate is observable. A
245
+ * `PRIMARY KEY` on `dedup_key` plus `INSERT OR REPLACE` satisfies "a repeated
246
+ * key is one record" by violating "observable as a defect", which is the shape
247
+ * of contract this whole roadmap exists to refuse. So the table accepts both
248
+ * rows, {@link readRecords} collapses them, and {@link readSummary} reports how
249
+ * many were collapsed.
250
+ */
251
+ const SCHEMA = `
252
+ CREATE TABLE IF NOT EXISTS collector_events (
253
+ row_id INTEGER PRIMARY KEY AUTOINCREMENT,
254
+ dedup_key TEXT NOT NULL,
255
+ schema_version INTEGER NOT NULL,
256
+ machine_id TEXT NOT NULL,
257
+ episode_id TEXT NOT NULL,
258
+ event TEXT NOT NULL,
259
+ sequence INTEGER NOT NULL,
260
+ outcome TEXT NOT NULL,
261
+ platform TEXT NOT NULL,
262
+ occurred_on TEXT NOT NULL,
263
+ collector_version TEXT NOT NULL
264
+ );
265
+ CREATE INDEX IF NOT EXISTS collector_events_dedup
266
+ ON collector_events (dedup_key);
267
+ CREATE INDEX IF NOT EXISTS collector_events_machine
268
+ ON collector_events (machine_id, occurred_on);
269
+ `;
270
+
271
+ /**
272
+ * One migration step: `from` → `from + 1`. Registered explicitly, so a version
273
+ * with no registered step is a REFUSAL rather than a silent stamp.
274
+ *
275
+ * R2 finding 2, and it is the finding that mattered most. The previous
276
+ * implementation ran `db.exec(SCHEMA)` — `CREATE TABLE IF NOT EXISTS`, a no-op
277
+ * on an existing table of ANY shape — and then stamped `user_version` forward.
278
+ * The whole "migration" was the stamp, and its test seeded only the version
279
+ * integer backwards over the CURRENT table shape, so it passed for a stamp-only
280
+ * implementation and could never have caught the missing DDL. The first real
281
+ * column change would have stamped an old-shaped store as current.
282
+ *
283
+ * The ladder makes the mechanism real and falsifiable. The one registered step
284
+ * (0 → 1) is genuinely a no-op **because no schema delta exists yet**, and that
285
+ * is stated here rather than implied by an empty function body: version 0 is the
286
+ * un-stamped state a database gets before this module writes to it, and the
287
+ * table it lands with is already the v1 shape.
288
+ */
289
+ export type MigrationStep = (db: DatabaseSync) => void;
290
+
291
+ export const MIGRATIONS: Readonly<Record<number, MigrationStep>> = Object.freeze({
292
+ // 0 → 1. No DDL: `user_version` 0 is the default SQLite assigns, and a
293
+ // database created by this module is already at the v1 shape. The entry
294
+ // exists so the path RESOLVES rather than being inferred from its absence.
295
+ 0: () => {},
296
+ });
297
+
298
+ /**
299
+ * The steps needed to walk `from` → `to`, or `null` when the walk is impossible.
300
+ *
301
+ * Pure, exported, and tested directly — which is what makes the ladder
302
+ * falsifiable without seeding a database per hypothesis. `null` means the caller
303
+ * must quarantine: refusing an unmigratable store is the safe branch, and
304
+ * stamping it is the unsafe one this replaces.
305
+ */
306
+ export function migrationPath(from: number, to: number): number[] | null {
307
+ if (from > to) return null; // a NEWER store; quarantine, never downgrade
308
+ const steps: number[] = [];
309
+ for (let v = from; v < to; v++) {
310
+ if (!(v in MIGRATIONS)) return null;
311
+ steps.push(v);
312
+ }
313
+ return steps;
314
+ }
315
+
316
+ /** Is `node:sqlite` usable in this runtime? */
317
+ export function isStoreAvailable(): boolean {
318
+ return isSqliteAvailableSync();
319
+ }
320
+
321
+ /**
322
+ * Move a store this revision cannot read aside, preserving it byte-for-byte.
323
+ *
324
+ * 3.1's rollback matrix requires an incompatible schema to be *preserved
325
+ * without reading or rewriting*. Quarantine is that: a rename, and the only
326
+ * read is a whole-file digest to name the destination — **the bytes are never
327
+ * interpreted as records**, which is the claim row 5 means and the earlier
328
+ * wording ("a rename, never a read") overstated (R2 finding 10). The digest read
329
+ * is a line item owed to step 3.2's disk budget.
330
+ *
331
+ * The name carries a CONTENT DIGEST rather than a timestamp so two quarantines
332
+ * of the same bytes collapse instead of accumulating — R-A7 applied to a
333
+ * directory nobody prunes.
334
+ *
335
+ * WAL sidecars are **moved, not deleted** (R2 finding 6). Deleting them would
336
+ * drop any committed data still in the WAL while a byte-equality assertion on
337
+ * the main file stayed green — preserving the file and discarding its tail is
338
+ * not preservation.
339
+ *
340
+ * **Measured while fixing it, and it demotes the finding rather than confirming
341
+ * it:** the loss is NOT reachable through {@link openCollectorStore}. That
342
+ * function opens the database before reading its version, and opening removes an
343
+ * unrecognised `-wal` (a real WAL-mode close checkpoints its contents into the
344
+ * main file first), so by the time this function runs there is no sidecar left
345
+ * to lose. No `PRAGMA journal_mode = WAL` is set anywhere either. Two tests pin
346
+ * both halves — the move, called directly, and the removal-on-open that makes
347
+ * the end-to-end path unreachable — so enabling WAL later reds the second one
348
+ * and re-opens the question deliberately instead of silently.
349
+ */
350
+ export function quarantine(location: StoreLocation): string {
351
+ fs.mkdirSync(location.quarantineDir, { recursive: true });
352
+ const digest = crypto
353
+ .createHash('sha256')
354
+ .update(fs.readFileSync(location.path))
355
+ .digest('hex')
356
+ .slice(0, 12);
357
+ const target = path.join(location.quarantineDir, `${COLLECTOR_FILE_NAME}.${digest}`);
358
+ if (fs.existsSync(target)) {
359
+ // Same bytes already preserved. Drop the duplicate rather than growing
360
+ // the directory: the artefact is already there, byte-identical.
361
+ fs.rmSync(location.path, { force: true });
362
+ for (const sidecar of ['-wal', '-shm']) {
363
+ fs.rmSync(`${location.path}${sidecar}`, { force: true });
364
+ }
365
+ return target;
366
+ }
367
+ fs.renameSync(location.path, target);
368
+ for (const sidecar of ['-wal', '-shm']) {
369
+ const from = `${location.path}${sidecar}`;
370
+ if (fs.existsSync(from)) fs.renameSync(from, `${target}${sidecar}`);
371
+ }
372
+ return target;
373
+ }
374
+
375
+ /**
376
+ * Open the store, migrating or quarantining as the on-disk version requires.
377
+ *
378
+ * The five transitions of 2.4 all resolve here:
379
+ *
380
+ * 1. **Fresh store** — created at {@link COLLECTOR_SCHEMA_VERSION}.
381
+ * 2. **Older records, newer package** — walked forward through
382
+ * {@link MIGRATIONS} inside a marked window. A version with NO registered
383
+ * path is quarantined, not stamped.
384
+ * 3. **Newer records, older package** — QUARANTINED. Never read as records,
385
+ * never rewritten. Reading a record shape you do not understand is how a
386
+ * field gets silently dropped, and dropping is what this schema refuses.
387
+ * 4. **Crash mid-migration** — the marker survives the crash, so the next open
388
+ * finds a half-migrated store and quarantines it rather than resuming a
389
+ * migration whose progress nothing recorded.
390
+ * 5. **Uninstall** — {@link uninstall}, which removes the store and leaves the
391
+ * quarantine directory, because a quarantined store is evidence.
392
+ */
393
+ export function openCollectorStore(userRoot: string = defaultUserRoot()): StoreHandle {
394
+ if (!isSqliteAvailableSync()) {
395
+ throw new CollectorStoreError('node:sqlite is unavailable in this runtime');
396
+ }
397
+ const location = resolveCollectorStore(userRoot);
398
+ fs.mkdirSync(location.root, { recursive: true });
399
+ const sqlite = loadSqliteSync(CALLER);
400
+
401
+ // Transition 4 comes FIRST: a store carrying an in-flight marker is
402
+ // half-migrated by definition, and its version stamp cannot be trusted to
403
+ // say which half. Checked before the version is even read.
404
+ //
405
+ // The marker is cleared whenever it EXISTS, independent of the database
406
+ // (R2 finding 3). Previously the guard required marker AND db, while
407
+ // `quarantine()` renames the db away before the marker is removed — so a
408
+ // crash between those two steps left marker-present / db-absent, the next
409
+ // open took the fresh-store branch and never cleared it, and the open after
410
+ // that quarantined a healthy store.
411
+ let markerQuarantined = false;
412
+ if (fs.existsSync(location.migrationMarker)) {
413
+ if (fs.existsSync(location.path)) {
414
+ quarantine(location);
415
+ markerQuarantined = true;
416
+ }
417
+ fs.rmSync(location.migrationMarker, { force: true });
418
+ }
419
+ if (markerQuarantined) {
420
+ const db = new sqlite.DatabaseSync(location.path);
421
+ db.exec(SCHEMA);
422
+ stampUserVersion(db, COLLECTOR_SCHEMA_VERSION);
423
+ return handleFor(db, location, false);
424
+ }
425
+
426
+ const existed = fs.existsSync(location.path);
427
+ const db = new sqlite.DatabaseSync(location.path);
428
+
429
+ if (!existed) {
430
+ db.exec(SCHEMA);
431
+ stampUserVersion(db, COLLECTOR_SCHEMA_VERSION);
432
+ return handleFor(db, location, false);
433
+ }
434
+
435
+ const onDisk = readUserVersion(db);
436
+ const steps = migrationPath(onDisk, COLLECTOR_SCHEMA_VERSION);
437
+
438
+ if (steps === null) {
439
+ // Transition 3, and the no-registered-path case with it. Close before
440
+ // renaming: a quarantine that leaves an open handle on the moved file is
441
+ // a rename that looks successful and is not.
442
+ db.close();
443
+ quarantine(location);
444
+ const fresh = new sqlite.DatabaseSync(location.path);
445
+ fresh.exec(SCHEMA);
446
+ stampUserVersion(fresh, COLLECTOR_SCHEMA_VERSION);
447
+ return handleFor(fresh, location, true);
448
+ }
449
+
450
+ if (steps.length > 0) {
451
+ // Transition 2, inside a marked window so transition 4 can see a crash.
452
+ fs.writeFileSync(
453
+ location.migrationMarker,
454
+ `${onDisk} -> ${COLLECTOR_SCHEMA_VERSION} via ${steps.join(',')}\n`,
455
+ );
456
+ try {
457
+ db.exec(SCHEMA);
458
+ for (const step of steps) {
459
+ (MIGRATIONS[step] as MigrationStep)(db);
460
+ }
461
+ stampUserVersion(db, COLLECTOR_SCHEMA_VERSION);
462
+ } finally {
463
+ fs.rmSync(location.migrationMarker, { force: true });
464
+ }
465
+ return handleFor(db, location, false);
466
+ }
467
+
468
+ db.exec(SCHEMA);
469
+ return handleFor(db, location, false);
470
+ }
471
+
472
+ function handleFor(
473
+ db: DatabaseSync,
474
+ location: StoreLocation,
475
+ quarantine_performed: boolean,
476
+ ): StoreHandle {
477
+ return {
478
+ db,
479
+ location,
480
+ quarantine_performed,
481
+ close(): void {
482
+ db.close();
483
+ },
484
+ };
485
+ }
486
+
487
+ /**
488
+ * Write one record, or refuse with a named reason.
489
+ *
490
+ * Opt-out is checked HERE and not by the caller, which is the point: a consent
491
+ * check a caller can forget is a consent check that will be forgotten.
492
+ */
493
+ export function writeRecord(handle: StoreHandle, candidate: unknown): WriteOutcome {
494
+ if (isOptedOut(handle.location.userRoot)) {
495
+ return { written: false, refusal: 'opted-out', errors: [] };
496
+ }
497
+ if (handle.quarantine_performed) {
498
+ return { written: false, refusal: 'schema-quarantined', errors: [] };
499
+ }
500
+ const verdict = validateRecord(candidate);
501
+ if (!verdict.ok) {
502
+ return { written: false, refusal: 'invalid-record', errors: verdict.errors };
503
+ }
504
+ const record = candidate as CollectorRecord;
505
+ handle.db
506
+ .prepare(
507
+ `INSERT INTO collector_events
508
+ (dedup_key, schema_version, machine_id, episode_id, event, sequence,
509
+ outcome, platform, occurred_on, collector_version)
510
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
511
+ )
512
+ .run(
513
+ dedupKey(record),
514
+ record.schema_version,
515
+ record.machine_id,
516
+ record.episode_id,
517
+ record.event,
518
+ record.sequence,
519
+ record.outcome,
520
+ record.platform,
521
+ record.occurred_on,
522
+ record.collector_version,
523
+ );
524
+ return { written: true, refusal: null, errors: [] };
525
+ }
526
+
527
+ /** What a read collapsed, so a duplicate is observable rather than invisible. */
528
+ export interface StoreSummary {
529
+ /** Rows physically present. */
530
+ rows: number;
531
+ /** Distinct `(machine_id, episode_id, event, sequence)` keys among them. */
532
+ unique: number;
533
+ /** `rows - unique`. Non-zero is a DEFECT to investigate, not a tidy-up. */
534
+ duplicates: number;
535
+ }
536
+
537
+ /**
538
+ * Row and key counts for the whole store.
539
+ *
540
+ * This is the observability half of metric item 4. Read-time dedup without a
541
+ * duplicate count would hide the defect just as effectively as a write-time
542
+ * `INSERT OR REPLACE` — the records would be collapsed by a different mechanism
543
+ * and nobody would know a retry had double-written.
544
+ */
545
+ export function readSummary(handle: StoreHandle): StoreSummary {
546
+ if (handle.quarantine_performed) return { rows: 0, unique: 0, duplicates: 0 };
547
+ const row = handle.db
548
+ .prepare('SELECT COUNT(*) AS n, COUNT(DISTINCT dedup_key) AS uniq FROM collector_events')
549
+ .get() as { n: number; uniq: number };
550
+ return { rows: row.n, unique: row.uniq, duplicates: row.n - row.uniq };
551
+ }
552
+
553
+ /**
554
+ * Every record, or every record for one machine. Never filtered by consent.
555
+ *
556
+ * DEDUPLICATED AT READ TIME, first occurrence by insertion order winning — the
557
+ * metric definition's item 4. The rows themselves stay on disk, so
558
+ * {@link readSummary} can still say a duplicate happened.
559
+ */
560
+ export function readRecords(handle: StoreHandle, machineId?: string): CollectorRecord[] {
561
+ if (handle.quarantine_performed) return [];
562
+ const columns = `dedup_key, schema_version, machine_id, episode_id, event, sequence,
563
+ outcome, platform, occurred_on, collector_version`;
564
+ const rows = (
565
+ machineId === undefined
566
+ ? handle.db.prepare(`SELECT ${columns} FROM collector_events ORDER BY row_id`).all()
567
+ : handle.db
568
+ .prepare(
569
+ `SELECT ${columns} FROM collector_events WHERE machine_id = ?
570
+ ORDER BY row_id`,
571
+ )
572
+ .all(machineId)
573
+ ) as unknown as ({ dedup_key: string } & CollectorRecord)[];
574
+
575
+ const seen = new Set<string>();
576
+ const out: CollectorRecord[] = [];
577
+ for (const row of rows) {
578
+ if (seen.has(row.dedup_key)) continue;
579
+ seen.add(row.dedup_key);
580
+ const { dedup_key: _ignored, ...record } = row;
581
+ out.push(record);
582
+ }
583
+ return out;
584
+ }
585
+
586
+ /**
587
+ * The supported deletion path. Returns how many ROWS were removed.
588
+ *
589
+ * A GDPR Art. 17 request names a subject, and on this store the subject is a
590
+ * machine — `machine_id` is the only identity a record carries. Deletion is
591
+ * therefore by machine and nothing coarser is offered: a "delete everything"
592
+ * verb would make a targeted request impossible to serve honestly.
593
+ *
594
+ * The count is the DELETE's own `changes` (R2 finding 7). It used to be a
595
+ * pre-delete SELECT count, which over-reports the moment the two disagree — on
596
+ * the one path this module frames as serving a legal request — and materialised
597
+ * every matching row into objects purely to take `.length`.
598
+ */
599
+ export function deleteMachine(handle: StoreHandle, machineId: string): number {
600
+ if (handle.quarantine_performed) return 0;
601
+ const result = handle.db
602
+ .prepare('DELETE FROM collector_events WHERE machine_id = ?')
603
+ .run(machineId);
604
+ return Number(result.changes);
605
+ }
606
+
607
+ /**
608
+ * Drop records older than the declared retention. Returns rows removed.
609
+ *
610
+ * The R-A7 obligation this module owed and did not have (R2 finding 8): an
611
+ * append-only table must declare retention as a TTL, a pruning job, a partition
612
+ * rotation or an archive path. This is the job; {@link RETENTION_DAYS} is the
613
+ * policy. `today` is passed in rather than read from the clock so the boundary
614
+ * is testable without freezing time.
615
+ *
616
+ * Compares `occurred_on`, a UTC calendar DATE, as a string — which is why the
617
+ * record's date field is a date and not a timestamp.
618
+ */
619
+ export function pruneOlderThan(
620
+ handle: StoreHandle,
621
+ today: string,
622
+ days: number = RETENTION_DAYS,
623
+ ): number {
624
+ if (handle.quarantine_performed) return 0;
625
+ const cutoff = new Date(Date.parse(`${today}T00:00:00Z`) - days * 86_400_000)
626
+ .toISOString()
627
+ .slice(0, 10);
628
+ const result = handle.db
629
+ .prepare('DELETE FROM collector_events WHERE occurred_on < ?')
630
+ .run(cutoff);
631
+ return Number(result.changes);
632
+ }
633
+
634
+ /**
635
+ * What uninstall removes: the database, its sidecars, and the migration marker.
636
+ *
637
+ * It does NOT remove the quarantine directory. A quarantined store is the
638
+ * record of an incompatible-schema event, and uninstall is not the moment to
639
+ * destroy evidence about it.
640
+ *
641
+ * It does NOT remove the **opt-out marker**, and that is a decision rather than
642
+ * an omission (R2 finding 5): reinstalling must not silently revoke a consent
643
+ * choice the user made, so opt-out outlives uninstall. The previous docstring
644
+ * said "the markers", plural, which would have invited a future edit to
645
+ * reconcile code to prose by deleting it. Named, and tested.
646
+ *
647
+ * Returns the paths actually removed.
648
+ */
649
+ export function uninstall(userRoot: string = defaultUserRoot()): string[] {
650
+ const location = resolveCollectorStore(userRoot);
651
+ const removed: string[] = [];
652
+ for (const target of [
653
+ location.path,
654
+ `${location.path}-wal`,
655
+ `${location.path}-shm`,
656
+ location.migrationMarker,
657
+ ]) {
658
+ if (fs.existsSync(target)) {
659
+ fs.rmSync(target, { force: true });
660
+ removed.push(target);
661
+ }
662
+ }
663
+ return removed;
664
+ }