session-orchestrator 3.16.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 (762) hide show
  1. package/.claude-plugin/marketplace.json +29 -0
  2. package/.claude-plugin/plugin.json +18 -0
  3. package/.codex-plugin/agents/explorer.toml +14 -0
  4. package/.codex-plugin/agents/session-reviewer.toml +23 -0
  5. package/.codex-plugin/agents/wave-worker.toml +15 -0
  6. package/.codex-plugin/config.toml +20 -0
  7. package/.codex-plugin/plugin.json +37 -0
  8. package/.cursor/rules/000-session-orchestrator.mdc +73 -0
  9. package/.cursor/rules/010-session-workflow.mdc +170 -0
  10. package/.cursor/rules/020-quality-gates.mdc +128 -0
  11. package/.cursor/rules/030-wave-execution.mdc +216 -0
  12. package/.cursor/rules/040-discovery.mdc +242 -0
  13. package/.cursor/rules/050-plan.mdc +235 -0
  14. package/.cursor/rules/060-evolve.mdc +232 -0
  15. package/.cursor/rules/070-gitlab-ops.mdc +246 -0
  16. package/.cursor/rules/080-ecosystem-health.mdc +145 -0
  17. package/.mcp.json +8 -0
  18. package/CHANGELOG.md +1544 -0
  19. package/LICENSE +21 -0
  20. package/NOTICE +64 -0
  21. package/README.md +242 -0
  22. package/SECURITY.md +90 -0
  23. package/agents/AGENTS.md +136 -0
  24. package/agents/analyst.md +99 -0
  25. package/agents/architect-reviewer.md +93 -0
  26. package/agents/code-implementer.md +106 -0
  27. package/agents/db-specialist.md +104 -0
  28. package/agents/dialectic-deriver.md +139 -0
  29. package/agents/docs-writer.md +113 -0
  30. package/agents/eval-judge.md +146 -0
  31. package/agents/memory-proposal-collector.md +297 -0
  32. package/agents/qa-strategist.md +102 -0
  33. package/agents/schemas/analyst.schema.json +46 -0
  34. package/agents/schemas/architect-reviewer.schema.json +50 -0
  35. package/agents/schemas/code-implementer.schema.json +61 -0
  36. package/agents/schemas/db-specialist.schema.json +80 -0
  37. package/agents/schemas/docs-writer.schema.json +56 -0
  38. package/agents/schemas/persona-panel-sidecar.schema.json +245 -0
  39. package/agents/schemas/qa-strategist.schema.json +46 -0
  40. package/agents/schemas/security-reviewer.schema.json +86 -0
  41. package/agents/schemas/session-reviewer.schema.json +69 -0
  42. package/agents/schemas/test-writer.schema.json +69 -0
  43. package/agents/schemas/ui-developer.schema.json +90 -0
  44. package/agents/schemas/ux-evaluator.schema.json +51 -0
  45. package/agents/security-reviewer.md +236 -0
  46. package/agents/session-reviewer.md +201 -0
  47. package/agents/skill-applied-judge.md +122 -0
  48. package/agents/test-writer.md +123 -0
  49. package/agents/ui-developer.md +109 -0
  50. package/agents/ux-evaluator.md +161 -0
  51. package/assets/icon.svg +11 -0
  52. package/assets/og-card.png +0 -0
  53. package/assets/og-card.svg +47 -0
  54. package/commands/autopilot-multi.md +74 -0
  55. package/commands/autopilot.md +80 -0
  56. package/commands/bootstrap.md +56 -0
  57. package/commands/brainstorm.md +48 -0
  58. package/commands/close.md +24 -0
  59. package/commands/debug.md +36 -0
  60. package/commands/discovery.md +32 -0
  61. package/commands/dispatcher.md +59 -0
  62. package/commands/eval.md +28 -0
  63. package/commands/evolve.md +10 -0
  64. package/commands/go.md +41 -0
  65. package/commands/grill.md +45 -0
  66. package/commands/harness-audit.md +26 -0
  67. package/commands/memory-cleanup.md +25 -0
  68. package/commands/persona-panel.md +121 -0
  69. package/commands/plan.md +15 -0
  70. package/commands/portfolio.md +97 -0
  71. package/commands/reconcile.md +23 -0
  72. package/commands/repo-audit.md +24 -0
  73. package/commands/session.md +30 -0
  74. package/commands/spinout.md +15 -0
  75. package/commands/sunset-review.md +27 -0
  76. package/commands/templates-ack.md +96 -0
  77. package/commands/test.md +97 -0
  78. package/docs/README.md +105 -0
  79. package/docs/USER-GUIDE.md +1403 -0
  80. package/docs/ci-setup.md +81 -0
  81. package/docs/codex-setup.md +142 -0
  82. package/docs/components.md +74 -0
  83. package/docs/cursor-setup.md +104 -0
  84. package/docs/events-schema.md +81 -0
  85. package/docs/migration-v3.md +148 -0
  86. package/docs/owner-config-schema.md +154 -0
  87. package/docs/persona-panel.md +433 -0
  88. package/docs/pi-setup.md +115 -0
  89. package/docs/plugin-architecture-v3.md +296 -0
  90. package/docs/pm-skills-marketplace.md +114 -0
  91. package/docs/policy-cache-validation-2026-04-28.md +118 -0
  92. package/docs/rule-authoring.md +316 -0
  93. package/docs/session-config-reference.md +1439 -0
  94. package/docs/session-config-template.md +961 -0
  95. package/docs/vault-docs-architecture.md +297 -0
  96. package/hooks/_lib/lock-bootstrap.mjs +272 -0
  97. package/hooks/_lib/lock-reconcile.mjs +93 -0
  98. package/hooks/_lib/profile-gate.mjs +95 -0
  99. package/hooks/_lib/transcript-history.mjs +211 -0
  100. package/hooks/agent-teams-h3-test.sh +362 -0
  101. package/hooks/config-protection.mjs +0 -0
  102. package/hooks/cwd-change-restore.mjs +131 -0
  103. package/hooks/enforce-commands.mjs +179 -0
  104. package/hooks/enforce-scope.mjs +273 -0
  105. package/hooks/hooks-codex.json +60 -0
  106. package/hooks/hooks-cursor.json +15 -0
  107. package/hooks/hooks-pi.json +115 -0
  108. package/hooks/hooks.json +215 -0
  109. package/hooks/loop-guard.mjs +260 -0
  110. package/hooks/on-session-end.mjs +217 -0
  111. package/hooks/on-session-start.mjs +660 -0
  112. package/hooks/on-stop.mjs +294 -0
  113. package/hooks/operator-steer.mjs +64 -0
  114. package/hooks/post-edit-validate.mjs +225 -0
  115. package/hooks/post-subagent-discovery-validator.mjs +398 -0
  116. package/hooks/post-tool-batch-wave-signal.mjs +328 -0
  117. package/hooks/post-tool-failure-corrective-context.mjs +248 -0
  118. package/hooks/post-tooluse-frontend-slop.mjs +184 -0
  119. package/hooks/pre-bash-destructive-guard.mjs +515 -0
  120. package/hooks/pre-bash-memory-propose-audit.mjs +206 -0
  121. package/hooks/pre-bash-staging-fence.mjs +223 -0
  122. package/hooks/pre-bash-templates-first.mjs +404 -0
  123. package/hooks/run-node.sh +72 -0
  124. package/hooks/skill-invocation-telemetry.mjs +99 -0
  125. package/hooks/subagent-telemetry.mjs +249 -0
  126. package/hooks/wave-scope-commit-guard.mjs +191 -0
  127. package/monitors/monitors.json +14 -0
  128. package/output-styles/finding-report.md +48 -0
  129. package/output-styles/session-report.md +53 -0
  130. package/output-styles/wave-summary.md +38 -0
  131. package/package.json +94 -0
  132. package/pi/extensions/session-orchestrator.ts +25 -0
  133. package/pi/prompts/autopilot-multi.md +12 -0
  134. package/pi/prompts/autopilot.md +12 -0
  135. package/pi/prompts/bootstrap.md +12 -0
  136. package/pi/prompts/brainstorm.md +12 -0
  137. package/pi/prompts/close.md +11 -0
  138. package/pi/prompts/debug.md +12 -0
  139. package/pi/prompts/discovery.md +12 -0
  140. package/pi/prompts/dispatcher.md +12 -0
  141. package/pi/prompts/eval.md +12 -0
  142. package/pi/prompts/evolve.md +12 -0
  143. package/pi/prompts/go.md +12 -0
  144. package/pi/prompts/grill.md +12 -0
  145. package/pi/prompts/harness-audit.md +12 -0
  146. package/pi/prompts/memory-cleanup.md +12 -0
  147. package/pi/prompts/persona-panel.md +12 -0
  148. package/pi/prompts/plan.md +12 -0
  149. package/pi/prompts/portfolio.md +12 -0
  150. package/pi/prompts/reconcile.md +12 -0
  151. package/pi/prompts/repo-audit.md +12 -0
  152. package/pi/prompts/session.md +12 -0
  153. package/pi/prompts/spinout.md +12 -0
  154. package/pi/prompts/sunset-review.md +12 -0
  155. package/pi/prompts/templates-ack.md +12 -0
  156. package/pi/prompts/test.md +12 -0
  157. package/rules/_index.md +51 -0
  158. package/rules/always-on/commit-discipline.md +26 -0
  159. package/rules/always-on/npm-quality-gates.md +26 -0
  160. package/rules/always-on/parallel-sessions.md +43 -0
  161. package/rules/opt-in-domain/prompt-caching.md +270 -0
  162. package/rules/opt-in-stack/backend-data.md +188 -0
  163. package/rules/opt-in-stack/backend.md +390 -0
  164. package/rules/opt-in-stack/frontend.md +98 -0
  165. package/rules/opt-in-stack/security-web.md +194 -0
  166. package/rules/opt-in-stack/swift.md +65 -0
  167. package/scripts/archive-closed-prds.mjs +416 -0
  168. package/scripts/autopilot-multi.mjs +802 -0
  169. package/scripts/autopilot.mjs +383 -0
  170. package/scripts/backfill-abandoned-sessions.mjs +265 -0
  171. package/scripts/backfill-learnings-expires.mjs +196 -0
  172. package/scripts/backfill-learnings.mjs +203 -0
  173. package/scripts/backfill-sessions.mjs +282 -0
  174. package/scripts/check-doc-consistency.sh +279 -0
  175. package/scripts/check-package-manager.mjs +445 -0
  176. package/scripts/ci/assert-vitest-green.mjs +267 -0
  177. package/scripts/codex-install.mjs +435 -0
  178. package/scripts/compute-grounding-injection.sh +186 -0
  179. package/scripts/cursor-install.mjs +113 -0
  180. package/scripts/dialectic-deriver.mjs +573 -0
  181. package/scripts/emit-event.mjs +160 -0
  182. package/scripts/emit-session.mjs +212 -0
  183. package/scripts/eval-session.mjs +262 -0
  184. package/scripts/export-hw-learnings.mjs +437 -0
  185. package/scripts/gc-stale-worktrees.mjs +666 -0
  186. package/scripts/generate-pi-prompts.mjs +127 -0
  187. package/scripts/harness-audit.mjs +287 -0
  188. package/scripts/lib/agent-frontmatter.mjs +266 -0
  189. package/scripts/lib/agent-output-schema.mjs +166 -0
  190. package/scripts/lib/agent-status.mjs +303 -0
  191. package/scripts/lib/ajv-loader.mjs +34 -0
  192. package/scripts/lib/auto-dialectic.mjs +382 -0
  193. package/scripts/lib/auto-dream.mjs +471 -0
  194. package/scripts/lib/autonomy/suitability.mjs +212 -0
  195. package/scripts/lib/autopilot/dep-graph.mjs +417 -0
  196. package/scripts/lib/autopilot/durable-telemetry.mjs +121 -0
  197. package/scripts/lib/autopilot/flags.mjs +104 -0
  198. package/scripts/lib/autopilot/kill-switches.mjs +174 -0
  199. package/scripts/lib/autopilot/loop.mjs +320 -0
  200. package/scripts/lib/autopilot/mr-draft.mjs +520 -0
  201. package/scripts/lib/autopilot/multi-killswitch.mjs +184 -0
  202. package/scripts/lib/autopilot/recent-runs.mjs +106 -0
  203. package/scripts/lib/autopilot/stall-sampler.mjs +97 -0
  204. package/scripts/lib/autopilot/telemetry.mjs +224 -0
  205. package/scripts/lib/autopilot/worktree-pipeline.mjs +605 -0
  206. package/scripts/lib/autopilot-telemetry.mjs +11 -0
  207. package/scripts/lib/autopilot.mjs +39 -0
  208. package/scripts/lib/backlog-scan.mjs +179 -0
  209. package/scripts/lib/bootstrap-lock-freshness.mjs +260 -0
  210. package/scripts/lib/bootstrap-lock-refresh.mjs +186 -0
  211. package/scripts/lib/build-live-signals.mjs +150 -0
  212. package/scripts/lib/ci-status-banner.mjs +425 -0
  213. package/scripts/lib/claude-md-budget-lint.mjs +246 -0
  214. package/scripts/lib/cli-flags.mjs +158 -0
  215. package/scripts/lib/codex/plugin-contract.mjs +610 -0
  216. package/scripts/lib/cold-start-detector.mjs +240 -0
  217. package/scripts/lib/command-blocker.mjs +458 -0
  218. package/scripts/lib/common.mjs +333 -0
  219. package/scripts/lib/config/auto-dream.mjs +77 -0
  220. package/scripts/lib/config/block-header.mjs +94 -0
  221. package/scripts/lib/config/broken-window.mjs +114 -0
  222. package/scripts/lib/config/coercers.mjs +248 -0
  223. package/scripts/lib/config/cold-start.mjs +92 -0
  224. package/scripts/lib/config/config-protection.mjs +120 -0
  225. package/scripts/lib/config/cross-repo.mjs +104 -0
  226. package/scripts/lib/config/custom-phases.mjs +213 -0
  227. package/scripts/lib/config/dialectic.mjs +92 -0
  228. package/scripts/lib/config/discovery-validator.mjs +75 -0
  229. package/scripts/lib/config/dispatcher-autonomy-capture.mjs +240 -0
  230. package/scripts/lib/config/dispatcher-autonomy.mjs +152 -0
  231. package/scripts/lib/config/docs-orchestrator.mjs +90 -0
  232. package/scripts/lib/config/docs-staleness.mjs +96 -0
  233. package/scripts/lib/config/drift-check.mjs +155 -0
  234. package/scripts/lib/config/eval.mjs +130 -0
  235. package/scripts/lib/config/events-rotation.mjs +74 -0
  236. package/scripts/lib/config/evolve.mjs +308 -0
  237. package/scripts/lib/config/frontend-slop-hook.mjs +104 -0
  238. package/scripts/lib/config/gitlab-portfolio.mjs +150 -0
  239. package/scripts/lib/config/handover-gate.mjs +106 -0
  240. package/scripts/lib/config/host-paths.mjs +76 -0
  241. package/scripts/lib/config/io.mjs +54 -0
  242. package/scripts/lib/config/loop-guard.mjs +117 -0
  243. package/scripts/lib/config/memory.mjs +150 -0
  244. package/scripts/lib/config/persona-gate-wave.mjs +258 -0
  245. package/scripts/lib/config/reconcile.mjs +205 -0
  246. package/scripts/lib/config/section-extractor.mjs +100 -0
  247. package/scripts/lib/config/skill-evolution.mjs +112 -0
  248. package/scripts/lib/config/slopcheck.mjs +99 -0
  249. package/scripts/lib/config/state-md-lock.mjs +83 -0
  250. package/scripts/lib/config/templates-first.mjs +94 -0
  251. package/scripts/lib/config/test.mjs +113 -0
  252. package/scripts/lib/config/vault-integration.mjs +201 -0
  253. package/scripts/lib/config/vault-mirror-quality.mjs +99 -0
  254. package/scripts/lib/config/vault-staleness.mjs +84 -0
  255. package/scripts/lib/config/vault-sync.mjs +96 -0
  256. package/scripts/lib/config/verification-auto-fix.mjs +84 -0
  257. package/scripts/lib/config/wave-reviewers.mjs +133 -0
  258. package/scripts/lib/config-schema.mjs +345 -0
  259. package/scripts/lib/config.mjs +474 -0
  260. package/scripts/lib/convergence-monitor.mjs +389 -0
  261. package/scripts/lib/coordinator-snapshot.mjs +371 -0
  262. package/scripts/lib/crypto-digest-utils.mjs +91 -0
  263. package/scripts/lib/discovery/helpers.mjs +127 -0
  264. package/scripts/lib/discovery/triage-state.mjs +279 -0
  265. package/scripts/lib/dispatcher/cli.mjs +257 -0
  266. package/scripts/lib/dispatcher/enumerate.mjs +243 -0
  267. package/scripts/lib/dispatcher/rank.mjs +363 -0
  268. package/scripts/lib/ecosystem-health.mjs +224 -0
  269. package/scripts/lib/ecosystem-wizard/ci-detector.mjs +18 -0
  270. package/scripts/lib/ecosystem-wizard/config-parser.mjs +54 -0
  271. package/scripts/lib/ecosystem-wizard/config-writer.mjs +287 -0
  272. package/scripts/lib/ecosystem-wizard/package-manager-detector.mjs +42 -0
  273. package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +246 -0
  274. package/scripts/lib/ecosystem-wizard.mjs +48 -0
  275. package/scripts/lib/env-check.mjs +89 -0
  276. package/scripts/lib/eval/engine.mjs +605 -0
  277. package/scripts/lib/eval/judge.mjs +433 -0
  278. package/scripts/lib/eval/report.mjs +367 -0
  279. package/scripts/lib/eval/schema.mjs +618 -0
  280. package/scripts/lib/eval/session-resolve.mjs +137 -0
  281. package/scripts/lib/eval/sink.mjs +77 -0
  282. package/scripts/lib/events-rotation.mjs +86 -0
  283. package/scripts/lib/events-schema.mjs +81 -0
  284. package/scripts/lib/events.mjs +80 -0
  285. package/scripts/lib/evolve/autonomy-verdict.mjs +461 -0
  286. package/scripts/lib/evolve/autopilot-effectiveness.mjs +293 -0
  287. package/scripts/lib/exclusivity-matrix.mjs +68 -0
  288. package/scripts/lib/fetch-baseline.mjs +311 -0
  289. package/scripts/lib/file-lock.mjs +512 -0
  290. package/scripts/lib/frontend-detect/detect.mjs +138 -0
  291. package/scripts/lib/frontend-detect/rules.mjs +295 -0
  292. package/scripts/lib/frontmatter-guard.mjs +241 -0
  293. package/scripts/lib/gates/echo-stub-detect.mjs +39 -0
  294. package/scripts/lib/gates/gate-baseline.mjs +42 -0
  295. package/scripts/lib/gates/gate-full.mjs +85 -0
  296. package/scripts/lib/gates/gate-helpers.mjs +231 -0
  297. package/scripts/lib/gates/gate-incremental.mjs +76 -0
  298. package/scripts/lib/gates/gate-per-file.mjs +55 -0
  299. package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +447 -0
  300. package/scripts/lib/gitlab-portfolio/aggregator.mjs +383 -0
  301. package/scripts/lib/gitlab-portfolio/cli.mjs +428 -0
  302. package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +289 -0
  303. package/scripts/lib/gitlab-portfolio/vcs-detect.mjs +182 -0
  304. package/scripts/lib/handover-gate.mjs +222 -0
  305. package/scripts/lib/hardening.mjs +43 -0
  306. package/scripts/lib/hardware-pattern-detector.mjs +238 -0
  307. package/scripts/lib/harness-audit/categories/category1.mjs +123 -0
  308. package/scripts/lib/harness-audit/categories/category2.mjs +145 -0
  309. package/scripts/lib/harness-audit/categories/category3.mjs +143 -0
  310. package/scripts/lib/harness-audit/categories/category4.mjs +202 -0
  311. package/scripts/lib/harness-audit/categories/category5.mjs +152 -0
  312. package/scripts/lib/harness-audit/categories/category6.mjs +211 -0
  313. package/scripts/lib/harness-audit/categories/category7.mjs +125 -0
  314. package/scripts/lib/harness-audit/categories/category8.mjs +328 -0
  315. package/scripts/lib/harness-audit/categories/category9.mjs +294 -0
  316. package/scripts/lib/harness-audit/categories/helpers.mjs +165 -0
  317. package/scripts/lib/harness-audit/categories.mjs +19 -0
  318. package/scripts/lib/historical-guard.mjs +15 -0
  319. package/scripts/lib/host-identity.mjs +262 -0
  320. package/scripts/lib/instruction-budget-guard.mjs +332 -0
  321. package/scripts/lib/io.mjs +304 -0
  322. package/scripts/lib/issue-close-strip-labels.mjs +161 -0
  323. package/scripts/lib/language-mappers/README.md +57 -0
  324. package/scripts/lib/language-mappers/index.mjs +165 -0
  325. package/scripts/lib/language-mappers/markdown.mjs +149 -0
  326. package/scripts/lib/language-mappers/python.mjs +249 -0
  327. package/scripts/lib/language-mappers/swift.mjs +201 -0
  328. package/scripts/lib/language-mappers/typescript.mjs +433 -0
  329. package/scripts/lib/learnings/expiry-sweep.mjs +164 -0
  330. package/scripts/lib/learnings/filters.mjs +43 -0
  331. package/scripts/lib/learnings/io.mjs +255 -0
  332. package/scripts/lib/learnings/schema.mjs +518 -0
  333. package/scripts/lib/learnings/surface.mjs +207 -0
  334. package/scripts/lib/learnings.mjs +42 -0
  335. package/scripts/lib/lock-reaper.mjs +648 -0
  336. package/scripts/lib/locks/index.mjs +31 -0
  337. package/scripts/lib/locks/lock-body.mjs +62 -0
  338. package/scripts/lib/locks/staging-fence-lock.mjs +267 -0
  339. package/scripts/lib/locks/state-md-lock.mjs +351 -0
  340. package/scripts/lib/loop-readiness-banner.mjs +144 -0
  341. package/scripts/lib/memory-banner.mjs +478 -0
  342. package/scripts/lib/memory-cleanup/worktree-sweep.mjs +108 -0
  343. package/scripts/lib/memory-cleanup-stamp.mjs +56 -0
  344. package/scripts/lib/memory-paths.mjs +31 -0
  345. package/scripts/lib/memory-proposals/collector.mjs +334 -0
  346. package/scripts/lib/memory-proposals/schema.mjs +289 -0
  347. package/scripts/lib/memory-proposals/sink.mjs +507 -0
  348. package/scripts/lib/memory-proposals/store.mjs +441 -0
  349. package/scripts/lib/mission-status-schema.mjs +114 -0
  350. package/scripts/lib/mode-selector/alternatives.mjs +64 -0
  351. package/scripts/lib/mode-selector/constants.mjs +29 -0
  352. package/scripts/lib/mode-selector/context-pressure.mjs +157 -0
  353. package/scripts/lib/mode-selector/rationale.mjs +55 -0
  354. package/scripts/lib/mode-selector/scoring.mjs +221 -0
  355. package/scripts/lib/mode-selector-accuracy.mjs +121 -0
  356. package/scripts/lib/mode-selector.mjs +160 -0
  357. package/scripts/lib/multi-provider-build/providers.mjs +64 -0
  358. package/scripts/lib/multi-provider-build/templating.mjs +130 -0
  359. package/scripts/lib/named-baseline-resolver.mjs +233 -0
  360. package/scripts/lib/named-vault-resolver.mjs +433 -0
  361. package/scripts/lib/owner-config/coerce.mjs +29 -0
  362. package/scripts/lib/owner-config/constants.mjs +21 -0
  363. package/scripts/lib/owner-config/defaults.mjs +50 -0
  364. package/scripts/lib/owner-config/error.mjs +19 -0
  365. package/scripts/lib/owner-config/index.mjs +13 -0
  366. package/scripts/lib/owner-config/merge.mjs +52 -0
  367. package/scripts/lib/owner-config/validate.mjs +259 -0
  368. package/scripts/lib/owner-config-banner.mjs +126 -0
  369. package/scripts/lib/owner-config-loader.mjs +159 -0
  370. package/scripts/lib/owner-config.example.yaml +72 -0
  371. package/scripts/lib/owner-config.mjs +28 -0
  372. package/scripts/lib/owner-interview.mjs +243 -0
  373. package/scripts/lib/owner-yaml.mjs +571 -0
  374. package/scripts/lib/package-manager.mjs +160 -0
  375. package/scripts/lib/path-utils.mjs +217 -0
  376. package/scripts/lib/peer-cards/merger.mjs +310 -0
  377. package/scripts/lib/peer-cards/reader.mjs +125 -0
  378. package/scripts/lib/peer-cards/schema.mjs +230 -0
  379. package/scripts/lib/peer-cards/staleness-banner.mjs +86 -0
  380. package/scripts/lib/peer-cards/writer.mjs +138 -0
  381. package/scripts/lib/peer-discovery.mjs +200 -0
  382. package/scripts/lib/persona-panel/catalog-loader.mjs +577 -0
  383. package/scripts/lib/persona-panel/consolidator.mjs +370 -0
  384. package/scripts/lib/persona-panel/persona-runner.mjs +375 -0
  385. package/scripts/lib/persona-panel/threshold.mjs +130 -0
  386. package/scripts/lib/pi-hook-bridge.mjs +328 -0
  387. package/scripts/lib/platform.mjs +266 -0
  388. package/scripts/lib/playwright-driver/runner.mjs +297 -0
  389. package/scripts/lib/plugin-root.mjs +210 -0
  390. package/scripts/lib/pre-dispatch-check.mjs +126 -0
  391. package/scripts/lib/product-repo-detect.mjs +121 -0
  392. package/scripts/lib/profiles/registry.mjs +176 -0
  393. package/scripts/lib/profiles/schema.mjs +209 -0
  394. package/scripts/lib/qg-command-drift-banner.mjs +88 -0
  395. package/scripts/lib/quality-gate/diagnostics.mjs +92 -0
  396. package/scripts/lib/quality-gate.mjs +536 -0
  397. package/scripts/lib/quality-gates-cache.mjs +228 -0
  398. package/scripts/lib/quality-gates-policy.mjs +95 -0
  399. package/scripts/lib/recommendations-v0.mjs +156 -0
  400. package/scripts/lib/reconcile/eligibility.mjs +203 -0
  401. package/scripts/lib/reconcile/emitter.mjs +244 -0
  402. package/scripts/lib/reconcile/engine.mjs +412 -0
  403. package/scripts/lib/reconcile/idempotency.mjs +239 -0
  404. package/scripts/lib/reconcile/renderer.mjs +211 -0
  405. package/scripts/lib/reconcile/writer.mjs +293 -0
  406. package/scripts/lib/reconcile-nudge-banner.mjs +284 -0
  407. package/scripts/lib/resource-probe/evaluate.mjs +190 -0
  408. package/scripts/lib/resource-probe/parsers.mjs +181 -0
  409. package/scripts/lib/resource-probe/probe-platform.mjs +300 -0
  410. package/scripts/lib/resource-probe.mjs +95 -0
  411. package/scripts/lib/rule-loader.mjs +552 -0
  412. package/scripts/lib/rules-sync.mjs +439 -0
  413. package/scripts/lib/scope-gate.mjs +496 -0
  414. package/scripts/lib/session-close-backfill.mjs +539 -0
  415. package/scripts/lib/session-discovery.mjs +256 -0
  416. package/scripts/lib/session-end/phase-skip.mjs +357 -0
  417. package/scripts/lib/session-end/worktree-cleanup.mjs +112 -0
  418. package/scripts/lib/session-id.mjs +362 -0
  419. package/scripts/lib/session-lock.mjs +703 -0
  420. package/scripts/lib/session-registry.mjs +355 -0
  421. package/scripts/lib/session-schema/aliases.mjs +71 -0
  422. package/scripts/lib/session-schema/constants.mjs +115 -0
  423. package/scripts/lib/session-schema/normalizer.mjs +66 -0
  424. package/scripts/lib/session-schema/timestamps.mjs +64 -0
  425. package/scripts/lib/session-schema/validator.mjs +453 -0
  426. package/scripts/lib/session-schema.mjs +71 -0
  427. package/scripts/lib/session-token-rollup.mjs +137 -0
  428. package/scripts/lib/sessions-staleness-banner.mjs +247 -0
  429. package/scripts/lib/skill-evolution/blast-radius-classifier.mjs +114 -0
  430. package/scripts/lib/skill-evolution/candidate-intake.mjs +270 -0
  431. package/scripts/lib/skill-evolution/config-validation-gate.mjs +279 -0
  432. package/scripts/lib/skill-evolution/engine.mjs +719 -0
  433. package/scripts/lib/skill-evolution/idempotency.mjs +279 -0
  434. package/scripts/lib/skill-evolution/mr-opener.mjs +507 -0
  435. package/scripts/lib/skill-health/join.mjs +181 -0
  436. package/scripts/lib/skill-health/score.mjs +123 -0
  437. package/scripts/lib/skill-invocations-schema.mjs +214 -0
  438. package/scripts/lib/skill-judge.mjs +348 -0
  439. package/scripts/lib/skill-judgments-schema.mjs +264 -0
  440. package/scripts/lib/slopcheck.mjs +501 -0
  441. package/scripts/lib/soul-resolve.mjs +118 -0
  442. package/scripts/lib/spiral-carryover.mjs +495 -0
  443. package/scripts/lib/state-md/body-sections.mjs +851 -0
  444. package/scripts/lib/state-md/frontmatter-mutators.mjs +453 -0
  445. package/scripts/lib/state-md/mission-status.mjs +247 -0
  446. package/scripts/lib/state-md/recommendations.mjs +57 -0
  447. package/scripts/lib/state-md/yaml-parser.mjs +234 -0
  448. package/scripts/lib/state-md-peer-guard.mjs +232 -0
  449. package/scripts/lib/state-md.mjs +53 -0
  450. package/scripts/lib/subagents-schema.mjs +309 -0
  451. package/scripts/lib/sunset/walker.mjs +1192 -0
  452. package/scripts/lib/test-runner/artifact-paths.mjs +94 -0
  453. package/scripts/lib/test-runner/fingerprint.mjs +33 -0
  454. package/scripts/lib/test-runner/issue-reconcile.mjs +770 -0
  455. package/scripts/lib/tmux-layout/layouts.mjs +224 -0
  456. package/scripts/lib/tmux-layout/telemetry-stats.mjs +100 -0
  457. package/scripts/lib/tmux-layout/telemetry.mjs +88 -0
  458. package/scripts/lib/tmux-layout/tmux-shell.mjs +82 -0
  459. package/scripts/lib/tmux-layout/vcs-detector.mjs +88 -0
  460. package/scripts/lib/validate/check-agents.mjs +457 -0
  461. package/scripts/lib/validate/check-codex-plugin.mjs +37 -0
  462. package/scripts/lib/validate/check-commands.mjs +148 -0
  463. package/scripts/lib/validate/check-component-paths.mjs +112 -0
  464. package/scripts/lib/validate/check-dead-bridge.mjs +180 -0
  465. package/scripts/lib/validate/check-hooks-symmetry.mjs +258 -0
  466. package/scripts/lib/validate/check-json-files.mjs +116 -0
  467. package/scripts/lib/validate/check-owner-leakage.mjs +1011 -0
  468. package/scripts/lib/validate/check-path-utils-canary.mjs +175 -0
  469. package/scripts/lib/validate/check-peekaboo-driver-canary.mjs +201 -0
  470. package/scripts/lib/validate/check-pi-package.mjs +110 -0
  471. package/scripts/lib/validate/check-pi-prompts.mjs +43 -0
  472. package/scripts/lib/validate/check-playwright-mcp-canary.mjs +154 -0
  473. package/scripts/lib/validate/check-plugin-json.mjs +96 -0
  474. package/scripts/lib/validate/check-plugin-monitors.mjs +206 -0
  475. package/scripts/lib/validate/check-plugin-schema.mjs +137 -0
  476. package/scripts/lib/validate/check-rules.mjs +143 -0
  477. package/scripts/lib/validate/check-session-plan-routing.mjs +154 -0
  478. package/scripts/lib/validate/check-test-fixture-shapes.mjs +280 -0
  479. package/scripts/lib/validate/check-unicode-safety.mjs +533 -0
  480. package/scripts/lib/validate/confidential-names.mjs +169 -0
  481. package/scripts/lib/validate/dead-bridge-corpus.mjs +141 -0
  482. package/scripts/lib/validate/dead-bridge-detectors.mjs +568 -0
  483. package/scripts/lib/validate/tier-inference.mjs +100 -0
  484. package/scripts/lib/validate-vendored-rules.mjs +519 -0
  485. package/scripts/lib/vault-archive.mjs +404 -0
  486. package/scripts/lib/vault-backfill/glab.mjs +164 -0
  487. package/scripts/lib/vault-backfill/manifest.mjs +75 -0
  488. package/scripts/lib/vault-backfill/template.mjs +130 -0
  489. package/scripts/lib/vault-consolidate-fs.mjs +331 -0
  490. package/scripts/lib/vault-migration-rules.mjs +155 -0
  491. package/scripts/lib/vault-mirror/auto-commit.mjs +203 -0
  492. package/scripts/lib/vault-mirror/namespace.mjs +152 -0
  493. package/scripts/lib/vault-mirror/process.mjs +567 -0
  494. package/scripts/lib/vault-mirror/pseudonym-map.mjs +164 -0
  495. package/scripts/lib/vault-mirror/render-learnings.mjs +201 -0
  496. package/scripts/lib/vault-mirror/render-sessions.mjs +367 -0
  497. package/scripts/lib/vault-mirror/render.mjs +8 -0
  498. package/scripts/lib/vault-mirror/utils.mjs +217 -0
  499. package/scripts/lib/vault-relocation-rules.mjs +555 -0
  500. package/scripts/lib/vault-repo-backfill.mjs +235 -0
  501. package/scripts/lib/vault-staleness-banner.mjs +142 -0
  502. package/scripts/lib/vault-status/board-writer.mjs +769 -0
  503. package/scripts/lib/vault-status/narrative-mirror.mjs +544 -0
  504. package/scripts/lib/vault-sync-baseline.mjs +152 -0
  505. package/scripts/lib/wave-context.mjs +29 -0
  506. package/scripts/lib/wave-executor/pool.mjs +248 -0
  507. package/scripts/lib/wave-resource-gate.mjs +204 -0
  508. package/scripts/lib/wave-sizing.mjs +75 -0
  509. package/scripts/lib/webhook-url.mjs +105 -0
  510. package/scripts/lib/workspace.mjs +198 -0
  511. package/scripts/lib/worktree/constants.mjs +35 -0
  512. package/scripts/lib/worktree/index.mjs +17 -0
  513. package/scripts/lib/worktree/lifecycle.mjs +287 -0
  514. package/scripts/lib/worktree/listing.mjs +118 -0
  515. package/scripts/lib/worktree/meta.mjs +64 -0
  516. package/scripts/lib/worktree-freshness.mjs +313 -0
  517. package/scripts/lib/worktree.mjs +15 -0
  518. package/scripts/lifecycle-sim-v6.mjs +347 -0
  519. package/scripts/lock-reaper.mjs +185 -0
  520. package/scripts/mcp-server.sh +241 -0
  521. package/scripts/measure-policy-cache-effectiveness.mjs +427 -0
  522. package/scripts/memory-propose.mjs +464 -0
  523. package/scripts/migrate-cold-start-seed.mjs +404 -0
  524. package/scripts/migrate-learnings-jsonl.mjs +189 -0
  525. package/scripts/migrate-legacy-learnings.sh +61 -0
  526. package/scripts/migrate-sessions-jsonl.mjs +448 -0
  527. package/scripts/migrate-subagents-jsonl.mjs +196 -0
  528. package/scripts/migrate-vault-paths.mjs +796 -0
  529. package/scripts/parse-config.mjs +149 -0
  530. package/scripts/pi-install.mjs +117 -0
  531. package/scripts/print-applicable-rules.mjs +247 -0
  532. package/scripts/promote-vault-strict.mjs +496 -0
  533. package/scripts/relocate-vault-corpus.mjs +1178 -0
  534. package/scripts/run-migrate-v2-cross-repo.mjs +385 -0
  535. package/scripts/run-quality-gate.mjs +216 -0
  536. package/scripts/spikes/h3-agent-teams/preflight.sh +53 -0
  537. package/scripts/spikes/h3-agent-teams/run-h3.sh +112 -0
  538. package/scripts/spikes/h3-agent-teams/setup.sh +137 -0
  539. package/scripts/spikes/h3-agent-teams/toggle.sh +38 -0
  540. package/scripts/sweep-expired-learnings.mjs +135 -0
  541. package/scripts/sync-vault-schema.mjs +376 -0
  542. package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +8 -0
  543. package/scripts/tmux-layout.mjs +245 -0
  544. package/scripts/token-audit.sh +191 -0
  545. package/scripts/typecheck.mjs +42 -0
  546. package/scripts/upload-social-preview.mjs +316 -0
  547. package/scripts/validate-config.mjs +46 -0
  548. package/scripts/validate-plugin-manifests.mjs +163 -0
  549. package/scripts/validate-plugin.mjs +264 -0
  550. package/scripts/validate-wave-scope.mjs +289 -0
  551. package/scripts/vault-backfill.mjs +404 -0
  552. package/scripts/vault-consolidate.mjs +596 -0
  553. package/scripts/vault-integration-watcher.mjs +394 -0
  554. package/scripts/vault-mirror.mjs +430 -0
  555. package/skills/_shared/bootstrap-gate.md +111 -0
  556. package/skills/_shared/config-reading.md +226 -0
  557. package/skills/_shared/instruction-file-resolution.md +79 -0
  558. package/skills/_shared/model-selection.md +64 -0
  559. package/skills/_shared/monitor-patterns.md +300 -0
  560. package/skills/_shared/parallel-aware-auq.md +121 -0
  561. package/skills/_shared/parallel-aware-preamble.md +185 -0
  562. package/skills/_shared/platform-tools.md +96 -0
  563. package/skills/_shared/state-ownership.md +221 -0
  564. package/skills/architecture/DEEPENING.md +37 -0
  565. package/skills/architecture/INTERFACE-DESIGN.md +44 -0
  566. package/skills/architecture/LANGUAGE.md +53 -0
  567. package/skills/architecture/SKILL.md +92 -0
  568. package/skills/autopilot/SKILL.md +419 -0
  569. package/skills/bootstrap/SKILL.md +592 -0
  570. package/skills/bootstrap/STATE.md.template +24 -0
  571. package/skills/bootstrap/_shared-template.md +243 -0
  572. package/skills/bootstrap/deep-template.md +659 -0
  573. package/skills/bootstrap/fast-template.md +251 -0
  574. package/skills/bootstrap/intensity-heuristic.md +80 -0
  575. package/skills/bootstrap/public-fallback.md +342 -0
  576. package/skills/bootstrap/standard-template.md +736 -0
  577. package/skills/bootstrap/templates/agents/project-code-review.md +18 -0
  578. package/skills/bootstrap/templates/agents/project-discovery.md +18 -0
  579. package/skills/bootstrap/templates/agents/project-quality-gate.md +18 -0
  580. package/skills/brainstorm/SKILL.md +268 -0
  581. package/skills/brainstorm/soul.md +49 -0
  582. package/skills/claude-md-drift-check/SKILL.md +186 -0
  583. package/skills/claude-md-drift-check/checker.mjs +1380 -0
  584. package/skills/claude-md-drift-check/checker.sh +37 -0
  585. package/skills/claude-md-drift-check/package.json +16 -0
  586. package/skills/convergence-monitoring/README.md +39 -0
  587. package/skills/convergence-monitoring/SIGNALS.md +246 -0
  588. package/skills/convergence-monitoring/SKILL.md +285 -0
  589. package/skills/daily/SKILL.md +222 -0
  590. package/skills/daily/generate.sh +92 -0
  591. package/skills/daily/templates/daily.md.tpl +36 -0
  592. package/skills/debug/SKILL.md +188 -0
  593. package/skills/debug/soul.md +35 -0
  594. package/skills/discovery/SKILL.md +567 -0
  595. package/skills/discovery/issue-templates.md +237 -0
  596. package/skills/discovery/probes/docs-staleness.mjs +195 -0
  597. package/skills/discovery/probes/frontend-slop.mjs +186 -0
  598. package/skills/discovery/probes/ssot-code-diff.mjs +310 -0
  599. package/skills/discovery/probes/supply-chain-slopcheck.mjs +440 -0
  600. package/skills/discovery/probes/vault-narrative-staleness.mjs +355 -0
  601. package/skills/discovery/probes/vault-staleness.mjs +272 -0
  602. package/skills/discovery/probes-arch.md +252 -0
  603. package/skills/discovery/probes-audit.md +95 -0
  604. package/skills/discovery/probes-code.md +329 -0
  605. package/skills/discovery/probes-docs.md +76 -0
  606. package/skills/discovery/probes-feature.md +150 -0
  607. package/skills/discovery/probes-infra.md +138 -0
  608. package/skills/discovery/probes-intro.md +25 -0
  609. package/skills/discovery/probes-session.md +495 -0
  610. package/skills/discovery/probes-supply-chain.md +94 -0
  611. package/skills/discovery/probes-ui.md +147 -0
  612. package/skills/discovery/probes-vault.md +64 -0
  613. package/skills/discovery/slop-patterns.md +115 -0
  614. package/skills/dispatcher/SKILL.md +173 -0
  615. package/skills/docs-orchestrator/SKILL.md +362 -0
  616. package/skills/docs-orchestrator/audience-mapping.md +140 -0
  617. package/skills/domain-model/ADR-FORMAT.md +47 -0
  618. package/skills/domain-model/CONTEXT-FORMAT.md +77 -0
  619. package/skills/domain-model/SKILL.md +85 -0
  620. package/skills/ecosystem-health/SKILL.md +119 -0
  621. package/skills/ecosystem-health/wizard.md +193 -0
  622. package/skills/eval/SKILL.md +293 -0
  623. package/skills/eval/rubric-v1.md +218 -0
  624. package/skills/evolve/SKILL.md +546 -0
  625. package/skills/frontmatter-guard/SKILL.md +126 -0
  626. package/skills/gitlab-ops/SKILL.md +368 -0
  627. package/skills/gitlab-portfolio/SKILL.md +196 -0
  628. package/skills/grill/SKILL.md +185 -0
  629. package/skills/grill/soul.md +55 -0
  630. package/skills/hook-development/SKILL.md +413 -0
  631. package/skills/mcp-builder/SKILL.md +260 -0
  632. package/skills/memory-cleanup/SKILL.md +310 -0
  633. package/skills/mode-selector/SKILL.md +226 -0
  634. package/skills/peekaboo-driver/SKILL.md +237 -0
  635. package/skills/peekaboo-driver/soul.md +32 -0
  636. package/skills/persona-panel/SKILL.md +365 -0
  637. package/skills/persona-panel/persona-format.md +205 -0
  638. package/skills/persona-panel/presets/designer-lens.md +87 -0
  639. package/skills/persona-panel/presets/engineer-lens.md +88 -0
  640. package/skills/persona-panel/presets/pm-lens.md +86 -0
  641. package/skills/plan/SKILL.md +496 -0
  642. package/skills/plan/mode-feature.md +141 -0
  643. package/skills/plan/mode-new.md +297 -0
  644. package/skills/plan/mode-retro.md +271 -0
  645. package/skills/plan/prd-feature-template.md +132 -0
  646. package/skills/plan/prd-full-template.md +151 -0
  647. package/skills/plan/prd-reviewer-prompt.md +103 -0
  648. package/skills/plan/retro-template.md +75 -0
  649. package/skills/plan/soul.md +62 -0
  650. package/skills/playwright-driver/SKILL.md +226 -0
  651. package/skills/playwright-driver/soul.md +30 -0
  652. package/skills/quality-gates/SKILL.md +212 -0
  653. package/skills/reconcile/SKILL.md +324 -0
  654. package/skills/repo-audit/SKILL.md +272 -0
  655. package/skills/session-end/SKILL.md +1044 -0
  656. package/skills/session-end/discovery-scan.md +37 -0
  657. package/skills/session-end/drift-operations.md +97 -0
  658. package/skills/session-end/learning-patterns.md +78 -0
  659. package/skills/session-end/metrics-collection.md +175 -0
  660. package/skills/session-end/phase-3-2-docs-verification.md +148 -0
  661. package/skills/session-end/phase-3-6-tail.md +344 -0
  662. package/skills/session-end/phase-3-7a-recommendations.md +86 -0
  663. package/skills/session-end/plan-verification.md +288 -0
  664. package/skills/session-end/session-metrics-write.md +223 -0
  665. package/skills/session-end/vault-operations.md +50 -0
  666. package/skills/session-end/verification-checklist.md +20 -0
  667. package/skills/session-plan/SKILL.md +554 -0
  668. package/skills/session-plan/wave-template.md +37 -0
  669. package/skills/session-start/SKILL.md +1043 -0
  670. package/skills/session-start/phase-2-5-docs-planning.md +119 -0
  671. package/skills/session-start/phase-4-5-resource-health.md +49 -0
  672. package/skills/session-start/phase-7-1-premise-check.md +47 -0
  673. package/skills/session-start/phase-7-5-mode-selector.md +237 -0
  674. package/skills/session-start/phase-8-5-express-path.md +61 -0
  675. package/skills/session-start/presentation-format.md +81 -0
  676. package/skills/session-start/soul.md +57 -0
  677. package/skills/skill-creator/SKILL.md +168 -0
  678. package/skills/spinout/SKILL.md +76 -0
  679. package/skills/sunset-review/SKILL.md +96 -0
  680. package/skills/test-runner/SKILL.md +362 -0
  681. package/skills/test-runner/rubric-v1.md +388 -0
  682. package/skills/test-runner/soul.md +46 -0
  683. package/skills/tmux-layout/SKILL.md +104 -0
  684. package/skills/ubiquitous-language/SKILL.md +97 -0
  685. package/skills/using-orchestrator/SKILL.md +144 -0
  686. package/skills/vault-mirror/SKILL.md +234 -0
  687. package/skills/vault-sync/SKILL.md +319 -0
  688. package/skills/vault-sync/package-lock.json +40 -0
  689. package/skills/vault-sync/package.json +11 -0
  690. package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +8 -0
  691. package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
  692. package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +8 -0
  693. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
  694. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +8 -0
  695. package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +8 -0
  696. package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +8 -0
  697. package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +10 -0
  698. package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +8 -0
  699. package/skills/vault-sync/tests/fixtures/clean-vault/README.md +3 -0
  700. package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +11 -0
  701. package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
  702. package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +9 -0
  703. package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +8 -0
  704. package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
  705. package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
  706. package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +7 -0
  707. package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +9 -0
  708. package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
  709. package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +11 -0
  710. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +3 -0
  711. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +3 -0
  712. package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
  713. package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +11 -0
  714. package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
  715. package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +11 -0
  716. package/skills/vault-sync/tests/schema-drift.test.mjs +133 -0
  717. package/skills/vault-sync/validator.mjs +658 -0
  718. package/skills/vault-sync/validator.sh +55 -0
  719. package/skills/wave-executor/SKILL.md +496 -0
  720. package/skills/wave-executor/circuit-breaker.md +169 -0
  721. package/skills/wave-executor/wave-loop.md +1043 -0
  722. package/skills/write-executable-plan/SKILL.md +237 -0
  723. package/skills/write-executable-plan/plan-template.md +154 -0
  724. package/templates/_minimal/CLAUDE.md.tmpl +41 -0
  725. package/templates/_minimal/README.md.tmpl +15 -0
  726. package/templates/_minimal/gitignore.tmpl +47 -0
  727. package/templates/_shared/harte-regeln.md +16 -0
  728. package/templates/_shared/loop.md +90 -0
  729. package/templates/_shared/rules/parallel-sessions.md +77 -0
  730. package/templates/nextjs-minimal/README.md +30 -0
  731. package/templates/nextjs-minimal/app/layout.tsx +18 -0
  732. package/templates/nextjs-minimal/app/page.tsx +7 -0
  733. package/templates/nextjs-minimal/eslint.config.mjs +16 -0
  734. package/templates/nextjs-minimal/next.config.mjs +4 -0
  735. package/templates/nextjs-minimal/package.json +27 -0
  736. package/templates/nextjs-minimal/tsconfig.json +23 -0
  737. package/templates/node-minimal/README.md +33 -0
  738. package/templates/node-minimal/eslint.config.mjs +10 -0
  739. package/templates/node-minimal/package.json +21 -0
  740. package/templates/node-minimal/src/index.ts +1 -0
  741. package/templates/node-minimal/tests/sanity.test.ts +5 -0
  742. package/templates/node-minimal/tsconfig.json +17 -0
  743. package/templates/personas/README.md +150 -0
  744. package/templates/personas/accounting-compliance.v1.md +120 -0
  745. package/templates/personas/accounting-tax-advisor.v1.md +116 -0
  746. package/templates/personas/buyer-p1-cto.v1.md +125 -0
  747. package/templates/personas/buyer-p2-kanzlei.v1.md +134 -0
  748. package/templates/personas/buyer-p3-build.v1.md +130 -0
  749. package/templates/personas/buyer-p4-tech-veto.v1.md +130 -0
  750. package/templates/personas/buyer-p5-solo.v1.md +132 -0
  751. package/templates/personas/buyer-p6-ld.v1.md +130 -0
  752. package/templates/personas/klima-ai-expert.v1.md +114 -0
  753. package/templates/personas/klima-physicist.v1.md +117 -0
  754. package/templates/python-uv/README.md +28 -0
  755. package/templates/python-uv/pyproject.toml +32 -0
  756. package/templates/python-uv/src/__PROJECT_NAME__/__init__.py +0 -0
  757. package/templates/python-uv/src/__PROJECT_NAME__/main.py +6 -0
  758. package/templates/python-uv/tests/test_sanity.py +2 -0
  759. package/templates/static-html/README.md +19 -0
  760. package/templates/static-html/index.html +15 -0
  761. package/templates/static-html/script.js +1 -0
  762. package/templates/static-html/styles.css +26 -0
@@ -0,0 +1,1178 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * relocate-vault-corpus.mjs — IO/CLI layer for flat-corpus vault relocation.
4
+ *
5
+ * Moves flat markdown files under 40-learnings/ and 50-sessions/ in a vault
6
+ * into per-repo namespace subdirectories using the classification logic from
7
+ * scripts/lib/vault-relocation-rules.mjs (C1).
8
+ *
9
+ * Usage:
10
+ * node scripts/relocate-vault-corpus.mjs --vault-dir <path> [--dry-run|--apply]
11
+ * [--derivable-only] [--with-backfill] [--repos-root <dir>]
12
+ * [--rollback <manifest>] [--learnings-only|--sessions-only] [--json] [--help]
13
+ *
14
+ * Flags:
15
+ * --vault-dir DIR Path to the vault git repo root (REQUIRED — no default for safety)
16
+ * --dry-run Preview plan, write nothing (DEFAULT; mutex with --apply)
17
+ * --apply Move files via git mv and write reverse-manifest (mutex with --dry-run)
18
+ * --derivable-only Only move files where confident===true; skip _unsorted/redacted-repo/unknown-repo
19
+ * --with-backfill Cross-repo session→repo backfill (Issue #700): read each repo's
20
+ * sessions.jsonl under --repos-root to attribute historical sessions
21
+ * (and, transitively, their learnings) that carry no in-file repo signal.
22
+ * --repos-root DIR Parent dir holding sibling repos to scan for sessions.jsonl
23
+ * (default: parent of --vault-dir). Only used with --with-backfill.
24
+ * --rollback MANIFEST Reverse a previous --apply run given its manifest JSON path
25
+ * --learnings-only Scope to 40-learnings/ only
26
+ * --sessions-only Scope to 50-sessions/ only
27
+ * --json Emit JSONL records on stdout (one per file)
28
+ * --help, -h Show this help
29
+ *
30
+ * Exit codes:
31
+ * 0 Success (including no-op / dry-run)
32
+ * 1 Input/arg error
33
+ * 2 IO error
34
+ *
35
+ * Data → stdout. Diagnostics/summary → stderr.
36
+ *
37
+ * Structural idempotency: flat files at maxdepth 1 are enumerated; files already
38
+ * at depth ≥ 2 (already namespaced) are never enumerated nor moved.
39
+ */
40
+
41
+ import { promises as fs, existsSync } from 'node:fs';
42
+ import { spawnSync } from 'node:child_process';
43
+ import { pathToFileURL } from 'node:url';
44
+ import path from 'node:path';
45
+ import {
46
+ loadVaultRelocationRules,
47
+ VAULT_RELOCATION_RULES_PATH,
48
+ parseRelocationFrontmatter,
49
+ namespaceForSession,
50
+ classifyOwner,
51
+ isConfident,
52
+ computeDest,
53
+ isAlreadyNamespaced,
54
+ _setResolverForTest,
55
+ } from './lib/vault-relocation-rules.mjs';
56
+ import { buildBackfillIndex, parseSessionId } from './lib/vault-repo-backfill.mjs';
57
+ import { parseColumnFlags, CliFlagError } from './lib/cli-flags.mjs';
58
+
59
+ // ---------------------------------------------------------------------------
60
+ // Constants
61
+ // ---------------------------------------------------------------------------
62
+
63
+ const SCRIPT_NAME = 'relocate-vault-corpus';
64
+ const LEARNINGS_SUBDIR = '40-learnings';
65
+ const SESSIONS_SUBDIR = '50-sessions';
66
+ const MANIFEST_DIR = '.orchestrator';
67
+ const SCHEMA_VERSION = 1;
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Arg parsing
71
+ // ---------------------------------------------------------------------------
72
+
73
+ function parseArgs(argv) {
74
+ let parsedFlags;
75
+ try {
76
+ parsedFlags = parseColumnFlags({
77
+ argv: argv.slice(2),
78
+ knownBool: {
79
+ help: { short: 'h', default: false },
80
+ apply: false,
81
+ 'dry-run': false,
82
+ json: false,
83
+ 'derivable-only': false,
84
+ 'with-backfill': false,
85
+ 'learnings-only': false,
86
+ 'sessions-only': false,
87
+ },
88
+ knownString: {
89
+ 'vault-dir': null,
90
+ 'repos-root': null,
91
+ rollback: null,
92
+ },
93
+ });
94
+ } catch (err) {
95
+ if (err instanceof CliFlagError) {
96
+ process.stderr.write(`${SCRIPT_NAME}: ${err.message}\n`);
97
+ process.exit(1);
98
+ }
99
+ throw err;
100
+ }
101
+
102
+ const values = parsedFlags.values;
103
+
104
+ // Mutex check: --dry-run and --apply on the same invocation
105
+ const applyFlag = values.apply === true;
106
+ const dryRunFlag = values['dry-run'] === true;
107
+ if (applyFlag && dryRunFlag) {
108
+ process.stderr.write(`${SCRIPT_NAME}: --dry-run and --apply are mutually exclusive\n`);
109
+ process.exit(1);
110
+ }
111
+
112
+ // --learnings-only and --sessions-only are mutually exclusive
113
+ if (values['learnings-only'] && values['sessions-only']) {
114
+ process.stderr.write(`${SCRIPT_NAME}: --learnings-only and --sessions-only are mutually exclusive\n`);
115
+ process.exit(1);
116
+ }
117
+
118
+ return {
119
+ help: values.help === true,
120
+ apply: applyFlag,
121
+ json: values.json === true,
122
+ derivableOnly: values['derivable-only'] === true,
123
+ withBackfill: values['with-backfill'] === true,
124
+ reposRoot: values['repos-root'] ?? null,
125
+ rollback: values.rollback ?? null,
126
+ vaultDir: values['vault-dir'] ?? null,
127
+ learningsOnly: values['learnings-only'] === true,
128
+ sessionsOnly: values['sessions-only'] === true,
129
+ };
130
+ }
131
+
132
+ function printHelp() {
133
+ process.stdout.write(
134
+ `Usage: ${SCRIPT_NAME}.mjs --vault-dir <path> [--dry-run|--apply]
135
+ [--derivable-only] [--with-backfill] [--repos-root <dir>]
136
+ [--rollback <manifest>] [--learnings-only|--sessions-only] [--json] [--help]
137
+
138
+ Moves flat markdown files from 40-learnings/ and 50-sessions/ roots in a vault
139
+ git repo into per-repo namespace subdirectories.
140
+
141
+ Flat files at depth 1 are enumerated and classified; files already in a
142
+ subdirectory (depth >= 2, already namespaced) are never touched.
143
+
144
+ REQUIRED:
145
+ --vault-dir DIR Path to the vault git repo root
146
+
147
+ MODES (default: --dry-run):
148
+ --dry-run Preview plan, write nothing (DEFAULT)
149
+ --apply Move files via git mv, write reverse-manifest (STAGE ONLY — no commit)
150
+ --rollback MANIFEST Reverse a previous --apply run given its manifest JSON path
151
+
152
+ FILTERS:
153
+ --derivable-only Only move files where confident===true; skip _unsorted/redacted-repo/unknown-repo
154
+ --learnings-only Scope to 40-learnings/ only
155
+ --sessions-only Scope to 50-sessions/ only
156
+
157
+ BACKFILL (Issue #700 — cross-repo session→repo attribution):
158
+ --with-backfill Read each sibling repo's sessions.jsonl to attribute
159
+ historical sessions (and transitively their learnings)
160
+ that carry NO in-file repo signal. Default OFF — when
161
+ absent, no sessions.jsonl is read and output is
162
+ byte-identical to the non-backfill run.
163
+ --repos-root DIR Parent dir holding sibling repos to scan
164
+ (default: parent of --vault-dir). Only used with --with-backfill.
165
+
166
+ OUTPUT:
167
+ --json Emit JSONL records on stdout (one per file)
168
+ --help, -h Show this help
169
+
170
+ Exit codes:
171
+ 0 Success (including no-op / dry-run)
172
+ 1 Input/arg error
173
+ 2 IO error
174
+
175
+ Data goes to stdout; diagnostics and summary go to stderr.
176
+
177
+ WARNING: This tool operates on a git repo (--vault-dir). Run with --dry-run
178
+ first to verify the plan. The --apply mode stages moves (git mv) but does NOT
179
+ commit — the operator must review and commit separately.
180
+
181
+ The reverse-manifest is written to <vault-dir>/.orchestrator/relocation-manifest-<ISO>.json
182
+ on a successful --apply run and can be used with --rollback to undo.
183
+
184
+ Config: ${VAULT_RELOCATION_RULES_PATH}
185
+ `,
186
+ );
187
+ }
188
+
189
+ // ---------------------------------------------------------------------------
190
+ // Flat-file discovery (maxdepth 1 only — structural idempotency)
191
+ // ---------------------------------------------------------------------------
192
+
193
+ /**
194
+ * Enumerate flat markdown files directly under <dir> (maxdepth 1, files only).
195
+ * Files at depth >= 2 (already in a subdirectory) are never returned.
196
+ *
197
+ * @param {string} dir - absolute path to the corpus subdir (e.g. vault/40-learnings)
198
+ * @returns {string[]} absolute file paths of flat .md files
199
+ */
200
+ function enumerateFlatFiles(dir) {
201
+ if (!existsSync(dir)) return [];
202
+
203
+ const result = spawnSync(
204
+ 'find',
205
+ [dir, '-maxdepth', '1', '-type', 'f', '-name', '*.md'],
206
+ { encoding: 'utf8' },
207
+ );
208
+
209
+ if (result.error) {
210
+ process.stderr.write(
211
+ `${SCRIPT_NAME}: find not found on PATH (${result.error.code ?? result.error.message})\n`,
212
+ );
213
+ process.exit(2);
214
+ }
215
+ if (result.status !== 0) {
216
+ process.stderr.write(`${SCRIPT_NAME}: find failed: ${result.stderr}\n`);
217
+ process.exit(2);
218
+ }
219
+
220
+ return result.stdout
221
+ .split('\n')
222
+ .map((l) => l.trim())
223
+ .filter(Boolean);
224
+ }
225
+
226
+ // ---------------------------------------------------------------------------
227
+ // Cross-repo sessions.jsonl indices (CLI-side IO — only built under --with-backfill)
228
+ // ---------------------------------------------------------------------------
229
+
230
+ /**
231
+ * Build the authoritative cross-repo backfill indices by scanning each sibling
232
+ * repo's `.orchestrator/metrics/sessions.jsonl` under reposRoot.
233
+ *
234
+ * sidIndex: Map<session_id, Set<repoSlug>>
235
+ * bdIndex: Map<`${branch}|${date}`, Set<repoSlug>>
236
+ *
237
+ * Immediate subdirectories of reposRoot are enumerated; `Archiv` and any
238
+ * dot-prefixed name are excluded. Malformed JSONL lines are skipped (never throw
239
+ * the whole run). The repo identity stored in the indices is the repo's CANONICAL
240
+ * slug derived from its git origin remote (`repoCanonicalSlug`), NOT the directory
241
+ * basename — so a backfilled session lands in the SAME `<repo>/` namespace as that
242
+ * repo's own native vault-mirror writes (which use `deriveRepo()` off the same
243
+ * remote). Using the basename would split e.g. `AcmeWidgetV2/` → `acmewidgetv2`
244
+ * while source-repo:/repo:-carrying notes use the canonical `acme-widget-v2`. Leak-guarding
245
+ * still happens later in the pure backfill module.
246
+ *
247
+ * @param {string} reposRoot - absolute path to the parent dir of sibling repos
248
+ * @returns {{ sidIndex: Map<string, Set<string>>, bdIndex: Map<string, Set<string>>, reposScanned: number }}
249
+ */
250
+ function repoCanonicalSlug(reposRoot, name) {
251
+ // Derive the repo's canonical namespace identity from its git origin remote so
252
+ // it matches the slug `deriveRepo()` would produce for that repo's own writes.
253
+ // Falls back to the directory basename when there is no readable remote.
254
+ try {
255
+ const res = spawnSync('git', ['-C', path.join(reposRoot, name), 'config', '--get', 'remote.origin.url'], {
256
+ encoding: 'utf8',
257
+ });
258
+ const url = (res.stdout || '').trim();
259
+ if (res.status === 0 && url) {
260
+ const seg = url.replace(/\.git$/, '').split(/[/:]/).filter(Boolean).pop();
261
+ if (seg) return seg;
262
+ }
263
+ } catch {
264
+ // fall through to basename
265
+ }
266
+ return name;
267
+ }
268
+
269
+ async function buildCrossRepoIndices(reposRoot) {
270
+ const sidIndex = new Map();
271
+ const bdIndex = new Map();
272
+ let reposScanned = 0;
273
+
274
+ if (!existsSync(reposRoot)) {
275
+ process.stderr.write(`${SCRIPT_NAME}: WARN: --repos-root not found: ${reposRoot} — backfill indices empty\n`);
276
+ return { sidIndex, bdIndex, reposScanned };
277
+ }
278
+
279
+ let entries;
280
+ try {
281
+ entries = await fs.readdir(reposRoot, { withFileTypes: true });
282
+ } catch (err) {
283
+ process.stderr.write(`${SCRIPT_NAME}: WARN: could not read --repos-root ${reposRoot}: ${err.message} — backfill indices empty\n`);
284
+ return { sidIndex, bdIndex, reposScanned };
285
+ }
286
+
287
+ for (const dirent of entries) {
288
+ if (!dirent.isDirectory()) continue;
289
+ const name = dirent.name;
290
+ if (name === 'Archiv' || name.startsWith('.')) continue;
291
+
292
+ const jsonlPath = path.join(reposRoot, name, '.orchestrator', 'metrics', 'sessions.jsonl');
293
+ if (!existsSync(jsonlPath)) continue;
294
+
295
+ let content;
296
+ try {
297
+ content = await fs.readFile(jsonlPath, 'utf8');
298
+ } catch (err) {
299
+ process.stderr.write(`${SCRIPT_NAME}: WARN: skipping ${name}/sessions.jsonl (read error: ${err.message})\n`);
300
+ continue;
301
+ }
302
+
303
+ reposScanned++;
304
+
305
+ // Canonical namespace identity from the git remote (NOT the basename) so a
306
+ // backfilled session shares the folder with that repo's own native writes.
307
+ const repoId = repoCanonicalSlug(reposRoot, name);
308
+
309
+ for (const rawLine of content.split('\n')) {
310
+ const line = rawLine.trim();
311
+ if (!line) continue;
312
+
313
+ let record;
314
+ try {
315
+ record = JSON.parse(line);
316
+ } catch {
317
+ // Malformed JSONL line — skip and continue (never abort the whole run).
318
+ continue;
319
+ }
320
+ if (!record || typeof record !== 'object') continue;
321
+
322
+ const sessionId = typeof record.session_id === 'string' ? record.session_id.trim() : '';
323
+ if (!sessionId) continue;
324
+
325
+ // sid index — the authoritative join (HIGH tier in the backfill module)
326
+ addToSetIndex(sidIndex, sessionId, repoId);
327
+
328
+ // branch/date index — the MEDIUM-tier fallback
329
+ const parsed = parseSessionId(sessionId);
330
+ const branch =
331
+ (typeof record.branch === 'string' && record.branch.trim()) || (parsed ? parsed.branch : null);
332
+ const date =
333
+ (parsed ? parsed.date : null) ||
334
+ (typeof record.started_at === 'string' ? record.started_at.slice(0, 10) : null);
335
+
336
+ // Skip records where no (branch,date) signal can be derived — the bdIndex
337
+ // key requires both. A bare date with no branch is not a usable key.
338
+ if (branch && date && /^\d{4}-\d{2}-\d{2}$/.test(date)) {
339
+ addToSetIndex(bdIndex, `${branch}|${date}`, repoId);
340
+ }
341
+ }
342
+ }
343
+
344
+ return { sidIndex, bdIndex, reposScanned };
345
+ }
346
+
347
+ /**
348
+ * Learn an authoritative `repoIdentity → canonicalNamespace` map from the vault's
349
+ * OWN `source-repo:`/`repo:`-carrying session notes. A repo dir without a readable git remote
350
+ * (so `repoCanonicalSlug` fell back to its CamelCase basename, e.g. `AcmeWidgetV2`
351
+ * → `acmewidgetv2`) cannot derive the hyphenated canonical slug (`acme-widget-v2`)
352
+ * from its name alone. But if that dir's `sessions.jsonl` contains the id of a vault
353
+ * session that DOES carry a `source-repo:` or `repo:` field, that session's resolved namespace is the
354
+ * ground-truth canonical for the dir — so backfilled repo-less sessions in the same
355
+ * dir land in the SAME `<repo>/` folder instead of a split CamelCase variant.
356
+ *
357
+ * Only UNANIMOUS mappings are learned; an identity whose source-repo:/repo:
358
+ * sessions disagree on the namespace is left unmapped (ambiguous → no remap).
359
+ *
360
+ * @param {{ sidIndex: Map<string, Set<string>>, parsedVaultSessions: Array<{id:string, frontmatter:object}> }} args
361
+ * @returns {Map<string, string>} identity → canonical namespace
362
+ */
363
+ function learnCanonicalMap({ sidIndex, parsedVaultSessions }) {
364
+ // Strip case + every separator so two slug FORMS of the SAME repo compare equal:
365
+ // 'AcmeWidgetV2' → 'acmewidgetv2' === 'acme-widget-v2' → 'acmewidgetv2'.
366
+ const normalize = (s) => String(s).toLowerCase().replace(/[^a-z0-9]/g, '');
367
+ const votes = new Map(); // identity → Map<canonicalNs, count>
368
+ for (const { id, frontmatter } of parsedVaultSessions) {
369
+ if (!id || !frontmatter || !hasSessionRepoSignal(frontmatter)) continue;
370
+ const { namespace } = namespaceForSession(frontmatter); // source-repo:/repo: → canonical
371
+ if (!isConfident(namespace)) continue; // skip _unsorted/redacted-repo/unknown-repo
372
+ const identities = sidIndex.get(id);
373
+ if (!identities) continue;
374
+ // Only an UNAMBIGUOUS source-repo:/repo: note teaches: if its id appears in >1 repo's jsonl,
375
+ // it would mis-teach every other identity its own namespace (collision pollution).
376
+ if (identities.size !== 1) continue;
377
+ for (const identity of identities) {
378
+ if (identity === namespace) continue; // already canonical — nothing to learn
379
+ // GUARD against coincidental session-id collisions: session_ids are NOT globally
380
+ // unique, so a size-1 match can still be the WRONG repo (a different repo that
381
+ // happens to share the id string). Only learn when identity and namespace are
382
+ // the same repo in a different slug FORM (CamelCase basename vs hyphenated slug).
383
+ if (normalize(identity) !== normalize(namespace)) continue;
384
+ let m = votes.get(identity);
385
+ if (!m) {
386
+ m = new Map();
387
+ votes.set(identity, m);
388
+ }
389
+ m.set(namespace, (m.get(namespace) || 0) + 1);
390
+ }
391
+ }
392
+ const learned = new Map();
393
+ for (const [identity, m] of votes) {
394
+ if (m.size === 1) learned.set(identity, [...m.keys()][0]); // unanimous only
395
+ }
396
+ return learned;
397
+ }
398
+
399
+ function hasSessionRepoSignal(frontmatter) {
400
+ return hasStringSignal(frontmatter['source-repo']) || hasStringSignal(frontmatter.repo);
401
+ }
402
+
403
+ function hasStringSignal(value) {
404
+ return typeof value === 'string' && value.trim() !== '';
405
+ }
406
+
407
+ /**
408
+ * Rewrite the cross-repo index identities through the learned canonical map (and
409
+ * dedup the resulting sets — two raw dirs that canonicalise to the same repo are
410
+ * one repo). No-op when `learned` is empty (e.g. no `--with-backfill`, or a fixture
411
+ * with no source-repo:/repo:-carrying sessions), preserving byte-identical behaviour.
412
+ */
413
+ function remapIndices({ sidIndex, bdIndex }, learned) {
414
+ if (learned.size === 0) return { sidIndex, bdIndex };
415
+ const remapSet = (set) => {
416
+ const out = new Set();
417
+ for (const member of set) out.add(learned.get(member) ?? member);
418
+ return out;
419
+ };
420
+ const sid2 = new Map();
421
+ for (const [k, set] of sidIndex) sid2.set(k, remapSet(set));
422
+ const bd2 = new Map();
423
+ for (const [k, set] of bdIndex) bd2.set(k, remapSet(set));
424
+ return { sidIndex: sid2, bdIndex: bd2 };
425
+ }
426
+
427
+ /**
428
+ * Append a value to a Set under a key in a Map<string, Set<string>>, creating the
429
+ * Set on first insert.
430
+ *
431
+ * @param {Map<string, Set<string>>} map
432
+ * @param {string} key
433
+ * @param {string} value
434
+ */
435
+ function addToSetIndex(map, key, value) {
436
+ let set = map.get(key);
437
+ if (!set) {
438
+ set = new Set();
439
+ map.set(key, set);
440
+ }
441
+ set.add(value);
442
+ }
443
+
444
+ /**
445
+ * Parse every vault session note (flat + already-namespaced) into the
446
+ * `{ id, frontmatter }` shape that buildBackfillIndex consumes. Reuses the same
447
+ * recursive find pass that buildSessionRepoIndex uses, so session files are read
448
+ * once per concern (this pass is only run under --with-backfill).
449
+ *
450
+ * @param {string} sessionsDir - absolute path to 50-sessions/
451
+ * @returns {Promise<Array<{ id: string, frontmatter: object }>>}
452
+ */
453
+ async function collectParsedVaultSessions(sessionsDir) {
454
+ const out = [];
455
+ if (!existsSync(sessionsDir)) return out;
456
+
457
+ const result = spawnSync(
458
+ 'find',
459
+ [sessionsDir, '-type', 'f', '-name', '*.md'],
460
+ { encoding: 'utf8' },
461
+ );
462
+ if (result.error || result.status !== 0) {
463
+ process.stderr.write(`${SCRIPT_NAME}: WARN: could not scan sessions for backfill: ${result.stderr || result.error?.message || 'unknown'}\n`);
464
+ return out;
465
+ }
466
+
467
+ const files = result.stdout
468
+ .split('\n')
469
+ .map((l) => l.trim())
470
+ .filter(Boolean);
471
+
472
+ for (const filePath of files) {
473
+ let content;
474
+ try {
475
+ content = await fs.readFile(filePath, 'utf8');
476
+ } catch {
477
+ continue;
478
+ }
479
+ const frontmatter = parseRelocationFrontmatter(content);
480
+ // Prefer the frontmatter id; fall back to the basename (the session id by convention).
481
+ const id =
482
+ (typeof frontmatter.id === 'string' && frontmatter.id.trim()) ||
483
+ path.basename(filePath).replace(/\.md$/, '');
484
+ if (id) out.push({ id, frontmatter });
485
+ }
486
+
487
+ return out;
488
+ }
489
+
490
+ // ---------------------------------------------------------------------------
491
+ // Session→repo index builder
492
+ // ---------------------------------------------------------------------------
493
+
494
+ /**
495
+ * Build a Map<sessionId, namespace> by scanning all session files — both flat
496
+ * root AND existing <repo>/ subdirs (which carry frontmatter source-repo:/repo:).
497
+ *
498
+ * The index is passed to namespaceForLearning so it can resolve
499
+ * source_session links to their owning namespace.
500
+ *
501
+ * When a backfillIndex is supplied (--with-backfill, Issue #700), it is forwarded
502
+ * to namespaceForSession so backfilled sessions (no in-file repo signal) resolve
503
+ * to a confident namespace and land in THIS index — which transitively lifts their
504
+ * learnings via namespaceForLearning's source_session resolution.
505
+ *
506
+ * @param {string} sessionsDir - absolute path to 50-sessions/
507
+ * @param {{ backfillIndex?: Map<string, { repo: string, confidence: string, source: string }> }} [opts]
508
+ * @returns {Map<string, string>} sessionId → namespace
509
+ */
510
+ async function buildSessionRepoIndex(sessionsDir, opts = {}) {
511
+ const { backfillIndex } = opts;
512
+ const index = new Map();
513
+ if (!existsSync(sessionsDir)) return index;
514
+
515
+ // All session .md files: flat + in subdirs (already-namespaced)
516
+ const result = spawnSync(
517
+ 'find',
518
+ [sessionsDir, '-type', 'f', '-name', '*.md'],
519
+ { encoding: 'utf8' },
520
+ );
521
+
522
+ if (result.error || result.status !== 0) {
523
+ // Non-fatal: return empty index, but warn so the operator knows source_session resolution is disabled
524
+ process.stderr.write(`${SCRIPT_NAME}: WARN: could not scan sessions for index — transitive source_session resolution disabled: ${result.stderr || result.error?.message || 'unknown'}\n`);
525
+ return index;
526
+ }
527
+
528
+ const files = result.stdout
529
+ .split('\n')
530
+ .map((l) => l.trim())
531
+ .filter(Boolean);
532
+
533
+ for (const filePath of files) {
534
+ let content;
535
+ try {
536
+ content = await fs.readFile(filePath, 'utf8');
537
+ } catch (e) {
538
+ process.stderr.write(`${SCRIPT_NAME}: WARN: skipping session file for index (${path.basename(filePath)}): ${e.message}\n`);
539
+ continue;
540
+ }
541
+
542
+ const frontmatter = parseRelocationFrontmatter(content);
543
+ const basename = path.basename(filePath);
544
+ // namespaceForSession returns { namespace, source }. Forwarding backfillIndex
545
+ // (undefined when --with-backfill is absent → inert) lets backfilled sessions
546
+ // resolve to a confident namespace and land in this index, which transitively
547
+ // lifts their learnings via namespaceForLearning's source_session resolution.
548
+ const { namespace } = namespaceForSession(frontmatter, { backfillIndex });
549
+ if (namespace && namespace !== '_unsorted' && namespace !== 'redacted-repo' && namespace !== 'unknown-repo') {
550
+ // Derive sessionId from basename (strip .md)
551
+ const sessionId = basename.replace(/\.md$/, '');
552
+ index.set(sessionId, namespace);
553
+ }
554
+ }
555
+
556
+ return index;
557
+ }
558
+
559
+ // ---------------------------------------------------------------------------
560
+ // Classification helpers
561
+ // ---------------------------------------------------------------------------
562
+
563
+ /**
564
+ * Classify a single file, returning the plan entry.
565
+ *
566
+ * @param {object} opts
567
+ * @param {string} opts.filePath - absolute path
568
+ * @param {string} opts.corpusRoot - absolute path to 40-learnings/ or 50-sessions/
569
+ * @param {Map<string, string>} opts.sessionRepoIndex
570
+ * @param {Map<string, { repo: string, confidence: string, source: string }>} [opts.backfillIndex]
571
+ * @returns {{ from: string, to: string, namespace: string, source: string, confident: boolean }}
572
+ */
573
+ async function classifyFile({ filePath, corpusRoot, sessionRepoIndex, backfillIndex }) {
574
+ const basename = path.basename(filePath);
575
+
576
+ let content;
577
+ try {
578
+ content = await fs.readFile(filePath, 'utf8');
579
+ } catch (err) {
580
+ return {
581
+ from: filePath,
582
+ to: null,
583
+ namespace: '_unsorted',
584
+ source: 'error',
585
+ confident: false,
586
+ error: err.message,
587
+ };
588
+ }
589
+
590
+ const frontmatter = parseRelocationFrontmatter(content);
591
+
592
+ // classifyOwner dispatches on frontmatter.type — handles both sessions and learnings.
593
+ // backfillIndex is undefined unless --with-backfill is set → inert in the default path.
594
+ const classifyResult = classifyOwner({ frontmatter, sessionRepoIndex, backfillIndex });
595
+
596
+ const { namespace, source, confident } = classifyResult;
597
+
598
+ const dest = computeDest({ basename, root: corpusRoot, namespace });
599
+
600
+ return {
601
+ from: filePath,
602
+ to: dest,
603
+ namespace,
604
+ source,
605
+ confident,
606
+ };
607
+ }
608
+
609
+ // ---------------------------------------------------------------------------
610
+ // Emit (stdout record per file)
611
+ // ---------------------------------------------------------------------------
612
+
613
+ /**
614
+ * Emit a single JSONL record or human-readable line per file.
615
+ *
616
+ * @param {boolean} jsonMode
617
+ * @param {object} rec
618
+ */
619
+ function emit(jsonMode, rec) {
620
+ if (jsonMode) {
621
+ process.stdout.write(JSON.stringify(rec) + '\n');
622
+ return;
623
+ }
624
+ // Human-readable: action from → to [namespace] (source)
625
+ const fromRel = rec.from ? path.basename(rec.from) : '?';
626
+ const toRel = rec.to ? path.relative(path.dirname(rec.from), rec.to) : '?';
627
+ const reasonStr = rec.reason ? ` [${rec.reason}]` : '';
628
+ process.stdout.write(
629
+ `${SCRIPT_NAME}: ${rec.action}: ${fromRel} → ${toRel} (${rec.namespace ?? rec.reason ?? '?'})${reasonStr}\n`,
630
+ );
631
+ }
632
+
633
+ // ---------------------------------------------------------------------------
634
+ // git mv wrapper
635
+ // ---------------------------------------------------------------------------
636
+
637
+ /**
638
+ * Ensure a directory exists inside the vault (creates via mkdir -p, not git).
639
+ * Vault directories can be created outside git since they'll be picked up when
640
+ * files are moved in.
641
+ *
642
+ * @param {string} dir - absolute path
643
+ */
644
+ async function ensureDir(dir) {
645
+ await fs.mkdir(dir, { recursive: true });
646
+ }
647
+
648
+ /**
649
+ * Move a file using `git mv` (stages the rename, does NOT commit).
650
+ *
651
+ * @param {string} vaultDir - absolute path to vault git root
652
+ * @param {string} from - absolute path (source)
653
+ * @param {string} to - absolute path (destination)
654
+ * @returns {{ ok: boolean, error?: string }}
655
+ */
656
+ function gitMv(vaultDir, from, to) {
657
+ // git mv wants relative paths from the repo root
658
+ const fromRel = path.relative(vaultDir, from);
659
+ const toRel = path.relative(vaultDir, to);
660
+
661
+ const result = spawnSync(
662
+ 'git',
663
+ ['-C', vaultDir, 'mv', fromRel, toRel],
664
+ { encoding: 'utf8' },
665
+ );
666
+
667
+ if (result.error) {
668
+ return { ok: false, error: result.error.message };
669
+ }
670
+ if (result.status !== 0) {
671
+ return { ok: false, error: (result.stderr || result.stdout || 'git mv failed').trim() };
672
+ }
673
+ return { ok: true };
674
+ }
675
+
676
+ // ---------------------------------------------------------------------------
677
+ // Manifest writer (atomic tmp + rename)
678
+ // ---------------------------------------------------------------------------
679
+
680
+ /**
681
+ * Write the reverse-manifest atomically under <vaultDir>/.orchestrator/.
682
+ *
683
+ * @param {object} opts
684
+ * @param {string} opts.vaultDir
685
+ * @param {boolean} opts.derivableOnly
686
+ * @param {object} opts.summary
687
+ * @param {Array<{from:string,to:string,namespace:string,source:string}>} opts.moves
688
+ * @returns {string} absolute path to the written manifest
689
+ */
690
+ async function writeManifest({ vaultDir, derivableOnly, summary, moves }) {
691
+ const manifestDir = path.join(vaultDir, MANIFEST_DIR);
692
+ await ensureDir(manifestDir);
693
+
694
+ const ts = new Date().toISOString().replace(/[:.]/g, '-');
695
+ const manifestPath = path.join(manifestDir, `relocation-manifest-${ts}.json`);
696
+ const tmpPath = `${manifestPath}.tmp-${process.pid}`;
697
+
698
+ // Store vault-relative paths in the manifest
699
+ const movesRel = moves.map((m) => ({
700
+ from: path.relative(vaultDir, m.from),
701
+ to: path.relative(vaultDir, m.to),
702
+ namespace: m.namespace,
703
+ source: m.source,
704
+ }));
705
+
706
+ const manifest = {
707
+ schemaVersion: SCHEMA_VERSION,
708
+ tool: SCRIPT_NAME,
709
+ createdAt: new Date().toISOString(),
710
+ vaultDir,
711
+ mode: derivableOnly ? 'derivable-only' : 'full',
712
+ mechanism: 'git-mv',
713
+ summary,
714
+ moves: movesRel,
715
+ };
716
+
717
+ await fs.writeFile(tmpPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
718
+ await fs.rename(tmpPath, manifestPath);
719
+
720
+ return manifestPath;
721
+ }
722
+
723
+ // ---------------------------------------------------------------------------
724
+ // Rollback
725
+ // ---------------------------------------------------------------------------
726
+
727
+ /**
728
+ * Reverse a previous --apply run by reversing each move in the manifest.
729
+ *
730
+ * @param {string} manifestPath
731
+ * @param {boolean} jsonMode
732
+ */
733
+ async function runRollback(manifestPath, jsonMode) {
734
+ let manifestContent;
735
+ try {
736
+ manifestContent = await fs.readFile(manifestPath, 'utf8');
737
+ } catch (err) {
738
+ process.stderr.write(`${SCRIPT_NAME}: failed to read manifest: ${err.message}\n`);
739
+ process.exit(2);
740
+ }
741
+
742
+ let manifest;
743
+ try {
744
+ manifest = JSON.parse(manifestContent);
745
+ } catch (err) {
746
+ process.stderr.write(`${SCRIPT_NAME}: failed to parse manifest: ${err.message}\n`);
747
+ process.exit(2);
748
+ }
749
+
750
+ const { vaultDir, moves } = manifest;
751
+ if (!vaultDir || !Array.isArray(moves)) {
752
+ process.stderr.write(`${SCRIPT_NAME}: invalid manifest schema\n`);
753
+ process.exit(1);
754
+ }
755
+
756
+ let reversed = 0;
757
+ let skipped = 0;
758
+ let errors = 0;
759
+
760
+ // Reverse in reverse order (mirrors file-by-file; order matters if subdirs)
761
+ for (let i = moves.length - 1; i >= 0; i--) {
762
+ const m = moves[i];
763
+ const absTo = path.join(vaultDir, m.to); // current location
764
+ const absFrom = path.join(vaultDir, m.from); // destination (original)
765
+
766
+ // Assert: to exists, from absent
767
+ if (!existsSync(absTo)) {
768
+ emit(jsonMode, {
769
+ action: 'skipped',
770
+ from: absTo,
771
+ to: absFrom,
772
+ namespace: m.namespace,
773
+ source: m.source,
774
+ confident: null,
775
+ reason: 'source-missing-for-rollback',
776
+ });
777
+ skipped++;
778
+ continue;
779
+ }
780
+ if (existsSync(absFrom)) {
781
+ emit(jsonMode, {
782
+ action: 'skipped',
783
+ from: absTo,
784
+ to: absFrom,
785
+ namespace: m.namespace,
786
+ source: m.source,
787
+ confident: null,
788
+ reason: 'dest-exists-for-rollback',
789
+ });
790
+ skipped++;
791
+ continue;
792
+ }
793
+
794
+ const mvResult = gitMv(vaultDir, absTo, absFrom);
795
+ if (!mvResult.ok) {
796
+ process.stderr.write(`${SCRIPT_NAME}: ERROR git mv failed: ${mvResult.error}\n`);
797
+ errors++;
798
+ emit(jsonMode, {
799
+ action: 'error',
800
+ from: absTo,
801
+ to: absFrom,
802
+ namespace: m.namespace,
803
+ source: m.source,
804
+ confident: null,
805
+ reason: mvResult.error,
806
+ });
807
+ } else {
808
+ emit(jsonMode, {
809
+ action: 'rolled-back',
810
+ from: absTo,
811
+ to: absFrom,
812
+ namespace: m.namespace,
813
+ source: m.source,
814
+ confident: null,
815
+ });
816
+ reversed++;
817
+ }
818
+ }
819
+
820
+ process.stderr.write(
821
+ `${SCRIPT_NAME}: rollback complete — ${reversed} reversed, ${skipped} skipped, ${errors} errors\n`,
822
+ );
823
+
824
+ process.exit(errors > 0 ? 2 : 0);
825
+ }
826
+
827
+ // ---------------------------------------------------------------------------
828
+ // Main
829
+ // ---------------------------------------------------------------------------
830
+
831
+ async function main() {
832
+ const opts = parseArgs(process.argv);
833
+
834
+ if (opts.help) {
835
+ printHelp();
836
+ process.exit(0);
837
+ }
838
+
839
+ // Handle --rollback before requiring --vault-dir (manifest carries vaultDir)
840
+ if (opts.rollback !== null) {
841
+ await runRollback(opts.rollback, opts.json);
842
+ return; // runRollback calls process.exit
843
+ }
844
+
845
+ // --vault-dir is required for all other modes
846
+ if (!opts.vaultDir) {
847
+ process.stderr.write(
848
+ `${SCRIPT_NAME}: --vault-dir is required (no default to prevent accidental writes)\n`,
849
+ );
850
+ process.exit(1);
851
+ }
852
+
853
+ const vaultDir = path.resolve(opts.vaultDir);
854
+ if (!existsSync(vaultDir)) {
855
+ process.stderr.write(`${SCRIPT_NAME}: vault-dir not found: ${vaultDir}\n`);
856
+ process.exit(1);
857
+ }
858
+
859
+ // Load relocation rules (errors are warnings, not fatal)
860
+ const ruleset = loadVaultRelocationRules();
861
+ for (const e of ruleset.errors ?? []) {
862
+ process.stderr.write(`${SCRIPT_NAME}: config: ${e}\n`);
863
+ }
864
+
865
+ // ── Corpus paths ─────────────────────────────────────────────────────────
866
+ const learningsDir = path.join(vaultDir, LEARNINGS_SUBDIR);
867
+ const sessionsDir = path.join(vaultDir, SESSIONS_SUBDIR);
868
+
869
+ // ── Step 0: Build the cross-repo backfill index (--with-backfill only) ─────
870
+ // When --with-backfill is ABSENT, NO sessions.jsonl is read and backfillIndex
871
+ // stays undefined → every downstream consumer is inert → byte-identical default.
872
+ let backfillIndex; // undefined unless --with-backfill
873
+ if (opts.withBackfill) {
874
+ const reposRoot = opts.reposRoot
875
+ ? path.resolve(opts.reposRoot)
876
+ : path.dirname(vaultDir);
877
+ const rawIndices = await buildCrossRepoIndices(reposRoot);
878
+ const parsedVaultSessions = await collectParsedVaultSessions(sessionsDir);
879
+ // Canonicalise repo identities against the vault's own source-repo:/repo:
880
+ // ground truth so a remote-less dir shares the folder with its repo-signaled notes.
881
+ const learned = learnCanonicalMap({ sidIndex: rawIndices.sidIndex, parsedVaultSessions });
882
+ const { sidIndex, bdIndex } = remapIndices(rawIndices, learned);
883
+ backfillIndex = buildBackfillIndex(parsedVaultSessions, { sidIndex, bdIndex });
884
+ process.stderr.write(
885
+ `${SCRIPT_NAME}: backfill: scanned ${rawIndices.reposScanned} repos under ${reposRoot} ` +
886
+ `(${sidIndex.size} sids, ${bdIndex.size} branch|date keys, ${learned.size} canonical-learned) → ` +
887
+ `${backfillIndex.size} sessions attributed from ${parsedVaultSessions.length} vault sessions\n`,
888
+ );
889
+ }
890
+
891
+ // ── Step 1: Build session→repo index (single pass over ALL session files) ──
892
+ // Forwarding backfillIndex lets backfilled sessions land in this index, which
893
+ // transitively lifts their learnings via source_session resolution.
894
+ const sessionRepoIndex = await buildSessionRepoIndex(sessionsDir, { backfillIndex });
895
+
896
+ // ── Step 2: Enumerate flat files (maxdepth 1 only) ───────────────────────
897
+ const scopeSessions = !opts.learningsOnly;
898
+ const scopeLearnings = !opts.sessionsOnly;
899
+
900
+ const sessionFiles = scopeSessions ? enumerateFlatFiles(sessionsDir) : [];
901
+ const learningFiles = scopeLearnings ? enumerateFlatFiles(learningsDir) : [];
902
+
903
+ // ── Step 3: Classify each flat file ──────────────────────────────────────
904
+ /** @type {Array<{from:string,to:string,namespace:string,source:string,confident:boolean,corpusRoot:string}>} */
905
+ const plan = [];
906
+
907
+ for (const filePath of sessionFiles) {
908
+ const entry = await classifyFile({
909
+ filePath,
910
+ corpusRoot: sessionsDir,
911
+ sessionRepoIndex,
912
+ backfillIndex,
913
+ });
914
+ plan.push({ ...entry, corpusRoot: sessionsDir });
915
+ }
916
+
917
+ for (const filePath of learningFiles) {
918
+ const entry = await classifyFile({
919
+ filePath,
920
+ corpusRoot: learningsDir,
921
+ sessionRepoIndex,
922
+ backfillIndex,
923
+ });
924
+ plan.push({ ...entry, corpusRoot: learningsDir });
925
+ }
926
+
927
+ // ── Step 3.5: Pre-flight intra-batch dest-uniqueness detection ────────────
928
+ // W1-D3 safety gap: computeDest is purely structural, so two distinct source
929
+ // files with the same basename + namespace map to the SAME `to`. The runtime
930
+ // existsSync guard only catches collisions against files that ALREADY exist —
931
+ // it cannot see same-batch collisions in --dry-run (nothing has moved yet), so
932
+ // dry-run under-counts. Group move-ELIGIBLE entries by `to`; any `to` reached
933
+ // by ≥2 distinct `from` is an intra-batch collision. This surfaces them up-front.
934
+ const destGroups = new Map(); // to → Set<from>
935
+ for (const entry of plan) {
936
+ if (entry.error || !entry.to) continue;
937
+ const relToCorpus = path.relative(entry.corpusRoot, entry.from);
938
+ if (isAlreadyNamespaced(relToCorpus)) continue;
939
+ // Mirror the --derivable-only skip: non-confident entries don't reach the move
940
+ // stage in that mode, so they cannot collide there either.
941
+ if (opts.derivableOnly && !isConfident(entry.namespace)) continue;
942
+ let set = destGroups.get(entry.to);
943
+ if (!set) {
944
+ set = new Set();
945
+ destGroups.set(entry.to, set);
946
+ }
947
+ set.add(entry.from);
948
+ }
949
+ /** @type {Set<string>} colliding `to` paths (≥2 distinct sources) */
950
+ const collidingDests = new Set();
951
+ for (const [to, froms] of destGroups) {
952
+ if (froms.size >= 2) collidingDests.add(to);
953
+ }
954
+
955
+ // ── Step 4: Apply mode decision, emit records ─────────────────────────────
956
+ const summary = {
957
+ moved: 0,
958
+ skippedAlreadyNamespaced: 0,
959
+ fallbackBucket: 0,
960
+ skippedNonConfident: 0,
961
+ destCollisions: 0,
962
+ intraBatchCollisions: collidingDests.size,
963
+ ioErrors: 0,
964
+ };
965
+
966
+ // Surface each intra-batch-colliding dest as an emit record (one per colliding
967
+ // `to`, listing the count of competing sources) so --dry-run is honest about them.
968
+ for (const to of collidingDests) {
969
+ emit(opts.json, {
970
+ action: 'intra-batch-collision',
971
+ from: null,
972
+ to,
973
+ namespace: null,
974
+ source: 'pre-flight',
975
+ confident: null,
976
+ reason: `intra-batch-collision: ${destGroups.get(to).size} sources map to this dest`,
977
+ collidingSources: [...destGroups.get(to)],
978
+ });
979
+ }
980
+
981
+ /** @type {Array<{from:string,to:string,namespace:string,source:string}>} */
982
+ const appliedMoves = [];
983
+
984
+ for (const entry of plan) {
985
+ // isAlreadyNamespaced is a structural guard — should never be hit (we enumerate maxdepth 1)
986
+ // but defensive check in case find returned a deeper path
987
+ const relToCorpus = path.relative(entry.corpusRoot, entry.from);
988
+ if (isAlreadyNamespaced(relToCorpus)) {
989
+ summary.skippedAlreadyNamespaced++;
990
+ emit(opts.json, {
991
+ action: 'skipped',
992
+ from: entry.from,
993
+ to: entry.to,
994
+ namespace: entry.namespace,
995
+ source: entry.source,
996
+ confident: entry.confident,
997
+ reason: 'already-namespaced',
998
+ });
999
+ continue;
1000
+ }
1001
+
1002
+ if (entry.error) {
1003
+ summary.ioErrors++;
1004
+ emit(opts.json, {
1005
+ action: 'skipped',
1006
+ from: entry.from,
1007
+ to: null,
1008
+ namespace: '_unsorted',
1009
+ source: 'error',
1010
+ confident: false,
1011
+ reason: `read-error: ${entry.error}`,
1012
+ });
1013
+ continue;
1014
+ }
1015
+
1016
+ // --derivable-only: skip non-confident entries
1017
+ if (opts.derivableOnly && !isConfident(entry.namespace)) {
1018
+ summary.skippedNonConfident++;
1019
+ emit(opts.json, {
1020
+ action: 'skipped',
1021
+ from: entry.from,
1022
+ to: entry.to,
1023
+ namespace: entry.namespace,
1024
+ source: entry.source,
1025
+ confident: entry.confident,
1026
+ reason: 'non-confident',
1027
+ });
1028
+ continue;
1029
+ }
1030
+
1031
+ // Track fallback bucket
1032
+ if (!isConfident(entry.namespace)) {
1033
+ summary.fallbackBucket++;
1034
+ }
1035
+
1036
+ // Dest-collision guard
1037
+ if (existsSync(entry.to)) {
1038
+ summary.destCollisions++;
1039
+ emit(opts.json, {
1040
+ action: 'skipped',
1041
+ from: entry.from,
1042
+ to: entry.to,
1043
+ namespace: entry.namespace,
1044
+ source: entry.source,
1045
+ confident: entry.confident,
1046
+ reason: 'dest-exists',
1047
+ });
1048
+ continue;
1049
+ }
1050
+
1051
+ if (!opts.apply) {
1052
+ // Dry-run: report what would happen
1053
+ emit(opts.json, {
1054
+ action: 'would-move',
1055
+ from: entry.from,
1056
+ to: entry.to,
1057
+ namespace: entry.namespace,
1058
+ source: entry.source,
1059
+ confident: entry.confident,
1060
+ });
1061
+ summary.moved++;
1062
+ } else {
1063
+ // Apply: mkdir, git mv, record
1064
+ try {
1065
+ await ensureDir(path.dirname(entry.to));
1066
+ } catch (err) {
1067
+ summary.ioErrors++;
1068
+ process.stderr.write(
1069
+ `${SCRIPT_NAME}: ERROR creating dir ${path.dirname(entry.to)}: ${err.message}\n`,
1070
+ );
1071
+ emit(opts.json, {
1072
+ action: 'skipped',
1073
+ from: entry.from,
1074
+ to: entry.to,
1075
+ namespace: entry.namespace,
1076
+ source: entry.source,
1077
+ confident: entry.confident,
1078
+ reason: `mkdir-error: ${err.message}`,
1079
+ });
1080
+ continue;
1081
+ }
1082
+
1083
+ const mvResult = gitMv(vaultDir, entry.from, entry.to);
1084
+ if (!mvResult.ok) {
1085
+ summary.ioErrors++;
1086
+ process.stderr.write(`${SCRIPT_NAME}: ERROR git mv failed: ${mvResult.error}\n`);
1087
+ emit(opts.json, {
1088
+ action: 'skipped',
1089
+ from: entry.from,
1090
+ to: entry.to,
1091
+ namespace: entry.namespace,
1092
+ source: entry.source,
1093
+ confident: entry.confident,
1094
+ reason: `git-mv-error: ${mvResult.error}`,
1095
+ });
1096
+ continue;
1097
+ }
1098
+
1099
+ emit(opts.json, {
1100
+ action: 'moved',
1101
+ from: entry.from,
1102
+ to: entry.to,
1103
+ namespace: entry.namespace,
1104
+ source: entry.source,
1105
+ confident: entry.confident,
1106
+ });
1107
+ summary.moved++;
1108
+ appliedMoves.push({
1109
+ from: entry.from,
1110
+ to: entry.to,
1111
+ namespace: entry.namespace,
1112
+ source: entry.source,
1113
+ });
1114
+ }
1115
+ }
1116
+
1117
+ // ── Step 5: Write manifest on --apply ─────────────────────────────────────
1118
+ if (opts.apply && appliedMoves.length > 0) {
1119
+ try {
1120
+ const writtenPath = await writeManifest({
1121
+ vaultDir,
1122
+ derivableOnly: opts.derivableOnly,
1123
+ summary,
1124
+ moves: appliedMoves,
1125
+ });
1126
+ process.stderr.write(`${SCRIPT_NAME}: manifest written: ${writtenPath}\n`);
1127
+ } catch (err) {
1128
+ summary.ioErrors++;
1129
+ process.stderr.write(`${SCRIPT_NAME}: WARN: manifest write failed — moves were staged via git mv but --rollback via manifest is unavailable; reverse manually via 'git -C <vault> reset' or 'git mv' back. Error: ${err.message}\n`);
1130
+ }
1131
+ }
1132
+
1133
+ // ── Summary ───────────────────────────────────────────────────────────────
1134
+ const modeLabel = opts.apply ? 'applied' : 'dry-run';
1135
+ const derivableLabel = opts.derivableOnly ? ' [derivable-only]' : '';
1136
+ // The intra-batch segment is appended only when collisions exist OR backfill is
1137
+ // active. This preserves byte-identical summary output for the legacy default
1138
+ // path (no --with-backfill, no intra-batch collisions), while making the new
1139
+ // signal visible exactly when it carries information.
1140
+ const intraBatchSegment =
1141
+ collidingDests.size > 0 || opts.withBackfill
1142
+ ? `${summary.intraBatchCollisions} intra-batch-collisions, `
1143
+ : '';
1144
+ process.stderr.write(
1145
+ `${SCRIPT_NAME}: ${summary.moved} ${opts.apply ? 'moved' : 'would-move'}, ` +
1146
+ `${summary.skippedAlreadyNamespaced} already-namespaced, ` +
1147
+ `${summary.skippedNonConfident} non-confident skipped, ` +
1148
+ `${summary.destCollisions} dest-collisions, ` +
1149
+ intraBatchSegment +
1150
+ `${summary.ioErrors} I/O errors` +
1151
+ ` [${modeLabel}${derivableLabel}]\n`,
1152
+ );
1153
+
1154
+ process.exit(summary.ioErrors > 0 ? 2 : 0);
1155
+ }
1156
+
1157
+ // ---------------------------------------------------------------------------
1158
+ // Entry guard (run only when invoked directly)
1159
+ // ---------------------------------------------------------------------------
1160
+
1161
+ const invokedDirectly =
1162
+ process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
1163
+
1164
+ if (invokedDirectly) {
1165
+ main().catch((err) => {
1166
+ process.stderr.write(`${SCRIPT_NAME}: FATAL: ${err.message}\n`);
1167
+ process.exit(2);
1168
+ });
1169
+ }
1170
+
1171
+ export {
1172
+ enumerateFlatFiles,
1173
+ buildSessionRepoIndex,
1174
+ classifyFile,
1175
+ writeManifest,
1176
+ runRollback,
1177
+ _setResolverForTest,
1178
+ };