session-orchestrator 5.11.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.claude-plugin/marketplace.json +12 -1
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/.codex-plugin/agents/explorer.toml +1 -1
  4. package/.codex-plugin/agents/session-reviewer.toml +2 -2
  5. package/.codex-plugin/agents/wave-worker.toml +1 -1
  6. package/.codex-plugin/plugin.json +1 -1
  7. package/.cursor-plugin/plugin.json +1 -1
  8. package/.orchestrator/policy/blocked-commands.json +8 -0
  9. package/.orchestrator/policy/public-export.json +30 -0
  10. package/CHANGELOG.md +180 -3
  11. package/README.md +126 -117
  12. package/agents/analyst.md +7 -2
  13. package/agents/architect-reviewer.md +7 -2
  14. package/agents/code-implementer.md +4 -2
  15. package/agents/db-specialist.md +9 -2
  16. package/agents/dialectic-deriver.md +9 -3
  17. package/agents/docs-writer.md +7 -2
  18. package/agents/eval-judge.md +7 -1
  19. package/agents/qa-strategist.md +7 -2
  20. package/agents/security-reviewer.md +9 -2
  21. package/agents/session-reviewer.md +18 -2
  22. package/agents/skill-applied-judge.md +7 -1
  23. package/agents/test-writer.md +9 -2
  24. package/agents/ui-developer.md +9 -2
  25. package/agents/ux-evaluator.md +6 -1
  26. package/commands/session.md +4 -2
  27. package/commands/templates-ack.md +89 -18
  28. package/docs/agent-authoring.md +30 -11
  29. package/docs/ci-setup.md +38 -2
  30. package/docs/codex-setup.md +33 -9
  31. package/docs/components.md +13 -2
  32. package/docs/cursor-setup.md +3 -3
  33. package/docs/events-schema.md +5 -4
  34. package/docs/evidence-candidates.md +63 -0
  35. package/docs/fleet-checkin-audit.md +7 -4
  36. package/docs/github-mirror-protection.md +150 -3
  37. package/docs/install.md +102 -25
  38. package/docs/instruction-delivery.md +1 -1
  39. package/docs/migration-v4.md +2 -2
  40. package/docs/mods.md +118 -0
  41. package/docs/owner-config-schema.md +1 -0
  42. package/docs/plugin-architecture-v3.md +1 -1
  43. package/docs/pm-skills-marketplace.md +4 -3
  44. package/docs/rule-authoring.md +24 -13
  45. package/docs/scope-collision-guard.md +2 -1
  46. package/docs/session-config-reference.md +55 -21
  47. package/docs/session-config-template.md +32 -24
  48. package/docs/sessions-ledger-repair.md +137 -0
  49. package/docs/vault-docs-architecture.md +1 -1
  50. package/hooks/_lib/hook-import-set.json +406 -73
  51. package/hooks/_lib/hook-root.mjs +301 -0
  52. package/hooks/_lib/lock-bootstrap.mjs +434 -4
  53. package/hooks/_lib/orchestrator-ignore.mjs +3 -0
  54. package/hooks/_lib/subagent-transcript.mjs +2 -2
  55. package/hooks/_lib/vcs-create-matcher.mjs +13 -17
  56. package/hooks/cwd-change-restore.mjs +5 -1
  57. package/hooks/enforce-commands.mjs +4 -3
  58. package/hooks/enforce-scope.mjs +11 -1
  59. package/hooks/hooks-codex.json +48 -2
  60. package/hooks/hooks.json +31 -31
  61. package/hooks/on-session-end.mjs +38 -15
  62. package/hooks/on-session-start.mjs +158 -59
  63. package/hooks/on-stop.mjs +11 -6
  64. package/hooks/post-bash-issue-budget-refund.mjs +6 -1
  65. package/hooks/post-bash-write-verify.mjs +8 -4
  66. package/hooks/post-edit-validate.mjs +45 -52
  67. package/hooks/post-subagent-discovery-validator.mjs +2 -2
  68. package/hooks/post-tool-batch-wave-signal.mjs +8 -4
  69. package/hooks/post-tool-failure-corrective-context.mjs +5 -1
  70. package/hooks/pre-bash-destructive-guard.mjs +220 -8
  71. package/hooks/pre-bash-issue-budget.mjs +6 -1
  72. package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
  73. package/hooks/pre-bash-staging-fence.mjs +258 -14
  74. package/hooks/pre-bash-templates-first.mjs +28 -27
  75. package/hooks/run-node.sh +1 -1
  76. package/hooks/skill-invocation-telemetry.mjs +15 -8
  77. package/hooks/subagent-telemetry.mjs +77 -14
  78. package/hooks/wave-scope-commit-guard.mjs +143 -14
  79. package/mods/so-hud/.claude-plugin/plugin.json +11 -0
  80. package/mods/so-hud/README.md +57 -0
  81. package/mods/so-hud/hooks/beacon-io.mjs +101 -0
  82. package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
  83. package/mods/so-hud/hooks/hooks.json +1 -0
  84. package/mods/so-hud/hooks/logic.ts +182 -0
  85. package/mods/so-hud/hooks/register.tsx +670 -0
  86. package/mods/so-hud/hooks/rules.ts +2998 -0
  87. package/mods/so-hud/tsconfig.json +8 -0
  88. package/mods/so-hud/types/index.d.ts +36 -0
  89. package/package.json +4 -3
  90. package/rules/_index.md +1 -1
  91. package/rules/always-on/ask-via-tool.md +1 -1
  92. package/rules/always-on/loop-and-monitor.md +17 -194
  93. package/rules/always-on/parallel-sessions.md +3 -1
  94. package/rules/opt-in-stack/backend-data.md +1 -1
  95. package/rules/opt-in-stack/backend.md +1 -1
  96. package/rules/opt-in-stack/security-web.md +5 -1
  97. package/scripts/agent-usage-census.mjs +79 -0
  98. package/scripts/apply-session-learnings.mjs +34 -6
  99. package/scripts/archive-closed-prds.mjs +21 -5
  100. package/scripts/autopilot.mjs +11 -5
  101. package/scripts/backfill-abandoned-sessions.mjs +13 -3
  102. package/scripts/backfill-evidence-digest.mjs +2 -1
  103. package/scripts/backfill-learnings-from-vault.mjs +61 -6
  104. package/scripts/backfill-sessions.mjs +16 -2
  105. package/scripts/check-package-manager.mjs +26 -8
  106. package/scripts/check-sessions-integrity.mjs +4 -1
  107. package/scripts/ci/assert-vitest-green.mjs +3 -0
  108. package/scripts/ci/windows-smoke.mjs +201 -0
  109. package/scripts/close-precheck.mjs +55 -0
  110. package/scripts/dialectic-deriver.mjs +20 -12
  111. package/scripts/emit-event.mjs +2 -2
  112. package/scripts/emit-session.mjs +239 -20
  113. package/scripts/eval-session.mjs +17 -3
  114. package/scripts/export-hw-learnings.mjs +29 -2
  115. package/scripts/fleet-checkin-audit.mjs +8 -4
  116. package/scripts/fleet-checkin.mjs +73 -3
  117. package/scripts/gc-stale-worktrees.mjs +6 -1
  118. package/scripts/github-export.mjs +128 -0
  119. package/scripts/github-protection-audit.mjs +3 -1
  120. package/scripts/handover-queue-check.mjs +209 -0
  121. package/scripts/harness-audit.mjs +6 -4
  122. package/scripts/lib/agent-frontmatter.mjs +57 -10
  123. package/scripts/lib/agent-status.mjs +125 -7
  124. package/scripts/lib/agent-usage-census.mjs +394 -0
  125. package/scripts/lib/auto-dialectic.mjs +17 -10
  126. package/scripts/lib/auto-dream.mjs +22 -14
  127. package/scripts/lib/autopilot/loop.mjs +3 -1
  128. package/scripts/lib/autopilot/recent-runs.mjs +4 -2
  129. package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
  130. package/scripts/lib/autopilot/telemetry.mjs +5 -1
  131. package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
  132. package/scripts/lib/baseline-archetypes.mjs +10 -2
  133. package/scripts/lib/blocked-commands-policy.mjs +4 -3
  134. package/scripts/lib/build-live-signals.mjs +13 -9
  135. package/scripts/lib/ci-status-banner.mjs +329 -24
  136. package/scripts/lib/codex/plugin-contract.mjs +8 -0
  137. package/scripts/lib/cold-start-detector.mjs +5 -2
  138. package/scripts/lib/command-blocker.mjs +603 -43
  139. package/scripts/lib/common.mjs +3 -2
  140. package/scripts/lib/config/cross-repo.mjs +74 -7
  141. package/scripts/lib/config/host-paths.mjs +6 -2
  142. package/scripts/lib/config/remote-hosts.mjs +2 -2
  143. package/scripts/lib/config/section-extractor.mjs +65 -11
  144. package/scripts/lib/config/template-keys.mjs +153 -0
  145. package/scripts/lib/config/vault-integration.mjs +1 -0
  146. package/scripts/lib/config.mjs +3 -3
  147. package/scripts/lib/coordinator-snapshot.mjs +1 -1
  148. package/scripts/lib/discovery/triage-state.mjs +2 -1
  149. package/scripts/lib/dispatcher/rank.mjs +5 -7
  150. package/scripts/lib/ecosystem-health.mjs +2 -2
  151. package/scripts/lib/eval/engine.mjs +3 -2
  152. package/scripts/lib/eval/session-resolve.mjs +6 -2
  153. package/scripts/lib/eval/sink.mjs +4 -3
  154. package/scripts/lib/events-retention-banner.mjs +3 -2
  155. package/scripts/lib/events-schema.mjs +10 -0
  156. package/scripts/lib/events.mjs +23 -12
  157. package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
  158. package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
  159. package/scripts/lib/fleet-protocol.mjs +21 -0
  160. package/scripts/lib/gates/gate-full.mjs +172 -11
  161. package/scripts/lib/gates/gate-helpers.mjs +236 -23
  162. package/scripts/lib/git-config-drift.mjs +141 -7
  163. package/scripts/lib/github-export.mjs +447 -0
  164. package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
  165. package/scripts/lib/handover-queue-marks.mjs +159 -0
  166. package/scripts/lib/hardware-pattern-detector.mjs +103 -13
  167. package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
  168. package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
  169. package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
  170. package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
  171. package/scripts/lib/host-identity.mjs +9 -5
  172. package/scripts/lib/http-request.mjs +64 -0
  173. package/scripts/lib/instruction-budget-guard.mjs +30 -22
  174. package/scripts/lib/io.mjs +6 -3
  175. package/scripts/lib/issue-budget-reconcile.mjs +3 -2
  176. package/scripts/lib/issue-budget.mjs +36 -7
  177. package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
  178. package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
  179. package/scripts/lib/learnings/io.mjs +289 -15
  180. package/scripts/lib/learnings/select.mjs +53 -4
  181. package/scripts/lib/learnings/store-role.mjs +209 -0
  182. package/scripts/lib/learnings/surface.mjs +46 -7
  183. package/scripts/lib/ledger-path.mjs +113 -0
  184. package/scripts/lib/maintenance-due-banner.mjs +63 -12
  185. package/scripts/lib/memory-banner.mjs +5 -6
  186. package/scripts/lib/memory-proposals/collector.mjs +5 -2
  187. package/scripts/lib/memory-proposals/sink.mjs +7 -4
  188. package/scripts/lib/memory-proposals/store.mjs +22 -5
  189. package/scripts/lib/mode-selector-accuracy.mjs +2 -1
  190. package/scripts/lib/no-follow-open.mjs +57 -0
  191. package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
  192. package/scripts/lib/own-lock-root.mjs +95 -0
  193. package/scripts/lib/owner-interview.mjs +2 -1
  194. package/scripts/lib/owner-yaml.mjs +70 -7
  195. package/scripts/lib/platform.mjs +161 -9
  196. package/scripts/lib/plugin-update-banner.mjs +15 -11
  197. package/scripts/lib/pre-push-local-partial.mjs +117 -0
  198. package/scripts/lib/pre-push-remote-gate.mjs +151 -25
  199. package/scripts/lib/process-group.mjs +270 -34
  200. package/scripts/lib/project-hygiene.mjs +28 -0
  201. package/scripts/lib/quality-gate.mjs +33 -9
  202. package/scripts/lib/reconcile/backlog.mjs +29 -15
  203. package/scripts/lib/reconcile/engine.mjs +26 -2
  204. package/scripts/lib/reconcile/idempotency.mjs +65 -3
  205. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
  206. package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
  207. package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
  208. package/scripts/lib/resource-probe/evaluate.mjs +32 -9
  209. package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
  210. package/scripts/lib/rule-loader.mjs +20 -10
  211. package/scripts/lib/scope-baseline.mjs +123 -8
  212. package/scripts/lib/scope-echo.mjs +5 -2
  213. package/scripts/lib/scope-gate.mjs +55 -20
  214. package/scripts/lib/session-close-backfill.mjs +75 -7
  215. package/scripts/lib/session-end/close-precheck.mjs +151 -0
  216. package/scripts/lib/session-end/phase-skip.mjs +8 -7
  217. package/scripts/lib/session-end/tail-runner.mjs +7 -2
  218. package/scripts/lib/session-end/vault-orphans.mjs +205 -0
  219. package/scripts/lib/session-id.mjs +9 -1
  220. package/scripts/lib/session-identity/own-session.mjs +63 -0
  221. package/scripts/lib/session-invocation.mjs +14 -3
  222. package/scripts/lib/session-lock.mjs +114 -0
  223. package/scripts/lib/session-record-repair.mjs +245 -35
  224. package/scripts/lib/session-registry.mjs +85 -12
  225. package/scripts/lib/session-schema/constants.mjs +9 -0
  226. package/scripts/lib/session-schema/filters.mjs +56 -1
  227. package/scripts/lib/session-schema/validator.mjs +15 -5
  228. package/scripts/lib/session-start-probes.mjs +149 -22
  229. package/scripts/lib/session-token-rollup.mjs +2 -1
  230. package/scripts/lib/session-work-trees.mjs +166 -0
  231. package/scripts/lib/sessions-canonical.mjs +61 -3
  232. package/scripts/lib/sessions-integrity-banner.mjs +17 -39
  233. package/scripts/lib/sessions-staleness-banner.mjs +8 -5
  234. package/scripts/lib/sh-quote.mjs +1 -0
  235. package/scripts/lib/skill-evidence-window.mjs +5 -1
  236. package/scripts/lib/skill-health/join.mjs +10 -9
  237. package/scripts/lib/skill-invocations-schema.mjs +13 -0
  238. package/scripts/lib/skill-judgments-schema.mjs +6 -2
  239. package/scripts/lib/snapshot-probe.mjs +414 -0
  240. package/scripts/lib/so-hud/collect.mjs +174 -0
  241. package/scripts/lib/so-hud/runtime.mjs +87 -0
  242. package/scripts/lib/spiral-carryover.mjs +8 -1
  243. package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
  244. package/scripts/lib/state-md/idle-reset.mjs +248 -0
  245. package/scripts/lib/state-md/mission-status.mjs +5 -1
  246. package/scripts/lib/state-md/recommendations.mjs +26 -4
  247. package/scripts/lib/state-md-peer-guard.mjs +2 -0
  248. package/scripts/lib/state-md.mjs +31 -8
  249. package/scripts/lib/sunset/walker.mjs +4 -3
  250. package/scripts/lib/telemetry/consent.mjs +4 -2
  251. package/scripts/lib/telemetry/queue.mjs +3 -3
  252. package/scripts/lib/telemetry/sync.mjs +45 -9
  253. package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
  254. package/scripts/lib/tmux-layout/layouts.mjs +20 -3
  255. package/scripts/lib/ux-grill/paths.mjs +5 -1
  256. package/scripts/lib/validate/check-agents.mjs +37 -6
  257. package/scripts/lib/validate/check-component-paths.mjs +25 -1
  258. package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
  259. package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
  260. package/scripts/lib/validate/check-json-files.mjs +3 -1
  261. package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
  262. package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
  263. package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
  264. package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
  265. package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
  266. package/scripts/lib/validate/check-unwired-features.mjs +4 -2
  267. package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
  268. package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
  269. package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
  270. package/scripts/lib/validate/confidential-names.mjs +3 -1
  271. package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
  272. package/scripts/lib/vault-mirror/namespace.mjs +22 -9
  273. package/scripts/lib/vault-mirror/process.mjs +197 -65
  274. package/scripts/lib/vault-mirror/prune.mjs +5 -2
  275. package/scripts/lib/vault-mirror/render-learnings.mjs +8 -7
  276. package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
  277. package/scripts/lib/vault-mirror/retention.mjs +2 -2
  278. package/scripts/lib/vault-mirror/utils.mjs +106 -13
  279. package/scripts/lib/vault-staleness-banner.mjs +4 -7
  280. package/scripts/lib/vault-status/board-writer.mjs +34 -5
  281. package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
  282. package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
  283. package/scripts/lib/win-process-tree.mjs +137 -0
  284. package/scripts/lib/workspace.mjs +1 -1
  285. package/scripts/lib/worktree/constants.mjs +1 -1
  286. package/scripts/lib/worktree/lifecycle.mjs +1 -1
  287. package/scripts/lib/worktree/listing.mjs +1 -1
  288. package/scripts/lib/zx.mjs +50 -0
  289. package/scripts/mcp-server.mjs +16 -7
  290. package/scripts/mcp-server.sh +13 -83
  291. package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
  292. package/scripts/memory-propose.mjs +10 -3
  293. package/scripts/migrate-cold-start-seed.mjs +7 -0
  294. package/scripts/migrate-legacy-learnings.sh +22 -0
  295. package/scripts/migrate-sessions-jsonl.mjs +16 -2
  296. package/scripts/migrate-vault-paths.mjs +29 -13
  297. package/scripts/parse-config.mjs +71 -14
  298. package/scripts/print-learnings-index.mjs +25 -2
  299. package/scripts/release-session-lock.mjs +94 -7
  300. package/scripts/release.mjs +451 -24
  301. package/scripts/relocate-vault-corpus.mjs +11 -7
  302. package/scripts/repair-invalid-sessions.mjs +68 -3
  303. package/scripts/resolve-session-invocation.mjs +57 -8
  304. package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
  305. package/scripts/run-quality-gate.mjs +33 -7
  306. package/scripts/self-update.mjs +56 -7
  307. package/scripts/session-commit-files.mjs +133 -0
  308. package/scripts/site-numbers.mjs +52 -9
  309. package/scripts/snapshot-probe.mjs +156 -0
  310. package/scripts/so-hud.mjs +68 -0
  311. package/scripts/state-md-idle-reset.mjs +82 -0
  312. package/scripts/sweep-expired-learnings.mjs +54 -6
  313. package/scripts/sweep-expired-rules.mjs +4 -1
  314. package/scripts/validate-plugin.mjs +22 -0
  315. package/scripts/vault-integration-watcher.mjs +54 -6
  316. package/scripts/vault-mirror.mjs +9 -0
  317. package/scripts/vault-orphans.mjs +171 -0
  318. package/skills/_shared/fleet-protocol.md +166 -3
  319. package/skills/_shared/instruction-file-resolution.md +1 -1
  320. package/skills/_shared/monitor-patterns.md +44 -25
  321. package/skills/_shared/platform-tools.md +7 -0
  322. package/skills/_shared/state-ownership.md +5 -4
  323. package/skills/architecture/references/domain-model.md +1 -1
  324. package/skills/autopilot/SKILL.md +3 -2
  325. package/skills/bootstrap/SKILL.md +3 -1
  326. package/skills/bootstrap/_shared-template.md +4 -4
  327. package/skills/bootstrap/fast-template.md +1 -1
  328. package/skills/bootstrap/private-contract.md +1 -1
  329. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
  330. package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
  331. package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
  332. package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
  333. package/skills/claude-md-drift-check/SKILL.md +3 -3
  334. package/skills/claude-md-drift-check/checker.mjs +61 -101
  335. package/skills/close/SKILL.md +20 -7
  336. package/skills/discovery/probes/docs-staleness.mjs +7 -5
  337. package/skills/discovery/probes/frontend-slop.mjs +7 -3
  338. package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
  339. package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
  340. package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
  341. package/skills/discovery/probes/vault-staleness.mjs +5 -3
  342. package/skills/discovery/probes-session.md +3 -0
  343. package/skills/discovery/probes-vault.md +2 -0
  344. package/skills/dispatcher/SKILL.md +1 -1
  345. package/skills/eval/SKILL.md +3 -1
  346. package/skills/evolve/SKILL.md +2 -0
  347. package/skills/evolve/references/evolve-analyze-mode.md +3 -3
  348. package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
  349. package/skills/gitlab-ops/SKILL.md +7 -6
  350. package/skills/gitlab-portfolio/SKILL.md +2 -2
  351. package/skills/go/SKILL.md +13 -1
  352. package/skills/npm-publish/SKILL.md +5 -2
  353. package/skills/plan/SKILL.md +23 -10
  354. package/skills/plan/mode-feature.md +13 -8
  355. package/skills/plan/mode-new.md +6 -14
  356. package/skills/plan/prd-feature-template.md +19 -0
  357. package/skills/plan/prd-full-template.md +12 -5
  358. package/skills/plan/prd-premortem-panel.md +48 -0
  359. package/skills/plan/prd-reviewer-prompt.md +11 -3
  360. package/skills/plan/soul.md +1 -1
  361. package/skills/portfolio/SKILL.md +5 -6
  362. package/skills/quality-gates/SKILL.md +2 -4
  363. package/skills/reconcile/SKILL.md +27 -4
  364. package/skills/release/SKILL.md +16 -8
  365. package/skills/session-end/SKILL.md +97 -13
  366. package/skills/session-end/phase-3-2-docs-verification.md +1 -0
  367. package/skills/session-end/phase-3-6-tail.md +11 -7
  368. package/skills/session-end/phase-3-7a-recommendations.md +9 -2
  369. package/skills/session-end/plan-verification.md +16 -1
  370. package/skills/session-end/references/phase-3-7e-vault-orphans.md +63 -0
  371. package/skills/session-end/references/phase-3-documentation-updates.md +25 -13
  372. package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
  373. package/skills/session-end/references/session-summary-template.md +8 -0
  374. package/skills/session-end/session-metrics-write.md +7 -4
  375. package/skills/session-plan/SKILL.md +5 -3
  376. package/skills/session-start/SKILL.md +10 -4
  377. package/skills/session-start/phase-4-5-resource-health.md +11 -5
  378. package/skills/session-start/phase-7-5-mode-selector.md +3 -1
  379. package/skills/session-start/phase-8-5-express-path.md +4 -3
  380. package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
  381. package/skills/session-start/references/phase-1-5-session-continuity.md +13 -5
  382. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  383. package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
  384. package/skills/tmux-layout/SKILL.md +2 -2
  385. package/skills/vault-sync/validator.mjs +3 -2
  386. package/skills/wave-executor/SKILL.md +5 -4
  387. package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
  388. package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
  389. package/skills/wave-executor/references/wave-loop-review.md +13 -5
  390. package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
  391. package/AGENTS.md +0 -207
  392. package/assets/og-card.png +0 -0
  393. package/assets/og-card.svg +0 -47
  394. package/assets/wave-lifecycle.svg +0 -98
@@ -80,6 +80,25 @@ import {
80
80
  * ever observed writing outside its worktree despite this flag, the sandbox is
81
81
  * not the boundary it is assumed to be here and the `hookTampering` check below
82
82
  * becomes the only defence rather than the backstop.
83
+ *
84
+ * `--disable-codebase-ref` keeps the CLI from starting its codebase index
85
+ * (#1683). Without it every run leaves a `worker-server` and an
86
+ * `npx typescript-language-server --stdio` pair behind; they detach via setsid,
87
+ * so the timeout kill in `runChild` never reaches them. Measured 2026-10-19 on
88
+ * cursor-agent 2026.10.01-e373342: 3 orphans per run without the flag, 0 with
89
+ * it, same answer either way. The foreign agent loses semantic search and
90
+ * LSP-backed lints; read and grep stay, and the Claude diff review remains the
91
+ * quality anchor. Named ceiling (BV-004): the flag is registered in the CLI but
92
+ * missing from `--help`, so a release may drop it silently. Revisit trigger: a
93
+ * `worker-server` or `typescript-language-server` with PPID 1 after a dispatch.
94
+ *
95
+ * The hidden `--disable-indexing` flag is deliberately absent (#1687): it only
96
+ * gates the codebase sync inside that same `worker-server`, which this argv
97
+ * never starts. Measured 2026-10-20 on cursor-agent 2026.10.01-e373342 with
98
+ * per-process `nettop` against a 1.5 MB throwaway repo: 52–71 KB out per run
99
+ * with or without the flag, no worker-server, while a 1.5 MB loopback control
100
+ * read back as exactly 1,500,000 bytes. Revisit trigger: a dispatch whose
101
+ * outbound volume grows with the worktree size.
83
102
  * @type {readonly string[]}
84
103
  */
85
104
  export const FOREIGN_AGENT_BASE_ARGS = Object.freeze([
@@ -87,6 +106,7 @@ export const FOREIGN_AGENT_BASE_ARGS = Object.freeze([
87
106
  '--force',
88
107
  '--sandbox',
89
108
  'enabled',
109
+ '--disable-codebase-ref',
90
110
  ]);
91
111
 
92
112
  /**
@@ -0,0 +1,137 @@
1
+ /** Windows tree cleanup: PowerShell 5.1 CIM identities, then taskkill and readback. */
2
+ import { spawnSync } from 'node:child_process';
3
+ import path from 'node:path';
4
+
5
+ // Ceiling per native call: 5 s. Revisit if measured CIM startup exceeds this;
6
+ // a hung PowerShell must never hang gate cleanup (including the exit handler).
7
+ const OPTIONS = { encoding: 'utf8', shell: false, windowsHide: true, timeout: 5000, maxBuffer: 16 * 1024 * 1024 };
8
+ const system32 = () => path.join(process.env.SystemRoot || 'C:\\Windows', 'System32');
9
+
10
+ function readWindowsProcesses(spawnSyncFn = spawnSync) {
11
+ const result = spawnSyncFn(
12
+ path.join(system32(), 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
13
+ ['-NoProfile', '-NonInteractive', '-Command', 'Get-CimInstance Win32_Process | Select-Object ProcessId,ParentProcessId,CreationDate | ConvertTo-Json -Compress'],
14
+ OPTIONS,
15
+ );
16
+ if (result.error || result.status !== 0) throw new Error('CIM snapshot failed');
17
+ let parsed;
18
+ try {
19
+ parsed = JSON.parse(String(result.stdout));
20
+ } catch {
21
+ // A truncated or banner-polluted snapshot is "not measurable", never "no processes".
22
+ throw new Error('CIM snapshot output is not JSON');
23
+ }
24
+ const rows = Array.isArray(parsed) ? parsed : [parsed];
25
+ if (!rows.length || rows.some(p => !p || !Number.isInteger(p.ProcessId)
26
+ || !Number.isInteger(p.ParentProcessId))) {
27
+ throw new Error('CIM snapshot has no usable process identities');
28
+ }
29
+ return rows;
30
+ }
31
+
32
+ // PowerShell 5.1 ConvertTo-Json uses /Date(epoch milliseconds)/.
33
+ // Other formats (including WMI dates) remain explicitly unmeasurable.
34
+ function creationMs(row) {
35
+ const match = /^\/Date\((-?\d+)\)\/$/.exec(row?.CreationDate);
36
+ return match && Number.isFinite(Number(match[1])) ? Number(match[1]) : null;
37
+ }
38
+
39
+ function descendantIds(rows, seeds) {
40
+ const ids = new Set(seeds);
41
+ let size;
42
+ do {
43
+ size = ids.size;
44
+ for (const p of rows) if (ids.has(p.ParentProcessId)) ids.add(p.ProcessId);
45
+ } while (ids.size !== size);
46
+ return ids;
47
+ }
48
+
49
+ /** Same synchronous primitive for timeout and process-exit cleanup.
50
+ * A live ChildProcess holds libuv's Windows handle, preventing root PID reuse.
51
+ * After exit, only dated descendants may be targeted; the root is never killed.
52
+ * null survivors means unmeasurable, never a successful empty measurement.
53
+ */
54
+ export function killWindowsProcessTree(pid, {
55
+ spawnSyncFn = spawnSync,
56
+ snapshotFn = () => readWindowsProcesses(spawnSyncFn),
57
+ child,
58
+ spawnedAtMs,
59
+ verifyTree = true,
60
+ } = {}) {
61
+ const base = { signalsSent: [], aborted: null };
62
+ const unknown = reason => ({ ...base, ok: false, survivors: null, error: `nicht messbar auf win32: ${reason}` });
63
+ const rootHeld = child?.exitCode === null && child?.signalCode === null;
64
+ let killError = null;
65
+ const kill = (target, tree = true) => {
66
+ base.signalsSent.push('taskkill');
67
+ try {
68
+ const args = tree ? ['/PID', String(target), '/T', '/F'] : ['/PID', String(target), '/F'];
69
+ const result = spawnSyncFn(path.join(system32(), 'taskkill.exe'), args, OPTIONS);
70
+ if (result.error || result.status !== 0) killError = result.error?.message ?? `taskkill exit ${result.status}`;
71
+ } catch (err) { killError = String(err); }
72
+ };
73
+ if (!verifyTree) {
74
+ if (rootHeld) kill(pid);
75
+ return unknown(`nicht gemessen (verifyTree: false)${killError ? `; ${killError}` : ''}`);
76
+ }
77
+
78
+ let before;
79
+ let measurementError;
80
+ try {
81
+ before = snapshotFn();
82
+ if (!Array.isArray(before)) throw Error('invalid snapshot');
83
+ } catch (err) { measurementError = err; }
84
+ if (measurementError) {
85
+ if (rootHeld) kill(pid);
86
+ return unknown(measurementError);
87
+ }
88
+ const ids = descendantIds(before, [pid]);
89
+ const floor = Number.isFinite(spawnedAtMs) ? spawnedAtMs - 2000 : null;
90
+ let targets = before.filter(p => ids.has(p.ProcessId));
91
+ if (!rootHeld) {
92
+ targets = targets.filter(p => p.ProcessId !== pid);
93
+ if (floor === null || targets.some(p => creationMs(p) === null)) return unknown('missing descendant CreationDate or spawnedAtMs');
94
+ targets = targets.filter(p => creationMs(p) >= floor);
95
+ for (const p of targets) kill(p.ProcessId);
96
+ } else {
97
+ // Windows never rewrites ParentProcessId when a parent dies, so a process
98
+ // older than our root can still name the root's (reused) PID as its parent
99
+ // (Opus review M2, #1612). Such a process is not ours: drop it from the
100
+ // targets, and when one exists, end ours one by one without /T so
101
+ // taskkill's own PPID walk cannot reach it.
102
+ const rootBorn = creationMs(targets.find(p => p.ProcessId === pid)) ?? floor;
103
+ const stale = rootBorn === null ? [] : targets.filter(p => p.ProcessId !== pid
104
+ && creationMs(p) !== null && creationMs(p) < rootBorn);
105
+ if (stale.length === 0) {
106
+ kill(pid);
107
+ } else {
108
+ targets = targets.filter(p => !stale.includes(p));
109
+ for (const p of targets) kill(p.ProcessId, false);
110
+ }
111
+ }
112
+
113
+ let after;
114
+ try {
115
+ after = snapshotFn();
116
+ if (!Array.isArray(after)) throw Error('invalid snapshot');
117
+ } catch (err) { return unknown(err); }
118
+ if ((rootHeld && !targets.some(p => p.ProcessId === pid))
119
+ || targets.some(p => creationMs(p) === null)
120
+ || after.some(p => targets.some(q => q.ProcessId === p.ProcessId) && creationMs(p) === null)) {
121
+ return unknown('missing tree identity');
122
+ }
123
+ const survivors = new Set(targets.filter(p => after.some(q =>
124
+ q.ProcessId === p.ProcessId && q.CreationDate === p.CreationDate)).map(p => p.ProcessId));
125
+
126
+ // Include descendants born during taskkill, following parents through both
127
+ // snapshots even when an intermediate parent has already disappeared.
128
+ const treeIds = new Set([pid, ...targets.map(p => p.ProcessId)]);
129
+ const related = descendantIds([...before, ...after], treeIds);
130
+ for (const p of after) {
131
+ if (p.ProcessId === pid || !related.has(p.ParentProcessId)) continue;
132
+ const date = creationMs(p);
133
+ if (date === null || floor === null) return unknown('missing descendant CreationDate or spawnedAtMs');
134
+ if (date >= floor) survivors.add(p.ProcessId);
135
+ }
136
+ return { ...base, ok: survivors.size === 0 && !killError, survivors: [...survivors], error: killError };
137
+ }
@@ -13,7 +13,7 @@
13
13
  import fs from 'node:fs/promises';
14
14
  import fsSync from 'node:fs';
15
15
  import path from 'node:path';
16
- import { $ } from 'zx';
16
+ import { $ } from './zx.mjs';
17
17
  import { listWorktrees } from './worktree.mjs';
18
18
 
19
19
  // Do not spam stdout/stderr with git command echoes.
@@ -7,7 +7,7 @@
7
7
  * Side effect: configures zx to suppress git command echoes on load.
8
8
  */
9
9
 
10
- import { $ } from 'zx';
10
+ import { $ } from '../zx.mjs';
11
11
 
12
12
  // Do not spam stdout/stderr with git command echoes.
13
13
  $.verbose = false;
@@ -17,7 +17,7 @@
17
17
  * lifecycle → config.mjs (readConfigFile, parseSessionConfig)
18
18
  */
19
19
 
20
- import { $, nothrow, ProcessOutput } from 'zx';
20
+ import { $, nothrow, ProcessOutput } from '../zx.mjs';
21
21
  import fs from 'node:fs/promises';
22
22
  import os from 'node:os';
23
23
  import path from 'node:path';
@@ -10,7 +10,7 @@
10
10
  * lifecycle import chain (lifecycle imports listing, not the reverse).
11
11
  */
12
12
 
13
- import { $ as defaultDollar } from 'zx';
13
+ import { $ as defaultDollar } from '../zx.mjs';
14
14
  import fs from 'node:fs/promises';
15
15
  import path from 'node:path';
16
16
 
@@ -0,0 +1,50 @@
1
+ import { existsSync as fileExistsSync } from 'node:fs';
2
+ import { win32 as path } from 'node:path';
3
+ import { $, quote } from 'zx';
4
+
5
+ export * from 'zx';
6
+
7
+ // Windows 11 / Node 24.14 / zx 8.8.5 / Git 2.55, measured 2026-10-14:
8
+ // MSYS halves backslashes in Node's -c command line before bash interprets
9
+ // ANSI-C quoting (\b becomes backspace, \r CR). Doubling zx's escapes gave
10
+ // 14/14 exact argument roundtrips, versus 10/14 with stock quote (#1612).
11
+ // Always start from stock quote, so repeated configuration cannot stack escapes.
12
+ const quoteGitBash = (arg) => quote(arg).replace(/\\/g, '\\\\');
13
+
14
+ /** Configure the zx singleton; injection also permits Windows checks on POSIX. */
15
+ export function configureZx({
16
+ platform = process.platform,
17
+ existsSync = fileExistsSync,
18
+ target = $,
19
+ env = process.env,
20
+ } = {}) {
21
+ if (platform !== 'win32') return;
22
+
23
+ const shell = typeof target.shell === 'string' ? path.normalize(target.shell) : '';
24
+ const wsl = path.join(env.SystemRoot || 'C:\\Windows', 'System32', 'bash.exe');
25
+ if (!target.shell || shell.toLowerCase() === wsl.toLowerCase()) {
26
+ const candidates = [env.ProgramFiles, env['ProgramFiles(x86)']]
27
+ .filter(Boolean).map(dir => path.join(dir, 'Git', 'bin', 'bash.exe'));
28
+ let bash = candidates.find(candidate => existsSync(candidate));
29
+ if (!bash) {
30
+ for (const dir of (env.PATH || '').split(';').filter(Boolean)) {
31
+ const gitDir = dir.replace(/^"|"$/g, '');
32
+ if (!existsSync(path.join(gitDir, 'git.exe'))) continue;
33
+ const candidate = path.resolve(gitDir, '..', 'bin', 'bash.exe');
34
+ if (existsSync(candidate)) {
35
+ bash = candidate;
36
+ break;
37
+ }
38
+ }
39
+ }
40
+ if (!bash) return;
41
+ target.shell = bash;
42
+ }
43
+
44
+ if (typeof target.shell === 'string' &&
45
+ /\\(?:Git\\bin|usr\\bin)\\bash\.exe$/i.test(path.normalize(target.shell))) {
46
+ target.quote = quoteGitBash;
47
+ }
48
+ }
49
+
50
+ configureZx();
@@ -6,6 +6,8 @@ import { join } from 'node:path';
6
6
  import { createInterface } from 'node:readline';
7
7
  import { isSessionConfigHeading } from './lib/config/section-extractor.mjs';
8
8
  import { isMainModule } from './lib/is-main-module.mjs';
9
+ import { canonicalizeSessions, isMeasuredSession } from './lib/sessions-canonical.mjs';
10
+ import { resolveEventsLedgerRoot } from './lib/platform.mjs';
9
11
 
10
12
  const SERVER_INFO = { name: 'session-orchestrator', version: '2.0.0' };
11
13
  const TOOLS = [
@@ -16,7 +18,7 @@ const TOOLS = [
16
18
  },
17
19
  {
18
20
  name: 'session_metrics',
19
- description: 'Reads the last 5 REAL session metrics entries from .orchestrator/metrics/sessions.jsonl (abandoned phantom stubs excluded)',
21
+ description: 'Reads the last 5 MEASURED session metrics entries from .orchestrator/metrics/sessions.jsonl (unmeasured records excluded)',
20
22
  inputSchema: { type: 'object', properties: {}, required: [] },
21
23
  },
22
24
  ];
@@ -65,24 +67,31 @@ export function readSessionConfig(root) {
65
67
  return body.join('\n').replace(/\n+$/, '') || `No '## Session Config' section found in ${file}`;
66
68
  }
67
69
 
68
- /** Read the last five real records, with whole-ledger stub count and token coverage. */
70
+ /** Read the last five canonical measured records, with exclusions and token coverage. */
69
71
  export function readSessionMetrics(root) {
70
- const file = ['.orchestrator', '.claude'].map((dir) => join(root, dir, 'metrics', 'sessions.jsonl'))
72
+ const file = ['.orchestrator', '.claude'].map((dir) => join(resolveEventsLedgerRoot(root), dir, 'metrics', 'sessions.jsonl'))
71
73
  .find((path) => isFile(path));
72
74
  if (!file) return 'No metrics found (checked .orchestrator/metrics/ and .claude/metrics/)';
73
75
  const entries = [];
74
76
  let stubCount = 0;
77
+ const parsed = [];
75
78
  for (const line of readFileSync(file, 'utf8').split(/\r?\n/)) {
76
79
  let record;
77
80
  try { record = JSON.parse(line); } catch { continue; }
78
- // Torn writes and non-record JSON must not hide subsequent valid sessions.
81
+ // Preserve torn-write/non-record guards and actionable read errors.
79
82
  if (!record || typeof record !== 'object' || Array.isArray(record)) continue;
80
- if (record.status === 'abandoned') { stubCount++; continue; }
83
+ parsed.push(record);
84
+ }
85
+ // Keep ID-less legacy measurements and original append order for the window.
86
+ const canonical = new Set(canonicalizeSessions(parsed, { keepUnidentified: true }));
87
+ for (const record of parsed) {
88
+ if (!canonical.has(record)) continue;
89
+ if (!isMeasuredSession(record)) { stubCount++; continue; }
81
90
  entries.push(record);
82
91
  if (entries.length > 5) entries.shift();
83
92
  }
84
93
  if (!entries.length) {
85
- return stubCount ? `No real sessions (abandoned stubs excluded: ${stubCount})`
94
+ return stubCount ? `No measured sessions (unmeasured records excluded: ${stubCount})`
86
95
  : 'No metrics found (file is empty)';
87
96
  }
88
97
  const record = entries.findLast((entry) => (entry.total_token_input !== null && entry.total_token_input !== undefined)
@@ -95,7 +104,7 @@ export function readSessionMetrics(root) {
95
104
  + ` [session: ${record.session_id ?? 'unknown'}]`
96
105
  : 'tokens: no token data in last 5 sessions (subagent telemetry not yet captured)';
97
106
  return entries.map((entry) => JSON.stringify(entry)).join('\n')
98
- + `\n\nabandoned stubs excluded: ${stubCount}\n\n--- token summary ---\n${summary}`;
107
+ + `\n\nunmeasured records excluded: ${stubCount}\n\n--- token summary ---\n${summary}`;
99
108
  }
100
109
 
101
110
  /** Handle one JSON-RPC line. null means a notification or blank line, not a reply. */
@@ -8,8 +8,8 @@ set -euo pipefail
8
8
  # - session_config — reads Session Config from the project instruction file
9
9
  # (CLAUDE.md, or AGENTS.md alias on Codex CLI — see
10
10
  # skills/_shared/instruction-file-resolution.md)
11
- # - session_metrics — reads last 5 REAL session metrics entries (#834:
12
- # abandoned phantom stubs are filtered out first)
11
+ # - session_metrics — reads last 5 canonical MEASURED session metrics entries
12
+ # via the shared native reader (#1584)
13
13
 
14
14
  # ---------------------------------------------------------------------------
15
15
  # Helpers
@@ -85,7 +85,7 @@ handle_tools_list() {
85
85
  },
86
86
  {
87
87
  "name": "session_metrics",
88
- "description": "Reads the last 5 REAL session metrics entries from .orchestrator/metrics/sessions.jsonl (abandoned phantom stubs excluded)",
88
+ "description": "Reads the last 5 MEASURED session metrics entries from .orchestrator/metrics/sessions.jsonl (unmeasured records excluded)",
89
89
  "inputSchema": {"type": "object", "properties": {}, "required": []}
90
90
  }
91
91
  ]
@@ -171,88 +171,18 @@ tool_session_metrics() {
171
171
  return
172
172
  }
173
173
 
174
- local metrics_file="$project_root/.orchestrator/metrics/sessions.jsonl"
175
- if [[ ! -f "$metrics_file" ]]; then
176
- metrics_file="$project_root/.claude/metrics/sessions.jsonl"
177
- fi
178
- if [[ ! -f "$metrics_file" ]]; then
179
- respond "$id" "$(text_content "No metrics found (checked .orchestrator/metrics/ and .claude/metrics/)")"
180
- return
181
- fi
182
-
183
- # Filter out phantom `status: 'abandoned'` stubs (#834, session-close-backfill
184
- # — 0 waves, seconds of runtime) BEFORE taking the tail, so a recent phantom
185
- # cannot displace real session records out of the last-5 window.
186
- #
187
- # `-R` (raw-input) + `fromjson?` parses each line individually and SKIPS
188
- # unparseable ones instead of aborting the whole stream — plain
189
- # `jq -c 'select(...)'` aborts at the FIRST malformed line (jq: parse error,
190
- # exit 5), which is fatal here because sessions.jsonl is append-only from
191
- # multiple writers and a torn write is exactly the case that matters. This
192
- # mirrors the per-line try/catch behaviour of the .mjs path
193
- # (scripts/lib/session-schema/filters.mjs).
194
- local entries
195
- entries=$(jq -R -c 'fromjson? | select(.status != "abandoned")' "$metrics_file" 2>/dev/null | tail -n 5) || true
196
-
197
- # Report the stubs the filter above dropped (#1296) — counted over the WHOLE
198
- # file, not the tail, with the same torn-line tolerance (`fromjson?`, plus
199
- # `.status?` so a non-object line cannot abort the count either). Computed
200
- # BEFORE the empty check: a stubs-only ledger is not an empty file.
201
- local stub_count
202
- stub_count=$(jq -R -n '[inputs | fromjson? | select(.status? == "abandoned")] | length' "$metrics_file" 2>/dev/null) || stub_count=0
203
- [[ -z "$stub_count" ]] && stub_count=0
204
-
205
- if [[ -z "$entries" ]]; then
206
- if [[ "$stub_count" -gt 0 ]]; then
207
- respond "$id" "$(text_content "No real sessions (abandoned stubs excluded: ${stub_count})")"
208
- else
209
- respond "$id" "$(text_content "No metrics found (file is empty)")"
210
- fi
174
+ # Delegate to the same reader as the native transport: canonical identity,
175
+ # measured-session definition, main-checkout ledger and token summary.
176
+ local server_dir output
177
+ server_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
178
+ if ! output=$(node --input-type=module -e '
179
+ import { pathToFileURL } from "node:url";
180
+ const { readSessionMetrics } = await import(pathToFileURL(process.argv[2]).href);
181
+ process.stdout.write(readSessionMetrics(process.argv[3]));
182
+ ' session-metrics-reader "$server_dir/mcp-server.mjs" "$project_root" </dev/null); then
183
+ respond_error "$id" -32603 "Unable to read session data"
211
184
  return
212
185
  fi
213
-
214
- # Derive a token summary from the most-recent record that carries token totals.
215
- # Fields total_token_input / total_token_output / subagents_with_tokens are
216
- # optional (null when no subagent token data was captured for that session).
217
- # Low coverage (subagents_with_tokens == 0 while matched_records > 0) means
218
- # the total is PARTIAL — do not read it as "free / zero cost".
219
- local token_summary
220
- # Use -Rrs (raw-input + slurp + raw-output) so jq receives the multi-line
221
- # JSONL block as a single string, can split + parse each line independently,
222
- # and outputs plain text (no surrounding JSON quotes on the result string).
223
- # The plain -r flag alone does not work for multi-record JSONL piped as one
224
- # string — it treats the entire input as a single JSON value.
225
- token_summary=$(printf '%s' "$entries" | \
226
- jq -Rrs '
227
- # Parse each non-empty line as JSON; skip malformed lines.
228
- [ split("\n")[] | select(length > 0) | (try fromjson catch null) | select(. != null) ]
229
- | reverse
230
- | ( map(select( (.total_token_input != null) or (.total_token_output != null) )) | first ) as $rec
231
- | if $rec then
232
- "tokens: \($rec.total_token_input // "?") in / \($rec.total_token_output // "?") out" +
233
- " (coverage: \($rec.subagents_with_tokens // 0) subagents)" +
234
- ( if ($rec.subagents_with_tokens // 0) == 0 then
235
- " ⚠ partial — subagent token data missing; total is not a reliable cost estimate"
236
- else "" end ) +
237
- " [session: \($rec.session_id // "unknown")]"
238
- else
239
- "tokens: no token data in last 5 sessions (subagent telemetry not yet captured)"
240
- end
241
- ' 2>/dev/null) || token_summary=""
242
-
243
- local output
244
- entries="${entries}
245
-
246
- abandoned stubs excluded: ${stub_count}"
247
- if [[ -n "$token_summary" ]]; then
248
- output="${entries}
249
-
250
- --- token summary ---
251
- ${token_summary}"
252
- else
253
- output="$entries"
254
- fi
255
-
256
186
  respond "$id" "$(text_content "$output")"
257
187
  }
258
188
 
@@ -37,7 +37,7 @@ import { spawnSync } from 'node:child_process';
37
37
  import { mkdtempSync, writeFileSync, rmSync, mkdirSync } from 'node:fs';
38
38
  import { join, resolve } from 'node:path';
39
39
  import { tmpdir } from 'node:os';
40
- import { fileURLToPath } from 'node:url';
40
+ import { fileURLToPath, pathToFileURL } from 'node:url';
41
41
 
42
42
  // ---------------------------------------------------------------------------
43
43
  // CLI flags
@@ -57,8 +57,8 @@ const _REPO_ROOT = resolve(flagValue('--repo-root', process.cwd()));
57
57
  const JSON_MODE = flagBool('--json');
58
58
 
59
59
  const SCRIPT_DIR = fileURLToPath(new URL('.', import.meta.url));
60
- const CACHE_MODULE = join(SCRIPT_DIR, 'lib', 'quality-gates-cache.mjs');
61
- const POLICY_MODULE = join(SCRIPT_DIR, 'lib', 'quality-gates-policy.mjs');
60
+ const CACHE_MODULE = pathToFileURL(join(SCRIPT_DIR, 'lib', 'quality-gates-cache.mjs')).href;
61
+ const POLICY_MODULE = pathToFileURL(join(SCRIPT_DIR, 'lib', 'quality-gates-policy.mjs')).href;
62
62
 
63
63
  // ---------------------------------------------------------------------------
64
64
  // Helpers
@@ -340,11 +340,13 @@ async function main() {
340
340
  } else {
341
341
  printHumanReport(report);
342
342
  }
343
-
344
- process.exit(0);
345
343
  } finally {
346
344
  teardownTmpRepo(tmpRepo);
347
345
  }
346
+ // Exit only AFTER the finally: process.exit() ends the process on the spot,
347
+ // so calling it inside the try skipped teardownTmpRepo and leaked one
348
+ // pce-repo-* dir under os.tmpdir() on every successful run (#1648).
349
+ process.exit(0);
348
350
  }
349
351
 
350
352
  function deriveRecommendation(cacheSubprocess, cacheInProcess, policySubprocess, _policyInProcess) {
@@ -278,12 +278,19 @@ let stateMdReadOk = false;
278
278
  let stateDir = null;
279
279
 
280
280
  try {
281
- // resolveStateMdPath from state-md.mjs: falls back to .claude/STATE.md
282
281
  const stateMdMod = await import('./lib/state-md.mjs');
283
- const stateMdPath = stateMdMod.resolveStateMdPath(process.cwd());
282
+ const { resolveStateMdWriteTarget, warnForeignStateMd } = await import(
283
+ './lib/state-md/frontmatter-mutators.mjs'
284
+ );
285
+ // #1636: the write side's ownership rule — another platform's STATE.md not
286
+ // proven to be this process's takes the "missing" branch. On rejection
287
+ // `stateMdPath` is this platform's own target, so stateDir (also the wave
288
+ // manifest's dir) never points into the foreign platform's state dir.
289
+ const { path: stateMdPath, rejected } = resolveStateMdWriteTarget(process.cwd());
290
+ if (rejected) warnForeignStateMd('memory-propose', rejected, stateMdPath);
284
291
  stateDir = dirname(stateMdPath);
285
292
 
286
- if (!existsSync(stateMdPath)) {
293
+ if (rejected || !existsSync(stateMdPath)) {
287
294
  if (!dryRun) {
288
295
  exit(
289
296
  { status: STATUS.REJECTED_WRONG_CONTEXT, detail: 'STATE.md missing or unparseable' },
@@ -245,6 +245,13 @@ async function processRepo(repo, { apply }) {
245
245
 
246
246
  // Sessions.jsonl liveness check — even if marker is absent, an active repo
247
247
  // should not get a "welcome" banner.
248
+ // Deliberately THIS path's own ledger, not resolveEventsLedgerRoot(repo)
249
+ // (#1592): the inputs are main-checkout repo paths by construction
250
+ // (--repos / dormant-repos), and the marker above is seeded into this same
251
+ // path — liveness and seed stay one checkout. Ceiling: a linked worktree
252
+ // passed via --repos is judged by its own (absent) ledger; revisit if
253
+ // worktree paths ever enter dormant-repos.
254
+ // ledger-root: deliberate own-path ledger (#1592), see the comment above
248
255
  const sessionsPath = join(repo, SESSIONS_REL);
249
256
  try {
250
257
  if (await sessionsHasContent(sessionsPath)) {
@@ -7,6 +7,28 @@ set -euo pipefail
7
7
  ROOT="${1:-$(pwd)}"
8
8
  CANONICAL="$ROOT/.orchestrator/metrics/learnings.jsonl"
9
9
  TODAY="$(date +%Y-%m-%d)"
10
+
11
+ # #1516: a host whose learnings-store role is `inbox` never rewrites the
12
+ # canonical store. The role resolver is the JS one (env SO_LEARNINGS_STORE_ROLE
13
+ # > owner.yaml learnings.store-role > writer), imported relative to this script.
14
+ # Fail-closed: if node fails, or the role is `invalid` (set but unusable) or
15
+ # anything other than writer/inbox, nothing is written and the script exits 4.
16
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
17
+ STORE_ROLE="$(node --input-type=module -e '
18
+ const { pathToFileURL } = await import("node:url");
19
+ const m = await import(pathToFileURL(process.argv[1]).href);
20
+ process.stdout.write(m.resolveStoreRole().role);
21
+ ' "$SCRIPT_DIR/lib/learnings/store-role.mjs" 2>/dev/null)" || STORE_ROLE="unresolved"
22
+ if [ "$STORE_ROLE" = "inbox" ]; then
23
+ echo "migrate-legacy-learnings: learnings store role is inbox on this host (#1516) — nothing written; run it on the writer host" >&2
24
+ printf '{"status":"refused_store_role"}\n'
25
+ exit 3
26
+ fi
27
+ if [ "$STORE_ROLE" != "writer" ]; then
28
+ echo "migrate-legacy-learnings: learnings store role is ${STORE_ROLE} on this host (#1516) — nothing written; fix owner.yaml learnings.store-role or SO_LEARNINGS_STORE_ROLE" >&2
29
+ printf '{"status":"refused_store_role_invalid"}\n'
30
+ exit 4
31
+ fi
10
32
  LEGACY_PATHS=("$ROOT/.claude/metrics/learnings.jsonl" "$ROOT/.codex/metrics/learnings.jsonl" "$ROOT/.cursor/metrics/learnings.jsonl")
11
33
 
12
34
  tmp=""
@@ -39,9 +39,10 @@
39
39
  */
40
40
 
41
41
  import { readFileSync, copyFileSync, existsSync } from 'node:fs';
42
- import { basename } from 'node:path';
42
+ import { basename, join, resolve } from 'node:path';
43
43
  import { atomicWriteWithBackup, envelopeToError } from './lib/io.mjs';
44
44
  import { validateSession, normalizeSession, ValidationError } from './lib/session-schema.mjs';
45
+ import { resolveEventsLedgerRoot } from './lib/platform.mjs';
45
46
  import { isMainModule } from './lib/is-main-module.mjs';
46
47
 
47
48
  const DEFAULT_FILE = '.orchestrator/metrics/sessions.jsonl';
@@ -327,8 +328,21 @@ function processEntry(raw) {
327
328
  // CLI
328
329
  // ---------------------------------------------------------------------------
329
330
 
331
+ /**
332
+ * Default target: sessions.jsonl of the repo's one ledger root (#1514/#1584,
333
+ * #1722) — the main checkout when cwd is a linked worktree. Outside one the
334
+ * relative DEFAULT_FILE is kept verbatim, so paths in output do not change.
335
+ *
336
+ * @returns {string}
337
+ */
338
+ function defaultFile() {
339
+ const root = resolveEventsLedgerRoot(process.cwd());
340
+ // ledger-root: `root` is resolveEventsLedgerRoot() of the line above
341
+ return root === resolve(process.cwd()) ? DEFAULT_FILE : join(root, DEFAULT_FILE);
342
+ }
343
+
330
344
  function parseArgs(argv) {
331
- const args = { file: DEFAULT_FILE, apply: false };
345
+ const args = { file: defaultFile(), apply: false };
332
346
  for (let i = 0; i < argv.length; i++) {
333
347
  const a = argv[i];
334
348
  if (a === '--file') {