session-orchestrator 3.24.0 → 4.0.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 (350) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +1 -1
  47. package/.cursor/commands/autopilot.md +2 -2
  48. package/.cursor/commands/bootstrap.md +1 -1
  49. package/.cursor/commands/brainstorm.md +1 -1
  50. package/.cursor/commands/debug.md +1 -1
  51. package/.cursor/commands/discovery.md +1 -1
  52. package/.cursor/commands/dispatcher.md +2 -2
  53. package/.cursor/commands/eli5.md +2 -2
  54. package/.cursor/commands/eval.md +2 -2
  55. package/.cursor/commands/evolve.md +1 -1
  56. package/.cursor/commands/go.md +1 -1
  57. package/.cursor/commands/grill.md +2 -2
  58. package/.cursor/commands/memory-cleanup.md +2 -2
  59. package/.cursor/commands/persona-panel.md +1 -1
  60. package/.cursor/commands/plan.md +1 -1
  61. package/.cursor/commands/portfolio.md +1 -1
  62. package/.cursor/commands/reconcile.md +2 -2
  63. package/.cursor/commands/release.md +2 -2
  64. package/.cursor/commands/session.md +2 -2
  65. package/.cursor/commands/spinout.md +2 -2
  66. package/.cursor/commands/sunset-review.md +2 -2
  67. package/.cursor/commands/templates-ack.md +2 -2
  68. package/.cursor/commands/test.md +2 -2
  69. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  70. package/.cursor/skills/eval/SKILL.md +1 -1
  71. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  72. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  73. package/.orchestrator/policy/blocked-commands.json +121 -0
  74. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  75. package/.orchestrator/policy/quality-gates.example.json +16 -0
  76. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  77. package/.orchestrator/policy/templates-policy.json +27 -0
  78. package/.orchestrator/policy/test-profiles.json +47 -0
  79. package/AGENTS.md +225 -0
  80. package/CHANGELOG.md +1125 -2
  81. package/NOTICE +11 -6
  82. package/README.md +127 -94
  83. package/agents/eval-judge.md +1 -1
  84. package/agents/skill-applied-judge.md +1 -1
  85. package/assets/wave-lifecycle.svg +98 -0
  86. package/commands/release.md +6 -3
  87. package/commands/session.md +18 -3
  88. package/docs/README.md +4 -0
  89. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  90. package/docs/baseline.md +67 -0
  91. package/docs/ci-setup.md +108 -62
  92. package/docs/codex-setup.md +65 -21
  93. package/docs/components.md +36 -15
  94. package/docs/cursor-setup.md +6 -2
  95. package/docs/events-schema.md +9 -6
  96. package/docs/instruction-delivery.md +62 -0
  97. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  98. package/docs/migration-v4.md +341 -0
  99. package/docs/pi-setup.md +6 -1
  100. package/docs/plugin-architecture-v3.md +1 -1
  101. package/docs/rule-authoring.md +85 -19
  102. package/docs/scope-collision-guard.md +5 -5
  103. package/docs/session-config-reference.md +57 -56
  104. package/docs/session-config-template.md +6 -29
  105. package/docs/telemetry.md +157 -3
  106. package/docs/vault-docs-architecture.md +50 -11
  107. package/hooks/_lib/hook-import-set.json +1487 -0
  108. package/hooks/_lib/subagent-transcript.mjs +562 -0
  109. package/hooks/config-protection.mjs +2 -2
  110. package/hooks/cwd-change-restore.mjs +2 -2
  111. package/hooks/enforce-commands.mjs +69 -0
  112. package/hooks/hooks-codex.json +1 -1
  113. package/hooks/hooks-cursor.json +10 -0
  114. package/hooks/hooks-pi.json +5 -0
  115. package/hooks/hooks.json +6 -1
  116. package/hooks/loop-guard.mjs +3 -3
  117. package/hooks/on-session-end.mjs +2 -2
  118. package/hooks/on-session-start.mjs +103 -2
  119. package/hooks/on-stop.mjs +36 -11
  120. package/hooks/operator-steer.mjs +2 -2
  121. package/hooks/post-bash-write-verify.mjs +85 -0
  122. package/hooks/post-edit-import-probe.mjs +344 -0
  123. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  124. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  125. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  126. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  127. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  128. package/hooks/skill-invocation-telemetry.mjs +17 -5
  129. package/hooks/subagent-telemetry.mjs +13 -4
  130. package/monitors/monitors.json +3 -3
  131. package/package.json +9 -1
  132. package/pi/prompts/session.md +2 -2
  133. package/plugin.json +27 -0
  134. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  135. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  136. package/scripts/dialectic-deriver.mjs +73 -8
  137. package/scripts/export-hw-learnings.mjs +113 -1
  138. package/scripts/generate-agents-skills.mjs +378 -0
  139. package/scripts/generate-cursor-adapter.mjs +45 -8
  140. package/scripts/generate-hook-import-set.mjs +249 -0
  141. package/scripts/lib/agent-status.mjs +13 -2
  142. package/scripts/lib/auto-dream.mjs +38 -36
  143. package/scripts/lib/autonomy/suitability.mjs +6 -0
  144. package/scripts/lib/autopilot/loop.mjs +2 -2
  145. package/scripts/lib/ci-status-banner.mjs +220 -75
  146. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  147. package/scripts/lib/config/auto-dream.mjs +2 -1
  148. package/scripts/lib/config/block-header.mjs +8 -0
  149. package/scripts/lib/config/block-preprocess.mjs +177 -0
  150. package/scripts/lib/config/broken-window.mjs +2 -1
  151. package/scripts/lib/config/cold-start.mjs +2 -1
  152. package/scripts/lib/config/config-protection.mjs +22 -2
  153. package/scripts/lib/config/context-coverage.mjs +2 -1
  154. package/scripts/lib/config/cross-repo.mjs +2 -1
  155. package/scripts/lib/config/custom-phases.mjs +2 -1
  156. package/scripts/lib/config/dialectic.mjs +2 -1
  157. package/scripts/lib/config/discovery-validator.mjs +2 -1
  158. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  159. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  160. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  161. package/scripts/lib/config/docs-staleness.mjs +2 -1
  162. package/scripts/lib/config/drift-check.mjs +2 -1
  163. package/scripts/lib/config/eval.mjs +2 -1
  164. package/scripts/lib/config/events-rotation.mjs +2 -1
  165. package/scripts/lib/config/evolve.mjs +8 -2
  166. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  167. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  168. package/scripts/lib/config/handover-gate.mjs +2 -1
  169. package/scripts/lib/config/health-endpoints.mjs +7 -2
  170. package/scripts/lib/config/issue-budget.mjs +2 -1
  171. package/scripts/lib/config/loop-guard.mjs +2 -1
  172. package/scripts/lib/config/memory.mjs +2 -1
  173. package/scripts/lib/config/moc-staleness.mjs +2 -1
  174. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  175. package/scripts/lib/config/private-config-dir.mjs +67 -0
  176. package/scripts/lib/config/reconcile.mjs +2 -1
  177. package/scripts/lib/config/remote-hosts.mjs +2 -1
  178. package/scripts/lib/config/section-extractor.mjs +7 -1
  179. package/scripts/lib/config/skill-evolution.mjs +2 -1
  180. package/scripts/lib/config/slopcheck.mjs +2 -1
  181. package/scripts/lib/config/state-md-lock.mjs +2 -1
  182. package/scripts/lib/config/templates-first.mjs +2 -1
  183. package/scripts/lib/config/test.mjs +2 -1
  184. package/scripts/lib/config/vault-integration.mjs +7 -1
  185. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  186. package/scripts/lib/config/vault-staleness.mjs +2 -1
  187. package/scripts/lib/config/vault-sync.mjs +2 -1
  188. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  189. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  190. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  191. package/scripts/lib/convergence-monitor.mjs +82 -16
  192. package/scripts/lib/dispatcher/rank.mjs +124 -48
  193. package/scripts/lib/ecosystem-health.mjs +16 -2
  194. package/scripts/lib/eval/engine.mjs +9 -1
  195. package/scripts/lib/eval/session-resolve.mjs +23 -4
  196. package/scripts/lib/events.mjs +22 -6
  197. package/scripts/lib/frontmatter-guard.mjs +131 -13
  198. package/scripts/lib/gates/gate-full.mjs +26 -0
  199. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  200. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  201. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  202. package/scripts/lib/host-identity.mjs +50 -11
  203. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  204. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  205. package/scripts/lib/learnings/io.mjs +60 -6
  206. package/scripts/lib/memory-proposals/store.mjs +30 -22
  207. package/scripts/lib/owner-config-banner.mjs +43 -6
  208. package/scripts/lib/owner-config-loader.mjs +21 -10
  209. package/scripts/lib/owner-interview.mjs +3 -3
  210. package/scripts/lib/owner-yaml.mjs +207 -14
  211. package/scripts/lib/platform.mjs +108 -15
  212. package/scripts/lib/plugin-update-banner.mjs +406 -0
  213. package/scripts/lib/project-hygiene.mjs +38 -2
  214. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  215. package/scripts/lib/quality-gate.mjs +133 -44
  216. package/scripts/lib/reconcile/emitter.mjs +68 -6
  217. package/scripts/lib/reconcile/engine.mjs +13 -4
  218. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  219. package/scripts/lib/reconcile/writer.mjs +40 -18
  220. package/scripts/lib/session-close-backfill.mjs +67 -9
  221. package/scripts/lib/session-id.mjs +12 -23
  222. package/scripts/lib/session-identity/own-session.mjs +125 -10
  223. package/scripts/lib/session-lock-shape.mjs +43 -0
  224. package/scripts/lib/session-lock.mjs +5 -10
  225. package/scripts/lib/session-registry.mjs +25 -9
  226. package/scripts/lib/session-schema/constants.mjs +36 -2
  227. package/scripts/lib/session-schema/validator.mjs +38 -4
  228. package/scripts/lib/session-start-probes.mjs +18 -1
  229. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  230. package/scripts/lib/skill-health/join.mjs +17 -4
  231. package/scripts/lib/state-md.mjs +78 -0
  232. package/scripts/lib/sunset/walker.mjs +6 -0
  233. package/scripts/lib/telemetry/schema.mjs +181 -9
  234. package/scripts/lib/telemetry/sync.mjs +368 -12
  235. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  236. package/scripts/lib/validate/check-agents.mjs +3 -3
  237. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  238. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  239. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  240. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  241. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  242. package/scripts/lib/validate/check-unwired-features.mjs +0 -2
  243. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  244. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  245. package/scripts/lib/vault-backfill/template.mjs +63 -6
  246. package/scripts/lib/vault-mirror/process.mjs +165 -42
  247. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  248. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  249. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  250. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  251. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  252. package/scripts/lib/wave-resource-gate.mjs +8 -2
  253. package/scripts/lib/wave-sizing.mjs +4 -1
  254. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  255. package/scripts/materialize-wave-scope.mjs +12 -5
  256. package/scripts/memory-propose.mjs +19 -5
  257. package/scripts/migrate-cold-start-seed.mjs +4 -1
  258. package/scripts/parse-config.mjs +60 -3
  259. package/scripts/release.mjs +337 -29
  260. package/scripts/repair-invalid-sessions.mjs +3 -3
  261. package/scripts/run-quality-gate.mjs +128 -11
  262. package/scripts/sweep-expired-learnings.mjs +90 -0
  263. package/scripts/sync-vault-schema.mjs +3 -1
  264. package/scripts/telemetry.mjs +2 -2
  265. package/scripts/validate-plugin.mjs +161 -0
  266. package/scripts/validate-wave-scope.mjs +28 -8
  267. package/scripts/wave-scope-binding.mjs +215 -0
  268. package/skills/_shared/instruction-file-resolution.md +10 -0
  269. package/skills/_shared/parallel-aware-preamble.md +1 -0
  270. package/skills/_shared/platform-tools.md +1 -1
  271. package/skills/_shared/state-ownership.md +1 -1
  272. package/skills/architecture/SKILL.md +7 -5
  273. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  274. package/skills/autopilot/SKILL.md +4 -18
  275. package/skills/claude-md-drift-check/SKILL.md +5 -1
  276. package/skills/claude-md-drift-check/checker.mjs +62 -2
  277. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  278. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  279. package/skills/discovery/probes-arch.md +20 -18
  280. package/skills/dispatcher/SKILL.md +3 -2
  281. package/skills/evolve/SKILL.md +65 -26
  282. package/skills/frontmatter-guard/SKILL.md +11 -5
  283. package/skills/npm-publish/SKILL.md +1 -1
  284. package/skills/reconcile/SKILL.md +33 -0
  285. package/skills/remote-offload/SKILL.md +1 -1
  286. package/skills/session-end/SKILL.md +18 -905
  287. package/skills/session-end/phase-3-6-tail.md +10 -3
  288. package/skills/session-end/plan-verification.md +221 -155
  289. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  290. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  291. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  292. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  293. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  294. package/skills/session-end/references/session-summary-template.md +62 -0
  295. package/skills/session-plan/SKILL.md +49 -0
  296. package/skills/session-start/SKILL.md +22 -904
  297. package/skills/session-start/phase-8-5-express-path.md +1 -1
  298. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  299. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  300. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  301. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  302. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  303. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  304. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  305. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  306. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  307. package/skills/vault-sync/validator.mjs +21 -27
  308. package/skills/wave-executor/SKILL.md +15 -1
  309. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  310. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  311. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  312. package/skills/wave-executor/wave-loop.md +14 -1309
  313. package/templates/_shared/journey-manifest.md +10 -6
  314. package/.cursor/commands/autopilot-multi.md +0 -14
  315. package/.cursor/commands/contract-version-bump.md +0 -14
  316. package/.cursor/commands/journey-audit.md +0 -14
  317. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  318. package/.cursor/skills/daily/SKILL.md +0 -12
  319. package/.cursor/skills/domain-model/SKILL.md +0 -13
  320. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  321. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  322. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  323. package/commands/autopilot-multi.md +0 -74
  324. package/commands/contract-version-bump.md +0 -28
  325. package/commands/journey-audit.md +0 -43
  326. package/pi/prompts/autopilot-multi.md +0 -12
  327. package/pi/prompts/contract-version-bump.md +0 -12
  328. package/pi/prompts/journey-audit.md +0 -12
  329. package/scripts/autopilot-multi.mjs +0 -885
  330. package/scripts/backfill-learnings-expires.mjs +0 -196
  331. package/scripts/backfill-learnings.mjs +0 -203
  332. package/scripts/fleet-instruction-scan.mjs +0 -141
  333. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  334. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  335. package/scripts/lib/webhook-url.mjs +0 -105
  336. package/scripts/lifecycle-sim-v6.mjs +0 -347
  337. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  338. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  339. package/scripts/upload-social-preview.mjs +0 -316
  340. package/skills/_shared/model-selection.md +0 -64
  341. package/skills/contract-version-bump/SKILL.md +0 -219
  342. package/skills/daily/SKILL.md +0 -222
  343. package/skills/daily/generate.sh +0 -92
  344. package/skills/daily/templates/daily.md.tpl +0 -36
  345. package/skills/journey-audit/SKILL.md +0 -270
  346. package/skills/skill-creator/SKILL.md +0 -168
  347. package/skills/ubiquitous-language/SKILL.md +0 -97
  348. package/skills/vault-sync/package-lock.json +0 -40
  349. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  350. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -10,12 +10,21 @@
10
10
  * Output: {findings[], metrics, duration_ms, [skipped_reason]}. Never throws.
11
11
  * Also appends one JSONL summary record to .orchestrator/metrics/vault-staleness.jsonl.
12
12
  *
13
- * Current limitation: we compare lastSync age against probe run time only —
14
- * not against the upstream repo's most-recent commit. Mapping vault slugs to
15
- * local repo paths is not reliably available from the probe's inputs. A future
16
- * iteration should resolve each slug to a repo path and run
17
- * `git log -1 --format=%aI` to obtain the true lastCommit timestamp, then
18
- * compute |lastCommit - lastSync| instead.
13
+ * Denominator (GitLab #1238): staleness is `lastCommit - lastSync` how far the
14
+ * upstream repo advanced PAST the last sync — not `now - lastSync`. A mirror of a
15
+ * repo nobody has committed to in three weeks is CURRENT, not three weeks stale.
16
+ * `lastCommit` needs no slug→repo-path resolution: the vault sync writer already
17
+ * stamps it into the same `_overview.md` frontmatter it writes `lastSync` into,
18
+ * so both sides of the comparison come from one read of one file.
19
+ *
20
+ * Measured against the live vault before the fix (2026-09-05): 33 of 48 overviews
21
+ * reported "stale", 26 of them >7d, with a demonstrably healthy sync chain —
22
+ * because the clock, not the repo, was the denominator.
23
+ *
24
+ * Fallback: an overview WITHOUT `lastCommit` carries no repo-activity signal at
25
+ * all, so the wall-clock comparison is the only thing left. It is retained for
26
+ * that case only, marked `basis: 'probe-runtime'` in the evidence and carried at
27
+ * lower confidence, so a consumer can tell a measured delta from a guessed one.
19
28
  */
20
29
 
21
30
  import { existsSync, readFileSync, readdirSync, mkdirSync, appendFileSync } from 'node:fs';
@@ -173,6 +182,7 @@ export async function runProbe(projectRoot, config) {
173
182
  const slug = fm.slug || entry.name;
174
183
  const tier = fm.tier || undefined;
175
184
  const lastSync = fm.lastSync || undefined;
185
+ const lastCommit = fm.lastCommit || undefined;
176
186
 
177
187
  if (!lastSync) {
178
188
  metrics.stale_count++;
@@ -202,21 +212,33 @@ export async function runProbe(projectRoot, config) {
202
212
  continue;
203
213
  }
204
214
 
205
- const delta = now - lastSyncMs;
215
+ // #1238 the denominator. `lastCommit` is the repo's own newest activity,
216
+ // so `lastCommit - lastSync` measures what the mirror actually MISSED.
217
+ // A negative or zero delta means the sync ran at or after the newest
218
+ // commit: current, whatever the wall clock says.
219
+ const lastCommitMs = lastCommit ? Date.parse(lastCommit) : NaN;
220
+ const haveCommitBasis = !isNaN(lastCommitMs);
221
+ const basis = haveCommitBasis ? 'lastCommit' : 'probe-runtime';
222
+ const delta = haveCommitBasis ? lastCommitMs - lastSyncMs : now - lastSyncMs;
223
+
206
224
  if (delta > HOURS_24) {
207
225
  const severity = delta > HOURS_168 ? 'medium' : 'low';
208
226
  const dh = deltaHours(delta);
209
227
  metrics.stale_count++;
210
228
  findings.push({
211
229
  severity,
212
- confidence: 0.9,
230
+ // A repo-anchored delta is a measurement; a clock-anchored one is the
231
+ // best available guess about a repo this probe cannot see.
232
+ confidence: haveCommitBasis ? 0.9 : 0.6,
213
233
  file_path: overviewPath,
214
234
  title: `[vault-staleness] ${slug}: ${formatDelta(delta)} since last sync`,
215
- description:
216
- `lastSync is ${formatDelta(delta)} old (threshold: 24h). ` +
217
- `Note: this compares lastSync age against probe run time, not upstream lastCommit — ` +
218
- `a future iteration will add repo-path resolution for a more precise delta.`,
219
- evidence: { slug, tier, lastSync, delta_hours: dh },
235
+ description: haveCommitBasis
236
+ ? `The repo advanced ${formatDelta(delta)} past the last vault sync ` +
237
+ `(lastCommit ${lastCommit} vs lastSync ${lastSync}, threshold: 24h).`
238
+ : `lastSync is ${formatDelta(delta)} old (threshold: 24h). ` +
239
+ `No lastCommit in the frontmatter, so this compares against probe run time — ` +
240
+ `an idle repo reads as stale here. Add lastCommit to make the delta repo-anchored.`,
241
+ evidence: { slug, tier, lastSync, lastCommit, basis, delta_hours: dh },
220
242
  });
221
243
  }
222
244
  }
@@ -237,6 +259,8 @@ export async function runProbe(projectRoot, config) {
237
259
  slug: f.evidence.slug,
238
260
  severity: f.severity,
239
261
  last_sync: f.evidence.lastSync,
262
+ last_commit: f.evidence.lastCommit,
263
+ basis: f.evidence.basis,
240
264
  delta_hours: f.evidence.delta_hours,
241
265
  flag: 'stale-yes',
242
266
  })),
@@ -8,23 +8,16 @@
8
8
 
9
9
  **Detection Method:**
10
10
 
11
- **Preferred (language-mapper-supported projects):**
12
-
13
- Call `extractSemanticSlices(filePath, { type: 'imports' })` from `scripts/lib/language-mappers/index.mjs` for each source file to obtain a typed list of import edges. The mapper handles TypeScript, JavaScript, and Markdown files natively; unsupported file types return an empty array (graceful degradation). Feed the resulting `{source_file -> [imported_file]}` adjacency map directly into the cycle-detection loop below.
14
-
15
- ```js
16
- // Pseudocodewire once per probe run
17
- import { extractSemanticSlices } from '$PLUGIN_ROOT/scripts/lib/language-mappers/index.mjs';
18
-
19
- for (const filePath of sourceFiles) {
20
- const imports = await extractSemanticSlices(filePath, { type: 'imports' });
21
- for (const { resolved } of imports) {
22
- adjacency.get(filePath).push(resolved);
23
- }
24
- }
25
- ```
26
-
27
- **Fallback (when language-mapper returns empty or for unsupported file types):**
11
+ > **The language-mapper does NOT produce import edges.** An earlier revision of this
12
+ > file described a "preferred" path calling
13
+ > `extractSemanticSlices(filePath, { type: 'imports' })`. Neither half of that call
14
+ > exists: the real signature is
15
+ > `extractSemanticSlices(filePath, content, options)` — the second parameter is the
16
+ > file's CONTENT, not an options object and `'imports'` is not a slice kind.
17
+ > `SLICE_KINDS` (`scripts/lib/language-mappers/index.mjs`) is
18
+ > `function | class | interface | type | export | section`; there is no import kind
19
+ > and no `resolved` field, so no adjacency map can be built from it. The grep/madge
20
+ > method below is the only detection method this probe has.
28
21
 
29
22
  ```bash
30
23
  # Build import graph from source files
@@ -39,7 +32,7 @@ Grep pattern: (import\s+.*from\s+["']([^"']+)["']|require\s*\(\s*["']([^"']+)["'
39
32
  npx madge --circular --extensions ts,tsx,js,jsx src/ 2>/dev/null
40
33
  ```
41
34
 
42
- Algorithm (cycle detection — same for both paths above):
35
+ Algorithm (cycle detection):
43
36
  1. Build `{source_file -> [imported_file]}` adjacency map
44
37
  2. Resolve relative imports to absolute paths
45
38
  3. For each file, BFS through imports with depth limit of 10
@@ -56,6 +49,15 @@ Files Involved:
56
49
 
57
50
  **Default Severity:** High.
58
51
 
52
+ **Related committed artefact (this repo only, and NOT a substitute):**
53
+ `hooks/_lib/hook-import-set.json`, regenerated by
54
+ `node scripts/generate-hook-import-set.mjs` (flags: `--plugin-root`, `--out`,
55
+ `--check`). It records `{file, reachable_from[]}` — which modules are transitively
56
+ reachable from a HOOK entry file — so it is a reachability allowlist scoped to
57
+ `hooks/`, not an edge list over the whole tree, and it cannot detect cycles. Use it
58
+ to answer "does an edit to this module reach a hook?", never as the adjacency map
59
+ above.
60
+
59
61
  ---
60
62
 
61
63
  ### Probe: complexity-hotspots
@@ -65,7 +65,7 @@ Compute the suitability verdict for **R** via the pure four-gate engine `compute
65
65
  | `autonomy` | `resolveDispatcherAutonomy({ committed, env, ownerConfig })` from `scripts/lib/config/dispatcher-autonomy.mjs` | The effective dial. Defaults to `'off'` when unset (fail-closed). |
66
66
  | `confidenceFloor` | the `confidence-floor` from the parsed `dispatcher-autonomy:` block (default 0.5) | Same source object as `autonomy`. |
67
67
  | `confidence` | mode-selector `selectMode(signals).confidence` (0..1 float) for the recommended session-type | The same mode-selector the Phase-2 heuristic and autopilot use. |
68
- | `ci` | `checkCiStatus({ repoRoot: R })` → `{ status }` \| `null` | **CRITICAL (NICE-b):** Phase-1 `rank.mjs` exposes only the BARE status string (`readiness.ciStatus`). The engine's G3 gate expects an OBJECT `{ status }` — wrap it as `{ status: ciStatus }` when you HAVE a status; on a CI-fetch FAILURE pass `ci = null` (checkCiStatus already returns `null` on failure — pass it straight through). **Do NOT synthesize `{ status: undefined }`** (or `{}`): that present-but-unusable object hits the engine's MALFORMED branch (`'CI signal malformed — treated as absent'`) instead of the clean ABSENT branch (`'CI signal absent'`). Both pass G3 + warn, but `null` is the honest "no signal" — reserve the malformed branch for a genuinely unexpected shape. A bare string ALSO hits the malformed branch — always wrap or null. |
68
+ | `ci` | `checkCiStatus({ repoRoot: R })` → `{ status }` \| `null` | **CRITICAL (NICE-b):** Phase-1 `rank.mjs` exposes only the BARE status string (`readiness.ciStatus`). The engine's G3 gate expects an OBJECT `{ status }` — wrap it as `{ status: ciStatus }` when you HAVE a status; on a genuine ABSENCE (no CI configured) pass `ci = null`. **Since #1031 `checkCiStatus` no longer returns `null` on failure**an unreadable state comes back as `{severity:'warn', ok:false, degraded:<reason>}`, and Phase-1 `rank.mjs` surfaces exactly that as `readiness.ciDegraded` with `readiness.ciStatus === 'unknown'`. Wrap that state as `{ status: 'unknown' }` (the engine warns `'CI signal unknown — treated as absent'` and G3 still passes) rather than flattening it to `null` — `null` claims a measured absence the probe never established. **Do NOT synthesize `{ status: undefined }`** (or `{}`): that present-but-unusable object hits the engine's MALFORMED branch (`'CI signal malformed — treated as absent'`) instead of the clean ABSENT branch (`'CI signal absent'`). Both pass G3 + warn, but `null` is the honest "no signal" — reserve the malformed branch for a genuinely unexpected shape. A bare string ALSO hits the malformed branch — always wrap or null. |
69
69
  | `resourceVerdict` | the host resource verdict string (`'green'\|'warn'\|'degraded'\|'critical'`) from `rank.mjs` (`readiness.resourceVerdict`) or a fresh `evaluate(probe(), thresholds).verdict` | Host-level — already fetched once in Phase 1. **NICE-b:** on a genuine probe FAILURE (no signal), prefer `resourceVerdict = null` over synthesizing `'green'`. `null` = "no signal" ⇒ G4 passes + warns (`'resource signal absent'`) — honest. Synthesizing `'green'` fabricates a positive signal the host never reported and can let an autonomous launch proceed against an unknown host state. Pass the real verdict string when you have one; `null` when you do not. |
70
70
  | `recentRuns` | `readRecentAutopilotRuns({ repoRoot: R })` from `scripts/lib/autopilot/recent-runs.mjs` | NEW reader. Reads `<R>/.orchestrator/metrics/autopilot.jsonl`, returns the most-recent records (newest-last), never throws (`[]` on missing/unreadable). Pass the TRUE count — the engine's G2 gate omits-with-warn below 5 runs and otherwise checks `fired/N < 0.2`. |
71
71
 
@@ -167,7 +167,8 @@ Data → stdout, warnings/errors → stderr (never mixed). Exit codes follow `.c
167
167
  - **Running this from a subagent** — coordinator-only (AUQ is unavailable in subagents).
168
168
  - **Fail-OPEN verdict gate (#682)** — keying the autonomous launch on `verdict.suitable` alone (ignoring `autonomy === 'autonomous-gated'`) auto-launches in `advisory`/`off` mode. ALWAYS gate on BOTH.
169
169
  - **Feeding the bare CI string into the engine** — `rank.mjs` exposes `ciStatus` as a bare string; `computeSuitabilityVerdict` wants `{ status }`. Wrap it (`{ status: ciStatus }`) or pass `null` — a bare string silently hits the malformed-absent branch. A red CI fed as a bare string would PASS G3 (masked as malformed) instead of forcing the FORCED-fail branch (NICE-c).
170
- - **Synthesizing an absent signal instead of passing `null` (NICE-b)** — on a CI-fetch or resource-probe failure, pass `ci = null` / `resourceVerdict = null` (honest "no signal" gate passes + warns). Do NOT synthesize `{ status: undefined }` (hits the malformed branch) and do NOT fabricate `'green'` / `{ status: 'green' }` (invents a positive signal the host never reported and can green-light an autonomous launch against an unknown state).
170
+ - **Flattening a DEGRADED CI reading to `null` (#1031)** — `readiness.ciDegraded` set (⇒ `readiness.ciStatus === 'unknown'`) means the state could NOT be read, which is weaker than absence, not equal to it. Pass `{ status: 'unknown' }`; `null` asserts a measured absence nothing established.
171
+ - **Synthesizing an absent signal instead of passing `null` (NICE-b)** — on a resource-probe failure, pass `resourceVerdict = null` (honest "no signal" ⇒ gate passes + warns). Do NOT synthesize `{ status: undefined }` (hits the malformed branch) and do NOT fabricate `'green'` / `{ status: 'green' }` (invents a positive signal the host never reported and can green-light an autonomous launch against an unknown state).
171
172
  - **Pre-truncating `recentRuns` below 5** — passing fewer than the true on-disk count when ≥ 5 runs exist falsely triggers the engine's <5-run omission branch and skips the kill-switch gate. Pass the TRUE count; never call `readRecentAutopilotRuns` with `limit < 5` on the launch-gate read (the reader honours a small `limit` literally and will not clamp it upward).
172
173
  - **Auto-launching against a non-green verdict** — a CI-red / resource-critical / low-confidence verdict ALWAYS falls to inform + ask, even under `autonomous-gated`. Never proceed straight to claim on a non-suitable verdict.
173
174
 
@@ -46,7 +46,7 @@ Extract `persistence` from `$CONFIG`. If `persistence` is `false`, abort with me
46
46
 
47
47
  > "Learnings require persistence to be enabled in Session Config. Add `persistence: true` to your Session Config block (CLAUDE.md for Claude Code, AGENTS.md for Codex CLI)."
48
48
 
49
- **Telemetry on abort (#1200):** before stopping, emit the abort form of the run-completion event:
49
+ **Telemetry on abort (#1200, #1206):** before stopping, emit the abort form of the run-completion event. Kept as a minimal `emit-event.mjs` call, not routed through `scripts/sweep-expired-learnings.mjs` — no store-write CLI has run yet at this gate (it fires before Step 1.4 even reads `learnings.jsonl`), so there is no mechanical pipeline call site to fold this emit into, unlike the Step 3.5(5)/(6) success path below:
50
50
 
51
51
  ```bash
52
52
  node scripts/emit-event.mjs --type orchestrator.evolve.completed --payload \
@@ -100,7 +100,7 @@ Extract learnings from session history.
100
100
  - Read all entries from `.orchestrator/metrics/sessions.jsonl` (or `<state-dir>/metrics/sessions.jsonl` if the v2 path does not exist — see Phase 1.4 fallback)
101
101
  - Parse each JSONL line as JSON
102
102
  - Sort by `completed_at` descending (most recent first)
103
- - If no sessions found, abort: "No session data available. Complete at least one session before running evolve." **Telemetry on abort (#1200):** before stopping, emit:
103
+ - If no sessions found, abort: "No session data available. Complete at least one session before running evolve." **Telemetry on abort (#1200, #1206):** before stopping, emit — same minimal `emit-event.mjs` call as Phase 1.2's abort, and for the same reason: this gate fires before the Step 3.5(5) `sweep-expired-learnings.mjs --prune` call exists to fold the emit into:
104
104
 
105
105
  ```bash
106
106
  node scripts/emit-event.mjs --type orchestrator.evolve.completed --payload \
@@ -181,7 +181,7 @@ For each of the 9 built-in analyzer learning types, apply these heuristics:
181
181
  - Read `.orchestrator/metrics/events.jsonl` (session + wave events) and the registry `sweep.log` at `~/.config/session-orchestrator/sessions/sweep.log`. Both are optional — missing files produce no candidates.
182
182
  - Invoke `scripts/lib/hardware-pattern-detector.mjs` → `detectHardwarePatterns({events, sweepLogEntries, thresholds})`. Thresholds come from Session Config `resource-thresholds` when present, falling back to `DEFAULT_THRESHOLDS`.
183
183
  - Five detection signals (aggregated per `(signal, host_class)` pair, ≥2 occurrences required):
184
- - **oom-kill** — `orchestrator.session.stopped` with `exit_code: 137` or OOM-marker in `error`
184
+ - **oom-kill** — `orchestrator.turn.stopped` (or its deprecated alias `orchestrator.session.stopped`, which `hooks/on-stop.mjs` still emits with `deprecated: true` until **2027-03-06**) with `exit_code: 137` or OOM-marker in `error`. Both names are accepted for the deprecation window because every OOM record already on disk carries only the legacy name; the detector's set lives in `OOM_TERMINAL_EVENTS` (`scripts/lib/hardware-pattern-detector.mjs`) and drops the alias on that date.
185
185
  - **heartbeat-gap** — registry sweep-log entries with `gap_minutes` above `resource-thresholds.zombie-threshold-min`
186
186
  - **concurrent-session-pressure** — session-start events with `peer_count ≥ concurrent-sessions-warn`
187
187
  - **disk-full** — events whose `error` matches `ENOSPC` / "no space left"
@@ -316,22 +316,34 @@ For confirmed learnings, use atomic rewrite strategy:
316
316
  Write the full next-generation entry set (existing entries **with** the step-2/3 confidence
317
317
  updates, **plus** the step-4 new learnings) as JSONL to a temp sidecar **via the Write tool**
318
318
  (not a shell `>` redirect — the destructive-command guard blocks it), then invoke the
319
- `--prune` subcommand of the sweep CLI:
319
+ `--prune` subcommand of the sweep CLI. **This call is also `/evolve`'s ONLY
320
+ `orchestrator.evolve.completed` success emit (#1206)** — export `N` (Step 3.5(4)'s
321
+ new-learnings count), `M` (Step 3.5(2)'s reinforced-existing count) and `DURATION_MS`
322
+ (elapsed ms since the Phase 1 marker) as real shell variables before running this line;
323
+ `${N:-0}`-style expansion means an un-exported variable degrades to a safe `0` rather than
324
+ an argument error:
320
325
 
321
326
  ```bash
322
327
  NEXT=".orchestrator/metrics/.learnings-next.jsonl" # written by the step above
323
- node scripts/sweep-expired-learnings.mjs --prune --apply --json --entries "$NEXT" && rm -f "$NEXT"
328
+ node scripts/sweep-expired-learnings.mjs --prune --apply --json --entries "$NEXT" \
329
+ --appended "${N:-0}" --boosted "${M:-0}" --duration-ms "${DURATION_MS:-0}" \
330
+ --repo-root "$(pwd)" && rm -f "$NEXT"
324
331
  ```
325
332
 
326
333
  `--file` / `--archive` default to the canonical store + archive paths — pass them only when
327
334
  operating on a non-default pair. The command prints ONE JSON line; capture it as `$PRUNE` and
328
- report its `{scanned, kept, archived, byReason}` in the final summary. Preview first with
329
- `--prune --dry-run --json` (same counts, zero writes) whenever the next generation was
330
- hand-assembled.
331
-
332
- > **This step is `/evolve`'s only store-write path.** Until #1017 the invocation lived here as
333
- > an inline `node --input-type=module -e` block, which is a mechanism hiding inside prose: no
334
- > `--help`, no exit-code contract, no test. Do not re-inline it, and do not hand-roll a
335
+ report its `{scanned, kept, archived, byReason}` in the final summary `$PRUNE.archived` is
336
+ also the `pruned` counter the emit above just wrote, so there is nothing left to compute for
337
+ the telemetry after this line. Preview first with `--prune --dry-run --json` (same counts,
338
+ zero writes, **no telemetry emit** — dry-run never claims a completed run) whenever the next
339
+ generation was hand-assembled.
340
+
341
+ > **This step is `/evolve`'s only store-write path, and (since #1206) its only
342
+ > `orchestrator.evolve.completed` success emit.** Until #1017 the store write lived here as
343
+ > an inline `node --input-type=module -e` block, and until #1206 the telemetry emit was a
344
+ > SEPARATE `emit-event.mjs` call further down this file — both were a mechanism hiding inside
345
+ > prose: no `--help`, no exit-code contract, no test, and (for the emit) forgettable
346
+ > independently of the write it reported on. Do not re-inline either, and do not hand-roll a
335
347
  > `jq | ... > learnings.jsonl` pass — that bypasses every #721 safety net.
336
348
 
337
349
  **Exit codes are the no-op rule.** `0` = applied (or a clean no-op). `1` = input error: the
@@ -394,12 +406,12 @@ For confirmed learnings, use atomic rewrite strategy:
394
406
 
395
407
  Report: "Saved N new learnings, updated M existing. Total active: K."
396
408
 
397
- **Telemetry (#1200):** emit the run-completion event as the last action of this step, using the counts already computed above `N` (Step 3.5(4) new-learnings count) → `appended`, `M` (Step 3.5(2) reinforced-existing count) → `boosted`, `$PRUNE.archived` (the sweep CLI's returned total, Step 3.5(5)) → `pruned`. `promoted` is always `0` from THIS call site: promotion to `public` scope is the separate `npm run share:hw-learnings -- --promote` CLI, never invoked by `/evolve analyze` itself — see `docs/events-schema.md`. All four counters are ALWAYS present, including as `0`:
398
-
399
- ```bash
400
- node scripts/emit-event.mjs --type orchestrator.evolve.completed --payload \
401
- "$(node -e "process.stdout.write(JSON.stringify({appended: N, boosted: M, pruned: PRUNED, promoted: 0, duration_ms: DURATION_MS}))")"
402
- ```
409
+ **Telemetry (#1200, #1206):** already emitted by `scripts/sweep-expired-learnings.mjs --prune`
410
+ at Step 3.5(5) above — no separate action here. `appended`/`boosted`/`duration_ms` are whatever
411
+ `$N`/`$M`/`$DURATION_MS` carried into that call, and `pruned` is `$PRUNE.archived` (the sweep
412
+ CLI's own returned total). `promoted` is always `0` from THIS call site: promotion to `public`
413
+ scope is the separate `npm run share:hw-learnings -- --promote` CLI, never invoked by
414
+ `/evolve analyze` itself — see `docs/events-schema.md`.
403
415
 
404
416
  ### Step 3.6: C2 Auto-Repair Feeder (opt-in — #647)
405
417
 
@@ -626,11 +638,27 @@ const result = await runDialecticDeriver({
626
638
  - If `--apply`: call `mergePeerCard(existingBody, managedUpdates)` from `scripts/lib/peer-cards/merger.mjs` for each card target, then `writePeerCard(repoRoot, 'user', mergedUserCard)` and `writePeerCard(repoRoot, 'agent', mergedAgentCard)` from `scripts/lib/peer-cards/writer.mjs`. Update the `updated:` frontmatter.
627
639
  - Report: `Dialectic-derived: M deltas to USER.md, N deltas to AGENT.md. Dry-run | Applied. Tokens: in=<X> out=<Y>.`
628
640
 
629
- **Telemetry (#1200):** immediately after the report line above, emit the success form (`mode` mirrors which branch ran):
641
+ **Telemetry (#1200, #1206) emitted by `scripts/dialectic-deriver.mjs`, not skill prose.**
642
+ The dry-run branch needs no action here: `runDialecticDeriver()` already emitted the success
643
+ form (`mode: 'dry-run'`) internally at Step 6.2, using `countManagedSections(diff)` on the SAME
644
+ diff this step presents — in dry-run the diff IS the final artefact, so the event and the
645
+ artefact are computed from the same value. The **apply** branch is the one case that pipeline
646
+ cannot record on its own: the merge above happens here, one layer up, so call
647
+ `recordDialecticRun()` (the sibling export beside `emitEvolveCompleted` in
648
+ `scripts/lib/learnings/evolve-telemetry.mjs`) immediately after the `writePeerCard()` calls,
649
+ using each target's `mergePeerCard()` `stats` for the deltas:
630
650
 
631
- ```bash
632
- node scripts/emit-event.mjs --type orchestrator.dialectic.completed --payload \
633
- "$(node -e "process.stdout.write(JSON.stringify({mode: 'MODE', user_deltas: M, agent_deltas: N, tokens_in: X, tokens_out: Y, duration_ms: DURATION_MS}))")"
651
+ ```javascript
652
+ await recordDialecticRun({
653
+ repoRoot,
654
+ status: 'ok',
655
+ mode: 'apply',
656
+ userDeltas: userMergeStats.replaced + userMergeStats.appended,
657
+ agentDeltas: agentMergeStats.replaced + agentMergeStats.appended,
658
+ tokensIn: result.usage?.input_tokens,
659
+ tokensOut: result.usage?.output_tokens,
660
+ durationMs: DURATION_MS,
661
+ });
634
662
  ```
635
663
 
636
664
  ### Step 6.5: Error Handling
@@ -640,11 +668,22 @@ node scripts/emit-event.mjs --type orchestrator.dialectic.completed --payload \
640
668
  - `status: 'empty-input'` → exit clean with message "dialectic: skipped (no input)"
641
669
  - subagent crash → log ⚠, exit cleanly (do NOT write to `.orchestrator/dialectic-pending.md`)
642
670
 
643
- **Telemetry (#1200):** for EACH outcome above, before exiting, emit `orchestrator.dialectic.completed` in its abort form `SLUG` is `unknown-model` \| `budget-exceeded` \| `would-empty-card` \| `empty-input` \| `subagent-crash` respectively (the subagent-crash case has no `runDialecticDeriver` status of its own; use the literal slug `subagent-crash`):
671
+ **Telemetry (#1200, #1206)emitted by `scripts/dialectic-deriver.mjs` for THREE of the five
672
+ outcomes.** `budget-exceeded`, `would-empty-card`, and `empty-input` are `runDialecticDeriver()`
673
+ RETURN values, so the module records them itself, mechanically, at the exact return point —
674
+ nothing to do here for those three. The remaining two are THROWN, not returned, and can only be
675
+ caught one layer up:
644
676
 
645
- ```bash
646
- node scripts/emit-event.mjs --type orchestrator.dialectic.completed --payload \
647
- "$(node -e "process.stdout.write(JSON.stringify({aborted: 'SLUG', duration_ms: DURATION_MS}))")"
677
+ - `unknown-model` — `validateModel()` throws synchronously before `runDialecticDeriver()` can
678
+ record anything about the call.
679
+ - `subagent-crash` — a `dispatchAgent`/`Agent()` failure propagates out of
680
+ `runDialecticDeriver()` uncaught (it has no status of its own for this case).
681
+
682
+ Catch both here and call the SAME `recordDialecticRun()` used in Step 6.4's apply branch,
683
+ passing the literal slug as `status` (the abort form: `{aborted: status, duration_ms}`):
684
+
685
+ ```javascript
686
+ await recordDialecticRun({ repoRoot, status: 'unknown-model' /* or 'subagent-crash' */, durationMs: DURATION_MS });
648
687
  ```
649
688
 
650
689
  Cross-reference: PRD #506 AC1-AC4 + EARS gates. Vault Integration: dialectic does NOT mirror to vault (#506 scope — peer cards are repo-local by design; vault mirror is for cross-repo sessions/learnings).
@@ -94,13 +94,19 @@ The output is stable across calls for the same schema version. Regenerate only w
94
94
 
95
95
  ## Schema Source Path
96
96
 
97
- The canonical schema source is:
97
+ The canonical schema source is `packages/zod-schemas/src/vault-frontmatter.ts` inside a **projects-baseline** checkout. That checkout is **optional and private** — see [`docs/baseline.md`](../../docs/baseline.md) — so the path is RESOLVED, never hardcoded. `resolveSchemaSourcePath()` resolves it in two tiers and returns `null` when nothing resolves. When the EXPLICIT tier is set it is used **alone** — probing past a wrong explicit value would silently read a different baseline than the one named:
98
98
 
99
- ```
100
- ~/Projects/projects-baseline/packages/zod-schemas/src/vault-frontmatter.ts
101
- ```
99
+ | Tier | Candidate | Set by |
100
+ |---|---|---|
101
+ | **explicit** (exclusive) | `<baseline-path>/packages/zod-schemas/src/vault-frontmatter.ts` | `SO_BASELINE_PATH` env, else `owner.yaml` `paths.baseline-path` (host-local, never committed) — via `resolveHostPath('baseline-path', …)` |
102
+ | convention 1 | `<repoRoot>/../projects-baseline/packages/…` | sibling-checkout convention, the same one `scripts/sync-vault-schema.mjs` uses |
103
+ | convention 2 | `~/Projects/projects-baseline/packages/…` | legacy default this module shipped with |
104
+
105
+ Before this was resolved, convention 2 was the ONLY path and it was hardcoded: on a host whose checkout lives anywhere else, `readVaultSchema()` returned `null` and `generateFrontmatterSnippet()` then died with `Cannot destructure property 'typeEnum' of 'schema' as it is undefined`.
106
+
107
+ `readVaultSchema()` reads the resolved file on every call unless the in-memory mtime cache is current. It returns `null` (no throw) when no candidate resolves or the file is unreadable.
102
108
 
103
- `readVaultSchema()` reads this file on every call unless the in-memory mtime cache is current. The function returns `null` (no throw) when the file is absent or unreadable.
109
+ **Degraded mode.** With `null`, `generateFrontmatterSnippet()` does not throw: it falls back to an in-module enum/field set mirroring `skills/vault-sync/validator.mjs` (this repo's own in-tree copy of the schema, and what `vault-sync` actually validates against) and writes ONE stderr WARN per process. `computeSchemaHash()` returns `null` in that state — never the SHA-256 of the empty string, which would look like a real measurement and compare equal across every baseline-less host.
104
110
 
105
111
  The parsed output includes:
106
112
 
@@ -39,7 +39,7 @@ The script gates mechanics. These three are yours, and it will not make them for
39
39
 
40
40
  **2. What a leak means when one is found.** A hit from the leakage gate is not a pattern to silence. Decide which of two it is: a real leak (fix `package.json` `files`, re-pack, re-check) or genuine over-matching (fix `LEAKAGE_PATTERNS` in `scripts/release.mjs` **with a test**). There is no third option, and neither is "publish anyway and clean it up in the next version" — an npm publish is not revocable, and unpublishing burns the version number permanently. Operator handling detail: `docs/distribution/npm-publish-checklist.md` § 3.
41
41
 
42
- **3. When to abort instead of repair.** Abort — do not patch forward — when the failure is upstream of the target-confirmed npm receipt: a red preflight row, a lagging `github` mirror, CI not green on the exact commit, a dead token, or a publish that did not issue the target receipt. These are cheap to fix and re-run from the top. Repair-in-place is only appropriate *after* that receipt, where the version is already immutable: registry propagation, a missing GitHub release, or a lagging site deploy can be reconciled because npm already has the correct artifact. When `--publish` reports **Post-publish reconciliation required**, **do not rerun `--publish`**; repair the listed state directly. `commands/release.md` § Abort criteria is the operative list.
42
+ **3. When to abort instead of repair.** Abort — do not patch forward — when the failure is upstream of the target-confirmed npm receipt: a red preflight row, a lagging `github` mirror, CI not green on the exact commit **on either platform** (`--check` carries both `ci-green-on-head` for GitLab and `ci-green-on-head-github` for the mirror, whose macOS matrix leg has no GitLab equivalent), a dead token, or a publish that did not issue the target receipt. These are cheap to fix and re-run from the top. Repair-in-place is only appropriate *after* that receipt, where the version is already immutable: registry propagation, a missing GitHub release, or a lagging site deploy can be reconciled because npm already has the correct artifact. When `--publish` reports **Post-publish reconciliation required**, **do not rerun `--publish`**; repair the listed state directly. `commands/release.md` § Abort criteria is the operative list.
43
43
 
44
44
  ## Failure-mode table
45
45
 
@@ -356,6 +356,39 @@ If `written === 0` and `approved.length === 0`:
356
356
 
357
357
  ---
358
358
 
359
+ ## Consolidating and Dropping Generated Rules (merge contract)
360
+
361
+ `.claude/rules/` grows one file per approved learning, so it accumulates. This
362
+ repo consolidated 43 generated files (112,443 B, 46.2 % frontmatter+provenance
363
+ overhead) into 8 thematic files plus 10 drops on 2026-09-06. Both operations
364
+ are safe ONLY under the contract below — the full authoring spec is
365
+ [`docs/rule-authoring.md`](../../docs/rule-authoring.md) § "Consolidated rules:
366
+ N provenance pairs in ONE file". The three facts that decide whether a
367
+ consolidation survives the next `/reconcile`:
368
+
369
+ - **A target file may carry N provenance bullet PAIRS.** Frontmatter
370
+ `learning-key:` is a scalar, so at most one marker fits there; the other N−1
371
+ live in the body as `` - learning-key: `…` `` + `` - learning-id: `…` ``
372
+ bullets, which `engine.mjs` reads via `BODY_LEARNING_KEY_RE` /
373
+ `BODY_LEARNING_ID_RE`. One pair per absorbed learning — a missing pair
374
+ regenerates that learning as a standalone file on the next run.
375
+ - **A merged file's `expires-at` is the EARLIEST of its parts**, never the
376
+ latest: it must not outlive its shortest-lived content.
377
+ - **A dropped learning must be STAMPED before deletion, or it regenerates.**
378
+ `rm .claude/rules/<slug>.md` alone leaves `isProcessed()` false and no
379
+ on-disk marker, so the engine re-proposes it. Stamp it terminal first with
380
+ `markCandidateProcessed({ learningKey, outcome: 'rejected', fallbackSlug,
381
+ repoRoot })` from `scripts/lib/reconcile/idempotency.mjs` — the ONLY
382
+ sanctioned writer of `.orchestrator/runtime/reconcile-candidates.jsonl`
383
+ (never append to that file by hand; the read-side shape guard drops foreign
384
+ records and `mergeCandidates` rewrites the store in full).
385
+
386
+ **Verify a consolidation with a dry run**, not by eye: `alreadyMaterialized`
387
+ must equal absorbed + dropped. If it equals only the absorbed count, the drops
388
+ were not stamped and the next run will resurrect them. Do the whole operation
389
+ while `reconcile.enabled: false` in Session Config, so nothing regenerates
390
+ underneath you mid-edit.
391
+
359
392
  ## Critical Rules
360
393
 
361
394
  - **NEVER** call `writeApprovedRules` before the operator has confirmed via AUQ — this is the
@@ -33,7 +33,7 @@ The gate decides, not the coordinator. `applyOffloadDecision()` in `scripts/lib/
33
33
  - `opts.remoteReady` — `{ [alias]: boolean }`, built from the SessionStart banner line `Offload <alias>: ready=yes …`, or
34
34
  - `opts.probeFn` — an async `(alias) => boolean` fallback, consulted only for aliases `remoteReady` doesn't answer for (backed by `remoteDoctor()`, i.e. `offload doctor -H <alias> --brief` parsed by `parseDoctorLine()`).
35
35
 
36
- With neither supplied, no host counts as ready and the wave stays local — the gate fails toward local, never toward an unverified host. A role in `NEVER_FOREIGN_ROLES` (`scripts/lib/wave-executor/foreign-dispatch.mjs`: `impl-core`, `security-review`, `migration`, `release`, `secrets`) is never offloaded regardless of readiness.
36
+ With neither supplied, no host counts as ready and the wave stays local — the gate fails toward local, never toward an unverified host. A role in `NEVER_FOREIGN_ROLES` (`scripts/lib/wave-executor/dispatch-common.mjs`: `impl-core`, `security-review`, `migration`, `release`, `secrets`) is never offloaded regardless of readiness.
37
37
 
38
38
  ## 2. What is declared where
39
39