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
@@ -48,10 +48,14 @@ For session-end specifically: the preamble is DETECTION-ONLY. The lock-release p
48
48
 
49
49
  ```javascript
50
50
  try {
51
+ const { pathToFileURL } = await import('node:url');
51
52
  const { appendSkillInvocation, DEFAULT_SKILL_INVOCATIONS_PATH } =
52
- await import('${PLUGIN_ROOT}/scripts/lib/skill-invocations-schema.mjs');
53
- const nodePath = await import('node:path');
54
- await appendSkillInvocation(nodePath.join(process.cwd(), DEFAULT_SKILL_INVOCATIONS_PATH), {
53
+ await import(pathToFileURL('${PLUGIN_ROOT}/scripts/lib/skill-invocations-schema.mjs').href);
54
+ const { resolveLedgerPath } =
55
+ await import(pathToFileURL('${PLUGIN_ROOT}/scripts/lib/ledger-path.mjs').href);
56
+ // In a linked worktree the main checkout's ledger when it is gitignored there,
57
+ // so the record survives the worktree (#1722).
58
+ await appendSkillInvocation(resolveLedgerPath(DEFAULT_SKILL_INVOCATIONS_PATH, process.cwd()), {
55
59
  timestamp: new Date().toISOString(),
56
60
  event: 'selected',
57
61
  skill: 'session-orchestrator:session-end',
@@ -63,7 +67,7 @@ try {
63
67
 
64
68
  ## Phase 1: Plan Verification
65
69
 
66
- > Always runs, first. Reads back the agreed plan and dispositions every item: 1.1 Done (verify with evidence) + 1.1a File-Level Grounding, 1.2 / 1.3 / 1.4 which COLLECT carryover candidates and file nothing (#769), 1.5 Discovery Scan, 1.6 Safety Review + 1.6.6 "What Not To Retry", then **1.65 Handover Alignment Gate** — the only place `[Carryover]` filing is authorized to originate, fail-open to "everything carries" — followed by 1.7 Metrics Collection, 1.8 Session Review, and 1.9 / 1.10 Mission-Status classification + breakdown. Full procedure: [`plan-verification.md`](plan-verification.md).
70
+ > Always runs, first. Reads back the agreed plan and dispositions every item: 1.1 Done (verify with evidence) + 1.1a File-Level Grounding + 1.1b External Acceptance (open `[Abnahme]` issues, closed only on oracle evidence), 1.2 / 1.3 / 1.4 which COLLECT carryover candidates and file nothing (#769), 1.5 Discovery Scan, 1.6 Safety Review + 1.6.6 "What Not To Retry", then **1.65 Handover Alignment Gate** — the only place `[Carryover]` filing is authorized to originate, fail-open to "everything carries" — followed by 1.7 Metrics Collection, 1.8 Session Review, and 1.9 / 1.10 Mission-Status classification + breakdown. Full procedure: [`plan-verification.md`](plan-verification.md).
67
71
 
68
72
  ## Phase 2: Quality Gate
69
73
 
@@ -145,7 +149,7 @@ discipline as `createSpiralCarryoverIssue`).
145
149
 
146
150
  ## Phase 3: Documentation Updates
147
151
 
148
- > Always runs. Refreshes the session-lock heartbeat at entry (`updateHeartbeat`, #590-3 — skip when `persistence: false`) so a long close-out cannot lapse the 4h TTL, then walks 3.0 Defensive Cleanup → 3.1 SSOT files → 3.2 Docs Verification → 3.2a Session Handover → 3.3 Claude-rules freshness → 3.4/3.4a STATE.md write + coordinator-snapshot cleanup → 3.45 Telemetry Flush → 3.5/3.5a/3.6.x session memory, learning extraction and the mechanical skip-plan tail → 3.7/3.7a/3.7b/3.7c/3.7d metrics write, recommendations, durable commit (#490), vault board → closed, session-eval. Full procedure: [`references/phase-3-documentation-updates.md`](references/phase-3-documentation-updates.md).
152
+ > Always runs. Refreshes the session-lock heartbeat at entry (`updateHeartbeat`, #590-3 — skip when `persistence: false`) so a long close-out cannot lapse the 4h TTL, then walks 3.0 Defensive Cleanup → 3.1 SSOT files → 3.2 Docs Verification → 3.2a Session Handover → 3.3 Claude-rules freshness → 3.4/3.4a STATE.md write + coordinator-snapshot cleanup → 3.45 Telemetry Flush → 3.5/3.5a/3.6.x session memory, learning extraction and the mechanical skip-plan tail → 3.7/3.7a/3.7b/3.7c/3.7d/3.7e metrics write, recommendations, durable commit (#490), vault board → closed, session-eval, vault orphans named (#1563). Full procedure: [`references/phase-3-documentation-updates.md`](references/phase-3-documentation-updates.md).
149
153
 
150
154
  ## Phase 3.8: Session Lock Release (#330)
151
155
 
@@ -159,7 +163,9 @@ node scripts/release-session-lock.mjs --session-id "<raw session_id>" --json
159
163
 
160
164
  `--session-id` is the PHYSICAL raw value established by session-start Phase 1.2 and stored in `.orchestrator/session.lock` `session_id` — never STATE.md `session:` and never `semantic_session_id`, both of which are attribution labels.
161
165
 
162
- The command releases the lock only when that raw id owns it, emits the terminal `orchestrator.session.lock.released` breadcrumb, and then RE-READS the lock path: **exit 0 means the lock is provably gone.** It replaces the hand-executed `release()` this phase used to prescribe, which deleted the lock silently — the SessionEnd hook then found `status: 'absent'` and emitted nothing either, so a lock lifecycle ended with no terminal event at all (#1395: 5 `lock.acquired` against 0 `lock.released` in this repo). `outcome: "absent"` is a normal exit-0 result (idempotent re-run; nothing was released, so no event is written).
166
+ No `--repo-root`: the command looks for the checkout whose lock carries that id itself (#1730) — the registry's `session_root`, the cwd's checkout, its main checkout, a registered linked worktree — and names it in the JSON (`repo_root`, `root_source`). Before, it read only the cwd, answered `absent` with exit 0 when the lock sat in the other checkout, and the lock stayed.
167
+
168
+ The command releases the lock only when that raw id owns it, emits the terminal `orchestrator.session.lock.released` breadcrumb, and then RE-READS the lock path: **exit 0 means the lock is provably gone.** It replaces the hand-executed `release()` this phase used to prescribe, which deleted the lock silently — the SessionEnd hook then found `status: 'absent'` and emitted nothing either, so a lock lifecycle ended with no terminal event at all (#1395: 5 `lock.acquired` against 0 `lock.released` in this repo). `outcome: "absent"` is a normal exit-0 result (idempotent re-run; nothing was released, so no event is written). A verified release also leaves `.orchestrator/runtime/lock-released/<raw session_id>.json` (`release_marker: true` in the JSON), so a later `compact` or `resume` of the same raw session does not re-create the lock (#1651); `startup` and `clear` take a lock as before, and the session's next `acquire()` removes the marker.
163
169
 
164
170
  A non-zero exit is a WARN, never a blocker — log `⚠ session-lock: <outcome>` and continue; the TTL provides automatic expiry for the next session:
165
171
 
@@ -187,7 +193,7 @@ Use Conventional Commits format:
187
193
  type(scope): description
188
194
 
189
195
  - [bullet points of what changed]
190
- - Closes #IID1, #IID2 (if applicable)
196
+ - Closes #IID1, #IID2 (if applicable — never an `[Abnahme]` IID, plan-verification 1.1b)
191
197
 
192
198
  Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
193
199
  ```
@@ -218,7 +224,37 @@ between the markers and executes it, so no second copy of this command may exist
218
224
  # so a consumer repo (even one without a Session Config) skips with exit 0.
219
225
  # With a remote, Session Config `mirror` decides (#1034), read through parse-config.mjs:
220
226
  # `mirror: none` → never push, even with a 'github' remote (explicit opt-out)
221
- # `mirror: github` or key absent (`mirror: null` counts as absent) → push
227
+ # `mirror: github` or key absent (`mirror: null` counts as absent) → push HEAD,
228
+ # but only after `github-export.mjs --plain-guard` (#1645):
229
+ # when HEAD or origin's main (ls-remote, fetched if not
230
+ # local) carries .orchestrator/policy/public-export.json,
231
+ # the repo publishes curated only — a checkout whose
232
+ # CLAUDE.md predates `mirror: curated` must not push its
233
+ # full private history → WARN, exit 1, nothing pushed.
234
+ # origin unreadable → same refusal (fail closed); no
235
+ # 'origin' remote → refused unless `mirror: github` is
236
+ # set explicitly (nothing to check the policy against).
237
+ # `mirror: curated` → publish ONE squash snapshot (scripts/github-export.mjs,
238
+ # #1631) on top of the verified public github main,
239
+ # never HEAD. Source anchor: only when HEAD is on branch
240
+ # `main` AND equals origin's main (`git ls-remote`, after
241
+ # the 4.3 push) — GitHub main deploys production, so a
242
+ # feature branch or unpushed/unreviewed local commits
243
+ # skip (exit 0, nothing pushed); origin unreadable →
244
+ # WARN, exit 1. Export exit 3 = nothing to publish (no
245
+ # push), other non-zero → WARN, exit 1. Bound to the
246
+ # scanned sha (read from the export's stdout only):
247
+ # push URL must equal fetch URL, then `--verify <sha>`
248
+ # re-checks it right before the push (one commit on the
249
+ # ls-remote-verified github main, trailer
250
+ # `Source-Commit: <origin main>`, no excluded path in its
251
+ # tree), else WARN, exit 1; the push sends that sha, not
252
+ # the export ref. Plain push, no lease: the commit's
253
+ # parent IS the verified sha, and a plain push is
254
+ # fast-forward-only, so GitHub itself rejects it if its
255
+ # main moved to anything that is not an ancestor of that
256
+ # sha — the same anchor a lease would enforce, without a
257
+ # force flag (blocked by the destructive-command guard).
222
258
  # any other value / config unreadable → loud WARN, no push, exit 1
223
259
  # The plugin root is resolved, not assumed: a fresh /close shell often lacks
224
260
  # PLUGIN_ROOT. Order per skills/_shared/config-reading.md (native PLUGIN_ROOT,
@@ -260,8 +296,49 @@ elif ! mirror_cfg=$(printf '%s' "$cfg_json" | node -e 'let s="";process.stdin.on
260
296
  exit 1
261
297
  elif [ "$mirror_cfg" = "none" ]; then
262
298
  echo "GitHub mirror: 'mirror: none' in Session Config — skipping (not an error)."
263
- elif [ -n "$mirror_cfg" ] && [ "$mirror_cfg" != "github" ]; then
264
- echo "WARN GitHub mirror: unknown 'mirror: ${mirror_cfg}' (expected none | github) — nothing pushed." >&2
299
+ elif [ -n "$mirror_cfg" ] && [ "$mirror_cfg" != "github" ] && [ "$mirror_cfg" != "curated" ]; then
300
+ echo "WARN GitHub mirror: unknown 'mirror: ${mirror_cfg}' (expected none | github | curated) — nothing pushed." >&2
301
+ exit 1
302
+ elif [ "$mirror_cfg" = "curated" ]; then
303
+ if ! origin_ls=$(git ls-remote origin refs/heads/main 2>&1); then
304
+ echo "WARN GitHub mirror: cannot read origin's main — curated source not verifiable, nothing pushed." >&2
305
+ echo " git said: ${origin_ls}" >&2
306
+ exit 1
307
+ fi
308
+ origin_main=$(printf '%s\n' "$origin_ls" | awk '$2 == "refs/heads/main" { print $1 }')
309
+ if [ "$(git symbolic-ref --short -q HEAD)" != "main" ] || [ -z "$origin_main" ] || [ "$(git rev-parse HEAD)" != "$origin_main" ]; then
310
+ echo "GitHub mirror: 'mirror: curated' publishes only a HEAD on main that equals origin's main (HEAD $(git symbolic-ref --short -q HEAD || echo detached) $(git rev-parse --short HEAD), origin main ${origin_main:-none}) — skipping (not an error)."
311
+ else
312
+ # stdout carries ONLY the sha (stderr goes straight to the operator), so the
313
+ # sha pushed below is the one the export scanned — never a ref name that could
314
+ # be moved in between.
315
+ export_sha=$(GIT_NO_REPLACE_OBJECTS=1 node "$so_root/scripts/github-export.mjs" --source "$origin_main")
316
+ export_rc=$?
317
+ if [ "$export_rc" -eq 3 ]; then
318
+ echo "GitHub mirror: curated snapshot of ${origin_main} unchanged — nothing to publish."
319
+ elif [ "$export_rc" -ne 0 ]; then
320
+ echo "WARN GitHub mirror: curated export REFUSED (exit ${export_rc}, reason above) — nothing pushed." >&2
321
+ exit 1
322
+ elif ! printf '%s' "$export_sha" | grep -Eqx '[0-9a-f]{40}([0-9a-f]{24})?'; then
323
+ echo "WARN GitHub mirror: the export printed no commit sha — nothing pushed." >&2
324
+ exit 1
325
+ elif [ "$(git remote get-url github)" != "$(git remote get-url --push github)" ]; then
326
+ echo "WARN GitHub mirror: the push URL of 'github' differs from its fetch URL (pushurl or pushInsteadOf) — the verified main would not describe the push target; nothing pushed." >&2
327
+ exit 1
328
+ elif ! GIT_NO_REPLACE_OBJECTS=1 node "$so_root/scripts/github-export.mjs" --verify "$export_sha" --source "$origin_main"; then
329
+ echo "WARN GitHub mirror: ${export_sha} is not one clean snapshot of ${origin_main} on the verified github main — nothing pushed." >&2
330
+ exit 1
331
+ elif push_out=$(git push github "${export_sha}:refs/heads/main" 2>&1); then
332
+ echo "GitHub mirror: pushed curated snapshot ${export_sha} (source ${origin_main}) -> ${mirror_url}"
333
+ else
334
+ echo "WARN GitHub mirror PUSH FAILED: curated snapshot ${export_sha} is NOT on ${mirror_url}" >&2
335
+ echo "${push_out}" >&2
336
+ echo "WARN Mirror is stale — anything wired to it (site deploy) will not update." >&2
337
+ exit 1
338
+ fi
339
+ fi
340
+ elif ! GIT_NO_REPLACE_OBJECTS=1 node "$so_root/scripts/github-export.mjs" --plain-guard --mirror-key "${mirror_cfg:-unset}"; then
341
+ echo "WARN GitHub mirror: plain push of HEAD REFUSED (reason above) — this repo publishes curated only; nothing pushed." >&2
265
342
  exit 1
266
343
  elif push_out=$(git push github HEAD 2>&1); then
267
344
  echo "GitHub mirror: pushed $(git rev-parse HEAD) -> ${mirror_url}"
@@ -278,6 +355,12 @@ State 3 exits non-zero on purpose: it is the only machine-readable signal that t
278
355
  behind. Report it to the operator in the session summary; do not retry silently and do not
279
356
  swallow it with `|| true`.
280
357
 
358
+ ### 4.5 Only a Pipeline or Release Train Left
359
+
360
+ Do not wait for a pipeline or release train inside session-end. If the session has no other work left, apply `skills/_shared/fleet-protocol.md` § Rücklauf an den Navigator (Pflicht) with the wait case `zug-<name>`: exactly one report in this `/close` turn, only with fleet integration (`flotte: true`). That the waiting answer before `/close` already reported the train is intended — every waiting turn triggered by an input (`/close` is one) reports once (#1611); turns woken by `/loop`, a Monitor event, an own background agent or shell, or ScheduleWakeup do not. Write NO handover or train manifest here — a coordinator only acts on one written with the waiting answer before `/close` (same section, Übergabe und Zug-Manifest) — and never invoke `/close` or another close yourself. Then continue with Phase 4a; never gate the close on a reply (CSM-004).
361
+
362
+ **Queue marks on the own handover (#1639).** With fleet integration — whether or not a train is left — re-check the newest own handover or train manifest in the fleet folder (fleet-protocol § Flotten-Ordner und Session-Name gives `<name>`, `<cwd>` and `<startedAt>`): `node "$PLUGIN_ROOT/scripts/handover-queue-check.mjs" --name <name> --fleet-dir <cwd>/.orchestrator/runtime/fleet --since <startedAt> --apply --repo <repo> [--navigator-cli <koordinator-cli>]`, same `<repo>` and CLI as fleet-protocol Schritt 1a. It files every open point that still lacks a `queue:#`/`issue:#` mark and writes the id into the file; this edits the existing file and is not the forbidden new handover. `--since` keeps a predecessor's file of the same session name out (names repeat across days). Exit 3 (points left unmarked, e.g. the CLI is unreachable): name `Übergabe ohne Queue-IDs: <n> Punkte` with the listed points in the session summary. It never blocks the close; without fleet integration or without an own file it is a silent no-op (exit 0).
363
+
281
364
  ## Phase 4a: Auto-Promoted Worktree Cleanup (#575 P3.2)
282
365
 
283
366
  > Skip when `persistence: false`; skip silently unless the CURRENT worktree is an auto-promoted sibling — `detectAutoPromotedWorktree()` from `scripts/lib/session-end/worktree-cleanup.mjs` (marker `.orchestrator/promoted-from.json` first, legacy basename match as fallback, #1069). Runs AFTER the Phase 4 commit+push, never before (#490 durableCommit ordering). Clean worktree → auto-remove with WARN; dirty → the 3-option AUQ (`Behalten` / `Löschen` / `Manuell`) before any destructive action, per PSA-003. Full procedure: [`references/phase-4a-worktree-cleanup.md`](references/phase-4a-worktree-cleanup.md).
@@ -292,7 +375,7 @@ swallow it with `|| true`.
292
375
 
293
376
  ## Phase 6: Final Report
294
377
 
295
- Present to the user the **Session Summary**: Completed / Carried Over / Dropped at Handover Gate (#769) / New Issues Created / Unresolved Review Findings (MED-LOW, #617) / Metrics — including the Docs Health line rendered from the Phase 2.3 result and the Custom Phases line from Phase 2.5 — / Next Session Recommendations. Full template plus the Test-delta and Documentation-Coverage anchors: [`references/session-summary-template.md`](references/session-summary-template.md).
378
+ Present to the user the **Session Summary**: Completed / Abnahme offen (open `[Abnahme]` issues from Phase 1.1b — never counted as Completed, #1678) / Carried Over / Dropped at Handover Gate (#769) / New Issues Created / Unresolved Review Findings (MED-LOW, #617) / Metrics — including the Docs Health line rendered from the Phase 2.3 result and the Custom Phases line from Phase 2.5 — / Next Session Recommendations. Full template plus the Test-delta and Documentation-Coverage anchors: [`references/session-summary-template.md`](references/session-summary-template.md).
296
379
 
297
380
  ## Sub-File Reference
298
381
 
@@ -305,7 +388,7 @@ Present to the user the **Session Summary**: Completed / Carried Over / Dropped
305
388
  | `metrics-collection.md` | Phase 1.7 JSONL schema and conditional field rules |
306
389
  | `vault-operations.md` | Phase 2.1 validator bash contract and reporting matrix |
307
390
  | `drift-operations.md` | Phase 2.2 drift-checker bash contract and reporting matrix |
308
- | `references/phase-3-documentation-updates.md` | Phase 3 full procedural body — final heartbeat (#590-3), 3.0 Defensive Cleanup, 3.1 SSOT files, 3.2/3.2a docs + handover, 3.3 rules freshness, 3.4/3.4a STATE.md write + snapshot cleanup, 3.45 Telemetry Flush, 3.5/3.5a/3.6.x memory + learnings + tail dispatcher, 3.7/3.7a/3.7b/3.7c/3.7d metrics, recommendations, durable commit, vault board, session-eval |
391
+ | `references/phase-3-documentation-updates.md` | Phase 3 full procedural body — final heartbeat (#590-3), 3.0 Defensive Cleanup, 3.1 SSOT files, 3.2/3.2a docs + handover, 3.3 rules freshness, 3.4/3.4a STATE.md write + snapshot cleanup, 3.45 Telemetry Flush, 3.5/3.5a/3.6.x memory + learnings + tail dispatcher, 3.7/3.7a/3.7b/3.7c/3.7d/3.7e metrics, recommendations, durable commit, vault board, session-eval, vault orphans |
309
392
  | `phase-3-2-docs-verification.md` | Phase 3.2 full procedural body — docs-tasks load, SESSION_START_REF, per-task loop, mode-gated report, Documentation Coverage block |
310
393
  | `learning-patterns.md` | Phases 3.5a + 3.6 extraction heuristics, confidence updates, passive decay, and JSONL write procedure |
311
394
  | `phase-3-6-tail.md` | Phase 3.6.x tail — detail procedures for the tail phases: 3.6.3 Memory-Proposals Collection (`collectProposals` + AUQ multiSelect + `promoteAndClear`, composing `writeApproved` + `clearProposalsJsonl` behind a mechanical write-before-clear guard, #828), **3.6.4 Expired-Learnings Sweep — MECHANICAL since 2026-09-09**: after `planTailPhases()`, call `runTailPhases({ repoRoot, plan })` from `scripts/lib/session-end/tail-runner.mjs` (delegating to `runExpiredSweep`) and report `result['3.6.4']` (`ran`, `scanned`, `archived`); the event `orchestrator.learnings.sweep_applied` is the proof it ran (Epic #723 B4), **3.6.5 Auto-Dream — RETIRED** and **3.6.7 Auto-Dialectic — RETIRED** (both replaced by the session-start `maintenance-due` probe, `checkMaintenanceDue` in `scripts/lib/maintenance-due-banner.mjs`; headings kept as two-line stubs because other docs cite them), 3.6.6 Skill-Applied Judge (#645 L3 — two calls, not one: `buildSkillEvidence` from `scripts/lib/skill-evidence-window.mjs` renders this session's transcript into the evidence window, then `runSkillJudge` judges it and the coordinator writes each judgment via `appendSkillJudgment`; on empty evidence text `runSkillJudge` returns `status: 'no-evidence'` and does NOT dispatch, #1399 — the exact call shape is in the sub-file, do not reconstruct it here), 3.6.8 Reconciliation Rule Proposals (#696 FA3 — `runReconcile` + AUQ + `writeApprovedRules`). Loaded on demand by the SKILL.md skip-plan dispatcher (#724) — only phases with `run: true` in the `planTailPhases()` plan execute |
@@ -316,8 +399,9 @@ Present to the user the **Session Summary**: Completed / Carried Over / Dropped
316
399
  | `phase-3-7a-recommendations.md` § 3.7b | Phase 3.7b full procedural body — `withDurableCommit` invocation for `sessions.jsonl` + `STATE.md` (#490 AC2), `enabled:false` local no-op, autopilot.jsonl exclusion note |
317
400
  | `references/phase-3-documentation-updates.md` § 3.7c | Vault Board → Closed (#674) — `mirrorBoard({ explicitStatus: 'closed' })` transitions this repo's board row to `closed`; gated on `vault-integration.enabled`, generator-marked + idempotent, non-blocking, ordered after 3.7b and before 3.7d/3.4/3.8 |
318
401
  | `references/phase-3-documentation-updates.md` § 3.7d | Session-Eval (opt-in — #803) — `node scripts/eval-session.mjs --json` scores the just-closed session; gated on `eval.enabled` + `eval.mode != off` (parsed by `scripts/lib/config/eval.mjs`), optional `eval-judge` dispatch + `writeEvalReport`, advisory/never-blocks-close, ordered after 3.7 (record must exist) and before 3.4/Phase 4 (record committed with the session). Full flow in `skills/eval/SKILL.md` |
402
+ | `references/phase-3-7e-vault-orphans.md` | Phase 3.7e Vault Orphans (#1563) — `node scripts/vault-orphans.mjs --since <started_at> --json` (`findVaultOrphans` in `scripts/lib/session-end/vault-orphans.mjs`, read-only `git ls-files --others`) names untracked vault files modified since the session start, minus `_generator: session-orchestrator-` files; candidates only (mtime is no proof), one Open Question via `appendOpenQuestionOnDisk`, never commits/moves/deletes in the vault; `unavailable` = "could not measure", never "no orphans"; gated on `vault-integration.enabled`, ordered after 3.7d and before 3.4 |
319
403
  | (inline) Phase 3.8 | Session Lock Release — `release()` uses the physical raw `session_id`; raw mismatch/absent is non-fatal but never repaired with semantic labels or proof (live ambiguity remains for TTL/Reaper); fs-errors are non-fatal; runs after STATE.md writes and before Phase 4 commit staging |
320
- | (inline) Phase 4 | Commit & Push — stage individually (PSA-004), commit, push to origin, then the `github-mirror-push` block (4 states: not-a-repo / no `github` remote / pushed / push failed) |
404
+ | (inline) Phase 4 | Commit & Push — stage individually (PSA-004), commit, push to origin, then the `github-mirror-push` block (4 states: not-a-repo / no `github` remote / pushed / push failed), then 4.5 (only a pipeline or train left → one fleet Rücklauf `zug-<name>` in this turn, fleet integration only; no manifest, no self-close) |
321
405
  | `references/phase-4a-worktree-cleanup.md` | Phase 4a full procedural body — auto-promoted-worktree detection (`detectAutoPromotedWorktree`, marker-keyed since #1069), clean-check, clean auto-remove path, dirty 3-option AUQ (`Behalten`/`Löschen`/`Manuell`), PSA-003 + #490 ordering rationale |
322
406
  | `references/phase-4b-worktree-orphan-sweep.md` | Phase 4b full procedural body — `checkWorktreeOrphans()` read-only proposal set, the coordinator-rendered AUQ, opt-in `worktree-orphans.enabled` gate |
323
407
  | `references/phase-5-issue-cleanup.md` | Phase 5 full procedural body — close resolved issues via `node "$PLUGIN_ROOT/scripts/lib/issue-close-strip-labels.mjs" --close` (strip `status:*` → close → re-read; `closed: false` is a finding, not done, #308), Step 3 filing of the Phase 1.65 carry-list incl. the deferred `createSpiralCarryoverIssue` and `markOpenQuestionAnsweredOnDisk`, Step 3b `[Backlog-Sammel]` overflow, discovery-issue creation |
@@ -36,6 +36,7 @@ process.stdout.write(JSON.stringify(tasks));
36
36
  Run the accessor in `plan-verification.md § SESSION_START_REF accessor`; it exports `$SESSION_RANGE`, one complete diff range. Summary:
37
37
  - Primary: the pinned `session-start-ref` from STATE.md frontmatter → `<sha>..HEAD`.
38
38
  - Field absent: the baseline `resolveBaselineRange()` resolves → `<base>...HEAD` (never a hard-coded `origin/main`).
39
+ - Multi-tree (#1659): also run `node scripts/session-commit-files.mjs` and treat its union as additional changed files (commits in the session's other own worktrees). The own-tree range above stays authoritative; no diff across trees is computed; skipped trees (stderr) are unmeasured, not unchanged.
39
40
  - Nothing resolves: `$SESSION_RANGE` is empty and the accessor names `no-baseline-ref` or `unresolvable-ref` — log `⚠ Phase 3.2: no session range (<reason>). Docs verification skipped.` and skip Phase 3.2. Never treat the empty range as "no docs changed".
40
41
 
41
42
  If `git diff` itself fails (network issue, corrupt repo), log:
@@ -147,12 +147,12 @@ import { runTailPhases } from '${PLUGIN_ROOT}/scripts/lib/session-end/tail-runne
147
147
 
148
148
  const tail = await runTailPhases({ repoRoot: process.cwd(), plan });
149
149
  const sweep = tail['3.6.4'];
150
- // { ran: true, scanned, archived, archivePath } | { ran: false, reason: 'plan-skip' | 'no-plan' | 'error', error? }
150
+ // { ran: true, scanned, archived, archivePath } | { ran: false, reason: 'plan-skip' | 'no-plan' | 'store-role-inbox' | 'error', error? }
151
151
  ```
152
152
 
153
153
  - `runTailPhases` delegates to `runExpiredSweep({ repoRoot, plan, now })` — the same module's single-phase entry point — and returns a KEYED shape so a caller keeps working when a second phase becomes mechanical. Today exactly one phase is: 3.6.3, 3.6.5–3.6.8 stay coordinator-executed because they are AUQ-gated or need a subagent dispatch a library function cannot make.
154
154
  - **Never throws, fails CLOSED.** Any error yields `{ ran: false, reason: 'error' }` and the close proceeds. Stale-past-grace entries move into `.orchestrator/metrics/learnings-archive.jsonl` (append-only, never deleted).
155
- - **Report** `sweep.ran`, `sweep.scanned` and `sweep.archived` in the Phase 6 Final Report, e.g. `expired-sweep: 12 archived of 640 scanned`. When `ran: false`, report the `reason` instead — a skipped sweep is a stated outcome, never silence.
155
+ - **Report** `sweep.ran`, `sweep.scanned` and `sweep.archived` in the Phase 6 Final Report, e.g. `expired-sweep: 12 archived of 640 scanned`. When `ran: false`, report the `reason` instead — a skipped sweep is a stated outcome, never silence. `store-role-inbox` means this host only appends to `learnings-inbox.jsonl` (owner.yaml `learnings.store-role: inbox`, #1516); the writer host sweeps.
156
156
  - **The proof it ran is the event `orchestrator.learnings.sweep_applied`** in `.orchestrator/metrics/events.jsonl` (payload source `session-end-3.6.4`, which separates it from the standalone CLI). A close claiming a sweep with no such event did not sweep.
157
157
 
158
158
  The standalone `node scripts/sweep-expired-learnings.mjs --apply --json` CLI remains available for manual/out-of-session use; it is no longer the session-end path.
@@ -180,17 +180,19 @@ After learnings are written (Phase 3.6), and when the judge is enabled, run a **
180
180
 
181
181
  2. Determine the **judged set** — only THIS session's selected skills.
182
182
 
183
- > **Join on the RAW UUID, never on the semantic session id.** The writer is the `PreToolUse` hook `hooks/skill-invocation-telemetry.mjs`, which stamps `session_id` straight from the hook payload (`:190`) — that is the harness UUID. Measured 2026-09-19 over `.orchestrator/metrics/skill-invocations.jsonl` (841 lines): **767 raw UUIDs, 55 semantic ids, 17 null**. Joining on `main-<date>-session-N` therefore matches ~0 rows for a current session, the judged set comes back empty, and the phase reports `empty-input` — a silent no-op indistinguishable from "this session used no skills". The raw id is `process.env.CLAUDE_CODE_SESSION_ID` (or `session_id` of a live `.orchestrator/session.lock`); it is also the id that names the transcript file in step 3.
183
+ > **Join on the RAW UUID, never on the semantic session id.** The writer is the `PreToolUse` hook `hooks/skill-invocation-telemetry.mjs`, which stamps `session_id` straight from the hook payload (`:190`) — that is the harness UUID. Measured 2026-09-19 over `.orchestrator/metrics/skill-invocations.jsonl` (841 lines): **767 raw UUIDs, 55 semantic ids, 17 null**. Joining on `main-<date>-session-N` therefore matches ~0 rows for a current session, the judged set comes back empty, and the phase reports `empty-input` — a silent no-op indistinguishable from "this session used no skills". The raw id is `process.env.CLAUDE_CODE_SESSION_ID`; it is also the id that names the transcript file in step 3. The repo-global `.orchestrator/session.lock` may name a PEER session (#1664, same class as #1663), so its `session_id` counts only via `resolveSessionIds()`, which returns it only when it is among this process's own ids (`readProcessLocalSessionIds()`) and `null` otherwise. No provable id → empty judged set → `empty-input`, never a judgment of the peer's transcript.
184
184
 
185
185
  ```javascript
186
186
  import { readFileSync } from 'node:fs';
187
- import { readLock } from '${PLUGIN_ROOT}/scripts/lib/session-lock.mjs';
187
+ import { resolveSessionIds } from '${PLUGIN_ROOT}/scripts/lib/state-md.mjs';
188
188
 
189
+ // Lock id only when provably OWN — never a peer's (#1664).
189
190
  const rawSessionId =
190
191
  (process.env.CLAUDE_CODE_SESSION_ID || '').trim() ||
191
- (readLock({ repoRoot: process.cwd() })?.session_id || '').trim();
192
+ resolveSessionIds({ repoRoot: process.cwd() }).session_id;
192
193
 
193
- const selectedSkills = [...new Set(
194
+ // No id → no join: `null` would otherwise match the null-session rows.
195
+ const selectedSkills = !rawSessionId ? [] : [...new Set(
194
196
  readFileSync('.orchestrator/metrics/skill-invocations.jsonl', 'utf8')
195
197
  .split('\n').filter(Boolean)
196
198
  .flatMap((l) => { try { return [JSON.parse(l)]; } catch { return []; } })
@@ -207,6 +209,7 @@ After learnings are written (Phase 3.6), and when the judge is enabled, run a **
207
209
  import { buildSkillEvidence } from '${PLUGIN_ROOT}/scripts/lib/skill-evidence-window.mjs';
208
210
  import { runSkillJudge, evidenceBudgetChars } from '${PLUGIN_ROOT}/scripts/lib/skill-judge.mjs';
209
211
  import { appendSkillJudgment } from '${PLUGIN_ROOT}/scripts/lib/skill-judgments-schema.mjs';
212
+ import { resolveLedgerPath } from '${PLUGIN_ROOT}/scripts/lib/ledger-path.mjs';
210
213
  import path from 'node:path';
211
214
 
212
215
  const budgetTokens = config['skill-evolution']['judge-budget-tokens'] ?? 8000;
@@ -253,7 +256,8 @@ After learnings are written (Phase 3.6), and when the judge is enabled, run a **
253
256
  4. On `result.status === 'ok'`: the **COORDINATOR** writes each returned judgment to the sidecar. Stamp the per-record metadata (`timestamp`, `event: 'judged'`, `session_id`, `advisory: true`, `model`, `schema_version: 1`) and append:
254
257
 
255
258
  ```javascript
256
- const judgmentsPath = path.join(process.cwd(), '.orchestrator/metrics/skill-judgments.jsonl');
259
+ // In a linked worktree the main checkout's ledger when it is gitignored there (#1722).
260
+ const judgmentsPath = resolveLedgerPath('.orchestrator/metrics/skill-judgments.jsonl', process.cwd());
257
261
  const nowIso = new Date().toISOString();
258
262
  for (const j of result.judgments) {
259
263
  await appendSkillJudgment(
@@ -24,6 +24,10 @@ try {
24
24
  // parallel-session CWD drift). Passing an undefined repoRoot here sends every close down
25
25
  // the fail-open catch below and silently omits all 5 fields (GitLab #1036).
26
26
  const repoRoot = execSync('git rev-parse --show-toplevel', {encoding: 'utf8'}).trim();
27
+ // SAME path Phase 3.4 writes `status: completed` into (#1640): the STATE.md
28
+ // this session's state-init wrote. Pass it explicitly — never let the
29
+ // resolver re-guess, or a foreign .claude/STATE.md can take the fields.
30
+ const statePath = '<state-dir>/STATE.md';
27
31
 
28
32
  // In-memory session metrics — pulled from the session's running state,
29
33
  // NOT re-read from sessions.jsonl (which was just-written in Phase 3.7).
@@ -41,8 +45,9 @@ try {
41
45
  'rationale': rec.rationale,
42
46
  };
43
47
 
44
- await updateFrontmatterFieldsOnDisk(repoRoot, fields);
45
- console.log('Recommendations written: ' + rec.mode + ' (' + rec.rationale + ')');
48
+ const res = await updateFrontmatterFieldsOnDisk(repoRoot, fields, {statePath});
49
+ if (res.reason) throw new Error('STATE.md not written (' + res.reason + '): ' + res.path);
50
+ console.log('Recommendations written to ' + res.path + ': ' + rec.mode + ' (' + rec.rationale + ')');
46
51
  } catch (err) {
47
52
  // AC3: defensive — exception must NOT block Phase 3.4 status: completed.
48
53
  mkdirSync('.orchestrator/metrics', {recursive: true});
@@ -63,6 +68,8 @@ try {
63
68
 
64
69
  **Repo-root contract (#1036 / Kanevry#65):** `updateFrontmatterFieldsOnDisk(repoRoot, fields)` takes the repo root as its FIRST argument and rejects a missing one via `requireRepoRoot()` — there is no implicit `process.cwd()` fallback, deliberately (parallel-session CWD drift, PSA rules). Run the snippet verbatim, including the `repoRoot` binding: an `undefined` first argument throws *before* STATE.md is touched, and the AC3 catch in the snippet turns that throw into a green close with all 5 fields missing. That failure is invisible in STATE.md — it looks exactly like "this session produced no recommendation".
65
70
 
71
+ **Shared path source (#1640):** `statePath` is the one `<state-dir>/STATE.md` value this close uses for BOTH Phase 3.7a and Phase 3.4 — substitute the identical path in both snippets. The writer still refuses a target that is another session's COMPLETED record (`reason: 'foreign-state-md'`, stderr names the file); it never overwrites such a record and never redirects the write. Phase 3.4 re-stamps `session-id` to the closing process's own id, so a re-run of this phase after 3.4 still finds its own file.
72
+
66
73
  **Data source guarantee:** The three inputs (`completionRate`, `carryoverRatio`, `carryoverIssues`) MUST come from the in-memory session metrics object built in Phase 1.7, NOT from a re-read of `.orchestrator/metrics/sessions.jsonl`. Reading the just-written JSONL would introduce a circular dependency and risk reading a truncated line if Phase 3.7's `appendJsonl` was mid-flush.
67
74
 
68
75
  **Field precision:**
@@ -43,6 +43,7 @@ Read back the session plan that was agreed at the start. For EACH planned item:
43
43
  - **Verify with evidence**: read the changed files, check git diff, run relevant test
44
44
  - Confirm acceptance criteria are met
45
45
  - Mark as completed
46
+ - An `[Abnahme]` issue is never a Done item, even when its implementation landed this session — it goes to 1.1b (#1678)
46
47
 
47
48
  ### 1.1a File-Level Grounding
48
49
 
@@ -51,7 +52,7 @@ Read back the session plan that was agreed at the start. For EACH planned item:
51
52
  Compare the files the plan said would be touched against the files actually changed in the session. Catches both **scope creep** (files changed that were not in any agent's prompt scope) and **incomplete coverage** (files in the plan that were never edited).
52
53
 
53
54
  1. **Planned files** = union of all file paths from agent prompt scopes across all waves. Source: STATE.md Wave History, falling back to the original session plan's per-agent "Files:" specs. Glob patterns are expanded against the working tree at session-start time.
54
- 2. **Actual files** = `git diff --name-only "$SESSION_RANGE"` (accessor above: the pinned `session-start-ref`, else the resolved baseline range). Empty `$SESSION_RANGE` → skip 1.1a with the accessor's reason.
55
+ 2. **Actual files** = `git diff --name-only "$SESSION_RANGE"` (accessor above: the pinned `session-start-ref`, else the resolved baseline range). Empty `$SESSION_RANGE` → skip 1.1a with the accessor's reason. **Multi-tree (#1659):** also run `node scripts/session-commit-files.mjs` (session id defaults to `$CLAUDE_CODE_SESSION_ID`; `--json` prints `{ trees, perTree, skipped, union, malformed }`) and add its union — files committed in the session's OTHER worktrees, recorded by `hooks/pre-bash-staging-fence.mjs` as `orchestrator.session.work_tree` events — to Actual. The own-tree `session-start-ref` range stays authoritative for the own tree; the CLI diffs each recorded tree only inside itself, never across trees. Trees it lists as skipped (stderr) were not measured: report them, do not read them as "no changes".
55
56
  3. **Compute discrepancies:**
56
57
  - **Touched** = files in both Planned and Actual
57
58
  - **Unplanned (scope creep)** = files in Actual but not in Planned
@@ -83,6 +84,20 @@ Compare the files the plan said would be touched against the files actually chan
83
84
  ```
84
85
  The metrics field is conditional on `grounding-check: true` — when the gate is off, omit the field entirely (do not write `null`).
85
86
 
87
+ ### 1.1b External Acceptance (`[Abnahme]` issues, #1678)
88
+
89
+ `/plan` files every PRD § External Acceptance row as its own issue `[Abnahme] <Kriterium>` — real-world acceptance by the owner or an external oracle, which the implementation issue does not close. List the OPEN ones, separate from 1.1:
90
+
91
+ ```bash
92
+ glab issue list --search "[Abnahme]" --in title --per-page 100 # GitLab (open is the default)
93
+ gh issue list --search "[Abnahme] in:title" --state open --limit 100 # GitHub
94
+ ```
95
+
96
+ - **Title filter:** keep only titles that START with `[Abnahme]` — GitHub search ignores punctuation and also matches e.g. "Abnahme-Checkliste".
97
+ - **Scope:** keep only issues linked to this session's planned issues or their epic (issue links, `Epic`/`Parent` reference in the body, shared epic label). If the link is not determinable, list all open `[Abnahme]` issues and say so in the report (`scope: all open — link not determinable`).
98
+ - **Per issue:** `#<IID> <Kriterium> — owner: <from the body, else assignee, else "Owner"> — not-before: <date from the body or a label, else —>`. `/plan` writes owner, not-before, pass condition, fallback and oracle command into the body (`skills/plan/SKILL.md` § 6.1), so read the body first.
99
+ - **Closing rule:** an `[Abnahme]` issue is closed ONLY with oracle evidence in the closing comment — the command plus its result, or the owner's observation, quoted and dated. A green test alone is not enough. session-end never closes one on its own because the implementation landed, and files no carryover candidate for it (it stays open by design); it reports it in Phase 6 under **Abnahme offen**.
100
+
86
101
  ### 1.2 Partially Done Items
87
102
  - Document what was completed and what remains
88
103
  - **Do NOT file the carryover issue here (#769).** Collect a carryover **candidate** instead — append it to the in-memory candidate list that the Phase 1.65 Handover Alignment Gate consumes. The issue is filed (only if the gate confirms it) in Phase 5 Step 3. Candidate record (JS keys as `routeCandidates` / `normalizeCandidate` read them — `source-phase`→`sourcePhase`, `origin-issue`→`originIssue`; see `plan-verification.md § Candidate Record Format`):
@@ -0,0 +1,63 @@
1
+ # Phase 3.7e: Vault Orphans (#1563)
2
+
3
+ > Sub-file of the session-end skill (#1157 - agentskills.io: SKILL.md core < 500 lines, procedure in `references/`). `SKILL.md` and `references/phase-3-documentation-updates.md` keep a short stub naming this phase and its gate condition.
4
+
5
+ ## Why
6
+
7
+ A session that writes into the Meta-Vault (an audit, a report, a review, a handover) leaves an untracked file in ANOTHER repo. Its own close commits only in its own repo, and vault-mirror auto-commits only its own generator-marked files in `40-learnings/` and `50-sessions/` (`scripts/lib/vault-mirror/auto-commit.mjs`). Nothing else ever looks. Measured 2026-10-11: 268 untracked files had piled up in one vault checkout over five days. The writing session is the only one that knows it wrote them, so this phase makes it look before it closes.
8
+
9
+ ## Gate
10
+
11
+ - Skip silently unless `vault-integration.enabled: true` in Session Config.
12
+ - Skip silently when `<state-dir>/STATE.md` does not exist or has no `started_at` in its frontmatter (`persistence: false`): there is no session window to measure against and no place to record the result.
13
+
14
+ ## Ordering
15
+
16
+ Runs AFTER Phase 3.7d (Session-Eval) and BEFORE Phase 3.4 (`status: completed`), so the Open Question below is written while STATE.md is still `status: active`. See the Phase 3.4 Runtime Ordering Note.
17
+
18
+ ## Procedure
19
+
20
+ 1. Read `started_at` from the STATE.md frontmatter (the same value Phase 3.7 writes into `sessions.jsonl`).
21
+ 2. Run the read-only probe from the repo root, so the host-local vault-dir resolution (`SO_VAULT_DIR` > owner.yaml > committed `vault-integration.vault-dir`) sees this repo's Session Config:
22
+
23
+ ```bash
24
+ node "$PLUGIN_ROOT/scripts/vault-orphans.mjs" --since "<STATE.md started_at>" --json
25
+ ```
26
+
27
+ Exit `0` = measured (the JSON may hold zero candidates), `1` = bad arguments (fix the call), `2` = could not measure. The JSON is the result of `findVaultOrphans()` in `scripts/lib/session-end/vault-orphans.mjs`:
28
+
29
+ - `{ status: 'ok', candidates: [{ path, mtimeMs }], total, truncated, untracked }` - `path` is vault-relative; `candidates` holds at most 200 entries sorted by path, `total` counts all of them, `untracked` is everything git listed before the filters.
30
+ - `{ status: 'unavailable', reason, detail?, candidates: [] }` - no vault dir, not a git repo, or git failed.
31
+
32
+ A candidate is an untracked, not gitignored vault file whose mtime is at or after `started_at` and whose first 4 KB carry no `_generator: session-orchestrator-` frontmatter line (those files belong to vault-mirror, the narrative mirror, the active-sessions board or the portfolio writer).
33
+
34
+ 3. Disposition:
35
+
36
+ - **`total > 0`** - NAME the candidates in the Final Report (see `session-summary-template.md`, line `Vault orphans`), then record ONE Open Question in STATE.md, listing up to 10 paths and the count:
37
+
38
+ ```javascript
39
+ import { appendOpenQuestionOnDisk } from '${PLUGIN_ROOT}/scripts/lib/state-md.mjs';
40
+
41
+ const shown = result.candidates.slice(0, 10).map((c) => c.path);
42
+ const more = result.total - shown.length;
43
+ await appendOpenQuestionOnDisk(process.cwd(), {
44
+ question:
45
+ `Vault: ${result.total} untracked file(s) modified during this session: ` +
46
+ `${shown.join(', ')}${more > 0 ? ` (+${more} more)` : ''}. ` +
47
+ 'Commit, move or delete them in the vault - mtime is a hint, not proof this session wrote them.',
48
+ source: 'session-end/3.7e',
49
+ priority: 'medium',
50
+ });
51
+ ```
52
+
53
+ The question survives the next session-start Idle Reset and is surfaced there as a forced read (`skills/_shared/state-ownership.md` Open Questions), so the next session or the vault owner sorts the files in.
54
+ - **`total === 0`** - report `none since session start` in the Final Report. No Open Question.
55
+ - **`status: unavailable`** (exit 2) - report `could not measure (<reason>)`. NEVER report it as "no orphans": a missing measurement is not a zero. No Open Question.
56
+
57
+ ## What this phase never does
58
+
59
+ It does NOT commit into the vault, does NOT delete, does NOT move anything (PSA-003). The mtime window does not prove that this session wrote a file - a concurrent session of another repo may have written the same file in the same window - and the vault is another repo with its own rules. The phase reports; the vault owner disposes.
60
+
61
+ **Non-blocking:** a probe failure, a non-zero exit or a failed STATE.md write MUST NOT block the close. Log one `WARNING: vault orphans - <reason>; continuing close` line and proceed.
62
+
63
+ No Session Config key and no event type belong to this phase; it is a report.
@@ -11,7 +11,9 @@
11
11
  > // so a long-idle deep session's 4h-TTL lock does not lapse mid-close.
12
12
  > // BEFORE Phase 3.8 lock-release (which deletes the lock).
13
13
  > import { updateHeartbeat } from 'scripts/lib/session-lock.mjs';
14
- > updateHeartbeat({ sessionId, repoRoot: process.cwd() });
14
+ > import { resolveOwnLockRootFor } from 'scripts/lib/own-lock-root.mjs';
15
+ > // The checkout holding this session's lock, not the cwd's (#1730) — at the wrong root nothing is written.
16
+ > updateHeartbeat({ sessionId, repoRoot: resolveOwnLockRootFor({ sessionId }).root });
15
17
  > ```
16
18
  >
17
19
  > Skip silently if `persistence: false` in Session Config (no session.lock exists in that mode).
@@ -63,24 +65,24 @@ Review `<state-dir>/rules/` files that are relevant to this session's work:
63
65
 
64
66
  ### 3.4 Update STATE.md
65
67
 
66
- > **Ownership Reference:** See `skills/_shared/state-ownership.md`. session-end is authorized to set `status: completed` plus the optional `updated` timestamp (#184), and — as of Phase A of Epic #271 — the 5 Recommendation fields written by Phase 3.7a. No other fields.
68
+ > **Ownership Reference:** See `skills/_shared/state-ownership.md`. session-end is authorized to set `status: completed` plus the optional `updated` timestamp (#184), the final `current-wave`, the re-stamped `session-id` (#1640, see step 1), and — as of Phase A of Epic #271 — the 5 Recommendation fields written by Phase 3.7a. No other fields.
67
69
 
68
- > **Runtime Ordering Note (Epic #271 Phase A):** Phase 3.4's `status: completed` write executes LAST in Phase 3, AFTER Phase 3.7 (sessions.jsonl) and Phase 3.7a (Compute and Write Recommendations). The ordinal position here (3.4) is kept for historical compatibility; the canonical runtime order is `3.1 → 3.2 → 3.3 → 3.4a → 3.5 → 3.5a → 3.6 → 3.6.3 → 3.6.4 → 3.6.6 → 3.6.8 → 3.7 → 3.45 → 3.7a → 3.7b → 3.7c → 3.7d → 3.4` (3.6.3/3.6.4/3.6.6 were missing from this note pre-#724; 3.6.5 Auto-Dream and 3.6.7 Auto-Dialectic were RETIRED 2026-09-09 — see `phase-3-6-tail.md` — leaving four live tail phases, not six; the Tail-Diät skip-plan dispatcher dispatches exactly those four mechanically). Rationale: Phase 3.7a reads in-memory session metrics and writes the 5 Recommendation fields via `updateFrontmatterFields`; that write must complete BEFORE the STATE.md frontmatter is finalized with `status: completed` so the Recommendation fields are visible to the next session-start while STATE.md is still `status: active`. Crash-resilience: if `/close` aborts between 3.7a and 3.4, STATE.md carries `status: active` + Recommendations; session-start Phase 1.5 offers resume (and the banner renders). If the reverse ordering were used (status: completed first), a crash would leave `status: completed` without Recommendations — the Reader would silently no-op the banner, losing the handoff. Phase 3.45 (Telemetry Flush, #844) sits AFTER Phase 3.7 because it drains the send-queue with the just-written `sessions.jsonl` record already included, and BEFORE Phase 3.7a because it is a fire-and-forget side-effect with no dependency on the Recommendation-write ordering below it. Phase 3.7d (Session-Eval, #803) sits AFTER Phase 3.7 because it scores the `sessions.jsonl` record that phase just wrote — the record must exist first — and BEFORE Phase 3.4 because its `eval.jsonl` output is advisory and must never block the close.
70
+ > **Runtime Ordering Note (Epic #271 Phase A):** Phase 3.4's `status: completed` write executes LAST in Phase 3, AFTER Phase 3.7 (sessions.jsonl) and Phase 3.7a (Compute and Write Recommendations). The ordinal position here (3.4) is kept for historical compatibility; the canonical runtime order is `3.1 → 3.2 → 3.3 → 3.4a → 3.5 → 3.5a → 3.6 → 3.6.3 → 3.6.4 → 3.6.6 → 3.6.8 → 3.7 → 3.45 → 3.7a → 3.7b → 3.7c → 3.7d → 3.7e → 3.4` (3.6.3/3.6.4/3.6.6 were missing from this note pre-#724; 3.6.5 Auto-Dream and 3.6.7 Auto-Dialectic were RETIRED 2026-09-09 — see `phase-3-6-tail.md` — leaving four live tail phases, not six; the Tail-Diät skip-plan dispatcher dispatches exactly those four mechanically). Rationale: Phase 3.7a reads in-memory session metrics and writes the 5 Recommendation fields via `updateFrontmatterFields`; that write must complete BEFORE the STATE.md frontmatter is finalized with `status: completed` so the Recommendation fields are visible to the next session-start while STATE.md is still `status: active`. Crash-resilience: if `/close` aborts between 3.7a and 3.4, STATE.md carries `status: active` + Recommendations; session-start Phase 1.5 offers resume (and the banner renders). If the reverse ordering were used (status: completed first), a crash would leave `status: completed` without Recommendations — the Reader would silently no-op the banner, losing the handoff. Phase 3.45 (Telemetry Flush, #844) sits AFTER Phase 3.7 because it drains the send-queue with the just-written `sessions.jsonl` record already included, and BEFORE Phase 3.7a because it is a fire-and-forget side-effect with no dependency on the Recommendation-write ordering below it. Phase 3.7d (Session-Eval, #803) sits AFTER Phase 3.7 because it scores the `sessions.jsonl` record that phase just wrote — the record must exist first — and BEFORE Phase 3.4 because its `eval.jsonl` output is advisory and must never block the close.
69
71
 
70
72
  > Gate: Only run if `persistence` is enabled in Session Config and `<state-dir>/STATE.md` exists.
71
- 1. Set frontmatter `status: completed`
72
- 2. Record final wave count and completion time in the frontmatter
73
- 3. Touch `updated: <ISO 8601 UTC>` in the frontmatter (issue #184). Use `scripts/lib/state-md.mjs` → `touchUpdatedField` for safety:
73
+ 1. Finalize the frontmatter in ONE lock-guarded write, into the SAME file Phase 3.7a wrote (#1640 — identical `statePath`, passed explicitly): `status: completed`, `updated: <ISO 8601 UTC>` (#184), the final wave count as `current-wave`, and `session-id` re-stamped to this process's own raw id when one is measurable (after a mid-session `/clear` the record still names the pre-`/clear` id; the re-stamp keeps it this session's). Do not hand-edit any of these fields:
74
74
  ```bash
75
75
  node --input-type=module -e "
76
- import {readFileSync, writeFileSync} from 'node:fs';
77
- import {touchUpdatedField} from '${PLUGIN_ROOT}/scripts/lib/state-md.mjs';
78
- const p = '<state-dir>/STATE.md';
79
- writeFileSync(p, touchUpdatedField(readFileSync(p, 'utf8'), new Date().toISOString()));
76
+ import {execSync} from 'node:child_process';
77
+ import {markStateMdCompletedOnDisk} from '${PLUGIN_ROOT}/scripts/lib/state-md/frontmatter-mutators.mjs';
78
+ const repoRoot = execSync('git rev-parse --show-toplevel', {encoding: 'utf8'}).trim();
79
+ const statePath = '<state-dir>/STATE.md'; // same value as Phase 3.7a
80
+ const res = await markStateMdCompletedOnDisk(repoRoot, {statePath, currentWave: <final wave number>});
81
+ console.log(res.written ? 'STATE.md completed: ' + res.path : 'STATE.md NOT written (' + (res.reason ?? 'no change') + '): ' + res.path);
80
82
  "
81
83
  ```
82
- Silent no-op if the file has no frontmatter.
83
- 4. Keep the file as a record — do NOT delete it (next session-start reads it)
84
+ No-op if the file has no frontmatter. `reason: 'foreign-state-md'` means the path holds another session's completed record — report it as a Deviation, never hand-edit it.
85
+ 2. Keep the file as a record — do NOT delete it (next session-start reads it)
84
86
 
85
87
  If STATE.md doesn't exist, skip this subsection.
86
88
 
@@ -233,9 +235,19 @@ const boardResult = await mirrorBoard({
233
235
 
234
236
  Evaluate the just-closed session deterministically and, when configured, with an advisory LLM judge. This phase is a thin hook — the full evaluation flow lives in `skills/eval/SKILL.md`; only the close-out integration is described here.
235
237
 
236
- 1. **Deterministic run.** Invoke `node scripts/eval-session.mjs --json` — with no `--session`, the cascade (`resolveSession`, revised #822) walks records newest-to-oldest (source order) and evaluates the first one that is either `status:'completed'` or non-abandoned with evidence of completed work (typically the record Phase 3.7 just appended). Model capture: the coordinator passes `--model-id <id> --model-source self-report`; the `$ANTHROPIC_MODEL` env var wins automatically when set. Pass the configured pseudonym through with `--handle <eval.handle>` (omit when `null`). The CLI appends the eval record to `.orchestrator/metrics/eval.jsonl` (`appendEvalRecord` is never-throw).
238
+ 1. **Deterministic run.** Invoke `node scripts/eval-session.mjs --json` — with no `--session`, the cascade (`resolveSession`, revised #822) walks records newest-to-oldest (source order) and evaluates the first one that is either `status:'completed'` or non-abandoned with evidence of completed work (typically the record Phase 3.7 just appended). Model capture: the coordinator passes `--model-id <id> --model-source self-report`; the `$ANTHROPIC_MODEL` env var wins automatically when set. Pass the configured pseudonym through with `--handle <eval.handle>` (omit when `null`). The CLI appends the eval record to `.orchestrator/metrics/eval.jsonl` (`appendEvalRecord` is never-throw); run from a linked worktree, that is the main checkout's journal when the worktree ignores it (`resolveLedgerPath`, #1722), so the record survives the worktree.
237
239
  2. **Advisory judge (opt-in).** When `eval.judge` is not `off`, run the judge flow per `skills/eval/SKILL.md` § Phase 3: the coordinator dispatches the read-only `eval-judge` agent (DI'd dispatch, untrusted-data nonce fence), merges the advisory judge dimensions (`method: "judge"`, `advisory: true`, `calibration_status: "uncalibrated"`) into the record, and appends the merged record. When `eval.judge: off`, no agent is dispatched and no judge dimensions are produced.
238
240
  3. **HTML report (opt-in).** When `eval.report: html` (the default), call `writeEvalReport(record, …)` from `scripts/lib/eval/report.mjs` to emit the self-contained run report under `.orchestrator/eval/reports/<run-id>.html` (gitignored, regenerable from the record). When `eval.report: none`, skip the report.
239
241
 
240
242
  **Advisory — never blocks the close (FA6):** an eval failure — a non-zero `eval-session.mjs` exit, a judge-dispatch error, or a report-write error — MUST NOT abort `/close`. Under `mode: warn`, log a single `WARNING: session-eval failed — <reason>; continuing close` line to stderr and proceed to Phase 3.8 / Phase 4. There is NO exit-code gate on this phase. See `skills/eval/SKILL.md` for the full deterministic-engine + judge + report detail flow.
241
243
 
244
+ ### Phase 3.7e: Vault Orphans (#1563)
245
+
246
+ > Gate: Skip silently unless `vault-integration.enabled: true` in Session Config, and when STATE.md has no `started_at` (no session window to measure).
247
+
248
+ > **Ordering:** Runs AFTER Phase 3.7d and BEFORE Phase 3.4 (`status: completed`), so its one Open Question lands while STATE.md is still `status: active`. See the Phase 3.4 Runtime Ordering Note canonical order.
249
+
250
+ Names the untracked vault files this session may have left behind: `node "$PLUGIN_ROOT/scripts/vault-orphans.mjs" --since <STATE.md started_at> --json` lists untracked vault files modified since the session start, minus files carrying a `_generator: session-orchestrator-` marker. The results are candidates — mtime is a hint, not proof of authorship. The coordinator names them in the Final Report and records ONE Open Question via `appendOpenQuestionOnDisk` (up to 10 paths plus the count). It never commits, moves or deletes anything in the vault (PSA-003). Exit 2 / `status: unavailable` is reported as "could not measure", never as "no orphans". Non-blocking.
251
+
252
+ **See `phase-3-7e-vault-orphans.md` for full details.**
253
+
@@ -6,7 +6,7 @@
6
6
 
7
7
  > **VCS Reference:** Use CLI commands per the "Common CLI Commands" section of the gitlab-ops skill.
8
8
 
9
- 1. **Close resolved issues — one command strips, closes and verifies (#308):** run the CLI ONCE with every resolved issue ID, `--vcs` taken from Session Config:
9
+ 1. **Close resolved issues — one command strips, closes and verifies (#308):** run the CLI ONCE with every resolved issue ID, `--vcs` taken from Session Config. An `[Abnahme]` issue is never in that list: it closes only on oracle evidence (`plan-verification.md` § 1.1b, #1678).
10
10
 
11
11
  ```bash
12
12
  node "$PLUGIN_ROOT/scripts/lib/issue-close-strip-labels.mjs" --close --vcs <gitlab|github> <iid> [<iid> ...]
@@ -11,6 +11,10 @@ Present to the user:
11
11
  - [x] Issue #N: [description] — [evidence: tests passing, files changed]
12
12
  - [x] Issue #M: [description]
13
13
 
14
+ ### Abnahme offen (implemented ≠ accepted — never counted as Completed) [#1678]
15
+ - [ ] #<IID> [Abnahme] <Kriterium> — owner: <body | assignee | Owner> — not-before: <date | —>
16
+ - scope: [linked to this session's plan/epic | all open — link not determinable]
17
+
14
18
  ### Carried Over
15
19
  - [ ] Issue #P: [what's left] — new issue #Q created
16
20
  - [ ] [description] — blocked by [reason]
@@ -46,6 +50,10 @@ Present to the user:
46
50
  - Custom Phases: [render based on Phase 2.5 result — omit the line entirely if `custom-phases` was absent/empty]
47
51
  - Per phase: `<name>: <pass|FAIL> (exit <code>, mode=<mode>)[ — review: <path>]`
48
52
  - None ran (all filtered out by `when`): `none applicable for session-type=<type>.`
53
+ - Vault orphans: [render based on Phase 3.7e — omit the line entirely when `vault-integration.enabled` is not `true`]
54
+ - Candidates present: `<N> untracked vault file(s) modified since session start (candidates, mtime is no proof): <path>, <path>, ... — recorded as Open Question, nothing committed or deleted.`
55
+ - None: `none since session start (<U> untracked in total).`
56
+ - Could not measure (exit 2 / `status: unavailable`): `could not measure (<reason>)` — never "none".
49
57
  - Enforcement: [N violations blocked / M warnings] (or "N/A" if enforcement off)
50
58
  - Circuit breaker: [N agents hit limits, M spirals detected] (or "none")
51
59
  - Metrics written to: `.orchestrator/metrics/sessions.jsonl`
@@ -13,9 +13,10 @@
13
13
  >
14
14
  > Minimal invocation:
15
15
  > ```bash
16
- > printf '%s' "$METRICS_ENTRY" | node "$PLUGIN_ROOT/scripts/emit-session.mjs" --file .orchestrator/metrics/sessions.jsonl
16
+ > printf '%s' "$METRICS_ENTRY" | node "$PLUGIN_ROOT/scripts/emit-session.mjs"
17
17
  > ```
18
18
  > See step 2 below for the full invocation including exit-code handling.
19
+ > Pass no `--file`: the default is the main checkout's `sessions.jsonl` (#1584). A relative `--file` is taken verbatim and, run from a linked worktree, writes the worktree's own copy, which is gitignored here and lost with the worktree (#1722).
19
20
 
20
21
  1. Ensure `.orchestrator/metrics/` directory exists: `mkdir -p .orchestrator/metrics`
21
22
 
@@ -29,7 +30,7 @@
29
30
 
30
31
  > **#701.2 DOC NOTE — `completed_at >= started_at` guard:** This invariant is enforced mechanically by `scripts/emit-session.mjs`. The writer applies `clampTimestampsMonotonic()` (from `scripts/lib/session-schema/timestamps.mjs`) before `validateSession()`, clamping any inversion of `completed_at < started_at` to `started_at` and recording forensics in `_clamped: true` / `_original_completed_at`. Previously-inverted entries (e.g. `main-2026-06-21-session-4`) are already corrected. **No per-session coordinator action is needed** — the writer enforces the invariant at write time. Do not add defensive clamping logic here; the canonical guard lives in `emit-session.mjs`.
31
32
 
32
- 1a. **Token Rollup (#644, #1244 — mechanical since #1436)** — no coordinator action. `scripts/emit-session.mjs` (step 2) runs `rollupSessionTokens()` itself whenever the incoming record carries no `total_tokens`: it resolves the session's raw UUID from `.orchestrator/current-session.json` (adopted only when its `semantic_session_id` equals the record's `session_id` and no process-local session id contradicts it), joins the sibling `subagents.jsonl` on `parent_session_id`, and fills `total_tokens`, `total_token_input`, `total_token_output`, the three cache buckets, `total_cost_usd`, `subagents_with_tokens`, `matched_records`, `cost_records_priced`, `cost_records_total` and `_token_schema` for every key the record does not already carry — plus `raw_session_id`. No matching records, an unreadable ledger or a foreign marker omits the fields with a WARN on stderr, never a fabricated `0`. An explicit key on `$METRICS_ENTRY` still wins. The same UUID also backs `session_start_ref` when STATE.md has none (the `head_sha` of the session's own `orchestrator.session.started` event).
33
+ 1a. **Token Rollup (#644, #1244 — mechanical since #1436)** — no coordinator action. `scripts/emit-session.mjs` (step 2) runs `rollupSessionTokens()` itself whenever the incoming record carries no `total_tokens`: it resolves the session's raw UUID from `.orchestrator/current-session.json` (adopted only when its `semantic_session_id` equals the record's `session_id` and no process-local session id contradicts it), joins the sibling `subagents.jsonl` on `parent_session_id`, and fills `total_tokens`, `total_token_input`, `total_token_output`, the three cache buckets, `total_cost_usd`, `subagents_with_tokens`, `matched_records`, `cost_records_priced`, `cost_records_total` and `_token_schema` for every key the record does not already carry — plus `raw_session_id`. No matching records, an unreadable ledger or a foreign marker omits the fields with a WARN on stderr, never a fabricated `0`. An explicit key on `$METRICS_ENTRY` still wins. The same UUID also backs `session_start_ref` when STATE.md has none (the `head_sha` of the session's own `orchestrator.session.started` event) — except when the session's own registry entry shows `work_root` ≠ `session_root` (#1652): that sha was measured in the SessionStart root, not the tree the session works in, so the field is omitted with a WARN.
33
34
 
34
35
  **Semantics:** `null` totals mean "no token data was captured for this session" — this is NOT the same as zero cost. Do NOT coerce null to 0 when displaying or summing across sessions. The same holds for `total_cost_usd`: it is omitted whenever `cost_records_priced < cost_records_total` (#1475) — at least one cost-relevant subagent record could not be priced (a model the price table does not know, a token-bearing turn naming no model, a transcript that yielded no tokens, e.g. one over the hook's size cap, or an agent with a start record whose stop found no transcript of its own, mostly `workflow-subagent`, or an agent known only from `schema_version` 1 records — #1487 Pkt 1) — and also when `cost_records_total` is 0, because there was nothing to price. It is never "$0" for an unknown cost and never a partial sum over the v2 agents the ledger can see. Phantom stops (#939, no start record) are no agents and never count. The two counters are persisted beside it, so a missing cost says whether part of the session was unpriceable or nothing was priceable. Blind spots remain: an agent whose start record fell outside the hook's tail window looks like a phantom, an agent with neither start nor stop record is invisible. A session spanning the 2026-09-09 schema boundary therefore reports no cost (its v1 agents are unpriced candidates since 2026-10-02; before, it was priced over its v2 records only — 4 distinct fleet sessions carried such a partial sum). A subagent whose turns span several models carries a per-model `models_usage` breakdown in `subagents.jsonl` (#1470) and is priced part by part. A written `total_cost_usd` is the **API list-price equivalent** of the metered tokens, never a payment: subscription quota, purchased credits and the amount actually billed are invisible to the rollup, and no record states any of them. Since #1470 emit-session marks this with `cost_basis: "api-list-equivalent"` beside every `total_cost_usd` it writes, and writes `pricing_table_date` (the `PRICING_TABLE_DATE` of `scripts/lib/telemetry/pricing.mjs` that priced the session) beside the cost counters. A record without these fields predates #1470; its cost is a list-price estimate too, priced by an older table, not a verified bill. The emit-session warning names the reason via `match_status` (ledger absent, ledger with no readable records, or records present but none for the own raw UUID); none of these means zero cost.
35
36
 
@@ -39,7 +40,7 @@
39
40
 
40
41
  2. Append the prepared JSONL entry (from Phase 1.7; the writer itself adds the token fields — step 1a) via the validating writer `scripts/emit-session.mjs` (issue #249):
41
42
  ```bash
42
- printf '%s' "$METRICS_ENTRY" | node "$PLUGIN_ROOT/scripts/emit-session.mjs" --file .orchestrator/metrics/sessions.jsonl
43
+ printf '%s' "$METRICS_ENTRY" | node "$PLUGIN_ROOT/scripts/emit-session.mjs"
43
44
  EMIT_EXIT=$?
44
45
  if [[ $EMIT_EXIT -eq 1 ]]; then
45
46
  echo "ERROR: session-end validation failed — entry rejected by scripts/emit-session.mjs. See stderr above. Session metrics NOT written." >&2
@@ -199,7 +200,9 @@
199
200
  # gate. Non-blocking: a failure surfaces a warning and never blocks close,
200
201
  # using the same strict/warn degradation idiom as the vault-mirror step above.
201
202
  NM_OUTPUT=$(node -e "
202
- import('$PLUGIN_ROOT/scripts/lib/vault-status/narrative-mirror.mjs').then(async (m) => {
203
+ import('node:url').then(({ pathToFileURL }) =>
204
+ import(pathToFileURL('$PLUGIN_ROOT/scripts/lib/vault-status/narrative-mirror.mjs').href)
205
+ ).then(async (m) => {
203
206
  const r = await m.mirrorNarrative({ repoRoot: process.cwd() });
204
207
  process.stdout.write(JSON.stringify(r));
205
208
  }).catch((e) => { process.stderr.write(String(e && e.message || e)); process.exit(3); });