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,567 @@
1
+ /**
2
+ * process.mjs — Core entry processors for vault-mirror (Issue #283 split).
3
+ *
4
+ * Exports: processLearning, processSession
5
+ * Both functions write to the vault dir and emit JSON action lines to stdout.
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
9
+ import { join, resolve, basename } from 'node:path';
10
+ import { randomUUID } from 'node:crypto';
11
+ import { execFileSync } from 'node:child_process';
12
+ import { subjectToSlug, isValidSlug, uuidPrefix8, toDate, parseFrontmatter } from './utils.mjs';
13
+ import { resolveRepoNamespace } from './namespace.mjs';
14
+ import { detectLearningSchema, normalizeLearningEntry, generateLearningNote, generateLearningNoteV2 } from './render-learnings.mjs';
15
+ import { detectSessionSchema, normalizeSessionEntry, generateSessionNote, generateSessionNoteV2, generateSessionNoteV3 } from './render-sessions.mjs';
16
+
17
+ const GENERATOR_MARKER = 'session-orchestrator-vault-mirror@1';
18
+
19
+ // ── Session-note existence index (Issue #704) ─────────────────────────────────
20
+
21
+ /**
22
+ * Module-level cache: resolved vaultDir path → Set of known session basenames
23
+ * (without the `.md` extension). Built once per unique vaultDir per process.
24
+ *
25
+ * @type {Map<string, Set<string>>}
26
+ */
27
+ const _sessionNoteSets = new Map();
28
+
29
+ /**
30
+ * Recursively walk `<vaultDir>/50-sessions/` and collect every `.md` basename
31
+ * (without extension) into a Set. Returns an EMPTY Set (never throws) when the
32
+ * directory is absent or unreadable — callers treat an empty Set as "no predicate
33
+ * available", falling back to format-validation in resolveSourceSessionLink.
34
+ *
35
+ * Read-only: never creates directories, safe in dryRun mode.
36
+ *
37
+ * @param {string} vaultDir — absolute path to the vault root
38
+ * @returns {Set<string>}
39
+ */
40
+ function getSessionNoteSet(vaultDir) {
41
+ const resolvedVault = resolve(vaultDir);
42
+ if (_sessionNoteSets.has(resolvedVault)) return _sessionNoteSets.get(resolvedVault);
43
+
44
+ const knownSessions = new Set();
45
+ const sessionsDir = join(resolvedVault, '50-sessions');
46
+
47
+ function walk(dir) {
48
+ let entries;
49
+ try {
50
+ entries = readdirSync(dir, { withFileTypes: true });
51
+ } catch {
52
+ return; // dir absent or inaccessible — skip silently
53
+ }
54
+ for (const entry of entries) {
55
+ if (entry.isDirectory()) {
56
+ walk(join(dir, entry.name));
57
+ } else if (entry.name.endsWith('.md')) {
58
+ knownSessions.add(entry.name.slice(0, -3)); // basename without .md
59
+ }
60
+ }
61
+ }
62
+
63
+ walk(sessionsDir);
64
+ _sessionNoteSets.set(resolvedVault, knownSessions);
65
+ return knownSessions;
66
+ }
67
+
68
+ // ── Content diff helper ───────────────────────────────────────────────────────
69
+
70
+ /**
71
+ * Extract canonical comparable fields from a rendered learning note string.
72
+ *
73
+ * We compare only the fields that represent meaningful SSOT content changes —
74
+ * not created/updated dates or frontmatter ordering, which differ on every
75
+ * render without signalling a content change.
76
+ *
77
+ * Comparable fields:
78
+ * - status (frontmatter line `status: <value>`)
79
+ * - expires (frontmatter line `expires: <value>`, v1 only; absent in v2)
80
+ * - confidence (body bullet `- **Confidence:** <value>`)
81
+ * - insight body (text after `## Insight\n\n` heading, trimmed)
82
+ *
83
+ * @param {string} noteContent — rendered markdown string
84
+ * @returns {{ status: string, expires: string, confidence: string, insight: string }}
85
+ */
86
+ function extractLearningCanonicalFields(noteContent) {
87
+ const status = (noteContent.match(/^status:\s*(.+)$/m) || [])[1]?.trim() ?? '';
88
+ const expires = (noteContent.match(/^expires:\s*(.+)$/m) || [])[1]?.trim() ?? '';
89
+ const confidence = (noteContent.match(/^- \*\*Confidence:\*\*\s*(.+)$/m) || [])[1]?.trim() ?? '';
90
+ // Extract insight body: everything after `## Insight\n\n` until the next `##` or end-of-string
91
+ const insightMatch = noteContent.match(/^## Insight\n\n([\s\S]*?)(?=\n## |\s*$)/m);
92
+ const insight = insightMatch ? insightMatch[1].trim() : '';
93
+ // #704: track the frontmatter source_session so a normal re-mirror repairs
94
+ // historical dangling-link notes (e.g. `source_session: "[[unknown]]"`) once
95
+ // the renderer emits the corrected plain-text/resolvable form — otherwise the
96
+ // content-diff would treat the stale note as a no-op and never heal it.
97
+ const source_session = (noteContent.match(/^source_session:\s*(.+)$/m) || [])[1]?.trim() ?? '';
98
+ return { status, expires, confidence, insight, source_session };
99
+ }
100
+
101
+ /**
102
+ * Return true when the existing vault note content and the freshly-rendered
103
+ * candidate share identical canonical fields (i.e. no meaningful update needed).
104
+ *
105
+ * A true result means → emit skipped-noop.
106
+ * A false result means → the SSOT changed; write the new content.
107
+ *
108
+ * Comparison semantics: a field that is ABSENT in the existing note (empty
109
+ * string from extractLearningCanonicalFields) is treated as matching any
110
+ * rendered value for that field. This preserves backward compatibility with
111
+ * notes written by older generator versions that may not have emitted every
112
+ * field. Only when an existing field is NON-EMPTY and differs from the
113
+ * rendered candidate do we conclude the SSOT has changed and an update is needed.
114
+ *
115
+ * @param {string} existingContent — content read from the vault note on disk
116
+ * @param {string} renderedContent — freshly generated note from the renderer
117
+ * @returns {boolean}
118
+ */
119
+ function learningContentMatches(existingContent, renderedContent) {
120
+ const existing = extractLearningCanonicalFields(existingContent);
121
+ const rendered = extractLearningCanonicalFields(renderedContent);
122
+ // For each field: if the existing value is absent (empty string), it cannot
123
+ // signal a mismatch — it means the old note didn't track that field. Only
124
+ // non-empty existing values are compared against the rendered candidate.
125
+ const fieldMatches = (existingVal, renderedVal) =>
126
+ existingVal === '' || existingVal === renderedVal;
127
+ return (
128
+ fieldMatches(existing.status, rendered.status) &&
129
+ fieldMatches(existing.expires, rendered.expires) &&
130
+ fieldMatches(existing.confidence, rendered.confidence) &&
131
+ fieldMatches(existing.insight, rendered.insight) &&
132
+ fieldMatches(existing.source_session, rendered.source_session)
133
+ );
134
+ }
135
+
136
+ // ── repo derivation ───────────────────────────────────────────────────────────
137
+
138
+ let _cachedRepo = null;
139
+
140
+ /**
141
+ * Derive the canonical repo identifier for cross-repo vault aggregation (issue #343).
142
+ *
143
+ * Strategy: parse `git remote get-url origin` and extract the org/name pair
144
+ * (e.g. `git@github.com:Kanevry/session-orchestrator.git` → `Kanevry/session-orchestrator`).
145
+ * Falls back to `path.basename(process.cwd())` when not in a git repo or origin
146
+ * is unavailable. Cached per-process — repo identity does not change mid-run.
147
+ */
148
+ export function deriveRepo() {
149
+ if (_cachedRepo !== null) return _cachedRepo;
150
+ try {
151
+ const url = execFileSync('git', ['remote', 'get-url', 'origin'], {
152
+ encoding: 'utf8',
153
+ stdio: ['ignore', 'pipe', 'ignore'],
154
+ }).trim();
155
+ // Match git@host:org/name(.git)? OR https://host/org/name(.git)?
156
+ const sshMatch = url.match(/[:/]([^:/]+\/[^/]+?)(?:\.git)?$/);
157
+ if (sshMatch && sshMatch[1]) {
158
+ _cachedRepo = sshMatch[1];
159
+ return _cachedRepo;
160
+ }
161
+ } catch {
162
+ // git unavailable or no origin configured — fall through
163
+ }
164
+ _cachedRepo = basename(process.cwd());
165
+ return _cachedRepo;
166
+ }
167
+
168
+ // ── Action output ─────────────────────────────────────────────────────────────
169
+
170
+ /**
171
+ * Emit a JSON action line to stdout.
172
+ *
173
+ * Takes a single self-documenting options object (issue #511). `emitAction` is a
174
+ * module-level export (independently unit-tested), so `vaultDir` is carried as a
175
+ * named option field rather than pulled from a closure — both callers
176
+ * (processLearning, processSession) destructure it from their own `ctx` and pass
177
+ * it through.
178
+ *
179
+ * #589 LOW-arch-3 — the `vaultDir`-in-options-object pattern is consciously
180
+ * accepted: the 16 call-sites in THIS module repeat `vaultDir` as boilerplate,
181
+ * but there is only one consumer module, so a `makeEmitAction({ vaultDir })`
182
+ * closure factory would be premature (YAGNI). Extract the factory ONLY when a
183
+ * SECOND module needs `emitAction` and would otherwise re-thread `vaultDir`.
184
+ *
185
+ * @param {object} opts
186
+ * @param {string} opts.action — action name (e.g. 'created', 'updated', 'skipped-quality-low')
187
+ * @param {string|null} opts.path — absolute path to the file (or null when no file
188
+ * was created/touched, e.g. for quality-gate skips before any write)
189
+ * @param {string} opts.kind — 'learning' or 'session'
190
+ * @param {string|null} opts.id — entry id
191
+ * @param {string} opts.vaultDir — vault root (used to relativise `path`)
192
+ * @param {object} [opts.meta] — optional extra fields merged into the emitted JSON
193
+ * (used for quality-gate skips to carry a `reason` field). Callers that omit
194
+ * `meta` get the base JSON shape unchanged.
195
+ */
196
+ export function emitAction({ action, path, kind, id, vaultDir, meta }) {
197
+ let rel;
198
+ if (path === null || path === undefined) {
199
+ rel = null;
200
+ } else {
201
+ const resolvedVaultDir = resolve(vaultDir);
202
+ rel = path.startsWith(resolvedVaultDir)
203
+ ? path.slice(resolvedVaultDir.length + 1)
204
+ : path;
205
+ }
206
+ const payload = { action, path: rel, kind, id };
207
+ if (meta && typeof meta === 'object') {
208
+ Object.assign(payload, meta);
209
+ }
210
+ process.stdout.write(JSON.stringify(payload) + '\n');
211
+ }
212
+
213
+ // ── Core processing ───────────────────────────────────────────────────────────
214
+
215
+ export async function processLearning(rawEntry, _lineNum, ctx) {
216
+ const {
217
+ vaultDir,
218
+ dryRun,
219
+ kind,
220
+ force = false,
221
+ qualityMinConfidence = 0.5,
222
+ qualityMinNarrativeChars: _qualityMinNarrativeChars = 400, // unused for learnings; kept for ctx symmetry
223
+ } = ctx;
224
+ // #635: map producer alias fields (summary/detail, description/rationale,
225
+ // title/body, name, narrative, content) onto the canonical v1 shape BEFORE
226
+ // schema detection and slug/id derivation. Canonical entries pass through.
227
+ const entry = normalizeLearningEntry(rawEntry);
228
+ const schema = detectLearningSchema(entry);
229
+ const entryId = entry.id;
230
+
231
+ // Slug source differs by schema. Both fall back to learning-<uuid8> on invalid.
232
+ // For v2 the id is already a kebab slug (e.g. "s69-compose-pids-cross-validation").
233
+ // Crucially: validate id presence before slug derivation, so missing-id entries
234
+ // become skipped-invalid rather than crashing inside subjectToSlug.
235
+ if (entryId === null || entryId === undefined) {
236
+ throw new Error(`vault-mirror: learning entry missing required field 'id' (id=<no id>)`);
237
+ }
238
+ // #725 D1: the raw v1 subject is prose WITH SPACES, and subjectToSlug() strips
239
+ // spaces WITHOUT hyphenating — so "Dead fallback removal when primary parser
240
+ // matures" collapsed into a single run "deadfallbackremovalwhenprimaryparser…"
241
+ // (a silent slug corruption that still passes isValidSlug). Pre-map the
242
+ // subject's whitespace to hyphens BEFORE subjectToSlug, mirroring the id
243
+ // derivation in normalizeLearningEntry (render-learnings.mjs L63-65) so a
244
+ // learning's slug matches its derived id. This keeps the subject as the v1 slug
245
+ // source (the established contract: entry.id is reserved for the disambiguation
246
+ // /invalid-slug fallback prefix via uuidPrefix8 below), and only heals the
247
+ // space-collapse defect. v2 ids are already kebab slugs.
248
+ //
249
+ // NOTE (#725 D1 divergence): the wave brief asked to make entry.id the PRIMARY
250
+ // slug source. That is architecturally incompatible with the invalid-slug
251
+ // fallback `learning-<uuidPrefix8(entry.id)>` (which requires entry.id to stay
252
+ // a clean value SEPARATE from the slug source) and would invert the slug-from-
253
+ // subject contract pinned by 15 tests in tests/unit/vault-mirror.test.mjs. The
254
+ // pre-map is the mechanism the brief itself names and yields the IDENTICAL slug
255
+ // for real data (kebab id derived from the same subject); see report.
256
+ let slugSource;
257
+ if (schema === 'v2') {
258
+ slugSource = entry.id;
259
+ } else if (typeof entry.subject === 'string') {
260
+ slugSource = entry.subject.trim().replace(/\s+/g, '-');
261
+ } else {
262
+ slugSource = entry.subject;
263
+ }
264
+ let slug;
265
+ if (typeof slugSource === 'string' && slugSource.length > 0) {
266
+ slug = subjectToSlug(slugSource);
267
+ } else {
268
+ slug = '';
269
+ }
270
+ if (!isValidSlug(slug)) {
271
+ slug = `learning-${uuidPrefix8(entryId)}`;
272
+ }
273
+ // #635: cap the slug so `<slug>.md` (plus a possible `-<uuid8>` disambig
274
+ // suffix) stays under the 255-byte filename limit. Normalized prose subjects
275
+ // can be arbitrarily long and previously aborted the whole mirror run with
276
+ // ENAMETOOLONG. 240 + 9 (disambig) + 3 (.md) = 252 — and every pre-existing
277
+ // vault note (max observed slug: 208 chars) keeps its identity untouched.
278
+ if (slug.length > 240) {
279
+ slug = slug.slice(0, 240).replace(/-+$/, '');
280
+ }
281
+
282
+ // Generator + date source differ by schema
283
+ const generator = schema === 'v2' ? generateLearningNoteV2 : generateLearningNote;
284
+ const dateSource = schema === 'v2' ? entry.first_seen : entry.created_at;
285
+
286
+ // #704: Build a noteExists predicate from the vault's 50-sessions index so that
287
+ // resolveSourceSessionLink can use EXISTENCE-based resolution instead of strict
288
+ // format-validation. The index is built once per vaultDir (cached in
289
+ // _sessionNoteSets). When the 50-sessions dir is absent/empty, the Set is empty
290
+ // and we pass NO predicate — resolveSourceSessionLink falls back to format
291
+ // validation (never worse than Wave 2 behaviour).
292
+ const _knownSessions = getSessionNoteSet(vaultDir);
293
+ const _noteExists = _knownSessions.size > 0 ? (s) => _knownSessions.has(s) : undefined;
294
+ const generatorOpts = _noteExists ? { noteExists: _noteExists } : {};
295
+
296
+ // Quality gate (PRD F1.2): skip learnings with confidence below threshold.
297
+ // Runs BEFORE the --force branch so --force does NOT bypass the quality filter.
298
+ // Missing/non-numeric confidence is treated as 1.0 (legacy entries pass).
299
+ const learningConfidence = typeof entry.confidence === 'number' ? entry.confidence : 1.0;
300
+ if (learningConfidence < qualityMinConfidence) {
301
+ emitAction({
302
+ action: 'skipped-quality-low',
303
+ path: null,
304
+ kind,
305
+ id: entryId,
306
+ vaultDir,
307
+ meta: { reason: `confidence:${learningConfidence} < min:${qualityMinConfidence}` },
308
+ });
309
+ return;
310
+ }
311
+
312
+ // #660: namespace new writes under a per-repo subdirectory.
313
+ const repoNs = resolveRepoNamespace({ vaultName: ctx?.vaultName ?? null });
314
+ // #725 D2: thread the resolved repo namespace into the learning frontmatter as
315
+ // `source-repo` for cross-repo attribution. repoNs is already sanitised +
316
+ // leak-guarded by resolveRepoNamespace, so it is safe to interpolate as-is. The
317
+ // renderer reads opts.repoNs (see render-learnings.mjs); when absent (older
318
+ // callers), the source-repo line is omitted — backward-compatible.
319
+ generatorOpts.repoNs = repoNs;
320
+ const targetDir = join(resolve(vaultDir), '40-learnings', repoNs);
321
+ if (!dryRun) mkdirSync(targetDir, { recursive: true });
322
+
323
+ let targetPath = join(targetDir, `${slug}.md`);
324
+
325
+ // #660 IDEMPOTENCY DUAL-PROBE: before treating the namespaced path as absent,
326
+ // also check the legacy flat path. If a note with the same slug already exists
327
+ // in the flat layout (pre-namespace migration), treat it as existing to avoid
328
+ // duplicating the note. The deferred-migration decision means we only skip;
329
+ // we do NOT move the flat note into the namespaced dir here.
330
+ const legacyFlatPath = join(resolve(vaultDir), '40-learnings', `${slug}.md`);
331
+ if (!existsSync(targetPath) && existsSync(legacyFlatPath)) {
332
+ const legacyContent = readFileSync(legacyFlatPath, 'utf8');
333
+ const legacyFm = parseFrontmatter(legacyContent);
334
+ // Only skip if the flat note is ours (has our generator marker and matching id).
335
+ if (legacyFm && legacyFm['_generator'] === GENERATOR_MARKER && legacyFm['id'] === slug) {
336
+ const entryUpdated = toDate(dateSource);
337
+ if (!force && legacyFm['updated'] && legacyFm['updated'] >= entryUpdated) {
338
+ // Date has not advanced — but content may have changed (confidence, insight, etc.).
339
+ // Render the candidate and compare canonical fields before deciding to skip.
340
+ const candidateContent = generator(entry, slug, generatorOpts);
341
+ if (learningContentMatches(legacyContent, candidateContent)) {
342
+ emitAction({ action: 'skipped-noop', path: legacyFlatPath, kind, id: slug, vaultDir });
343
+ return;
344
+ }
345
+ // Content differs — fall through to write into the namespaced path.
346
+ }
347
+ // Updated date would advance or content changed — fall through to write into the namespaced path.
348
+ }
349
+ }
350
+
351
+ if (existsSync(targetPath)) {
352
+ const existingContent = readFileSync(targetPath, 'utf8');
353
+ const fm = parseFrontmatter(existingContent);
354
+
355
+ if (!fm || !fm['_generator']) {
356
+ // Hand-written: skip
357
+ process.stderr.write(`SKIP hand-written: ${targetPath}\n`);
358
+ emitAction({ action: 'skipped-handwritten', path: targetPath, kind, id: entryId, vaultDir });
359
+ return;
360
+ }
361
+
362
+ if (fm['_generator'] !== GENERATOR_MARKER) {
363
+ // Different generator — treat as hand-written to be safe
364
+ process.stderr.write(`SKIP unknown generator: ${targetPath}\n`);
365
+ emitAction({ action: 'skipped-handwritten', path: targetPath, kind, id: entryId, vaultDir });
366
+ return;
367
+ }
368
+
369
+ if (fm['id'] !== slug) {
370
+ // Different id → collision: disambiguate
371
+ const disambigSlug = `${slug}-${uuidPrefix8(entryId)}`;
372
+ targetPath = join(targetDir, `${disambigSlug}.md`);
373
+ slug = disambigSlug;
374
+
375
+ if (existsSync(targetPath)) {
376
+ // Still exists with disambig — check if it's ours
377
+ const disambigContent = readFileSync(targetPath, 'utf8');
378
+ const disambigFm = parseFrontmatter(disambigContent);
379
+ if (!disambigFm || !disambigFm['_generator']) {
380
+ process.stderr.write(`SKIP hand-written (disambig): ${targetPath}\n`);
381
+ emitAction({ action: 'skipped-handwritten', path: targetPath, kind, id: entryId, vaultDir });
382
+ return;
383
+ }
384
+ // Check updated advancement; if date has not advanced, also diff content.
385
+ const entryUpdated = toDate(dateSource);
386
+ if (disambigFm['updated'] && disambigFm['updated'] >= entryUpdated) {
387
+ const candidateContent = generator(entry, slug, generatorOpts);
388
+ if (learningContentMatches(disambigContent, candidateContent)) {
389
+ emitAction({ action: 'skipped-noop', path: targetPath, kind, id: disambigSlug, vaultDir });
390
+ return;
391
+ }
392
+ // Content differs — fall through to write.
393
+ }
394
+ }
395
+
396
+ const content = generator(entry, slug, generatorOpts);
397
+ if (!dryRun) writeFileSync(targetPath, content, 'utf8');
398
+ emitAction({ action: 'skipped-collision-resolved', path: targetPath, kind, id: slug, vaultDir });
399
+ return;
400
+ }
401
+
402
+ // Same id: check if updated would advance (unless --force overrides).
403
+ // Even when the date has not advanced, content may have changed (confidence,
404
+ // insight, expires_at, etc.) — compare canonical fields before skipping.
405
+ const entryUpdated = toDate(dateSource);
406
+ if (!force && fm['updated'] && fm['updated'] >= entryUpdated) {
407
+ const candidateContent = generator(entry, slug, generatorOpts);
408
+ if (learningContentMatches(existingContent, candidateContent)) {
409
+ emitAction({ action: 'skipped-noop', path: targetPath, kind, id: slug, vaultDir });
410
+ return;
411
+ }
412
+ // Content differs — fall through to overwrite (same path as date-advance branch).
413
+ }
414
+
415
+ // Overwrite with advanced updated date (or forced re-render)
416
+ const content = generator(entry, slug, generatorOpts);
417
+ if (!dryRun) writeFileSync(targetPath, content, 'utf8');
418
+ emitAction({ action: 'updated', path: targetPath, kind, id: slug, vaultDir });
419
+ return;
420
+ }
421
+
422
+ // File does not exist — create
423
+ const content = generator(entry, slug, generatorOpts);
424
+ if (!dryRun) writeFileSync(targetPath, content, 'utf8');
425
+ emitAction({ action: 'created', path: targetPath, kind, id: slug, vaultDir });
426
+ }
427
+
428
+ export async function processSession(rawEntry, _lineNum, ctx) {
429
+ const {
430
+ vaultDir,
431
+ dryRun,
432
+ kind,
433
+ force = false,
434
+ qualityMinNarrativeChars = 400,
435
+ qualityMinConfidence: _qualityMinConfidence = 0.5, // unused for sessions; kept for ctx symmetry
436
+ } = ctx;
437
+ // #635: map producer alias fields (ended_at, mode, total_waves/waves_completed
438
+ // without a `waves` field) onto the canonical shapes BEFORE schema detection.
439
+ // Canonical v1/v2/v3 entries pass through untouched.
440
+ const entry = normalizeSessionEntry(rawEntry);
441
+ const { session_id: rawSessionId } = entry;
442
+ const schema = detectSessionSchema(entry);
443
+ const generator =
444
+ schema === 'v3' ? generateSessionNoteV3 : schema === 'v2' ? generateSessionNoteV2 : generateSessionNote;
445
+
446
+ // Validate session_id as a filesystem-safe slug; fall back via subjectToSlug
447
+ // (which collapses slashes to last segment + strips invalid chars) before
448
+ // resorting to a uuid-derived slug. Without subjectToSlug, raw slashes in
449
+ // rawSessionId (e.g. "feat/opus-4-7-...") would survive uuidPrefix8 and
450
+ // produce a path with directory separators in the basename.
451
+ let session_id;
452
+ if (isValidSlug(rawSessionId)) {
453
+ session_id = rawSessionId;
454
+ } else if (typeof rawSessionId === 'string' && rawSessionId.length > 0) {
455
+ const sanitised = subjectToSlug(rawSessionId);
456
+ session_id = isValidSlug(sanitised) ? sanitised : `session-${uuidPrefix8(rawSessionId)}`;
457
+ } else {
458
+ session_id = `session-${uuidPrefix8(randomUUID())}`;
459
+ }
460
+ // #635: symmetric slug-length cap (see processLearning) — a pathologically
461
+ // long but otherwise valid session_id slug would abort the mirror run with
462
+ // ENAMETOOLONG when the filename exceeds the 255-byte limit.
463
+ if (session_id.length > 240) {
464
+ session_id = session_id.slice(0, 240).replace(/-+$/, '');
465
+ }
466
+
467
+ // #732: resolve the leak-guarded repo namespace ONCE per session, BEFORE the
468
+ // quality gate, so both the write path (targetDir, below) AND the rendered
469
+ // frontmatter (`source-repo`) use the SAME sanitised / pseudonym-mapped value.
470
+ // Previously the frontmatter carried the RAW deriveRepo() output via a `repo:`
471
+ // field while only the write path routed through resolveRepoNamespace() — an
472
+ // owner-leaky repo's real name reached the vault through the session-note
473
+ // frontmatter even though the directory AND the learning `source-repo` field
474
+ // were already pseudonym-mapped/redacted (#732 leak-guard bypass).
475
+ const repoNs = resolveRepoNamespace({ vaultName: ctx?.vaultName ?? null });
476
+
477
+ // Quality gate (PRD F1.2): skip sessions whose rendered narrative is too short.
478
+ // Measure on the rendered markdown body so the check is schema-agnostic across
479
+ // v1 and v2 producers. The render is cheap and idempotent; we reuse the result
480
+ // below instead of calling generator() a second time.
481
+ // Runs BEFORE the --force branch so --force does NOT bypass the quality filter.
482
+ // If the generator throws a `vault-mirror: …` validation error (missing
483
+ // required field), it propagates up to vault-mirror.mjs which classifies it
484
+ // as `skipped-invalid` rather than `skipped-quality-low` — semantically more
485
+ // accurate for the metrics summary in session-end Phase 3.7.
486
+ const renderedBody = generator(entry, { repoNs });
487
+ // Strip YAML frontmatter (lines between the first two `---` markers) so we
488
+ // measure only narrative content, not boilerplate.
489
+ const narrativeBody = renderedBody.replace(/^---[\s\S]*?---/m, '').trim();
490
+ const narrativeChars = narrativeBody.length;
491
+ if (narrativeChars < qualityMinNarrativeChars) {
492
+ emitAction({
493
+ action: 'skipped-quality-low',
494
+ path: null,
495
+ kind,
496
+ id: session_id,
497
+ vaultDir,
498
+ meta: { reason: `narrative:${narrativeChars} < min:${qualityMinNarrativeChars}` },
499
+ });
500
+ return;
501
+ }
502
+
503
+ // #660: namespace new writes under a per-repo subdirectory. repoNs was
504
+ // resolved above (before the quality gate) so it is reused here unchanged —
505
+ // the write path and the rendered `source-repo` frontmatter share one value.
506
+ const targetDir = join(resolve(vaultDir), '50-sessions', repoNs);
507
+ if (!dryRun) mkdirSync(targetDir, { recursive: true });
508
+
509
+ // Canonical filename pattern (issue #343): `<session_id>.md` where session_id
510
+ // follows `<branch>-<YYYY-MM-DD>-<HHmm>-<slug>` per the session-id schema.
511
+ // session_id has been validated/sanitised above (isValidSlug → subjectToSlug
512
+ // → uuid fallback). Historical filename inconsistencies in 50-sessions/ are
513
+ // pre-existing on-disk artefacts and are NOT retroactively renamed here.
514
+ const targetPath = join(targetDir, `${session_id}.md`);
515
+
516
+ // #660 IDEMPOTENCY DUAL-PROBE: check the legacy flat path before treating
517
+ // the namespaced path as absent. If a session note already exists flat
518
+ // (pre-namespace migration), skip creating a duplicate.
519
+ const legacyFlatPath = join(resolve(vaultDir), '50-sessions', `${session_id}.md`);
520
+ if (!existsSync(targetPath) && existsSync(legacyFlatPath)) {
521
+ const legacyContent = readFileSync(legacyFlatPath, 'utf8');
522
+ const legacyFm = parseFrontmatter(legacyContent);
523
+ if (legacyFm && legacyFm['_generator'] === GENERATOR_MARKER && legacyFm['id'] === session_id) {
524
+ const entryUpdated = toDate(entry.completed_at);
525
+ if (!force && legacyFm['updated'] && legacyFm['updated'] >= entryUpdated) {
526
+ emitAction({ action: 'skipped-noop', path: legacyFlatPath, kind, id: session_id, vaultDir });
527
+ return;
528
+ }
529
+ // Updated date would advance — fall through to write into the namespaced path.
530
+ }
531
+ }
532
+
533
+ if (existsSync(targetPath)) {
534
+ const existingContent = readFileSync(targetPath, 'utf8');
535
+ const fm = parseFrontmatter(existingContent);
536
+
537
+ if (!fm || !fm['_generator']) {
538
+ // Hand-written: skip
539
+ process.stderr.write(`SKIP hand-written: ${targetPath}\n`);
540
+ emitAction({ action: 'skipped-handwritten', path: targetPath, kind, id: session_id, vaultDir });
541
+ return;
542
+ }
543
+
544
+ if (fm['_generator'] !== GENERATOR_MARKER) {
545
+ process.stderr.write(`SKIP unknown generator: ${targetPath}\n`);
546
+ emitAction({ action: 'skipped-handwritten', path: targetPath, kind, id: session_id, vaultDir });
547
+ return;
548
+ }
549
+
550
+ // Same generator: check id and updated
551
+ if (fm['id'] === session_id) {
552
+ const entryUpdated = toDate(entry.completed_at);
553
+ if (!force && fm['updated'] && fm['updated'] >= entryUpdated) {
554
+ emitAction({ action: 'skipped-noop', path: targetPath, kind, id: session_id, vaultDir });
555
+ return;
556
+ }
557
+ if (!dryRun) writeFileSync(targetPath, renderedBody, 'utf8');
558
+ emitAction({ action: 'updated', path: targetPath, kind, id: session_id, vaultDir });
559
+ return;
560
+ }
561
+ }
562
+
563
+ // File does not exist — create. Reuse the rendered body computed during the
564
+ // quality-gate check (avoids a second generator invocation).
565
+ if (!dryRun) writeFileSync(targetPath, renderedBody, 'utf8');
566
+ emitAction({ action: 'created', path: targetPath, kind, id: session_id, vaultDir });
567
+ }