session-orchestrator 3.24.0 → 4.0.1

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 (435) 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 +3 -2
  47. package/.codex-plugin/skills/architecture/SKILL.md +20 -0
  48. package/.codex-plugin/skills/autopilot/SKILL.md +21 -0
  49. package/.codex-plugin/skills/autopilot/agents/openai.yaml +5 -0
  50. package/.codex-plugin/skills/bootstrap/SKILL.md +22 -0
  51. package/.codex-plugin/skills/bootstrap/agents/openai.yaml +5 -0
  52. package/.codex-plugin/skills/brainstorm/SKILL.md +22 -0
  53. package/.codex-plugin/skills/brainstorm/agents/openai.yaml +5 -0
  54. package/.codex-plugin/skills/claude-md-drift-check/SKILL.md +17 -0
  55. package/.codex-plugin/skills/close/SKILL.md +21 -0
  56. package/.codex-plugin/skills/close/agents/openai.yaml +5 -0
  57. package/.codex-plugin/skills/convergence-monitoring/SKILL.md +24 -0
  58. package/.codex-plugin/skills/debug/SKILL.md +21 -0
  59. package/.codex-plugin/skills/debug/agents/openai.yaml +5 -0
  60. package/.codex-plugin/skills/discovery/SKILL.md +21 -0
  61. package/.codex-plugin/skills/discovery/agents/openai.yaml +5 -0
  62. package/.codex-plugin/skills/dispatcher/SKILL.md +21 -0
  63. package/.codex-plugin/skills/dispatcher/agents/openai.yaml +5 -0
  64. package/.codex-plugin/skills/docs-orchestrator/SKILL.md +20 -0
  65. package/.codex-plugin/skills/ecosystem-health/SKILL.md +22 -0
  66. package/.codex-plugin/skills/eli5/SKILL.md +21 -0
  67. package/.codex-plugin/skills/eli5/agents/openai.yaml +5 -0
  68. package/.codex-plugin/skills/eval/SKILL.md +21 -0
  69. package/.codex-plugin/skills/eval/agents/openai.yaml +5 -0
  70. package/.codex-plugin/skills/evolve/SKILL.md +21 -0
  71. package/.codex-plugin/skills/evolve/agents/openai.yaml +5 -0
  72. package/.codex-plugin/skills/frontmatter-guard/SKILL.md +17 -0
  73. package/.codex-plugin/skills/gitlab-ops/SKILL.md +22 -0
  74. package/.codex-plugin/skills/gitlab-portfolio/SKILL.md +17 -0
  75. package/.codex-plugin/skills/go/SKILL.md +22 -0
  76. package/.codex-plugin/skills/go/agents/openai.yaml +5 -0
  77. package/.codex-plugin/skills/grill/SKILL.md +21 -0
  78. package/.codex-plugin/skills/grill/agents/openai.yaml +5 -0
  79. package/.codex-plugin/skills/harness-audit/SKILL.md +19 -0
  80. package/.codex-plugin/skills/harness-audit/agents/openai.yaml +5 -0
  81. package/.codex-plugin/skills/hook-development/SKILL.md +17 -0
  82. package/.codex-plugin/skills/mcp-builder/SKILL.md +17 -0
  83. package/.codex-plugin/skills/memory-cleanup/SKILL.md +21 -0
  84. package/.codex-plugin/skills/memory-cleanup/agents/openai.yaml +5 -0
  85. package/.codex-plugin/skills/mode-selector/SKILL.md +19 -0
  86. package/.codex-plugin/skills/npm-publish/SKILL.md +18 -0
  87. package/.codex-plugin/skills/peekaboo-driver/SKILL.md +20 -0
  88. package/.codex-plugin/skills/persona-panel/SKILL.md +22 -0
  89. package/.codex-plugin/skills/persona-panel/agents/openai.yaml +5 -0
  90. package/.codex-plugin/skills/plan/SKILL.md +22 -0
  91. package/.codex-plugin/skills/plan/agents/openai.yaml +5 -0
  92. package/.codex-plugin/skills/playwright-driver/SKILL.md +22 -0
  93. package/.codex-plugin/skills/portfolio/SKILL.md +21 -0
  94. package/.codex-plugin/skills/portfolio/agents/openai.yaml +5 -0
  95. package/.codex-plugin/skills/quality-gates/SKILL.md +22 -0
  96. package/.codex-plugin/skills/reconcile/SKILL.md +21 -0
  97. package/.codex-plugin/skills/reconcile/agents/openai.yaml +5 -0
  98. package/.codex-plugin/skills/release/SKILL.md +22 -0
  99. package/.codex-plugin/skills/release/agents/openai.yaml +5 -0
  100. package/.codex-plugin/skills/remote-offload/SKILL.md +22 -0
  101. package/.codex-plugin/skills/repo-audit/SKILL.md +19 -0
  102. package/.codex-plugin/skills/repo-audit/agents/openai.yaml +5 -0
  103. package/.codex-plugin/skills/session/SKILL.md +21 -0
  104. package/.codex-plugin/skills/session/agents/openai.yaml +5 -0
  105. package/.codex-plugin/skills/session-end/SKILL.md +22 -0
  106. package/.codex-plugin/skills/session-plan/SKILL.md +22 -0
  107. package/.codex-plugin/skills/session-start/SKILL.md +22 -0
  108. package/.codex-plugin/skills/spinout/SKILL.md +21 -0
  109. package/.codex-plugin/skills/spinout/agents/openai.yaml +5 -0
  110. package/.codex-plugin/skills/sunset-review/SKILL.md +21 -0
  111. package/.codex-plugin/skills/sunset-review/agents/openai.yaml +5 -0
  112. package/.codex-plugin/skills/templates-ack/SKILL.md +21 -0
  113. package/.codex-plugin/skills/templates-ack/agents/openai.yaml +5 -0
  114. package/.codex-plugin/skills/test/SKILL.md +21 -0
  115. package/.codex-plugin/skills/test/agents/openai.yaml +5 -0
  116. package/.codex-plugin/skills/test-runner/SKILL.md +22 -0
  117. package/.codex-plugin/skills/tmux-layout/SKILL.md +23 -0
  118. package/.codex-plugin/skills/using-orchestrator/SKILL.md +19 -0
  119. package/.codex-plugin/skills/vault-mirror/SKILL.md +17 -0
  120. package/.codex-plugin/skills/vault-sync/SKILL.md +17 -0
  121. package/.codex-plugin/skills/wave-executor/SKILL.md +22 -0
  122. package/.codex-plugin/skills/write-executable-plan/SKILL.md +24 -0
  123. package/.cursor/commands/autopilot.md +2 -2
  124. package/.cursor/commands/bootstrap.md +1 -1
  125. package/.cursor/commands/brainstorm.md +1 -1
  126. package/.cursor/commands/debug.md +1 -1
  127. package/.cursor/commands/discovery.md +1 -1
  128. package/.cursor/commands/dispatcher.md +2 -2
  129. package/.cursor/commands/eli5.md +2 -2
  130. package/.cursor/commands/eval.md +2 -2
  131. package/.cursor/commands/evolve.md +1 -1
  132. package/.cursor/commands/go.md +1 -1
  133. package/.cursor/commands/grill.md +2 -2
  134. package/.cursor/commands/memory-cleanup.md +2 -2
  135. package/.cursor/commands/persona-panel.md +1 -1
  136. package/.cursor/commands/plan.md +1 -1
  137. package/.cursor/commands/portfolio.md +1 -1
  138. package/.cursor/commands/reconcile.md +2 -2
  139. package/.cursor/commands/release.md +2 -2
  140. package/.cursor/commands/session.md +2 -2
  141. package/.cursor/commands/spinout.md +2 -2
  142. package/.cursor/commands/sunset-review.md +2 -2
  143. package/.cursor/commands/templates-ack.md +2 -2
  144. package/.cursor/commands/test.md +2 -2
  145. package/.cursor/skills/brainstorm/SKILL.md +1 -1
  146. package/.cursor/skills/eval/SKILL.md +1 -1
  147. package/.cursor/skills/quality-gates/SKILL.md +1 -1
  148. package/.cursor/skills/remote-offload/SKILL.md +1 -1
  149. package/.cursor-plugin/plugin.json +30 -0
  150. package/.orchestrator/policy/blocked-commands.json +121 -0
  151. package/.orchestrator/policy/ecosystem.schema.json +66 -0
  152. package/.orchestrator/policy/quality-gates.example.json +16 -0
  153. package/.orchestrator/policy/quality-gates.schema.json +38 -0
  154. package/.orchestrator/policy/templates-policy.json +27 -0
  155. package/.orchestrator/policy/test-profiles.json +47 -0
  156. package/AGENTS.md +225 -0
  157. package/CHANGELOG.md +1314 -2
  158. package/NOTICE +11 -6
  159. package/README.md +135 -94
  160. package/agents/eval-judge.md +1 -1
  161. package/agents/skill-applied-judge.md +1 -1
  162. package/assets/wave-lifecycle.svg +98 -0
  163. package/commands/release.md +6 -3
  164. package/commands/session.md +18 -3
  165. package/docs/README.md +4 -0
  166. package/{agents/AGENTS.md → docs/agent-authoring.md} +19 -26
  167. package/docs/baseline.md +67 -0
  168. package/docs/ci-setup.md +108 -62
  169. package/docs/codex-setup.md +107 -29
  170. package/docs/components.md +38 -16
  171. package/docs/cursor-setup.md +6 -2
  172. package/docs/events-schema.md +9 -6
  173. package/docs/instruction-delivery.md +69 -0
  174. package/{agents/memory-proposal-collector.md → docs/memory-proposal-flow.md} +1 -8
  175. package/docs/migration-v4.md +365 -0
  176. package/docs/pi-setup.md +6 -1
  177. package/docs/plugin-architecture-v3.md +1 -1
  178. package/docs/rule-authoring.md +85 -19
  179. package/docs/scope-collision-guard.md +5 -5
  180. package/docs/session-config-reference.md +57 -56
  181. package/docs/session-config-template.md +6 -29
  182. package/docs/telemetry.md +157 -3
  183. package/docs/vault-docs-architecture.md +50 -11
  184. package/hooks/_lib/hook-import-set.json +1488 -0
  185. package/hooks/_lib/subagent-transcript.mjs +562 -0
  186. package/hooks/config-protection.mjs +2 -2
  187. package/hooks/cwd-change-restore.mjs +2 -2
  188. package/hooks/enforce-commands.mjs +69 -0
  189. package/hooks/hooks-codex.json +1 -1
  190. package/hooks/hooks-cursor.json +10 -0
  191. package/hooks/hooks-pi.json +5 -0
  192. package/hooks/hooks.json +6 -1
  193. package/hooks/loop-guard.mjs +3 -3
  194. package/hooks/on-session-end.mjs +2 -2
  195. package/hooks/on-session-start.mjs +103 -2
  196. package/hooks/on-stop.mjs +60 -14
  197. package/hooks/operator-steer.mjs +2 -2
  198. package/hooks/post-bash-write-verify.mjs +85 -0
  199. package/hooks/post-edit-import-probe.mjs +344 -0
  200. package/hooks/post-subagent-discovery-validator.mjs +187 -431
  201. package/hooks/post-tool-batch-wave-signal.mjs +118 -4
  202. package/hooks/post-tool-failure-corrective-context.mjs +2 -2
  203. package/hooks/post-tooluse-frontend-slop.mjs +3 -3
  204. package/hooks/pre-bash-destructive-guard.mjs +39 -13
  205. package/hooks/skill-invocation-telemetry.mjs +17 -5
  206. package/hooks/subagent-telemetry.mjs +13 -4
  207. package/monitors/monitors.json +3 -3
  208. package/package.json +9 -1
  209. package/pi/prompts/session.md +2 -2
  210. package/scripts/backfill-abandoned-sessions.mjs +50 -4
  211. package/scripts/backfill-learnings-from-vault.mjs +9 -3
  212. package/scripts/dialectic-deriver.mjs +73 -8
  213. package/scripts/export-hw-learnings.mjs +113 -1
  214. package/scripts/generate-agents-skills.mjs +378 -0
  215. package/scripts/generate-codex-skills.mjs +246 -0
  216. package/scripts/generate-cursor-adapter.mjs +45 -8
  217. package/scripts/generate-hook-import-set.mjs +292 -0
  218. package/scripts/lib/agent-status.mjs +13 -2
  219. package/scripts/lib/auto-dream.mjs +38 -36
  220. package/scripts/lib/autonomy/suitability.mjs +6 -0
  221. package/scripts/lib/autopilot/loop.mjs +2 -2
  222. package/scripts/lib/ci-status-banner.mjs +220 -75
  223. package/scripts/lib/codex/plugin-contract.mjs +88 -6
  224. package/scripts/lib/config/auto-dream.mjs +2 -1
  225. package/scripts/lib/config/block-header.mjs +8 -0
  226. package/scripts/lib/config/block-preprocess.mjs +177 -0
  227. package/scripts/lib/config/broken-window.mjs +2 -1
  228. package/scripts/lib/config/cold-start.mjs +2 -1
  229. package/scripts/lib/config/config-protection.mjs +22 -2
  230. package/scripts/lib/config/context-coverage.mjs +2 -1
  231. package/scripts/lib/config/cross-repo.mjs +2 -1
  232. package/scripts/lib/config/custom-phases.mjs +2 -1
  233. package/scripts/lib/config/dialectic.mjs +2 -1
  234. package/scripts/lib/config/discovery-validator.mjs +2 -1
  235. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +24 -1
  236. package/scripts/lib/config/dispatcher-autonomy.mjs +2 -1
  237. package/scripts/lib/config/docs-orchestrator.mjs +2 -1
  238. package/scripts/lib/config/docs-staleness.mjs +2 -1
  239. package/scripts/lib/config/drift-check.mjs +2 -1
  240. package/scripts/lib/config/eval.mjs +2 -1
  241. package/scripts/lib/config/events-rotation.mjs +2 -1
  242. package/scripts/lib/config/evolve.mjs +8 -2
  243. package/scripts/lib/config/frontend-slop-hook.mjs +7 -3
  244. package/scripts/lib/config/gitlab-portfolio.mjs +2 -1
  245. package/scripts/lib/config/handover-gate.mjs +2 -1
  246. package/scripts/lib/config/health-endpoints.mjs +7 -2
  247. package/scripts/lib/config/host-paths.mjs +20 -4
  248. package/scripts/lib/config/issue-budget.mjs +2 -1
  249. package/scripts/lib/config/loop-guard.mjs +2 -1
  250. package/scripts/lib/config/memory.mjs +2 -1
  251. package/scripts/lib/config/moc-staleness.mjs +2 -1
  252. package/scripts/lib/config/persona-gate-wave.mjs +2 -1
  253. package/scripts/lib/config/private-config-dir.mjs +67 -0
  254. package/scripts/lib/config/reconcile.mjs +2 -1
  255. package/scripts/lib/config/remote-hosts.mjs +2 -1
  256. package/scripts/lib/config/section-extractor.mjs +7 -1
  257. package/scripts/lib/config/skill-evolution.mjs +2 -1
  258. package/scripts/lib/config/slopcheck.mjs +2 -1
  259. package/scripts/lib/config/state-md-lock.mjs +2 -1
  260. package/scripts/lib/config/templates-first.mjs +2 -1
  261. package/scripts/lib/config/test.mjs +2 -1
  262. package/scripts/lib/config/vault-integration.mjs +7 -1
  263. package/scripts/lib/config/vault-mirror-quality.mjs +2 -1
  264. package/scripts/lib/config/vault-staleness.mjs +2 -1
  265. package/scripts/lib/config/vault-sync.mjs +2 -1
  266. package/scripts/lib/config/verification-auto-fix.mjs +2 -1
  267. package/scripts/lib/config/wave-reviewers.mjs +2 -1
  268. package/scripts/lib/config/worktree-orphans.mjs +2 -1
  269. package/scripts/lib/convergence-monitor.mjs +82 -16
  270. package/scripts/lib/dispatcher/rank.mjs +124 -48
  271. package/scripts/lib/ecosystem-health.mjs +16 -2
  272. package/scripts/lib/eval/engine.mjs +9 -1
  273. package/scripts/lib/eval/session-resolve.mjs +23 -4
  274. package/scripts/lib/events.mjs +22 -6
  275. package/scripts/lib/frontmatter-guard.mjs +131 -13
  276. package/scripts/lib/gates/gate-full.mjs +30 -0
  277. package/scripts/lib/gates/gate-helpers.mjs +76 -0
  278. package/scripts/lib/hardware-pattern-detector.mjs +18 -1
  279. package/scripts/lib/harness-audit/categories/category4.mjs +31 -11
  280. package/scripts/lib/host-identity.mjs +50 -11
  281. package/scripts/lib/instruction-budget-guard.mjs +171 -5
  282. package/scripts/lib/learnings/evolve-telemetry.mjs +178 -0
  283. package/scripts/lib/learnings/io.mjs +60 -6
  284. package/scripts/lib/memory-proposals/store.mjs +30 -22
  285. package/scripts/lib/owner-config-banner.mjs +41 -6
  286. package/scripts/lib/owner-config-loader.mjs +21 -10
  287. package/scripts/lib/owner-interview.mjs +3 -3
  288. package/scripts/lib/owner-yaml.mjs +215 -15
  289. package/scripts/lib/platform.mjs +108 -15
  290. package/scripts/lib/plugin-update-banner.mjs +414 -0
  291. package/scripts/lib/project-hygiene.mjs +38 -2
  292. package/scripts/lib/qg-command-drift-banner.mjs +50 -12
  293. package/scripts/lib/quality-gate.mjs +133 -44
  294. package/scripts/lib/reconcile/emitter.mjs +68 -6
  295. package/scripts/lib/reconcile/engine.mjs +51 -11
  296. package/scripts/lib/reconcile/idempotency.mjs +37 -4
  297. package/scripts/lib/reconcile/writer.mjs +40 -18
  298. package/scripts/lib/session-close-backfill.mjs +67 -9
  299. package/scripts/lib/session-id.mjs +12 -23
  300. package/scripts/lib/session-identity/own-session.mjs +125 -10
  301. package/scripts/lib/session-lock-shape.mjs +43 -0
  302. package/scripts/lib/session-lock.mjs +5 -10
  303. package/scripts/lib/session-registry.mjs +25 -9
  304. package/scripts/lib/session-schema/constants.mjs +64 -3
  305. package/scripts/lib/session-schema/validator.mjs +38 -4
  306. package/scripts/lib/session-start-probes.mjs +30 -1
  307. package/scripts/lib/sessions-staleness-banner.mjs +18 -11
  308. package/scripts/lib/skill-health/join.mjs +17 -4
  309. package/scripts/lib/state-md.mjs +78 -0
  310. package/scripts/lib/sunset/walker.mjs +6 -0
  311. package/scripts/lib/telemetry/schema.mjs +202 -9
  312. package/scripts/lib/telemetry/sync.mjs +368 -12
  313. package/scripts/lib/telemetry-flush-health-banner.mjs +211 -0
  314. package/scripts/lib/validate/check-agents-skills.mjs +327 -0
  315. package/scripts/lib/validate/check-agents.mjs +3 -3
  316. package/scripts/lib/validate/check-codex-skills.mjs +191 -0
  317. package/scripts/lib/validate/check-cursor-adapter.mjs +234 -72
  318. package/scripts/lib/validate/check-hooks-symmetry.mjs +45 -16
  319. package/scripts/lib/validate/check-owner-leakage.mjs +319 -22
  320. package/scripts/lib/validate/check-skill-links.mjs +193 -0
  321. package/scripts/lib/validate/check-skill-script-paths.mjs +47 -28
  322. package/scripts/lib/validate/check-test-git-config-target.mjs +192 -12
  323. package/scripts/lib/validate/check-unwired-features.mjs +163 -15
  324. package/scripts/lib/validate/check-validator-registration.mjs +10 -4
  325. package/scripts/lib/validate/confidential-names.mjs +95 -30
  326. package/scripts/lib/validate/enumerate-repo-files.mjs +317 -0
  327. package/scripts/lib/validate/repo-files.mjs +48 -14
  328. package/scripts/lib/vault-backfill/template.mjs +63 -6
  329. package/scripts/lib/vault-mirror/process.mjs +165 -42
  330. package/scripts/lib/vault-mirror/telemetry.mjs +2 -2
  331. package/scripts/lib/vault-status/narrative-mirror.mjs +127 -18
  332. package/scripts/lib/wave-executor/dispatch-common.mjs +164 -0
  333. package/scripts/lib/wave-executor/foreign-dispatch.mjs +7 -142
  334. package/scripts/lib/wave-executor/remote-dispatch.mjs +5 -7
  335. package/scripts/lib/wave-resource-gate.mjs +8 -2
  336. package/scripts/lib/wave-sizing.mjs +4 -1
  337. package/scripts/lib/wave-transcript-tail.mjs +118 -4
  338. package/scripts/materialize-wave-scope.mjs +12 -5
  339. package/scripts/memory-propose.mjs +19 -5
  340. package/scripts/migrate-cold-start-seed.mjs +4 -1
  341. package/scripts/parse-config.mjs +60 -3
  342. package/scripts/release.mjs +430 -31
  343. package/scripts/repair-invalid-sessions.mjs +3 -3
  344. package/scripts/run-quality-gate.mjs +128 -11
  345. package/scripts/site-numbers.mjs +344 -8
  346. package/scripts/sweep-expired-learnings.mjs +90 -0
  347. package/scripts/sync-vault-schema.mjs +3 -1
  348. package/scripts/telemetry.mjs +2 -2
  349. package/scripts/validate-plugin.mjs +164 -0
  350. package/scripts/validate-wave-scope.mjs +28 -8
  351. package/scripts/wave-scope-binding.mjs +215 -0
  352. package/skills/_shared/instruction-file-resolution.md +10 -0
  353. package/skills/_shared/parallel-aware-preamble.md +1 -0
  354. package/skills/_shared/platform-tools.md +1 -1
  355. package/skills/_shared/state-ownership.md +1 -1
  356. package/skills/architecture/SKILL.md +7 -5
  357. package/skills/{domain-model/SKILL.md → architecture/references/domain-model.md} +9 -9
  358. package/skills/autopilot/SKILL.md +4 -18
  359. package/skills/claude-md-drift-check/SKILL.md +5 -1
  360. package/skills/claude-md-drift-check/checker.mjs +62 -2
  361. package/skills/convergence-monitoring/SIGNALS.md +55 -0
  362. package/skills/discovery/probes/vault-staleness.mjs +37 -13
  363. package/skills/discovery/probes-arch.md +20 -18
  364. package/skills/dispatcher/SKILL.md +3 -2
  365. package/skills/evolve/SKILL.md +65 -26
  366. package/skills/frontmatter-guard/SKILL.md +11 -5
  367. package/skills/npm-publish/SKILL.md +1 -1
  368. package/skills/reconcile/SKILL.md +33 -0
  369. package/skills/remote-offload/SKILL.md +1 -1
  370. package/skills/session-end/SKILL.md +18 -905
  371. package/skills/session-end/phase-3-6-tail.md +10 -3
  372. package/skills/session-end/plan-verification.md +221 -155
  373. package/skills/session-end/references/phase-2-quality-gate.md +93 -0
  374. package/skills/session-end/references/phase-3-documentation-updates.md +229 -0
  375. package/skills/session-end/references/phase-4a-worktree-cleanup.md +120 -0
  376. package/skills/session-end/references/phase-4b-worktree-orphan-sweep.md +58 -0
  377. package/skills/session-end/references/phase-5-issue-cleanup.md +104 -0
  378. package/skills/session-end/references/session-summary-template.md +62 -0
  379. package/skills/session-plan/SKILL.md +49 -0
  380. package/skills/session-start/SKILL.md +22 -904
  381. package/skills/session-start/phase-8-5-express-path.md +1 -1
  382. package/skills/session-start/references/phase-1-1-dispatcher-autonomy-capture.md +55 -0
  383. package/skills/session-start/references/phase-1-2-session-lock.md +140 -0
  384. package/skills/session-start/references/phase-1-5-session-continuity.md +254 -0
  385. package/skills/session-start/references/phase-1-7-vault-status-board.md +53 -0
  386. package/skills/session-start/references/phase-2-7-portfolio-snapshot.md +75 -0
  387. package/skills/session-start/references/phase-4-ssot-environment-check.md +160 -0
  388. package/skills/session-start/references/phase-6-5-forced-reads.md +75 -0
  389. package/skills/session-start/references/phase-6-6-project-intelligence.md +81 -0
  390. package/skills/session-start/references/phase-6-7-memory-banner-telemetry-consent.md +103 -0
  391. package/skills/vault-sync/SKILL.md +10 -0
  392. package/skills/vault-sync/validator.mjs +21 -27
  393. package/skills/wave-executor/SKILL.md +15 -1
  394. package/skills/wave-executor/references/wave-loop-dispatch.md +612 -0
  395. package/skills/wave-executor/references/wave-loop-review.md +570 -0
  396. package/skills/wave-executor/references/wave-loop-scope-manifest.md +162 -0
  397. package/skills/wave-executor/wave-loop.md +14 -1309
  398. package/templates/_shared/journey-manifest.md +10 -6
  399. package/.cursor/commands/autopilot-multi.md +0 -14
  400. package/.cursor/commands/contract-version-bump.md +0 -14
  401. package/.cursor/commands/journey-audit.md +0 -14
  402. package/.cursor/skills/contract-version-bump/SKILL.md +0 -12
  403. package/.cursor/skills/daily/SKILL.md +0 -12
  404. package/.cursor/skills/domain-model/SKILL.md +0 -13
  405. package/.cursor/skills/journey-audit/SKILL.md +0 -13
  406. package/.cursor/skills/skill-creator/SKILL.md +0 -13
  407. package/.cursor/skills/ubiquitous-language/SKILL.md +0 -13
  408. package/commands/autopilot-multi.md +0 -74
  409. package/commands/contract-version-bump.md +0 -28
  410. package/commands/journey-audit.md +0 -43
  411. package/pi/prompts/autopilot-multi.md +0 -12
  412. package/pi/prompts/contract-version-bump.md +0 -12
  413. package/pi/prompts/journey-audit.md +0 -12
  414. package/scripts/autopilot-multi.mjs +0 -885
  415. package/scripts/backfill-learnings-expires.mjs +0 -196
  416. package/scripts/backfill-learnings.mjs +0 -203
  417. package/scripts/fleet-instruction-scan.mjs +0 -141
  418. package/scripts/lib/autopilot/dep-graph.mjs +0 -417
  419. package/scripts/lib/autopilot/multi-killswitch.mjs +0 -184
  420. package/scripts/lib/webhook-url.mjs +0 -105
  421. package/scripts/lifecycle-sim-v6.mjs +0 -347
  422. package/scripts/migrate-learnings-jsonl.mjs +0 -189
  423. package/scripts/migrate-subagents-jsonl.mjs +0 -196
  424. package/scripts/upload-social-preview.mjs +0 -316
  425. package/skills/_shared/model-selection.md +0 -64
  426. package/skills/contract-version-bump/SKILL.md +0 -219
  427. package/skills/daily/SKILL.md +0 -222
  428. package/skills/daily/generate.sh +0 -92
  429. package/skills/daily/templates/daily.md.tpl +0 -36
  430. package/skills/journey-audit/SKILL.md +0 -270
  431. package/skills/skill-creator/SKILL.md +0 -168
  432. package/skills/ubiquitous-language/SKILL.md +0 -97
  433. package/skills/vault-sync/package-lock.json +0 -40
  434. /package/skills/{domain-model → architecture/references}/ADR-FORMAT.md +0 -0
  435. /package/skills/{domain-model → architecture/references}/CONTEXT-FORMAT.md +0 -0
@@ -0,0 +1,317 @@
1
+ /**
2
+ * scripts/lib/validate/enumerate-repo-files.mjs
3
+ *
4
+ * ONE enumerator for the scanners whose question is **"does this file exist in
5
+ * the repository right now?"** — GitLab #1248.
6
+ *
7
+ * ## The three populations, and why this is a third one
8
+ *
9
+ * `./repo-files.mjs` already answers two questions, and this module answers a
10
+ * third rather than duplicating either:
11
+ *
12
+ * | function | population |
13
+ * |-----------------------------|-----------------------------------------------------|
14
+ * | `listRepoFiles()` | what git TRACKS — "will ship / is versioned" |
15
+ * | `listOnDiskFiles()` | what the filesystem HOLDS minus a name list |
16
+ * | `enumerateRepoFiles()` here | **exists under these roots, tracked or not, minus what `.gitignore` excludes** |
17
+ *
18
+ * Pick deliberately. A scanner asking "is this file versioned / will it ship?"
19
+ * (a packaging check, a tarball manifest) MUST keep `listRepoFiles()`. A
20
+ * scanner asking "is this file PRESENT, so is the citation pointing at it
21
+ * dead?" belongs here — for it, the index is the wrong oracle.
22
+ *
23
+ * ## Why the index is the wrong oracle for an existence check (#1248)
24
+ *
25
+ * MEASURED in a clone (Wave-1 D6, 2026-09-06): an UNTRACKED
26
+ * `skills/zz-probe/SKILL.md` citing `scripts/does-not-exist.mjs` made
27
+ * `check-skill-script-paths.mjs` report `1 passed, 0 failed` BEFORE
28
+ * `git add -A` and `0 passed, 1 failed` AFTER — same working tree, same
29
+ * defect, no edit in between. The moment a defect is most likely to exist (a
30
+ * brand-new, not-yet-staged skill or checker) is exactly the moment the index
31
+ * cannot see it, so the check reports clean on the tree that carries the bug.
32
+ * `.claude/rules/measurement-discipline.md` § "A `git grep` drift sweep cannot
33
+ * see untracked files" is the same incident class on the release sweep.
34
+ *
35
+ * ## Why NOT a bare `readdirSync` walk
36
+ *
37
+ * The naive repair — swap `git ls-files` for a filesystem walk with a fixed
38
+ * prune list — reintroduces the MEASURED regression #1143 that put
39
+ * `repo-files.mjs` there in the first place: a walk cannot see `.gitignore`,
40
+ * so a gitignored worktree under `.claude/worktrees/<name>` drops a COMPLETE
41
+ * second checkout into the census (measured with one peer worktree present:
42
+ * +755 `.md`, +1209 `.mjs`, 133 MB, and a peer's copy of a rule file counted
43
+ * as an independent document). MEASURED here 2026-09-06 @ `befdda47` on a
44
+ * clean tree with `.claude/worktrees/` empty:
45
+ *
46
+ * git ls-files -- skills commands agents docs → 287 .md
47
+ * git ls-files --cached --others
48
+ * --exclude-standard -- (same dirs) → 287 .md
49
+ * listOnDiskFiles() walk, EXCLUDED_DIRS -- (same dirs) → 290 .md
50
+ *
51
+ * The 3-file surplus of the walk is entirely gitignored content
52
+ * (`docs/specs/2026-04-04-plan-skill-design.md`,
53
+ * `docs/specs/2026-04-16-bootstrap-gate-design.md`,
54
+ * `docs/specs/2026-05-26-parallel-aware-sessions-design.md`) — private design
55
+ * notes that would enter the census as if they were repository documentation.
56
+ * So the oracle is `--cached --others --exclude-standard`: it sees the
57
+ * untracked file #1248 is about AND honours `.gitignore`, which no prune list
58
+ * can approximate.
59
+ *
60
+ * ## The failure contract: absent is not unreadable (#1248 follow-up)
61
+ *
62
+ * The population above is "exists under these roots". Deciding whether a path
63
+ * EXISTS costs one `statSync`, and that `statSync` can fail for two
64
+ * fundamentally different reasons which an earlier version of this module
65
+ * collapsed into one bare `catch { continue }`:
66
+ *
67
+ * | stat error | what it means | this module |
68
+ * |-----------------------------------|-------------------------------------------------|-------------|
69
+ * | `ENOENT` | git lists it, the checkout lacks it — sparse checkout, a deletion staged elsewhere | SKIP (contributes zero files) |
70
+ * | `ENOTDIR` | a parent component is a file, so the path cannot exist either | SKIP (same class) |
71
+ * | `EACCES` / `EPERM` | the file EXISTS; this process may not look at it | THROW {@link RepoEnumerationError} |
72
+ * | `ELOOP` / `EIO` / `ENAMETOOLONG` / anything else | the answer is unknown | THROW {@link RepoEnumerationError} |
73
+ *
74
+ * The skip set is deliberately the same two codes `listRepoFiles()` documents
75
+ * in its own filter comment ("a tracked path can be absent from the working
76
+ * tree"), narrowed from a catch-all to exactly the codes that mean ABSENT.
77
+ * Every other code means the enumerator does not know whether the file is
78
+ * there, and a census that silently omits a file it could not look at reports
79
+ * a SMALLER population than the truth — with no signal that it did.
80
+ *
81
+ * That silence had a measured consequence. `collectDriftHits()` in
82
+ * `scripts/release.mjs` sweeps every enumerated file for the previous release
83
+ * literal; with `README.md` unreadable, the swallow made the sweep read ZERO
84
+ * files, return `status 1` ("no match"), and `evaluateDriftSweep()` reported
85
+ * `ok: true` — a release gate passing on a file it could not open. Fail-closed
86
+ * is one line there, and it was already written:
87
+ *
88
+ * } catch (err) {
89
+ * return { status: 128, stdout: '', stderr: `enumerateRepoFiles failed: ${err && err.message}` };
90
+ *
91
+ * (`scripts/release.mjs:576-578`; `evaluateDriftSweep` maps any status outside
92
+ * {0,1} to `ok: false` — "sweep is inconclusive".) So THROWING is what turns
93
+ * an unreadable file into an inconclusive sweep instead of a clean one.
94
+ *
95
+ * NAMED CEILING (BV-004): only the git path fails closed. The non-git fallback
96
+ * walks through `listOnDiskFiles()`, whose `readdirSync` walk skips an
97
+ * unreadable sub-tree by design and cannot report it — see
98
+ * {@link enumerateRepoFiles}. Revisit trigger: the first consumer that needs
99
+ * fail-closed enumeration on a NON-git root (a tarball export, a vendored
100
+ * copy); the answer then is an error-collecting walk in `repo-files.mjs`, not
101
+ * a second stat pass here.
102
+ *
103
+ * ## NAMED CEILING (BV-004)
104
+ *
105
+ * The `prune` list is a fixed set of path segments / repo-relative prefixes,
106
+ * NOT a `.gitignore` parser — it is only load-bearing on the FALLBACK path
107
+ * (a root that is not a git top level: a tarball export, a vendored copy, a
108
+ * tmpdir fixture). On the primary path git already applies the real ignore
109
+ * rules and the prune list is a cheap second filter. REVISIT if a scanner
110
+ * ever needs this on a non-git root whose ignored trees are not covered by
111
+ * the default prune set — then reach for a real ignore parser, do not grow
112
+ * this list a sixth time.
113
+ *
114
+ * @module scripts/lib/validate/enumerate-repo-files
115
+ */
116
+
117
+ import { execFileSync } from 'node:child_process';
118
+ import { statSync } from 'node:fs';
119
+ import path from 'node:path';
120
+
121
+ import { EXCLUDED_DIRS, isGitToplevel, listOnDiskFiles } from './repo-files.mjs';
122
+
123
+ /**
124
+ * Environment handed to `git`. An allowlist rather than `process.env`: an
125
+ * inherited `GIT_DIR` / `GIT_WORK_TREE` (set by any hook that spawned us)
126
+ * would silently re-point `ls-files` at a DIFFERENT repository, and the result
127
+ * would look like a plausible file list. Same allowlist as
128
+ * `repo-files.mjs`, which is not exported there.
129
+ */
130
+ const GIT_ENV_ALLOWLIST = Object.freeze(['PATH', 'HOME', 'LANG', 'LC_ALL', 'TMPDIR', 'TZ']);
131
+
132
+ /**
133
+ * Path segments (or repo-relative path prefixes) never enumerated — reused
134
+ * from `repo-files.mjs` rather than retyped, so an addition there reaches this
135
+ * module too.
136
+ *
137
+ * Deliberately NOT extended with this repo's own ignored trees
138
+ * (`.orchestrator/tmp/`, `.claude/worktrees/`): on the primary path `git` has
139
+ * already applied the real ignore rules — measured, `.orchestrator/tmp/` is
140
+ * `.gitignore:122` — so such an entry would be dead weight there, and naming
141
+ * an untracked path in a module a test imports is itself a finding
142
+ * (`check-untracked-test-deps.mjs` R2). A caller scanning a NON-git root whose
143
+ * ignored subtree a basename cannot express passes it via `prune` instead —
144
+ * that is what the prefix form is for.
145
+ */
146
+ export const DEFAULT_PRUNE = EXCLUDED_DIRS;
147
+
148
+ /**
149
+ * `stat` error codes that mean the path is ABSENT from the working tree, and
150
+ * are therefore skipped rather than raised. See the module header's failure
151
+ * table for why the set is exactly these two and not a catch-all.
152
+ */
153
+ const ABSENT_STAT_CODES = Object.freeze(new Set(['ENOENT', 'ENOTDIR']));
154
+
155
+ /**
156
+ * Raised when a path git listed could not be RESOLVED — the file may well be
157
+ * there and this process could not look at it (`EACCES`, `EPERM`, `ELOOP`,
158
+ * `EIO`, …). Callers are expected to fail closed on it: an enumeration that
159
+ * threw describes no population at all.
160
+ *
161
+ * @property {string} code the underlying `stat` error code (`EACCES`, …)
162
+ * @property {string} path the absolute path that could not be resolved
163
+ */
164
+ export class RepoEnumerationError extends Error {
165
+ /**
166
+ * @param {string} message
167
+ * @param {{code?: string, path?: string, cause?: unknown}} details
168
+ */
169
+ constructor(message, { code, path: target, cause } = {}) {
170
+ super(message, cause === undefined ? undefined : { cause });
171
+ this.name = 'RepoEnumerationError';
172
+ this.code = code;
173
+ this.path = target;
174
+ }
175
+ }
176
+
177
+ /**
178
+ * True when `relative` (POSIX, repo-relative) is inside a pruned tree.
179
+ * A prune entry matches either a whole path SEGMENT (`node_modules`) or a
180
+ * repo-relative path PREFIX (`.orchestrator/tmp`).
181
+ *
182
+ * @param {string} relative repo-relative POSIX path
183
+ * @param {string[]} prune
184
+ * @returns {boolean}
185
+ */
186
+ function isPruned(relative, prune) {
187
+ const segments = relative.split('/');
188
+ for (const entry of prune) {
189
+ if (entry.includes('/')) {
190
+ if (relative === entry || relative.startsWith(`${entry}/`)) return true;
191
+ } else if (segments.includes(entry)) {
192
+ return true;
193
+ }
194
+ }
195
+ return false;
196
+ }
197
+
198
+ /** @returns {NodeJS.ProcessEnv} the allowlisted git environment */
199
+ function gitEnv() {
200
+ /** @type {NodeJS.ProcessEnv} */
201
+ const env = {};
202
+ for (const key of GIT_ENV_ALLOWLIST) {
203
+ if (process.env[key] !== undefined) env[key] = process.env[key];
204
+ }
205
+ return env;
206
+ }
207
+
208
+ /**
209
+ * Normalise `exts` into a predicate over an absolute path.
210
+ * @param {string[] | null | undefined} exts extensions, with or without the dot
211
+ * @returns {(absolute: string) => boolean}
212
+ */
213
+ function extFilter(exts) {
214
+ if (!exts || exts.length === 0) return () => true;
215
+ const set = new Set(exts.map((e) => (e.startsWith('.') ? e : `.${e}`)));
216
+ return (absolute) => set.has(path.extname(absolute));
217
+ }
218
+
219
+ /**
220
+ * Files that EXIST under `dirs` — tracked or not — minus gitignored content
221
+ * and minus `prune`.
222
+ *
223
+ * Drop-in shaped like `listRepoFiles()`: absolute paths, sorted, deduplicated,
224
+ * never throwing for a missing directory (it contributes zero files).
225
+ *
226
+ * @param {object} options
227
+ * @param {string} options.repoRoot absolute repository root
228
+ * @param {string[]} [options.dirs] repo-relative directories to scan (default: the whole root)
229
+ * @param {string[] | null} [options.exts] extensions to keep (default: every file)
230
+ * @param {string[]} [options.prune] path segments / repo-relative prefixes to skip
231
+ * (default: {@link DEFAULT_PRUNE}; REPLACES the default when given, so pass
232
+ * `[...DEFAULT_PRUNE, 'extra']` to extend it rather than to swap it)
233
+ * @param {(target: string) => import('node:fs').Stats} [options.stat] injection
234
+ * seam for tests (default: `statSync`) — the same shape `collectDriftHits()`
235
+ * uses for its `enumerate`/`read` seams
236
+ * @returns {string[]} absolute paths, sorted
237
+ * @throws {RepoEnumerationError} when a listed path can neither be resolved
238
+ * nor proven absent (`EACCES`, `EPERM`, `ELOOP`, …) — see the module header's
239
+ * failure table. Only the git path raises; the non-git fallback walk cannot.
240
+ */
241
+ export function enumerateRepoFiles({
242
+ repoRoot,
243
+ dirs,
244
+ exts = null,
245
+ prune = DEFAULT_PRUNE,
246
+ stat = statSync,
247
+ } = {}) {
248
+ const matches = extFilter(exts);
249
+ const pruneList = [...prune];
250
+ const env = gitEnv();
251
+
252
+ if (isGitToplevel(repoRoot, env)) {
253
+ const pathspecs = dirs && dirs.length > 0 ? dirs.filter((d) => d !== '.') : [];
254
+ try {
255
+ // `--cached --others --exclude-standard` = tracked PLUS untracked, minus
256
+ // everything `.gitignore`/`.git/info/exclude` excludes. `--deduplicate`
257
+ // is deliberately not used: it needs git >= 2.31 and the Set below is
258
+ // free. See the module header for why this beats both a bare
259
+ // `ls-files` (#1248) and a bare walk (#1143).
260
+ const out = execFileSync(
261
+ 'git',
262
+ ['ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', ...pathspecs],
263
+ {
264
+ cwd: repoRoot,
265
+ encoding: 'utf8',
266
+ stdio: ['ignore', 'pipe', 'ignore'],
267
+ maxBuffer: 64 * 1024 * 1024,
268
+ env,
269
+ },
270
+ );
271
+ const found = new Set();
272
+ for (const rel of out.split('\0')) {
273
+ if (!rel) continue;
274
+ if (isPruned(rel, pruneList)) continue;
275
+ const absolute = path.join(repoRoot, rel);
276
+ if (!matches(absolute)) continue;
277
+ // A tracked path can be absent from the working tree (sparse checkout,
278
+ // a deletion staged elsewhere). A scanner that then read it would
279
+ // report a tool-error for a file nobody removed — so ABSENT is skipped.
280
+ // Anything else means the file may be there and we could not look:
281
+ // fail closed rather than shrink the census in silence (header table).
282
+ let entry;
283
+ try {
284
+ entry = stat(absolute);
285
+ } catch (err) {
286
+ const code = err && err.code;
287
+ if (ABSENT_STAT_CODES.has(code)) continue;
288
+ throw new RepoEnumerationError(
289
+ `cannot stat ${absolute} (${code || 'unknown error'}): the file may exist and could not be read — enumeration is inconclusive`,
290
+ { code, path: absolute, cause: err },
291
+ );
292
+ }
293
+ if (!entry.isFile()) continue;
294
+ found.add(absolute);
295
+ }
296
+ return [...found].sort();
297
+ } catch (err) {
298
+ // A resolution failure is NOT a reason to retry with a weaker oracle:
299
+ // the walk would skip the same unreadable entry silently and hand back a
300
+ // census that looks complete. Only a git/`ls-files` failure falls
301
+ // through — there we have no index to trust in the first place.
302
+ if (err instanceof RepoEnumerationError) throw err;
303
+ // fall through to the walk — a git that answered rev-parse but failed
304
+ // ls-files leaves us with no index to trust.
305
+ }
306
+ }
307
+
308
+ // Non-git root (tarball export, vendored copy, tmpdir fixture): the walk in
309
+ // `repo-files.mjs` is the reuse — it already skips symlinks and unreadable
310
+ // sub-trees. It excludes by directory BASENAME only, so prefix-shaped prune
311
+ // entries are re-applied here.
312
+ const basenamePrune = pruneList.filter((entry) => !entry.includes('/'));
313
+ return listOnDiskFiles(repoRoot, { dirs, exts, exclude: basenamePrune }).filter((absolute) => {
314
+ const rel = path.relative(repoRoot, absolute).split(path.sep).join('/');
315
+ return !isPruned(rel, pruneList);
316
+ });
317
+ }
@@ -178,6 +178,37 @@ function walk(absDir, matches, exclude, acc = []) {
178
178
  return acc;
179
179
  }
180
180
 
181
+ /**
182
+ * Error codes that mean "this tracked path is not in the working tree" — a
183
+ * sparse checkout, or a deletion staged from somewhere else. Both are ordinary
184
+ * repository states, so the path is dropped from the census silently.
185
+ *
186
+ * Every OTHER stat error (EACCES on an unreadable parent, EIO, ELOOP, ENAMETOOLONG)
187
+ * describes a filesystem the caller cannot enumerate. Swallowing those returned a
188
+ * SHORTER census that looked exactly like a smaller repository, which is the
189
+ * failure mode a scanner can neither see nor report.
190
+ */
191
+ const ABSENT_FROM_WORKTREE = Object.freeze(['ENOENT', 'ENOTDIR']);
192
+
193
+ /**
194
+ * True when `absolute` is a regular file present in the working tree; false
195
+ * when it is absent for one of the {@link ABSENT_FROM_WORKTREE} reasons.
196
+ * Rethrows every other stat error.
197
+ *
198
+ * @param {string} absolute
199
+ * @returns {boolean}
200
+ */
201
+ function isPresentFile(absolute) {
202
+ try {
203
+ return statSync(absolute).isFile();
204
+ } catch (err) {
205
+ if (ABSENT_FROM_WORKTREE.includes(/** @type {NodeJS.ErrnoException} */ (err).code)) {
206
+ return false;
207
+ }
208
+ throw err;
209
+ }
210
+ }
211
+
181
212
  /**
182
213
  * Resolve the `dirs` option to absolute directories under `root`.
183
214
  * `'.'` (or an empty list) means the root itself.
@@ -212,33 +243,36 @@ export function listRepoFiles(root, options = {}) {
212
243
 
213
244
  if (isGitToplevel(root, env)) {
214
245
  const pathspecs = dirs && dirs.length > 0 ? dirs.filter((d) => d !== '.') : [];
246
+ /** @type {string | null} */
247
+ let out = null;
215
248
  try {
216
- const out = execFileSync('git', ['ls-files', '-z', '--', ...pathspecs], {
249
+ out = execFileSync('git', ['ls-files', '-z', '--', ...pathspecs], {
217
250
  cwd: root,
218
251
  encoding: 'utf8',
219
252
  stdio: ['ignore', 'pipe', 'ignore'],
220
253
  maxBuffer: 64 * 1024 * 1024,
221
254
  env,
222
255
  });
256
+ } catch {
257
+ // fall through to the walk — a git that answered rev-parse but failed
258
+ // ls-files leaves us with no index to trust.
259
+ //
260
+ // This catch covers the `ls-files` INVOCATION only. The census below is
261
+ // deliberately outside it: a stat error there is not "git has no index",
262
+ // and folding the two together would turn an unreadable working tree
263
+ // into a silent full-repo re-walk.
264
+ //
265
+ // `out` keeps its `null` initialiser here — no reassignment, so the
266
+ // `out !== null` test below is the single place the two paths diverge.
267
+ }
268
+ if (out !== null) {
223
269
  return out
224
270
  .split('\0')
225
271
  .filter(Boolean)
226
272
  .map((rel) => path.join(root, rel))
227
273
  .filter(matches)
228
- // A tracked path can be absent from the working tree (sparse checkout,
229
- // a deletion staged elsewhere). A scanner that then read it would
230
- // report a tool-error for a file nobody removed.
231
- .filter((absolute) => {
232
- try {
233
- return statSync(absolute).isFile();
234
- } catch {
235
- return false;
236
- }
237
- })
274
+ .filter(isPresentFile)
238
275
  .sort();
239
- } catch {
240
- // fall through to the walk — a git that answered rev-parse but failed
241
- // ls-files leaves us with no index to trust.
242
276
  }
243
277
  }
244
278
 
@@ -1,17 +1,71 @@
1
1
  /**
2
2
  * template.mjs — Canonical .vault.yaml template renderer for vault-backfill.
3
3
  *
4
- * Reads the template once from projects-baseline; subsequent calls use cache.
4
+ * Reads the template once from a projects-baseline checkout; subsequent calls
5
+ * use the cache. The checkout is optional — see docs/baseline.md.
5
6
  * Part of scripts/vault-backfill.mjs (Issue #241).
6
7
  */
7
8
 
8
9
  import { readFileSync, existsSync } from 'node:fs';
9
10
  import { homedir } from 'node:os';
10
- import { resolve } from 'node:path';
11
+ import { dirname, resolve } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { resolveHostPath } from '../config/host-paths.mjs';
11
14
 
12
- export const TEMPLATE_PATH = process.env.PROJECTS_BASELINE_DIR
13
- ? resolve(process.env.PROJECTS_BASELINE_DIR, 'templates/shared/.vault.yaml.template')
14
- : resolve(homedir(), 'Projects/projects-baseline/templates/shared/.vault.yaml.template');
15
+ /** Path of the template RELATIVE to a projects-baseline checkout root. */
16
+ const TEMPLATE_REL_PATH = 'templates/shared/.vault.yaml.template';
17
+
18
+ /** This file lives at `<repoRoot>/scripts/lib/vault-backfill/`. */
19
+ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
20
+
21
+ /**
22
+ * Candidate projects-baseline checkout roots. The baseline is optional and
23
+ * private (`docs/baseline.md`), so no host-specific directory name may be
24
+ * committed here.
25
+ *
26
+ * Two tiers, and the split is load-bearing:
27
+ *
28
+ * EXPLICIT — `PROJECTS_BASELINE_DIR`, else `SO_BASELINE_PATH` / `owner.yaml`
29
+ * `paths.baseline-path`. When the operator has SAID where the baseline is,
30
+ * that answer is used ALONE. Probing past a wrong explicit value would resolve
31
+ * a DIFFERENT baseline than the one named and report success — silently using
32
+ * a corpus nobody asked for is worse than the abort, and it would hide the
33
+ * typo forever.
34
+ *
35
+ * CONVENTION — the sibling checkout `scripts/sync-vault-schema.mjs` already
36
+ * uses, then the legacy `~/Projects` default this module shipped with. These
37
+ * are guesses, so probing among them is exactly right.
38
+ *
39
+ * @returns {string[]} never empty
40
+ */
41
+ function baselineCandidates() {
42
+ const envDir = (process.env.PROJECTS_BASELINE_DIR || '').trim();
43
+ if (envDir) return [envDir];
44
+ const hostDir = resolveHostPath('baseline-path', null);
45
+ if (typeof hostDir === 'string' && hostDir.trim() !== '') return [hostDir.trim()];
46
+ return [
47
+ resolve(REPO_ROOT, '..', 'projects-baseline'),
48
+ resolve(homedir(), 'Projects/projects-baseline'),
49
+ ];
50
+ }
51
+
52
+ /**
53
+ * Resolved absolute path of the canonical template.
54
+ *
55
+ * Import-time resolution is retained deliberately: the export is a plain string
56
+ * that `scripts/vault-backfill.mjs` and the tests both read directly. Ceiling:
57
+ * at most two `existsSync` calls at import. When no candidate exists the FIRST
58
+ * candidate is exported anyway, so `loadTemplate`'s die message names the path
59
+ * the operator most likely meant rather than `undefined`.
60
+ */
61
+ export const TEMPLATE_PATH = (() => {
62
+ const candidates = baselineCandidates();
63
+ for (const base of candidates) {
64
+ const candidate = resolve(base, TEMPLATE_REL_PATH);
65
+ if (existsSync(candidate)) return candidate;
66
+ }
67
+ return resolve(candidates[0], TEMPLATE_REL_PATH);
68
+ })();
15
69
 
16
70
  const TODAY = new Date().toISOString().slice(0, 10);
17
71
 
@@ -27,7 +81,10 @@ export function loadTemplate(dieFn) {
27
81
  dieFn(
28
82
  2,
29
83
  `canonical template not found at ${TEMPLATE_PATH} — ` +
30
- `set PROJECTS_BASELINE_DIR env var or check projects-baseline checkout at $HOME/Projects/projects-baseline`,
84
+ `the projects-baseline checkout is optional and private (see docs/baseline.md). ` +
85
+ `Point at it with owner.yaml \`paths.baseline-path\` (host-local, never committed), ` +
86
+ `the SO_BASELINE_PATH env var, or PROJECTS_BASELINE_DIR; a sibling checkout at ` +
87
+ `../projects-baseline is picked up automatically.`,
31
88
  );
32
89
  }
33
90