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
@@ -29,23 +29,26 @@
29
29
  */
30
30
 
31
31
  import { readFile, writeFile, mkdir } from 'node:fs/promises';
32
- import { writeSync } from 'node:fs';
32
+ import { existsSync, writeSync } from 'node:fs';
33
33
  import { execFile } from 'node:child_process';
34
34
  import { promisify } from 'node:util';
35
35
  import { randomUUID } from 'node:crypto';
36
+ import { hostname } from 'node:os';
36
37
  import path from 'node:path';
37
38
 
38
39
  import { shouldRunHook } from './_lib/profile-gate.mjs';
39
40
  import { ensureOrchestratorIgnore } from './_lib/orchestrator-ignore.mjs';
41
+ import { resolveSessionStartRoot } from './_lib/hook-root.mjs';
40
42
 
41
43
  import { emitEvent, eventsFilePath } from '../scripts/lib/events.mjs';
42
- import { readNavigatorLease } from '../scripts/lib/fleet-protocol.mjs';
44
+ import { readNavigatorLease, fleetIntegrationEnabled } from '../scripts/lib/fleet-protocol.mjs';
43
45
  import { maybeRotate } from '../scripts/lib/events-rotation.mjs';
44
46
  import { readConfigFile, parseSessionConfig } from '../scripts/lib/config.mjs';
45
- import { getPlatform, resolveProjectDir } from '../scripts/lib/platform.mjs';
47
+ import { getPlatform, resolveProjectDir, resolveSessionRoot } from '../scripts/lib/platform.mjs';
46
48
  import {
47
49
  registerSelf,
48
50
  detectPeers,
51
+ countRecentStarts,
49
52
  sweepZombies,
50
53
  logSweepEvent,
51
54
  repoPathHash,
@@ -58,8 +61,9 @@ import { isMainModule } from '../scripts/lib/is-main-module.mjs';
58
61
  // SSOT for the "is this a re-entry into the same logical session?" question
59
62
  // (#1091). Defined in the lock-bootstrap leaf module, which this hook already
60
63
  // loads, so the preservation branch below and the lock force-refresh gate can
61
- // never drift apart.
62
- import { SAME_LOGICAL_SESSION_SOURCES } from './_lib/lock-bootstrap.mjs';
64
+ // never drift apart. readClaudePid() is the process witness current-session.json
65
+ // carries next to the raw id (#1746).
66
+ import { SAME_LOGICAL_SESSION_SOURCES, readClaudePid } from './_lib/lock-bootstrap.mjs';
63
67
 
64
68
  const execFileAsync = promisify(execFile);
65
69
 
@@ -187,14 +191,20 @@ function supersessionMarker(peer) {
187
191
  }
188
192
 
189
193
  /**
190
- * Queue the ONE `additionalContext` string for the single end-of-hook flush.
191
- * Last writer wins; there is deliberately no accumulation, because every
192
- * character here costs context on every session start.
194
+ * Queue text for the ONE `additionalContext` string of the single end-of-hook
195
+ * flush. Two writers exist (the consent nudge, the #1635 lead notice) and both
196
+ * are needed by the model, so a second text is appended on its own line; an
197
+ * identical text is not repeated. Every character costs context on every
198
+ * session start, so writers stay terse and conditional.
193
199
  * @param {string|null|undefined} text
194
200
  */
195
201
  function setAdditionalContext(text) {
196
202
  if (typeof text !== 'string' || text.length === 0) return;
197
- pendingAdditionalContext = text;
203
+ if (typeof pendingAdditionalContext !== 'string' || pendingAdditionalContext.length === 0) {
204
+ pendingAdditionalContext = text;
205
+ } else if (!pendingAdditionalContext.split('\n').includes(text)) {
206
+ pendingAdditionalContext = `${pendingAdditionalContext}\n${text}`;
207
+ }
198
208
  }
199
209
 
200
210
  /**
@@ -241,14 +251,16 @@ function flushBanner() {
241
251
  // ---------------------------------------------------------------------------
242
252
 
243
253
  /**
244
- * Run a git command in cwd; return trimmed stdout. Returns null on failure.
254
+ * Run a git command in cwd; return trimmed stdout. Returns null on failure
255
+ * (including a timeout, when `timeoutMs` is given).
245
256
  * @param {string[]} args
246
257
  * @param {string} cwd
258
+ * @param {number} [timeoutMs] — kill the git child after this many ms (0 = no limit)
247
259
  * @returns {Promise<string|null>}
248
260
  */
249
- async function gitOutput(args, cwd) {
261
+ async function gitOutput(args, cwd, timeoutMs = 0) {
250
262
  try {
251
- const { stdout } = await execFileAsync('git', args, { cwd });
263
+ const { stdout } = await execFileAsync('git', args, { cwd, timeout: timeoutMs });
252
264
  return stdout.trim();
253
265
  } catch {
254
266
  return null;
@@ -316,25 +328,27 @@ export async function readColdStartConfig(projectRoot) {
316
328
  }
317
329
 
318
330
  /**
319
- * Read `resource-thresholds.concurrent-sessions-warn` from Session Config.
320
- * Used to decide when the peer banner should show the WARN icon. Defaults to
321
- * 5 — matches the documented default in docs/session-config-reference.md.
331
+ * Start-wave rule (#1650): WARN when at least START_WAVE_MIN foreign registry
332
+ * entries started within the last START_WAVE_WINDOW_MIN minutes.
333
+ *
334
+ * Why not `peer_count >= concurrent-sessions-warn` (the previous WARN trigger):
335
+ * that is a permanent state, not an event. Measured 2026-10-17 08:08Z/08:09Z
336
+ * over `orchestrator.session.started` since 2026-08-24, dedup session_id +
337
+ * timestamp, per `host_class`: peer_count >= 5 fired on 880/1305 = 67.4% (M4)
338
+ * and 255/991 = 25.7% (M5) of starts — a broken instrument per HR-101.
339
+ * This rule, reconstructed from the start timestamps themselves, fires on
340
+ * 35/1305 = 2.7% (M4) and 52/991 = 5.2% (M5). Neighbouring candidates:
341
+ * >=3 in 2 min 4.0% / 7.1%, >=5 in 2 min 1.7% / 3.3%, >=3 in 5 min 6.3% / 10.7%.
342
+ * 5 min was chosen over 2 min so a wave spread over a few minutes still shows.
343
+ *
344
+ * Ceiling (BV-004): fixed constants, no config key; the reconstruction is an
345
+ * upper bound (sessions that ended inside the window leave the registry).
346
+ * Revisit when either host's firing rate on `recent_starts_count >= 5` in the
347
+ * event ledger exceeds 10%, or a start wave that hurt was not flagged.
348
+ * Report only — this single soft signal caps nothing (HR-104).
322
349
  */
323
- async function peerWarnThreshold(projectRoot) {
324
- const DEFAULT = 5;
325
- for (const name of ['CLAUDE.md', 'AGENTS.md']) {
326
- try {
327
- const raw = await readFile(path.join(projectRoot, name), 'utf8');
328
- // Match `concurrent-sessions-warn: <int>` inside resource-thresholds or top-level.
329
- const m = raw.match(/^\s*concurrent-sessions-warn:\s*(\d+)\b/im);
330
- if (m) {
331
- const n = parseInt(m[1], 10);
332
- if (!Number.isNaN(n) && n > 0) return n;
333
- }
334
- } catch { /* file missing is fine */ }
335
- }
336
- return DEFAULT;
337
- }
350
+ const START_WAVE_WINDOW_MIN = 5;
351
+ const START_WAVE_MIN = 5;
338
352
 
339
353
  /**
340
354
  * Non-blocking stdin read for Claude Code hook payload. Returns parsed JSON or
@@ -372,8 +386,13 @@ async function readStdinJson(timeoutMs = 500) {
372
386
  * Codex/Cursor may not).
373
387
  *
374
388
  * Best-effort: any persistence failure is swallowed (hook must remain non-blocking).
389
+ *
390
+ * @param {object|null} input — the parsed SessionStart payload.
391
+ * @param {string} projectRoot
392
+ * @param {number|null} [claudePid=null] — readClaudePid(); recorded with the
393
+ * host as the writer's process witness (#1746).
375
394
  */
376
- async function resolveSessionId(input, projectRoot) {
395
+ async function resolveSessionId(input, projectRoot, claudePid = null) {
377
396
  const nativeSource = typeof input?.source === 'string' && input.source.length > 0
378
397
  ? input.source
379
398
  : null;
@@ -477,9 +496,12 @@ async function resolveSessionId(input, projectRoot) {
477
496
  // (`resume` was ABSENT from hooks.json until #1091, so on Claude Code this
478
497
  // hook never ran on a native resume), `startup|reload|new|resume|fork` on
479
498
  // pi, and every source on Cursor (empty matcher). Several of those sources
480
- // are re-entries into the SAME logical session. On clear/compact the UUID
481
- // `session_id` changes while the `semantic_session_id`
482
- // (branch+date+mode+n) stays stable; whether Claude Code PRESERVES the raw
499
+ // are re-entries into the SAME logical session. The `semantic_session_id`
500
+ // (branch+date+mode+n) is meant to stay stable across them. The raw UUID
501
+ // `session_id` does not behave alike per source: on Claude Code `compact`
502
+ // KEEPS it (12/12 compact events in events.jsonl carry
503
+ // `resume_linkage: raw-id`, measured 2026-10-14), while `clear` showed
504
+ // 6/6 `resume_linkage: none`. Whether Claude Code PRESERVES the raw
483
505
  // `session_id` across a native resume is what #1091 measures via
484
506
  // `native_source` + `resume_linkage` on `orchestrator.session.started` —
485
507
  // nothing here ASSUMES either answer, it only reacts to the raw id it can
@@ -548,6 +570,11 @@ async function resolveSessionId(input, projectRoot) {
548
570
  pid: process.pid,
549
571
  source,
550
572
  timestamp: new Date().toISOString(),
573
+ // #1746 — `pid` above is this short-lived hook process, no witness of
574
+ // anything after it exits. The Claude Code process survives /clear, so
575
+ // the next run can tell its own predecessor from a peer that started
576
+ // later in this checkout. Absent without a readable CLAUDE_PID.
577
+ ...(typeof claudePid === 'number' ? { claude_pid: claudePid, host: hostname() } : {}),
551
578
  // Spread AFTER the base fields so the preserved high-water marks survive
552
579
  // (and never clobber the identity fields above).
553
580
  ...preserved,
@@ -565,12 +592,18 @@ async function resolveSessionId(input, projectRoot) {
565
592
  mode: normalizedMode,
566
593
  nativeSource,
567
594
  resumeLinkage,
568
- // The raw session_id our OWN previous run of this hook recorded, or null.
569
- // Surfaced (rather than kept local) because bootstrapLock() needs it as the
570
- // third conjunct of the #1091 F2 force-refresh — see the call site in main().
595
+ // The raw session_id the LAST session start in this checkout recorded, or
596
+ // null. Every session start rewrites the file, so it is our own previous
597
+ // run only when no peer started here since (#1746). Surfaced (rather than
598
+ // kept local) because bootstrapLock() needs it as the third conjunct of
599
+ // the #1091 F2 force-refresh — see the call site in main().
571
600
  predecessorSessionId: typeof prev?.session_id === 'string' && prev.session_id.length > 0
572
601
  ? prev.session_id
573
602
  : null,
603
+ // #1746 — the process witness that writer recorded, unvalidated: the
604
+ // consumer compares it with isSameClaudeProcess(), which admits only an
605
+ // exact pid match on this host.
606
+ predecessorProcess: prev ? { claude_pid: prev.claude_pid, host: prev.host } : null,
574
607
  };
575
608
  }
576
609
 
@@ -780,7 +813,14 @@ function maybeQueueConsentNudge() {
780
813
  // ---------------------------------------------------------------------------
781
814
 
782
815
  async function main() {
783
- const projectRoot = resolveProjectDir();
816
+ // #1605 — the root depends on the payload `source`/`cwd`/`session_id`, so the
817
+ // optional stdin payload is read BEFORE the first writer below. Best-effort:
818
+ // readStdinJson resolves null on a closed/empty/unparseable stdin and caps
819
+ // itself at 500 ms. The rule (entered worktree only for clear/compact/resume,
820
+ // only a verified linked worktree, never over another live session's state)
821
+ // lives in hooks/_lib/hook-root.mjs, beside the readers' own-lock lookup.
822
+ const input = await readStdinJson();
823
+ const projectRoot = await resolveSessionStartRoot(input, resolveProjectDir());
784
824
 
785
825
  // #1515 Pkt 5 — before this hook's own writers (host.json, session.lock,
786
826
  // events.jsonl) run: an un-bootstrapped repo gets `.orchestrator/.gitignore`
@@ -788,11 +828,6 @@ async function main() {
788
828
  // pay two existsSync and nothing else. Never throws.
789
829
  await ensureOrchestratorIgnore(projectRoot);
790
830
 
791
- // Read optional stdin payload in parallel with git info so we don't stall
792
- // the hook. Both are best-effort and the promise races against a 500 ms cap
793
- // inside readStdinJson itself.
794
- const stdinPromise = readStdinJson();
795
-
796
831
  // Resolve project name: basename of git toplevel, falling back to cwd basename.
797
832
  const topLevel = await gitOutput(['rev-parse', '--show-toplevel'], projectRoot);
798
833
  const projectName = topLevel
@@ -809,6 +844,24 @@ async function main() {
809
844
  const rawHeadSha = await gitOutput(['rev-parse', 'HEAD'], projectRoot);
810
845
  const headSha = rawHeadSha && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(rawHeadSha) ? rawHeadSha : null;
811
846
 
847
+ // #1668 — the upstream state at start, so emit-session can tell a stale
848
+ // head_sha (checkout behind its upstream: a later pull drags foreign commits
849
+ // into `<head_sha>..HEAD`) from a current one. Local refs only — no fetch, no
850
+ // network — and bounded at 1 s per call. No upstream configured, a timeout or
851
+ // a non-numeric count → BOTH keys omitted (not measured), never 0.
852
+ let upstreamSha = null;
853
+ let behind = null;
854
+ if (headSha) {
855
+ const [rawUpstream, rawBehind] = await Promise.all([
856
+ gitOutput(['rev-parse', '--verify', '--quiet', '@{u}^{commit}'], projectRoot, 1000),
857
+ gitOutput(['rev-list', '--count', 'HEAD..@{u}'], projectRoot, 1000),
858
+ ]);
859
+ if (rawUpstream && /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(rawUpstream) && rawBehind && /^\d+$/.test(rawBehind)) {
860
+ upstreamSha = rawUpstream;
861
+ behind = Number(rawBehind);
862
+ }
863
+ }
864
+
812
865
  // v3.1.0 env-aware banner (opt-out via enable-host-banner: false in Session Config).
813
866
  // The ask-via-tool nudge rides the same opt-out flag — both are coordinator
814
867
  // reminders shown at session start; users who silence one expect silence.
@@ -817,15 +870,23 @@ async function main() {
817
870
  // null on its own error paths, which would silently re-open the nudge.
818
871
  const hostBannerEnabled = await isHostBannerEnabled(projectRoot);
819
872
  let bannerData = null;
873
+ // #1559: a git repository with no bootstrap.lock — neither at its toplevel
874
+ // nor under projectRoot (a bootstrapped sub-project) — has one first step,
875
+ // /bootstrap; /go and /close do not work before it. Judged at the toplevel
876
+ // too, so a session started in a subdirectory of a bootstrapped repo is not
877
+ // told to bootstrap again; outside a git repository there is nothing to set up.
878
+ const needsBootstrap = Boolean(topLevel)
879
+ && !existsSync(path.join(topLevel, '.orchestrator', 'bootstrap.lock'))
880
+ && !existsSync(path.join(projectRoot, '.orchestrator', 'bootstrap.lock'));
820
881
  if (hostBannerEnabled) {
821
882
  bannerData = await emitHostBanner(projectRoot);
822
- // Always-on nudge: a user decision has three legitimate forms and AUQ-001
823
- // routes between them in order — operator verb first (nothing is blocked
824
- // while the operator picks his moment), then derive-and-report from Session
825
- // Config / STATE.md / git, and only then the tool. The banner carries the
826
- // ORDER, not an absolute; full routing + exceptions in
827
- // .claude/rules/ask-via-tool.md.
828
- pushBanner('🎯 Decide: operator verb (/go) > derive+report > AUQ if blocking (.claude/rules/ask-via-tool.md).');
883
+ // AUQ-001 in plain words (#1559): name the command when nothing is
884
+ // blocked, read available answers from config / STATE.md / git, and ask
885
+ // only when blocked — then with the question tool, never as a prose list.
886
+ // Before bootstrap, the setup step replaces it as the first action.
887
+ pushBanner(needsBootstrap
888
+ ? 'ℹ New here? Start with /bootstrap — it sets up this repo for session-orchestrator.'
889
+ : '🎯 Next step: name the command (/go, /close) when nothing is blocked; read answers from config, STATE.md or git; when blocked, ask with the question tool, never as a prose list.');
829
890
  }
830
891
 
831
892
  // F1.3 cold-start abandonment fix (PRD 2026-05-21). Emit a one-shot
@@ -864,13 +925,18 @@ async function main() {
864
925
 
865
926
  // v3.1.0 multi-session registry (#168). All steps best-effort — failures
866
927
  // must never break the hook, which is informational-only.
867
- const input = await stdinPromise;
868
928
  // #1091 — the native SessionStart `source` (Claude Code: startup|resume|
869
929
  // clear|compact), plus the linkage decision resolveSessionId() derived from
870
930
  // it (see SAME_LOGICAL_SESSION_SOURCES). Both are surfaced on the event so
871
931
  // #1091 gets its measurement instead of an inference.
872
- const { sessionId, semanticSessionId, mode, nativeSource, resumeLinkage, predecessorSessionId } =
873
- await resolveSessionId(input, projectRoot);
932
+ // #1641: the Claude Code process survives /clear while the raw id and the
933
+ // semantic label are minted anew — its pid is the witness that a live lock,
934
+ // or the current-session.json entry (#1746), is our own predecessor's.
935
+ // Admitted only as this hook's direct parent: a Codex child of a Claude
936
+ // session inherits the variable.
937
+ const claudePid = readClaudePid();
938
+ const { sessionId, semanticSessionId, mode, nativeSource, resumeLinkage, predecessorSessionId, predecessorProcess } =
939
+ await resolveSessionId(input, projectRoot, claudePid);
874
940
  // getPlatform() ALREADY implements the SO_PLATFORM override as step 1 of its
875
941
  // precedence — and, unlike a bare `??`, it validates the value against the
876
942
  // four-platform allowlist and trims it. The former `process.env.SO_PLATFORM ??
@@ -897,12 +963,23 @@ async function main() {
897
963
  mode,
898
964
  ttlHours: 4,
899
965
  nativeSource,
900
- // The raw session_id our OWN previous run of this hook recorded. It is the
966
+ // The raw session_id the last session start in this checkout recorded —
967
+ // our own previous run unless a peer started here since. It is the
901
968
  // third conjunct of the same-logical-session force-refresh (#1091 F2): a
902
969
  // semantic-label match alone is collidable on one host (#1066), so the
903
970
  // live lock must ALSO carry our predecessor's raw id before we take it
904
971
  // over. `prev` is read above, before the semantic id is minted.
905
972
  predecessorSessionId,
973
+ // #1746: who wrote that raw id — the lead yield trusts it as "our own
974
+ // predecessor" only when this same Claude process did.
975
+ predecessorProcess,
976
+ claudePid,
977
+ // #1635: a fleet lead holds this main checkout — no lock was taken; the
978
+ // model and the operator are told to work in a worktree instead.
979
+ onNotice: (context, banner) => {
980
+ setAdditionalContext(context);
981
+ pushBanner(banner);
982
+ },
906
983
  });
907
984
  } catch { /* hook must remain non-blocking */ }
908
985
 
@@ -965,6 +1042,12 @@ async function main() {
965
1042
  platform,
966
1043
  mode,
967
1044
  hostClass: bannerData?.host?.host_class ?? null,
1045
+ // #1605 — the pointer the readers (Stop, PostToolBatch, SessionEnd, the
1046
+ // current-session.json side writers) follow to THIS session's lock,
1047
+ // whatever their cwd; and the working copy, so a later relocation
1048
+ // into it by another session sees this one (B4). Stop refreshes it.
1049
+ sessionRoot: path.resolve(projectRoot),
1050
+ workRoot: path.resolve(resolveSessionRoot(input?.cwd)),
968
1051
  });
969
1052
  } catch (err) {
970
1053
  // Registration failed — emit an observability breadcrumb to sweep.log.
@@ -973,6 +1056,9 @@ async function main() {
973
1056
  }
974
1057
  peers = await detectPeers({ sessionId }).catch(() => []);
975
1058
  } catch { /* swallow — hook must remain non-blocking */ }
1059
+ // #1650 — foreign starts inside the window. detectPeers() already excludes
1060
+ // self, and its 15-min freshness covers the 5-min window (see helper JSDoc).
1061
+ const recentStarts = countRecentStarts(peers, { windowMin: START_WAVE_WINDOW_MIN });
976
1062
 
977
1063
  // Append a peer line to the host banner when a banner was already emitted
978
1064
  // and at least one peer is live on this host.
@@ -996,10 +1082,11 @@ async function main() {
996
1082
  selfRepoHash && p.repo_path_hash ? p.repo_path_hash === selfRepoHash : false);
997
1083
  const elsewhere = peers.filter((p) => !sameCopy.includes(p));
998
1084
 
999
- const threshold = await peerWarnThreshold(projectRoot);
1000
1085
  // WARN on any peer in THIS working copy — one is already enough to collide
1001
- // — or on host-wide count crossing the configured threshold.
1002
- const icon = sameCopy.length > 0 || peers.length >= threshold ? '⚠️ ' : '';
1086
+ // — or on a start wave (#1650). The host-wide peer count stays on the line
1087
+ // as information only: as a WARN trigger it fired on 67.4% of starts.
1088
+ const startWave = recentStarts >= START_WAVE_MIN;
1089
+ const icon = sameCopy.length > 0 || startWave ? '⚠️ ' : '';
1003
1090
 
1004
1091
  // Semantic ids are NOT unique — several live sessions routinely share e.g.
1005
1092
  // `main-2026-08-21-session-2`, which makes a bare semantic label unusable
@@ -1011,6 +1098,9 @@ async function main() {
1011
1098
  return `${label}${disc}:${p.branch ?? 'unknown'}:wave-${p.current_wave ?? 0}`;
1012
1099
  };
1013
1100
  pushBanner(`${icon}👥 Peers: ${peers.length} live on this host`);
1101
+ if (startWave) {
1102
+ pushBanner(` ⚠️ Start wave: ${recentStarts} sessions started or resumed in the last ${START_WAVE_WINDOW_MIN} min — stagger further starts`);
1103
+ }
1014
1104
  if (sameCopy.length > 0) {
1015
1105
  const list = sameCopy.slice(0, 3).map(fmt).join(', ');
1016
1106
  const more = sameCopy.length > 3 ? ` +${sameCopy.length - 3} more` : '';
@@ -1040,7 +1130,8 @@ async function main() {
1040
1130
  // Gated like the peer lines above: no host banner → no navigator line. A read
1041
1131
  // failure must never block the hook — the helper is fail-closed itself, the
1042
1132
  // try/catch only guards against the unexpected.
1043
- if (bannerData) {
1133
+ // #1559: solo users without fleet integration see no fleet line.
1134
+ if (bannerData && fleetIntegrationEnabled()) {
1044
1135
  try {
1045
1136
  pushBanner(navigatorBannerLine(await readNavigatorLease()));
1046
1137
  } catch { /* swallow — hook must remain non-blocking */ }
@@ -1165,7 +1256,7 @@ async function main() {
1165
1256
  pluginUpdate = await checkPluginUpdate({
1166
1257
  cacheDir: path.join(projectRoot, '.orchestrator', 'runtime'),
1167
1258
  // The probe's own fallback is env-only (SO_PLATFORM, then each harness's
1168
- // compat var) and cannot run detectPlatform()'s third step, the marker-
1259
+ // compat var) and cannot run detectPlatform()'s step 4, the marker-
1169
1260
  // directory walk from cwd. This hook already resolved the platform at the
1170
1261
  // top of run() through exactly that full precedence, so handing it over
1171
1262
  // makes the remedy instruction MEASURED rather than guessed — a Codex or
@@ -1186,8 +1277,16 @@ async function main() {
1186
1277
  // this root supersedes. `peer_count` is deliberately unchanged; this field
1187
1278
  // makes the supersession rate measurable instead of inferred (HR-105).
1188
1279
  peers_superseded: mechanicalPeersSuperseded,
1280
+ // #1650 — start-wave signal (HR-105): foreign registry entries whose
1281
+ // started_at lies within the last `recent_starts_window_min` minutes.
1282
+ recent_starts_count: recentStarts,
1283
+ recent_starts_window_min: START_WAVE_WINDOW_MIN,
1189
1284
  };
1190
1285
  if (headSha) payload.head_sha = headSha;
1286
+ if (upstreamSha !== null) {
1287
+ payload.upstream_sha = upstreamSha;
1288
+ payload.behind = behind;
1289
+ }
1191
1290
 
1192
1291
  // #1091 — record the native SessionStart source so "does the same raw
1193
1292
  // session_id repeat under source=resume?" becomes answerable from
package/hooks/on-stop.mjs CHANGED
@@ -47,6 +47,7 @@ import { AGENT_ID_RE, resolveSubagentSidecar } from './_lib/subagent-paths.mjs';
47
47
 
48
48
  import { emitEvent } from '../scripts/lib/events.mjs';
49
49
  import { detectPlatform, getProjectDir, resolveSessionRoot } from '../scripts/lib/platform.mjs';
50
+ import { resolveOwnLockRoot } from './_lib/hook-root.mjs';
50
51
  import { findOwnScopeFile } from '../scripts/lib/scope-gate.mjs';
51
52
  import {
52
53
  classifyManifestSession,
@@ -119,7 +120,7 @@ function discriminate(input) {
119
120
  // dependency degradation (GH Kanevry/session-orchestrator#63)
120
121
  // ---------------------------------------------------------------------------
121
122
  //
122
- // zx is loaded LAZILY. A static `import { $ } from 'zx'` fails at MODULE LOAD
123
+ // zx is loaded LAZILY. A static `import { $ } from '../scripts/lib/zx.mjs'` fails at MODULE LOAD
123
124
  // time when node_modules is absent (interrupted install, EPERM sandbox, half-
124
125
  // synced plugin cache), so the harness prints a 10-frame ERR_MODULE_NOT_FOUND
125
126
  // stack on EVERY turn end with no hint that `npm install` is the fix. This
@@ -175,7 +176,7 @@ function warnDependenciesMissingOnce() {
175
176
  */
176
177
  async function loadZx() {
177
178
  try {
178
- return (await import('zx')).$;
179
+ return (await import('../scripts/lib/zx.mjs')).$;
179
180
  } catch (err) {
180
181
  if (err?.code !== 'ERR_MODULE_NOT_FOUND') throw err;
181
182
  warnDependenciesMissingOnce();
@@ -401,11 +402,13 @@ function reaperTriggerField(trigger) {
401
402
  */
402
403
  async function handleStop(input, reaperTrigger = null) {
403
404
  // Two roots, by writer (#1511 point b). `current-session.json` and
404
- // `session.lock` (session id, lock heartbeat, duration) stay at the LAUNCH
405
- // dir: on-session-start.mjs writes both at `resolveProjectDir()`. The wave
405
+ // `session.lock` (session id, lock heartbeat, duration) are read where
406
+ // on-session-start.mjs wrote them: the launch dir, or — after EnterWorktree
407
+ // and a /clear, compact or resume there (#1605) — the entered worktree, found
408
+ // by the lock carrying this session's raw id (`resolveOwnLockRoot`). The wave
406
409
  // and the commit/branch describe the tree the session WORKS in — after
407
410
  // EnterWorktree that is the session root, not the launch checkout.
408
- const projectRoot = getProjectDir();
411
+ const projectRoot = resolveOwnLockRoot(input, getProjectDir());
409
412
  const sessionRoot = resolveSessionRoot(input?.cwd);
410
413
 
411
414
  const wave = await readWaveNumber(sessionRoot, input);
@@ -453,7 +456,9 @@ async function handleStop(input, reaperTrigger = null) {
453
456
  // is visible in sweep.log rather than invisible. One line per turn while
454
457
  // the entry is absent; that volume IS the signal, and the next
455
458
  // SessionStart's registerSelf() ends it.
456
- const refreshed = await heartbeat(sessionId);
459
+ // #1605 B4 — `work_root` follows the session into an entered worktree,
460
+ // so a relocating SessionStart of another session sees it working there.
461
+ const refreshed = await heartbeat(sessionId, { workRoot: path.resolve(sessionRoot) });
457
462
  if (refreshed === null) {
458
463
  logSweepEvent({
459
464
  event: 'heartbeat-missing',
@@ -100,7 +100,12 @@ async function resolveSessionId(input, projectDir) {
100
100
 
101
101
  let currentSession = null;
102
102
  if (projectDir) {
103
- const persisted = path.join(projectDir, '.orchestrator', 'current-session.json');
103
+ // #1605 — read THIS session's record where on-session-start.mjs wrote it
104
+ // (the entered worktree after a /clear there), found by the lock carrying
105
+ // the raw id. Lazy: only a matched issue command pays for the lookup.
106
+ const { resolveOwnLockRoot } = await import('./_lib/hook-root.mjs');
107
+ const ownRoot = resolveOwnLockRoot({ session_id: nativeRawId, cwd: input?.cwd }, projectDir);
108
+ const persisted = path.join(ownRoot, '.orchestrator', 'current-session.json');
104
109
  if (existsSync(persisted)) {
105
110
  try {
106
111
  currentSession = await readJson(persisted);
@@ -1093,9 +1093,10 @@ async function main() {
1093
1093
  // manifest a Bash call just corrupted. `'own'` and `'unknown'` (legacy,
1094
1094
  // unbound, or no resolvable own identity) fall through unchanged.
1095
1095
  //
1096
- // CEILING (BV-004): on a harness that supplies no session id at all (Codex
1097
- // CLI, Cursor today) the own-id set is empty, so this gate is permanently
1098
- // `'unknown'` = full pre-#1153 behaviour there.
1096
+ // CEILING (BV-004): on a harness that supplies no session id at all (Cursor
1097
+ // today) the own-id set is empty, so this gate is permanently `'unknown'` =
1098
+ // full pre-#1153 behaviour there. Codex CLI sends `session_id` in the hook
1099
+ // payload (measured 2026-10-20, codex-cli 0.160.1, #1691).
1099
1100
  {
1100
1101
  const { verdict, manifestIds } = classifyManifestSession(scope ?? {}, ownIds);
1101
1102
  if (verdict === 'foreign') {
@@ -1172,7 +1173,10 @@ async function main() {
1172
1173
  // `toContain('out-of-scope.mjs')` test to say otherwise.
1173
1174
  const missingSnapshotNotice = prevScopeState === null
1174
1175
  ? formatSnapshotMissingNotice({
1175
- sessionAge: sessionAgeMs(context.manifestRoot),
1176
+ // #1634: the clock lives beside this session's own lock, which after
1177
+ // ExitWorktree is its `session_root`, not the manifest root. Loaded lazily:
1178
+ // this branch is rare and the module graph costs ~7 ms per Bash call.
1179
+ sessionAge: sessionAgeMs((await import('./_lib/hook-root.mjs')).resolveOwnLockRoot(input, context.manifestRoot)),
1176
1180
  scopeAge: typeof scopeMtimeMs === 'number' ? Date.now() - scopeMtimeMs : null,
1177
1181
  reason: snapshot === null ? 'absent' : 'untrusted',
1178
1182
  })