session-orchestrator 5.1.0 → 5.3.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 (484) hide show
  1. package/.agents/skills/architecture/SKILL.md +3 -1
  2. package/.agents/skills/autopilot/SKILL.md +6 -1
  3. package/.agents/skills/autopilot/agents/openai.yaml +5 -0
  4. package/.agents/skills/bootstrap/SKILL.md +7 -1
  5. package/.agents/skills/bootstrap/agents/openai.yaml +5 -0
  6. package/.agents/skills/brainstorm/SKILL.md +8 -1
  7. package/.agents/skills/brainstorm/agents/openai.yaml +5 -0
  8. package/.agents/skills/claude-md-drift-check/SKILL.md +3 -1
  9. package/.agents/skills/close/SKILL.md +21 -0
  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 +7 -1
  13. package/.agents/skills/debug/agents/openai.yaml +5 -0
  14. package/.agents/skills/discovery/SKILL.md +7 -2
  15. package/.agents/skills/discovery/agents/openai.yaml +5 -0
  16. package/.agents/skills/dispatcher/SKILL.md +7 -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 +7 -1
  21. package/.agents/skills/eli5/agents/openai.yaml +5 -0
  22. package/.agents/skills/eval/SKILL.md +7 -2
  23. package/.agents/skills/eval/agents/openai.yaml +5 -0
  24. package/.agents/skills/evolve/SKILL.md +8 -3
  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 +22 -0
  30. package/.agents/skills/go/agents/openai.yaml +5 -0
  31. package/.agents/skills/grill/SKILL.md +7 -1
  32. package/.agents/skills/grill/agents/openai.yaml +5 -0
  33. package/.agents/skills/harness-audit/SKILL.md +20 -0
  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 +6 -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/npm-publish/SKILL.md +4 -2
  41. package/.agents/skills/peekaboo-driver/SKILL.md +3 -1
  42. package/.agents/skills/persona-panel/SKILL.md +6 -1
  43. package/.agents/skills/persona-panel/agents/openai.yaml +5 -0
  44. package/.agents/skills/plan/SKILL.md +8 -2
  45. package/.agents/skills/plan/agents/openai.yaml +5 -0
  46. package/.agents/skills/playwright-driver/SKILL.md +3 -1
  47. package/.agents/skills/portfolio/SKILL.md +21 -0
  48. package/.agents/skills/portfolio/agents/openai.yaml +5 -0
  49. package/.agents/skills/quality-gates/SKILL.md +3 -1
  50. package/.agents/skills/reconcile/SKILL.md +6 -1
  51. package/.agents/skills/reconcile/agents/openai.yaml +5 -0
  52. package/.agents/skills/release/SKILL.md +22 -0
  53. package/.agents/skills/release/agents/openai.yaml +5 -0
  54. package/.agents/skills/remote-offload/SKILL.md +3 -1
  55. package/.agents/skills/repo-audit/SKILL.md +6 -1
  56. package/.agents/skills/repo-audit/agents/openai.yaml +5 -0
  57. package/.agents/skills/session/SKILL.md +21 -0
  58. package/.agents/skills/session/agents/openai.yaml +5 -0
  59. package/.agents/skills/session-end/SKILL.md +3 -1
  60. package/.agents/skills/session-plan/SKILL.md +3 -1
  61. package/.agents/skills/session-start/SKILL.md +3 -1
  62. package/.agents/skills/spinout/SKILL.md +6 -1
  63. package/.agents/skills/spinout/agents/openai.yaml +5 -0
  64. package/.agents/skills/sunset-review/SKILL.md +7 -1
  65. package/.agents/skills/sunset-review/agents/openai.yaml +5 -0
  66. package/.agents/skills/templates-ack/SKILL.md +21 -0
  67. package/.agents/skills/templates-ack/agents/openai.yaml +5 -0
  68. package/.agents/skills/test/SKILL.md +21 -0
  69. package/.agents/skills/test/agents/openai.yaml +5 -0
  70. package/.agents/skills/test-runner/SKILL.md +3 -1
  71. package/.agents/skills/tmux-layout/SKILL.md +3 -1
  72. package/.agents/skills/using-orchestrator/SKILL.md +3 -1
  73. package/.agents/skills/ux-grill/SKILL.md +7 -1
  74. package/.agents/skills/ux-grill/agents/openai.yaml +5 -0
  75. package/.agents/skills/vault-mirror/SKILL.md +3 -1
  76. package/.agents/skills/vault-sync/SKILL.md +3 -1
  77. package/.agents/skills/wave-executor/SKILL.md +3 -1
  78. package/.agents/skills/write-executable-plan/SKILL.md +3 -1
  79. package/.claude-plugin/marketplace.json +1 -1
  80. package/.claude-plugin/plugin.json +1 -1
  81. package/.codex-plugin/plugin.json +4 -4
  82. package/.codex-plugin/skills/autopilot/SKILL.md +5 -4
  83. package/.codex-plugin/skills/bootstrap/SKILL.md +8 -4
  84. package/.codex-plugin/skills/brainstorm/SKILL.md +11 -4
  85. package/.codex-plugin/skills/close/SKILL.md +3 -3
  86. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +1 -1
  87. package/.codex-plugin/skills/debug/SKILL.md +11 -4
  88. package/.codex-plugin/skills/discovery/SKILL.md +8 -4
  89. package/.codex-plugin/skills/dispatcher/SKILL.md +4 -4
  90. package/.codex-plugin/skills/eli5/SKILL.md +9 -4
  91. package/.codex-plugin/skills/eval/SKILL.md +9 -4
  92. package/.codex-plugin/skills/evolve/SKILL.md +9 -4
  93. package/.codex-plugin/skills/go/SKILL.md +3 -3
  94. package/.codex-plugin/skills/grill/SKILL.md +11 -4
  95. package/.codex-plugin/skills/harness-audit/SKILL.md +4 -3
  96. package/.codex-plugin/skills/memory-cleanup/SKILL.md +9 -4
  97. package/.codex-plugin/skills/npm-publish/SKILL.md +1 -1
  98. package/.codex-plugin/skills/persona-panel/SKILL.md +5 -5
  99. package/.codex-plugin/skills/plan/SKILL.md +8 -4
  100. package/.codex-plugin/skills/portfolio/SKILL.md +3 -3
  101. package/.codex-plugin/skills/reconcile/SKILL.md +9 -4
  102. package/.codex-plugin/skills/release/SKILL.md +3 -3
  103. package/.codex-plugin/skills/repo-audit/SKILL.md +6 -4
  104. package/.codex-plugin/skills/session/SKILL.md +1 -1
  105. package/.codex-plugin/skills/spinout/SKILL.md +4 -4
  106. package/.codex-plugin/skills/sunset-review/SKILL.md +5 -4
  107. package/.codex-plugin/skills/test/SKILL.md +3 -3
  108. package/.codex-plugin/skills/ux-grill/SKILL.md +11 -4
  109. package/.cursor/commands/autopilot.md +4 -4
  110. package/.cursor/commands/bootstrap.md +5 -4
  111. package/.cursor/commands/brainstorm.md +5 -4
  112. package/.cursor/commands/close.md +4 -3
  113. package/.cursor/commands/debug.md +4 -4
  114. package/.cursor/commands/discovery.md +4 -4
  115. package/.cursor/commands/dispatcher.md +4 -4
  116. package/.cursor/commands/eli5.md +4 -4
  117. package/.cursor/commands/eval.md +4 -4
  118. package/.cursor/commands/evolve.md +4 -4
  119. package/.cursor/commands/go.md +4 -3
  120. package/.cursor/commands/grill.md +4 -4
  121. package/.cursor/commands/harness-audit.md +3 -3
  122. package/.cursor/commands/memory-cleanup.md +4 -4
  123. package/.cursor/commands/persona-panel.md +4 -4
  124. package/.cursor/commands/plan.md +5 -4
  125. package/.cursor/commands/portfolio.md +3 -3
  126. package/.cursor/commands/reconcile.md +4 -4
  127. package/.cursor/commands/release.md +4 -3
  128. package/.cursor/commands/repo-audit.md +4 -4
  129. package/.cursor/commands/session.md +1 -1
  130. package/.cursor/commands/spinout.md +4 -4
  131. package/.cursor/commands/sunset-review.md +4 -4
  132. package/.cursor/commands/test.md +3 -3
  133. package/.cursor/commands/ux-grill.md +4 -4
  134. package/.cursor/rules/000-session-orchestrator.mdc +0 -2
  135. package/.cursor/rules/010-session-workflow.mdc +2 -2
  136. package/.cursor/rules/050-plan.mdc +1 -1
  137. package/.cursor/skills/bootstrap/SKILL.md +1 -0
  138. package/.cursor/skills/close/SKILL.md +13 -0
  139. package/.cursor/skills/convergence-monitoring/SKILL.md +1 -0
  140. package/.cursor/skills/debug/SKILL.md +0 -1
  141. package/.cursor/skills/discovery/SKILL.md +0 -1
  142. package/.cursor/skills/dispatcher/SKILL.md +0 -1
  143. package/.cursor/skills/eli5/SKILL.md +0 -1
  144. package/.cursor/skills/eval/SKILL.md +1 -1
  145. package/.cursor/skills/evolve/SKILL.md +0 -1
  146. package/.cursor/skills/go/SKILL.md +13 -0
  147. package/.cursor/skills/grill/SKILL.md +0 -1
  148. package/.cursor/skills/harness-audit/SKILL.md +12 -0
  149. package/.cursor/skills/npm-publish/SKILL.md +1 -0
  150. package/.cursor/skills/portfolio/SKILL.md +12 -0
  151. package/.cursor/skills/release/SKILL.md +13 -0
  152. package/.cursor/skills/repo-audit/SKILL.md +0 -1
  153. package/.cursor/skills/sunset-review/SKILL.md +0 -1
  154. package/.cursor/skills/test/SKILL.md +12 -0
  155. package/.cursor/skills/ux-grill/SKILL.md +0 -1
  156. package/.cursor-plugin/plugin.json +1 -1
  157. package/.orchestrator/policy/blocked-commands.json +13 -4
  158. package/AGENTS.md +3 -2
  159. package/CHANGELOG.md +197 -0
  160. package/README.md +11 -9
  161. package/SECURITY.md +12 -0
  162. package/agents/dialectic-deriver.md +13 -10
  163. package/agents/eval-judge.md +67 -45
  164. package/agents/skill-applied-judge.md +34 -19
  165. package/commands/session.md +17 -3
  166. package/docs/baseline.md +12 -6
  167. package/docs/ci-setup.md +53 -0
  168. package/docs/codex-setup.md +15 -3
  169. package/docs/components.md +13 -6
  170. package/docs/events-schema.md +59 -9
  171. package/docs/install.md +16 -0
  172. package/docs/persona-panel.md +1 -1
  173. package/docs/pi-setup.md +1 -1
  174. package/docs/rule-authoring.md +135 -14
  175. package/docs/scope-collision-guard.md +2 -0
  176. package/docs/session-config-reference.md +106 -11
  177. package/docs/session-config-template.md +31 -2
  178. package/docs/telemetry.md +2 -0
  179. package/hooks/_lib/hook-import-set.json +125 -8
  180. package/hooks/_lib/subagent-paths.mjs +15 -0
  181. package/hooks/_lib/subagent-transcript.mjs +582 -31
  182. package/hooks/_lib/vcs-create-matcher.mjs +217 -62
  183. package/hooks/config-protection.mjs +11 -3
  184. package/hooks/cwd-change-restore.mjs +11 -3
  185. package/hooks/enforce-commands.mjs +70 -23
  186. package/hooks/enforce-scope.mjs +143 -33
  187. package/hooks/hooks-codex.json +1 -1
  188. package/hooks/hooks.json +1 -1
  189. package/hooks/loop-guard.mjs +11 -3
  190. package/hooks/on-session-end.mjs +72 -25
  191. package/hooks/on-session-start.mjs +48 -11
  192. package/hooks/on-stop.mjs +211 -23
  193. package/hooks/operator-steer.mjs +11 -3
  194. package/hooks/post-bash-issue-budget-refund.mjs +18 -8
  195. package/hooks/post-bash-write-verify.mjs +6 -2
  196. package/hooks/post-edit-import-probe.mjs +17 -9
  197. package/hooks/post-edit-validate.mjs +13 -5
  198. package/hooks/post-subagent-discovery-validator.mjs +98 -13
  199. package/hooks/post-tool-batch-wave-signal.mjs +200 -38
  200. package/hooks/post-tool-failure-corrective-context.mjs +11 -5
  201. package/hooks/post-tooluse-frontend-slop.mjs +10 -4
  202. package/hooks/pre-auq-clarity.mjs +18 -2
  203. package/hooks/pre-bash-destructive-guard.mjs +80 -9
  204. package/hooks/pre-bash-issue-budget.mjs +119 -28
  205. package/hooks/pre-bash-memory-propose-audit.mjs +86 -54
  206. package/hooks/pre-bash-sessions-ledger-guard.mjs +391 -20
  207. package/hooks/pre-bash-staging-fence.mjs +335 -31
  208. package/hooks/pre-bash-templates-first.mjs +19 -14
  209. package/hooks/pre-task-scope-disjoint.mjs +385 -5
  210. package/hooks/skill-invocation-telemetry.mjs +2 -1
  211. package/hooks/subagent-telemetry.mjs +15 -19
  212. package/hooks/wave-scope-commit-guard.mjs +197 -100
  213. package/monitors/monitors.json +1 -1
  214. package/output-styles/wave-summary.md +1 -1
  215. package/package.json +2 -1
  216. package/pi/prompts/autopilot.md +3 -3
  217. package/pi/prompts/bootstrap.md +3 -3
  218. package/pi/prompts/brainstorm.md +3 -3
  219. package/pi/prompts/close.md +2 -2
  220. package/pi/prompts/debug.md +3 -3
  221. package/pi/prompts/discovery.md +3 -3
  222. package/pi/prompts/dispatcher.md +3 -3
  223. package/pi/prompts/eli5.md +3 -3
  224. package/pi/prompts/eval.md +3 -3
  225. package/pi/prompts/evolve.md +3 -3
  226. package/pi/prompts/go.md +2 -2
  227. package/pi/prompts/grill.md +3 -3
  228. package/pi/prompts/harness-audit.md +2 -3
  229. package/pi/prompts/memory-cleanup.md +3 -3
  230. package/pi/prompts/persona-panel.md +3 -3
  231. package/pi/prompts/plan.md +3 -3
  232. package/pi/prompts/portfolio.md +2 -2
  233. package/pi/prompts/reconcile.md +3 -3
  234. package/pi/prompts/release.md +3 -3
  235. package/pi/prompts/repo-audit.md +3 -4
  236. package/pi/prompts/session.md +2 -2
  237. package/pi/prompts/spinout.md +3 -3
  238. package/pi/prompts/sunset-review.md +3 -3
  239. package/pi/prompts/templates-ack.md +1 -1
  240. package/pi/prompts/test.md +3 -3
  241. package/pi/prompts/ux-grill.md +3 -3
  242. package/rules/README.md +1 -1
  243. package/rules/opt-in-domain/prompt-caching.md +1 -1
  244. package/rules/opt-in-stack/backend-data.md +1 -1
  245. package/rules/opt-in-stack/backend.md +3 -3
  246. package/rules/opt-in-stack/frontend.md +1 -1
  247. package/rules/opt-in-stack/security-web.md +3 -3
  248. package/rules/opt-in-stack/swift.md +1 -1
  249. package/scripts/archive-closed-prds.mjs +2 -2
  250. package/scripts/auq-audit.mjs +2 -3
  251. package/scripts/autopilot.mjs +23 -2
  252. package/scripts/backfill-abandoned-sessions.mjs +171 -15
  253. package/scripts/backfill-evidence-digest.mjs +2 -1
  254. package/scripts/backfill-learnings-from-vault.mjs +2 -2
  255. package/scripts/check-package-manager.mjs +2 -2
  256. package/scripts/check-sessions-integrity.mjs +300 -0
  257. package/scripts/ci/assert-vitest-green.mjs +2 -1
  258. package/scripts/dialectic-deriver.mjs +50 -13
  259. package/scripts/emit-session.mjs +77 -32
  260. package/scripts/eval-session.mjs +65 -3
  261. package/scripts/export-hw-learnings.mjs +2 -1
  262. package/scripts/express-path.mjs +1 -1
  263. package/scripts/gc-stale-worktrees.mjs +2 -1
  264. package/scripts/generate-agents-skills.mjs +102 -29
  265. package/scripts/generate-codex-skills.mjs +48 -4
  266. package/scripts/generate-cursor-adapter.mjs +220 -11
  267. package/scripts/generate-hook-import-set.mjs +12 -27
  268. package/scripts/generate-pi-prompts.mjs +183 -13
  269. package/scripts/github-protection-audit.mjs +2 -3
  270. package/scripts/lib/agent-frontmatter.mjs +23 -1
  271. package/scripts/lib/agent-status.mjs +2 -31
  272. package/scripts/lib/auq/clarity.mjs +10 -2
  273. package/scripts/lib/auq/parse.mjs +12 -31
  274. package/scripts/lib/auq/schema.mjs +56 -41
  275. package/scripts/lib/auto-dialectic.mjs +304 -15
  276. package/scripts/lib/autopilot/flags.mjs +12 -1
  277. package/scripts/lib/autopilot/kill-switches.mjs +6 -3
  278. package/scripts/lib/autopilot/loop.mjs +14 -1
  279. package/scripts/lib/autopilot/stall-sampler.mjs +80 -23
  280. package/scripts/lib/ci-status-banner.mjs +376 -16
  281. package/scripts/lib/claude-md-budget-lint.mjs +2 -5
  282. package/scripts/lib/command-blocker.mjs +408 -33
  283. package/scripts/lib/config/dialectic.mjs +12 -3
  284. package/scripts/lib/config/drift-check.mjs +19 -0
  285. package/scripts/lib/config/gate.mjs +74 -0
  286. package/scripts/lib/config/reaper.mjs +162 -0
  287. package/scripts/lib/config.mjs +14 -0
  288. package/scripts/lib/convergence-monitor.mjs +76 -13
  289. package/scripts/lib/cursor-hook-bridge.mjs +2 -2
  290. package/scripts/lib/description-surface.mjs +2 -5
  291. package/scripts/lib/dispatcher/cli.mjs +2 -1
  292. package/scripts/lib/ecosystem-health.mjs +11 -0
  293. package/scripts/lib/ecosystem-wizard.mjs +2 -1
  294. package/scripts/lib/eval/engine.mjs +421 -53
  295. package/scripts/lib/eval/judge.mjs +463 -40
  296. package/scripts/lib/eval/schema.mjs +10 -1
  297. package/scripts/lib/events-rotation.mjs +221 -25
  298. package/scripts/lib/events-schema.mjs +114 -0
  299. package/scripts/lib/events.mjs +524 -5
  300. package/scripts/lib/fetch-baseline.mjs +3 -8
  301. package/scripts/lib/frontmatter-guard.mjs +21 -10
  302. package/scripts/lib/gates/gate-baseline.mjs +27 -2
  303. package/scripts/lib/gates/gate-full.mjs +28 -3
  304. package/scripts/lib/gates/gate-helpers.mjs +243 -21
  305. package/scripts/lib/gates/gate-incremental.mjs +28 -3
  306. package/scripts/lib/gates/gate-per-file.mjs +27 -2
  307. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +2 -1
  308. package/scripts/lib/gitlab-portfolio/cli.mjs +2 -1
  309. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +6 -1
  310. package/scripts/lib/instruction-budget-guard.mjs +332 -50
  311. package/scripts/lib/io.mjs +42 -8
  312. package/scripts/lib/is-main-module.mjs +82 -0
  313. package/scripts/lib/issue-close-strip-labels.mjs +207 -49
  314. package/scripts/lib/js-mask.mjs +197 -0
  315. package/scripts/lib/learnings/evolve-telemetry.mjs +11 -7
  316. package/scripts/lib/locks/index.mjs +32 -25
  317. package/scripts/lib/maintenance-due-banner.mjs +122 -91
  318. package/scripts/lib/orphan-reaper.mjs +1588 -0
  319. package/scripts/lib/peer-cards/merger.mjs +48 -10
  320. package/scripts/lib/peer-cards/reader.mjs +78 -2
  321. package/scripts/lib/peer-discovery.mjs +2 -5
  322. package/scripts/lib/playwright-driver/runner.mjs +2 -1
  323. package/scripts/lib/process-group.mjs +899 -0
  324. package/scripts/lib/quality-gate.mjs +107 -28
  325. package/scripts/lib/reconcile/backlog.mjs +368 -0
  326. package/scripts/lib/reconcile/engine.mjs +55 -188
  327. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +884 -0
  328. package/scripts/lib/reconcile/sanitize.mjs +69 -3
  329. package/scripts/lib/reconcile-nudge-banner.mjs +138 -45
  330. package/scripts/lib/resource-probe/parsers.mjs +31 -0
  331. package/scripts/lib/rule-loader.mjs +41 -12
  332. package/scripts/lib/rules-sync.mjs +2 -5
  333. package/scripts/lib/scope-echo.mjs +429 -7
  334. package/scripts/lib/scope-gate.mjs +605 -1
  335. package/scripts/lib/session-close-backfill.mjs +91 -12
  336. package/scripts/lib/session-id.mjs +9 -20
  337. package/scripts/lib/session-invocation.mjs +20 -0
  338. package/scripts/lib/session-schema/constants.mjs +30 -2
  339. package/scripts/lib/session-schema/normalizer.mjs +56 -4
  340. package/scripts/lib/session-schema.mjs +8 -3
  341. package/scripts/lib/session-start-probes.mjs +95 -10
  342. package/scripts/lib/sessions-canonical.mjs +23 -0
  343. package/scripts/lib/sessions-integrity-banner.mjs +7 -1
  344. package/scripts/lib/sessions-staleness-banner.mjs +193 -51
  345. package/scripts/lib/skill-evidence-window.mjs +891 -0
  346. package/scripts/lib/skill-evolution/candidate-intake.mjs +133 -12
  347. package/scripts/lib/skill-evolution/engine.mjs +18 -9
  348. package/scripts/lib/skill-judge.mjs +45 -3
  349. package/scripts/lib/state-md.mjs +84 -3
  350. package/scripts/lib/sunset/walker.mjs +31 -4
  351. package/scripts/lib/tail-window.mjs +56 -0
  352. package/scripts/lib/telemetry/schema.mjs +30 -0
  353. package/scripts/lib/telemetry/sync.mjs +61 -6
  354. package/scripts/lib/telemetry-flush-health-banner.mjs +4 -22
  355. package/scripts/lib/test-runner/issue-reconcile.mjs +48 -16
  356. package/scripts/lib/tests-src-ratio.mjs +2 -6
  357. package/scripts/lib/tmux-layout/telemetry-stats.mjs +74 -14
  358. package/scripts/lib/user-invocable-skills.mjs +205 -0
  359. package/scripts/lib/ux-grill/reconcile.mjs +48 -22
  360. package/scripts/lib/validate/check-agents-skills.mjs +26 -15
  361. package/scripts/lib/validate/check-banner-parity.mjs +2 -2
  362. package/scripts/lib/validate/check-cursor-adapter.mjs +3 -2
  363. package/scripts/lib/validate/check-dead-bridge.mjs +2 -2
  364. package/scripts/lib/validate/check-doc-cli-commands.mjs +2 -2
  365. package/scripts/lib/validate/check-entry-guard.mjs +329 -0
  366. package/scripts/lib/validate/check-guard-requires-parity.mjs +2 -2
  367. package/scripts/lib/validate/check-hook-entry-guards.mjs +636 -0
  368. package/scripts/lib/validate/check-hooks-emit-event-guard.mjs +2 -2
  369. package/scripts/lib/validate/check-learning-provenance.mjs +2 -2
  370. package/scripts/lib/validate/check-pi-prompts.mjs +1 -0
  371. package/scripts/lib/validate/check-rules.mjs +7 -5
  372. package/scripts/lib/validate/check-skill-links.mjs +35 -6
  373. package/scripts/lib/validate/check-skill-script-paths.mjs +241 -29
  374. package/scripts/lib/validate/check-test-git-config-target.mjs +26 -36
  375. package/scripts/lib/validate/check-unicode-safety.mjs +2 -2
  376. package/scripts/lib/validate/check-untracked-test-deps.mjs +9 -104
  377. package/scripts/lib/validate/check-unwired-features.mjs +220 -33
  378. package/scripts/lib/validate/check-validator-registration.mjs +36 -12
  379. package/scripts/lib/validate/check-vcs-repo-flag.mjs +2 -2
  380. package/scripts/lib/validate/confidential-names.mjs +10 -0
  381. package/scripts/lib/validate-vendored-rules.mjs +39 -12
  382. package/scripts/lib/vault-mirror/namespace.mjs +46 -8
  383. package/scripts/lib/vault-mirror/process.mjs +10 -3
  384. package/scripts/lib/vault-mirror/render-sessions.mjs +12 -2
  385. package/scripts/lib/vault-status/narrative-mirror.mjs +31 -7
  386. package/scripts/lib/vault-yaml.mjs +118 -0
  387. package/scripts/lib/wave-transcript-tail.mjs +2 -2
  388. package/scripts/lib/worktree/lifecycle.mjs +153 -1
  389. package/scripts/lock-reaper.mjs +2 -1
  390. package/scripts/materialize-wave-scope.mjs +87 -4
  391. package/scripts/migrate-sessions-jsonl.mjs +2 -1
  392. package/scripts/migrate-vault-paths.mjs +2 -3
  393. package/scripts/release-session-lock.mjs +305 -0
  394. package/scripts/release.mjs +109 -39
  395. package/scripts/relocate-vault-corpus.mjs +2 -3
  396. package/scripts/repair-invalid-sessions.mjs +2 -2
  397. package/scripts/resolve-session-invocation.mjs +59 -0
  398. package/scripts/run-quality-gate.mjs +156 -17
  399. package/scripts/session-shape.mjs +2 -2
  400. package/scripts/site-numbers.mjs +35 -11
  401. package/scripts/sweep-expired-rules.mjs +227 -0
  402. package/scripts/validate-plugin.mjs +21 -0
  403. package/scripts/validate-wave-scope.mjs +32 -105
  404. package/scripts/vault-consolidate.mjs +2 -2
  405. package/scripts/vault-mirror.mjs +11 -4
  406. package/scripts/wave-scope-binding.mjs +2 -3
  407. package/skills/_shared/bootstrap-gate.md +1 -1
  408. package/skills/_shared/monitor-patterns.md +1 -1
  409. package/skills/_shared/platform-tools.md +23 -11
  410. package/skills/_shared/research-evidence.md +53 -0
  411. package/skills/_shared/state-ownership.md +3 -0
  412. package/skills/autopilot/SKILL.md +80 -11
  413. package/skills/bootstrap/SKILL.md +51 -1
  414. package/skills/brainstorm/SKILL.md +16 -0
  415. package/skills/claude-md-drift-check/SKILL.md +1 -1
  416. package/skills/claude-md-drift-check/checker.mjs +49 -11
  417. package/{commands/close.md → skills/close/SKILL.md} +9 -3
  418. package/skills/convergence-monitoring/README.md +8 -1
  419. package/skills/convergence-monitoring/SIGNALS.md +50 -6
  420. package/skills/convergence-monitoring/SKILL.md +15 -6
  421. package/skills/debug/SKILL.md +10 -0
  422. package/skills/discovery/SKILL.md +24 -1
  423. package/skills/discovery/probes-session.md +2 -2
  424. package/skills/dispatcher/SKILL.md +38 -7
  425. package/skills/eli5/SKILL.md +11 -0
  426. package/skills/eval/SKILL.md +52 -23
  427. package/skills/eval/rubric-v1.md +1 -0
  428. package/skills/eval/rubric-v2.md +457 -0
  429. package/skills/evolve/SKILL.md +9 -2
  430. package/skills/evolve/references/evolve-dialectic-mode.md +46 -25
  431. package/skills/gitlab-ops/SKILL.md +3 -2
  432. package/{commands/go.md → skills/go/SKILL.md} +9 -1
  433. package/skills/grill/SKILL.md +19 -0
  434. package/{commands/harness-audit.md → skills/harness-audit/SKILL.md} +7 -2
  435. package/skills/hook-development/SKILL.md +46 -41
  436. package/skills/memory-cleanup/SKILL.md +7 -0
  437. package/skills/npm-publish/SKILL.md +2 -2
  438. package/skills/persona-panel/SKILL.md +56 -1
  439. package/skills/persona-panel/persona-format.md +1 -1
  440. package/skills/plan/SKILL.md +28 -1
  441. package/{commands/portfolio.md → skills/portfolio/SKILL.md} +8 -2
  442. package/skills/reconcile/SKILL.md +21 -0
  443. package/{commands/release.md → skills/release/SKILL.md} +16 -2
  444. package/skills/repo-audit/SKILL.md +7 -0
  445. package/skills/session-end/SKILL.md +13 -16
  446. package/skills/session-end/discovery-scan.md +1 -1
  447. package/skills/session-end/phase-3-6-tail.md +55 -9
  448. package/skills/session-end/plan-verification.md +2 -2
  449. package/skills/session-end/references/phase-5-issue-cleanup.md +9 -14
  450. package/skills/session-end/session-metrics-write.md +10 -0
  451. package/skills/session-plan/SKILL.md +18 -6
  452. package/skills/session-plan/references/session-plan-task-classification.md +2 -2
  453. package/skills/session-start/SKILL.md +5 -4
  454. package/skills/session-start/phase-8-5-express-path.md +6 -6
  455. package/skills/session-start/references/phase-1-5-session-continuity.md +1 -1
  456. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +1 -1
  457. package/skills/session-start/references/phase-4-ssot-environment-check.md +6 -4
  458. package/skills/spinout/SKILL.md +12 -1
  459. package/skills/sunset-review/SKILL.md +13 -0
  460. package/{commands/test.md → skills/test/SKILL.md} +10 -4
  461. package/skills/ux-grill/SKILL.md +20 -2
  462. package/skills/wave-executor/SKILL.md +14 -7
  463. package/skills/wave-executor/circuit-breaker.md +2 -0
  464. package/skills/wave-executor/references/wave-executor-state-init.md +18 -4
  465. package/skills/wave-executor/references/wave-loop-dispatch.md +5 -2
  466. package/skills/wave-executor/references/wave-loop-review.md +17 -1
  467. package/commands/autopilot.md +0 -80
  468. package/commands/bootstrap.md +0 -56
  469. package/commands/brainstorm.md +0 -48
  470. package/commands/debug.md +0 -36
  471. package/commands/discovery.md +0 -32
  472. package/commands/dispatcher.md +0 -59
  473. package/commands/eli5.md +0 -33
  474. package/commands/eval.md +0 -28
  475. package/commands/evolve.md +0 -10
  476. package/commands/grill.md +0 -45
  477. package/commands/memory-cleanup.md +0 -26
  478. package/commands/persona-panel.md +0 -121
  479. package/commands/plan.md +0 -15
  480. package/commands/reconcile.md +0 -23
  481. package/commands/repo-audit.md +0 -24
  482. package/commands/spinout.md +0 -15
  483. package/commands/sunset-review.md +0 -27
  484. package/commands/ux-grill.md +0 -51
@@ -177,6 +177,16 @@ export function inspectConfidentialNames({ namesPath, deps = {} } = {}) {
177
177
  // Unconfigured → no list, no noise. This is the normal case for public repos
178
178
  // and for any host that has not opted into confidential-name scanning.
179
179
  if (typeof namesPath !== 'string' || namesPath.trim() === '') {
180
+ // UNREACHABLE from the one production caller today (#1273 P1):
181
+ // `check-owner-leakage.mjs` resolves `namesPath` via `resolveHostPath()`
182
+ // and returns its own early `disabledReason` on an empty/non-string path
183
+ // BEFORE ever calling `inspectConfidentialNames` — so in production this
184
+ // branch never runs. It is reachable only for a direct caller (or a test)
185
+ // that skips that pre-filter. Kept regardless: `status` is part of the
186
+ // documented public contract every deep importer may switch on, and
187
+ // 'unconfigured' is the correct answer for exactly this input shape.
188
+ // REVISIT TRIGGER (BV-004): the production caller stops pre-filtering an
189
+ // empty/non-string `namesPath` before calling `inspectConfidentialNames`.
180
190
  return { status: 'unconfigured', names: [] };
181
191
  }
182
192
 
@@ -6,9 +6,12 @@
6
6
  * Catches vendoring bugs that are invisible at the source-of-truth layer but
7
7
  * become live footguns once synced into a target repo:
8
8
  *
9
- * - `paths:` frontmatter `rule-loader.mjs` only understands `globs:`; a
10
- * `paths:` key is silently ignored, so the rule loads as always-on
11
- * instead of the intended glob-scoped subset.
9
+ * - `paths:` frontmatter in a `rules/` LIBRARY SOURCE a vendoring-
10
+ * CONVENTION gate, not a loader-compatibility one. Since #795
11
+ * `rule-loader.mjs` accepts `paths:` as an alias for `globs:`, so such a
12
+ * rule IS glob-scoped; `globs:` stays the canonical form for a rule
13
+ * vendored out through the library (#742). See § Scope below — this probe
14
+ * never judges a repo's own consolidated `.claude/rules/` files.
12
15
  * - Missing provenance header — `rules-sync.mjs` detects "plugin-owned vs.
13
16
  * local override" purely by checking whether the first line starts with
14
17
  * `PLUGIN_HEADER_PREFIX`. A source file missing that header gets
@@ -31,6 +34,25 @@
31
34
  * module imports the already-exported `parseGlobsFrontmatter` from
32
35
  * `rule-loader.mjs` directly.
33
36
  *
37
+ * ## Scope: the `rules/` fleet library, never a repo's own `.claude/rules/`
38
+ *
39
+ * Every production caller feeds this module SOURCE files from the plugin's
40
+ * fleet library: `syncRules()` (`rules-sync.mjs`) validates
41
+ * `<pluginRoot>/rules/<relPath>` for each entry listed in `rules/_index.md`
42
+ * BEFORE writing the vendored copy into a consumer's `.claude/rules/` — the
43
+ * written target is never read back through the gate. The CLI's `--dir` is
44
+ * operator-supplied and means that same library.
45
+ *
46
+ * This is load-bearing for `paths-frontmatter`: a repo's own CONSOLIDATED rules
47
+ * under `.claude/rules/` are `paths:`-canonical by design, because Claude
48
+ * Code's native loader reads ONLY `paths:` and treats a rule without it as
49
+ * always-on (`validate/check-rules.mjs` #1108; `docs/rule-authoring.md`
50
+ * § Consolidated rules point 3). Measured 2026-09-16 in this repo: 10
51
+ * path-scoped rule files there, 9 of them `paths:`-only. Pointing `--dir` at
52
+ * such a tree would emit findings that, if obeyed, UNDO that consolidation —
53
+ * which is also why `check-rules.mjs` deliberately does not duplicate this
54
+ * probe for `.claude/rules/`.
55
+ *
34
56
  * @module validate-vendored-rules
35
57
  */
36
58
 
@@ -40,6 +62,7 @@ import { fileURLToPath } from 'node:url';
40
62
  import { execFileSync } from 'node:child_process';
41
63
  import { createRequire } from 'node:module';
42
64
  import { parseGlobsFrontmatter } from './rule-loader.mjs';
65
+ import { isMainModule } from './is-main-module.mjs';
43
66
 
44
67
  // Mirrors rules-sync.mjs's exported PLUGIN_HEADER_PREFIX (rules-sync.mjs
45
68
  // line ~13). NOT imported from there on purpose: rules-sync.mjs imports
@@ -478,10 +501,17 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
478
501
  // `paths:`-only rule IS glob-scoped. The probe itself stays: it enforces the canonical
479
502
  // vendoring form, which is a convention gate, not a loader-compatibility gate. That
480
503
  // intent survives #795 and is the subject of the #742 fleet canonicalisation sweep.
504
+ // SCOPE (2026-09-16): this fires only over `rules/` library sources — the module doc's
505
+ // § Scope section names every caller. Do NOT re-point it at a repo's own
506
+ // `.claude/rules/`: those consolidated files are `paths:`-canonical and obeying this
507
+ // remedy there would undo the consolidation (docs/rule-authoring.md point 3).
481
508
  message:
482
509
  `${relPath}: frontmatter declares a top-level 'paths:' key. It is a recognized alias ` +
483
510
  `for 'globs:' (issue #795), so the rule does load glob-scoped — but 'globs:' is the ` +
484
- `canonical form for vendored rules. Migrate to 'globs:' (see issue #742).`,
511
+ `canonical form for a rule VENDORED OUT through the plugin's rules/ library, which is ` +
512
+ `this probe's only population. Migrate to 'globs:' (see issue #742). This does NOT ` +
513
+ `apply to a repo's own consolidated .claude/rules/ files — those are paths:-canonical ` +
514
+ `(Claude Code's native loader reads only 'paths:') and this gate never scans them.`,
485
515
  line: lineWithinFrontmatter(fm.startLine, fm.body, pathsMatch.index),
486
516
  });
487
517
  }
@@ -587,8 +617,9 @@ export function validateRuleContent({ content, relPath, targetRoot = null, requi
587
617
  }
588
618
 
589
619
  /**
590
- * Validates every `*.md` rule file under `dir`, recursively (skips dotfiles
591
- * and `_index.md`).
620
+ * Validates every `*.md` rule file under `dir`, recursively (skips dotfiles,
621
+ * `_index.md` and `README.md` — neither is a vendored rule: `rules-sync.mjs`
622
+ * vendors only the entries listed in `_index.md`, #1164).
592
623
  *
593
624
  * When `pluginRoot` is given, every scanned file is additionally passed through
594
625
  * `scanVendoringLeaks()` and the findings are collected into the additive
@@ -612,7 +643,7 @@ export function validateRulesDir({ dir, targetRoot = null, requireProvenance = f
612
643
  const absPath = join(absDir, entry.name);
613
644
  if (entry.isDirectory()) {
614
645
  files.push(...collectRuleFiles(absPath));
615
- } else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== '_index.md') {
646
+ } else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== '_index.md' && entry.name !== 'README.md') {
616
647
  files.push(relative(dir, absPath).replace(/\\/g, '/'));
617
648
  }
618
649
  }
@@ -652,11 +683,7 @@ export function validateRulesDir({ dir, targetRoot = null, requireProvenance = f
652
683
 
653
684
  const __filename = fileURLToPath(import.meta.url);
654
685
 
655
- const isMain =
656
- typeof process !== 'undefined' &&
657
- process.argv[1] !== null &&
658
- process.argv[1] !== undefined &&
659
- resolve(process.argv[1]) === resolve(__filename);
686
+ const isMain =isMainModule(import.meta.url);
660
687
 
661
688
  /**
662
689
  * @param {{ ok: boolean, files: Array<{ file: string, violations: RuleViolation[] }>, errorCount: number, warnCount: number }} result
@@ -5,10 +5,18 @@
5
5
  * vault writes under `40-learnings/<repoNs>/` and `50-sessions/<repoNs>/`.
6
6
  *
7
7
  * Contract:
8
- * resolveRepoNamespace({ vaultName?, cwd? }) → string
8
+ * resolveRepoNamespace({ vaultName?, repoRoot? }) → string
9
9
  *
10
- * - Pure + deterministic (given the same git remote / cwd / vaultName input,
11
- * and the same host-local pseudonym map).
10
+ * - Pure + deterministic (given the same vaultName, the same `.vault.yaml`
11
+ * under `repoRoot`, the same git remote, and the same host-local pseudonym
12
+ * map).
13
+ * - `repoRoot` (#1389) selects the directory whose `.vault.yaml` is read;
14
+ * it defaults to `process.cwd()`. Named limit: it scopes ONLY that lookup —
15
+ * the {@link deriveRepo} fallback still resolves from the process cwd and
16
+ * is cached per process, so a `repoRoot` that differs from the cwd AND has
17
+ * no declared slug falls back to the cwd's git identity. Every production
18
+ * caller passes its own cwd today; revisit if a caller ever mirrors a repo
19
+ * other than the one it runs in.
12
20
  * - Returns a lowercase kebab slug safe for use as a filesystem path segment.
13
21
  * - Host-local pseudonym mapping (Epic #725 D5): consulted ONLY at the redaction
14
22
  * site (only when a segment is owner-leaky). If such a repo (raw or sanitised)
@@ -47,6 +55,7 @@ import { isOwnerLeakySegment } from '../../lib/validate/check-owner-leakage.mjs'
47
55
  import { loadPseudonymMap } from './pseudonym-map.mjs';
48
56
  import { loadHostPaths, resolveHostPath } from '../config/host-paths.mjs';
49
57
  import { isQueryFailure, resolvePreferredRemote } from '../vcs-repo-spec.mjs';
58
+ import { readVaultSlug } from '../vault-yaml.mjs';
50
59
 
51
60
  // ── Lazy pseudonym-map path resolution (Epic #725 D5) ────────────────────────
52
61
  // The map path comes from env SO_NAMESPACE_MAP > owner.yaml paths.namespace-map-path
@@ -237,19 +246,48 @@ function lookupPseudonym(base, seg) {
237
246
  *
238
247
  * @param {object} [opts]
239
248
  * @param {string|null} [opts.vaultName] - Optional override for the repo identifier.
240
- * When non-empty and non-whitespace, used in place of the git-derived repo name.
241
- * When absent, the namespace is derived from the git origin via deriveRepo().
249
+ * When non-empty and non-whitespace, used in place of the declared/git-derived
250
+ * repo name. When absent, the base is the repo's declared `.vault.yaml`
251
+ * `metadata.slug` (#1131), else the git origin via deriveRepo().
252
+ * @param {string|null} [opts.repoRoot] - Directory whose `.vault.yaml` supplies
253
+ * the declared slug (#1389). `undefined`/`null` → `process.cwd()`. Does NOT
254
+ * redirect the deriveRepo() fallback (see the module header's named limit).
242
255
  * @returns {string} A single kebab-slug path segment, e.g. 'session-orchestrator'.
243
256
  * Special returns:
244
257
  * - 'unknown-repo' — slug derivation produced an empty string.
245
258
  * - 'redacted-repo' — the raw or slugified value matched an owner-leakage pattern
246
259
  * (CP1 personal home path / CP6 private slug / CP10 personal name in Projects path).
247
260
  */
248
- export function resolveRepoNamespace({ vaultName = null } = {}) {
249
- // Choose the base identifier: explicit override first, then git-derived.
261
+ export function resolveRepoNamespace({ vaultName = null, repoRoot = null } = {}) {
262
+ // Choose the base identifier. Precedence: explicit `vaultName` override >
263
+ // the repo's declared `.vault.yaml` `metadata.slug` (#1131) > git-derived.
264
+ //
265
+ // WHY the slug sits HERE and not in deriveRepo(): deriveRepo() is the RAW
266
+ // repo IDENTITY (`org/repo`) and has consumers that want exactly that and
267
+ // nothing else — `vault-relocation-rules.mjs`, `relocate-vault-corpus.mjs`,
268
+ // `named-vault-resolver.mjs` (which documents the `org/repo-name` format).
269
+ // #1131 concerns the vault FOLDER namespace, which is what THIS function
270
+ // decides, so the lookup belongs at this candidate-selection point.
271
+ //
272
+ // Note what this placement does NOT buy: the `source-repo:` frontmatter is
273
+ // this function's output, not deriveRepo()'s (#732; process.mjs deliberately
274
+ // shares ONE value between the write path and the rendered field), so a repo
275
+ // whose declared slug differs from its derived name gets a new `source-repo`
276
+ // either way. That is inherent to moving the folder, not a side effect of
277
+ // where the lookup lives.
278
+ //
279
+ // It sits BELOW `vaultName` (an explicit per-call/operator override must stay
280
+ // honourable) and ABOVE derivation (a declared registration beats a guess from
281
+ // the remote URL or the checkout directory: the remote answers "where does
282
+ // this code live?", the slug answers "which vault project IS this?", and a
283
+ // host-side rename legitimately diverges the two). It sits ABOVE the leak
284
+ // guard below deliberately — a declared slug is operator data flowing into a
285
+ // written path and is checked by CP1/CP6/CP10 and the pseudonym map exactly
286
+ // like any git-derived identifier.
287
+ const declaredSlug = readVaultSlug(repoRoot ?? process.cwd());
250
288
  const base = (vaultName && typeof vaultName === 'string' && vaultName.trim())
251
289
  ? vaultName.trim()
252
- : deriveRepo();
290
+ : (declaredSlug ?? deriveRepo());
253
291
 
254
292
  // Sanitise: collapse to last path segment, lowercase, strip non-[a-z0-9-].
255
293
  const seg = subjectToSlug(base);
@@ -666,8 +666,12 @@ export async function processLearning(rawEntry, _lineNum, ctx) {
666
666
  });
667
667
  }
668
668
 
669
- // #660: namespace new writes under a per-repo subdirectory.
670
- const repoNs = resolveRepoNamespace({ vaultName: ctx?.vaultName ?? null });
669
+ // #660: namespace new writes under a per-repo subdirectory. #1389: `ctx.repoRoot`
670
+ // is optional — absent, resolveRepoNamespace falls back to process.cwd().
671
+ const repoNs = resolveRepoNamespace({
672
+ vaultName: ctx?.vaultName ?? null,
673
+ repoRoot: ctx?.repoRoot ?? null,
674
+ });
671
675
  // #725 D2: thread the resolved repo namespace into the learning frontmatter as
672
676
  // `source-repo` for cross-repo attribution. repoNs is already sanitised +
673
677
  // leak-guarded by resolveRepoNamespace, so it is safe to interpolate as-is. The
@@ -898,7 +902,10 @@ export async function processSession(rawEntry, _lineNum, ctx) {
898
902
  // owner-leaky repo's real name reached the vault through the session-note
899
903
  // frontmatter even though the directory AND the learning `source-repo` field
900
904
  // were already pseudonym-mapped/redacted (#732 leak-guard bypass).
901
- const repoNs = resolveRepoNamespace({ vaultName: ctx?.vaultName ?? null });
905
+ const repoNs = resolveRepoNamespace({
906
+ vaultName: ctx?.vaultName ?? null,
907
+ repoRoot: ctx?.repoRoot ?? null,
908
+ });
902
909
 
903
910
  // Quality gate (PRD F1.2): skip sessions whose rendered narrative is too short.
904
911
  // Measure on the rendered markdown body so the check is schema-agnostic across
@@ -476,14 +476,24 @@ export function generateSessionNote(entry, options = {}) {
476
476
  // #1276: lifecycle-only records omit every older count alias. Started
477
477
  // measures participation; completed/planned-only counts retain their
478
478
  // labels so a plan or a completion count never claims dispatch coverage.
479
- const completedAgents = waveCount(w.agent_count_completed);
480
- const plannedAgents = waveCount(w.agent_count_planned);
479
+ // The `agents_*` triple is the same lifecycle measurement under the
480
+ // de-underscored producer spelling: `metrics-collection.md` documents
481
+ // `agent_count_{planned,started,completed}`, but the live ledger carries
482
+ // one session (`main-2026-09-18-session-1`, 5 waves, measured 2026-09-18
483
+ // over `.orchestrator/metrics/sessions.jsonl`) whose waves emit only
484
+ // `agents_planned` / `agents_started` / `agents_completed` — every one of
485
+ // its 15 counts rendered `?` beside a real number, exactly the #1074 bug
486
+ // class. Each alias sits at the precedence of its documented twin, so no
487
+ // cell that already rendered a value can change.
488
+ const completedAgents = waveCount(w.agent_count_completed) ?? waveCount(w.agents_completed);
489
+ const plannedAgents = waveCount(w.agent_count_planned) ?? waveCount(w.agents_planned);
481
490
  const agentsCell =
482
491
  waveCount(w.agent_count) ??
483
492
  waveCount(w.agents) ??
484
493
  waveCount(w.agents_dispatched) ??
485
494
  waveCount(w.dispatched) ??
486
495
  waveCount(w.agent_count_started) ??
496
+ waveCount(w.agents_started) ??
487
497
  (completedAgents === undefined ? undefined : `${completedAgents} completed`) ??
488
498
  (plannedAgents === undefined ? MISSING_CELL : `${plannedAgents} planned`);
489
499
  const filesCell = waveCount(w.files_changed) ?? waveCount(w.files) ?? MISSING_CELL;
@@ -43,6 +43,7 @@ import { readConfigFile, parseSessionConfig } from '../config.mjs';
43
43
  import { validatePathInsideProject } from '../path-utils.mjs';
44
44
  import { createSecretValueMasker } from '../secret-masker.mjs';
45
45
  import { expandTilde } from '../common.mjs';
46
+ import { readVaultSlug } from '../vault-yaml.mjs';
46
47
 
47
48
  /** Frontmatter sentinel that identifies generator-owned narrative files. */
48
49
  export const GENERATOR_MARKER = 'session-orchestrator-vault-status-narrative@1';
@@ -869,15 +870,29 @@ async function runNarrativeMirror(opts) {
869
870
 
870
871
  // Defense-in-depth: when the caller omits (or passes an empty) `repo`, derive
871
872
  // it from the operator-configured `vault-name` override (#660/#832) when set,
872
- // else the repoRoot basename never silently mis-file under 'unknown' (#675
873
- // review). Precedence: explicit `repo` opt > `vault-name` > basename.
873
+ // else the `.vault.yaml` slug (#1131), else the repoRoot basename never
874
+ // silently mis-file under 'unknown' (#675 review).
874
875
  const vaultNameOverride =
875
876
  typeof vaultIntegration['vault-name'] === 'string' && vaultIntegration['vault-name'].trim()
876
877
  ? vaultIntegration['vault-name'].trim()
877
878
  : null;
878
- const repoName = (typeof repo === 'string' && repo.trim().length > 0)
879
+ const explicitName = (typeof repo === 'string' && repo.trim().length > 0)
879
880
  ? repo
880
- : vaultNameOverride ?? path.basename(path.resolve(repoRoot));
881
+ : vaultNameOverride;
882
+
883
+ // #1131: with NO explicit override, the repo's own `.vault.yaml`
884
+ // `metadata.slug` — the CANONICAL vault registration — outranks the
885
+ // directory basename. Precedence: explicit `repo` opt > `vault-name` >
886
+ // `.vault.yaml` slug > basename.
887
+ //
888
+ // WHY the two overrides still win: both are things a CALLER/operator states
889
+ // for THIS call (`repo` is only ever passed to override the derived name —
890
+ // the sole production caller, `skills/session-end/session-metrics-write.md`,
891
+ // passes `repoRoot` alone), so honouring the file over them would make an
892
+ // explicit override unhonourable. The file wins over the basename, which is
893
+ // a guess nobody stated.
894
+ const vaultYamlSlug = explicitName ? null : readVaultSlug(repoRoot);
895
+ const repoName = explicitName ?? vaultYamlSlug ?? path.basename(path.resolve(repoRoot));
881
896
 
882
897
  const rawVaultDir = vaultIntegration['vault-dir'];
883
898
  if (!rawVaultDir || typeof rawVaultDir !== 'string') {
@@ -886,10 +901,19 @@ async function runNarrativeMirror(opts) {
886
901
 
887
902
  const vaultDir = path.resolve(expandTilde(rawVaultDir));
888
903
  const candidateSlug = subjectToSlug(repoName) || 'unknown';
889
- // Loose-match against existing 01-projects/ folders before minting a new
890
- // slug (issue #829 Finding 3) see resolveLooseSlug for the ambiguity
904
+ // A declared `.vault.yaml` slug needs no healing it IS the canonical folder
905
+ // name, so resolveLooseSlug is SKIPPED for it. Running the healer over it
906
+ // would re-open the bug this fixes: a vault that also carries an unhealed
907
+ // legacy folder (e.g. `foobarapp` beside the declared `foo-bar-app`) makes
908
+ // the loose match ambiguous, and ambiguity falls back to the candidate —
909
+ // which is how a duplicate pair, once created, keeps being written to.
910
+ //
911
+ // Otherwise: loose-match against existing 01-projects/ folders before minting
912
+ // a new slug (issue #829 Finding 3) — see resolveLooseSlug for the ambiguity
891
913
  // rules. Falls through to `candidateSlug` unchanged on any read failure.
892
- const repoSlug = resolveLooseSlug(vaultDir, candidateSlug, { readdirSync: injectedFs?.readdirSync });
914
+ const repoSlug = vaultYamlSlug
915
+ ? candidateSlug
916
+ : resolveLooseSlug(vaultDir, candidateSlug, { readdirSync: injectedFs?.readdirSync });
893
917
  const outputPath = resolveNarrativePath(vaultDir, repoSlug);
894
918
 
895
919
  // Defense-in-depth: ensure the resolved file stays inside the vault root.
@@ -0,0 +1,118 @@
1
+ /**
2
+ * vault-yaml.mjs — Reader for a repo's `.vault.yaml` registration slug (#1131).
3
+ *
4
+ * A repo registered in the Meta-Vault carries `.vault.yaml` at its root, whose
5
+ * `metadata.slug` is the CANONICAL vault slug for that project — the name of
6
+ * its `01-projects/<slug>/` folder. Until #1131 nothing READ that key: every
7
+ * vault writer derived the slug from the checkout DIRECTORY name instead
8
+ * (`scripts/lib/vault-status/narrative-mirror.mjs` via `subjectToSlug`,
9
+ * `scripts/lib/vault-mirror/namespace.mjs` via `basename(process.cwd())`), so a
10
+ * repo whose directory name differs lexically from its registered slug had its
11
+ * narrative and its learnings/sessions filed under a second, wrong folder — and
12
+ * the loose-slug healer in narrative-mirror (#829) only repairs case and
13
+ * punctuation drift, never a genuinely different name.
14
+ *
15
+ * Exports:
16
+ * VAULT_YAML_FILE — the basename this module reads
17
+ * readVaultSlug — repoRoot → validated `metadata.slug` string, or null
18
+ *
19
+ * Contract: NEVER throws. Absent file, unreadable file, malformed YAML, missing
20
+ * key, non-string value, or a value that fails the repo's slug predicate all
21
+ * return `null`, leaving every caller on its pre-#1131 derivation chain.
22
+ *
23
+ * Import closure is deliberately tiny (`node:fs`, `node:path`, `node:module`,
24
+ * `./vault-mirror/utils.mjs`) — `namespace.mjs` is documented as a leaf-ward
25
+ * module and must stay one.
26
+ */
27
+
28
+ import { readFileSync } from 'node:fs';
29
+ import path from 'node:path';
30
+ import { createRequire } from 'node:module';
31
+
32
+ import { isValidSlug } from './vault-mirror/utils.mjs';
33
+
34
+ /** Basename of the per-repo vault registration file. */
35
+ export const VAULT_YAML_FILE = '.vault.yaml';
36
+
37
+ // ── Lazy js-yaml resolution ──────────────────────────────────────────────────
38
+ //
39
+ // Same shape (and same reason) as `getYaml()` in scripts/lib/owner-yaml.mjs:
40
+ // a static `import yaml from 'js-yaml'` crashes any consumer running without
41
+ // `node_modules`, at MODULE LOAD, where the failure is not catchable. Both
42
+ // consumers of this reader are synchronous, so `createRequire` — not
43
+ // `await import()` — is the resolution mechanism.
44
+
45
+ /** @type {null | false | { load: Function }} */
46
+ let _yaml = null;
47
+ let _yamlWarned = false;
48
+
49
+ function getYaml() {
50
+ if (_yaml !== null) return _yaml === false ? null : _yaml;
51
+ try {
52
+ _yaml = createRequire(import.meta.url)('js-yaml');
53
+ } catch {
54
+ _yaml = false;
55
+ }
56
+ return _yaml === false ? null : _yaml;
57
+ }
58
+
59
+ /**
60
+ * One-per-process actionable WARN, emitted ONLY when a `.vault.yaml` actually
61
+ * exists and could therefore have changed the resolved slug. A repo without the
62
+ * file loses nothing by the parser being absent, so it stays silent.
63
+ */
64
+ function warnYamlMissing() {
65
+ if (_yamlWarned) return;
66
+ _yamlWarned = true;
67
+ process.stderr.write(
68
+ "WARN vault-yaml: 'js-yaml' is not installed — .vault.yaml cannot be parsed; " +
69
+ 'the vault slug falls back to the directory-derived name (run \'npm install\' in the plugin directory)\n',
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Read the canonical vault slug declared at `metadata.slug` in
75
+ * `<repoRoot>/.vault.yaml`.
76
+ *
77
+ * The value becomes a FILESYSTEM PATH SEGMENT under the operator's vault, so it
78
+ * is validated with the repo's existing slug predicate ({@link isValidSlug},
79
+ * `/^[a-z0-9]+(?:-[a-z0-9]+)*$/`) rather than trusted. That rejects `/`, `..`,
80
+ * leading dots, whitespace and uppercase — this validation is a trust-boundary
81
+ * control, not hygiene: `.vault.yaml` is repo-supplied data, and a slug such as
82
+ * `../../etc` would otherwise escape the vault root.
83
+ *
84
+ * @param {string} repoRoot - absolute or relative path to the repository root.
85
+ * @returns {string|null} the validated slug, or `null` when it cannot be read.
86
+ */
87
+ export function readVaultSlug(repoRoot) {
88
+ if (typeof repoRoot !== 'string' || repoRoot.trim() === '') return null;
89
+
90
+ let text;
91
+ try {
92
+ text = readFileSync(path.join(repoRoot, VAULT_YAML_FILE), 'utf8');
93
+ } catch {
94
+ // Absent, unreadable, a directory, … — all "no declared slug".
95
+ return null;
96
+ }
97
+
98
+ const yaml = getYaml();
99
+ if (!yaml) {
100
+ warnYamlMissing();
101
+ return null;
102
+ }
103
+
104
+ let doc;
105
+ try {
106
+ doc = yaml.load(text);
107
+ } catch {
108
+ // Malformed YAML must never break a vault write.
109
+ return null;
110
+ }
111
+
112
+ if (!doc || typeof doc !== 'object') return null;
113
+ const metadata = doc.metadata;
114
+ if (!metadata || typeof metadata !== 'object') return null;
115
+
116
+ const slug = metadata.slug;
117
+ return isValidSlug(slug) ? slug : null;
118
+ }
@@ -55,12 +55,12 @@ import {
55
55
  } from 'node:fs';
56
56
  import { homedir } from 'node:os';
57
57
  import { join, relative, isAbsolute } from 'node:path';
58
- import { pathToFileURL } from 'node:url';
59
58
 
60
59
  import { emitEvent, sessionAttribution } from './events.mjs';
61
60
  import { tryAcquireFileLock, releaseFileLock } from './file-lock.mjs';
62
61
  import { readLock, isLockLive } from './session-lock.mjs';
63
62
  import { resolveSubagentSidecar } from '../../hooks/_lib/subagent-paths.mjs';
63
+ import { isMainModule } from './is-main-module.mjs';
64
64
 
65
65
  const DEFAULT_INTERVAL_S = 2;
66
66
  const EVENTS_FILE_REL = '.orchestrator/metrics/events.jsonl';
@@ -998,6 +998,6 @@ function main() {
998
998
 
999
999
  // Run only when executed as a script — importing for unit tests must not parse
1000
1000
  // vitest's argv and exit 1.
1001
- if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) {
1001
+ if (isMainModule(import.meta.url)) {
1002
1002
  main();
1003
1003
  }
@@ -46,6 +46,15 @@ async function _exists(p) {
46
46
  }
47
47
  }
48
48
 
49
+ /**
50
+ * The one directory session-orchestrator worktrees may live in.
51
+ * Host-shared: several sessions run concurrently against the same base.
52
+ * @returns {string}
53
+ */
54
+ function _worktreeBaseDir() {
55
+ return path.join(os.tmpdir(), 'so-worktrees');
56
+ }
57
+
49
58
  /**
50
59
  * Compute the standard branch name and tmp path for a given suffix.
51
60
  * @param {string} suffix
@@ -53,10 +62,146 @@ async function _exists(p) {
53
62
  */
54
63
  function _worktreeInfo(suffix) {
55
64
  const branch = `so-worktree-${suffix}`;
56
- const wtPath = path.join(os.tmpdir(), 'so-worktrees', branch);
65
+ const wtPath = path.join(_worktreeBaseDir(), branch);
57
66
  return { branch, wtPath };
58
67
  }
59
68
 
69
+ /**
70
+ * Resolve symlinks, returning null when the path does not exist.
71
+ * @param {string} p
72
+ * @returns {Promise<string|null>}
73
+ */
74
+ async function _realpathOrNull(p) {
75
+ try {
76
+ return await fs.realpath(p);
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ /**
83
+ * True when `wtPath` is NOT a live working tree of any repository — i.e. either
84
+ * it carries no `.git` entry at all, or its `.git` file points at an
85
+ * administrative directory that no longer exists (the registration was dropped).
86
+ *
87
+ * A directory whose registration is intact is never an orphan: `git worktree
88
+ * remove --force` handles that case, so this predicate is what keeps a live
89
+ * worktree — including one owned by a PEER session, or by another repository —
90
+ * out of the delete path below.
91
+ *
92
+ * @param {string} wtPath
93
+ * @returns {Promise<boolean>}
94
+ */
95
+ async function _isUnregisteredWorktreeDir(wtPath) {
96
+ const dotGit = path.join(wtPath, '.git');
97
+ let st;
98
+ try {
99
+ st = await fs.lstat(dotGit);
100
+ } catch {
101
+ return true; // No .git entry — not a working tree of anything.
102
+ }
103
+ // A real .git DIRECTORY means a full repository lives here; never ours to delete.
104
+ if (!st.isFile()) return false;
105
+
106
+ const content = await fs.readFile(dotGit, 'utf8').catch(() => '');
107
+ const match = /^gitdir:\s*(.+)$/m.exec(content);
108
+ if (!match) return false; // Unparseable — treat as live, refuse.
109
+
110
+ const adminDir = path.resolve(wtPath, match[1].trim());
111
+ // Admin dir still present → git knows this worktree → not an orphan.
112
+ return !(await _exists(adminDir));
113
+ }
114
+
115
+ /**
116
+ * Remove an ORPHAN worktree directory (issue #984).
117
+ *
118
+ * An interrupted `removeWorktree` drops the git registration first and can die
119
+ * before the directory is gone. `git worktree add` then fails with
120
+ * "'…' already exists" while `git worktree remove --force` fails with
121
+ * "'…' is not a working tree" (swallowed by nothrow) — the retry loops on the
122
+ * same error until someone deletes the directory by hand.
123
+ *
124
+ * SAFETY — this is a recursive delete inside library code, so every condition
125
+ * below is asserted in code before `fs.rm` runs. Any failed condition returns
126
+ * false and leaves the directory alone (the retry then fails with git's own
127
+ * error, which is the pre-#984 behaviour):
128
+ * 1. `wtPath` itself is a real directory and NOT a symlink — the LEAF is never
129
+ * followed. (A symlink one level up is covered by condition 2.)
130
+ * 2. The literal base `<os.tmpdir()>/so-worktrees` is itself a real directory
131
+ * and NOT a symlink. The base lives on a host-shared /tmp where a co-tenant
132
+ * can pre-create it; without this check the realpath of a planted base
133
+ * symlink BECOMES the delete root, so a `so-worktree-*` directory anywhere
134
+ * on the filesystem would qualify.
135
+ * 3. The REALPATH of `wtPath` is a DIRECT child of the REALPATH of that base
136
+ * — both sides resolved, because macOS resolves /tmp → /private/tmp and
137
+ * os.tmpdir() itself may be a symlinked path.
138
+ * 4. The resolved basename starts with `so-worktree-`.
139
+ * 5. It is not a live working tree of ANY repository (see above).
140
+ * Note what is NOT asserted: an intermediate component of `wtPath` other than
141
+ * the base cannot exist — the path is `<base>/so-worktree-<suffix>`, computed by
142
+ * `_worktreeInfo`, never caller-supplied.
143
+ * `git worktree prune` is deliberately NOT used: it is repo-global and would
144
+ * drop a peer session's stale registration.
145
+ *
146
+ * CEILING (BV-004): the delete is bounded to one directory directly under the
147
+ * fixed base `<os.tmpdir()>/so-worktrees` whose name carries the `so-worktree-`
148
+ * prefix. Revisit when worktree paths become caller-supplied (the
149
+ * `validateWorkspacePath` follow-up noted above) or move out of os.tmpdir() —
150
+ * at that point the containment base is no longer a constant and must be passed
151
+ * in and validated by the caller.
152
+ *
153
+ * @param {string} wtPath
154
+ * @returns {Promise<boolean>} true when a directory was actually removed.
155
+ */
156
+ async function _removeOrphanWorktreeDir(wtPath) {
157
+ let lst;
158
+ try {
159
+ lst = await fs.lstat(wtPath);
160
+ } catch {
161
+ return false; // Nothing there.
162
+ }
163
+ if (lst.isSymbolicLink() || !lst.isDirectory()) return false;
164
+
165
+ // The containment BASE must be a real directory too: on a host-shared /tmp a
166
+ // co-tenant can pre-create `<os.tmpdir()>/so-worktrees` as a symlink, and a
167
+ // realpath-resolved base would then relocate the delete root off-tmpdir.
168
+ const baseDir = _worktreeBaseDir();
169
+ let baseLst;
170
+ try {
171
+ baseLst = await fs.lstat(baseDir);
172
+ } catch {
173
+ return false; // No base directory — nothing under it to remove.
174
+ }
175
+ if (baseLst.isSymbolicLink() || !baseLst.isDirectory()) return false;
176
+
177
+ const realWt = await _realpathOrNull(wtPath);
178
+ const realBase = await _realpathOrNull(baseDir);
179
+ if (!realWt || !realBase) return false;
180
+ if (!isPathInside(realWt, realBase)) return false;
181
+ // Direct child only — matches the CEILING below; no nested path qualifies.
182
+ if (path.dirname(realWt) !== realBase) return false;
183
+ if (!path.basename(realWt).startsWith('so-worktree-')) return false;
184
+
185
+ if (!(await _isUnregisteredWorktreeDir(realWt))) return false;
186
+
187
+ // RACE WINDOW (accepted, #1390 P7): `_isUnregisteredWorktreeDir` above and
188
+ // `fs.rm` below are two separate syscalls, not one atomic check-then-act.
189
+ // Both plausible racers here are session-orchestrator sessions on the SAME
190
+ // host, contending over the same `<os.tmpdir()>/so-worktrees` base: one
191
+ // could register (`git worktree add`) or delete `realWt` in the gap between
192
+ // the two calls. No lock primitive guards this sub-millisecond window
193
+ // between our own sessions — the worst case is a benign retry (the caller's
194
+ // `git worktree add` above already retries once on failure), never data
195
+ // loss, because neither racer ever touches a path outside its own
196
+ // `so-worktree-*` name.
197
+ // REVISIT TRIGGER (BV-004): an UNRELATED process gains the ability to
198
+ // create or delete `so-worktree-*` directories under this base — at that
199
+ // point the race is no longer bounded to our own retry semantics and this
200
+ // window needs an actual lock, not an accepted gap.
201
+ await fs.rm(realWt, { recursive: true, force: true });
202
+ return true;
203
+ }
204
+
60
205
  // ---------------------------------------------------------------------------
61
206
  // validateWorkspacePath
62
207
  // ---------------------------------------------------------------------------
@@ -136,6 +281,13 @@ export async function createWorktree(suffix, baseRef = 'HEAD', options = {}) {
136
281
  await nothrow(git`git worktree remove ${wtPath} --force`);
137
282
  await nothrow(git`git branch -D ${branch}`);
138
283
 
284
+ // Still on disk after the remove attempt → git has no registration for it
285
+ // (issue #984). Delete the orphan directory under the guards documented on
286
+ // _removeOrphanWorktreeDir; a live/registered worktree never reaches here.
287
+ if (await _exists(wtPath)) {
288
+ await _removeOrphanWorktreeDir(wtPath);
289
+ }
290
+
139
291
  try {
140
292
  await git`git worktree add -b ${branch} ${wtPath} ${baseRef}`;
141
293
  } catch (secondErr) {