session-orchestrator 5.11.0 → 5.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (394) hide show
  1. package/.claude-plugin/marketplace.json +12 -1
  2. package/.claude-plugin/plugin.json +4 -1
  3. package/.codex-plugin/agents/explorer.toml +1 -1
  4. package/.codex-plugin/agents/session-reviewer.toml +2 -2
  5. package/.codex-plugin/agents/wave-worker.toml +1 -1
  6. package/.codex-plugin/plugin.json +1 -1
  7. package/.cursor-plugin/plugin.json +1 -1
  8. package/.orchestrator/policy/blocked-commands.json +8 -0
  9. package/.orchestrator/policy/public-export.json +30 -0
  10. package/CHANGELOG.md +180 -3
  11. package/README.md +126 -117
  12. package/agents/analyst.md +7 -2
  13. package/agents/architect-reviewer.md +7 -2
  14. package/agents/code-implementer.md +4 -2
  15. package/agents/db-specialist.md +9 -2
  16. package/agents/dialectic-deriver.md +9 -3
  17. package/agents/docs-writer.md +7 -2
  18. package/agents/eval-judge.md +7 -1
  19. package/agents/qa-strategist.md +7 -2
  20. package/agents/security-reviewer.md +9 -2
  21. package/agents/session-reviewer.md +18 -2
  22. package/agents/skill-applied-judge.md +7 -1
  23. package/agents/test-writer.md +9 -2
  24. package/agents/ui-developer.md +9 -2
  25. package/agents/ux-evaluator.md +6 -1
  26. package/commands/session.md +4 -2
  27. package/commands/templates-ack.md +89 -18
  28. package/docs/agent-authoring.md +30 -11
  29. package/docs/ci-setup.md +38 -2
  30. package/docs/codex-setup.md +33 -9
  31. package/docs/components.md +13 -2
  32. package/docs/cursor-setup.md +3 -3
  33. package/docs/events-schema.md +5 -4
  34. package/docs/evidence-candidates.md +63 -0
  35. package/docs/fleet-checkin-audit.md +7 -4
  36. package/docs/github-mirror-protection.md +150 -3
  37. package/docs/install.md +102 -25
  38. package/docs/instruction-delivery.md +1 -1
  39. package/docs/migration-v4.md +2 -2
  40. package/docs/mods.md +118 -0
  41. package/docs/owner-config-schema.md +1 -0
  42. package/docs/plugin-architecture-v3.md +1 -1
  43. package/docs/pm-skills-marketplace.md +4 -3
  44. package/docs/rule-authoring.md +24 -13
  45. package/docs/scope-collision-guard.md +2 -1
  46. package/docs/session-config-reference.md +55 -21
  47. package/docs/session-config-template.md +32 -24
  48. package/docs/sessions-ledger-repair.md +137 -0
  49. package/docs/vault-docs-architecture.md +1 -1
  50. package/hooks/_lib/hook-import-set.json +406 -73
  51. package/hooks/_lib/hook-root.mjs +301 -0
  52. package/hooks/_lib/lock-bootstrap.mjs +434 -4
  53. package/hooks/_lib/orchestrator-ignore.mjs +3 -0
  54. package/hooks/_lib/subagent-transcript.mjs +2 -2
  55. package/hooks/_lib/vcs-create-matcher.mjs +13 -17
  56. package/hooks/cwd-change-restore.mjs +5 -1
  57. package/hooks/enforce-commands.mjs +4 -3
  58. package/hooks/enforce-scope.mjs +11 -1
  59. package/hooks/hooks-codex.json +48 -2
  60. package/hooks/hooks.json +31 -31
  61. package/hooks/on-session-end.mjs +38 -15
  62. package/hooks/on-session-start.mjs +158 -59
  63. package/hooks/on-stop.mjs +11 -6
  64. package/hooks/post-bash-issue-budget-refund.mjs +6 -1
  65. package/hooks/post-bash-write-verify.mjs +8 -4
  66. package/hooks/post-edit-validate.mjs +45 -52
  67. package/hooks/post-subagent-discovery-validator.mjs +2 -2
  68. package/hooks/post-tool-batch-wave-signal.mjs +8 -4
  69. package/hooks/post-tool-failure-corrective-context.mjs +5 -1
  70. package/hooks/pre-bash-destructive-guard.mjs +220 -8
  71. package/hooks/pre-bash-issue-budget.mjs +6 -1
  72. package/hooks/pre-bash-sessions-ledger-guard.mjs +691 -123
  73. package/hooks/pre-bash-staging-fence.mjs +258 -14
  74. package/hooks/pre-bash-templates-first.mjs +28 -27
  75. package/hooks/run-node.sh +1 -1
  76. package/hooks/skill-invocation-telemetry.mjs +15 -8
  77. package/hooks/subagent-telemetry.mjs +77 -14
  78. package/hooks/wave-scope-commit-guard.mjs +143 -14
  79. package/mods/so-hud/.claude-plugin/plugin.json +11 -0
  80. package/mods/so-hud/README.md +57 -0
  81. package/mods/so-hud/hooks/beacon-io.mjs +101 -0
  82. package/mods/so-hud/hooks/heredoc-scan.ts +394 -0
  83. package/mods/so-hud/hooks/hooks.json +1 -0
  84. package/mods/so-hud/hooks/logic.ts +182 -0
  85. package/mods/so-hud/hooks/register.tsx +670 -0
  86. package/mods/so-hud/hooks/rules.ts +2998 -0
  87. package/mods/so-hud/tsconfig.json +8 -0
  88. package/mods/so-hud/types/index.d.ts +36 -0
  89. package/package.json +4 -3
  90. package/rules/_index.md +1 -1
  91. package/rules/always-on/ask-via-tool.md +1 -1
  92. package/rules/always-on/loop-and-monitor.md +17 -194
  93. package/rules/always-on/parallel-sessions.md +3 -1
  94. package/rules/opt-in-stack/backend-data.md +1 -1
  95. package/rules/opt-in-stack/backend.md +1 -1
  96. package/rules/opt-in-stack/security-web.md +5 -1
  97. package/scripts/agent-usage-census.mjs +79 -0
  98. package/scripts/apply-session-learnings.mjs +34 -6
  99. package/scripts/archive-closed-prds.mjs +21 -5
  100. package/scripts/autopilot.mjs +11 -5
  101. package/scripts/backfill-abandoned-sessions.mjs +13 -3
  102. package/scripts/backfill-evidence-digest.mjs +2 -1
  103. package/scripts/backfill-learnings-from-vault.mjs +61 -6
  104. package/scripts/backfill-sessions.mjs +16 -2
  105. package/scripts/check-package-manager.mjs +26 -8
  106. package/scripts/check-sessions-integrity.mjs +4 -1
  107. package/scripts/ci/assert-vitest-green.mjs +3 -0
  108. package/scripts/ci/windows-smoke.mjs +201 -0
  109. package/scripts/close-precheck.mjs +55 -0
  110. package/scripts/dialectic-deriver.mjs +20 -12
  111. package/scripts/emit-event.mjs +2 -2
  112. package/scripts/emit-session.mjs +239 -20
  113. package/scripts/eval-session.mjs +17 -3
  114. package/scripts/export-hw-learnings.mjs +29 -2
  115. package/scripts/fleet-checkin-audit.mjs +8 -4
  116. package/scripts/fleet-checkin.mjs +73 -3
  117. package/scripts/gc-stale-worktrees.mjs +6 -1
  118. package/scripts/github-export.mjs +128 -0
  119. package/scripts/github-protection-audit.mjs +3 -1
  120. package/scripts/handover-queue-check.mjs +209 -0
  121. package/scripts/harness-audit.mjs +6 -4
  122. package/scripts/lib/agent-frontmatter.mjs +57 -10
  123. package/scripts/lib/agent-status.mjs +125 -7
  124. package/scripts/lib/agent-usage-census.mjs +394 -0
  125. package/scripts/lib/auto-dialectic.mjs +17 -10
  126. package/scripts/lib/auto-dream.mjs +22 -14
  127. package/scripts/lib/autopilot/loop.mjs +3 -1
  128. package/scripts/lib/autopilot/recent-runs.mjs +4 -2
  129. package/scripts/lib/autopilot/stall-sampler.mjs +2 -1
  130. package/scripts/lib/autopilot/telemetry.mjs +5 -1
  131. package/scripts/lib/autopilot/worktree-pipeline.mjs +5 -5
  132. package/scripts/lib/baseline-archetypes.mjs +10 -2
  133. package/scripts/lib/blocked-commands-policy.mjs +4 -3
  134. package/scripts/lib/build-live-signals.mjs +13 -9
  135. package/scripts/lib/ci-status-banner.mjs +329 -24
  136. package/scripts/lib/codex/plugin-contract.mjs +8 -0
  137. package/scripts/lib/cold-start-detector.mjs +5 -2
  138. package/scripts/lib/command-blocker.mjs +603 -43
  139. package/scripts/lib/common.mjs +3 -2
  140. package/scripts/lib/config/cross-repo.mjs +74 -7
  141. package/scripts/lib/config/host-paths.mjs +6 -2
  142. package/scripts/lib/config/remote-hosts.mjs +2 -2
  143. package/scripts/lib/config/section-extractor.mjs +65 -11
  144. package/scripts/lib/config/template-keys.mjs +153 -0
  145. package/scripts/lib/config/vault-integration.mjs +1 -0
  146. package/scripts/lib/config.mjs +3 -3
  147. package/scripts/lib/coordinator-snapshot.mjs +1 -1
  148. package/scripts/lib/discovery/triage-state.mjs +2 -1
  149. package/scripts/lib/dispatcher/rank.mjs +5 -7
  150. package/scripts/lib/ecosystem-health.mjs +2 -2
  151. package/scripts/lib/eval/engine.mjs +3 -2
  152. package/scripts/lib/eval/session-resolve.mjs +6 -2
  153. package/scripts/lib/eval/sink.mjs +4 -3
  154. package/scripts/lib/events-retention-banner.mjs +3 -2
  155. package/scripts/lib/events-schema.mjs +10 -0
  156. package/scripts/lib/events.mjs +23 -12
  157. package/scripts/lib/evolve/autonomy-verdict.mjs +4 -9
  158. package/scripts/lib/evolve/autopilot-effectiveness.mjs +8 -18
  159. package/scripts/lib/fleet-protocol.mjs +21 -0
  160. package/scripts/lib/gates/gate-full.mjs +172 -11
  161. package/scripts/lib/gates/gate-helpers.mjs +236 -23
  162. package/scripts/lib/git-config-drift.mjs +141 -7
  163. package/scripts/lib/github-export.mjs +447 -0
  164. package/scripts/lib/gitlab-portfolio/cli.mjs +16 -5
  165. package/scripts/lib/handover-queue-marks.mjs +159 -0
  166. package/scripts/lib/hardware-pattern-detector.mjs +103 -13
  167. package/scripts/lib/harness-audit/categories/category1.mjs +16 -4
  168. package/scripts/lib/harness-audit/categories/category4.mjs +11 -8
  169. package/scripts/lib/harness-audit/categories/category6.mjs +25 -5
  170. package/scripts/lib/harness-audit/categories/category9.mjs +10 -4
  171. package/scripts/lib/host-identity.mjs +9 -5
  172. package/scripts/lib/http-request.mjs +64 -0
  173. package/scripts/lib/instruction-budget-guard.mjs +30 -22
  174. package/scripts/lib/io.mjs +6 -3
  175. package/scripts/lib/issue-budget-reconcile.mjs +3 -2
  176. package/scripts/lib/issue-budget.mjs +36 -7
  177. package/scripts/lib/learnings/evidence-candidates.mjs +154 -0
  178. package/scripts/lib/learnings/expiry-sweep.mjs +99 -7
  179. package/scripts/lib/learnings/io.mjs +289 -15
  180. package/scripts/lib/learnings/select.mjs +53 -4
  181. package/scripts/lib/learnings/store-role.mjs +209 -0
  182. package/scripts/lib/learnings/surface.mjs +46 -7
  183. package/scripts/lib/ledger-path.mjs +113 -0
  184. package/scripts/lib/maintenance-due-banner.mjs +63 -12
  185. package/scripts/lib/memory-banner.mjs +5 -6
  186. package/scripts/lib/memory-proposals/collector.mjs +5 -2
  187. package/scripts/lib/memory-proposals/sink.mjs +7 -4
  188. package/scripts/lib/memory-proposals/store.mjs +22 -5
  189. package/scripts/lib/mode-selector-accuracy.mjs +2 -1
  190. package/scripts/lib/no-follow-open.mjs +57 -0
  191. package/scripts/lib/orphan-reaper/reaper-audit.mjs +4 -2
  192. package/scripts/lib/own-lock-root.mjs +95 -0
  193. package/scripts/lib/owner-interview.mjs +2 -1
  194. package/scripts/lib/owner-yaml.mjs +70 -7
  195. package/scripts/lib/platform.mjs +161 -9
  196. package/scripts/lib/plugin-update-banner.mjs +15 -11
  197. package/scripts/lib/pre-push-local-partial.mjs +117 -0
  198. package/scripts/lib/pre-push-remote-gate.mjs +151 -25
  199. package/scripts/lib/process-group.mjs +270 -34
  200. package/scripts/lib/project-hygiene.mjs +28 -0
  201. package/scripts/lib/quality-gate.mjs +33 -9
  202. package/scripts/lib/reconcile/backlog.mjs +29 -15
  203. package/scripts/lib/reconcile/engine.mjs +26 -2
  204. package/scripts/lib/reconcile/idempotency.mjs +65 -3
  205. package/scripts/lib/reconcile/rule-expiry-sweep.mjs +53 -12
  206. package/scripts/lib/reconcile/verdict-ledger.mjs +224 -0
  207. package/scripts/lib/reconcile-nudge-banner.mjs +40 -6
  208. package/scripts/lib/resource-probe/evaluate.mjs +32 -9
  209. package/scripts/lib/resource-probe/probe-platform.mjs +3 -1
  210. package/scripts/lib/rule-loader.mjs +20 -10
  211. package/scripts/lib/scope-baseline.mjs +123 -8
  212. package/scripts/lib/scope-echo.mjs +5 -2
  213. package/scripts/lib/scope-gate.mjs +55 -20
  214. package/scripts/lib/session-close-backfill.mjs +75 -7
  215. package/scripts/lib/session-end/close-precheck.mjs +151 -0
  216. package/scripts/lib/session-end/phase-skip.mjs +8 -7
  217. package/scripts/lib/session-end/tail-runner.mjs +7 -2
  218. package/scripts/lib/session-end/vault-orphans.mjs +205 -0
  219. package/scripts/lib/session-id.mjs +9 -1
  220. package/scripts/lib/session-identity/own-session.mjs +63 -0
  221. package/scripts/lib/session-invocation.mjs +14 -3
  222. package/scripts/lib/session-lock.mjs +114 -0
  223. package/scripts/lib/session-record-repair.mjs +245 -35
  224. package/scripts/lib/session-registry.mjs +85 -12
  225. package/scripts/lib/session-schema/constants.mjs +9 -0
  226. package/scripts/lib/session-schema/filters.mjs +56 -1
  227. package/scripts/lib/session-schema/validator.mjs +15 -5
  228. package/scripts/lib/session-start-probes.mjs +149 -22
  229. package/scripts/lib/session-token-rollup.mjs +2 -1
  230. package/scripts/lib/session-work-trees.mjs +166 -0
  231. package/scripts/lib/sessions-canonical.mjs +61 -3
  232. package/scripts/lib/sessions-integrity-banner.mjs +17 -39
  233. package/scripts/lib/sessions-staleness-banner.mjs +8 -5
  234. package/scripts/lib/sh-quote.mjs +1 -0
  235. package/scripts/lib/skill-evidence-window.mjs +5 -1
  236. package/scripts/lib/skill-health/join.mjs +10 -9
  237. package/scripts/lib/skill-invocations-schema.mjs +13 -0
  238. package/scripts/lib/skill-judgments-schema.mjs +6 -2
  239. package/scripts/lib/snapshot-probe.mjs +414 -0
  240. package/scripts/lib/so-hud/collect.mjs +174 -0
  241. package/scripts/lib/so-hud/runtime.mjs +87 -0
  242. package/scripts/lib/spiral-carryover.mjs +8 -1
  243. package/scripts/lib/state-md/frontmatter-mutators.mjs +266 -8
  244. package/scripts/lib/state-md/idle-reset.mjs +248 -0
  245. package/scripts/lib/state-md/mission-status.mjs +5 -1
  246. package/scripts/lib/state-md/recommendations.mjs +26 -4
  247. package/scripts/lib/state-md-peer-guard.mjs +2 -0
  248. package/scripts/lib/state-md.mjs +31 -8
  249. package/scripts/lib/sunset/walker.mjs +4 -3
  250. package/scripts/lib/telemetry/consent.mjs +4 -2
  251. package/scripts/lib/telemetry/queue.mjs +3 -3
  252. package/scripts/lib/telemetry/sync.mjs +45 -9
  253. package/scripts/lib/test-runner/artifact-paths.mjs +10 -2
  254. package/scripts/lib/tmux-layout/layouts.mjs +20 -3
  255. package/scripts/lib/ux-grill/paths.mjs +5 -1
  256. package/scripts/lib/validate/check-agents.mjs +37 -6
  257. package/scripts/lib/validate/check-component-paths.mjs +25 -1
  258. package/scripts/lib/validate/check-guard-requires-parity.mjs +3 -3
  259. package/scripts/lib/validate/check-hooks-symmetry.mjs +53 -32
  260. package/scripts/lib/validate/check-json-files.mjs +3 -1
  261. package/scripts/lib/validate/check-learning-provenance.mjs +3 -2
  262. package/scripts/lib/validate/check-learnings-shell-writes.mjs +10 -4
  263. package/scripts/lib/validate/check-metrics-ledger-root.mjs +234 -0
  264. package/scripts/lib/validate/check-owner-leakage.mjs +159 -11
  265. package/scripts/lib/validate/check-test-posix-shell.mjs +246 -0
  266. package/scripts/lib/validate/check-unwired-features.mjs +4 -2
  267. package/scripts/lib/validate/check-vcs-repo-flag-positional.mjs +58 -0
  268. package/scripts/lib/validate/check-vcs-repo-flag.mjs +259 -73
  269. package/scripts/lib/validate/check-workflow-continue-on-error.mjs +63 -0
  270. package/scripts/lib/validate/confidential-names.mjs +3 -1
  271. package/scripts/lib/vault-mirror/migrate-ids.mjs +321 -0
  272. package/scripts/lib/vault-mirror/namespace.mjs +22 -9
  273. package/scripts/lib/vault-mirror/process.mjs +197 -65
  274. package/scripts/lib/vault-mirror/prune.mjs +5 -2
  275. package/scripts/lib/vault-mirror/render-learnings.mjs +8 -7
  276. package/scripts/lib/vault-mirror/render-sessions.mjs +36 -53
  277. package/scripts/lib/vault-mirror/retention.mjs +2 -2
  278. package/scripts/lib/vault-mirror/utils.mjs +106 -13
  279. package/scripts/lib/vault-staleness-banner.mjs +4 -7
  280. package/scripts/lib/vault-status/board-writer.mjs +34 -5
  281. package/scripts/lib/vault-status/narrative-mirror.mjs +66 -7
  282. package/scripts/lib/wave-executor/foreign-dispatch.mjs +20 -0
  283. package/scripts/lib/win-process-tree.mjs +137 -0
  284. package/scripts/lib/workspace.mjs +1 -1
  285. package/scripts/lib/worktree/constants.mjs +1 -1
  286. package/scripts/lib/worktree/lifecycle.mjs +1 -1
  287. package/scripts/lib/worktree/listing.mjs +1 -1
  288. package/scripts/lib/zx.mjs +50 -0
  289. package/scripts/mcp-server.mjs +16 -7
  290. package/scripts/mcp-server.sh +13 -83
  291. package/scripts/measure-policy-cache-effectiveness.mjs +7 -5
  292. package/scripts/memory-propose.mjs +10 -3
  293. package/scripts/migrate-cold-start-seed.mjs +7 -0
  294. package/scripts/migrate-legacy-learnings.sh +22 -0
  295. package/scripts/migrate-sessions-jsonl.mjs +16 -2
  296. package/scripts/migrate-vault-paths.mjs +29 -13
  297. package/scripts/parse-config.mjs +71 -14
  298. package/scripts/print-learnings-index.mjs +25 -2
  299. package/scripts/release-session-lock.mjs +94 -7
  300. package/scripts/release.mjs +451 -24
  301. package/scripts/relocate-vault-corpus.mjs +11 -7
  302. package/scripts/repair-invalid-sessions.mjs +68 -3
  303. package/scripts/resolve-session-invocation.mjs +57 -8
  304. package/scripts/run-migrate-v2-cross-repo.mjs +56 -1
  305. package/scripts/run-quality-gate.mjs +33 -7
  306. package/scripts/self-update.mjs +56 -7
  307. package/scripts/session-commit-files.mjs +133 -0
  308. package/scripts/site-numbers.mjs +52 -9
  309. package/scripts/snapshot-probe.mjs +156 -0
  310. package/scripts/so-hud.mjs +68 -0
  311. package/scripts/state-md-idle-reset.mjs +82 -0
  312. package/scripts/sweep-expired-learnings.mjs +54 -6
  313. package/scripts/sweep-expired-rules.mjs +4 -1
  314. package/scripts/validate-plugin.mjs +22 -0
  315. package/scripts/vault-integration-watcher.mjs +54 -6
  316. package/scripts/vault-mirror.mjs +9 -0
  317. package/scripts/vault-orphans.mjs +171 -0
  318. package/skills/_shared/fleet-protocol.md +166 -3
  319. package/skills/_shared/instruction-file-resolution.md +1 -1
  320. package/skills/_shared/monitor-patterns.md +44 -25
  321. package/skills/_shared/platform-tools.md +7 -0
  322. package/skills/_shared/state-ownership.md +5 -4
  323. package/skills/architecture/references/domain-model.md +1 -1
  324. package/skills/autopilot/SKILL.md +3 -2
  325. package/skills/bootstrap/SKILL.md +3 -1
  326. package/skills/bootstrap/_shared-template.md +4 -4
  327. package/skills/bootstrap/fast-template.md +1 -1
  328. package/skills/bootstrap/private-contract.md +1 -1
  329. package/skills/bootstrap/references/bootstrap-retroactive-flow.md +17 -9
  330. package/skills/bootstrap/templates/agents/project-code-review.md +5 -1
  331. package/skills/bootstrap/templates/agents/project-discovery.md +5 -1
  332. package/skills/bootstrap/templates/agents/project-quality-gate.md +5 -1
  333. package/skills/claude-md-drift-check/SKILL.md +3 -3
  334. package/skills/claude-md-drift-check/checker.mjs +61 -101
  335. package/skills/close/SKILL.md +20 -7
  336. package/skills/discovery/probes/docs-staleness.mjs +7 -5
  337. package/skills/discovery/probes/frontend-slop.mjs +7 -3
  338. package/skills/discovery/probes/ssot-code-diff.mjs +5 -3
  339. package/skills/discovery/probes/supply-chain-slopcheck.mjs +10 -8
  340. package/skills/discovery/probes/vault-narrative-staleness.mjs +30 -10
  341. package/skills/discovery/probes/vault-staleness.mjs +5 -3
  342. package/skills/discovery/probes-session.md +3 -0
  343. package/skills/discovery/probes-vault.md +2 -0
  344. package/skills/dispatcher/SKILL.md +1 -1
  345. package/skills/eval/SKILL.md +3 -1
  346. package/skills/evolve/SKILL.md +2 -0
  347. package/skills/evolve/references/evolve-analyze-mode.md +3 -3
  348. package/skills/evolve/references/evolve-dialectic-mode.md +1 -1
  349. package/skills/gitlab-ops/SKILL.md +7 -6
  350. package/skills/gitlab-portfolio/SKILL.md +2 -2
  351. package/skills/go/SKILL.md +13 -1
  352. package/skills/npm-publish/SKILL.md +5 -2
  353. package/skills/plan/SKILL.md +23 -10
  354. package/skills/plan/mode-feature.md +13 -8
  355. package/skills/plan/mode-new.md +6 -14
  356. package/skills/plan/prd-feature-template.md +19 -0
  357. package/skills/plan/prd-full-template.md +12 -5
  358. package/skills/plan/prd-premortem-panel.md +48 -0
  359. package/skills/plan/prd-reviewer-prompt.md +11 -3
  360. package/skills/plan/soul.md +1 -1
  361. package/skills/portfolio/SKILL.md +5 -6
  362. package/skills/quality-gates/SKILL.md +2 -4
  363. package/skills/reconcile/SKILL.md +27 -4
  364. package/skills/release/SKILL.md +16 -8
  365. package/skills/session-end/SKILL.md +97 -13
  366. package/skills/session-end/phase-3-2-docs-verification.md +1 -0
  367. package/skills/session-end/phase-3-6-tail.md +11 -7
  368. package/skills/session-end/phase-3-7a-recommendations.md +9 -2
  369. package/skills/session-end/plan-verification.md +16 -1
  370. package/skills/session-end/references/phase-3-7e-vault-orphans.md +63 -0
  371. package/skills/session-end/references/phase-3-documentation-updates.md +25 -13
  372. package/skills/session-end/references/phase-5-issue-cleanup.md +1 -1
  373. package/skills/session-end/references/session-summary-template.md +8 -0
  374. package/skills/session-end/session-metrics-write.md +7 -4
  375. package/skills/session-plan/SKILL.md +5 -3
  376. package/skills/session-start/SKILL.md +10 -4
  377. package/skills/session-start/phase-4-5-resource-health.md +11 -5
  378. package/skills/session-start/phase-7-5-mode-selector.md +3 -1
  379. package/skills/session-start/phase-8-5-express-path.md +4 -3
  380. package/skills/session-start/references/phase-1-2-session-lock.md +7 -5
  381. package/skills/session-start/references/phase-1-5-session-continuity.md +13 -5
  382. package/skills/session-start/references/phase-1-7-vault-status-board.md +1 -1
  383. package/skills/session-start/references/phase-4-ssot-environment-check.md +3 -2
  384. package/skills/tmux-layout/SKILL.md +2 -2
  385. package/skills/vault-sync/validator.mjs +3 -2
  386. package/skills/wave-executor/SKILL.md +5 -4
  387. package/skills/wave-executor/references/wave-executor-state-init.md +3 -3
  388. package/skills/wave-executor/references/wave-loop-dispatch.md +11 -5
  389. package/skills/wave-executor/references/wave-loop-review.md +13 -5
  390. package/skills/wave-executor/references/wave-loop-scope-manifest.md +1 -1
  391. package/AGENTS.md +0 -207
  392. package/assets/og-card.png +0 -0
  393. package/assets/og-card.svg +0 -47
  394. package/assets/wave-lifecycle.svg +0 -98
@@ -139,6 +139,7 @@ import { atomicWriteText, envelopeToError } from './lib/io.mjs';
139
139
  import { validateLearning } from './lib/learnings/schema.mjs';
140
140
  import { validatePathInsideProject } from './lib/path-utils.mjs';
141
141
  import { emitEvolveCompleted } from './lib/learnings/evolve-telemetry.mjs';
142
+ import { resolveLedgerPath, resolveLedgerRoot } from './lib/ledger-path.mjs';
142
143
 
143
144
  const DEFAULT_FILE = '.orchestrator/metrics/learnings.jsonl';
144
145
  const DEFAULT_ARCHIVE = '.orchestrator/metrics/learnings-archive.jsonl';
@@ -257,6 +258,7 @@ function parseArgs(argv) {
257
258
  generation: null,
258
259
  acceptEmbedded: false,
259
260
  };
261
+ let fileExplicit = false;
260
262
  for (let i = 0; i < argv.length; i++) {
261
263
  const a = argv[i];
262
264
  if (a === '--prune') {
@@ -296,6 +298,7 @@ function parseArgs(argv) {
296
298
  args.snapshot = pathValue(a, argv[++i]);
297
299
  } else if (a === '--file') {
298
300
  args.file = pathValue(a, argv[++i]);
301
+ fileExplicit = true;
299
302
  } else if (a === '--archive') {
300
303
  args.archive = pathValue(a, argv[++i]);
301
304
  args.archiveExplicit = true;
@@ -343,6 +346,12 @@ function parseArgs(argv) {
343
346
  }
344
347
  }
345
348
 
349
+ // #1722: the DEFAULT store/archive route through the ledger resolver (main
350
+ // checkout when cwd is a linked worktree that gitignores them); outside one
351
+ // the relative defaults are kept verbatim. Explicit --file/--archive stay.
352
+ if (!fileExplicit) args.file = routedDefault(DEFAULT_FILE);
353
+ if (!args.archiveExplicit) args.archive = routedDefault(DEFAULT_ARCHIVE);
354
+
346
355
  // Mode/flag mismatches are usage errors, never silent no-ops: a `--grace-days`
347
356
  // that the prune path ignores would read as "the grace window applied" in a
348
357
  // transcript, and an `--entries` the sweep ignores would read as "my next
@@ -381,7 +390,7 @@ function parseArgs(argv) {
381
390
  // (macOS /var → /private/var) refused a store inside it. The file goes
382
391
  // through resolveLedger (realpath when it exists), because
383
392
  // canonicalizeRoot realpaths only the root.
384
- if (!validatePathInsideProject(resolveLedger(args.file), args.repoRoot, { canonicalizeRoot: true }).ok) {
393
+ if (ledgerRootOf(args.file, args.repoRoot) === null) {
385
394
  usageError(`--file ${args.file} lies outside --repo-root ${args.repoRoot} — nothing written`);
386
395
  }
387
396
  }
@@ -441,7 +450,9 @@ async function runSweep(args) {
441
450
  } else {
442
451
  process.stdout.write(
443
452
  `sweep-expired-learnings: scanned=${summary.scanned} kept=${summary.kept} ` +
444
- `archived=${summary.archived} dry_run=${summary.dryRun} archive=${summary.archivePath}\n`
453
+ `archived=${summary.archived} dry_run=${summary.dryRun} archive=${summary.archivePath}` +
454
+ // An inbox-role apply (#1516) wrote nothing; say so, as --json does.
455
+ `${summary.skipped ? ` skipped=${summary.skipped}` : ''}\n`
445
456
  );
446
457
  }
447
458
  }
@@ -609,6 +620,39 @@ function resolveLedger(p) {
609
620
  }
610
621
  }
611
622
 
623
+ /**
624
+ * The default ledger for this cwd (#1722): `rel` verbatim unless the resolver
625
+ * maps it elsewhere (linked worktree that gitignores it), then that absolute path.
626
+ *
627
+ * @param {string} rel
628
+ * @returns {string}
629
+ */
630
+ function routedDefault(rel) {
631
+ const routed = resolveLedgerPath(rel, process.cwd());
632
+ return routed === path.resolve(rel) ? rel : routed;
633
+ }
634
+
635
+ /**
636
+ * Which root a store lies inside, canonically (#1500 F4): `repoRoot` itself, or
637
+ * its ledger root (the main checkout of a linked worktree, #1722) — but only for
638
+ * a file inside the DIRECTORY of the mapped default store, never anywhere in
639
+ * that other checkout (a worktree session must not rewrite a foreign tree).
640
+ * `null` when it lies outside both.
641
+ *
642
+ * @param {string} file
643
+ * @param {string} repoRoot
644
+ * @returns {string|null}
645
+ */
646
+ function ledgerRootOf(file, repoRoot) {
647
+ const abs = resolveLedger(file);
648
+ if (validatePathInsideProject(abs, repoRoot, { canonicalizeRoot: true }).ok) return repoRoot;
649
+ const storeDir = path.dirname(resolveLedgerPath(DEFAULT_FILE, repoRoot));
650
+ if (validatePathInsideProject(abs, storeDir, { canonicalizeRoot: true }).ok) {
651
+ return resolveLedgerRoot(DEFAULT_FILE, repoRoot);
652
+ }
653
+ return null;
654
+ }
655
+
612
656
  /**
613
657
  * Exit 1 when `--snapshot` would replace the store or the archive. The rename
614
658
  * lands the header as the store's line 1 with no `.bak` (and drops malformed
@@ -820,7 +864,9 @@ async function runPrune(args) {
820
864
  // hand-assembled `--entries` sidecar) never wrote anything, so it must not
821
865
  // report a completed run either. Best-effort — never changes this
822
866
  // command's exit code or stdout contract.
823
- if (!args.dryRun) {
867
+ // An inbox-role apply (#1516) returns `skipped`: it archived nothing and did
868
+ // not rewrite the store, so there is no completed store write to report.
869
+ if (!args.dryRun && !result.skipped) {
824
870
  await emitEvolveCompleted({
825
871
  repoRoot: args.repoRoot,
826
872
  appended: args.appended,
@@ -841,7 +887,7 @@ async function runPrune(args) {
841
887
  process.stdout.write(
842
888
  `sweep-expired-learnings: prune scanned=${summary.scanned} kept=${summary.kept} ` +
843
889
  `archived=${summary.archived} by_reason=${byReason} dry_run=${summary.dryRun} ` +
844
- `archive=${summary.archivePath}\n`
890
+ `archive=${summary.archivePath}${summary.skipped ? ` skipped=${summary.skipped}` : ''}\n`
845
891
  );
846
892
  }
847
893
  }
@@ -858,7 +904,7 @@ async function runPrune(args) {
858
904
  async function emitMalformedDropped(args, result) {
859
905
  // Both sides canonical, as the parseArgs check judged them: a lexical
860
906
  // relative() across /var vs /private/var reads `../../private/...`.
861
- const root = resolveLedger(args.repoRoot);
907
+ const root = resolveLedger(ledgerRootOf(args.file, args.repoRoot) ?? args.repoRoot);
862
908
  const rel = (p) => path.relative(root, resolveLedger(p));
863
909
  try {
864
910
  const { emitEvent, sessionAttribution } = await import('./lib/events.mjs');
@@ -916,7 +962,9 @@ async function runDropMalformed(args) {
916
962
  process.exit(2);
917
963
  }
918
964
 
919
- if (!args.dryRun) await emitMalformedDropped(args, result);
965
+ // An inbox-role host (#1516) returns the dry-run probe with `skipped` set:
966
+ // nothing was dropped, so no event may claim it was.
967
+ if (!args.dryRun && !result.skipped) await emitMalformedDropped(args, result);
920
968
 
921
969
  const summary = {
922
970
  file: args.file,
@@ -105,6 +105,7 @@ export function parseArgs(argv) {
105
105
  } else if (a === '--learnings') {
106
106
  const raw = argv[++i];
107
107
  if (typeof raw !== 'string' || raw.length === 0) usageError('--learnings requires a path');
108
+ if (path.isAbsolute(raw)) usageError('--learnings requires a repo-relative path');
108
109
  args.learnings = raw;
109
110
  } else if (a === '--help' || a === '-h') {
110
111
  printHelp();
@@ -140,6 +141,7 @@ export function summarize(plan, args, applied) {
140
141
  expired_entries: plan.plans.reduce((n, p) => n + p.expiredPairIds.length, 0),
141
142
  unresolved_pairs: plan.plans.reduce((n, p) => n + p.unresolvedPairIds.length, 0),
142
143
  malformed_lines: plan.malformedLines,
144
+ archive_malformed_lines: plan.archiveMalformedLines,
143
145
  rules_dir_readable: plan.ok,
144
146
  skipped: plan.skipped,
145
147
  plans: plan.plans.map(({ nextContent: _nextContent, ...rest }) => rest),
@@ -178,7 +180,8 @@ async function main(argv) {
178
180
  `(${summary.header_raises} header-raise(s)), ` +
179
181
  `${summary.deletes} delete(s), ${summary.expired_entries} expired entr(ies), ` +
180
182
  `${summary.unresolved_pairs} unresolved pair(s), ${summary.skipped.length} skipped, ` +
181
- `${summary.malformed_lines} malformed learnings line(s)\n`,
183
+ `${summary.malformed_lines} malformed learnings line(s), ` +
184
+ `${summary.archive_malformed_lines} malformed archive line(s)\n`,
182
185
  );
183
186
  for (const s of plan.skipped) process.stdout.write(` skipped ${s.file}: ${s.reason}\n`);
184
187
  for (const p of plan.plans) {
@@ -481,6 +481,11 @@ runCheck('check-learning-provenance.mjs');
481
481
  process.stdout.write('\n');
482
482
  runCheck('check-vcs-repo-flag.mjs');
483
483
 
484
+ // Separate advice contract (#1024): name the PR or drop a rejected repo flag.
485
+ // WARN-only, with tool errors surfaced through FAIL lines as in the bare census.
486
+ process.stdout.write('\n');
487
+ runCheck('check-vcs-repo-flag-positional.mjs');
488
+
484
489
  // WARN-only (#1023): a `gh`/`glab` command cited in docs that no released CLI
485
490
  // ever had — `glab repo edit --visibility`, `glab group list` — costs an
486
491
  // operator a failed command and a re-derivation. The oracle is the CLI's own
@@ -539,6 +544,13 @@ if (runCheck('check-learnings-shell-writes.mjs') !== 0) checkFailed = 1;
539
544
  process.stdout.write('\n');
540
545
  runCheck('check-test-git-config-target.mjs');
541
546
 
547
+ // BLOCKING (#1613): a test that spawns `sh`/`bash` by bare name is red on native
548
+ // Windows (no sh on PATH, bash is WSL) — 301 red tests at fd8d3673. It may block
549
+ // because the backlog was drained in the same change: the live census leaves only
550
+ // three files in open MRs, baselined inside the checker as WARN.
551
+ process.stdout.write('\n');
552
+ if (runCheck('check-test-posix-shell.mjs') !== 0) checkFailed = 1;
553
+
542
554
  // BLOCKING (#1183): every `emitEvent()` call site under hooks/**/*.mjs must be
543
555
  // try/catch-guarded (emitEvent() throws EventValidationError on a malformed
544
556
  // record — an unguarded call aborts the hook process). Five pre-existing
@@ -548,6 +560,16 @@ runCheck('check-test-git-config-target.mjs');
548
560
  process.stdout.write('\n');
549
561
  if (runCheck('check-hooks-emit-event-guard.mjs') !== 0) checkFailed = 1;
550
562
 
563
+ process.stdout.write('\n'); if (runCheck('check-workflow-continue-on-error.mjs') !== 0) checkFailed = 1;
564
+
565
+ // BLOCKING (#1722): a module under scripts/ or hooks/ that builds a
566
+ // `.orchestrator/metrics` path must route it through resolveLedgerPath()
567
+ // (events/sessions: resolveEventsLedgerRoot()). The ledgers are gitignored, so
568
+ // from a linked worktree an unrouted writer leaves a copy that is lost with the
569
+ // worktree. Deliberately local sites sit on the checker's own ALLOWLIST.
570
+ process.stdout.write('\n');
571
+ if (runCheck('check-metrics-ledger-root.mjs') !== 0) checkFailed = 1;
572
+
551
573
  // BLOCKING (#1184): every scripts/lib/validate/check-*.mjs must be referenced
552
574
  // by basename from this file, .husky/pre-commit, or .gitlab-ci.yml — or carry
553
575
  // a `// registration: standalone <reason>` header marker declaring itself
@@ -31,6 +31,9 @@
31
31
  */
32
32
 
33
33
  import { spawnSync } from 'node:child_process';
34
+ import { realpathSync } from 'node:fs';
35
+ import { homedir } from 'node:os';
36
+ import { sep as pathSep } from 'node:path';
34
37
  import { getCrossRepoProjects, getConfinementRoot } from './lib/config/cross-repo.mjs';
35
38
  import { validatePathInsideProject } from './lib/path-utils.mjs';
36
39
  import { resolveRepoSpec, redactUrlCredentials } from './lib/vcs-repo-spec.mjs';
@@ -278,15 +281,59 @@ function buildTickBody(issueMap, depIds, verdict, streak, date) {
278
281
  - Verdict: ${verdict}`;
279
282
  }
280
283
 
284
+ /**
285
+ * Canonical spelling of an absolute path: realpath (native) when it exists, else as given.
286
+ * On macOS the native realpath also returns the on-disk letter case.
287
+ * @param {string} p
288
+ * @returns {string}
289
+ */
290
+ function canonicalPath(p) {
291
+ try {
292
+ return realpathSync.native(p);
293
+ } catch {
294
+ return p;
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Path of `root` relative to the home dir, or null when it is not under home (#1622).
300
+ *
301
+ * Both sides go through realpath first, so symlinks and (on macOS) a differently
302
+ * cased spelling of an existing dir resolve to the same string. A root that does not
303
+ * exist cannot be canonicalised; on darwin/win32 the default volumes are
304
+ * case-insensitive, so the prefix compare is case-folded there as a fallback.
305
+ * Ceiling: a case-sensitive APFS volume could fold two distinct non-existent paths
306
+ * together; the snippet would then name a dir that is not there either way.
307
+ * @param {string} root
308
+ * @returns {string|null} '' when root IS home, the relative rest when inside, else null
309
+ */
310
+ function homeRelative(root) {
311
+ const home = canonicalPath(homedir());
312
+ const canonRoot = canonicalPath(root);
313
+ const fold = process.platform === 'darwin' || process.platform === 'win32';
314
+ const cmp = (s) => (fold ? s.toLocaleLowerCase('en-US') : s);
315
+ if (cmp(canonRoot) === cmp(home)) return '';
316
+ for (const sep of new Set(['/', pathSep])) {
317
+ if (cmp(canonRoot).startsWith(cmp(`${home}${sep}`))) return canonRoot.slice(home.length + 1);
318
+ }
319
+ return null;
320
+ }
321
+
281
322
  /**
282
323
  * Build the flip-ready trigger comment body.
283
324
  * @param {string[]} repos
325
+ * @param {string} root — projects root the repo names resolve against
326
+ * (getConfinementRoot(), so the snippet follows `projects-root`, #1567)
284
327
  * @returns {string}
285
328
  */
286
- function buildFlipBody(repos) {
329
+ function buildFlipBody(repos, root) {
287
330
  // Defense-in-depth: regex+confinement upstream already filter, but shell-quote here
288
331
  // makes the embedded bash safe under any future bypass.
289
332
  const repoList = repos.map(shellQuote).join(' ');
333
+ // The body is posted to a GitLab issue: a root under the home dir is written
334
+ // as "$HOME"/… so the comment never carries the owner's home path (#1490).
335
+ const rel = homeRelative(root);
336
+ const rootQ = rel === null ? shellQuote(root) : rel === '' ? '"$HOME"' : `"$HOME"/${shellQuote(rel)}`;
290
337
  // The sed/add/commit one-liner targets the project-instruction file. Most
291
338
  // ecosystem repos use CLAUDE.md, but Codex-CLI repos use AGENTS.md as a
292
339
  // transparent alias — see skills/_shared/instruction-file-resolution.md.
@@ -296,14 +343,15 @@ function buildFlipBody(repos) {
296
343
 
297
344
  Lokaler Befehl (ein-Liner pro Repo, aber besser per Session orchestriert; CLAUDE.md and AGENTS.md sind Aliase):
298
345
  \`\`\`bash
346
+ root=${rootQ}
299
347
  for d in ${repoList}; do
300
348
  for f in CLAUDE.md AGENTS.md; do
301
- [ -s ~/Projects/$d/$f ] || continue
302
- sed -i.bak 's/mode: warn }/mode: strict }/' ~/Projects/$d/$f
303
- git -C ~/Projects/$d add $f
349
+ [ -s "$root/$d/$f" ] || continue
350
+ sed -i.bak 's/mode: warn }/mode: strict }/' "$root/$d/$f"
351
+ git -C "$root/$d" add "$f"
304
352
  break
305
353
  done
306
- git -C ~/Projects/$d commit -m 'chore(orchestrator): Promote vault-integration to strict mode — closes #305'
354
+ git -C "$root/$d" commit -m 'chore(orchestrator): Promote vault-integration to strict mode — closes #305'
307
355
  done
308
356
  \`\`\``;
309
357
  }
@@ -402,7 +450,7 @@ async function main() {
402
450
  // 7. If streak == 3 (or more), post flip-ready trigger
403
451
  if (newStreak >= 3) {
404
452
  log(`streak=${newStreak} >= 3 — posting flip-ready trigger`);
405
- postComment(TRACKING_ISSUE, buildFlipBody(flipRepos));
453
+ postComment(TRACKING_ISSUE, buildFlipBody(flipRepos, watcherRoot));
406
454
  emit({ action: 'flip-ready-posted', issue: TRACKING_ISSUE });
407
455
  }
408
456
 
@@ -798,6 +798,15 @@ async function main() {
798
798
  // into `total` like any other line, so the run partition still holds. A
799
799
  // malformed archive line is reported and skipped — the archive is secondary
800
800
  // input and must not abort a mirror run that already wrote the live store.
801
+ //
802
+ // Boundary (BV-004, #1685): the sibling `learnings-inbox.jsonl` (#1516) is
803
+ // deliberately NOT a mirror source: the mirror reads only the live store (it
804
+ // checks no store role, and session-end runs it on every host). An inbox
805
+ // record gets its note on the first mirror run after it is ingested into the
806
+ // live store, on whichever host that run happens. Ceiling: an inbox host
807
+ // never mirrors its inbox-only learnings, and every note for an inbox record
808
+ // lags one ingest. Revisit when a real inbox host needs vault notes before
809
+ // ingest.
801
810
  if (kind === 'learning' && basename(resolve(source)) === 'learnings.jsonl') {
802
811
  const archivePath = join(dirname(resolve(source)), 'learnings-archive.jsonl');
803
812
  if (existsSync(archivePath)) {
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * vault-orphans.mjs - CLI for `findVaultOrphans` (#1563, session-end Phase 3.7e).
4
+ *
5
+ * Names the untracked files in the Meta-Vault modified since a given instant
6
+ * (the session's start). Read-only: it never commits, moves or deletes anything
7
+ * in the vault. See scripts/lib/session-end/vault-orphans.mjs for the design.
8
+ *
9
+ * USAGE
10
+ * node scripts/vault-orphans.mjs --since <ISO> [--vault-dir <dir>] [--json]
11
+ *
12
+ * EXIT CODES
13
+ * 0 measured (including zero candidates)
14
+ * 1 bad arguments
15
+ * 2 could not measure (no vault dir resolvable, not a git repo, git failed)
16
+ */
17
+
18
+ import { existsSync, readFileSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+ import { parseArgs } from 'node:util';
21
+
22
+ import { findVaultOrphans } from './lib/session-end/vault-orphans.mjs';
23
+ import { _parseVaultIntegration } from './lib/config/vault-integration.mjs';
24
+ import { loadHostPaths } from './lib/config/host-paths.mjs';
25
+ import { resolveVaultDir } from './lib/config.mjs';
26
+ import { expandTilde } from './lib/common.mjs';
27
+ import { isMainModule } from './lib/is-main-module.mjs';
28
+
29
+ const HELP = `vault-orphans.mjs - name untracked vault files written since a session started (#1563)
30
+
31
+ USAGE
32
+ node scripts/vault-orphans.mjs --since <ISO> [--vault-dir <dir>] [--json]
33
+
34
+ DESCRIPTION
35
+ Runs the read-only \`git -C <vault-dir> ls-files --others --exclude-standard\`
36
+ and lists every untracked file whose mtime is at or after --since. Files whose
37
+ frontmatter names one of this plugin's generators (\`_generator:
38
+ session-orchestrator-...\`) are left out: their writer owns them.
39
+
40
+ The results are CANDIDATES: mtime is a hint, not proof that this session
41
+ wrote the file. Nothing is committed, moved or deleted.
42
+
43
+ OPTIONS
44
+ --since <ISO> Required. ISO 8601 instant with Z or an offset, e.g. the
45
+ STATE.md \`started_at\` (2026-10-11T08:00:00.000Z).
46
+ --vault-dir <dir> Vault working tree. Default: the host-local resolution
47
+ SO_VAULT_DIR > owner.yaml > committed
48
+ vault-integration.vault-dir in ./CLAUDE.md or ./AGENTS.md.
49
+ --json Print the result object as JSON on stdout.
50
+ --help Show this help.
51
+ --version Print the plugin version.
52
+
53
+ EXIT CODES
54
+ 0 measured, including zero candidates
55
+ 1 bad arguments
56
+ 2 could not measure (no vault dir, not a git repo, git failed)
57
+
58
+ EXAMPLE
59
+ node scripts/vault-orphans.mjs --since 2026-10-11T08:00:00.000Z --json
60
+ `;
61
+
62
+ /** ISO 8601 with an explicit zone - a zone-less value would be read as local time. */
63
+ const ISO_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
64
+
65
+ /**
66
+ * Resolve the vault dir like the other vault writers: SO_VAULT_DIR > owner.yaml
67
+ * `vault-dirs:` cwd match > owner.yaml `paths.vault-dir` > committed value.
68
+ *
69
+ * @returns {string|null}
70
+ */
71
+ function resolveDefaultVaultDir() {
72
+ let content = '';
73
+ for (const name of ['CLAUDE.md', 'AGENTS.md']) {
74
+ const p = join(process.cwd(), name);
75
+ if (existsSync(p)) {
76
+ try {
77
+ content = readFileSync(p, 'utf8');
78
+ break;
79
+ } catch {
80
+ /* try the next file */
81
+ }
82
+ }
83
+ }
84
+ const vi = _parseVaultIntegration(content);
85
+ const value = resolveVaultDir(vi['vault-dir'], loadHostPaths()).value;
86
+ return typeof value === 'string' && value.trim() !== '' ? expandTilde(value) : null;
87
+ }
88
+
89
+ /** @param {{status: 'ok', candidates: Array<{path: string}>, total: number, truncated: boolean, untracked: number}} r */
90
+ function renderHuman(r, since) {
91
+ const lines = [];
92
+ if (r.total === 0) {
93
+ lines.push(`vault-orphans: no untracked vault file modified since ${since} (${r.untracked} untracked in total)`);
94
+ } else {
95
+ lines.push(
96
+ `vault-orphans: ${r.total} untracked vault file(s) modified since ${since} - candidates only, ` +
97
+ `mtime does not prove this session wrote them (${r.untracked} untracked in total)`,
98
+ );
99
+ for (const c of r.candidates) lines.push(` ${c.path}`);
100
+ if (r.truncated) lines.push(` ... showing ${r.candidates.length} of ${r.total}`);
101
+ }
102
+ return `${lines.join('\n')}\n`;
103
+ }
104
+
105
+ /**
106
+ * CLI entry.
107
+ *
108
+ * @param {string[]} [argv]
109
+ * @param {{stdout: {write: Function}, stderr: {write: Function}}} [io]
110
+ * @returns {number} exit code
111
+ */
112
+ export function main(argv = process.argv.slice(2), io = { stdout: process.stdout, stderr: process.stderr }) {
113
+ let values;
114
+ try {
115
+ ({ values } = parseArgs({
116
+ args: argv,
117
+ options: {
118
+ since: { type: 'string' },
119
+ 'vault-dir': { type: 'string' },
120
+ json: { type: 'boolean', default: false },
121
+ help: { type: 'boolean', default: false },
122
+ version: { type: 'boolean', default: false },
123
+ },
124
+ strict: true,
125
+ allowPositionals: false,
126
+ }));
127
+ } catch (err) {
128
+ io.stderr.write(`vault-orphans: ${err.message}\nRun with --help for usage.\n`);
129
+ return 1;
130
+ }
131
+
132
+ if (values.help) {
133
+ io.stdout.write(HELP);
134
+ return 0;
135
+ }
136
+ if (values.version) {
137
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
138
+ io.stdout.write(`${pkg.version}\n`);
139
+ return 0;
140
+ }
141
+
142
+ const since = values.since;
143
+ const sinceMs = typeof since === 'string' && ISO_RE.test(since) ? Date.parse(since) : NaN;
144
+ if (!Number.isFinite(sinceMs)) {
145
+ io.stderr.write('vault-orphans: --since <ISO> is required, e.g. 2026-10-11T08:00:00.000Z\n');
146
+ return 1;
147
+ }
148
+ if (values['vault-dir'] !== undefined && values['vault-dir'].trim() === '') {
149
+ io.stderr.write('vault-orphans: --vault-dir must not be empty\n');
150
+ return 1;
151
+ }
152
+
153
+ const vaultDir =
154
+ values['vault-dir'] !== undefined ? expandTilde(values['vault-dir']) : resolveDefaultVaultDir();
155
+ const result = findVaultOrphans({ vaultDir, sinceMs });
156
+
157
+ if (result.status !== 'ok') {
158
+ io.stderr.write(
159
+ `vault-orphans: could not measure (${result.reason}${result.detail ? `: ${result.detail}` : ''})\n`,
160
+ );
161
+ if (values.json) io.stdout.write(`${JSON.stringify(result)}\n`);
162
+ return 2;
163
+ }
164
+
165
+ io.stdout.write(values.json ? `${JSON.stringify(result)}\n` : renderHuman(result, since));
166
+ return 0;
167
+ }
168
+
169
+ if (isMainModule(import.meta.url)) {
170
+ process.exitCode = main();
171
+ }