session-orchestrator 3.23.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 (393) 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 +13 -0
  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 +1401 -0
  81. package/NOTICE +11 -6
  82. package/README.md +127 -92
  83. package/agents/db-specialist.md +0 -1
  84. package/agents/eval-judge.md +1 -1
  85. package/agents/skill-applied-judge.md +1 -1
  86. package/assets/wave-lifecycle.svg +98 -0
  87. package/commands/release.md +6 -3
  88. package/commands/session.md +18 -3
  89. package/docs/README.md +4 -0
  90. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  91. package/docs/baseline.md +67 -0
  92. package/docs/ci-setup.md +249 -48
  93. package/docs/codex-setup.md +66 -22
  94. package/docs/components.md +37 -16
  95. package/docs/cursor-setup.md +6 -2
  96. package/docs/events-schema.md +51 -10
  97. package/docs/instruction-delivery.md +62 -0
  98. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  99. package/docs/migration-v4.md +341 -0
  100. package/docs/pi-setup.md +6 -1
  101. package/docs/plugin-architecture-v3.md +1 -1
  102. package/docs/rule-authoring.md +85 -19
  103. package/docs/scope-collision-guard.md +8 -8
  104. package/docs/session-config-reference.md +120 -61
  105. package/docs/session-config-template.md +40 -33
  106. package/docs/telemetry/telemetry-claims.md +11 -10
  107. package/docs/telemetry.md +187 -4
  108. package/docs/vault-docs-architecture.md +50 -11
  109. package/hooks/_lib/atomic-json.mjs +111 -0
  110. package/hooks/_lib/hook-import-set.json +1487 -0
  111. package/hooks/_lib/subagent-paths.mjs +143 -0
  112. package/hooks/_lib/subagent-transcript.mjs +562 -0
  113. package/hooks/config-protection.mjs +2 -2
  114. package/hooks/cwd-change-restore.mjs +11 -31
  115. package/hooks/enforce-commands.mjs +69 -0
  116. package/hooks/enforce-scope.mjs +35 -6
  117. package/hooks/hooks-codex.json +1 -1
  118. package/hooks/hooks-cursor.json +10 -0
  119. package/hooks/hooks-pi.json +5 -0
  120. package/hooks/hooks.json +6 -1
  121. package/hooks/loop-guard.mjs +3 -3
  122. package/hooks/on-session-end.mjs +280 -14
  123. package/hooks/on-session-start.mjs +153 -4
  124. package/hooks/on-stop.mjs +371 -17
  125. package/hooks/operator-steer.mjs +2 -2
  126. package/hooks/post-bash-write-verify.mjs +189 -4
  127. package/hooks/post-edit-import-probe.mjs +344 -0
  128. package/hooks/post-subagent-discovery-validator.mjs +278 -392
  129. package/hooks/post-tool-batch-wave-signal.mjs +272 -44
  130. package/hooks/post-tool-failure-corrective-context.mjs +11 -34
  131. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  132. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  133. package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
  134. package/hooks/skill-invocation-telemetry.mjs +17 -5
  135. package/hooks/subagent-telemetry.mjs +24 -30
  136. package/monitors/monitors.json +3 -3
  137. package/package.json +9 -1
  138. package/pi/prompts/session.md +2 -2
  139. package/plugin.json +27 -0
  140. package/scripts/autopilot.mjs +26 -12
  141. package/scripts/backfill-abandoned-sessions.mjs +130 -15
  142. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  143. package/scripts/dialectic-deriver.mjs +73 -8
  144. package/scripts/emit-event.mjs +10 -2
  145. package/scripts/export-hw-learnings.mjs +113 -1
  146. package/scripts/generate-agents-skills.mjs +378 -0
  147. package/scripts/generate-cursor-adapter.mjs +45 -8
  148. package/scripts/generate-hook-import-set.mjs +249 -0
  149. package/scripts/lib/agent-status.mjs +13 -2
  150. package/scripts/lib/auq/parse.mjs +5 -29
  151. package/scripts/lib/auto-dialectic.mjs +68 -0
  152. package/scripts/lib/auto-dream.mjs +38 -36
  153. package/scripts/lib/autonomy/suitability.mjs +6 -0
  154. package/scripts/lib/autopilot/loop.mjs +2 -2
  155. package/scripts/lib/autopilot/worktree-pipeline.mjs +82 -6
  156. package/scripts/lib/build-live-signals.mjs +25 -22
  157. package/scripts/lib/ci-status-banner.mjs +220 -75
  158. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  159. package/scripts/lib/cold-start-detector.mjs +23 -14
  160. package/scripts/lib/config/auto-dream.mjs +2 -1
  161. package/scripts/lib/config/block-header.mjs +63 -0
  162. package/scripts/lib/config/block-preprocess.mjs +177 -0
  163. package/scripts/lib/config/broken-window.mjs +2 -1
  164. package/scripts/lib/config/cold-start.mjs +2 -1
  165. package/scripts/lib/config/config-protection.mjs +22 -2
  166. package/scripts/lib/config/context-coverage.mjs +2 -1
  167. package/scripts/lib/config/cross-repo.mjs +2 -1
  168. package/scripts/lib/config/custom-phases.mjs +2 -1
  169. package/scripts/lib/config/dialectic.mjs +2 -1
  170. package/scripts/lib/config/discovery-validator.mjs +9 -3
  171. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  172. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  173. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  174. package/scripts/lib/config/docs-staleness.mjs +2 -1
  175. package/scripts/lib/config/drift-check.mjs +2 -1
  176. package/scripts/lib/config/eval.mjs +2 -1
  177. package/scripts/lib/config/events-rotation.mjs +2 -1
  178. package/scripts/lib/config/evolve.mjs +8 -2
  179. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  180. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  181. package/scripts/lib/config/handover-gate.mjs +2 -1
  182. package/scripts/lib/config/health-endpoints.mjs +388 -0
  183. package/scripts/lib/config/issue-budget.mjs +2 -1
  184. package/scripts/lib/config/loop-guard.mjs +2 -1
  185. package/scripts/lib/config/memory.mjs +2 -1
  186. package/scripts/lib/config/moc-staleness.mjs +2 -1
  187. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  188. package/scripts/lib/config/private-config-dir.mjs +67 -0
  189. package/scripts/lib/config/reconcile.mjs +2 -1
  190. package/scripts/lib/config/remote-hosts.mjs +234 -0
  191. package/scripts/lib/config/section-extractor.mjs +7 -1
  192. package/scripts/lib/config/skill-evolution.mjs +2 -1
  193. package/scripts/lib/config/slopcheck.mjs +2 -1
  194. package/scripts/lib/config/state-md-lock.mjs +2 -1
  195. package/scripts/lib/config/templates-first.mjs +2 -1
  196. package/scripts/lib/config/test.mjs +2 -1
  197. package/scripts/lib/config/vault-integration.mjs +7 -1
  198. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  199. package/scripts/lib/config/vault-staleness.mjs +2 -1
  200. package/scripts/lib/config/vault-sync.mjs +2 -1
  201. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  202. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  203. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  204. package/scripts/lib/config.mjs +31 -3
  205. package/scripts/lib/convergence-monitor.mjs +82 -16
  206. package/scripts/lib/dispatcher/enumerate.mjs +2 -17
  207. package/scripts/lib/dispatcher/rank.mjs +124 -48
  208. package/scripts/lib/ecosystem-health.mjs +16 -2
  209. package/scripts/lib/eval/engine.mjs +9 -1
  210. package/scripts/lib/eval/session-resolve.mjs +23 -4
  211. package/scripts/lib/events-schema.mjs +48 -0
  212. package/scripts/lib/events.mjs +256 -7
  213. package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
  214. package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
  215. package/scripts/lib/frontmatter-guard.mjs +131 -13
  216. package/scripts/lib/gates/gate-full.mjs +26 -0
  217. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  218. package/scripts/lib/gitlab-portfolio/cli.mjs +3 -15
  219. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  220. package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
  221. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  222. package/scripts/lib/host-identity.mjs +50 -11
  223. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  224. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  225. package/scripts/lib/learnings/io.mjs +60 -6
  226. package/scripts/lib/memory-banner.mjs +20 -8
  227. package/scripts/lib/memory-proposals/store.mjs +30 -22
  228. package/scripts/lib/owner-config-banner.mjs +43 -6
  229. package/scripts/lib/owner-config-loader.mjs +21 -10
  230. package/scripts/lib/owner-interview.mjs +3 -3
  231. package/scripts/lib/owner-yaml.mjs +207 -14
  232. package/scripts/lib/peer-discovery.mjs +20 -2
  233. package/scripts/lib/platform.mjs +108 -15
  234. package/scripts/lib/plugin-update-banner.mjs +406 -0
  235. package/scripts/lib/project-hygiene.mjs +38 -2
  236. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  237. package/scripts/lib/quality-gate.mjs +133 -44
  238. package/scripts/lib/reconcile/emitter.mjs +68 -6
  239. package/scripts/lib/reconcile/engine.mjs +249 -9
  240. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  241. package/scripts/lib/reconcile/writer.mjs +40 -18
  242. package/scripts/lib/scope-gate.mjs +36 -0
  243. package/scripts/lib/session-close-backfill.mjs +125 -18
  244. package/scripts/lib/session-discovery.mjs +57 -3
  245. package/scripts/lib/session-end/phase-skip.mjs +2 -2
  246. package/scripts/lib/session-id.mjs +12 -23
  247. package/scripts/lib/session-identity/own-session.mjs +187 -11
  248. package/scripts/lib/session-lock-shape.mjs +43 -0
  249. package/scripts/lib/session-lock.mjs +5 -10
  250. package/scripts/lib/session-registry.mjs +25 -9
  251. package/scripts/lib/session-schema/constants.mjs +36 -2
  252. package/scripts/lib/session-schema/validator.mjs +38 -4
  253. package/scripts/lib/session-start-probes.mjs +18 -1
  254. package/scripts/lib/session-transition.mjs +1 -1
  255. package/scripts/lib/sessions-canonical.mjs +446 -0
  256. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  257. package/scripts/lib/skill-health/join.mjs +17 -4
  258. package/scripts/lib/state-md.mjs +78 -0
  259. package/scripts/lib/sunset/walker.mjs +6 -0
  260. package/scripts/lib/telemetry/schema.mjs +255 -17
  261. package/scripts/lib/telemetry/sync.mjs +417 -24
  262. package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
  263. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  264. package/scripts/lib/validate/check-agents.mjs +3 -3
  265. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  266. package/scripts/lib/validate/check-doc-cli-commands.mjs +9 -33
  267. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
  268. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  269. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  270. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  271. package/scripts/lib/validate/check-skill-script-paths.mjs +455 -0
  272. package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
  273. package/scripts/lib/validate/check-unwired-features.mjs +0 -9
  274. package/scripts/lib/validate/check-validator-registration.mjs +254 -0
  275. package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
  276. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  277. package/scripts/lib/validate/markdown-fences.mjs +196 -0
  278. package/scripts/lib/vault-backfill/template.mjs +63 -6
  279. package/scripts/lib/vault-mirror/process.mjs +165 -42
  280. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  281. package/scripts/lib/vault-status/board-lock.mjs +185 -0
  282. package/scripts/lib/vault-status/board-writer.mjs +174 -135
  283. package/scripts/lib/vault-status/narrative-mirror.mjs +129 -37
  284. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  285. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  286. package/scripts/lib/wave-executor/remote-dispatch.mjs +502 -0
  287. package/scripts/lib/wave-resource-gate.mjs +133 -7
  288. package/scripts/lib/wave-sizing.mjs +4 -1
  289. package/scripts/lib/wave-transcript-tail.mjs +142 -8
  290. package/scripts/materialize-wave-scope.mjs +32 -9
  291. package/scripts/memory-propose.mjs +146 -8
  292. package/scripts/migrate-cold-start-seed.mjs +4 -1
  293. package/scripts/parse-config.mjs +60 -3
  294. package/scripts/promote-vault-strict.mjs +4 -15
  295. package/scripts/release.mjs +337 -29
  296. package/scripts/repair-invalid-sessions.mjs +3 -3
  297. package/scripts/run-quality-gate.mjs +128 -11
  298. package/scripts/site-numbers.mjs +36 -4
  299. package/scripts/sweep-expired-learnings.mjs +90 -0
  300. package/scripts/sync-vault-schema.mjs +3 -1
  301. package/scripts/telemetry.mjs +2 -2
  302. package/scripts/validate-plugin.mjs +187 -0
  303. package/scripts/validate-wave-scope.mjs +28 -8
  304. package/scripts/vault-consolidate.mjs +3 -11
  305. package/scripts/vault-integration-watcher.mjs +2 -4
  306. package/scripts/vault-mirror.mjs +111 -26
  307. package/scripts/wave-scope-binding.mjs +215 -0
  308. package/skills/_shared/instruction-file-resolution.md +10 -0
  309. package/skills/_shared/parallel-aware-auq.md +31 -2
  310. package/skills/_shared/parallel-aware-preamble.md +18 -4
  311. package/skills/_shared/platform-tools.md +1 -1
  312. package/skills/_shared/state-ownership.md +1 -1
  313. package/skills/architecture/SKILL.md +7 -5
  314. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  315. package/skills/autopilot/SKILL.md +4 -18
  316. package/skills/claude-md-drift-check/SKILL.md +5 -1
  317. package/skills/claude-md-drift-check/checker.mjs +62 -2
  318. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  319. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  320. package/skills/discovery/probes-arch.md +20 -18
  321. package/skills/dispatcher/SKILL.md +3 -2
  322. package/skills/ecosystem-health/SKILL.md +4 -1
  323. package/skills/ecosystem-health/wizard.md +5 -0
  324. package/skills/evolve/SKILL.md +87 -11
  325. package/skills/frontmatter-guard/SKILL.md +11 -5
  326. package/skills/npm-publish/SKILL.md +1 -1
  327. package/skills/reconcile/SKILL.md +38 -2
  328. package/skills/remote-offload/SKILL.md +89 -0
  329. package/skills/session-end/SKILL.md +18 -905
  330. package/skills/session-end/phase-3-6-tail.md +19 -9
  331. package/skills/session-end/plan-verification.md +221 -155
  332. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  333. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  334. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  335. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  336. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  337. package/skills/session-end/references/session-summary-template.md +62 -0
  338. package/skills/session-plan/SKILL.md +49 -0
  339. package/skills/session-start/SKILL.md +41 -900
  340. package/skills/session-start/phase-8-5-express-path.md +1 -1
  341. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  342. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  343. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  344. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  345. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  346. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  347. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  348. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  349. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  350. package/skills/vault-sync/validator.mjs +21 -27
  351. package/skills/wave-executor/SKILL.md +16 -2
  352. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  353. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  354. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  355. package/skills/wave-executor/wave-loop.md +14 -1271
  356. package/templates/_shared/journey-manifest.md +10 -6
  357. package/.cursor/commands/autopilot-multi.md +0 -14
  358. package/.cursor/commands/contract-version-bump.md +0 -14
  359. package/.cursor/commands/journey-audit.md +0 -14
  360. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  361. package/.cursor/skills/daily/SKILL.md +0 -12
  362. package/.cursor/skills/domain-model/SKILL.md +0 -13
  363. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  364. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  365. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  366. package/commands/autopilot-multi.md +0 -74
  367. package/commands/contract-version-bump.md +0 -28
  368. package/commands/journey-audit.md +0 -43
  369. package/pi/prompts/autopilot-multi.md +0 -12
  370. package/pi/prompts/contract-version-bump.md +0 -12
  371. package/pi/prompts/journey-audit.md +0 -12
  372. package/scripts/autopilot-multi.mjs +0 -885
  373. package/scripts/backfill-learnings-expires.mjs +0 -196
  374. package/scripts/backfill-learnings.mjs +0 -203
  375. package/scripts/fleet-instruction-scan.mjs +0 -141
  376. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  377. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  378. package/scripts/lib/webhook-url.mjs +0 -105
  379. package/scripts/lifecycle-sim-v6.mjs +0 -347
  380. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  381. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  382. package/scripts/upload-social-preview.mjs +0 -316
  383. package/skills/_shared/model-selection.md +0 -64
  384. package/skills/contract-version-bump/SKILL.md +0 -219
  385. package/skills/daily/SKILL.md +0 -222
  386. package/skills/daily/generate.sh +0 -92
  387. package/skills/daily/templates/daily.md.tpl +0 -36
  388. package/skills/journey-audit/SKILL.md +0 -269
  389. package/skills/skill-creator/SKILL.md +0 -168
  390. package/skills/ubiquitous-language/SKILL.md +0 -97
  391. package/skills/vault-sync/package-lock.json +0 -40
  392. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  393. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -33,7 +33,7 @@ import { spawn } from 'node:child_process';
33
33
 
34
34
  import { shouldRunHook } from './_lib/profile-gate.mjs';
35
35
  import { appendSkillInvocation } from '../scripts/lib/skill-invocations-schema.mjs';
36
- import { SO_PROJECT_DIR } from '../scripts/lib/platform.mjs';
36
+ import { getProjectDir } from '../scripts/lib/platform.mjs';
37
37
  import { shouldDailyFlush } from '../scripts/lib/telemetry/sync.mjs';
38
38
  import { resolveConsent, readTelemetryState } from '../scripts/lib/telemetry/consent.mjs';
39
39
  import { loadOwnerConfig } from '../scripts/lib/owner-yaml.mjs';
@@ -42,9 +42,21 @@ import { loadOwnerConfig } from '../scripts/lib/owner-yaml.mjs';
42
42
  // Constants
43
43
  // ---------------------------------------------------------------------------
44
44
 
45
- const METRICS_DIR = path.join(SO_PROJECT_DIR, '.orchestrator', 'metrics');
45
+ /**
46
+ * Metrics directory / ledger path. FUNCTIONS, not module-level consts:
47
+ * `getProjectDir()` walks the filesystem on first call, and this hook must not
48
+ * pay that at import time (#1153 P5).
49
+ *
50
+ * @returns {string}
51
+ */
52
+ function metricsDirPath() {
53
+ return path.join(getProjectDir(), '.orchestrator', 'metrics');
54
+ }
46
55
 
47
- const JSONL_PATH = path.join(METRICS_DIR, 'skill-invocations.jsonl');
56
+ /** @returns {string} */
57
+ function jsonlPath() {
58
+ return path.join(metricsDirPath(), 'skill-invocations.jsonl');
59
+ }
48
60
 
49
61
  /** Absolute path to the telemetry CLI (carries the hidden `_flush` subcommand). */
50
62
  const TELEMETRY_CLI_PATH = path.resolve(
@@ -123,7 +135,7 @@ export function maybeSpawnDailyFlush({
123
135
  now = Date.now(),
124
136
  statePath,
125
137
  queuePath,
126
- metricsDir = METRICS_DIR,
138
+ metricsDir = metricsDirPath(),
127
139
  } = {}) {
128
140
  try {
129
141
  // 1. Cheapest gate: env kill-switches, no file I/O.
@@ -187,7 +199,7 @@ async function main() {
187
199
  schema_version: 1,
188
200
  };
189
201
 
190
- await appendSkillInvocation(JSONL_PATH, record);
202
+ await appendSkillInvocation(jsonlPath(), record);
191
203
 
192
204
  // Daily-fallback telemetry flush — non-blocking, best-effort, never throws.
193
205
  maybeSpawnDailyFlush();
@@ -84,13 +84,23 @@ if (!shouldRunHook('subagent-telemetry')) process.exit(0);
84
84
  import fs from 'node:fs';
85
85
  import path from 'node:path';
86
86
  import { appendSubagent } from '../scripts/lib/subagents-schema.mjs';
87
- import { SO_PROJECT_DIR } from '../scripts/lib/platform.mjs';
87
+ import { getProjectDir } from '../scripts/lib/platform.mjs';
88
+ import { resolveSubagentSidecar } from './_lib/subagent-paths.mjs';
88
89
 
89
90
  // ---------------------------------------------------------------------------
90
91
  // Constants
91
92
  // ---------------------------------------------------------------------------
92
93
 
93
- const JSONL_PATH = path.join(SO_PROJECT_DIR, '.orchestrator', 'metrics', 'subagents.jsonl');
94
+ /**
95
+ * Absolute path to the subagent ledger. A FUNCTION, not a module-level const:
96
+ * `getProjectDir()` walks the filesystem on first call, and this hook must not
97
+ * pay that at import time (#1153 P5).
98
+ *
99
+ * @returns {string}
100
+ */
101
+ function jsonlPath() {
102
+ return path.join(getProjectDir(), '.orchestrator', 'metrics', 'subagents.jsonl');
103
+ }
94
104
 
95
105
  /**
96
106
  * Defense-in-depth byte ceiling for transcript reads (#624). The transcript path
@@ -489,38 +499,22 @@ function extractTranscriptUsage(transcriptPath) {
489
499
  * Derive the path of the subagent's OWN transcript from the parent transcript
490
500
  * path the harness sends on stdin (#949).
491
501
  *
492
- * The `transcript_path` in a SubagentStop payload is the PARENT session
493
- * transcript — `<transcriptDir>/<parent_session_id>.jsonl`. Alongside it the
494
- * harness maintains one file per real Task subagent at
495
- * `<transcriptDir>/<parent_session_id>/subagents/agent-<agent_id>.jsonl`
496
- * (plus an `agent-<agent_id>.meta.json` carrying `agentType`). Verified against
497
- * live transcripts on 2026-07-31.
498
- *
499
- * Returns null — never the parent path — when the derivation is not possible.
500
- * Falling back to `transcript_path` IS the #949 defect: it makes every stop
501
- * inherit the parent's running token totals, so the caller must record null
502
- * instead (an honest absence).
503
- *
504
- * The agent_id is charset-restricted before it is interpolated into a path.
505
- * Real ids are hex-ish tokens (e.g. `a60348a01ca982b4c`); anything else is
506
- * rejected rather than sanitised, so no payload value can traverse out of the
507
- * `subagents/` directory.
502
+ * Thin wrapper over the consolidated derivation (#1196) —
503
+ * `hooks/_lib/subagent-paths.mjs` `resolveSubagentSidecar()` — which now
504
+ * applies a `{1,64}` bound to `agentId` (STRICTER than this file's prior
505
+ * unbounded `+` charset check; see that module's header for the full
506
+ * divergence table). Returns null — never the parent path — when the
507
+ * derivation is not possible. Falling back to `transcript_path` IS the #949
508
+ * defect: it makes every stop inherit the parent's running token totals, so
509
+ * the caller must record null instead (an honest absence).
508
510
  *
509
511
  * @param {string|undefined|null} parentTranscriptPath — stdin `transcript_path`
510
512
  * @param {string} agentId — the stopping agent's id
511
513
  * @returns {string|null} absolute candidate path, or null when underivable
512
514
  */
513
515
  function resolveSubagentTranscriptPath(parentTranscriptPath, agentId) {
514
- if (typeof parentTranscriptPath !== 'string' || !parentTranscriptPath.trim()) return null;
515
- if (typeof agentId !== 'string' || !/^[A-Za-z0-9_-]+$/.test(agentId)) return null;
516
- // 'unknown' is the no-usable-id fallback — it names no file.
517
- if (agentId === 'unknown') return null;
518
-
519
- const dir = path.dirname(parentTranscriptPath);
520
- const base = path.basename(parentTranscriptPath).replace(/\.jsonl$/i, '');
521
- if (!base || base === '.' || base === '..') return null;
522
-
523
- return path.join(dir, base, 'subagents', `agent-${agentId}.jsonl`);
516
+ const sidecar = resolveSubagentSidecar({ transcriptPath: parentTranscriptPath, agentId });
517
+ return sidecar === null ? null : sidecar.transcript;
524
518
  }
525
519
 
526
520
  /**
@@ -684,7 +678,7 @@ async function main() {
684
678
  // duration measurement and the explicit orphan discriminator below.
685
679
  // 'unknown' is the agent-id fallback for a payload with no usable id —
686
680
  // joining on it would collide across unrelated agents, so never scan for it.
687
- const startedAtMs = agentId === 'unknown' ? null : findStartTimestampMs(JSONL_PATH, agentId);
681
+ const startedAtMs = agentId === 'unknown' ? null : findStartTimestampMs(jsonlPath(), agentId);
688
682
 
689
683
  // duration_ms (#917): prefer the harness value, else join backwards to this
690
684
  // agent's own start record, else null. Never 0 — see resolveDurationMs().
@@ -732,7 +726,7 @@ async function main() {
732
726
  record['gen_ai.system'] = 'anthropic';
733
727
  }
734
728
 
735
- await appendSubagent(JSONL_PATH, record);
729
+ await appendSubagent(jsonlPath(), record);
736
730
  }
737
731
 
738
732
  // Exit 0 always — informational hook must never block Claude.
@@ -2,13 +2,13 @@
2
2
  {
3
3
  "name": "ecosystem-health",
4
4
  "command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/lib/ecosystem-health.mjs --watch",
5
- "description": "Ecosystem repo health watcher (CI status, branch drift, stale baselines across vault-registered projects). Emits one NDJSON line per detected state change.",
6
- "when": "on-skill-invoke:ecosystem-health"
5
+ "description": "Ecosystem repo health watcher (CI status, branch drift, stale baselines across vault-registered projects). Emits one NDJSON line per detected state change. Hung on session-start because the previous trigger (on-skill-invoke:ecosystem-health) was self-referential: it could only start when the ecosystem-health skill ran, and that skill has 0 recorded invocations fleet-wide, so the watcher never started once.",
6
+ "when": "on-skill-invoke:session-start"
7
7
  },
8
8
  {
9
9
  "name": "convergence-monitor",
10
10
  "command": "node \"${CLAUDE_PLUGIN_ROOT}\"/scripts/lib/convergence-monitor.mjs --tail",
11
- "description": "Wave convergence telemetry monitor — tails .orchestrator/metrics/events.jsonl + sessions.jsonl, surfaces convergence signals (shrinking diff, pass-rate plateau, velocity).",
11
+ "description": "Wave convergence telemetry monitor — tails .orchestrator/metrics/events.jsonl (that file only; it never opens sessions.jsonl) and surfaces convergence signals (shrinking diff, pass-rate plateau, velocity).",
12
12
  "when": "on-skill-invoke:wave-executor"
13
13
  },
14
14
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "session-orchestrator",
3
- "version": "3.23.0",
3
+ "version": "4.0.0",
4
4
  "description": "Loop engineering for AI coding agents — turn ad-hoc sessions into a repeatable research → plan → wave-execute → close loop with verification gates. Runs on Claude Code, Codex CLI, Cursor, and Pi.",
5
5
  "type": "module",
6
6
  "homepage": "https://session-orchestrator.com",
@@ -23,7 +23,9 @@
23
23
  },
24
24
  "files": [
25
25
  ".mcp.json",
26
+ "AGENTS.md",
26
27
  "CHANGELOG.md",
28
+ "plugin.json",
27
29
  "pi/",
28
30
  "skills/",
29
31
  "agents/",
@@ -37,11 +39,14 @@
37
39
  "assets/",
38
40
  ".claude-plugin/",
39
41
  ".codex-plugin/",
42
+ ".agents/",
40
43
  ".cursor/",
44
+ ".orchestrator/policy/",
41
45
  "docs/*.md",
42
46
  "docs/telemetry/telemetry-claims.md",
43
47
  "NOTICE",
44
48
  "SECURITY.md",
49
+ "!skills/vault-sync/package-lock.json",
45
50
  "!scripts/tests/**",
46
51
  "!skills/vault-sync/tests/**",
47
52
  "!**/node_modules/**",
@@ -66,9 +71,12 @@
66
71
  "ajv": "^8.17.1",
67
72
  "js-yaml": "^4.3.1",
68
73
  "mdast-util-to-string": "^4.0.0",
74
+ "picomatch": "^4.0.4",
69
75
  "remark": "^15.0.0",
70
76
  "remark-parse": "^11.0.0",
71
77
  "unified": "^11.0.0",
78
+ "yaml": "^2.9.0",
79
+ "zod": "^3.25.76",
72
80
  "zx": "^8.1.0"
73
81
  },
74
82
  "devDependencies": {
@@ -1,6 +1,6 @@
1
1
  ---
2
- description: Start a development session (housekeeping, feature, deep)
3
- argument-hint: "[housekeeping|feature|deep]"
2
+ description: Start a development session (housekeeping, feature, deep; ultradeep = deep + profile)
3
+ argument-hint: "[housekeeping|feature|deep|ultradeep]"
4
4
  ---
5
5
 
6
6
  # /session
package/plugin.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
+ "name": "session-orchestrator",
4
+ "version": "4.0.0",
5
+ "description": "Loop engineering for AI coding agents — turn ad-hoc sessions into a repeatable research → plan → wave-execute → close loop with verification gates. Runs on Claude Code, Codex CLI, Cursor, and Pi.",
6
+ "author": {
7
+ "name": "Bernhard Goetzendorfer"
8
+ },
9
+ "homepage": "https://session-orchestrator.com",
10
+ "repository": "https://github.com/Kanevry/session-orchestrator",
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "session",
14
+ "orchestration",
15
+ "waves",
16
+ "gitlab",
17
+ "github",
18
+ "quality-gates",
19
+ "verification-gates",
20
+ "multi-agent",
21
+ "codex",
22
+ "cursor",
23
+ "pi"
24
+ ],
25
+ "skills": "./skills/",
26
+ "mcpServers": "./.mcp.json"
27
+ }
@@ -47,6 +47,7 @@ import { selectMode } from './lib/mode-selector.mjs';
47
47
  import { probe, evaluate, DEFAULT_RESOURCE_THRESHOLDS } from './lib/resource-probe.mjs';
48
48
  import { detectPeers } from './lib/session-registry.mjs';
49
49
  import { normalizeSession } from './lib/session-schema.mjs';
50
+ import { readCanonicalSessions } from './lib/sessions-canonical.mjs';
50
51
 
51
52
  // ---------------------------------------------------------------------------
52
53
  // CLI-level flag extraction
@@ -174,17 +175,29 @@ const branch = detectBranch();
174
175
  const SESSIONS_JSONL_PATH = resolve('.orchestrator/metrics/sessions.jsonl');
175
176
 
176
177
  /**
177
- * Count the number of non-empty lines in sessions.jsonl. Returns 0 if missing.
178
- * @returns {number}
178
+ * Set of the DISTINCT session identities currently recorded in sessions.jsonl.
179
+ * Empty set when the file is missing or unreadable.
180
+ *
181
+ * Identities, not lines (#1167): the ledger is append-only, so one physical
182
+ * session can occupy two lines (an abandoned stub plus the authoritative
183
+ * record that supersedes it, or the systemic double-stub pair). The
184
+ * pre/post comparison below asks "did the session I just spawned record
185
+ * itself?", and a raw line count answers that question wrong in BOTH
186
+ * directions — a duplicate pair looks like two sessions, while a supersede
187
+ * append (+1 line, −1 stub) leaves a canonical COUNT unchanged. Comparing the
188
+ * id SETS is immune to both.
189
+ *
190
+ * @returns {Set<string>}
179
191
  */
180
- function countSessionLines() {
181
- if (!existsSync(SESSIONS_JSONL_PATH)) return 0;
182
- try {
183
- const raw = readFileSync(SESSIONS_JSONL_PATH, 'utf8');
184
- return raw.split('\n').filter((l) => l.trim().length > 0).length;
185
- } catch {
186
- return 0;
192
+ function readSessionIds() {
193
+ if (!existsSync(SESSIONS_JSONL_PATH)) return new Set();
194
+ const ids = new Set();
195
+ for (const rec of readCanonicalSessions({ filePath: SESSIONS_JSONL_PATH })) {
196
+ if (typeof rec.session_id === 'string' && rec.session_id.length > 0) {
197
+ ids.add(rec.session_id);
198
+ }
187
199
  }
200
+ return ids;
188
201
  }
189
202
 
190
203
  /**
@@ -249,7 +262,7 @@ async function modeSelector() {
249
262
  * @returns {Promise<{session_id: string, agent_summary?: object, effectiveness?: object}>}
250
263
  */
251
264
  async function sessionRunner({ mode, autopilotRunId }) {
252
- const preCount = countSessionLines();
265
+ const preIds = readSessionIds();
253
266
 
254
267
  await new Promise((res, rej) => {
255
268
  const childStdio = hasVerbose
@@ -281,8 +294,9 @@ async function sessionRunner({ mode, autopilotRunId }) {
281
294
  });
282
295
  });
283
296
 
284
- const postCount = countSessionLines();
285
- if (postCount === preCount) {
297
+ const postIds = readSessionIds();
298
+ const appeared = [...postIds].some((id) => !preIds.has(id));
299
+ if (!appeared) {
286
300
  throw new Error('no session record appended');
287
301
  }
288
302
 
@@ -42,10 +42,12 @@ import { parseArgs } from 'node:util';
42
42
  import { fileURLToPath } from 'node:url';
43
43
 
44
44
  import { backfillAbandonedSession, isUuid } from './lib/session-close-backfill.mjs';
45
- import { SO_PROJECT_DIR } from './lib/platform.mjs';
45
+ import { emitEvent } from './lib/events.mjs';
46
+ import { getProjectDir } from './lib/platform.mjs';
46
47
 
47
48
  const LOCK_ACQUIRED = 'orchestrator.session.lock.acquired';
48
49
  const SESSION_STARTED = 'orchestrator.session.started';
50
+ const SESSION_ENDED = 'orchestrator.session.ended';
49
51
 
50
52
  /**
51
53
  * Default cap on how many candidates may reach the (expensive) shared core in a
@@ -54,12 +56,31 @@ const SESSION_STARTED = 'orchestrator.session.started';
54
56
  */
55
57
  export const SESSION_START_LIMIT = 25;
56
58
 
57
- /** Read a JSONL file into parsed objects; missing → []; malformed lines skipped. */
59
+ /**
60
+ * Read a JSONL file into parsed objects; MISSING (ENOENT) → `[]` silently,
61
+ * UNREADABLE (EACCES/EISDIR/…) → `[]` with a stderr WARN (#1188); malformed
62
+ * lines skipped.
63
+ */
58
64
  function readJsonl(filePath) {
59
65
  let raw;
60
66
  try {
61
67
  raw = fs.readFileSync(filePath, 'utf8');
62
- } catch {
68
+ } catch (err) {
69
+ // #1188 — ENOENT and EACCES/EISDIR are different facts: a missing ledger is
70
+ // the ordinary fresh-repo case; an UNREADABLE one previously read as "no
71
+ // records" and made every downstream count silently wrong. Same split as
72
+ // readLockDetailed (session-lock.mjs § absent vs unreadable).
73
+ if (!err || err.code !== 'ENOENT') {
74
+ process.stderr.write(
75
+ `⚠ backfill-abandoned-sessions: cannot read ${filePath} ` +
76
+ `(${err?.code ?? '?'}: ${err?.message ?? String(err)}) — ` +
77
+ 'treating as EMPTY, counts below are floors\n',
78
+ );
79
+ }
80
+ // CEILING (BV-004): still [] rather than throw — this runs on the
81
+ // SessionStart path (SESSION_START_LIMIT), where a permissions fault must
82
+ // not crash the session start. REVISIT if the warn rate in events.jsonl
83
+ // shows masked corruption.
63
84
  return [];
64
85
  }
65
86
  const out = [];
@@ -86,16 +107,22 @@ function readJsonl(filePath) {
86
107
  export function planSessions({ repoRoot }) {
87
108
  const events = readJsonl(path.join(repoRoot, '.orchestrator', 'metrics', 'events.jsonl'));
88
109
 
89
- const semanticByUuid = new Map();
110
+ // Two independent UUID -> semantic bridges (#1167). `lock.acquired` is the
111
+ // original one, but a session that LOST the lock-acquire race never emits it;
112
+ // its `session.ended` event carries `semantic_session_id` all the same (since
113
+ // #1068 AC1) and was never read here. Without that second bridge the
114
+ // candidate resolved semantic=null and the shared core minted a SYNTHETIC id,
115
+ // writing a duplicate stub beside the record the SessionEnd hook had already
116
+ // written under the real semantic id (measured 2026-09-02 @ c3ab480: 8 pairs).
117
+ // lock.acquired keeps precedence — it is the older, mode-carrying attestation.
118
+ const semanticFromLock = new Map();
119
+ const semanticFromEnded = new Map();
90
120
  for (const ev of events) {
91
- if (
92
- ev.event === LOCK_ACQUIRED &&
93
- typeof ev.session_id === 'string' &&
94
- typeof ev.semantic_session_id === 'string'
95
- ) {
96
- semanticByUuid.set(ev.session_id, ev.semantic_session_id);
97
- }
121
+ if (typeof ev.session_id !== 'string' || typeof ev.semantic_session_id !== 'string') continue;
122
+ if (ev.event === LOCK_ACQUIRED) semanticFromLock.set(ev.session_id, ev.semantic_session_id);
123
+ else if (ev.event === SESSION_ENDED) semanticFromEnded.set(ev.session_id, ev.semantic_session_id);
98
124
  }
125
+ const semanticByUuid = new Map([...semanticFromEnded, ...semanticFromLock]);
99
126
 
100
127
  const seen = new Set();
101
128
  const plan = [];
@@ -133,7 +160,14 @@ function readRecordedIds(repoRoot) {
133
160
  const records = readJsonl(path.join(repoRoot, '.orchestrator', 'metrics', 'sessions.jsonl'));
134
161
  const ids = new Set();
135
162
  for (const r of records) {
136
- if (r && typeof r.session_id === 'string') ids.add(r.session_id);
163
+ if (!r) continue;
164
+ if (typeof r.session_id === 'string') ids.add(r.session_id);
165
+ // #1167 — a backfilled record now stamps the harness UUID it was
166
+ // reconstructed from. Indexing it lets a bare-UUID candidate be recognised
167
+ // as already-recorded even though the record itself is keyed semantically —
168
+ // the join that was structurally impossible while the field was absent
169
+ // (0 of 286 records carried it, measured 2026-09-02 @ c3ab480).
170
+ if (typeof r.raw_session_id === 'string') ids.add(r.raw_session_id);
137
171
  }
138
172
  return ids;
139
173
  }
@@ -238,6 +272,7 @@ export async function runMigration({
238
272
  case 'backfilled':
239
273
  summary.backfilled += 1;
240
274
  if (res.deadByAge) summary.dead_by_age += 1;
275
+ await emitBackfillCompleted(repoRoot, item, res);
241
276
  // Keep the pre-filter snapshot in step with what we just wrote, so a
242
277
  // second candidate bridging to the SAME semantic id is skipped cheaply
243
278
  // instead of re-entering the core (which would reach the same verdict).
@@ -263,6 +298,40 @@ export async function runMigration({
263
298
  return summary;
264
299
  }
265
300
 
301
+ /**
302
+ * Emit `orchestrator.session.backfill_completed` for a record THIS path wrote
303
+ * (#1167). Mirrors the payload of `hooks/on-session-end.mjs::emitBackfillOutcome`
304
+ * so both writers are queryable with one filter.
305
+ *
306
+ * Until now the startup/CLI path wrote records SILENTLY: only the SessionEnd
307
+ * hook emitted the event, so nothing in the event stream distinguished "the
308
+ * backfill never ran" from "the backfill ran here". Best-effort by contract —
309
+ * observability must never fail a migration run.
310
+ *
311
+ * @param {string} repoRoot
312
+ * @param {{sessionId: string, semanticSessionId: string|null}} item
313
+ * @param {{action: string, sessionId?: string, supersedes?: string, error?: string}} res
314
+ */
315
+ async function emitBackfillCompleted(repoRoot, item, res) {
316
+ try {
317
+ await emitEvent(
318
+ 'orchestrator.session.backfill_completed',
319
+ {
320
+ kind: 'abandoned',
321
+ action: typeof res?.action === 'string' ? res.action : 'unknown',
322
+ ...(typeof item?.sessionId === 'string' ? { session_id: item.sessionId } : {}),
323
+ ...(typeof item?.semanticSessionId === 'string'
324
+ ? { semantic_session_id: item.semanticSessionId }
325
+ : {}),
326
+ ...(typeof res?.sessionId === 'string' ? { record_id: res.sessionId } : {}),
327
+ ...(typeof res?.supersedes === 'string' ? { supersedes: res.supersedes } : {}),
328
+ ...(typeof res?.error === 'string' ? { reason: res.error } : {}),
329
+ },
330
+ { repoRoot },
331
+ );
332
+ } catch { /* observability is best-effort */ }
333
+ }
334
+
266
335
  /**
267
336
  * SessionStart entry point (#926) — decouple the backfill from the /close path.
268
337
  *
@@ -313,13 +382,59 @@ export async function backfillOnSessionStart({ repoRoot, limit = SESSION_START_L
313
382
  if (typeof repoRoot !== 'string' || repoRoot.length === 0) return null;
314
383
  // newestFirst is mandatory here — see runMigration's param docs for the
315
384
  // measured failure (budget burned on ancient already-recorded candidates).
316
- return await runMigration({ repoRoot, apply: true, limit, newestFirst: true });
385
+ const summary = await runMigration({ repoRoot, apply: true, limit, newestFirst: true });
386
+ // The telemetry flush rides the SAME start-time trigger, for the same reason
387
+ // the backfill does — see flushTelemetryOnSessionStart.
388
+ await flushTelemetryOnSessionStart(repoRoot);
389
+ return summary;
317
390
  } catch {
318
391
  // Swallowed by contract — see the docblock. The hook is informational-only.
319
392
  return null;
320
393
  }
321
394
  }
322
395
 
396
+ /** POST budget for the start-time telemetry drain (ms). Deliberately tighter than
397
+ * the close-time budget: a session START must not wait on the network. Expiry is
398
+ * LOSSLESS — flush() routes a timed-out batch into the offline queue. */
399
+ const STARTUP_FLUSH_TIMEOUT_MS = 1500;
400
+
401
+ /**
402
+ * SECOND FLUSH TRIGGER (GitLab #1234) — drain the PREVIOUS session's telemetry
403
+ * queue at the NEXT session's start.
404
+ *
405
+ * THE BUG THIS FIXES: the mechanical flush (#1138) lives only in
406
+ * `hooks/on-session-end.mjs`, which fires on a REGULAR close. Measured 2026-09-06
407
+ * over 90 fleet days, the real close rate is 21,3 % (429 clean closes / 2.016
408
+ * distinct `session.started` ids) — so roughly four sessions in five never reach
409
+ * the only code path that sends. This is the exact argument
410
+ * `backfillOnSessionStart` already makes for the ledger, applied to the ping:
411
+ * SessionStart is the trigger that survives whatever killed the last session.
412
+ *
413
+ * Three properties make this safe to run on the hot start path:
414
+ * - LAZY IMPORT. `sync.mjs` is pulled in at CALL time, not module load. Loading
415
+ * it statically would put the whole telemetry subtree onto the SessionStart
416
+ * hook's import graph — the graph `tests/hooks/on-stop.test.mjs` guards for
417
+ * bare specifiers, and the cost would be paid on every start whether or not
418
+ * telemetry is even consented to.
419
+ * - GATED, ALWAYS. `flush()` opens with `resolveConsent()`; with no consent it
420
+ * returns before any network, queue write, or anon-ID mint.
421
+ * - NEVER THROWS, NEVER BLOCKS A START. Every failure is swallowed, and the POST
422
+ * is bounded at STARTUP_FLUSH_TIMEOUT_MS; a timeout queues the batch rather
423
+ * than losing it.
424
+ *
425
+ * @param {string} repoRoot
426
+ * @returns {Promise<void>}
427
+ */
428
+ async function flushTelemetryOnSessionStart(repoRoot) {
429
+ try {
430
+ const { flush } = await import('./lib/telemetry/sync.mjs');
431
+ await flush({
432
+ metricsDir: path.join(repoRoot, '.orchestrator', 'metrics'),
433
+ timeoutMs: STARTUP_FLUSH_TIMEOUT_MS,
434
+ });
435
+ } catch { /* fire-and-forget by contract */ }
436
+ }
437
+
323
438
  function renderHuman(summary) {
324
439
  const lines = [];
325
440
  lines.push(`Backfill abandoned sessions — ${summary.mode}`);
@@ -372,7 +487,7 @@ async function main() {
372
487
  ' --dry-run preview only (DEFAULT — nothing is written)\n' +
373
488
  ' --apply synthesize + append abandoned-session stubs\n' +
374
489
  ' --json emit the summary as JSON to stdout\n' +
375
- ' --repo-root override the project root (default: resolved SO_PROJECT_DIR)\n' +
490
+ ' --repo-root override the project root (default: resolved project dir)\n' +
376
491
  ' --assume-dead-before ISO-8601 cutoff; a candidate whose last known event\n' +
377
492
  ' predates it bypasses a live foreign session.lock\n' +
378
493
  'Exit codes: 0 ok, 1 arg error, 2 system error\n'
@@ -382,7 +497,7 @@ async function main() {
382
497
 
383
498
  // --apply is an explicit opt-in; absent it (or with --dry-run) we never write.
384
499
  const apply = values.apply === true && values['dry-run'] !== true;
385
- const repoRoot = values['repo-root'] || SO_PROJECT_DIR;
500
+ const repoRoot = values['repo-root'] || getProjectDir();
386
501
 
387
502
  let assumeDeadBeforeMs = null;
388
503
  if (typeof values['assume-dead-before'] === 'string' && values['assume-dead-before'].length > 0) {
@@ -71,7 +71,7 @@ import { parseSessionConfig } from './lib/config.mjs';
71
71
  import { subjectToSlug, parseFrontmatter } from './lib/vault-mirror/utils.mjs';
72
72
  import { kebab } from './lib/learnings/kebab.mjs';
73
73
  import { validateLearning } from './lib/learnings/schema.mjs';
74
- import { appendLearning } from './lib/learnings/io.mjs';
74
+ import { appendLearning, isBackupOf } from './lib/learnings/io.mjs';
75
75
 
76
76
  const DEFAULT_RULES_DIR = '.claude/rules';
77
77
  const DEFAULT_STORE = '.orchestrator/metrics/learnings.jsonl';
@@ -620,7 +620,13 @@ function idsInStore(filePath) {
620
620
  return ids;
621
621
  }
622
622
 
623
- /** List `<store>.bak-*` siblings (a verbatim original beats any reconstruction). */
623
+ /**
624
+ * List `<store>.bak[-.]*` siblings (a verbatim original beats any
625
+ * reconstruction). Both delimiter forms count — the legacy dot form
626
+ * (`learnings.jsonl.bak.evolve-<ts>`) is still on disk in consumer repos and
627
+ * was silently skipped here, i.e. a backup that exists and is not found in the
628
+ * one case it is for (#1173).
629
+ */
624
630
  function backupPaths(storePath) {
625
631
  const dir = dirname(storePath);
626
632
  const base = basename(storePath);
@@ -633,7 +639,7 @@ function backupPaths(storePath) {
633
639
  return [];
634
640
  }
635
641
  return names
636
- .filter((n) => n.startsWith(`${base}.bak-`))
642
+ .filter((n) => isBackupOf(base, n))
637
643
  .sort()
638
644
  .map((n) => join(dir, n));
639
645
  }