@event4u/agent-config 14.12.0 → 14.14.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 (521) 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 +118 -155
  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 +145 -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-comment-discipline.md +6 -0
  30. package/dist/agent-src/rules/code-provenance.md +4 -0
  31. package/dist/agent-src/rules/design-fidelity.md +1 -1
  32. package/dist/agent-src/rules/domain-safety-pii.md +4 -0
  33. package/dist/agent-src/rules/domain-safety-retention.md +4 -0
  34. package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
  35. package/dist/agent-src/rules/external-code-graph-interop.md +14 -12
  36. package/dist/agent-src/rules/improve-before-implement.md +6 -8
  37. package/dist/agent-src/rules/laravel-routing.md +1 -1
  38. package/dist/agent-src/rules/lethal-trifecta-guard.md +4 -0
  39. package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
  40. package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
  41. package/dist/agent-src/rules/source-confidentiality.md +12 -34
  42. package/dist/agent-src/rules/symfony-routing.md +1 -1
  43. package/dist/agent-src/rules/token-efficiency.md +14 -0
  44. package/dist/agent-src/rules/tool-safety.md +4 -0
  45. package/dist/agent-src/rules/untrusted-input-defense.md +4 -0
  46. package/dist/agent-src/scripts/archive_completed_roadmaps.ts +252 -8
  47. package/dist/agent-src/scripts/guarded_baseline.ts +343 -0
  48. package/dist/agent-src/scripts/roadmap_set_step.ts +253 -0
  49. package/dist/agent-src/scripts/stub_queue.ts +82 -0
  50. package/dist/agent-src/scripts/stubs_due.ts +516 -0
  51. package/dist/agent-src/scripts/ticket_bundles.ts +84 -0
  52. package/dist/agent-src/scripts/update_roadmap_progress.ts +16 -12
  53. package/dist/agent-src/skills/brand-asset-generation/SKILL.md +17 -4
  54. package/dist/agent-src/skills/brand-audit/SKILL.md +33 -0
  55. package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -0
  56. package/dist/agent-src/skills/database/SKILL.md +8 -0
  57. package/dist/agent-src/skills/dependency-upgrade/SKILL.md +27 -0
  58. package/dist/agent-src/skills/design-review/SKILL.md +1 -1
  59. package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
  60. package/dist/agent-src/skills/existing-ui-audit/SKILL.md +17 -1
  61. package/dist/agent-src/skills/feature-planning/SKILL.md +9 -2
  62. package/dist/agent-src/skills/frontend-render-security/SKILL.md +44 -0
  63. package/dist/agent-src/skills/git-workflow/SKILL.md +1 -1
  64. package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +38 -4
  65. package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +2 -2
  66. package/dist/agent-src/skills/laravel/SKILL.md +35 -0
  67. package/dist/agent-src/skills/laravel-migration/SKILL.md +57 -3
  68. package/dist/agent-src/skills/monorepo-workspace/SKILL.md +26 -0
  69. package/dist/agent-src/skills/playbook-authoring/SKILL.md +28 -1
  70. package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
  71. package/dist/agent-src/skills/skill-writing/references/procedure.md +5 -5
  72. package/dist/agent-src/skills/sql-writing/SKILL.md +7 -1
  73. package/dist/agent-src/skills/standards-from-config/SKILL.md +27 -4
  74. package/dist/agent-src/skills/supply-chain-intake/SKILL.md +11 -4
  75. package/dist/agent-src/skills/symfony-workflow/SKILL.md +35 -0
  76. package/dist/agent-src/skills/test-case-discovery/SKILL.md +85 -0
  77. package/dist/agent-src/skills/test-driven-development/SKILL.md +33 -9
  78. package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
  79. package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +37 -0
  80. package/dist/agent-src/skills/ui-component-architect/SKILL.md +45 -2
  81. package/dist/agent-src/skills/upstream-contribute/SKILL.md +38 -1
  82. package/dist/agent-src/skills/verify-repair-loop/SKILL.md +9 -4
  83. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  84. package/dist/agent-src/templates/roadmaps.md +97 -3
  85. package/dist/agent-src/templates/scripts/check_memory.ts +258 -17
  86. package/dist/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
  87. package/dist/agent-src/templates/scripts/memory_lookup.ts +86 -0
  88. package/dist/agent-src/templates/scripts/memory_signal.ts +44 -0
  89. package/dist/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
  90. package/dist/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
  91. package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
  92. package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
  93. package/dist/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
  94. package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
  95. package/dist/cli/commands/uiAudit.js +124 -5
  96. package/dist/cli/registry.js +2 -0
  97. package/dist/cli-delegate/{chunk-HYWSRNUX.js → chunk-2DK2VJWG.js} +3 -3
  98. package/dist/cli-delegate/{chunk-JCW7ZLHK.js → chunk-4FS4WRXP.js} +5 -0
  99. package/dist/cli-delegate/{chunk-OMIVVCG2.js → chunk-A4QJ6ENT.js} +24 -0
  100. package/dist/cli-delegate/{chunk-JJA2QEWA.js → chunk-AKJGUQNO.js} +466 -285
  101. package/dist/cli-delegate/{chunk-E46KPN6K.js → chunk-BTWUYDGN.js} +424 -363
  102. package/dist/cli-delegate/{chunk-K4FFODTS.js → chunk-ILMUPLGW.js} +1227 -316
  103. package/dist/cli-delegate/{chunk-FF7WAFMR.js → chunk-KPBWFOPZ.js} +5 -1
  104. package/dist/cli-delegate/cmd_adr_effective.js +102 -0
  105. package/dist/cli-delegate/cmd_conformance.js +80 -11
  106. package/dist/cli-delegate/cmd_doctor.js +3 -3
  107. package/dist/cli-delegate/cmd_export.js +3 -2
  108. package/dist/cli-delegate/cmd_fleet.js +2 -2
  109. package/dist/cli-delegate/cmd_handoff.js +1 -1
  110. package/dist/cli-delegate/cmd_preflight.js +2 -2
  111. package/dist/cli-delegate/cmd_prune.js +3 -2
  112. package/dist/cli-delegate/cmd_settings_get.js +1 -1
  113. package/dist/cli-delegate/cmd_settings_migrate.js +3 -2
  114. package/dist/cli-delegate/cmd_settings_set.js +2 -2
  115. package/dist/cli-delegate/cmd_sync.js +3 -2
  116. package/dist/cli-delegate/cmd_uninstall.js +3 -2
  117. package/dist/cli-delegate/cmd_validate.js +3 -2
  118. package/dist/discovery/deprecation-report.md +1 -1
  119. package/dist/discovery/discovery-manifest.json +58 -58
  120. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  121. package/dist/discovery/discovery-manifest.summary.md +1 -1
  122. package/dist/discovery/dormancy-report.md +1 -1
  123. package/dist/discovery/orphan-report.md +1 -1
  124. package/dist/discovery/packs.json +2 -2
  125. package/dist/discovery/trust-report.md +1 -1
  126. package/dist/discovery/workspaces.json +2 -2
  127. package/dist/hooks/dispatch.js +2900 -1049
  128. package/dist/install/claudeRuleRewrite.js +254 -0
  129. package/dist/install/detect.js +29 -16
  130. package/dist/install/detect_php_shape.js +159 -0
  131. package/dist/install/install.mjs +634 -428
  132. package/dist/mcp/awesome-mcp-servers.row.md +1 -1
  133. package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
  134. package/dist/mcp/registry-manifest.json +3 -3
  135. package/dist/mcp/server.json +3 -3
  136. package/dist/mcp/server.mjs +4895 -2627
  137. package/dist/server/schemas/settings.js +3 -0
  138. package/dist/ui/assets/index-D0jaCRPM.js +29 -0
  139. package/dist/ui/index.html +1 -1
  140. package/docs/MIGRATION.md +1 -1
  141. package/docs/THIRD-PARTY-NOTICES.md +25 -0
  142. package/docs/contracts/provider-lifecycle.md +1 -1
  143. package/docs/contracts/settings-classes.md +10 -4
  144. package/docs/guidelines/agent-infra/5w2h-analysis.md +3 -3
  145. package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -0
  146. package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +58 -3
  147. package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +2 -2
  148. package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +3 -3
  149. package/docs/guidelines/agent-infra/break-glass-usage.md +7 -7
  150. package/docs/guidelines/agent-infra/carve-out-predicates.md +3 -3
  151. package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
  152. package/docs/guidelines/agent-infra/critical-thinking.md +4 -4
  153. package/docs/guidelines/agent-infra/direct-answers-demos.md +2 -2
  154. package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +1 -1
  155. package/docs/guidelines/agent-infra/engineering-memory-data-format.md +3 -3
  156. package/docs/guidelines/agent-infra/existence-question-verdicts.md +80 -0
  157. package/docs/guidelines/agent-infra/first-principles.md +2 -2
  158. package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +2 -2
  159. package/docs/guidelines/agent-infra/guarded-baseline.md +39 -0
  160. package/docs/guidelines/agent-infra/inversion-thinking.md +5 -5
  161. package/docs/guidelines/agent-infra/language-and-tone-examples.md +1 -1
  162. package/docs/guidelines/agent-infra/layered-settings.md +10 -10
  163. package/docs/guidelines/agent-infra/memory-access.md +1 -1
  164. package/docs/guidelines/agent-infra/mental-models.md +3 -3
  165. package/docs/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
  166. package/docs/guidelines/agent-infra/review-routing-data-format.md +7 -7
  167. package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +13 -7
  168. package/docs/guidelines/agent-infra/role-contracts.md +2 -2
  169. package/docs/guidelines/agent-infra/role-mode-router.md +3 -3
  170. package/docs/guidelines/agent-infra/rule-type-governance.md +2 -2
  171. package/docs/guidelines/agent-infra/scqa-framework.md +5 -5
  172. package/docs/guidelines/agent-infra/self-improvement-pipeline.md +6 -6
  173. package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +2 -2
  174. package/docs/guidelines/agent-infra/six-hats.md +3 -3
  175. package/docs/guidelines/agent-infra/skill-quality-checklist.md +6 -6
  176. package/docs/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
  177. package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
  178. package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
  179. package/docs/guidelines/augment-portability-patterns.md +6 -4
  180. package/docs/guidelines/code-clarity.md +18 -0
  181. package/docs/guidelines/component-oriented-and-oop-development.md +3 -2
  182. package/docs/guidelines/cross-role-handoff.md +2 -2
  183. package/docs/guidelines/design-fidelity-mechanics.md +1 -1
  184. package/docs/guidelines/docs/readme-size-and-splitting.md +3 -3
  185. package/docs/guidelines/php/database.md +90 -7
  186. package/docs/guidelines/prompt-templates.md +7 -7
  187. package/llms.txt +2 -2
  188. package/package.json +5 -2
  189. package/src/agent-src/scripts/archive_completed_roadmaps.ts +252 -8
  190. package/src/agent-src/scripts/guarded_baseline.ts +343 -0
  191. package/src/agent-src/scripts/roadmap_set_step.ts +253 -0
  192. package/src/agent-src/scripts/stub_queue.ts +82 -0
  193. package/src/agent-src/scripts/stubs_due.ts +516 -0
  194. package/src/agent-src/scripts/ticket_bundles.ts +84 -0
  195. package/src/agent-src/scripts/update_roadmap_progress.ts +16 -12
  196. package/src/agent-src/templates/scripts/check_memory.ts +258 -17
  197. package/src/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
  198. package/src/agent-src/templates/scripts/memory_lookup.ts +86 -0
  199. package/src/agent-src/templates/scripts/memory_signal.ts +44 -0
  200. package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
  201. package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
  202. package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
  203. package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
  204. package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
  205. package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
  206. package/src/config/agent-settings.template.yml +32 -3
  207. package/src/config/assurance-capability-registry.json +21 -9
  208. package/src/config/canonical-terms.yml +119 -0
  209. package/src/config/capability-native-outcome-bars.json +81 -0
  210. package/src/config/ci-local-parity.yml +80 -0
  211. package/src/config/drafting-channels.yml +43 -0
  212. package/src/config/estate-count-budget.json +57 -36
  213. package/src/config/evaluator-budgets.json +14 -14
  214. package/src/config/gate-coverage.yml +729 -63
  215. package/src/config/gate-reachability-exemptions.json +21 -0
  216. package/src/config/gate-violation-baselines.json +42 -19
  217. package/src/config/harness-evolution-budget.json +25 -0
  218. package/src/config/lapsed-beta-baseline.json +96 -0
  219. package/src/config/memory-twin-verdicts.yml +129 -0
  220. package/src/config/metric-registry.yml +124 -0
  221. package/src/config/pack-size-budget.json +16 -15
  222. package/src/config/preamble-payload-budget.json +12 -4
  223. package/src/config/profiles/balanced.ini +27 -0
  224. package/src/config/profiles/full.ini +27 -0
  225. package/src/config/profiles/minimal.ini +27 -0
  226. package/src/config/publish-surface.json +9 -2
  227. package/src/config/routing-coverage-seed.json +16 -2
  228. package/src/config/rule-stub-ceilings.json +133 -112
  229. package/src/config/turnaround-budget.json +66 -0
  230. package/src/install/claudeRuleRewrite.ts +288 -0
  231. package/src/install/detect.ts +60 -17
  232. package/src/install/detect_php_shape.ts +191 -0
  233. package/src/scripts/_cli/cmd_doctor.ts +27 -112
  234. package/src/scripts/_dispatch.bash +23 -0
  235. package/src/scripts/_lib/activation_ladder.ts +255 -0
  236. package/src/scripts/_lib/activation_receipt_producer.ts +341 -0
  237. package/src/scripts/_lib/activation_states.ts +88 -0
  238. package/src/scripts/_lib/adherence_detectors.ts +143 -0
  239. package/src/scripts/_lib/adr_frontmatter.ts +26 -0
  240. package/src/scripts/_lib/arith_claims.ts +181 -0
  241. package/src/scripts/_lib/arm_ranking.ts +62 -0
  242. package/src/scripts/_lib/asset_delivery_ledger.ts +303 -0
  243. package/src/scripts/_lib/audit_field_provenance.ts +54 -0
  244. package/src/scripts/_lib/auto_dispatch.ts +32 -3
  245. package/src/scripts/_lib/bench_ab_size_claim.ts +98 -4
  246. package/src/scripts/_lib/benchmark_cache_fields.ts +158 -0
  247. package/src/scripts/_lib/candidate_pair_delta.ts +349 -0
  248. package/src/scripts/_lib/candidate_proposer.ts +402 -0
  249. package/src/scripts/_lib/candidate_record.ts +601 -0
  250. package/src/scripts/_lib/capture_rate.ts +319 -0
  251. package/src/scripts/_lib/carrier_status.ts +118 -0
  252. package/src/scripts/_lib/cascade_stage_enumeration.ts +92 -0
  253. package/src/scripts/_lib/collector_denominator.ts +771 -0
  254. package/src/scripts/_lib/collector_record.ts +340 -0
  255. package/src/scripts/_lib/collector_store.ts +664 -0
  256. package/src/scripts/_lib/collector_supervision.ts +827 -0
  257. package/src/scripts/_lib/concern_estate.ts +77 -0
  258. package/src/scripts/_lib/config_chain.ts +477 -0
  259. package/src/scripts/_lib/config_cost.ts +209 -0
  260. package/src/scripts/_lib/corpus_manifest.ts +726 -0
  261. package/src/scripts/_lib/council_settings_block.ts +6 -0
  262. package/src/scripts/_lib/curator_ops.ts +172 -0
  263. package/src/scripts/_lib/delivery_arm_experiment.ts +282 -0
  264. package/src/scripts/_lib/delivery_criticality.ts +126 -0
  265. package/src/scripts/_lib/doctor_runtime_checks.ts +70 -0
  266. package/src/scripts/_lib/empty_cycles.ts +88 -0
  267. package/src/scripts/_lib/estate_carrier_cases.ts +72 -0
  268. package/src/scripts/_lib/eval_publication.ts +354 -0
  269. package/src/scripts/_lib/evaluation_cascade.ts +452 -0
  270. package/src/scripts/_lib/evaluation_vector.ts +283 -0
  271. package/src/scripts/_lib/evaluator_promotion.ts +225 -0
  272. package/src/scripts/_lib/evidence_basis.ts +118 -0
  273. package/src/scripts/_lib/evolution_roi.ts +562 -0
  274. package/src/scripts/_lib/exec_evidence.ts +8 -0
  275. package/src/scripts/_lib/experience_card.ts +272 -0
  276. package/src/scripts/_lib/experience_report.ts +151 -0
  277. package/src/scripts/_lib/experiment_freeze.ts +112 -0
  278. package/src/scripts/_lib/generated_by.ts +66 -0
  279. package/src/scripts/_lib/harness_evolution_guards.ts +239 -0
  280. package/src/scripts/_lib/hook_effect_probe.ts +141 -0
  281. package/src/scripts/_lib/host_denominator.ts +687 -0
  282. package/src/scripts/_lib/host_projection_reach.ts +157 -0
  283. package/src/scripts/_lib/ignored_blocker.ts +148 -0
  284. package/src/scripts/_lib/install_reach_checks.ts +495 -0
  285. package/src/scripts/_lib/json_duplicate_keys.ts +150 -0
  286. package/src/scripts/_lib/judge_hygiene.ts +228 -0
  287. package/src/scripts/_lib/legacy_boundary_map.ts +228 -0
  288. package/src/scripts/_lib/lexical_shortlist.ts +215 -0
  289. package/src/scripts/_lib/llm_candidate_proposer.ts +461 -0
  290. package/src/scripts/_lib/llm_proposer_transport.ts +153 -0
  291. package/src/scripts/_lib/loop_guards.ts +299 -0
  292. package/src/scripts/_lib/loss_class.ts +129 -0
  293. package/src/scripts/_lib/machine_wake.ts +51 -0
  294. package/src/scripts/_lib/md_prose_lines.ts +177 -0
  295. package/src/scripts/_lib/minimality_tiebreak.ts +168 -0
  296. package/src/scripts/_lib/one_resolver_invariant.ts +449 -0
  297. package/src/scripts/_lib/orchestration_record.ts +253 -5
  298. package/src/scripts/_lib/outcome_envelope.ts +227 -0
  299. package/src/scripts/_lib/outcome_vocabularies.ts +125 -0
  300. package/src/scripts/_lib/paired_stats.ts +245 -0
  301. package/src/scripts/_lib/paired_verdict.ts +226 -0
  302. package/src/scripts/_lib/pathology_archive.ts +393 -0
  303. package/src/scripts/_lib/prefix_stable_surfaces.ts +123 -0
  304. package/src/scripts/_lib/privacy_class.ts +47 -0
  305. package/src/scripts/_lib/promotion_capability.ts +315 -0
  306. package/src/scripts/_lib/promotion_evidence.ts +414 -0
  307. package/src/scripts/_lib/promotion_review.ts +294 -0
  308. package/src/scripts/_lib/prompt_shape.ts +92 -0
  309. package/src/scripts/_lib/regression_neighbourhood.ts +259 -0
  310. package/src/scripts/_lib/release_highlights.ts +70 -1
  311. package/src/scripts/_lib/release_material.ts +125 -2
  312. package/src/scripts/_lib/repeated_failure.ts +105 -0
  313. package/src/scripts/_lib/repo_root.ts +111 -0
  314. package/src/scripts/_lib/review_skipped_record.ts +37 -0
  315. package/src/scripts/_lib/role_split.ts +338 -0
  316. package/src/scripts/_lib/routing_corpus.ts +307 -0
  317. package/src/scripts/_lib/routing_index_input.ts +104 -0
  318. package/src/scripts/_lib/rule_injection.ts +22 -1
  319. package/src/scripts/_lib/runtime_journal.ts +1499 -0
  320. package/src/scripts/_lib/runtime_wiring_checks.ts +430 -0
  321. package/src/scripts/_lib/semantic_noop.ts +167 -0
  322. package/src/scripts/_lib/skill_catalogue.ts +11 -1
  323. package/src/scripts/_lib/source_digest.ts +200 -0
  324. package/src/scripts/_lib/source_redact.ts +260 -0
  325. package/src/scripts/_lib/source_shape.ts +502 -0
  326. package/src/scripts/_lib/source_snapshot_dedup.ts +199 -0
  327. package/src/scripts/_lib/strip_comments.d.mts +8 -0
  328. package/src/scripts/_lib/strip_comments.mjs +65 -0
  329. package/src/scripts/_lib/test_red_state.ts +154 -0
  330. package/src/scripts/_lib/trigger_shift.ts +144 -0
  331. package/src/scripts/_lib/untrusted_content.ts +82 -7
  332. package/src/scripts/activation_receipt.ts +258 -0
  333. package/src/scripts/adoption_report.ts +1 -1
  334. package/src/scripts/adr/evidence_census.ts +1 -1
  335. package/src/scripts/adr/regenerate_index.ts +26 -2
  336. package/src/scripts/adr_cite_check.ts +164 -1
  337. package/src/scripts/ai_council/blind_review.ts +11 -1
  338. package/src/scripts/ai_council/clients.ts +4 -0
  339. package/src/scripts/ai_council/config.ts +3 -0
  340. package/src/scripts/ai_council/consensus.ts +28 -8
  341. package/src/scripts/ai_council/consensus_round.ts +226 -0
  342. package/src/scripts/ai_council/cross_exam.ts +128 -0
  343. package/src/scripts/ai_council/disagreement_signal.ts +338 -0
  344. package/src/scripts/ai_council/explain_route.ts +157 -0
  345. package/src/scripts/ai_council/information_gain.ts +293 -0
  346. package/src/scripts/ai_council/inline_findings.ts +340 -0
  347. package/src/scripts/ai_council/judge_position_bias.ts +197 -0
  348. package/src/scripts/ai_council/leakage_corpus.ts +385 -0
  349. package/src/scripts/ai_council/leakage_patterns.ts +453 -0
  350. package/src/scripts/ai_council/minority_retention.ts +171 -0
  351. package/src/scripts/ai_council/orchestrator.ts +16 -203
  352. package/src/scripts/ai_council/orchestrator_results.ts +23 -3
  353. package/src/scripts/ai_council/pricing.ts +106 -0
  354. package/src/scripts/ai_council/prompts.ts +74 -7
  355. package/src/scripts/ai_council/provider_leakage_bench.ts +327 -0
  356. package/src/scripts/ai_council/py_format.ts +14 -3
  357. package/src/scripts/ai_council/recouncil_guard.ts +293 -0
  358. package/src/scripts/ai_council/recouncil_savings.ts +299 -0
  359. package/src/scripts/ai_council/replay_route.ts +223 -0
  360. package/src/scripts/ai_council/response_record.ts +50 -0
  361. package/src/scripts/ai_council/reviewer_assignment.ts +279 -0
  362. package/src/scripts/ai_council/routing_training_row.ts +197 -0
  363. package/src/scripts/ai_council/synthesis_strategy.ts +159 -0
  364. package/src/scripts/ai_council/topology_bench_manifest.ts +860 -0
  365. package/src/scripts/ai_council/topology_vocabulary.ts +175 -0
  366. package/src/scripts/asset_delivery_ledger.ts +157 -0
  367. package/src/scripts/attest_artifact.ts +193 -0
  368. package/src/scripts/audit_auto_rules.ts +2 -1
  369. package/src/scripts/audit_command_surface.ts +1 -1
  370. package/src/scripts/audit_mcp_tools.ts +2 -2
  371. package/src/scripts/audit_skill_overlap_allowlist.json +6 -1
  372. package/src/scripts/audit_user_type_axis.ts +2 -1
  373. package/src/scripts/bench_ab_clone.ts +215 -10
  374. package/src/scripts/bench_ab_integrity.ts +119 -5
  375. package/src/scripts/bench_ab_v2_stats.ts +40 -213
  376. package/src/scripts/build_archive_index.ts +1 -1
  377. package/src/scripts/build_linear_digest.ts +1 -1
  378. package/src/scripts/build_proof.ts +11 -4
  379. package/src/scripts/build_rule_trigger_matrix.ts +1 -1
  380. package/src/scripts/build_source_digests.ts +157 -0
  381. package/src/scripts/cache_realization_report.ts +103 -0
  382. package/src/scripts/check_adr_frontmatter.ts +131 -10
  383. package/src/scripts/check_benchmark_report_fields.ts +207 -0
  384. package/src/scripts/check_beta_review_markers.ts +235 -6
  385. package/src/scripts/check_bundle_path_leakage.ts +34 -1
  386. package/src/scripts/check_claims.ts +576 -6
  387. package/src/scripts/check_composite_arming.ts +35 -0
  388. package/src/scripts/check_concern_admissions.ts +365 -0
  389. package/src/scripts/check_enforcement_coverage.ts +28 -0
  390. package/src/scripts/check_estate_count.ts +347 -25
  391. package/src/scripts/check_gate_reachability.ts +383 -0
  392. package/src/scripts/check_host_loadability.ts +104 -0
  393. package/src/scripts/check_loss_class_declared.ts +308 -0
  394. package/src/scripts/check_md_language.ts +15 -98
  395. package/src/scripts/check_memory_proposal.ts +18 -11
  396. package/src/scripts/check_no_currency_in_cost_surfaces.ts +225 -0
  397. package/src/scripts/check_no_external_sources.ts +281 -6
  398. package/src/scripts/check_pack_conformance_fixture.ts +11 -1
  399. package/src/scripts/check_pack_size.ts +59 -2
  400. package/src/scripts/check_preamble_payload_budget.ts +114 -3
  401. package/src/scripts/check_prefix_stable_mutation.ts +372 -0
  402. package/src/scripts/check_publish_surface.ts +10 -1
  403. package/src/scripts/check_references.ts +24 -1
  404. package/src/scripts/check_release_highlights.ts +96 -13
  405. package/src/scripts/check_roadmap_trackable.ts +17 -2
  406. package/src/scripts/check_rule_evidence_declaration.ts +316 -0
  407. package/src/scripts/check_rule_projection_integrity.ts +14 -4
  408. package/src/scripts/check_score_contract.ts +26 -3
  409. package/src/scripts/check_static_parity.ts +285 -0
  410. package/src/scripts/check_supervision_claim_atomicity.ts +498 -0
  411. package/src/scripts/check_suppression_hygiene.ts +15 -0
  412. package/src/scripts/ci_settle.ts +39 -2
  413. package/src/scripts/code_graph/build.ts +60 -3
  414. package/src/scripts/code_graph/extract.ts +44 -0
  415. package/src/scripts/code_graph/types.ts +24 -0
  416. package/src/scripts/collector_daemon.ts +714 -0
  417. package/src/scripts/config_cost_report.ts +302 -0
  418. package/src/scripts/consumer_matrix.ts +74 -1
  419. package/src/scripts/corpus_manifest.ts +235 -0
  420. package/src/scripts/cost/budget.mjs +10 -3
  421. package/src/scripts/cost/track.mjs +10 -3
  422. package/src/scripts/council_cli.ts +37 -54
  423. package/src/scripts/dispatch_r2_reviewer.ts +46 -5
  424. package/src/scripts/evolution_lab.ts +1153 -0
  425. package/src/scripts/external_sources_denylist.json +5 -18
  426. package/src/scripts/extract_audit_patterns.ts +21 -0
  427. package/src/scripts/fold_intake.ts +12 -1
  428. package/src/scripts/gen_discovery_baseline.ts +1 -1
  429. package/src/scripts/generate_capabilities_index.ts +12 -5
  430. package/src/scripts/generate_capability_matrix.ts +3 -3
  431. package/src/scripts/generate_command_flows.ts +2 -2
  432. package/src/scripts/generate_cookbook.ts +2 -2
  433. package/src/scripts/generate_index.ts +8 -1
  434. package/src/scripts/generate_knowledge_index.ts +2 -1
  435. package/src/scripts/generate_ownership_matrix.ts +2 -1
  436. package/src/scripts/generate_pack_manifests.ts +10 -2
  437. package/src/scripts/generate_role_experiences_catalog.ts +2 -2
  438. package/src/scripts/generate_settings_reference.ts +1 -1
  439. package/src/scripts/git_authorization_hook.ts +22 -1
  440. package/src/scripts/grade_target_readiness.ts +48 -5
  441. package/src/scripts/hook_effect_doctor.ts +261 -0
  442. package/src/scripts/hook_manifest.json +1 -1
  443. package/src/scripts/hook_manifest.yaml +125 -16
  444. package/src/scripts/hooks/block_speaking_inbox_dir.ts +271 -0
  445. package/src/scripts/hooks/block_unauthorized_git.ts +18 -0
  446. package/src/scripts/hooks/comment_discipline_hook.ts +146 -0
  447. package/src/scripts/hooks/concern_registry.ts +6 -0
  448. package/src/scripts/hooks/dispatch_hook.ts +32 -0
  449. package/src/scripts/hooks/journal_record_hook.ts +345 -0
  450. package/src/scripts/hooks/prepush_metadata_sources.sh +110 -0
  451. package/src/scripts/hooks/run_continuation_hook.ts +44 -9
  452. package/src/scripts/hooks/shims/install.sh +61 -0
  453. package/src/scripts/hooks/shims/php +101 -0
  454. package/src/scripts/hooks/suggestion_capture_hook.ts +10 -0
  455. package/src/scripts/hooks/turn_end_gate_hook.ts +9 -1
  456. package/src/scripts/hot_context_hook.ts +16 -0
  457. package/src/scripts/install-hooks.sh +20 -5
  458. package/src/scripts/install.ts +17 -35
  459. package/src/scripts/inventory_frontmatter.ts +2 -1
  460. package/src/scripts/language_mirror_hook.ts +14 -2
  461. package/src/scripts/lint_adr_sweep_routing.ts +12 -0
  462. package/src/scripts/lint_canonical_terms.ts +604 -0
  463. package/src/scripts/lint_carrier_integrity.ts +820 -0
  464. package/src/scripts/lint_code_comments.ts +643 -0
  465. package/src/scripts/lint_composition_review.ts +619 -0
  466. package/src/scripts/lint_consolidation_lineage.ts +666 -0
  467. package/src/scripts/lint_eval_specs.ts +593 -0
  468. package/src/scripts/lint_hook_manifest.ts +19 -0
  469. package/src/scripts/lint_host_portability.ts +373 -0
  470. package/src/scripts/lint_judge_prompt_expectation.ts +27 -1
  471. package/src/scripts/lint_memory_twin_parity.ts +335 -0
  472. package/src/scripts/lint_metric_consumers.ts +355 -0
  473. package/src/scripts/lint_plan_risk_register.ts +39 -19
  474. package/src/scripts/lint_promotion_paths.ts +779 -0
  475. package/src/scripts/lint_skill_descriptions.ts +18 -1
  476. package/src/scripts/lint_skill_frontmatter_safety.ts +58 -0
  477. package/src/scripts/lint_skill_link_reach.ts +252 -0
  478. package/src/scripts/lint_skill_trigger_corpus.ts +426 -23
  479. package/src/scripts/lint_ticket_buildable.ts +17 -1
  480. package/src/scripts/llm_propose.ts +256 -0
  481. package/src/scripts/mcp_server/consumer_tool_catalog.json +1 -1
  482. package/src/scripts/mcp_server/tool_catalog_source.ts +1 -1
  483. package/src/scripts/measure_delivery_sets.ts +361 -0
  484. package/src/scripts/measure_docs_dead_links.ts +348 -0
  485. package/src/scripts/measure_host_capture.ts +461 -0
  486. package/src/scripts/measure_routing_signal.ts +307 -0
  487. package/src/scripts/migration_status.ts +3 -3
  488. package/src/scripts/model_rule_injection.ts +76 -0
  489. package/src/scripts/orchestration_record.ts +14 -0
  490. package/src/scripts/preamble_byte_census.ts +139 -10
  491. package/src/scripts/prepack-check.mjs +4 -1
  492. package/src/scripts/probe_council_retention.ts +495 -0
  493. package/src/scripts/probe_turnaround.ts +384 -0
  494. package/src/scripts/probe_unblocked_ask.ts +279 -0
  495. package/src/scripts/release.ts +21 -37
  496. package/src/scripts/release_drill.ts +143 -0
  497. package/src/scripts/release_env.ts +35 -0
  498. package/src/scripts/release_publication.ts +84 -0
  499. package/src/scripts/render_benchmark_md.ts +1 -1
  500. package/src/scripts/rule_activation_census.ts +70 -9
  501. package/src/scripts/rule_backlinks.ts +1 -1
  502. package/src/scripts/run_lifecycle_suite.ts +208 -0
  503. package/src/scripts/run_skill_evals.ts +40 -0
  504. package/src/scripts/schemas/delivery-manifest.schema.json +43 -0
  505. package/src/scripts/schemas/drafting-channels.schema.json +43 -0
  506. package/src/scripts/schemas/evals.schema.json +24 -1
  507. package/src/scripts/schemas/rule.schema.json +52 -1
  508. package/src/scripts/schemas/skill.schema.json +17 -2
  509. package/src/scripts/skill_overlap.ts +7 -1
  510. package/src/scripts/skill_tools/run_block_d_eval.ts +8 -1
  511. package/src/scripts/skill_tools/score_skill_relevance.ts +84 -3
  512. package/src/scripts/skill_tools/suggest_skill_for_task.ts +8 -1
  513. package/src/scripts/skill_usage_report.ts +2 -1
  514. package/src/scripts/stubs_due.ts +211 -0
  515. package/src/scripts/sweep_source_surfaces.ts +678 -0
  516. package/src/scripts/sync_pr_branch.ts +3 -3
  517. package/src/scripts/trigger_eval_grandfather.json +202 -222
  518. package/src/scripts/utilization_report.ts +22 -2
  519. package/src/server/schemas/settings.ts +5 -0
  520. package/src/templates/marketing-copy.yml +1 -1
  521. package/dist/ui/assets/index-Cxgy63kz.js +0 -26
@@ -0,0 +1,714 @@
1
+ /**
2
+ * collector_daemon — the supervised telemetry collector itself
3
+ * (`road-to-supervised-telemetry-collector` step 4.1).
4
+ *
5
+ * Built against the contracts, not beside them: the record shape is
6
+ * `collector_record`, the store and its quarantine are `collector_store`, the
7
+ * budgets, kill switch, lock and heartbeat are `collector_supervision`, and the
8
+ * denominator plus the spool are `collector_denominator`. This file adds a
9
+ * loop and a signal handler and nothing else — every rule it obeys was decided
10
+ * in Phases 1 to 3 and is imported rather than restated.
11
+ *
12
+ * ## Default-off is a property, not a config line
13
+ *
14
+ * `start()` refuses unless the opt-in marker exists, and the marker is absent on
15
+ * a fresh install. There is no setting whose default could be misread, no
16
+ * environment variable that could be set in CI by accident, and nothing in the
17
+ * install path that creates the marker. The test that matters asserts this by
18
+ * PROCESS ENUMERATION after the full suite — reading the setting back would
19
+ * only prove the setting, which is the check step 4.1 explicitly refuses.
20
+ *
21
+ * ## The five questions (`resident-process-floors` § 2)
22
+ *
23
+ * 1. **Failure mode when absent** — static mode. Dispatches resolve
24
+ * identically; the denominator keeps counting and the capture rate falls,
25
+ * which is the true statement rather than a silent gap.
26
+ * 2. **A dispatch it cannot serve** — degraded, never blocked. The hook's only
27
+ * contact with this process is an append to a spool file, and both
28
+ * denominator and spool writes return `false` instead of raising.
29
+ * 3. **State on an unclean stop** — the heartbeat goes stale (readable as
30
+ * likely-dead), the lock is fenced by the next starter, and a claimed spool
31
+ * file is left in place and re-read on the next drain. Nothing is lost by a
32
+ * `SIGKILL` except the in-flight batch, which is re-drained.
33
+ * 4. **Who supervises it, with what privileges** — a per-user service manager,
34
+ * positively probed; no administrator privilege on either supported row.
35
+ * Where none is detected the mode is `degraded` and says so in the
36
+ * heartbeat, and it is never labelled supervised.
37
+ * 5. **Uniqueness namespace** — one live collector per OS user, enforced by the
38
+ * runtime lock, so two worktrees contend and the loser runs static.
39
+ */
40
+
41
+ import fs from 'node:fs';
42
+ import path from 'node:path';
43
+
44
+ import {
45
+ claimSpool,
46
+ isCollectorEnabled,
47
+ pruneEpisodeCounters,
48
+ pruneOpportunitiesOlderThan,
49
+ readClaimedSpool,
50
+ spoolDir,
51
+ } from './_lib/collector_denominator.js';
52
+ import {
53
+ isStoreAvailable,
54
+ openCollectorStore,
55
+ pruneOlderThan,
56
+ resolveCollectorStore,
57
+ writeRecord,
58
+ type StoreHandle,
59
+ } from './_lib/collector_store.js';
60
+ import {
61
+ acquireRuntimeLock,
62
+ budgetVerdict,
63
+ detectSupervisor,
64
+ killSwitchEngaged,
65
+ livenessFromBeat,
66
+ pullKillSwitch,
67
+ readHeartbeat,
68
+ releaseHeartbeat,
69
+ releaseRuntimeLock,
70
+ terminateCollector,
71
+ writeHeartbeat,
72
+ type BudgetName,
73
+ type ResourceReading,
74
+ } from './_lib/collector_supervision.js';
75
+
76
+ // An earlier draft exported a `DAEMON_PROCESS_MARKER` constant here, claiming
77
+ // the daemon put it into its own argv and that the 4.1 test greped for it. Both
78
+ // were false — its only use was a usage string — and R2 findings 6 and 7 caught
79
+ // it. It is REMOVED rather than made true: nothing can add a token to argv after
80
+ // `exec` (a JavaScript `process.argv` is not the kernel's copy, which is what
81
+ // `ps` reads), so honouring the claim would have meant re-exec'ing the daemon
82
+ // and changing the pid every launcher and test then re-derives — to solve a
83
+ // problem `--root` already solves exactly. Process enumeration greps the module
84
+ // path, which a real daemon's argv genuinely contains, and tells a test-owned
85
+ // daemon from a developer's by `--root`. See `tests/scripts/collector_daemon.test.ts`.
86
+
87
+ export const DEFAULT_BEAT_MS = 30_000;
88
+
89
+ export type StartRefusal =
90
+ | 'not-enabled'
91
+ | 'kill-switch-engaged'
92
+ | 'lock-held'
93
+ // Carried through from `LockOutcome` rather than collapsed into
94
+ // `lock-held` (R2 round-5 finding 4). `acquireRuntimeLock` distinguishes
95
+ // these precisely so an `EACCES` or a full disk is not reported as
96
+ // contention — and `start()` was throwing that distinction away one layer
97
+ // up, routing an operator to the "Two collectors" row of the ops page for a
98
+ // permissions problem. That is the misclassification round-2 finding 11
99
+ // fixed, re-introduced by the caller.
100
+ | 'lock-unwritable'
101
+ | 'lock-contested-out'
102
+ | 'store-unavailable';
103
+
104
+ export interface StartOutcome {
105
+ readonly started: boolean;
106
+ readonly refusal: StartRefusal | null;
107
+ readonly mode: 'supervised' | 'degraded' | null;
108
+ }
109
+
110
+ /* -------------------------------------------------------------------------- */
111
+ /* Resource sampling */
112
+ /* -------------------------------------------------------------------------- */
113
+
114
+ let lastCpu = process.cpuUsage();
115
+ let lastCpuAt = Date.now();
116
+ let lastCpuPercent = 0;
117
+
118
+ /**
119
+ * The shortest window a CPU percentage is computed over.
120
+ *
121
+ * MEASURED, not chosen: back-to-back sampling produced `103 %` and then
122
+ * `36800 %` on a real run, because a few hundred microseconds of elapsed wall
123
+ * clock divides into any CPU time at all to give a nonsense ratio. The budget's
124
+ * own unit says *"% of one core, averaged over 60 s"*, and a sub-second window
125
+ * is not that average — it is noise wearing its units.
126
+ */
127
+ export const MIN_CPU_WINDOW_MS = 1_000;
128
+
129
+ /**
130
+ * Sample the four budgeted resources for THIS process.
131
+ *
132
+ * CPU is a delta between samples rather than a lifetime average: a collector
133
+ * that spun for ten minutes an hour ago and is idle now is not currently over
134
+ * budget, and a lifetime figure would keep reporting the old spike.
135
+ *
136
+ * **Below {@link MIN_CPU_WINDOW_MS} the previous percentage is carried forward
137
+ * rather than recomputed**, and the initial value is 0. That is a deliberate
138
+ * choice between three bad options: recomputing gives the 36800 % nonsense
139
+ * above; returning `NaN` is read as a breach by `budgetVerdict` and would stop
140
+ * the daemon during its own startup; carrying the last value reports a stale
141
+ * figure for at most one second. The staleness is bounded and the other two are
142
+ * wrong, so this one is documented rather than hidden.
143
+ */
144
+ export function sampleResources(userRoot?: string): ResourceReading {
145
+ const now = Date.now();
146
+ const elapsedMs = now - lastCpuAt;
147
+ if (elapsedMs >= MIN_CPU_WINDOW_MS) {
148
+ const cpu = process.cpuUsage();
149
+ const usedMicros = cpu.user - lastCpu.user + (cpu.system - lastCpu.system);
150
+ lastCpuPercent = (usedMicros / (elapsedMs * 1000)) * 100;
151
+ lastCpu = cpu;
152
+ lastCpuAt = now;
153
+ }
154
+
155
+ return Object.freeze({
156
+ cpu_percent: lastCpuPercent,
157
+ resident_bytes: process.memoryUsage().rss,
158
+ disk_bytes: directorySize(resolveCollectorStore(userRoot).root),
159
+ file_descriptors: openDescriptorCount(),
160
+ });
161
+ }
162
+
163
+ function directorySize(dir: string): number {
164
+ let total = 0;
165
+ let entries: fs.Dirent[];
166
+ try {
167
+ entries = fs.readdirSync(dir, { withFileTypes: true });
168
+ } catch {
169
+ return 0;
170
+ }
171
+ for (const entry of entries) {
172
+ const full = path.join(dir, entry.name);
173
+ if (entry.isDirectory()) {
174
+ total += directorySize(full);
175
+ continue;
176
+ }
177
+ try {
178
+ total += fs.statSync(full).size;
179
+ } catch {
180
+ /* raced with a rename; the next sample sees it */
181
+ }
182
+ }
183
+ return total;
184
+ }
185
+
186
+ /**
187
+ * Count open descriptors without shelling out.
188
+ *
189
+ * `/proc/self/fd` on Linux, `/dev/fd` on macOS. Where neither is readable the
190
+ * answer is `NaN`, which {@link budgetVerdict} treats as a BREACH rather than a
191
+ * pass — an unmeasurable resource is not a satisfied budget, which is the R-A12
192
+ * rule this follows.
193
+ */
194
+ export function openDescriptorCount(): number {
195
+ for (const dir of ['/proc/self/fd', '/dev/fd']) {
196
+ try {
197
+ return fs.readdirSync(dir).length;
198
+ } catch {
199
+ /* try the next */
200
+ }
201
+ }
202
+ return Number.NaN;
203
+ }
204
+
205
+ /* -------------------------------------------------------------------------- */
206
+ /* Draining */
207
+ /* -------------------------------------------------------------------------- */
208
+
209
+ export interface DrainResult {
210
+ readonly claimed: number;
211
+ readonly written: number;
212
+ readonly refused: number;
213
+ readonly malformed: number;
214
+ /** Claimed files that could not be READ. Kept on disk for the next drain, never deleted. */
215
+ readonly unreadable: number;
216
+ }
217
+
218
+ /**
219
+ * Drain every claimed spool file into the store.
220
+ *
221
+ * Re-drains files left claimed by a previous unclean stop, which is answer 3 to
222
+ * the daemon checklist: the batch in flight when the process died is not lost,
223
+ * it is on disk under its claimed name and the next drain picks it up. A file
224
+ * is deleted only after every one of its records has been offered to the store,
225
+ * so a crash mid-batch re-offers the whole file.
226
+ *
227
+ * **That re-offer inserts duplicate ROWS, and it is safe for a stated reason
228
+ * rather than by accident.** `dedup_key` is deliberately NOT unique — the store
229
+ * is append-only and collapses duplicates at READ time (`readRecords`), while
230
+ * `readSummary` reports `rows` and distinct `dedup_key` separately so a
231
+ * duplicate is observable rather than invisible. Claiming insert-time
232
+ * idempotency here would have been the easier sentence and a false one.
233
+ */
234
+ export function drainOnce(handle: StoreHandle, userRoot?: string): DrainResult {
235
+ claimSpool(userRoot);
236
+ let claimedFiles: string[] = [];
237
+ try {
238
+ claimedFiles = fs
239
+ .readdirSync(spoolDir(userRoot))
240
+ .filter((name) => name.startsWith('draining-'))
241
+ .map((name) => path.join(spoolDir(userRoot), name));
242
+ } catch {
243
+ return Object.freeze({ claimed: 0, written: 0, refused: 0, malformed: 0, unreadable: 0 });
244
+ }
245
+
246
+ let written = 0;
247
+ let refused = 0;
248
+ let malformed = 0;
249
+ let unreadable = 0;
250
+ for (const file of claimedFiles) {
251
+ const parsed = readClaimedSpool(file);
252
+ if (parsed.unreadable) {
253
+ // KEEP IT (R2 round-3 finding 2). A file we could not read has not
254
+ // had its records offered to the store, and deleting it here
255
+ // destroyed the batch while reporting zero malformed — a transient
256
+ // EACCES or EMFILE was enough. The next drain re-reads it, which is
257
+ // the same recovery the crash path relies on.
258
+ unreadable += 1;
259
+ continue;
260
+ }
261
+ malformed += parsed.malformed;
262
+ for (const record of parsed.records) {
263
+ const outcome = writeRecord(handle, record);
264
+ if (outcome.written) written += 1;
265
+ else refused += 1;
266
+ }
267
+ fs.rmSync(file, { force: true });
268
+ }
269
+ return Object.freeze({
270
+ claimed: claimedFiles.length,
271
+ written,
272
+ refused,
273
+ malformed,
274
+ unreadable,
275
+ });
276
+ }
277
+
278
+ /* -------------------------------------------------------------------------- */
279
+ /* Lifecycle */
280
+ /* -------------------------------------------------------------------------- */
281
+
282
+ export interface RunOptions {
283
+ // `| undefined` is required, not decoration: `exactOptionalPropertyTypes` is
284
+ // on, so an optional property does NOT implicitly accept `undefined`, and
285
+ // `main` threads a `--root` that may legitimately be absent.
286
+ readonly userRoot?: string | undefined;
287
+ readonly beatMs?: number;
288
+ /** Stop after this many iterations. Tests use it; the real daemon does not pass it. */
289
+ readonly maxIterations?: number;
290
+ /** Injected for tests. */
291
+ readonly sample?: (userRoot?: string) => ResourceReading;
292
+ }
293
+
294
+ export interface RunSummary {
295
+ readonly iterations: number;
296
+ readonly drained: DrainResult;
297
+ readonly stoppedBy: 'signal' | 'kill-switch' | 'budget' | 'iterations';
298
+ readonly exceeded: readonly BudgetName[];
299
+ }
300
+
301
+ let stopRequested = false;
302
+
303
+ /**
304
+ * What `start()` learned, kept for the loop.
305
+ *
306
+ * The heartbeat's `mode` is the SUPERVISOR PROBE's answer, and it must survive a
307
+ * beat in which the heartbeat file is missing (R2 round-4 finding 8). Module
308
+ * state rather than a parameter because `runLoop` is called both by `main` and
309
+ * directly by tests, and a required parameter would make every test assert a
310
+ * fact it does not care about.
311
+ */
312
+ let startedMode: 'supervised' | 'degraded' = 'degraded';
313
+ let startedAt = Date.now();
314
+
315
+ /**
316
+ * Ask the loop to finish and exit. Idempotent.
317
+ *
318
+ * Wakes an in-flight {@link sleep} rather than only setting the flag: a daemon
319
+ * that acknowledges `SIGTERM` and then waits out a 30-second beat before acting
320
+ * on it has not honoured the signal in any sense an operator or a supervisor
321
+ * would recognise.
322
+ */
323
+ export function requestStop(): void {
324
+ stopRequested = true;
325
+ wake?.();
326
+ }
327
+
328
+ /**
329
+ * Refuse or accept the start, and take the lock when accepting.
330
+ *
331
+ * Order is deliberate and each step is cheaper than the next: the opt-in marker
332
+ * (a `stat`), the kill switch (a `stat`), store availability (a runtime probe),
333
+ * then the lock (a write). Probing availability before taking the lock keeps a
334
+ * runtime without `node:sqlite` from holding a lock it can never use.
335
+ */
336
+ export function start(userRoot?: string): StartOutcome {
337
+ // The KILL SWITCH is checked first, and the order is not cosmetic: since
338
+ // R2 round-5 finding 1 the switch also closes `isCollectorEnabled`, so
339
+ // checking enablement first would report every stopped collector as
340
+ // `not-enabled` and send an operator looking for a missing marker instead
341
+ // of the STOP file they created.
342
+ if (killSwitchEngaged(userRoot)) {
343
+ return Object.freeze({
344
+ started: false,
345
+ refusal: 'kill-switch-engaged' as const,
346
+ mode: null,
347
+ });
348
+ }
349
+ if (!isCollectorEnabled(userRoot)) {
350
+ return Object.freeze({ started: false, refusal: 'not-enabled' as const, mode: null });
351
+ }
352
+ if (!isStoreAvailable()) {
353
+ return Object.freeze({
354
+ started: false,
355
+ refusal: 'store-unavailable' as const,
356
+ mode: null,
357
+ });
358
+ }
359
+ const lock = acquireRuntimeLock(userRoot);
360
+ if (!lock.acquired) {
361
+ const refusal: StartRefusal =
362
+ lock.reason === 'unwritable'
363
+ ? 'lock-unwritable'
364
+ : lock.reason === 'contested-out'
365
+ ? 'lock-contested-out'
366
+ : lock.reason === 'kill-switch-engaged'
367
+ ? 'kill-switch-engaged'
368
+ : 'lock-held';
369
+ if (lock.errno !== null) {
370
+ process.stderr.write(`collector: lock unwritable (${lock.errno})\n`);
371
+ }
372
+ return Object.freeze({ started: false, refusal, mode: null });
373
+ }
374
+ const probe = detectSupervisor();
375
+ const mode = probe.tier === 'supported' ? ('supervised' as const) : ('degraded' as const);
376
+ const now = Date.now();
377
+ startedMode = mode;
378
+ startedAt = now;
379
+ writeHeartbeat({ pid: process.pid, started_at: now, last_heartbeat: now, mode }, userRoot);
380
+ stopRequested = false;
381
+ return Object.freeze({ started: true, refusal: null, mode });
382
+ }
383
+
384
+ /**
385
+ * Release the lock and remove the heartbeat — ONLY the ones this process owns.
386
+ *
387
+ * Both helpers are ownership-checked (R2 finding 10). The unconditional
388
+ * `rmSync` pair this replaces meant a FENCED predecessor tearing down through
389
+ * `runLoop`'s `finally` deleted the successor's lock and heartbeat: the next
390
+ * starter then also acquired, and `livenessFromBeat` reported `absent` for a
391
+ * process that was running.
392
+ *
393
+ * Returns what it actually removed, so a caller can tell a clean release from a
394
+ * no-op after a fence.
395
+ */
396
+ export function stop(userRoot?: string): { lock: boolean; heartbeat: boolean } {
397
+ return {
398
+ lock: releaseRuntimeLock(userRoot),
399
+ heartbeat: releaseHeartbeat(userRoot),
400
+ };
401
+ }
402
+
403
+ /**
404
+ * The loop. Beat, drain, sample, decide.
405
+ *
406
+ * ## Why this is `async`, and why the synchronous version was a real bug
407
+ *
408
+ * R2 round-2 finding 1. The first version slept with `Atomics.wait`, which
409
+ * blocks the main thread — and a Node signal handler is a JS callback on the
410
+ * event loop, so `SIGTERM` could not be observed until the sleep returned. Stop
411
+ * latency was therefore up to a full beat, the shipped default beat is 30 s and
412
+ * `terminateCollector`'s grace is 5 s, so **in the shipped configuration the
413
+ * graceful half of the kill switch could never win**: every stop escalated to
414
+ * `SIGKILL`, leaving the lock and heartbeat residue of an unclean death and an
415
+ * unclosed SQLite handle, every time.
416
+ *
417
+ * PROPERTY 2 passed anyway, and that is the part worth remembering: the
418
+ * lifecycle suite spawns with `--beat-ms 50`, the one configuration in which
419
+ * the graceful path is reachable. The suite certified a beat production never
420
+ * uses.
421
+ *
422
+ * `await setTimeout` yields to the event loop, so a handler runs while the
423
+ * daemon waits. Nothing else about the loop changed.
424
+ *
425
+ * A budget breach stops the collector on the SECOND consecutive reading, not
426
+ * the first. One sample is not a breach: the first CPU sample after start
427
+ * measures a window of a few milliseconds that includes the store open, and
428
+ * stopping on it would make the daemon unable to start under its own budget.
429
+ * Two consecutive readings over the ceiling is a load; one is a sample.
430
+ */
431
+ export async function runLoop(options: RunOptions = {}): Promise<RunSummary> {
432
+ const userRoot = options.userRoot;
433
+ const beatMs = options.beatMs ?? DEFAULT_BEAT_MS;
434
+ const sample = options.sample ?? sampleResources;
435
+
436
+ let iterations = 0;
437
+ let consecutiveBreaches = 0;
438
+ let exceeded: readonly BudgetName[] = [];
439
+ let drained: DrainResult = Object.freeze({
440
+ claimed: 0,
441
+ written: 0,
442
+ refused: 0,
443
+ malformed: 0,
444
+ unreadable: 0,
445
+ });
446
+ let stoppedBy: RunSummary['stoppedBy'] = 'iterations';
447
+
448
+ // INSIDE the try (R2 round-2 finding 13): opening the store can throw on a
449
+ // corrupt or unreadable file, and `isStoreAvailable()` in `start()` probes
450
+ // the runtime rather than this file. Outside the try that throw skipped the
451
+ // `finally` and left behind the lock and heartbeat `start()` had already
452
+ // taken — so a supervisor restart loop met its own stale lock on every
453
+ // attempt until the fencing path cleared it.
454
+ let handle: StoreHandle | null = null;
455
+ try {
456
+ handle = openCollectorStore(userRoot);
457
+ for (;;) {
458
+ iterations += 1;
459
+
460
+ if (stopRequested) {
461
+ stoppedBy = 'signal';
462
+ break;
463
+ }
464
+ if (killSwitchEngaged(userRoot)) {
465
+ stoppedBy = 'kill-switch';
466
+ break;
467
+ }
468
+
469
+ const beat = readHeartbeat(userRoot);
470
+ const now = Date.now();
471
+ writeHeartbeat(
472
+ {
473
+ pid: process.pid,
474
+ started_at: beat?.started_at ?? startedAt,
475
+ last_heartbeat: now,
476
+ // The mode `start()` computed from `detectSupervisor()`, not
477
+ // one re-derived from the beat we just read (R2 round-4
478
+ // finding 8). A heartbeat absent for one beat — an operator,
479
+ // a cleanup, a race — silently re-labelled a genuinely
480
+ // SUPERVISED daemon as `degraded` for the rest of its life,
481
+ // and nothing restored it. The probe result was available at
482
+ // start and was being discarded.
483
+ mode: startedMode,
484
+ },
485
+ userRoot,
486
+ );
487
+
488
+ const result = drainOnce(handle, userRoot);
489
+ drained = Object.freeze({
490
+ claimed: drained.claimed + result.claimed,
491
+ written: drained.written + result.written,
492
+ refused: drained.refused + result.refused,
493
+ malformed: drained.malformed + result.malformed,
494
+ unreadable: drained.unreadable + result.unreadable,
495
+ });
496
+ const today = new Date(now).toISOString().slice(0, 10);
497
+ pruneOlderThan(handle, today);
498
+ // BOTH sides age on the same clock (R2 round-2 finding 5). Pruning
499
+ // only the store made the ratio fall toward zero as the numerator
500
+ // aged out against a lifetime denominator, and left an unbounded
501
+ // file inside the directory the disk budget ceilings.
502
+ pruneOpportunitiesOlderThan(userRoot, today);
503
+ pruneEpisodeCounters(userRoot, now);
504
+
505
+ const verdict = budgetVerdict(sample(userRoot));
506
+ if (verdict.action === 'stop') {
507
+ consecutiveBreaches += 1;
508
+ exceeded = verdict.exceeded;
509
+ if (consecutiveBreaches >= 2) {
510
+ stoppedBy = 'budget';
511
+ break;
512
+ }
513
+ } else {
514
+ consecutiveBreaches = 0;
515
+ exceeded = [];
516
+ }
517
+
518
+ if (options.maxIterations !== undefined && iterations >= options.maxIterations) {
519
+ stoppedBy = 'iterations';
520
+ break;
521
+ }
522
+ await sleep(beatMs);
523
+ }
524
+ } finally {
525
+ // RELEASE FIRST (R2 round-3 finding 12). `db.close()` can throw on an
526
+ // already-invalidated handle, and running it first meant the lock and
527
+ // heartbeat `start()` took stayed on disk — the orphaned-residue case
528
+ // that moving `openCollectorStore` into the `try` was supposed to close,
529
+ // still reachable through the cleanup path itself.
530
+ stop(userRoot);
531
+ try {
532
+ handle?.db.close();
533
+ } catch {
534
+ /* an unclosable handle is this process's problem, not its successor's */
535
+ }
536
+ }
537
+
538
+ return Object.freeze({ iterations, drained, stoppedBy, exceeded });
539
+ }
540
+
541
+ /**
542
+ * Wakes the current {@link sleep} early. Set while the loop is waiting.
543
+ *
544
+ * Yielding to the event loop is necessary and NOT sufficient, which cost a
545
+ * measured round: with `await setTimeout` the `SIGTERM` handler ran promptly —
546
+ * and the loop still finished the full 30 s beat before it looked at
547
+ * `stopRequested`, so the observed stop latency was 29,995 ms against a 5 s
548
+ * grace. The handler has to be able to END the wait, not merely run during it.
549
+ */
550
+ let wake: (() => void) | null = null;
551
+
552
+ /**
553
+ * Wait up to `ms`, or until {@link requestStop} wakes us — whichever is first.
554
+ *
555
+ * Two properties, and the loop needs both (R2 round-2 finding 1). It yields, so
556
+ * signal handlers run at all; and it is interruptible, so a handler's effect is
557
+ * immediate rather than deferred to the end of the beat.
558
+ */
559
+ function sleep(ms: number): Promise<void> {
560
+ return new Promise((resolve) => {
561
+ const timer = setTimeout(() => {
562
+ wake = null;
563
+ resolve();
564
+ }, ms);
565
+ wake = () => {
566
+ clearTimeout(timer);
567
+ wake = null;
568
+ resolve();
569
+ };
570
+ });
571
+ }
572
+
573
+ /* -------------------------------------------------------------------------- */
574
+ /* Entry point */
575
+ /* -------------------------------------------------------------------------- */
576
+
577
+ export async function main(argv: readonly string[] = process.argv.slice(2)): Promise<number> {
578
+ const command = argv[0] ?? 'status';
579
+
580
+ // `--root` exists for the process-level lifecycle suite (step 5.1), which
581
+ // has to spawn REAL daemons and cannot be allowed to touch the developer's
582
+ // own `~/.event4u/agent-config`. It is not a production knob: the supervisor
583
+ // units never pass it, and omitting it resolves the real user root exactly
584
+ // as before.
585
+ //
586
+ // A TRAILING `--root` with no value is an ERROR, not a default (R2 finding
587
+ // 16). `argv[i + 1]` is `undefined` there, and `undefined` is precisely the
588
+ // value that falls back to the real user root — so a malformed invocation
589
+ // would do the one thing this flag was added to prevent.
590
+ // A flag's value must exist AND not be another flag (R2 round-2 finding 12).
591
+ // `--root --beat-ms 5` used to pass the presence check and silently take the
592
+ // literal string `--beat-ms` as the user root — which is the failure `--root`
593
+ // was added to prevent, arriving through a typo instead of an omission.
594
+ const flagValue = (name: string): string | null | undefined => {
595
+ const at = argv.indexOf(name);
596
+ if (at < 0) return undefined;
597
+ const value = argv[at + 1];
598
+ if (value === undefined || value.startsWith('--')) return null;
599
+ return value;
600
+ };
601
+
602
+ const rootValue = flagValue('--root');
603
+ if (rootValue === null) {
604
+ process.stderr.write('collector: --root needs a directory argument\n');
605
+ return 2;
606
+ }
607
+ const userRoot = rootValue;
608
+
609
+ const beatValue = flagValue('--beat-ms');
610
+ if (beatValue === null) {
611
+ process.stderr.write('collector: --beat-ms needs a number\n');
612
+ return 2;
613
+ }
614
+ const beatMs = beatValue === undefined ? undefined : Number.parseInt(beatValue, 10);
615
+ // Positive, not merely finite: 0 and a negative both turned the wait into a
616
+ // no-op, i.e. a busy loop that budget-stops the collector after two
617
+ // iterations — a spin dressed as a configuration.
618
+ if (beatMs !== undefined && (!Number.isFinite(beatMs) || beatMs <= 0)) {
619
+ process.stderr.write('collector: --beat-ms must be a positive number of milliseconds\n');
620
+ return 2;
621
+ }
622
+
623
+ if (command === 'status') {
624
+ const beat = readHeartbeat(userRoot);
625
+ const liveness = livenessFromBeat(beat);
626
+ const enabled = isCollectorEnabled(userRoot);
627
+ process.stdout.write(
628
+ `collector: ${enabled ? 'enabled' : 'default-off'} · ${liveness}`
629
+ + (beat === null ? '' : ` (pid ${beat.pid}, ${beat.mode})`)
630
+ + `\n`,
631
+ );
632
+ return 0;
633
+ }
634
+
635
+ // The operator-facing half of the kill switch (R2 finding 3). Before this
636
+ // existed, `terminateCollector` had no production caller at all and
637
+ // `docs/contracts/collector-operations.md` told an operator under pressure
638
+ // that there was nothing running to stop — while this very file had just
639
+ // shipped a daemon that runs.
640
+ if (command === 'stop') {
641
+ const engaged = argv.includes('--no-latch') ? false : true;
642
+ if (engaged) pullKillSwitch(userRoot);
643
+ const outcome = terminateCollector(userRoot, {
644
+ signalStale: argv.includes('--signal-stale'),
645
+ });
646
+ process.stdout.write(
647
+ `collector: ${outcome.stopped ? 'stopped' : 'NOT stopped'} (${outcome.via}`
648
+ + (outcome.pid === null ? '' : `, pid ${outcome.pid}`)
649
+ + `)${engaged ? ' · kill switch engaged — delete STOP to allow a restart' : ''}\n`,
650
+ );
651
+ if (outcome.via === 'stale-refused') {
652
+ process.stderr.write(
653
+ 'collector: the heartbeat is stale, so its pid may have been recycled — '
654
+ + 'refused rather than signalling a stranger. Re-run with --signal-stale '
655
+ + 'if you know that pid is still the collector.\n',
656
+ );
657
+ }
658
+ return outcome.stopped ? 0 : 1;
659
+ }
660
+
661
+ if (command === 'run') {
662
+ const outcome = start(userRoot);
663
+ if (!outcome.started) {
664
+ process.stderr.write(`collector: not started — ${outcome.refusal}\n`);
665
+ // Refusing to start is the designed behaviour on a default-off
666
+ // install, so it is exit 0. A non-zero here would make every
667
+ // supervisor on a fresh machine report a failing unit.
668
+ return 0;
669
+ }
670
+ process.on('SIGTERM', requestStop);
671
+ process.on('SIGINT', requestStop);
672
+ // Announce readiness AFTER the lock and heartbeat exist. A test that
673
+ // signals before this line is racing the startup it is trying to test —
674
+ // the same race the 3.3 wedged-process test learned the hard way.
675
+ process.stdout.write(
676
+ `collector: running pid=${process.pid} mode=${outcome.mode}\n`,
677
+ );
678
+ // WRAPPED (R2 round-2 finding 13): an uncaught throw out of the loop
679
+ // used to kill the process with `start()`'s lock and heartbeat still on
680
+ // disk. `runLoop`'s own `finally` releases them, but only for throws it
681
+ // can see; this catches the rest so a supervisor restart does not meet
682
+ // a stale lock every attempt.
683
+ let summary: RunSummary;
684
+ try {
685
+ summary = await runLoop(
686
+ beatMs !== undefined ? { userRoot, beatMs } : { userRoot },
687
+ );
688
+ } catch (err) {
689
+ stop(userRoot);
690
+ process.stderr.write(`collector: loop failed — ${(err as Error).message}\n`);
691
+ return 1;
692
+ }
693
+ process.stderr.write(
694
+ `collector: stopped by ${summary.stoppedBy}`
695
+ + (summary.exceeded.length > 0 ? ` (${summary.exceeded.join(', ')})` : '')
696
+ + `\n`,
697
+ );
698
+ return 0;
699
+ }
700
+
701
+ process.stderr.write(
702
+ 'usage: collector_daemon [status|run|stop] [--root <dir>] [--beat-ms <n>]'
703
+ + ' [--no-latch] [--signal-stale]\n',
704
+ );
705
+ return 2;
706
+ }
707
+
708
+ const invokedDirectly =
709
+ process.argv[1] !== undefined && process.argv[1].includes('collector_daemon');
710
+ if (invokedDirectly) {
711
+ void main().then((code) => {
712
+ process.exitCode = code;
713
+ });
714
+ }