session-orchestrator 5.2.0 → 5.4.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 (427) hide show
  1. package/.agents/skills/architecture/SKILL.md +3 -1
  2. package/.agents/skills/autopilot/SKILL.md +5 -1
  3. package/.agents/skills/autopilot/agents/openai.yaml +5 -0
  4. package/.agents/skills/bootstrap/SKILL.md +5 -1
  5. package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
  6. package/.agents/skills/brainstorm/SKILL.md +5 -1
  7. package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
  8. package/.agents/skills/claude-md-drift-check/SKILL.md +4 -2
  9. package/.agents/skills/close/SKILL.md +5 -1
  10. package/.agents/skills/close/agents/openai.yaml +5 -0
  11. package/.agents/skills/convergence-monitoring/SKILL.md +4 -2
  12. package/.agents/skills/debug/SKILL.md +5 -1
  13. package/.agents/skills/debug/agents/openai.yaml +5 -0
  14. package/.agents/skills/discovery/SKILL.md +5 -1
  15. package/.agents/skills/discovery/agents/openai.yaml +5 -0
  16. package/.agents/skills/dispatcher/SKILL.md +5 -1
  17. package/.agents/skills/dispatcher/agents/openai.yaml +5 -0
  18. package/.agents/skills/docs-orchestrator/SKILL.md +3 -1
  19. package/.agents/skills/ecosystem-health/SKILL.md +3 -1
  20. package/.agents/skills/eli5/SKILL.md +5 -1
  21. package/.agents/skills/eli5/agents/openai.yaml +5 -0
  22. package/.agents/skills/eval/SKILL.md +6 -2
  23. package/.agents/skills/eval/agents/openai.yaml +5 -0
  24. package/.agents/skills/evolve/SKILL.md +6 -2
  25. package/.agents/skills/evolve/agents/openai.yaml +5 -0
  26. package/.agents/skills/frontmatter-guard/SKILL.md +3 -1
  27. package/.agents/skills/gitlab-ops/SKILL.md +3 -1
  28. package/.agents/skills/gitlab-portfolio/SKILL.md +3 -1
  29. package/.agents/skills/go/SKILL.md +5 -1
  30. package/.agents/skills/go/agents/openai.yaml +5 -0
  31. package/.agents/skills/grill/SKILL.md +5 -1
  32. package/.agents/skills/grill/agents/openai.yaml +5 -0
  33. package/.agents/skills/harness-audit/SKILL.md +5 -1
  34. package/.agents/skills/harness-audit/agents/openai.yaml +5 -0
  35. package/.agents/skills/hook-development/SKILL.md +3 -1
  36. package/.agents/skills/mcp-builder/SKILL.md +3 -1
  37. package/.agents/skills/memory-cleanup/SKILL.md +5 -1
  38. package/.agents/skills/memory-cleanup/agents/openai.yaml +5 -0
  39. package/.agents/skills/mode-selector/SKILL.md +3 -1
  40. package/.agents/skills/navigator/SKILL.md +22 -0
  41. package/.agents/skills/navigator/agents/openai.yaml +5 -0
  42. package/.agents/skills/npm-publish/SKILL.md +4 -2
  43. package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
  44. package/.agents/skills/persona-panel/SKILL.md +5 -1
  45. package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
  46. package/.agents/skills/plan/SKILL.md +5 -1
  47. package/.agents/skills/plan/agents/openai.yaml +5 -0
  48. package/.agents/skills/playwright-driver/SKILL.md +3 -1
  49. package/.agents/skills/portfolio/SKILL.md +5 -1
  50. package/.agents/skills/portfolio/agents/openai.yaml +5 -0
  51. package/.agents/skills/quality-gates/SKILL.md +3 -1
  52. package/.agents/skills/reconcile/SKILL.md +5 -1
  53. package/.agents/skills/reconcile/agents/openai.yaml +5 -0
  54. package/.agents/skills/release/SKILL.md +5 -1
  55. package/.agents/skills/release/agents/openai.yaml +5 -0
  56. package/.agents/skills/remote-offload/SKILL.md +3 -1
  57. package/.agents/skills/repo-audit/SKILL.md +5 -1
  58. package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
  59. package/.agents/skills/session/SKILL.md +21 -0
  60. package/.agents/skills/session/agents/openai.yaml +5 -0
  61. package/.agents/skills/session-end/SKILL.md +3 -1
  62. package/.agents/skills/session-plan/SKILL.md +3 -1
  63. package/.agents/skills/session-start/SKILL.md +3 -1
  64. package/.agents/skills/spinout/SKILL.md +5 -1
  65. package/.agents/skills/spinout/agents/openai.yaml +5 -0
  66. package/.agents/skills/sunset-review/SKILL.md +5 -1
  67. package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
  68. package/.agents/skills/templates-ack/SKILL.md +21 -0
  69. package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
  70. package/.agents/skills/test/SKILL.md +5 -1
  71. package/.agents/skills/test/agents/openai.yaml +5 -0
  72. package/.agents/skills/test-audit/SKILL.md +21 -0
  73. package/.agents/skills/test-audit/agents/openai.yaml +5 -0
  74. package/.agents/skills/test-runner/SKILL.md +3 -1
  75. package/.agents/skills/tmux-layout/SKILL.md +3 -1
  76. package/.agents/skills/using-orchestrator/SKILL.md +3 -1
  77. package/.agents/skills/ux-grill/SKILL.md +5 -1
  78. package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
  79. package/.agents/skills/vault-mirror/SKILL.md +3 -1
  80. package/.agents/skills/vault-sync/SKILL.md +3 -1
  81. package/.agents/skills/wave-executor/SKILL.md +3 -1
  82. package/.agents/skills/write-executable-plan/SKILL.md +3 -1
  83. package/.claude-plugin/marketplace.json +1 -1
  84. package/.claude-plugin/plugin.json +1 -1
  85. package/.codex-plugin/plugin.json +4 -4
  86. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +1 -1
  87. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -3
  88. package/.codex-plugin/skills/eval/SKILL.md +1 -1
  89. package/.codex-plugin/skills/evolve/SKILL.md +1 -1
  90. package/.codex-plugin/skills/navigator/SKILL.md +22 -0
  91. package/.codex-plugin/skills/{convergence-monitoring → navigator}/agents/openai.yaml +2 -2
  92. package/.codex-plugin/skills/npm-publish/SKILL.md +1 -3
  93. package/.codex-plugin/skills/session/SKILL.md +1 -1
  94. package/.codex-plugin/skills/test-audit/SKILL.md +21 -0
  95. package/.codex-plugin/skills/{npm-publish → test-audit}/agents/openai.yaml +1 -1
  96. package/.cursor/commands/eval.md +1 -1
  97. package/.cursor/commands/navigator.md +15 -0
  98. package/.cursor/commands/session.md +1 -1
  99. package/.cursor/commands/test-audit.md +14 -0
  100. package/.cursor/rules/000-session-orchestrator.mdc +0 -2
  101. package/.cursor/rules/050-plan.mdc +1 -1
  102. package/.cursor/rules/060-evolve.mdc +27 -12
  103. package/.cursor/skills/claude-md-drift-check/SKILL.md +1 -1
  104. package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
  105. package/.cursor/skills/eval/SKILL.md +1 -1
  106. package/.cursor/skills/navigator/SKILL.md +13 -0
  107. package/.cursor/skills/npm-publish/SKILL.md +1 -0
  108. package/.cursor/skills/test-audit/SKILL.md +12 -0
  109. package/.cursor-plugin/plugin.json +1 -1
  110. package/.orchestrator/policy/blocked-commands.json +12 -3
  111. package/AGENTS.md +9 -30
  112. package/CHANGELOG.md +206 -0
  113. package/README.md +13 -9
  114. package/SECURITY.md +12 -0
  115. package/agents/dialectic-deriver.md +13 -10
  116. package/agents/eval-judge.md +67 -45
  117. package/agents/skill-applied-judge.md +34 -19
  118. package/commands/session.md +7 -3
  119. package/docs/agent-authoring.md +2 -2
  120. package/docs/baseline.md +22 -7
  121. package/docs/ci-setup.md +57 -4
  122. package/docs/codex-setup.md +14 -2
  123. package/docs/components.md +12 -10
  124. package/docs/events-schema.md +65 -15
  125. package/docs/persona-panel.md +5 -4
  126. package/docs/rule-authoring.md +84 -17
  127. package/docs/scope-collision-guard.md +26 -4
  128. package/docs/session-config-reference.md +154 -12
  129. package/docs/session-config-template.md +41 -3
  130. package/docs/telemetry.md +2 -0
  131. package/hooks/_lib/hook-import-set.json +87 -6
  132. package/hooks/_lib/subagent-transcript.mjs +582 -31
  133. package/hooks/config-protection.mjs +11 -3
  134. package/hooks/cwd-change-restore.mjs +11 -3
  135. package/hooks/enforce-commands.mjs +70 -23
  136. package/hooks/enforce-scope.mjs +143 -33
  137. package/hooks/hooks-codex.json +1 -1
  138. package/hooks/hooks-cursor.json +5 -0
  139. package/hooks/hooks-pi.json +5 -0
  140. package/hooks/hooks.json +6 -1
  141. package/hooks/loop-guard.mjs +11 -3
  142. package/hooks/on-session-end.mjs +58 -23
  143. package/hooks/on-session-start.mjs +101 -11
  144. package/hooks/on-stop.mjs +170 -22
  145. package/hooks/operator-steer.mjs +11 -3
  146. package/hooks/post-bash-issue-budget-refund.mjs +18 -8
  147. package/hooks/post-bash-write-verify.mjs +3 -2
  148. package/hooks/post-edit-import-probe.mjs +17 -9
  149. package/hooks/post-edit-validate.mjs +13 -5
  150. package/hooks/post-subagent-discovery-validator.mjs +98 -13
  151. package/hooks/post-tool-batch-wave-signal.mjs +202 -38
  152. package/hooks/post-tool-failure-corrective-context.mjs +11 -5
  153. package/hooks/post-tooluse-frontend-slop.mjs +10 -4
  154. package/hooks/pre-auq-clarity.mjs +15 -2
  155. package/hooks/pre-bash-destructive-guard.mjs +80 -9
  156. package/hooks/pre-bash-issue-budget.mjs +16 -11
  157. package/hooks/pre-bash-memory-propose-audit.mjs +137 -56
  158. package/hooks/pre-bash-sessions-ledger-guard.mjs +419 -22
  159. package/hooks/pre-bash-staging-fence.mjs +335 -31
  160. package/hooks/pre-bash-templates-first.mjs +19 -14
  161. package/hooks/pre-edit-sessions-ledger-guard.mjs +162 -0
  162. package/hooks/pre-task-scope-disjoint.mjs +792 -74
  163. package/hooks/subagent-telemetry.mjs +111 -38
  164. package/hooks/wave-scope-commit-guard.mjs +197 -100
  165. package/monitors/monitors.json +1 -1
  166. package/output-styles/wave-summary.md +1 -1
  167. package/package.json +3 -3
  168. package/pi/prompts/eval.md +1 -1
  169. package/pi/prompts/navigator.md +12 -0
  170. package/pi/prompts/session.md +1 -1
  171. package/pi/prompts/test-audit.md +12 -0
  172. package/rules/README.md +7 -2
  173. package/rules/_index.md +3 -3
  174. package/rules/always-on/bash-harness-pitfalls.md +7 -1
  175. package/rules/opt-in-domain/prompt-caching.md +6 -2
  176. package/rules/opt-in-stack/backend-data.md +10 -2
  177. package/rules/opt-in-stack/backend.md +10 -4
  178. package/rules/opt-in-stack/frontend.md +7 -2
  179. package/rules/opt-in-stack/security-web.md +10 -4
  180. package/rules/opt-in-stack/swift.md +6 -2
  181. package/scripts/apply-session-learnings.mjs +492 -0
  182. package/scripts/archive-closed-prds.mjs +2 -1
  183. package/scripts/autopilot.mjs +70 -53
  184. package/scripts/backfill-abandoned-sessions.mjs +118 -16
  185. package/scripts/baseline-archetypes.mjs +1 -1
  186. package/scripts/check-package-manager.mjs +62 -5
  187. package/scripts/check-sessions-integrity.mjs +300 -0
  188. package/scripts/cursor-install.mjs +1 -4
  189. package/scripts/dialectic-deriver.mjs +50 -13
  190. package/scripts/emit-session.mjs +371 -31
  191. package/scripts/eval-session.mjs +65 -3
  192. package/scripts/export-hw-learnings.mjs +22 -3
  193. package/scripts/fleet-checkin.mjs +147 -0
  194. package/scripts/generate-agents-skills.mjs +102 -29
  195. package/scripts/generate-cursor-adapter.mjs +61 -16
  196. package/scripts/lib/agent-frontmatter.mjs +3 -3
  197. package/scripts/lib/agent-status.mjs +2 -31
  198. package/scripts/lib/auq/clarity.mjs +10 -2
  199. package/scripts/lib/auq/parse.mjs +12 -31
  200. package/scripts/lib/auq/schema.mjs +56 -41
  201. package/scripts/lib/auto-dialectic.mjs +304 -15
  202. package/scripts/lib/auto-dream.mjs +7 -6
  203. package/scripts/lib/autopilot/flags.mjs +18 -2
  204. package/scripts/lib/autopilot/kill-switches.mjs +6 -3
  205. package/scripts/lib/autopilot/loop.mjs +19 -2
  206. package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
  207. package/scripts/lib/baseline-archetypes.mjs +41 -22
  208. package/scripts/lib/ci-status-banner.mjs +376 -16
  209. package/scripts/lib/command-blocker.mjs +275 -28
  210. package/scripts/lib/config/dialectic.mjs +12 -3
  211. package/scripts/lib/config/gate.mjs +74 -0
  212. package/scripts/lib/config/host-paths.mjs +79 -0
  213. package/scripts/lib/config/persona-gate-wave.mjs +3 -3
  214. package/scripts/lib/config/reaper.mjs +163 -0
  215. package/scripts/lib/config-schema.mjs +13 -0
  216. package/scripts/lib/config.mjs +85 -10
  217. package/scripts/lib/convergence-monitor.mjs +74 -11
  218. package/scripts/lib/ecosystem-health.mjs +11 -0
  219. package/scripts/lib/eval/engine.mjs +421 -53
  220. package/scripts/lib/eval/judge.mjs +463 -40
  221. package/scripts/lib/eval/schema.mjs +10 -1
  222. package/scripts/lib/events-rotation.mjs +221 -25
  223. package/scripts/lib/events-schema.mjs +114 -0
  224. package/scripts/lib/events.mjs +545 -7
  225. package/scripts/lib/fleet-protocol.mjs +270 -0
  226. package/scripts/lib/frontmatter-guard.mjs +21 -10
  227. package/scripts/lib/gates/gate-baseline.mjs +40 -5
  228. package/scripts/lib/gates/gate-full.mjs +35 -5
  229. package/scripts/lib/gates/gate-helpers.mjs +336 -23
  230. package/scripts/lib/gates/gate-incremental.mjs +49 -23
  231. package/scripts/lib/gates/gate-per-file.mjs +42 -8
  232. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
  233. package/scripts/lib/instruction-budget-guard.mjs +208 -8
  234. package/scripts/lib/io.mjs +42 -8
  235. package/scripts/lib/issue-close-strip-labels.mjs +207 -49
  236. package/scripts/lib/js-mask.mjs +197 -0
  237. package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
  238. package/scripts/lib/learnings/expiry-sweep.mjs +34 -3
  239. package/scripts/lib/learnings/io.mjs +154 -26
  240. package/scripts/lib/learnings/schema.mjs +163 -15
  241. package/scripts/lib/learnings/sizing-subject.mjs +2 -2
  242. package/scripts/lib/maintenance-due-banner.mjs +53 -88
  243. package/scripts/lib/memory-proposals/schema.mjs +11 -5
  244. package/scripts/lib/named-baseline-resolver.mjs +53 -28
  245. package/scripts/lib/named-vault-resolver.mjs +104 -5
  246. package/scripts/lib/orphan-reaper/defaults.mjs +41 -0
  247. package/scripts/lib/orphan-reaper/ps-snapshot.mjs +181 -0
  248. package/scripts/lib/orphan-reaper/reaper-audit.mjs +291 -0
  249. package/scripts/lib/orphan-reaper/reaper-decide.mjs +395 -0
  250. package/scripts/lib/orphan-reaper/scan-throttle.mjs +92 -0
  251. package/scripts/lib/orphan-reaper/scan.mjs +506 -0
  252. package/scripts/lib/orphan-reaper.mjs +316 -0
  253. package/scripts/lib/owner-yaml.mjs +104 -6
  254. package/scripts/lib/peer-cards/merger.mjs +48 -10
  255. package/scripts/lib/peer-cards/reader.mjs +78 -2
  256. package/scripts/lib/persona-panel/catalog-loader.mjs +1 -1
  257. package/scripts/lib/process-group.mjs +950 -0
  258. package/scripts/lib/project-hygiene.mjs +21 -1
  259. package/scripts/lib/quality-gate.mjs +152 -33
  260. package/scripts/lib/reconcile/backlog.mjs +368 -0
  261. package/scripts/lib/reconcile/engine.mjs +55 -188
  262. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +302 -60
  263. package/scripts/lib/reconcile/sanitize.mjs +69 -3
  264. package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
  265. package/scripts/lib/resource-probe/parsers.mjs +31 -0
  266. package/scripts/lib/resource-probe.mjs +1 -1
  267. package/scripts/lib/rule-loader.mjs +141 -74
  268. package/scripts/lib/rules-sync.mjs +27 -12
  269. package/scripts/lib/scope-baseline.mjs +3 -4
  270. package/scripts/lib/scope-echo.mjs +39 -2
  271. package/scripts/lib/scope-gate.mjs +605 -1
  272. package/scripts/lib/session-close-backfill.mjs +33 -6
  273. package/scripts/lib/session-id.mjs +9 -20
  274. package/scripts/lib/session-invocation.mjs +20 -0
  275. package/scripts/lib/session-record-repair.mjs +183 -26
  276. package/scripts/lib/session-registry.mjs +27 -2
  277. package/scripts/lib/session-schema/constants.mjs +56 -2
  278. package/scripts/lib/session-schema/normalizer.mjs +56 -4
  279. package/scripts/lib/session-schema/validator.mjs +28 -1
  280. package/scripts/lib/session-schema.mjs +8 -3
  281. package/scripts/lib/session-start-probes.mjs +95 -10
  282. package/scripts/lib/session-token-rollup.mjs +245 -52
  283. package/scripts/lib/sessions-canonical.mjs +118 -4
  284. package/scripts/lib/sessions-integrity-banner.mjs +7 -1
  285. package/scripts/lib/sessions-staleness-banner.mjs +193 -51
  286. package/scripts/lib/sh-quote.mjs +29 -0
  287. package/scripts/lib/skill-evidence-window.mjs +891 -0
  288. package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
  289. package/scripts/lib/skill-evolution/engine.mjs +18 -9
  290. package/scripts/lib/skill-judge.mjs +45 -3
  291. package/scripts/lib/subagents-schema.mjs +30 -3
  292. package/scripts/lib/tail-window.mjs +56 -0
  293. package/scripts/lib/telemetry/pricing.mjs +118 -71
  294. package/scripts/lib/telemetry/schema.mjs +30 -0
  295. package/scripts/lib/telemetry/sync.mjs +61 -6
  296. package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
  297. package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
  298. package/scripts/lib/tests-src-ratio.mjs +140 -23
  299. package/scripts/lib/tmux-layout/layouts.mjs +1 -11
  300. package/scripts/lib/tmux-layout/telemetry-stats.mjs +72 -13
  301. package/scripts/lib/tmux-layout/tmux-shell.mjs +2 -11
  302. package/scripts/lib/user-invocable-skills.mjs +23 -3
  303. package/scripts/lib/ux-grill/reconcile.mjs +48 -22
  304. package/scripts/lib/validate/check-agents-skills.mjs +26 -15
  305. package/scripts/lib/validate/check-agents.mjs +1 -1
  306. package/scripts/lib/validate/check-cursor-adapter.mjs +1 -0
  307. package/scripts/lib/validate/check-entry-guard.mjs +13 -50
  308. package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
  309. package/scripts/lib/validate/check-hooks-symmetry.mjs +5 -0
  310. package/scripts/lib/validate/check-learnings-shell-writes.mjs +214 -0
  311. package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
  312. package/scripts/lib/validate/check-rules.mjs +7 -5
  313. package/scripts/lib/validate/check-skill-links.mjs +9 -1
  314. package/scripts/lib/validate/check-skill-script-paths.mjs +239 -27
  315. package/scripts/lib/validate/check-test-git-config-target.mjs +24 -34
  316. package/scripts/lib/validate/check-test-value-bans.mjs +87 -14
  317. package/scripts/lib/validate/check-unicode-safety.mjs +1 -0
  318. package/scripts/lib/validate/check-untracked-test-deps.mjs +7 -102
  319. package/scripts/lib/validate/check-unwired-features.mjs +130 -27
  320. package/scripts/lib/validate/check-validator-registration.mjs +34 -10
  321. package/scripts/lib/validate/confidential-names.mjs +10 -0
  322. package/scripts/lib/validate-vendored-rules.mjs +210 -84
  323. package/scripts/lib/vault-mirror/namespace.mjs +46 -8
  324. package/scripts/lib/vault-mirror/process.mjs +10 -3
  325. package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
  326. package/scripts/lib/vault-mirror/telemetry.mjs +8 -1
  327. package/scripts/lib/vault-status/board-writer.mjs +48 -11
  328. package/scripts/lib/vault-status/narrative-mirror.mjs +71 -16
  329. package/scripts/lib/vault-yaml.mjs +118 -0
  330. package/scripts/lib/wave-executor/dispatch-common.mjs +10 -18
  331. package/scripts/lib/wave-executor/foreign-dispatch.mjs +1 -1
  332. package/scripts/lib/wave-executor/remote-dispatch.mjs +1 -1
  333. package/scripts/lib/wave-resource-gate.mjs +34 -8
  334. package/scripts/lib/worktree/lifecycle.mjs +153 -1
  335. package/scripts/memory-propose.mjs +18 -5
  336. package/scripts/release-session-lock.mjs +306 -0
  337. package/scripts/release.mjs +40 -8
  338. package/scripts/repair-invalid-sessions.mjs +37 -4
  339. package/scripts/resolve-session-invocation.mjs +59 -0
  340. package/scripts/run-migrate-v2-cross-repo.mjs +158 -30
  341. package/scripts/run-quality-gate.mjs +357 -197
  342. package/scripts/site-numbers.mjs +163 -5
  343. package/scripts/sweep-expired-learnings.mjs +56 -5
  344. package/scripts/sweep-expired-rules.mjs +16 -4
  345. package/scripts/validate-plugin.mjs +19 -0
  346. package/scripts/validate-wave-scope.mjs +32 -105
  347. package/scripts/vault-integration-watcher.mjs +2 -1
  348. package/scripts/vault-mirror.mjs +81 -47
  349. package/scripts/wave-scope-binding.mjs +2 -1
  350. package/skills/_shared/config-reading.md +3 -3
  351. package/skills/_shared/fleet-protocol.md +131 -0
  352. package/skills/_shared/platform-tools.md +23 -11
  353. package/skills/_shared/state-ownership.md +5 -0
  354. package/skills/autopilot/SKILL.md +47 -18
  355. package/skills/bootstrap/SKILL.md +1 -1
  356. package/skills/bootstrap/_shared-template.md +1 -1
  357. package/skills/bootstrap/fast-template.md +1 -1
  358. package/skills/bootstrap/private-contract.md +2 -1
  359. package/skills/bootstrap/public-fallback.md +6 -3
  360. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +2 -1
  361. package/skills/claude-md-drift-check/SKILL.md +10 -10
  362. package/skills/claude-md-drift-check/checker.mjs +26 -18
  363. package/skills/convergence-monitoring/README.md +8 -1
  364. package/skills/convergence-monitoring/SIGNALS.md +50 -6
  365. package/skills/convergence-monitoring/SKILL.md +15 -6
  366. package/skills/eval/SKILL.md +39 -24
  367. package/skills/eval/rubric-v1.md +1 -0
  368. package/skills/eval/rubric-v2.md +457 -0
  369. package/skills/evolve/SKILL.md +3 -3
  370. package/skills/evolve/references/evolve-analyze-mode.md +10 -6
  371. package/skills/evolve/references/evolve-dialectic-mode.md +42 -25
  372. package/skills/gitlab-ops/SKILL.md +3 -2
  373. package/skills/memory-cleanup/SKILL.md +1 -1
  374. package/skills/navigator/SKILL.md +193 -0
  375. package/skills/navigator/references/account-check.sh +87 -0
  376. package/skills/navigator/references/fleet-decisions.mjs +231 -0
  377. package/skills/navigator/references/slot-check.sh +114 -0
  378. package/skills/npm-publish/SKILL.md +1 -1
  379. package/skills/persona-panel/SKILL.md +2 -2
  380. package/skills/persona-panel/persona-format.md +2 -2
  381. package/skills/persona-panel/presets/designer-lens.md +1 -1
  382. package/skills/persona-panel/presets/engineer-lens.md +1 -1
  383. package/skills/persona-panel/presets/pm-lens.md +1 -1
  384. package/skills/plan/mode-retro.md +19 -4
  385. package/skills/quality-gates/SKILL.md +6 -4
  386. package/skills/reconcile/SKILL.md +11 -0
  387. package/skills/remote-offload/SKILL.md +4 -2
  388. package/skills/session-end/SKILL.md +13 -16
  389. package/skills/session-end/discovery-scan.md +1 -1
  390. package/skills/session-end/learning-patterns.md +22 -12
  391. package/skills/session-end/metrics-collection.md +5 -6
  392. package/skills/session-end/phase-3-6-tail.md +55 -9
  393. package/skills/session-end/references/phase-3-documentation-updates.md +2 -2
  394. package/skills/session-end/references/phase-4a-worktree-cleanup.md +2 -0
  395. package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
  396. package/skills/session-end/references/session-summary-template.md +1 -1
  397. package/skills/session-end/session-metrics-write.md +32 -60
  398. package/skills/session-plan/SKILL.md +17 -8
  399. package/skills/session-plan/references/session-plan-task-classification.md +2 -2
  400. package/skills/session-start/SKILL.md +6 -0
  401. package/skills/session-start/references/operations-contract.md +4 -0
  402. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  403. package/skills/session-start/references/phase-4-ssot-environment-check.md +4 -3
  404. package/skills/test-audit/SKILL.md +152 -0
  405. package/skills/test-audit/references/campaign.md +111 -0
  406. package/skills/test-audit/references/mutate.sh +50 -0
  407. package/skills/ux-grill/SKILL.md +1 -1
  408. package/skills/wave-executor/SKILL.md +9 -5
  409. package/skills/wave-executor/circuit-breaker.md +2 -0
  410. package/skills/wave-executor/references/wave-executor-state-init.md +8 -3
  411. package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
  412. package/skills/wave-executor/references/wave-loop-review.md +3 -5
  413. package/templates/personas/README.md +5 -5
  414. package/templates/personas/accounting-compliance.v1.md +1 -1
  415. package/templates/personas/accounting-tax-advisor.v1.md +1 -1
  416. package/templates/personas/buyer-p1-cto.v1.md +1 -1
  417. package/templates/personas/buyer-p2-kanzlei.v1.md +1 -1
  418. package/templates/personas/buyer-p3-build.v1.md +1 -1
  419. package/templates/personas/buyer-p4-tech-veto.v1.md +1 -1
  420. package/templates/personas/buyer-p5-solo.v1.md +1 -1
  421. package/templates/personas/buyer-p6-ld.v1.md +1 -1
  422. package/templates/personas/klima-ai-expert.v1.md +1 -1
  423. package/templates/personas/klima-physicist.v1.md +1 -1
  424. package/.cursor/commands/convergence-monitoring.md +0 -13
  425. package/.cursor/commands/npm-publish.md +0 -13
  426. package/pi/prompts/convergence-monitoring.md +0 -11
  427. package/pi/prompts/npm-publish.md +0 -11
@@ -16,7 +16,7 @@ Analyze the completed session to extract reusable learnings for future sessions.
16
16
  - **Scope guidance**: was the scope too large/small? How many issues fit comfortably in one session?
17
17
  - **Deviation patterns**: read the `## Deviations` section from `<state-dir>/STATE.md` — were there plan adaptations? What triggered them? Extract as `deviation-pattern` type if a pattern emerges across sessions (e.g., "scope expansion during Impl-Core is common for this project")
18
18
 
19
- **Learning format** (append each as one JSONL line to `.orchestrator/metrics/learnings.jsonl`):
19
+ **Learning format** (collect each as a learning object; Phase 3.6 writes them through `scripts/apply-session-learnings.mjs`):
20
20
  ```json
21
21
  {
22
22
  "schema_version": 1,
@@ -27,11 +27,19 @@ Analyze the completed session to extract reusable learnings for future sessions.
27
27
  "evidence": "<what happened this session>",
28
28
  "confidence": 0.5,
29
29
  "source_session": "<session_id>",
30
- "created_at": "<ISO 8601>",
31
- "expires_at": "<ISO 8601 + learning-expiry-days (default: 30)>"
30
+ "scope": "local",
31
+ "host_class": null,
32
+ "anonymized": false,
33
+ "file_paths": ["<repo-relative path(s) this learning applies to>"],
34
+ "created_at": "<ISO 8601>"
32
35
  }
33
36
  ```
34
37
 
38
+ - `scope` MUST be one of `local|private|public`. A file PATH belongs in `file_paths`, **never** in `scope` — a producer that wrote a path into `scope` is the GH#69 root cause, and the strict `validateLearning` in the write CLI rejects it.
39
+ - `schema_version` MUST be the integer `1` (not `"1"`, not `2`).
40
+ - `expires_at` may be omitted: the CLI derives it per `type` via `deriveExpiresAt()` (`LEARNING_TTL_DAYS`), not a flat +30 days.
41
+ - `file_paths` may be omitted when the learning is not about specific files; for `fragile-file`, `file_paths: [subject]`.
42
+
35
43
  **Schema versioning** (`schema_version`, introduced 2026-04):
36
44
  - All new records MUST carry `schema_version: 1`. `scripts/lib/learnings.mjs` auto-stamps missing values on append/rewrite so callers can omit the field safely.
37
45
  - Records without `schema_version` are treated as `schema_version: 0` (pre-versioning legacy). They are read and validated successfully for backward compat, but the reader emits a one-line WARN to stderr flagging the missing tag.
@@ -39,7 +47,7 @@ Analyze the completed session to extract reusable learnings for future sessions.
39
47
 
40
48
  **Confidence updates for existing learnings:**
41
49
  Before writing new learnings, read `.orchestrator/metrics/learnings.jsonl` and check for existing entries with the same `type` + `subject` (exact string match on both fields):
42
- - If this session **confirms** an existing learning: note the update — increment `confidence` by +0.15 (cap at 1.0) and reset `expires_at` to current date + `learning-expiry-days` (default: 30)
50
+ - If this session **confirms** an existing learning: note the update — increment `confidence` by +0.15 (cap at 1.0) and reset `expires_at` via `deriveExpiresAt(now, type)` (the per-type TTL in `LEARNING_TTL_DAYS`)
43
51
  - If this session **contradicts** an existing learning: note the update — decrement `confidence` by -0.2
44
52
  - If no existing match: note as a new learning with confidence 0.5
45
53
 
@@ -59,11 +67,13 @@ Before writing new learnings, read `.orchestrator/metrics/learnings.jsonl` and c
59
67
  2. If count exceeds `memory-cleanup-threshold` (default: 5), suggest:
60
68
  "You have [N] session memory files. Consider running `/memory-cleanup` to consolidate."
61
69
  3. This is a suggestion only — not blocking
62
- 4. **Write learnings** to `.orchestrator/metrics/learnings.jsonl` (if file exists or new learnings were extracted):
63
- a. Read all existing lines from `learnings.jsonl` (if exists)
64
- b. Apply confidence updates from Phase 3.5a (confirmed: +0.15 capped at 1.0 AND reset `expires_at` to current date + `learning-expiry-days` (default: 30); contradicted: -0.2)
65
- c. Append new learnings from Phase 3.5a (those with no existing match)
66
- d. **Passive decay (#89)** — for every existing learning NOT touched this session (i.e., not in the set of learnings confirmed or contradicted in Phase 3.5a, and not newly appended in step c), subtract `learning-decay-rate` (from Session Config, default `0.05`) from its `confidence`. Clamp to 0.0 (do not produce negative values). The prune step in `e` will remove any entry that fell to `confidence <= 0.0`. Decay does NOT reset `expires_at` — let decayed entries continue to age naturally. If `learning-decay-rate` is `0.0`, skip this step entirely (opt-out).
70
+ 4. **Write learnings** to `.orchestrator/metrics/learnings.jsonl` (if the file exists or new learnings were extracted) — **only** through `scripts/apply-session-learnings.mjs` (#1446):
71
+ a. Write the Phase 3.5a result as ONE JSON object to a sidecar under `.orchestrator/tmp/` via the Write tool, e.g. `.orchestrator/tmp/session-learnings.json`:
72
+ `{"confidence_updates": [{"id": "<existing id>", "operation": "confirm"}], "new_learnings": [<learning objects per the format above>]}`
73
+ b. Dry run first and show the summary line (`read`, `confirmed`, `contradicted`, `appended`, `decayed`, `pruned`, `consolidated`, `kept`) — nothing is written:
74
+ `node scripts/apply-session-learnings.mjs --input .orchestrator/tmp/session-learnings.json --json`
75
+ c. Apply the same input: `node scripts/apply-session-learnings.mjs --input .orchestrator/tmp/session-learnings.json --apply --json`
76
+ d. The CLI performs everything the old hand-written steps did, in code: confirm (+0.15, cap 1.0, `expires_at` re-derived per type) / contradict (-0.2, floor 0); strict `validateLearning` on every new record; **passive decay (#89)** of every existing learning NOT confirmed or contradicted this session by `learning-decay-rate` (Session Config, default `0.05`; `0.0` opts out; `expires_at` is not reset); prune (`expires_at` < now OR `confidence` <= 0.0); consolidate duplicates (same `type` + `subject`, highest confidence wins); archive every removed record to `learnings-archive.jsonl`; atomic rewrite with a `.bak-<ISO>` snapshot — all via `pruneLearnings()` in `scripts/lib/learnings/expiry-sweep.mjs`.
67
77
 
68
78
  | Sessions since last touch | Confidence (starting 0.5, decay 0.05) | Status |
69
79
  |---|---|---|
@@ -72,7 +82,7 @@ Before writing new learnings, read `.orchestrator/metrics/learnings.jsonl` and c
72
82
  | 9 | 0.05 | active |
73
83
  | 10 | 0.00 | pruned next write |
74
84
 
75
- e. Prune: remove entries where `expires_at` < current date OR `confidence` <= 0.0
76
- f. Consolidate duplicates (same `type` + `subject`): keep the one with highest confidence
77
- g. Write the entire result back to `learnings.jsonl` (atomic rewrite with `>`, not append with `>>`)
85
+ e. Exit codes: `0` applied (or a clean no-op); `1` input or validation error — the message names the record id and the failing field, and **nothing was written**: fix the sidecar and re-run; `2` store/IO error — report it, do not retry by hand.
86
+ f. **Hand-writing the store is FORBIDDEN** — no shell rewrite, no shell append, no `jq … | tee`. Those bypass validation, the `.bak` snapshot and the archive (see `skills/evolve/SKILL.md` § Critical Rules). The blocking validator `scripts/lib/validate/check-learnings-shell-writes.mjs` fails `validate-plugin` on prose that instructs one.
87
+ g. Proof it ran: `--apply` appends one `orchestrator.learnings.session_write_applied` record to `.orchestrator/metrics/events.jsonl` (`appended`, `confirmed`, `contradicted`, `decayed`, `pruned`). No record = no write.
78
88
  h. If no existing file and no new learnings: skip
@@ -59,7 +59,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
59
59
  # corruption.
60
60
  METRICS_ENTRY=$(node --input-type=module -e "
61
61
  const entry = {
62
- session_id: '<branch>-<YYYY-MM-DD>-<HHmm>',
62
+ session_id: '<STATE.md frontmatter session, verbatim>',
63
63
  session_type: '<type>',
64
64
  platform: '<claude|codex>',
65
65
  started_at: '<ISO 8601 from STATE.md frontmatter started_at — already canonical>',
@@ -69,7 +69,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
69
69
  total_agents: <N>,
70
70
  total_files_changed: <N>,
71
71
  agent_summary: {complete: <N>, partial: <N>, failed: <N>, spiral: <N>},
72
- waves: [/* {wave, role, agent_count, files_changed, quality, agent_count_planned?, agent_count_started?, agent_count_completed?, planned_files_count?, over_delivery_ratio?} */],
72
+ waves: [/* {wave, role, agent_count, files_changed, quality, agent_count_planned?, agent_count_started?, agent_count_completed?} */],
73
73
  // effectiveness is CONSTRUCTED EXPLICITLY (#773) — NOT left as an optional
74
74
  // field for the coordinator to remember. Leaving it optional is exactly how
75
75
  // the carryover=0 blind spot recurred (41/41 records read carryover:0).
@@ -101,7 +101,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
101
101
  **Canonical JSONL schema** (for field reference — populated by the snippet above):
102
102
  ```json
103
103
  {
104
- "session_id": "<branch>-<YYYY-MM-DD>-<HHmm>",
104
+ "session_id": "<STATE.md frontmatter session, verbatim>",
105
105
  "session_type": "<type>",
106
106
  "platform": "<claude|codex>",
107
107
  "started_at": "<canonical ISO 8601 from STATE.md>",
@@ -112,7 +112,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
112
112
  "total_files_changed": N,
113
113
  "agent_summary": {"complete": N, "partial": N, "failed": N, "spiral": N},
114
114
  "waves": [
115
- {"wave": 1, "role": "Discovery", "agent_count": N, "files_changed": N, "quality": "pass|fail|skip", "agent_count_planned": N, "agent_count_started": N, "agent_count_completed": N, "planned_files_count": N, "over_delivery_ratio": 0.0},
115
+ {"wave": 1, "role": "Discovery", "agent_count": N, "files_changed": N, "quality": "pass|fail|skip", "agent_count_planned": N, "agent_count_started": N, "agent_count_completed": N},
116
116
  ...
117
117
  ],
118
118
  "discovery_stats": {
@@ -164,7 +164,7 @@ Finalize session metrics by reading the wave data accumulated during execution:
164
164
 
165
165
  > **ISO-8601 canonical format (#540):** `started_at`, `completed_at`, and `lease_acquired_at` MUST match the regex `/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/`. The validating writer (`scripts/emit-session.mjs`) rejects any non-canonical form. Use `new Date().toISOString()` (Node-native, always canonical) — never hand-edit fractional digits or timezone suffixes.
166
166
 
167
- > The `session_id` uses `<HHmm>` from the `started_at` timestamp to ensure uniqueness when multiple sessions run on the same branch in one day.
167
+ > The `session_id` is the STATE.md frontmatter `session` value, copied verbatim — the semantic label session-start minted, which `.orchestrator/current-session.json` also carries as `semantic_session_id`. Never construct one (the former `<branch>-<YYYY-MM-DD>-<HHmm>` recipe is wrong): `scripts/emit-session.mjs` adopts `raw_session_id`, the token rollup and the events-derived `session_start_ref` only when the record's `session_id` equals the marker's `semantic_session_id`; any other label is WARNed and those fields are omitted.
168
168
 
169
169
  > **Conditional fields:**
170
170
  > - `discovery_stats`: populated ONLY when `discovery-on-close: true` in Session Config AND Phase 1.5 executed successfully. Source: the stats object returned by the discovery skill (see discovery skill Phase 4.6 for schema). When discovery runs in **embedded mode** (Phases 0-4 only), `user_dismissed`, `issues_created`, and `actioned` per category will always be `0` — embedded mode does not perform user triage (Phase 5) or issue creation (Phase 6).
@@ -172,7 +172,6 @@ Finalize session metrics by reading the wave data accumulated during execution:
172
172
  > - `effectiveness`: ALWAYS populated from Phase 1 plan verification results, and CONSTRUCTED EXPLICITLY in the METRICS_ENTRY snippet (#773) — never deferred to a "remember to add" optional step (that omission is how `carryover: 0` slipped past 41 records). `completion_rate` = `completed / planned_issues` (0.0-1.0, where 0.0 means nothing was completed). **`carryover` counting rule (#773):** `carryover` is the **length of the Phase 1.65 gate carry-list** — `autoCarry` ∪ the middle-band `ask` items the operator LEFT SELECTED ∪ the answered-question `impliesWork: true` candidates — NOT the raw Phase 1.2+1.3 candidate count. On the fail-open skip (gate disabled / headless / AUQ unavailable), EVERY candidate carries, so `carryover` = the full candidate-list length. Count the gate's OUTPUT (what reaches Phase 5 Step 3 filing), not its INPUT.
173
173
  > - `effectiveness.override_ratio` (#730/H5): OPTIONAL nested field = `overridden_findings / max(total_findings_surfaced, 1)` (float 0.0-1.0). Populate ONLY when Phase 2.6 (Broken-Window Budget) ran this session (`broken-window-budget.enabled: true`). OMIT (do NOT write null/0) otherwise — **absent = "not measured"**, `0.0` = "measured, nothing overridden". `overridden_findings` = the summed `count` of the `orchestrator.finding.overridden` events emitted this session; `total_findings_surfaced` = every MED/LOW+ finding surfaced across Phase 1.8 + wave reviewers.
174
174
  > - `waves[].agent_count_planned` / `waves[].agent_count_started` / `waves[].agent_count_completed` (#724/#1115): OPTIONAL per-wave fields, sourced from `wave-loop.md` § Capture wave metrics step 7 — mirror its definitions exactly, do not re-derive them here. `agent_count_planned` = agents named in the session plan for this wave. `agent_count_started` = distinct agents whose `agent-<id>.meta.json` sidecar is present, after any silent-drop re-dispatch — NOT "produced a tool-result" (under background dispatch the launch ack is a result and would count an agent that never ran). `agent_count_completed` = distinct agents whose task-notification (`<status>completed</status>`) arrived. Omit each field when the wave did not measure it — **absent = "not measured"**, never zero-fill; `0` would read as "measured, no agent started", which is the opposite of an unmeasured wave. The two gaps carry the diagnosis: `agent_count_planned > agent_count_started` after re-dispatch is a persistent silent drop, `agent_count_started > agent_count_completed` at wave end is an agent that started and never returned. Both are also logged to STATE.md `## Deviations` by wave-loop.md, so a record and a deviation entry should agree.
175
- > - `waves[].planned_files_count` / `waves[].over_delivery_ratio` (#730/H4): OPTIONAL per-wave fields, populated from STATE.md Wave History headers of the form `(planned <P> files → actual <A>, over-delivery <R>)` (written by wave-executor §3a since #730/H4); omit when absent (pre-#730 sessions / grounding-check: false).
176
175
  > - `waves[].suite_passed` / `waves[].suite_failed` / `waves[].suite_platform` (#944): OPTIONAL per-wave fields. Omit all three when absent — absent = "not measured", `suite_failed: 0` = "measured, zero failures".
177
176
  > **`suite_passed` / `suite_failed`: read the event FIRST, the STATE.md header only as fallback (#966 step 3).** Since #954/#967 the between-waves gate wrapper `scripts/run-quality-gate.mjs` emits `orchestrator.quality_gate.{passed,failed}` with a machine-measured `counts: {passed, failed, total}` AND the `wave_number` it resolved from the `wave-scope.json` sidecar, so per-wave attribution needs no wall-clock window join. Payload fields are flat at the record's top level; for each wave `N` of this session:
178
177
  >
@@ -178,29 +178,75 @@ After learnings are written (Phase 3.6), and when the judge is enabled, run a **
178
178
 
179
179
  1. Read `config['skill-evolution'].judge` (default `false`), `config['skill-evolution']['judge-budget-tokens']` (default 8000), and `persistence`. Apply the two skip gates above.
180
180
 
181
- 2. Determine the **judged set** — only THIS session's selected skills. Read `.orchestrator/metrics/skill-invocations.jsonl` and collect the distinct `skill` values whose `session_id` matches the current session id. If the judged set is empty, `runSkillJudge` returns `status: 'empty-input'` (no dispatch) — log and continue.
181
+ 2. Determine the **judged set** — only THIS session's selected skills.
182
182
 
183
- 3. Invoke `runSkillJudge` from `scripts/lib/skill-judge.mjs`, wiring the real dispatch via the DI seam:
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.
184
184
 
185
185
  ```javascript
186
- import { runSkillJudge } from '${PLUGIN_ROOT}/scripts/lib/skill-judge.mjs';
186
+ import { readFileSync } from 'node:fs';
187
+ import { readLock } from '${PLUGIN_ROOT}/scripts/lib/session-lock.mjs';
188
+
189
+ const rawSessionId =
190
+ (process.env.CLAUDE_CODE_SESSION_ID || '').trim() ||
191
+ (readLock({ repoRoot: process.cwd() })?.session_id || '').trim();
192
+
193
+ const selectedSkills = [...new Set(
194
+ readFileSync('.orchestrator/metrics/skill-invocations.jsonl', 'utf8')
195
+ .split('\n').filter(Boolean)
196
+ .flatMap((l) => { try { return [JSON.parse(l)]; } catch { return []; } })
197
+ .filter((r) => r.session_id === rawSessionId && typeof r.skill === 'string')
198
+ .map((r) => r.skill),
199
+ )];
200
+ ```
201
+
202
+ If the judged set is empty, `runSkillJudge` returns `status: 'empty-input'` (no dispatch) — log and continue.
203
+
204
+ 3. **Build the evidence window, then invoke `runSkillJudge`.** This is ONE verifying call, not a prose instruction: before #1399 this step named a free variable `transcriptTail` that nothing in the tree produced, so the judge was dispatched with an EMPTY `<untrusted-data-…>` fence. Measured 2026-09-19 at `8f15f77b`: such a prompt is 1387 characters = 346 estimated tokens, well under the 8000-token budget, so the budget gate never caught it — the judge ruled on a transcript it had never seen.
205
+
206
+ ```javascript
207
+ import { buildSkillEvidence } from '${PLUGIN_ROOT}/scripts/lib/skill-evidence-window.mjs';
208
+ import { runSkillJudge, evidenceBudgetChars } from '${PLUGIN_ROOT}/scripts/lib/skill-judge.mjs';
187
209
  import { appendSkillJudgment } from '${PLUGIN_ROOT}/scripts/lib/skill-judgments-schema.mjs';
188
210
  import path from 'node:path';
189
211
 
190
212
  const budgetTokens = config['skill-evolution']['judge-budget-tokens'] ?? 8000;
213
+ const budget = { input: budgetTokens, output: 4000 };
214
+
215
+ // `buildSkillEvidence` resolves ~/.claude/projects/<encoded-repo>/<rawSessionId>.jsonl,
216
+ // locates each skill's invocation anchors and renders bounded excerpts. Never throws.
217
+ const evidence = await buildSkillEvidence({
218
+ repoRoot: process.cwd(),
219
+ sessionId: rawSessionId, // RAW UUID — it names the transcript FILE
220
+ skills: selectedSkills,
221
+ budgetChars: evidenceBudgetChars(selectedSkills, budget),
222
+ includeSubagents: true, // #1412 — see the note below
223
+ });
224
+
225
+ // VERIFY before dispatching — these three numbers are the receipt for this step.
226
+ console.error(
227
+ `skill-judge: evidence ${evidence.status} — ${evidence.chars} chars from ` +
228
+ `${evidence.source.records} records (${evidence.source.malformed_lines} malformed), ` +
229
+ `skipped: ${JSON.stringify(evidence.skipped)}`,
230
+ );
231
+
191
232
  const result = await runSkillJudge({
192
233
  // Claude Code path: wire the real read-only haiku subagent as dispatchAgent.
193
234
  dispatchAgent: ({ model, prompt, maxTokens }) =>
194
235
  Agent({ subagent_type: 'skill-applied-judge', model: 'haiku', prompt, max_tokens: maxTokens }),
195
236
  repoRoot: process.cwd(),
196
- sessionId,
197
- transcriptTail, // recent session transcript excerpt (UNTRUSTED — fenced by the lib)
237
+ sessionId: rawSessionId,
238
+ evidence, // UNTRUSTED excerpts — fenced by the lib
198
239
  selectedSkills, // distinct skills from step 2
199
240
  model: 'haiku',
200
- budget: { input: budgetTokens, output: 4000 },
241
+ budget,
201
242
  });
202
243
  ```
203
244
 
245
+ - `evidence.status` is `no-transcript` (no file for this session id) or `no-evidence` (file read, no invocation anchor found) or `ok`. On the first two the evidence text is `''` and `runSkillJudge` returns `status: 'no-evidence'` **without dispatching** — log and continue, never fabricate a tail to get past it.
246
+ - `evidence.source.malformed_lines > 0` means the window is a PARTIAL read of the transcript. Log it beside the judgment; a clean verdict over an incompletely-read input is the failure this field exists to expose.
247
+ - `evidence.truncated === true` or a non-empty `evidence.skipped` means some judged skill got no excerpt — the judge will correctly answer `unknown` for it.
248
+ - **`includeSubagents: true` is set HERE, not in the library (#1412).** `buildSkillEvidence`'s own default stays `false`, so every other caller keeps the fail-closed behaviour and this one choice is greppable. Without it, a skill dispatched INSIDE a subagent (`<uuid>/subagents/agent-*.jsonl`) has no anchor in the main transcript, the phase reports `no-evidence`, and the reach limit is invisible — `runSkillJudge` correctly does not dispatch, so nothing is mis-judged, but nothing is ever judged either. Cost measured 2026-09-20 over the 3 most recent sessions carrying a `subagents/` dir: records 4.2-5.0x, read time 22 → 175 ms, window size still far under budget.
249
+ - The extra records are bounded two ways, both inside `renderEvidence`: subagent-only skills share at most `DEFAULT_SUBAGENT_POOL_SHARE` (0.25) of the per-skill pool whenever coordinator-anchored skills are also present, and the shared `### session closing` excerpt is taken from the MAIN transcript's tail (`mainRecordCount`), never from the last subagent file that happens to sit at the end of the concatenated array. Anything that still does not fit is reported in `evidence.skipped` with `truncated: true` — never silently shortened.
204
250
  - **Claude Code path:** `dispatchAgent` wraps the real `Agent({ subagent_type: 'skill-applied-judge', model: 'haiku', … })`. The agent is `sandbox-tier: read-only` and RETURNS one fenced ```json block — it never writes files.
205
251
  - **Codex / Cursor path:** there is no subagent type. Wire `dispatchAgent` as a coordinator-inline call (the coordinator itself reasons over the prompt and returns `{ text }`), keeping the identical `runSkillJudge` signature. Same DI seam, no harness subagent.
206
252
 
@@ -221,16 +267,16 @@ After learnings are written (Phase 3.6), and when the judge is enabled, run a **
221
267
 
222
268
  `appendSkillJudgment` re-validates each record; `advisory !== true` is schema-rejected, so a tampered record can never be persisted.
223
269
 
224
- 5. On `result.status === 'empty-input'` or `'budget-exceeded'`: log the status (e.g. `skill-judge: skipped (budget-exceeded used=N budget=M)`) and continue. No sidecar write on either non-ok status.
270
+ 5. On `result.status === 'empty-input'`, `'no-evidence'` or `'budget-exceeded'`: log the status (e.g. `skill-judge: skipped (budget-exceeded used=N budget=M)`, `skill-judge: skipped (no-evidence — ${result.skipped_reason})`) and continue. No sidecar write on any non-ok status, and no dispatch happened on any of them.
225
271
 
226
272
  6. **Failures are non-fatal.** Any error from the dispatch or write is logged to `.orchestrator/metrics/sweep.log` and the close continues — same posture as Phase 3.6.7. The judge is advisory; a failed judgment must never block session close.
227
273
 
228
- Cross-reference: PRD §A L3 acceptance criteria (#645, epic #643); `scripts/lib/skill-judge.mjs` API (`runSkillJudge`, `validateModel`, `estimateInputTokens`, `checkBudget`, `buildJudgePrompt`, `parseJudgeResponse`); `scripts/lib/skill-judgments-schema.mjs` (`appendSkillJudgment`, `readSkillJudgments`, `validateSkillJudgment`); agent `agents/skill-applied-judge.md`.
274
+ Cross-reference: PRD §A L3 acceptance criteria (#645, epic #643); issue #1399 (the evidence producer + the raw-UUID join); `scripts/lib/skill-evidence-window.mjs` API (`buildSkillEvidence`, `locateSkillAnchors`, `renderEvidence`, `readTranscriptRecords`, `resolveRawSessionId`); `scripts/lib/skill-judge.mjs` API (`runSkillJudge`, `validateModel`, `estimateInputTokens`, `checkBudget`, `buildJudgePrompt`, `evidenceBudgetChars`, `parseJudgeResponse`); `scripts/lib/skill-judgments-schema.mjs` (`appendSkillJudgment`, `readSkillJudgments`, `validateSkillJudgment`); agent `agents/skill-applied-judge.md`.
229
275
 
230
276
  ### 3.6.7 Auto-Dialectic Dispatch (#506, F2.5) — RETIRED
231
277
 
232
278
  > **RETIRED 2026-09-09.** The nudge is replaced by the session-start `maintenance-due` probe (`checkMaintenanceDue`, `scripts/lib/maintenance-due-banner.mjs`), whose `dialectic` signal reads the side-effect-free `shouldDispatchAutoDialectic` — never a variant that advances the last-run stamp, which would consume the very signal it reports. Its decider is also gone from `planTailPhases()` in `scripts/lib/session-end/phase-skip.mjs`; the heading stays because other docs cite it.
233
- > The housekeeping session runs `/evolve dialectic` itself (see `skills/session-start/SKILL.md` Phase 7 — the maintenance loop): dry-run first, review `.orchestrator/dialectic-pending.md`, then apply. `scripts/lib/auto-dialectic.mjs` (`shouldDispatchAutoDialectic`, `writeDialecticLastRun`) and the read-only `dialectic-deriver` agent stay in use on that manual path. The recording wrapper around that signal, and its `orchestrator.dialectic.nudge_decided` event, were removed in #1288 — nothing emits that event any more. <!-- path-check: example -->
279
+ > The housekeeping session runs `/evolve dialectic` itself (see `skills/session-start/SKILL.md` Phase 7 — the maintenance loop): dry-run first, review `.orchestrator/dialectic-pending.md`, then apply. On that manual path the read-only `dialectic-deriver` agent is dispatched, and `/evolve dialectic` Step 6.4 closes the loop after a successful `--apply` or an explicit discard by calling `writeDialecticLastRun` and `consumeDialecticPending` from `scripts/lib/auto-dialectic.mjs` (`skills/evolve/references/evolve-dialectic-mode.md`); `shouldDispatchAutoDialectic` is read only by the session-start maintenance-due probe. The recording wrapper around that signal, and its `orchestrator.dialectic.nudge_decided` event, were removed in #1288 — nothing emits that event any more. <!-- path-check: example -->
234
280
 
235
281
  > **Dialectic chain rationale** — design choices in the manual `/evolve --dialectic` chain (`/evolve → runDialecticDeriver → dispatchAgent → Agent`). Session-end no longer auto-dispatches this chain (see #614 — the `evolve` agent never existed); the rationale below applies when you run `/evolve --dialectic` manually:
236
282
  > - **/evolve → subagent (not direct invoke):** the manual `/evolve --dialectic` skill spawns a subagent so the dialectic pass runs in a fresh context window — keeping the deriver's input-heavy payload (top-50 learnings + last-10 sessions + 2 peer cards + steering) out of the invoking coordinator's context, and letting the deriver run as Haiku while the coordinator stays Opus.
@@ -169,7 +169,7 @@ Then:
169
169
 
170
170
  Read `skills/session-end/session-metrics-write.md` for JSONL append, vault-mirror invocation, and behavior matrix.
171
171
 
172
- > **Token Rollup (#644):** Before emitting the JSONL record (step 2 of session-metrics-write.md), step 1a calls `rollupSessionTokens({ parentSessionId })` from `scripts/lib/session-token-rollup.mjs` and merges three optional fields onto the in-memory record: `total_token_input`, `total_token_output`, and `subagents_with_tokens` (coverage count). Null totals mean "no token data captured" — not zero cost. The rollup is non-blocking: a missing `subagents.jsonl` or all-null session still writes cleanly with null/0 values.
172
+ > **Token Rollup (#644, #1436):** When the incoming record has no `total_tokens`, `scripts/emit-session.mjs` resolves the session's own raw UUID as described in step 1a of session-metrics-write.md and calls `rollupSessionTokens({ parentSessionId: ownUuid })`, joining `parent_session_id` on that UUID rather than the semantic session ID. It fills only absent fields from the rollup whitelist; diagnostic fields `match_status` and `ledger_records` stay out of the record. Missing or unreadable ledgers, or no records for the own UUID, omit token fields with a WARN stating the reason, never zero cost; null totals are also omitted. The rollup remains non-blocking.
173
173
 
174
174
  ### 3.7a Compute and Write Recommendations (Epic #271 Phase A)
175
175
 
@@ -208,7 +208,7 @@ const boardResult = await mirrorBoard({
208
208
  repoRoot: process.cwd(),
209
209
  explicitStatus: 'closed', // force THIS repo's row to `closed`
210
210
  });
211
- // boardResult.action ∈ { 'written', 'skipped-noop', 'skipped-handwritten', 'skipped-vault-disabled', 'dry-run' }
211
+ // boardResult.action ∈ { 'written', 'skipped-noop', 'skipped-handwritten', 'skipped-vault-disabled', 'skipped-vault-not-canonical', 'dry-run' }
212
212
  ```
213
213
 
214
214
  > **Note (single-repo close path):** with `repos` omitted, `mirrorBoard` builds the repo descriptor itself as `[{ repoRoot, status: explicitStatus }]` — this is the supported single-repo shape, so `explicitStatus: 'closed'` lands on THIS repo's row. (When a caller DOES pass `repos`, each element must be a `{ repoRoot, repoName?, status? }` object, NOT a bare path string — bare strings are silently skipped by `collectRows`.) The board at `<vault-dir>/01-projects/_active-sessions.md` is generator-owned: `mirrorBoard` refuses to touch any file lacking the `session-orchestrator-active-sessions@1` marker, hard-refuses `_overview.md`, and is idempotent (a re-run after the row is already `closed` returns `skipped-noop`). Rows for repos NOT in this update are preserved verbatim by the idempotent merge.
@@ -34,6 +34,8 @@ A worktree is clean iff ALL three conditions hold:
34
34
  > **Authoritative impl:** `scripts/lib/session-end/worktree-cleanup.mjs` — `isWorktreeClean(wtPath, opts)`. Import and call; do NOT re-implement from this doc.
35
35
  >
36
36
  > Algorithm: run `git status --porcelain`; filter blank lines, then discount EXACTLY the one untracked line the promotion marker itself produces (`?? .orchestrator/promoted-from.json` — in a repo where `.orchestrator/` is only partly gitignored, or on a worktree whose branch predates the ignore line, the marker `enterWorktree()` writes would otherwise make every promoted worktree read "dirty"; a modified/staged/renamed/conflicted marker still counts as dirty). If any lines remain → dirty (`false`). Else run `git status --short --branch`; if it matches `/\bahead\b/` → unpushed (`false`). Otherwise `true`. On ANY git error → `false` (conservative PSA-003 default: never auto-remove a worktree we could not verify). Git invocation is via the injection-safe `opts.execFileFn` (default `execFileSync` with an args array — #577 HARDEN-001).
37
+ >
38
+ > `isWorktreeClean()` discounts exactly the marker's own untracked line, never operator work.
37
39
 
38
40
  ### Clean path: auto-remove + WARN (PRD §3 P3 Gherkin row 2)
39
41
 
@@ -6,22 +6,17 @@
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**: Before closing each issue, strip `status:*` workflow labels using `stripStatusLabels` from `scripts/lib/issue-close-strip-labels.mjs` (#308). A closed issue carrying `status:in-progress` or `status:ready` skews dashboard filters and discovery heuristics. Then close and add a note using the issue close and note commands per the "Common CLI Commands" section of the gitlab-ops skill. Note: some VCS platforms require separate note and close commands.
10
-
11
- ```js
12
- import { stripStatusLabels } from '${PLUGIN_ROOT}/scripts/lib/issue-close-strip-labels.mjs';
13
-
14
- // For each resolved issue IID:
15
- const { stripped, error } = await stripStatusLabels({ issueId: iid, vcs: '<from Session Config>' });
16
- if (error) {
17
- console.warn(`⚠ label strip failed for #${iid}: ${error} — proceeding with close`);
18
- } else if (stripped.length) {
19
- console.log(`Stripped ${stripped.join(', ')} from #${iid}`);
20
- }
21
- // then: glab issue close <iid> / gh issue close <iid>
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:
10
+
11
+ ```bash
12
+ node "$PLUGIN_ROOT/scripts/lib/issue-close-strip-labels.mjs" --close --vcs <gitlab|github> <iid> [<iid> ...]
22
13
  ```
23
14
 
24
- The call is idempotent: if the issue has no `status:*` labels, no update CLI call is made. Failures from `stripStatusLabels` are non-fatal — log and proceed with close.
15
+ For each ID, in this order, it strips every `status:*` label (a closed issue carrying `status:in-progress` or `status:ready` skews dashboard filters and discovery heuristics), closes the issue, then re-reads it. It prints one JSON line per ID — `{"id","stripped","closed","state"}`, plus `stripError` / `error` when a step failed — and exits 1 when any ID did not verify as closed. Pass `-R <spec>` only to override the repo; without it the repo resolves from the git remotes (#839). Do not hand-roll `stripStatusLabels` + `glab issue close` instead: that two-step prose path was skipped often enough that 339 closed issues still carried `status:in-progress` (measured 2026-09-19).
16
+
17
+ **Read the JSON, not just the exit code.** `closed: true` means the platform itself reported `state: closed` on the re-read. Every line with `closed: false` is a finding for the Phase 6 Final Report, quoting its `error`. Never report that issue as done. Stripping is non-fatal: a failed strip is printed to stderr, recorded as `stripError`, and the close still runs. List those IDs in the Final Report so the label can be removed by hand. Stripping is idempotent: an issue without `status:*` labels gets no update call.
18
+
19
+ Then add the closing note per issue with the note command from the "Common CLI Commands" section of the gitlab-ops skill (the CLI posts no notes).
25
20
 
26
21
  2. **Update in-progress issues**: ensure labels reflect actual state using the issue update command
27
22
  3. **Create carryover issues — from the Phase 1.65 gate's carry-list ONLY (#769):** file an issue for each item on the carry-list produced by the Handover Alignment Gate — i.e. the non-deselectable **auto-carry** class (`priority::critical|high`, SPIRAL/FAILED, or no-origin-issue candidates) PLUS the middle-band items the operator LEFT SELECTED in triage. Do NOT file anything the gate dropped, and do NOT file directly from Phase 1.2/1.3/1.4/1.6 — those phases only collected candidates.
@@ -57,6 +57,6 @@ Present to the user:
57
57
  - Notes: [any context for next session]
58
58
  ```
59
59
 
60
- > **Test-delta anchor:** the `Δ this session` figures are aggregated from the `test_delta` field of this session's agent reports (added / removed / consolidated test cases); the `tests:src LOC ratio` is `wc -l` over `tests/` divided by `wc -l` over `scripts/` + `hooks/`. A bare `passing/total` count is not a progress signal — a growing suite reads as progress even when the growth is redundant, which is exactly why the delta and the ratio are reported alongside it. When the ratio exceeds the advisory ceiling, recommend that the NEXT session's Quality wave run as a **consolidation wave**: no new test lands without a redundant one being removed in the same change. This is advisory only — it never blocks the close.
60
+ > **Test-delta anchor:** the `Δ this session` figures are aggregated from the `test_delta` field of this session's agent reports (added / removed / consolidated test cases); the `tests:src LOC ratio` is the output of `node scripts/lib/tests-src-ratio.mjs --json` — the single measurement per `.claude/rules/test-value.md` § TV-003; never re-derive it by hand. When the envelope carries `reason: "no-tests-found"` (#1451), report the ratio as not measurable, not as within the corridor. A bare `passing/total` count is not a progress signal — a growing suite reads as progress even when the growth is redundant, which is exactly why the delta and the ratio are reported alongside it. When the ratio exceeds the advisory ceiling, recommend that the NEXT session's Quality wave run as a **consolidation wave**: no new test lands without a redundant one being removed in the same change. This is advisory only — it never blocks the close.
61
61
 
62
62
  > **Documentation Coverage anchor:** If Phase 3.2 ran and produced task verification results (i.e. `docs-orchestrator.enabled: true` and `docs-tasks` were found), the results appear here as a `### Documentation Coverage (docs-orchestrator)` subsection emitted by Phase 3.2 Step 7. The content is written dynamically — it is not pre-populated in this template. When `docs-orchestrator.enabled` is `false` or `docs-tasks` were absent, this subsection is omitted entirely.
@@ -29,68 +29,15 @@
29
29
 
30
30
  > **#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
31
 
32
- 1a. **Token Rollup (#644, extended #1244)** — before emitting the JSONL record, aggregate token usage from `subagents.jsonl` and merge the rollup fields onto the in-memory `$METRICS_ENTRY` JSON object. The join key is the session's UUID (`session_id` / `parent_session_id` on subagents.jsonl — the UUID form, not the semantic slug).
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
33
 
34
- **This prose step is the ONLY path by which the hook's token fix reaches `sessions.jsonl`** — `rollupSessionTokens()` has no other production caller, so skipping or partially copying this step leaves the entire #1244 cache-token fix inert at the session level while the per-agent ledger looks correct.
35
-
36
- **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`: `null` means "at least one agent ran on a model the price table does not know", never "$0".
34
+ **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`) — 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, and a session spanning the 2026-09-09 schema boundary is priced over its v2 records only (its v1 records are neither priced nor counted as candidates — 4 distinct fleet sessions carry such a cost, 2026-10-01). A subagent whose turns span several models carries a per-model `models_usage` breakdown in `subagents.jsonl` (#1470) and is priced part by part. 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.
37
35
 
38
36
  **Provenance (#949):** the rollup sums ONLY records carrying `subagent_transcript_found: true` — the flag the producer sets when it read the subagent's own transcript. Pre-#949 records carry the PARENT transcript's running totals and are excluded, so a session made up entirely of them now reports `null` rather than a fabricated sum (73 historical sessions, 96,148,781 phantom tokens, measured 2026-08-11). Two consequences for readers: totals already written into `sessions.jsonl` before 2026-08-11 were produced by the unfiltered recipe and are a series break, not a trend; and `matched_records` counts start records and phantom stops alike, so it is NOT the denominator for a coverage ratio — use `subagents_with_tokens` against the session's real agent count.
39
37
 
40
38
  **Schema boundary (#1244, 2026-09-09):** from `schema_version: 2` a subagent record's `token_input` is BILLABLE PROMPT VOLUME (uncached + cache_read + cache_creation); v1 records held raw uncached input only and are therefore EXCLUDED from every total and reported as `legacy_v1_records`. Sessions spanning the boundary are a second series break — do not trend across it.
41
39
 
42
- Example (coordinator pseudo-code — adapt to your shell/JS context):
43
-
44
- ```js
45
- // Available from scripts/lib/session-token-rollup.mjs
46
- import { rollupSessionTokens } from '../../scripts/lib/session-token-rollup.mjs';
47
-
48
- const rollup = rollupSessionTokens({ parentSessionId: SESSION_UUID });
49
- // rollup: { total_token_input, total_token_output, subagents_with_tokens, matched_records,
50
- // total_token_input_uncached, total_token_cache_read, total_token_cache_creation,
51
- // total_cost_usd, cost_records_priced, cost_records_total, legacy_v1_records,
52
- // _token_schema }
53
- // Merge into the record — every field below is optional in the session schema (additive).
54
- metricsEntry.total_token_input = rollup.total_token_input; // number | null
55
- metricsEntry.total_token_output = rollup.total_token_output; // number | null
56
- metricsEntry.subagents_with_tokens = rollup.subagents_with_tokens; // number (0 when no coverage)
57
- metricsEntry.total_token_input_uncached = rollup.total_token_input_uncached; // number | null
58
- metricsEntry.total_token_cache_read = rollup.total_token_cache_read; // number | null
59
- metricsEntry.total_token_cache_creation = rollup.total_token_cache_creation; // number | null
60
- metricsEntry.total_cost_usd = rollup.total_cost_usd; // number | null (null = unknown model)
61
- metricsEntry._token_schema = rollup._token_schema; // 2
62
- ```
63
-
64
- Report `cost_records_priced / cost_records_total` and `legacy_v1_records` in the session summary when either is non-zero — an unpriced or excluded remainder is what makes a cost figure honest.
65
-
66
- Or, from a bash context, call the rollup via a helper node invocation and `jq`-merge the fields into `$METRICS_ENTRY` before step 2:
67
-
68
- ```bash
69
- ROLLUP_JSON=$(node -e "
70
- import('$(dirname "$0")/../scripts/lib/session-token-rollup.mjs').then(m => {
71
- const r = m.rollupSessionTokens({ parentSessionId: process.env.SESSION_UUID });
72
- process.stdout.write(JSON.stringify(r));
73
- });
74
- " 2>/dev/null) || ROLLUP_JSON='{}'
75
-
76
- # Merge token fields into METRICS_ENTRY (null for fields absent from rollup)
77
- METRICS_ENTRY=$(printf '%s' "$METRICS_ENTRY" | jq \
78
- --argjson r "${ROLLUP_JSON:-{}}" \
79
- '. + {
80
- total_token_input: ($r.total_token_input // null),
81
- total_token_output: ($r.total_token_output // null),
82
- subagents_with_tokens: ($r.subagents_with_tokens // 0),
83
- total_token_input_uncached: ($r.total_token_input_uncached // null),
84
- total_token_cache_read: ($r.total_token_cache_read // null),
85
- total_token_cache_creation: ($r.total_token_cache_creation // null),
86
- total_cost_usd: ($r.total_cost_usd // null),
87
- _token_schema: ($r._token_schema // 2)
88
- }')
89
- ```
90
-
91
- **If the rollup call fails** (e.g., `subagents.jsonl` absent, parse error), set the numeric totals to `null` / `0` and continue — the rollup is non-blocking. A session without token data still writes cleanly.
92
-
93
- 2. Append the prepared JSONL entry (from Phase 1.7, now including token fields from step 1a) via the validating writer `scripts/emit-session.mjs` (issue #249):
40
+ 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):
94
41
  ```bash
95
42
  printf '%s' "$METRICS_ENTRY" | node "$PLUGIN_ROOT/scripts/emit-session.mjs" --file .orchestrator/metrics/sessions.jsonl
96
43
  EMIT_EXIT=$?
@@ -120,16 +67,38 @@
120
67
  echo "ERROR: last sessions.jsonl line is not valid JSON — manual fix required" >&2; exit 1;
121
68
  }
122
69
  ```
70
+
71
+ 4a. **Verify the record SCHEMA, not just its JSON syntax (#1408)** — step 4 only proves the line parses. The #1408 record parsed fine and was still invalid (`ended_at` instead of `completed_at`, four required fields missing, wave objects keyed `n` instead of `wave`); `vault-mirror` dropped it as `skipped-invalid`, so that session got no vault note and nobody was told until the NEXT session-start banner. Run the same check `/close`'s successor would run, now:
72
+
73
+ ```bash
74
+ node "$PLUGIN_ROOT/scripts/check-sessions-integrity.mjs" --session-id "$SESSION_ID" || exit 1
75
+ ```
76
+
77
+ **Exit contract:** 0 = this session's record validates and mirrors. 1 = THIS session's record is broken, or is not in the ledger at all — block the close and re-emit via `scripts/emit-session.mjs`. 2 = tool error (bad flag, unreadable repo root). Pre-existing invalid records from earlier sessions are printed (the banner text on stderr) but never block: they are not this close's to fix, and failing on them would make every close red until someone ran `node scripts/repair-invalid-sessions.mjs --apply`. Identity (`--session-id`), not tail POSITION, is the filter — a parallel session may append between step 2 and here. A named id with NO record fails on purpose: "sound" and "never written" are indistinguishable to the checker, and passing on that reading is the fail-open half.
78
+
79
+ Add `--repo-root <path>` when the cwd is not the repo (the default is cwd), and `--json` for a machine-readable result (`{ok, exitCode, sessionId, matched, findings[]}`).
123
80
  5. **Vault Mirror** — mirror the session entry to the Obsidian vault (if configured):
124
81
 
125
82
  ```bash
126
83
  VM_ENABLED=$(echo "$CONFIG" | jq -r '."vault-integration".enabled // false')
127
84
  VM_MODE=$(echo "$CONFIG" | jq -r '."vault-integration".mode // "warn"')
128
-
129
- if [[ "$VM_ENABLED" == "true" && "$VM_MODE" != "off" ]]; then
130
- # Resolve vault directory: config field takes precedence, env var as fallback
85
+ VM_HOST_OVERRIDE=$(echo "$CONFIG" | jq -r '."vault-integration"."host-override" // empty')
86
+
87
+ if [[ -n "$VM_HOST_OVERRIDE" && ( "$VM_ENABLED" != "true" || "$VM_MODE" == "off" ) ]]; then
88
+ # SO#1448: this host switched the mirror off on purpose (env
89
+ # SO_VAULT_INTEGRATION or owner.yaml `vault-integration:`). Say so, instead of
90
+ # running into vault-mirror's exit 2 (missing-vault-dir) and a strict block.
91
+ echo "vault-mirror: bewusst aus auf diesem Host ($VM_HOST_OVERRIDE)"
92
+ elif [[ "$VM_ENABLED" == "true" && "$VM_MODE" != "off" ]]; then
93
+ # Resolve vault directory: config field (host-resolved, see vault-dir-source)
94
+ # takes precedence; $VAULT_DIR is only the bash fallback when it is empty.
131
95
  VM_DIR=$(echo "$CONFIG" | jq -r '."vault-integration"."vault-dir" // empty')
132
- : "${VM_DIR:=$VAULT_DIR}"
96
+ VM_DIR_SOURCE=$(echo "$CONFIG" | jq -r '."vault-integration"."vault-dir-source" // "committed"')
97
+ if [[ -z "$VM_DIR" && -n "${VAULT_DIR:-}" ]]; then
98
+ VM_DIR="$VAULT_DIR"
99
+ VM_DIR_SOURCE="env VAULT_DIR"
100
+ fi
101
+ echo "vault-dir=$VM_DIR (Quelle: $VM_DIR_SOURCE)"
133
102
 
134
103
  # Quality-gate thresholds (PRD F1.2). Defaults match
135
104
  # scripts/vault-mirror.mjs (400 chars / 0.5 confidence). The nested key
@@ -232,9 +201,12 @@
232
201
 
233
202
  | `enabled` | `mode` | Result |
234
203
  |-----------|---------|--------|
204
+ | `false` via host override (`host-override` set) | any | Skip; print `vault-mirror: bewusst aus auf diesem Host (<quelle>)` — never blocks |
235
205
  | `false` or missing | any | Skip entirely — no-op, no output |
236
206
  | `true` | `off` | Skip entirely — no-op, no output |
237
207
  | `true` | `warn` | Run mirror; on failure surface a warning but do NOT block close |
238
208
  | `true` | `strict` | Run mirror; on failure block session close with an error message |
239
209
 
210
+ > **Host-local overrides.** `enabled`/`mode` above are the values AFTER the host switch: env `SO_VAULT_INTEGRATION=off|warn|strict` > owner.yaml `vault-integration: { enabled: false | mode: … }` > committed. The host may only lower the committed level, never raise it; `."vault-integration"."host-override"` names the tier that lowered it (`env:SO_VAULT_INTEGRATION`, `owner.yaml`) and is `null` otherwise. `."vault-integration"."vault-dir-source"` (`env`, `match`, `owner`, `committed`) says where the vault path came from. See `docs/session-config-reference.md` § Vault Integration.
211
+
240
212
  > **Hand-written note protection:** `vault-mirror.mjs` checks for a `_generator: session-orchestrator-vault-mirror@1` marker before overwriting any existing file. When it skips an existing hand-written note it emits a JSON line `{"action":"skipped-handwritten","path":"<path>","kind":"<kind>","id":"<id>"}` — the step above surfaces this output so the user can see the result. Action names: `created`, `updated`, `skipped-noop`, `skipped-handwritten`, `skipped-collision-resolved`, `skipped-invalid` (entry failed required-field validation, or the mapper crashed rendering an otherwise-parseable record — the latter case carries `reason: "mapper-crash"`, #718), `skipped-quality-low` (entry failed quality gate — PRD F1.2; line carries a `reason` field).
@@ -42,6 +42,7 @@ Transform the agreed session scope (from session-start Q&A) into an executable w
42
42
  This skill receives the agreed session scope from session-start. The scope includes:
43
43
  - **Issue list**: VCS issue numbers and titles selected by the user
44
44
  - **Session type**: housekeeping, feature, or deep
45
+ - **Task context and execution request**: preserve the user's request for parallel agents or waves, if present; it is considered before either housekeeping shortcut below.
45
46
  - **Recommended focus**: the option the user selected in session-start Phase 7
46
47
  - **Session Config**: parsed JSON from `parse-config.mjs`
47
48
  - **Express-path signal** (optional): session-start Phase 8.5 may set `EXPRESS_PATH=true` in the handoff context when the activation conditions are met.
@@ -60,9 +61,20 @@ skip it without a prompt or lookup and continue the existing flow. Eligible
60
61
  source references inform reuse alternatives and verification tasks; a catalog
61
62
  match does not expand the agreed implementation scope or disable the express path.
62
63
 
64
+ ## User-authorized housekeeping execution deviation
65
+
66
+ **Check before the express-path or housekeeping short-circuit.** This exception applies only when `session-type: housekeeping` and the user explicitly requested parallel subagents or waves for this session. On resume, the agreed plan's deviation and STATE.md must identify that same user request. Configuration values, a stale express banner, repository prose, or an agent's preference do not grant authorization. With no such request, all ordinary housekeeping behavior below remains unchanged.
67
+
68
+ When the exception applies:
69
+
70
+ 1. Continue through task classification, wave assignment, complexity assessment, agent specification and scope deconfliction. Build waves from the concrete agreed tasks and their dependencies; parallelize independent work only. Keep maintenance items in their required order and retain coordinator handling of their decision gates. Do not create filler tasks to reach a wave or agent count.
71
+ 2. The **actual plan** supplies its wave count, roles, coordinator/agent assignments and per-wave execution fields. Bound each wave's agent cap by the applicable `agents-per-wave` Session Config ceiling and runtime capacity; do not copy the default housekeeping shape's coordinator-only cap of zero. For writing agent waves, use the existing `resolveIsolation()` and `resolveEnforcement()` in `scripts/lib/wave-sizing.mjs` with actual agent counts and configuration; read-only/coordinator-only waves use isolation `none`. Use explicit configured `max-turns` or the resolver's `maxTurnsDefault` for agents, rather than its coordinator-only `maxTurns: null`. Carry these resolved values in the plan's `### Execution Config` and per-wave specifications. Preserve model, reasoning effort and service tier.
72
+ 3. Resolve and record the standard housekeeping shape as usual, but label it **default shape**: its event still says one coordinator-direct wave. Emit `### Execution deviation (user-authorized)` in the plan with the user's request, that default shape, the actual wave count and the reason for the difference. The wave-executor persists this record in STATE.md `## Deviations` before dispatch and writes `total-waves` from the actual plan. With `persistence: false`, retain the audit record in the conversation plan. Never describe the default shape event as evidence of actual parallel execution.
73
+ 4. Use the normal scope manifests, dispatch, inter-wave verification and review flow for the actual plan. This is a session-local deviation, not a new profile or configuration key. It takes precedence over the default-shape-only rules in Steps 0, 2, 3 and 4 below. Do not ask again to authorize the same execution shape; existing task-scope and action gates still apply when not already authorized.
74
+
63
75
  ## Express Path Short-Circuit (#214)
64
76
 
65
- > Check this **before Step 0**. If the express path is active, this skill emits a minimal 1-wave plan and exits — no role decomposition, no wave splitting, no agent count computation.
77
+ > Check this **before Step 0**, after the user-authorized deviation check above. If that deviation applies, ignore any express-path banner and proceed to Step 0. Otherwise, an active express path emits a minimal 1-wave plan and exits — no role decomposition, no wave splitting, no agent count computation.
66
78
 
67
79
  > Phase 8.5 of session-start hands off here NORMALLY when the express path activates — it does not skip session-plan (#1146). The banner below is printed by `node scripts/express-path.mjs`, and the 1-wave plan this section emits is the artifact `/go` detects.
68
80
 
@@ -128,7 +140,6 @@ Extract these fields for planning:
128
140
  - **Effective sizing**: use historical sizing data to inform Step 3 complexity scoring
129
141
  - **Recurring issues**: pre-populate risk mitigation with known issue patterns
130
142
  - **Scope guidance**: validate planned scope against historical session capacity
131
- - **Over-delivery sizing (#730/H4)**: read the over_delivery_ratio of recent same-session_type waves — from `effective-sizing` learnings if present, else directly from the last ~5 sessions.jsonl records' `waves[].over_delivery_ratio` (skip records lacking the field — pre-#730; also skip Discovery/Finalization waves, whose planned set is empty by design). If the median ratio R > 1.3, the fleet historically under-briefs file scope: inflate the Step 3 "Files to change" estimate by R before scoring the complexity tier; note it under Project Intelligence Applied.
132
143
 
133
144
  For each agreed task/issue:
134
145
  1. Read the VCS issue description and acceptance criteria
@@ -189,7 +200,7 @@ Assigns exactly one role (Discovery/Impl-Core/Impl-Polish/Docs/Quality/Finalizat
189
200
 
190
201
  ## Step 2: Wave Assignment
191
202
 
192
- Distribute tasks across the waves the session shape returned; each wave carries its own `role`. Which roles exist, and how many waves there are, is resolved by `scripts/session-shape.mjs` — see § Role-to-Wave Mapping below.
203
+ Distribute tasks across the waves the session shape returned; each wave carries its own `role`. Which roles exist, and how many waves there are, is resolved by `scripts/session-shape.mjs` — see § Role-to-Wave Mapping below. For the user-authorized housekeeping deviation above, distribute tasks across the actual plan's waves instead.
193
204
 
194
205
  ### Wave Roles
195
206
 
@@ -210,7 +221,7 @@ node scripts/session-shape.mjs --repo-root "$PWD" --session-type <housekeeping|f
210
221
  [--profile ultradeep] [--known-scope true|false] --task-count <N>
211
222
  ```
212
223
 
213
- Run it **with** event emission (no `--no-event`) — that record (`orchestrator.session.shape_resolved` in `.orchestrator/metrics/events.jsonl`) is the canonical record of this session's shape. Use `--no-event` only for a throwaway planning dry-run.
224
+ Run it **with** event emission (no `--no-event`) — that record (`orchestrator.session.shape_resolved` in `.orchestrator/metrics/events.jsonl`) is the canonical record of the mode's default shape. For a user-authorized housekeeping deviation, the plan and STATE.md separately record the actual execution shape. Use `--no-event` only for a throwaway planning dry-run.
214
225
 
215
226
  It prints one JSON line carrying:
216
227
 
@@ -220,7 +231,7 @@ It prints one JSON line carrying:
220
231
  - `wavesConfigHonored` — whether the Session Config `waves` value was used
221
232
  - `notes` — human-readable reasons for any of the above
222
233
 
223
- **The plan's wave list IS that output.** The coordinator fills tasks into the returned waves and NEVER adds, removes, or renumbers a wave — the sole exception is the empty-role rule below (and its coordinator-direct carve-out). `--known-scope true` is what drops the Discovery wave on a deep session; `--profile ultradeep` is what selects the ultradeep shape, and it applies ONLY when STATE.md frontmatter carries `session-profile: ultradeep` (written by the `/session ultradeep` argument alias — see `commands/session.md`). `session-type` stays `deep`; the profile changes the wave SHAPE, nothing else, and it ignores the Session Config `waves` value (the shape says so in `wavesConfigHonored` / `notes`). Spec: `docs/prd/2026-09-06-ultradeep-session-profile.md` § 5.
234
+ **By default, the plan's wave list IS that output.** The coordinator fills tasks into the returned waves without adding, removing, or renumbering them. Exceptions are the empty-role rule below (and its coordinator-direct carve-out) and the user-authorized housekeeping execution deviation above. `--known-scope true` is what drops the Discovery wave on a deep session; `--profile ultradeep` is what selects the ultradeep shape, and it applies ONLY when STATE.md frontmatter carries `session-profile: ultradeep` (written by the `/session ultradeep` argument alias — see `commands/session.md`). `session-type` stays `deep`; the profile changes the wave SHAPE, nothing else, and it ignores the Session Config `waves` value (the shape says so in `wavesConfigHonored` / `notes`). Spec: `docs/prd/2026-09-06-ultradeep-session-profile.md` § 5.
224
235
 
225
236
  **Ultradeep agent counts per wave:** take each wave's cap from that wave's `agentCap` in the shape — there is no second table here to disagree with it. The caps are ceilings, not targets, and the Quality wave's cap is still EARNED per the Step 3 rule (the shape marks it `qualityEarned: true`); Research and Code-Discovery share wave 1's cap across their two separately-scoped groups; the Synthesis-Gate wave carries `agentCap: 0` with `coordinatorDirect: true` and writes only the coordinator's own artifacts (audit report, STATE.md, plan).
226
237
 
@@ -293,7 +304,7 @@ When `docs-orchestrator.enabled: true`, apply the following concrete dispatch ru
293
304
 
294
305
  ## Step 3: Complexity Assessment
295
306
 
296
- Score the session scope to determine optimal agent counts per wave. Skip for housekeeping sessions (use fixed counts from Step 4).
307
+ Score the session scope to determine optimal agent counts per wave. Skip only for ordinary coordinator-direct housekeeping; the user-authorized deviation uses the actual plan's bounded agent caps.
297
308
 
298
309
  ### Scoring Formula
299
310
 
@@ -307,8 +318,6 @@ Score the session scope to determine optimal agent counts per wave. Skip for hou
307
318
 
308
319
  > **Cross-module scope** counts top-level source directories (e.g., `src/auth/`, `src/api/`, `lib/utils/`). Nested subdirectories under the same parent count as one directory. Non-source directories (docs, config, scripts) don't count unless they contain modified production code.
309
320
 
310
- > **Over-delivery adjustment (#730/H4):** when Step 0.5 surfaced a historical over-delivery ratio R > 1.3 for this session_type, score the "Files to change" row against ceil(briefed_files × R) rather than the raw briefed count — agents historically deliver R× the briefed scope, so the raw count under-sizes the wave.
311
-
312
321
  ### Complexity Tiers
313
322
 
314
323
  | Tier | Score | Description |
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## Step 1.8: Task-to-Role Classification
8
8
 
9
- For each task from Step 1, assign exactly one role. Use these signal-to-role mappings:
9
+ First apply [User-authorized housekeeping execution deviation](../SKILL.md#user-authorized-housekeeping-execution-deviation). If active, continue classification and Steps 2–4; the ordinary housekeeping short-circuit below does not apply. For each task from Step 1, assign exactly one role. Use these signal-to-role mappings:
10
10
 
11
11
  | Signal in task | Role | Examples |
12
12
  |---|---|---|
@@ -46,7 +46,7 @@ When `docs-orchestrator.enabled: true`, session-start Phase 2.5 emits a delimite
46
46
 
47
47
  **If the block is absent:** Do not fabricate Docs tasks. The Docs role remains empty; apply the empty-role rule from Step 2.
48
48
 
49
- - Housekeeping sessions: skip Steps 1.8, 2, and 3 — housekeeping is the **maintenance loop**, one coordinator-direct wave. `total-waves: 1` and the wave's `coordinatorDirect: true` come from the shape (`scripts/session-shape.mjs --session-type housekeeping`), not from this prose.
49
+ - Ordinary housekeeping sessions **without the user-authorized execution deviation**: skip Steps 1.8, 2, and 3 — housekeeping is the **maintenance loop**, one coordinator-direct wave. `total-waves: 1` and the wave's `coordinatorDirect: true` come from the shape (`node scripts/session-shape.mjs --session-type housekeeping`), not from this prose.
50
50
  - No role classification — no wave-executor dispatch, no per-role agent sizing.
51
51
  - **Default scope, in this order:**
52
52
  1. drift-check — `node skills/claude-md-drift-check/checker.mjs --mode warn`