session-orchestrator 3.23.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (393) hide show
  1. package/.agents/skills/architecture/SKILL.md +18 -0
  2. package/.agents/skills/autopilot/SKILL.md +17 -0
  3. package/.agents/skills/bootstrap/SKILL.md +20 -0
  4. package/.agents/skills/brainstorm/SKILL.md +22 -0
  5. package/.agents/skills/claude-md-drift-check/SKILL.md +15 -0
  6. package/.agents/skills/convergence-monitoring/SKILL.md +22 -0
  7. package/.agents/skills/debug/SKILL.md +22 -0
  8. package/.agents/skills/discovery/SKILL.md +20 -0
  9. package/.agents/skills/dispatcher/SKILL.md +15 -0
  10. package/.agents/skills/docs-orchestrator/SKILL.md +18 -0
  11. package/.agents/skills/ecosystem-health/SKILL.md +20 -0
  12. package/.agents/skills/eli5/SKILL.md +20 -0
  13. package/.agents/skills/eval/SKILL.md +21 -0
  14. package/.agents/skills/evolve/SKILL.md +21 -0
  15. package/.agents/skills/frontmatter-guard/SKILL.md +15 -0
  16. package/.agents/skills/gitlab-ops/SKILL.md +20 -0
  17. package/.agents/skills/gitlab-portfolio/SKILL.md +15 -0
  18. package/.agents/skills/grill/SKILL.md +22 -0
  19. package/.agents/skills/hook-development/SKILL.md +15 -0
  20. package/.agents/skills/mcp-builder/SKILL.md +15 -0
  21. package/.agents/skills/memory-cleanup/SKILL.md +21 -0
  22. package/.agents/skills/mode-selector/SKILL.md +17 -0
  23. package/.agents/skills/npm-publish/SKILL.md +16 -0
  24. package/.agents/skills/peekaboo-driver/SKILL.md +18 -0
  25. package/.agents/skills/persona-panel/SKILL.md +17 -0
  26. package/.agents/skills/plan/SKILL.md +20 -0
  27. package/.agents/skills/playwright-driver/SKILL.md +20 -0
  28. package/.agents/skills/quality-gates/SKILL.md +20 -0
  29. package/.agents/skills/reconcile/SKILL.md +21 -0
  30. package/.agents/skills/remote-offload/SKILL.md +20 -0
  31. package/.agents/skills/repo-audit/SKILL.md +16 -0
  32. package/.agents/skills/session-end/SKILL.md +20 -0
  33. package/.agents/skills/session-plan/SKILL.md +20 -0
  34. package/.agents/skills/session-start/SKILL.md +20 -0
  35. package/.agents/skills/spinout/SKILL.md +16 -0
  36. package/.agents/skills/sunset-review/SKILL.md +16 -0
  37. package/.agents/skills/test-runner/SKILL.md +20 -0
  38. package/.agents/skills/tmux-layout/SKILL.md +21 -0
  39. package/.agents/skills/using-orchestrator/SKILL.md +17 -0
  40. package/.agents/skills/vault-mirror/SKILL.md +15 -0
  41. package/.agents/skills/vault-sync/SKILL.md +15 -0
  42. package/.agents/skills/wave-executor/SKILL.md +20 -0
  43. package/.agents/skills/write-executable-plan/SKILL.md +22 -0
  44. package/.claude-plugin/marketplace.json +1 -1
  45. package/.claude-plugin/plugin.json +1 -1
  46. package/.codex-plugin/plugin.json +1 -1
  47. package/.cursor/commands/autopilot.md +2 -2
  48. package/.cursor/commands/bootstrap.md +1 -1
  49. package/.cursor/commands/brainstorm.md +1 -1
  50. package/.cursor/commands/debug.md +1 -1
  51. package/.cursor/commands/discovery.md +1 -1
  52. package/.cursor/commands/dispatcher.md +2 -2
  53. package/.cursor/commands/eli5.md +2 -2
  54. package/.cursor/commands/eval.md +2 -2
  55. package/.cursor/commands/evolve.md +1 -1
  56. package/.cursor/commands/go.md +1 -1
  57. package/.cursor/commands/grill.md +2 -2
  58. package/.cursor/commands/memory-cleanup.md +2 -2
  59. package/.cursor/commands/persona-panel.md +1 -1
  60. package/.cursor/commands/plan.md +1 -1
  61. package/.cursor/commands/portfolio.md +1 -1
  62. package/.cursor/commands/reconcile.md +2 -2
  63. package/.cursor/commands/release.md +2 -2
  64. package/.cursor/commands/session.md +2 -2
  65. package/.cursor/commands/spinout.md +2 -2
  66. package/.cursor/commands/sunset-review.md +2 -2
  67. package/.cursor/commands/templates-ack.md +2 -2
  68. package/.cursor/commands/test.md +2 -2
  69. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  70. package/.cursor/skills/eval/SKILL.md +1 -1
  71. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  72. package/.cursor/skills/remote-offload/SKILL.md +13 -0
  73. package/.orchestrator/policy/blocked-commands.json +121 -0
  74. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  75. package/.orchestrator/policy/quality-gates.example.json +16 -0
  76. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  77. package/.orchestrator/policy/templates-policy.json +27 -0
  78. package/.orchestrator/policy/test-profiles.json +47 -0
  79. package/AGENTS.md +225 -0
  80. package/CHANGELOG.md +1401 -0
  81. package/NOTICE +11 -6
  82. package/README.md +127 -92
  83. package/agents/db-specialist.md +0 -1
  84. package/agents/eval-judge.md +1 -1
  85. package/agents/skill-applied-judge.md +1 -1
  86. package/assets/wave-lifecycle.svg +98 -0
  87. package/commands/release.md +6 -3
  88. package/commands/session.md +18 -3
  89. package/docs/README.md +4 -0
  90. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  91. package/docs/baseline.md +67 -0
  92. package/docs/ci-setup.md +249 -48
  93. package/docs/codex-setup.md +66 -22
  94. package/docs/components.md +37 -16
  95. package/docs/cursor-setup.md +6 -2
  96. package/docs/events-schema.md +51 -10
  97. package/docs/instruction-delivery.md +62 -0
  98. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  99. package/docs/migration-v4.md +341 -0
  100. package/docs/pi-setup.md +6 -1
  101. package/docs/plugin-architecture-v3.md +1 -1
  102. package/docs/rule-authoring.md +85 -19
  103. package/docs/scope-collision-guard.md +8 -8
  104. package/docs/session-config-reference.md +120 -61
  105. package/docs/session-config-template.md +40 -33
  106. package/docs/telemetry/telemetry-claims.md +11 -10
  107. package/docs/telemetry.md +187 -4
  108. package/docs/vault-docs-architecture.md +50 -11
  109. package/hooks/_lib/atomic-json.mjs +111 -0
  110. package/hooks/_lib/hook-import-set.json +1487 -0
  111. package/hooks/_lib/subagent-paths.mjs +143 -0
  112. package/hooks/_lib/subagent-transcript.mjs +562 -0
  113. package/hooks/config-protection.mjs +2 -2
  114. package/hooks/cwd-change-restore.mjs +11 -31
  115. package/hooks/enforce-commands.mjs +69 -0
  116. package/hooks/enforce-scope.mjs +35 -6
  117. package/hooks/hooks-codex.json +1 -1
  118. package/hooks/hooks-cursor.json +10 -0
  119. package/hooks/hooks-pi.json +5 -0
  120. package/hooks/hooks.json +6 -1
  121. package/hooks/loop-guard.mjs +3 -3
  122. package/hooks/on-session-end.mjs +280 -14
  123. package/hooks/on-session-start.mjs +153 -4
  124. package/hooks/on-stop.mjs +371 -17
  125. package/hooks/operator-steer.mjs +2 -2
  126. package/hooks/post-bash-write-verify.mjs +189 -4
  127. package/hooks/post-edit-import-probe.mjs +344 -0
  128. package/hooks/post-subagent-discovery-validator.mjs +278 -392
  129. package/hooks/post-tool-batch-wave-signal.mjs +272 -44
  130. package/hooks/post-tool-failure-corrective-context.mjs +11 -34
  131. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  132. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  133. package/hooks/pre-bash-memory-propose-audit.mjs +13 -7
  134. package/hooks/skill-invocation-telemetry.mjs +17 -5
  135. package/hooks/subagent-telemetry.mjs +24 -30
  136. package/monitors/monitors.json +3 -3
  137. package/package.json +9 -1
  138. package/pi/prompts/session.md +2 -2
  139. package/plugin.json +27 -0
  140. package/scripts/autopilot.mjs +26 -12
  141. package/scripts/backfill-abandoned-sessions.mjs +130 -15
  142. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  143. package/scripts/dialectic-deriver.mjs +73 -8
  144. package/scripts/emit-event.mjs +10 -2
  145. package/scripts/export-hw-learnings.mjs +113 -1
  146. package/scripts/generate-agents-skills.mjs +378 -0
  147. package/scripts/generate-cursor-adapter.mjs +45 -8
  148. package/scripts/generate-hook-import-set.mjs +249 -0
  149. package/scripts/lib/agent-status.mjs +13 -2
  150. package/scripts/lib/auq/parse.mjs +5 -29
  151. package/scripts/lib/auto-dialectic.mjs +68 -0
  152. package/scripts/lib/auto-dream.mjs +38 -36
  153. package/scripts/lib/autonomy/suitability.mjs +6 -0
  154. package/scripts/lib/autopilot/loop.mjs +2 -2
  155. package/scripts/lib/autopilot/worktree-pipeline.mjs +82 -6
  156. package/scripts/lib/build-live-signals.mjs +25 -22
  157. package/scripts/lib/ci-status-banner.mjs +220 -75
  158. package/scripts/lib/codex/plugin-contract.mjs +82 -6
  159. package/scripts/lib/cold-start-detector.mjs +23 -14
  160. package/scripts/lib/config/auto-dream.mjs +2 -1
  161. package/scripts/lib/config/block-header.mjs +63 -0
  162. package/scripts/lib/config/block-preprocess.mjs +177 -0
  163. package/scripts/lib/config/broken-window.mjs +2 -1
  164. package/scripts/lib/config/cold-start.mjs +2 -1
  165. package/scripts/lib/config/config-protection.mjs +22 -2
  166. package/scripts/lib/config/context-coverage.mjs +2 -1
  167. package/scripts/lib/config/cross-repo.mjs +2 -1
  168. package/scripts/lib/config/custom-phases.mjs +2 -1
  169. package/scripts/lib/config/dialectic.mjs +2 -1
  170. package/scripts/lib/config/discovery-validator.mjs +9 -3
  171. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  172. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  173. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  174. package/scripts/lib/config/docs-staleness.mjs +2 -1
  175. package/scripts/lib/config/drift-check.mjs +2 -1
  176. package/scripts/lib/config/eval.mjs +2 -1
  177. package/scripts/lib/config/events-rotation.mjs +2 -1
  178. package/scripts/lib/config/evolve.mjs +8 -2
  179. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  180. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  181. package/scripts/lib/config/handover-gate.mjs +2 -1
  182. package/scripts/lib/config/health-endpoints.mjs +388 -0
  183. package/scripts/lib/config/issue-budget.mjs +2 -1
  184. package/scripts/lib/config/loop-guard.mjs +2 -1
  185. package/scripts/lib/config/memory.mjs +2 -1
  186. package/scripts/lib/config/moc-staleness.mjs +2 -1
  187. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  188. package/scripts/lib/config/private-config-dir.mjs +67 -0
  189. package/scripts/lib/config/reconcile.mjs +2 -1
  190. package/scripts/lib/config/remote-hosts.mjs +234 -0
  191. package/scripts/lib/config/section-extractor.mjs +7 -1
  192. package/scripts/lib/config/skill-evolution.mjs +2 -1
  193. package/scripts/lib/config/slopcheck.mjs +2 -1
  194. package/scripts/lib/config/state-md-lock.mjs +2 -1
  195. package/scripts/lib/config/templates-first.mjs +2 -1
  196. package/scripts/lib/config/test.mjs +2 -1
  197. package/scripts/lib/config/vault-integration.mjs +7 -1
  198. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  199. package/scripts/lib/config/vault-staleness.mjs +2 -1
  200. package/scripts/lib/config/vault-sync.mjs +2 -1
  201. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  202. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  203. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  204. package/scripts/lib/config.mjs +31 -3
  205. package/scripts/lib/convergence-monitor.mjs +82 -16
  206. package/scripts/lib/dispatcher/enumerate.mjs +2 -17
  207. package/scripts/lib/dispatcher/rank.mjs +124 -48
  208. package/scripts/lib/ecosystem-health.mjs +16 -2
  209. package/scripts/lib/eval/engine.mjs +9 -1
  210. package/scripts/lib/eval/session-resolve.mjs +23 -4
  211. package/scripts/lib/events-schema.mjs +48 -0
  212. package/scripts/lib/events.mjs +256 -7
  213. package/scripts/lib/evolve/autonomy-verdict.mjs +9 -4
  214. package/scripts/lib/evolve/autopilot-effectiveness.mjs +18 -1
  215. package/scripts/lib/frontmatter-guard.mjs +131 -13
  216. package/scripts/lib/gates/gate-full.mjs +26 -0
  217. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  218. package/scripts/lib/gitlab-portfolio/cli.mjs +3 -15
  219. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  220. package/scripts/lib/harness-audit/categories/category1.mjs +17 -6
  221. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  222. package/scripts/lib/host-identity.mjs +50 -11
  223. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  224. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  225. package/scripts/lib/learnings/io.mjs +60 -6
  226. package/scripts/lib/memory-banner.mjs +20 -8
  227. package/scripts/lib/memory-proposals/store.mjs +30 -22
  228. package/scripts/lib/owner-config-banner.mjs +43 -6
  229. package/scripts/lib/owner-config-loader.mjs +21 -10
  230. package/scripts/lib/owner-interview.mjs +3 -3
  231. package/scripts/lib/owner-yaml.mjs +207 -14
  232. package/scripts/lib/peer-discovery.mjs +20 -2
  233. package/scripts/lib/platform.mjs +108 -15
  234. package/scripts/lib/plugin-update-banner.mjs +406 -0
  235. package/scripts/lib/project-hygiene.mjs +38 -2
  236. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  237. package/scripts/lib/quality-gate.mjs +133 -44
  238. package/scripts/lib/reconcile/emitter.mjs +68 -6
  239. package/scripts/lib/reconcile/engine.mjs +249 -9
  240. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  241. package/scripts/lib/reconcile/writer.mjs +40 -18
  242. package/scripts/lib/scope-gate.mjs +36 -0
  243. package/scripts/lib/session-close-backfill.mjs +125 -18
  244. package/scripts/lib/session-discovery.mjs +57 -3
  245. package/scripts/lib/session-end/phase-skip.mjs +2 -2
  246. package/scripts/lib/session-id.mjs +12 -23
  247. package/scripts/lib/session-identity/own-session.mjs +187 -11
  248. package/scripts/lib/session-lock-shape.mjs +43 -0
  249. package/scripts/lib/session-lock.mjs +5 -10
  250. package/scripts/lib/session-registry.mjs +25 -9
  251. package/scripts/lib/session-schema/constants.mjs +36 -2
  252. package/scripts/lib/session-schema/validator.mjs +38 -4
  253. package/scripts/lib/session-start-probes.mjs +18 -1
  254. package/scripts/lib/session-transition.mjs +1 -1
  255. package/scripts/lib/sessions-canonical.mjs +446 -0
  256. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  257. package/scripts/lib/skill-health/join.mjs +17 -4
  258. package/scripts/lib/state-md.mjs +78 -0
  259. package/scripts/lib/sunset/walker.mjs +6 -0
  260. package/scripts/lib/telemetry/schema.mjs +255 -17
  261. package/scripts/lib/telemetry/sync.mjs +417 -24
  262. package/scripts/lib/tmux-layout/telemetry.mjs +14 -2
  263. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  264. package/scripts/lib/validate/check-agents.mjs +3 -3
  265. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  266. package/scripts/lib/validate/check-doc-cli-commands.mjs +9 -33
  267. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +370 -0
  268. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  269. package/scripts/lib/validate/check-owner-leakage.mjs +281 -20
  270. package/scripts/lib/validate/check-skill-links.mjs +163 -0
  271. package/scripts/lib/validate/check-skill-script-paths.mjs +455 -0
  272. package/scripts/lib/validate/check-untracked-test-deps.mjs +10 -0
  273. package/scripts/lib/validate/check-unwired-features.mjs +0 -9
  274. package/scripts/lib/validate/check-validator-registration.mjs +254 -0
  275. package/scripts/lib/validate/check-vcs-repo-flag.mjs +6 -28
  276. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  277. package/scripts/lib/validate/markdown-fences.mjs +196 -0
  278. package/scripts/lib/vault-backfill/template.mjs +63 -6
  279. package/scripts/lib/vault-mirror/process.mjs +165 -42
  280. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  281. package/scripts/lib/vault-status/board-lock.mjs +185 -0
  282. package/scripts/lib/vault-status/board-writer.mjs +174 -135
  283. package/scripts/lib/vault-status/narrative-mirror.mjs +129 -37
  284. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  285. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  286. package/scripts/lib/wave-executor/remote-dispatch.mjs +502 -0
  287. package/scripts/lib/wave-resource-gate.mjs +133 -7
  288. package/scripts/lib/wave-sizing.mjs +4 -1
  289. package/scripts/lib/wave-transcript-tail.mjs +142 -8
  290. package/scripts/materialize-wave-scope.mjs +32 -9
  291. package/scripts/memory-propose.mjs +146 -8
  292. package/scripts/migrate-cold-start-seed.mjs +4 -1
  293. package/scripts/parse-config.mjs +60 -3
  294. package/scripts/promote-vault-strict.mjs +4 -15
  295. package/scripts/release.mjs +337 -29
  296. package/scripts/repair-invalid-sessions.mjs +3 -3
  297. package/scripts/run-quality-gate.mjs +128 -11
  298. package/scripts/site-numbers.mjs +36 -4
  299. package/scripts/sweep-expired-learnings.mjs +90 -0
  300. package/scripts/sync-vault-schema.mjs +3 -1
  301. package/scripts/telemetry.mjs +2 -2
  302. package/scripts/validate-plugin.mjs +187 -0
  303. package/scripts/validate-wave-scope.mjs +28 -8
  304. package/scripts/vault-consolidate.mjs +3 -11
  305. package/scripts/vault-integration-watcher.mjs +2 -4
  306. package/scripts/vault-mirror.mjs +111 -26
  307. package/scripts/wave-scope-binding.mjs +215 -0
  308. package/skills/_shared/instruction-file-resolution.md +10 -0
  309. package/skills/_shared/parallel-aware-auq.md +31 -2
  310. package/skills/_shared/parallel-aware-preamble.md +18 -4
  311. package/skills/_shared/platform-tools.md +1 -1
  312. package/skills/_shared/state-ownership.md +1 -1
  313. package/skills/architecture/SKILL.md +7 -5
  314. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  315. package/skills/autopilot/SKILL.md +4 -18
  316. package/skills/claude-md-drift-check/SKILL.md +5 -1
  317. package/skills/claude-md-drift-check/checker.mjs +62 -2
  318. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  319. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  320. package/skills/discovery/probes-arch.md +20 -18
  321. package/skills/dispatcher/SKILL.md +3 -2
  322. package/skills/ecosystem-health/SKILL.md +4 -1
  323. package/skills/ecosystem-health/wizard.md +5 -0
  324. package/skills/evolve/SKILL.md +87 -11
  325. package/skills/frontmatter-guard/SKILL.md +11 -5
  326. package/skills/npm-publish/SKILL.md +1 -1
  327. package/skills/reconcile/SKILL.md +38 -2
  328. package/skills/remote-offload/SKILL.md +89 -0
  329. package/skills/session-end/SKILL.md +18 -905
  330. package/skills/session-end/phase-3-6-tail.md +19 -9
  331. package/skills/session-end/plan-verification.md +221 -155
  332. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  333. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  334. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  335. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  336. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  337. package/skills/session-end/references/session-summary-template.md +62 -0
  338. package/skills/session-plan/SKILL.md +49 -0
  339. package/skills/session-start/SKILL.md +41 -900
  340. package/skills/session-start/phase-8-5-express-path.md +1 -1
  341. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  342. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  343. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  344. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  345. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  346. package/skills/session-start/references/phase-4-ssot-environment-check.md +155 -0
  347. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  348. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  349. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  350. package/skills/vault-sync/validator.mjs +21 -27
  351. package/skills/wave-executor/SKILL.md +16 -2
  352. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  353. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  354. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  355. package/skills/wave-executor/wave-loop.md +14 -1271
  356. package/templates/_shared/journey-manifest.md +10 -6
  357. package/.cursor/commands/autopilot-multi.md +0 -14
  358. package/.cursor/commands/contract-version-bump.md +0 -14
  359. package/.cursor/commands/journey-audit.md +0 -14
  360. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  361. package/.cursor/skills/daily/SKILL.md +0 -12
  362. package/.cursor/skills/domain-model/SKILL.md +0 -13
  363. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  364. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  365. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  366. package/commands/autopilot-multi.md +0 -74
  367. package/commands/contract-version-bump.md +0 -28
  368. package/commands/journey-audit.md +0 -43
  369. package/pi/prompts/autopilot-multi.md +0 -12
  370. package/pi/prompts/contract-version-bump.md +0 -12
  371. package/pi/prompts/journey-audit.md +0 -12
  372. package/scripts/autopilot-multi.mjs +0 -885
  373. package/scripts/backfill-learnings-expires.mjs +0 -196
  374. package/scripts/backfill-learnings.mjs +0 -203
  375. package/scripts/fleet-instruction-scan.mjs +0 -141
  376. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  377. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  378. package/scripts/lib/webhook-url.mjs +0 -105
  379. package/scripts/lifecycle-sim-v6.mjs +0 -347
  380. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  381. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  382. package/scripts/upload-social-preview.mjs +0 -316
  383. package/skills/_shared/model-selection.md +0 -64
  384. package/skills/contract-version-bump/SKILL.md +0 -219
  385. package/skills/daily/SKILL.md +0 -222
  386. package/skills/daily/generate.sh +0 -92
  387. package/skills/daily/templates/daily.md.tpl +0 -36
  388. package/skills/journey-audit/SKILL.md +0 -269
  389. package/skills/skill-creator/SKILL.md +0 -168
  390. package/skills/ubiquitous-language/SKILL.md +0 -97
  391. package/skills/vault-sync/package-lock.json +0 -40
  392. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  393. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -26,7 +26,49 @@
26
26
  * session id.
27
27
  */
28
28
 
29
- import { readLock } from '../session-lock.mjs';
29
+ import { readFileSync } from 'node:fs';
30
+ import path from 'node:path';
31
+ import { isLockShape } from '../session-lock-shape.mjs';
32
+
33
+ /**
34
+ * Read the two session ids out of `<repoRoot>/.orchestrator/session.lock`.
35
+ *
36
+ * A deliberate, behaviour-identical stand-in for `readLock()` from
37
+ * `../session-lock.mjs` (#1153 P7): that import drags a static closure of six
38
+ * modules (session-lock → exclusivity-matrix, file-lock, io, host-identity,
39
+ * crypto-digest-utils — measured 2026-09-04 by following its `^import` lines)
40
+ * into every consumer of this module, of which the live
41
+ * `hooks/enforce-scope.mjs` runs on EVERY Edit/Write. Two strings do not need
42
+ * a lock manager.
43
+ *
44
+ * Tolerance is matched to `readLock()` exactly, which collapses every non-ok
45
+ * outcome of `readLockDetailed()` to `null`: missing file, unreadable file,
46
+ * invalid JSON, and **valid JSON that fails the lock schema** all yield `{}`
47
+ * here. The schema check is not reproduced but IMPORTED — `isLockShape()` from
48
+ * `../session-lock-shape.mjs` is the same predicate `parseLock()` applies, and
49
+ * that module imports nothing, so sharing it costs the hook chain no closure.
50
+ * A copy would have been free to drift the fail-OPEN way: a relaxed
51
+ * `parseLock` plus an unchanged copy here drops the lock tier's ids, the own
52
+ * manifest reads `foreign`, and enforcement switches itself off silently.
53
+ *
54
+ * Never throws.
55
+ *
56
+ * @param {string} repoRoot
57
+ * @returns {{ session_id?: string, semantic_session_id?: string }}
58
+ */
59
+ function readLockIds(repoRoot) {
60
+ try {
61
+ const raw = readFileSync(
62
+ path.join(repoRoot ?? process.cwd(), '.orchestrator', 'session.lock'),
63
+ 'utf8',
64
+ );
65
+ const obj = JSON.parse(raw);
66
+ if (!isLockShape(obj)) return {};
67
+ return { session_id: obj.session_id, semantic_session_id: obj.semantic_session_id };
68
+ } catch {
69
+ return {};
70
+ }
71
+ }
30
72
 
31
73
  /**
32
74
  * The set of session ids that provably name THIS session — the UNION of every
@@ -112,11 +154,66 @@ export function readOwnSessionIds(repoRoot, { hookInput = null } = {}) {
112
154
 
113
155
  // Source 3 — repo-global lock file (the manifest writer's own identity).
114
156
  try {
115
- const lock = readLock({ repoRoot });
157
+ const lock = readLockIds(repoRoot);
116
158
  for (const key of ['session_id', 'semantic_session_id']) add(lock?.[key]);
117
159
  } catch {
118
- /* readLock never throws by contract, but that contract is not ours to trust */
160
+ /* readLockIds never throws by contract, but that contract is not ours to trust */
161
+ }
162
+ return ids;
163
+ }
164
+
165
+ /**
166
+ * The ids that name this session and are PROCESS-LOCAL — tiers 1 and 2 only,
167
+ * never the lock and never STATE.md.
168
+ *
169
+ * A sibling of {@link readOwnSessionIds}, not a replacement: the two answer
170
+ * different questions and the difference is the whole point.
171
+ *
172
+ * - `readOwnSessionIds()` answers *"could this id name me?"* and unions three
173
+ * tiers, the third of which IS `session.lock`. That union is correct when
174
+ * the thing being classified was written by some OTHER process (a wave-scope
175
+ * manifest), because every id this process can legitimately claim counts.
176
+ * - This function answers *"which process is emitting right now?"*, and for
177
+ * that question the lock is **vacuous**: when the candidate ids under
178
+ * judgement are the lock's OWN values, a membership test against a set that
179
+ * contains the lock matches by construction — a peer-owned lock would
180
+ * classify as `own` 100% of the time.
181
+ *
182
+ * **STATE.md is excluded for the same reason, and this is the #1177-FX1 fix.**
183
+ * `.claude/STATE.md` is a SHARED working-copy artefact written by whichever
184
+ * session owns the working copy — normally the lock holder. So when a peer holds
185
+ * the lock, the peer also wrote STATE.md, and the two "independent" witnesses
186
+ * agree with each other about the PEER. Unioning a shared-file witness with a
187
+ * process-local one lets the weaker witness carry the verdict while a
188
+ * disagreeing process-local id cannot veto it (measured: lock=peer +
189
+ * STATE.md=peer + `CLAUDE_CODE_SESSION_ID`=me stamped the PEER's ids). Tiering
190
+ * rather than unioning is the fix — a better signal REPLACES a worse one
191
+ * (`.claude/rules/host-resources.md` § HR-102).
192
+ *
193
+ * Never throws.
194
+ *
195
+ * @param {{ env?: object, hookInput?: object|null }} [opts]
196
+ * @param {object} [opts.env=process.env] — injectable for tests.
197
+ * @param {object|null} [opts.hookInput=null] — the harness's statement about
198
+ * THIS invocation, when the caller is a hook.
199
+ * @returns {string[]} possibly EMPTY — an empty result means "this process
200
+ * cannot prove who it is", which callers must treat as unprovable rather
201
+ * than as a match.
202
+ */
203
+ export function readProcessLocalSessionIds({ env = process.env, hookInput = null } = {}) {
204
+ const ids = [];
205
+ const add = (value) => {
206
+ const trimmed = typeof value === 'string' ? value.trim() : '';
207
+ if (trimmed && !ids.includes(trimmed)) ids.push(trimmed);
208
+ };
209
+
210
+ // Tier 1 — the harness's statement about THIS invocation.
211
+ if (hookInput && typeof hookInput === 'object') {
212
+ for (const key of ['session_id', 'sessionId', 'parent_session_id']) add(hookInput[key]);
119
213
  }
214
+ // Tier 2 — process-scoped env var.
215
+ add(env?.CLAUDE_CODE_SESSION_ID);
216
+
120
217
  return ids;
121
218
  }
122
219
 
@@ -135,22 +232,101 @@ export function readOwnSessionIds(repoRoot, { hookInput = null } = {}) {
135
232
  * - `'own'` — an id matched.
136
233
  *
137
234
  * Both id fields are consulted because they address the same session under two
138
- * naming schemes: `session` is the raw harness session id (a UUID on Claude
139
- * Code), `semantic_session` the `<branch>-<date>-<mode>-<n>` form. A harness
235
+ * naming schemes: `session_id` is the raw harness session id (a UUID on Claude
236
+ * Code), `semantic_session_id` the `<branch>-<date>-<mode>-<n>` form. A harness
140
237
  * that resolves only the semantic one must still recognise its own manifest.
238
+ * The pre-#1153 spellings `session` / `semantic_session` are still READ (see
239
+ * {@link MANIFEST_SESSION_KEYS}).
141
240
  *
142
241
  * @param {unknown} scope — parsed wave-scope manifest (any shape; a non-object
143
242
  * simply yields no ids, hence `'unknown'`).
144
- * @param {Set<string>} ownIds — from {@link readOwnSessionIds}.
243
+ * @param {Set<string>} ownIds — from EITHER producer, depending on what is
244
+ * being judged: {@link readOwnSessionIds} when every id this process could
245
+ * legitimately claim counts, or `new Set(`{@link readProcessLocalSessionIds}
246
+ * `(...))` when the lock tier would match vacuously — which is the case for a
247
+ * wave-scope manifest in a checkout shared by two sessions (#1194). Note the
248
+ * latter returns a `string[]`: a bare array is NOT a Set and folds to the
249
+ * empty set below, yielding `'unknown'` for every manifest.
145
250
  * @returns {{ verdict: 'own'|'foreign'|'unknown', manifestIds: string[] }}
251
+ * @see MANIFEST_SESSION_KEYS / {@link manifestSessionBinding} — defined
252
+ * immediately below rather than above this doc comment, because the three
253
+ * live hooks import this module on EVERY tool call: a use-before-define here
254
+ * throws inside the hook chain and locks every session sharing the checkout
255
+ * out of Edit/Write/Bash (measured 2026-09-04, ~8 minutes, #1153 P2).
256
+ */
257
+ /**
258
+ * The session-binding key names of a `wave-scope.json` manifest — the ONE place
259
+ * these literals live (#1153 P2). Every reader imports them from here instead
260
+ * of repeating the strings: `scripts/materialize-wave-scope.mjs`,
261
+ * `scripts/validate-wave-scope.mjs`, `scripts/memory-propose.mjs`, and — via
262
+ * {@link classifyManifestSession} — `scripts/lib/events.mjs` plus the three
263
+ * live hooks.
264
+ *
265
+ * `current` are the canonical names, chosen to match the two neighbouring
266
+ * session artefacts a reader already knows: `.orchestrator/session.lock` and
267
+ * `current-session.json` both spell them `session_id` / `semantic_session_id`,
268
+ * and `scripts/lib/quality-gate.mjs` reads that exact pair. One session
269
+ * identity should not carry two spellings depending on which file names it.
270
+ *
271
+ * `legacy` are the pre-#1153 spellings, and they are **accepted on the READ
272
+ * side only, until the next minor release**. `wave-scope.json` is git-ignored
273
+ * and session-ephemeral, so the one surviving reason to read them is a package
274
+ * upgraded mid-wave with an old-format manifest already on disk. The writer
275
+ * (`scripts/wave-scope-binding.mjs`) emits `current` exclusively.
276
+ *
277
+ * @type {Readonly<{ current: readonly string[], legacy: readonly string[] }>}
146
278
  */
279
+ export const MANIFEST_SESSION_KEYS = Object.freeze({
280
+ current: Object.freeze(['session_id', 'semantic_session_id']),
281
+ legacy: Object.freeze(['session', 'semantic_session']),
282
+ });
283
+
284
+ /**
285
+ * Resolve the binding out of a manifest under BOTH key spellings. A non-object
286
+ * — including an ARRAY, which `typeof` calls `'object'` — yields no ids.
287
+ *
288
+ * **A CONFLICT yields no value for that slot, and that is fail-CLOSED.**
289
+ * When both spellings of one slot are present with different non-empty values,
290
+ * the manifest is self-contradictory (`scripts/validate-wave-scope.mjs` calls
291
+ * exactly this an ERROR — but no hook runs the validator, so the classifier is
292
+ * the only thing standing between the manifest and the guard). Preferring the
293
+ * current spelling let a peer DISARM this session's guard by appending
294
+ * `"session_id": "attacker"` beside a legitimate legacy `"session": "<me>"`:
295
+ * the slot then resolved to an id this session does not carry,
296
+ * {@link classifyManifestSession} returned `'foreign'`, and
297
+ * `hooks/enforce-scope.mjs` skips enforcement on `'foreign'`. Dropping the slot
298
+ * instead collapses the verdict to `'unknown'`, which every caller treats as
299
+ * "keep enforcing".
300
+ *
301
+ * Both present and EQUAL (after trim) → that value. Only one present → that
302
+ * value. Both present and different → the slot is omitted.
303
+ *
304
+ * @param {unknown} scope
305
+ * @returns {{ session_id?: string, semantic_session_id?: string }}
306
+ */
307
+ export function manifestSessionBinding(scope) {
308
+ const out = {};
309
+ if (!scope || typeof scope !== 'object' || Array.isArray(scope)) return out;
310
+ MANIFEST_SESSION_KEYS.current.forEach((key, i) => {
311
+ const legacyKey = MANIFEST_SESSION_KEYS.legacy[i];
312
+ const pick = (v) => (typeof v === 'string' && v.trim() ? v.trim() : '');
313
+ const current = pick(scope[key]);
314
+ const legacy = pick(scope[legacyKey]);
315
+ if (current && legacy && current !== legacy) return; // conflict → no value
316
+ const value = current || legacy;
317
+ if (value) out[key] = value;
318
+ });
319
+ return out;
320
+ }
321
+
322
+ /** @see the contract note above `MANIFEST_SESSION_KEYS` — the full docblock for
323
+ * this function sits there, separated from it only because the constants must
324
+ * be defined before use (#1153 P2). */
147
325
  export function classifyManifestSession(scope, ownIds) {
148
326
  const manifestIds = [];
149
- if (scope && typeof scope === 'object' && !Array.isArray(scope)) {
150
- for (const key of ['session', 'semantic_session']) {
151
- const value = typeof scope[key] === 'string' ? scope[key].trim() : '';
152
- if (value) manifestIds.push(value);
153
- }
327
+ const binding = manifestSessionBinding(scope);
328
+ for (const key of MANIFEST_SESSION_KEYS.current) {
329
+ if (binding[key]) manifestIds.push(binding[key]);
154
330
  }
155
331
  const own = ownIds instanceof Set ? ownIds : new Set();
156
332
  if (manifestIds.length === 0 || own.size === 0) return { verdict: 'unknown', manifestIds };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * session-lock-shape.mjs — the ONE predicate that decides whether a parsed
3
+ * JSON value is a `.orchestrator/session.lock` record (#1153 P7).
4
+ *
5
+ * **Zero imports on purpose.** One of the two consumers is
6
+ * `scripts/lib/session-identity/own-session.mjs`, which the live
7
+ * `hooks/enforce-scope.mjs` loads on EVERY Edit/Write; anything this module
8
+ * imported would join that hook's static closure. It therefore holds six
9
+ * `typeof` checks and nothing else.
10
+ *
11
+ * **The two consumers, and why they must not drift apart:**
12
+ *
13
+ * - `scripts/lib/session-lock.mjs` `parseLock()` — the lock manager's own
14
+ * reader; a value that fails this predicate is `null` there.
15
+ * - `scripts/lib/session-identity/own-session.mjs` `readLockIds()` — the
16
+ * dependency-free stand-in that reads the same two ids out of the same
17
+ * file without dragging the lock manager into a hook.
18
+ *
19
+ * The drift direction is FAIL-OPEN, which is why the predicate is shared
20
+ * rather than repeated: if `parseLock` relaxed the shape and `readLockIds` did
21
+ * not, the lock tier of {@link readOwnSessionIds} would stop contributing its
22
+ * ids, this session's own manifest would classify `foreign`, and
23
+ * `hooks/enforce-scope.mjs` would silently skip enforcement for the whole wave.
24
+ *
25
+ * @param {unknown} obj — a value already parsed from JSON.
26
+ * @returns {boolean} true when `obj` carries all six required lock fields with
27
+ * the right primitive types. `semantic_session_id` and `last_heartbeat` are
28
+ * deliberately NOT required — both are optional by schema (v1 locks predate
29
+ * `last_heartbeat`, and `semantic_session_id` is absent on harnesses that
30
+ * resolve no semantic id).
31
+ */
32
+ export function isLockShape(obj) {
33
+ return (
34
+ typeof obj === 'object' &&
35
+ obj !== null &&
36
+ typeof obj.session_id === 'string' &&
37
+ typeof obj.started_at === 'string' &&
38
+ typeof obj.mode === 'string' &&
39
+ typeof obj.pid === 'number' &&
40
+ typeof obj.host === 'string' &&
41
+ typeof obj.ttl_hours === 'number'
42
+ );
43
+ }
@@ -43,6 +43,7 @@ import os from 'node:os';
43
43
  import path from 'node:path';
44
44
  import crypto from 'node:crypto';
45
45
  import { classifyMode } from './exclusivity-matrix.mjs';
46
+ import { isLockShape } from './session-lock-shape.mjs';
46
47
  import { isPidAliveOnHost } from './file-lock.mjs';
47
48
  import { writeJsonAtomicSync } from './io.mjs';
48
49
  import { hostnamesMatch, lockHostCandidate, recordHostAlias, stableHostname } from './host-identity.mjs';
@@ -199,16 +200,10 @@ function heartbeatAgeMinutes(lock) {
199
200
  function parseLock(raw) {
200
201
  try {
201
202
  const obj = JSON.parse(raw);
202
- if (
203
- typeof obj === 'object' &&
204
- obj !== null &&
205
- typeof obj.session_id === 'string' &&
206
- typeof obj.started_at === 'string' &&
207
- typeof obj.mode === 'string' &&
208
- typeof obj.pid === 'number' &&
209
- typeof obj.host === 'string' &&
210
- typeof obj.ttl_hours === 'number'
211
- ) {
203
+ // The six-field predicate lives in ONE place (#1153 P7) — see
204
+ // `./session-lock-shape.mjs` for the other consumer and the fail-open
205
+ // drift this sharing prevents.
206
+ if (isLockShape(obj)) {
212
207
  // Schema v1 → v2 normalisation: when `last_heartbeat` is absent or
213
208
  // non-string, treat the lock as if it heartbeat-ed once at started_at.
214
209
  const normalised = { ...obj };
@@ -5,11 +5,13 @@
5
5
  * peer-detection (F2, #168) and hooks/on-stop.mjs clean deregister + zombie
6
6
  * sweep (F3, #169).
7
7
  *
8
- * Registry location: `~/.config/session-orchestrator/sessions/active/<sessionId>.json`
9
- * Sweep log: `~/.config/session-orchestrator/sessions/sweep.log` (JSONL)
8
+ * Registry location: `<private-config-dir>/sessions/active/<sessionId>.json`
9
+ * Sweep log: `<private-config-dir>/sessions/sweep.log` (JSONL)
10
10
  *
11
- * Overridable via env var `SO_SESSION_REGISTRY_DIR` (points to the parent
12
- * `sessions/` directory, not `active/`) — used by tests for isolation.
11
+ * `<private-config-dir>` is `resolvePrivateConfigDir()` `SO_CONFIG_HOME` >
12
+ * `XDG_CONFIG_HOME` > `~/.config/session-orchestrator`. `SO_SESSION_REGISTRY_DIR`
13
+ * overrides the whole `sessions/` directory (not `active/`) and outranks all
14
+ * three — used by tests for isolation.
13
15
  *
14
16
  * Heartbeat schema (per issue #167):
15
17
  * {
@@ -27,7 +29,6 @@
27
29
  * deliberately, see `skills/_shared/state-ownership.md` § Schema v1 Sunset.
28
30
  */
29
31
 
30
- import os from 'node:os';
31
32
  import path from 'node:path';
32
33
  import crypto from 'node:crypto';
33
34
  import { promises as fs } from 'node:fs';
@@ -36,16 +37,31 @@ import { digestSha256 } from './crypto-digest-utils.mjs';
36
37
  import { appendFileSync, mkdirSync } from 'node:fs';
37
38
 
38
39
  import { utcTimestamp, appendJsonl } from './common.mjs';
40
+ import { resolvePrivateConfigDir } from './config/private-config-dir.mjs';
39
41
 
40
42
  // ---------------------------------------------------------------------------
41
43
  // Paths
42
44
  // ---------------------------------------------------------------------------
43
45
 
44
- /** Parent directory for all session-registry state. */
46
+ /**
47
+ * Parent directory for all session-registry state.
48
+ *
49
+ * Precedence: `SO_SESSION_REGISTRY_DIR` (names the `sessions/` dir ITSELF —
50
+ * highest, and what every existing test uses for isolation) > the host-private
51
+ * config dir resolved by `resolvePrivateConfigDir()` (`SO_CONFIG_HOME` >
52
+ * `XDG_CONFIG_HOME` > `~/.config/session-orchestrator`).
53
+ *
54
+ * Hardcoding `~/.config` here was the #1223 hazard class in its registry form:
55
+ * a sandboxed probe run that set `SO_CONFIG_HOME=<tmp>` moved every OTHER
56
+ * host-private artefact but not this one, so the throwaway session registered
57
+ * itself in the operator's REAL registry and was then discovered as a live peer.
58
+ * `.trim()` guards the whitespace-only env value (`development.md` § Error
59
+ * Handling) — `' '` is truthy and would otherwise be returned verbatim.
60
+ */
45
61
  export function registryBaseDir() {
46
- const override = process.env.SO_SESSION_REGISTRY_DIR;
47
- if (override && override.length > 0) return override;
48
- return path.join(os.homedir(), '.config', 'session-orchestrator', 'sessions');
62
+ const override = (process.env.SO_SESSION_REGISTRY_DIR || '').trim();
63
+ if (override) return override;
64
+ return path.join(resolvePrivateConfigDir(), 'sessions');
49
65
  }
50
66
 
51
67
  /** Directory holding one JSON file per active session. */
@@ -56,8 +56,31 @@ export const SESSION_KEY_ALIASES = Object.freeze({
56
56
  // Enums / required field lists
57
57
  // ---------------------------------------------------------------------------
58
58
 
59
- /** Closed set of valid session_type values. */
60
- export const VALID_SESSION_TYPES = Object.freeze(['feature', 'deep', 'housekeeping']);
59
+ /**
60
+ * Closed set of valid session_type values.
61
+ *
62
+ * `unknown` (GitLab #1234, added 2026-09-06) is NOT a fourth session MODE — it is
63
+ * the absence of a measurement, and it exists so a reconstructed record can say
64
+ * so instead of guessing. Measured 2026-09-06 over the 90-day fleet corpus: all
65
+ * 1.656 `abandoned` records carry `_session_type_inferred: true` + `total_waves: 0`
66
+ * and NO organically written `abandoned` record exists anywhere — i.e. every one
67
+ * of them was labelled `housekeeping` by `scripts/lib/session-close-backfill.mjs`
68
+ * because the enum left it no alternative, and that guess is what produced the
69
+ * fleet-wide "27 % close rate" figure (the real rate is 21,3 %).
70
+ *
71
+ * Only `synthesizeRecord()` in `scripts/lib/session-close-backfill.mjs` writes it,
72
+ * and only for records it also flags `_session_type_inferred` + `_synthetic`.
73
+ * A live session never becomes `unknown`: `/session` still resolves one of the
74
+ * three modes, and `scripts/lib/wave-sizing.mjs` (which THROWS on a fourth value)
75
+ * is only ever fed the live type, never a ledger record.
76
+ *
77
+ * Known mis-bucket, out of this change's scope: `normalizeSessionType()` in
78
+ * `scripts/lib/telemetry/schema.mjs:382` maps any non-empty unlisted value to
79
+ * `'other'` ("something WAS measured and is not one of the three modes"), which
80
+ * is the opposite of what `unknown` means — even though that module already
81
+ * defines `SESSION_TYPE_UNKNOWN = 'unknown'` for its absent-branch.
82
+ */
83
+ export const VALID_SESSION_TYPES = Object.freeze(['feature', 'deep', 'housekeeping', 'unknown']);
61
84
 
62
85
  /**
63
86
  * Required fields for a schema_version=1 record. Validated by validateSession
@@ -131,4 +154,15 @@ export const OPTIONAL_FIELDS = Object.freeze([
131
154
  // into a note a human reads" ⊃ "schema-valid". A record missing it is a clean
132
155
  // vault-mirror skip, NOT a malformed record.
133
156
  'effectiveness',
157
+ // PRD docs/prd/2026-09-06-ultradeep-session-profile.md — `session_profile`
158
+ // names a WAVE-SHAPE variant on top of an unchanged `session_type`. It is
159
+ // additive and optional on purpose: `ultradeep` is deliberately NOT a member
160
+ // of VALID_SESSION_TYPES above, because that set is mirrored in
161
+ // scripts/lib/telemetry/schema.mjs (an unlisted type -> 'other') and in
162
+ // scripts/lib/wave-sizing.mjs (an unlisted type -> TypeError), where a new
163
+ // MODE would be MISLABELLED rather than rejected. That reasoning is unchanged
164
+ // by the `unknown` member added above: `unknown` is the absence of a
165
+ // measurement, not a mode, and nothing dispatches on it. Every historical
166
+ // record lacking the field validates unchanged.
167
+ 'session_profile',
134
168
  ]);
@@ -28,11 +28,33 @@ const EXPECTED_COST_TIERS = Object.freeze(['quick', 'standard', 'deep']);
28
28
 
29
29
  /**
30
30
  * Valid values for the optional `status` field (Epic #724 C1).
31
- * `completed` — record written by a normal /close flow.
32
- * `abandoned` — stub backfilled by the SessionEnd hook because the session
33
- * terminated without running /close.
31
+ * `completed` — record written by a normal /close flow.
32
+ * `abandoned` — stub backfilled by the SessionEnd hook because the session
33
+ * terminated without running /close.
34
+ * `unresolved` — the HONEST label for that same stub: "never reached /close" is
35
+ * observed, but "abandoned" is an interpretation of it (a session
36
+ * may have finished its work and merely skipped /close, or been
37
+ * killed). GitLab #1234, added 2026-09-06.
38
+ *
39
+ * CEILING (BV-004) — `unresolved` currently has NO writer, and the emitter must
40
+ * not adopt it yet. Six EXECUTABLE phantom-stub filters key on the literal
41
+ * `abandoned` and none is inside this change's file scope, so flipping the
42
+ * emitter today would make every new stub invisible to all of them and re-open
43
+ * the #834 phantom-in-signal class fleet-wide. Census measured 2026-09-06 via
44
+ * `rg -n "status\s*(!==|===|!=|==)\s*[\"']abandoned[\"']" scripts hooks --glob '!*.test.mjs'`:
45
+ * scripts/lib/session-schema/filters.mjs:56 (isRealSession — the shared helper,
46
+ * imported by sessions-staleness-banner, auto-dream, dialectic-deriver,
47
+ * harness-audit/categories/category4, evolve/autopilot-effectiveness)
48
+ * scripts/lib/sessions-canonical.mjs:149,172
49
+ * scripts/lib/eval/session-resolve.mjs:79
50
+ * scripts/mcp-server.sh:195
51
+ * scripts/compute-grounding-injection.sh:74
52
+ * REVISIT TRIGGER: once those six route through one predicate that accepts both
53
+ * `abandoned` and `unresolved`, switch `synthesizeRecord()` in
54
+ * `scripts/lib/session-close-backfill.mjs` to emit `unresolved` for inferred
55
+ * records — a one-line change, which is exactly why this value lands now.
34
56
  */
35
- const SESSION_STATUS = Object.freeze(['completed', 'abandoned']);
57
+ const SESSION_STATUS = Object.freeze(['completed', 'abandoned', 'unresolved']);
36
58
 
37
59
  /**
38
60
  * Canonical ISO-8601 UTC timestamp regex — accepts `YYYY-MM-DDTHH:MM:SSZ`
@@ -462,6 +484,18 @@ function _validateOptionalFields(entry) {
462
484
  }
463
485
  }
464
486
 
487
+ // `session_profile` — optional wave-shape profile (PRD
488
+ // docs/prd/2026-09-06-ultradeep-session-profile.md). Non-empty string or
489
+ // null/absent; absent means "no profile" and is NOT coerced to a string.
490
+ // Deliberately NOT validated against a closed set: unlike `session_type`,
491
+ // no consumer branches on the value, so an unrecognised profile is a
492
+ // readable record with an unknown shape rather than a silent mislabel.
493
+ if (entry.session_profile !== undefined && entry.session_profile !== null) {
494
+ if (typeof entry.session_profile !== 'string' || entry.session_profile.length === 0) {
495
+ throw new ValidationError('session_profile must be a non-empty string or null');
496
+ }
497
+ }
498
+
465
499
  // `_express_path_detail` — forensic sidecar written by `normalizeSession`
466
500
  // when it collapses a legacy object `express_path` onto its boolean. Holds
467
501
  // the pre-collapse object verbatim so the conversion stays reversible.
@@ -176,8 +176,16 @@ export const PROBES = [
176
176
  args: ({ repoRoot }) => ({ repoRoot }),
177
177
  // Bespoke shape: `{status, ok, details, …}` with no `message` field. The
178
178
  // banner text is prescribed by SKILL.md § Phase 4.
179
+ //
180
+ // The degraded branch is NOT decoration (#1031): this entry overrides BOTH
181
+ // `render` and `severityOf`, so the module-level defaults that already
182
+ // handle a `{severity:'warn', message, degraded}` result never run for this
183
+ // probe. Without these two lines a degraded ci-status result scored `'ok'`
184
+ // and rendered nothing — "could not read" displayed exactly like "green",
185
+ // which is the confusion the probe's own migration removed one layer down.
179
186
  render: (r) => {
180
187
  if (!r || typeof r !== 'object') return null;
188
+ if (r.degraded) return typeof r.message === 'string' && r.message ? r.message : null;
181
189
  if (r.status === 'red') {
182
190
  const pid = r.details?.currentPipelineId ?? '?';
183
191
  const green = r.lastGreen
@@ -193,7 +201,16 @@ export const PROBES = [
193
201
  return null;
194
202
  },
195
203
  // `status: 'red'` is an alert even though the probe publishes no severity.
196
- severityOf: (r) => (r?.status === 'red' ? 'alert' : r?.status === 'green' && r?.allowFailureJobs ? 'warn' : 'ok'),
204
+ // A degraded result is a finding, never clean same rule as the generic
205
+ // path in `severityOf()` below.
206
+ severityOf: (r) =>
207
+ r?.degraded
208
+ ? 'warn'
209
+ : r?.status === 'red'
210
+ ? 'alert'
211
+ : r?.status === 'green' && r?.allowFailureJobs
212
+ ? 'warn'
213
+ : 'ok',
197
214
  },
198
215
  {
199
216
  id: 'qg-command-drift',
@@ -134,7 +134,7 @@ export const LOCK_RELEASED_EVENT = 'orchestrator.session.lock.released';
134
134
  * lock owned by someone else, an unparseable lock, a registry entry pinned to
135
135
  * a different root, a registry unlink that failed, a contradictory owner
136
136
  * proof, or a filesystem error. Callers WARN on it and continue; see the
137
- * `parallel-aware: leaveSourceRoot: <reason>` contract in
137
+ * `enterWorktree: leaveSourceRoot: <reason>` contract in
138
138
  * `skills/_shared/parallel-aware-auq.md`.
139
139
  *
140
140
  * `reason` is present whenever something other than a full clean teardown