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,544 @@
1
+ /**
2
+ * narrative-mirror.mjs — Durable per-repo narrative mirror (Epic #673 Phase 1, #675).
3
+ *
4
+ * At session-end, extract from a repo's `.claude/STATE.md` the DURABLE narrative —
5
+ * `## Wave History`, `## Deviations`, `## What Not To Retry`, plus the mission-status
6
+ * rollup — and idempotently mirror it into a generator-owned per-repo vault file, so a
7
+ * reviewer or stand-in can read PER REPO what was done, what failed, and what not to
8
+ * retry, WITHOUT opening the repo.
9
+ *
10
+ * Design decision (Discovery D3): there is NO Wave-History parser and NO `readDeviations`
11
+ * in scripts/lib/state-md.mjs, and `readWhatNotToRetry`'s expected format DIVERGES from
12
+ * the real FeedFoundryV2 fixture (which uses a plain `- <text> (<session>, <date>) —
13
+ * why: …` bullet). Rather than rely on structured parsers that silently return `[]` on
14
+ * format drift, this module extracts the RAW verbatim markdown block of each section —
15
+ * robust to format drift and faithful to the "mirror the narrative for human
16
+ * traceability" goal. Both the top-level `## Wave History` form and the nested
17
+ * `### Wave History (…)` form (under `## Previous Session` in FeedFoundryV2) are handled.
18
+ *
19
+ * Exports:
20
+ * GENERATOR_MARKER — frontmatter sentinel identifying generator-owned files
21
+ * extractNarrative — pure: STATE.md contents → { waveHistory, deviations, whatNotToRetry, missionStatus }
22
+ * renderNarrative — pure: narrative + repo + now → full markdown (frontmatter + body)
23
+ * writeNarrative — idempotent write with skip-handwritten / skip-noop / dry-run + _overview refusal
24
+ * resolveNarrativePath — vaultDir + repoSlug → <vaultDir>/01-projects/<repoSlug>/_session-narrative.md
25
+ * mirrorNarrative — convenience: read STATE.md, resolve vault-dir from config, write (no-op when vault disabled)
26
+ */
27
+
28
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
29
+ import { readFile } from 'node:fs/promises';
30
+ import path from 'node:path';
31
+ import os from 'node:os';
32
+
33
+ import { parseStateMd, parseMissionStatus } from '../state-md.mjs';
34
+ import {
35
+ parseFrontmatter,
36
+ toDate,
37
+ yamlQuoteIfNeeded,
38
+ subjectToSlug,
39
+ } from '../vault-mirror/utils.mjs';
40
+ import { readConfigFile, parseSessionConfig } from '../config.mjs';
41
+ import { validatePathInsideProject } from '../path-utils.mjs';
42
+
43
+ /** Frontmatter sentinel that identifies generator-owned narrative files. */
44
+ export const GENERATOR_MARKER = 'session-orchestrator-vault-status-narrative@1';
45
+
46
+ /** Placeholder used for noop comparison (replaces the live `updated:` value). */
47
+ const UPDATED_PLACEHOLDER = '__UPDATED_PLACEHOLDER__';
48
+
49
+ /**
50
+ * The durable section headings we mirror, in render order. Each is extracted as
51
+ * a raw verbatim block (Discovery D3). Both the top-level `## <Name>` form and a
52
+ * nested `### <Name>` (optionally with a `(…)` suffix) are matched.
53
+ */
54
+ const SECTION_TITLES = {
55
+ waveHistory: 'Wave History',
56
+ deviations: 'Deviations',
57
+ whatNotToRetry: 'What Not To Retry',
58
+ };
59
+
60
+ // ── Raw section extraction ──────────────────────────────────────────────────────
61
+
62
+ /**
63
+ * Expand a leading `~` to the current user's home directory.
64
+ *
65
+ * NOTE: deferred shared-helper extraction. The same `expandHome` pattern lives in
66
+ * other vault-status modules; W2 forbids introducing a shared new file, so this is
67
+ * inlined here. Consolidate into a shared util in a follow-up wave.
68
+ *
69
+ * @param {string} p
70
+ * @returns {string}
71
+ */
72
+ function expandHome(p) {
73
+ if (typeof p !== 'string' || p.length === 0) return p;
74
+ if (p === '~') return os.homedir();
75
+ if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2));
76
+ return p;
77
+ }
78
+
79
+ /**
80
+ * Match an ATX markdown heading line. Returns `{ level, text }` or null.
81
+ *
82
+ * @param {string} line
83
+ * @returns {{ level: number, text: string } | null}
84
+ */
85
+ function matchHeading(line) {
86
+ const m = /^(#{1,6})\s+(.*?)\s*$/.exec(line);
87
+ if (!m) return null;
88
+ return { level: m[1].length, text: m[2] };
89
+ }
90
+
91
+ /**
92
+ * Determine whether a heading text refers to the given section title, tolerating
93
+ * a trailing parenthetical (e.g. `Wave History (main-2026-06-18-1646, completed)`).
94
+ *
95
+ * @param {string} headingText
96
+ * @param {string} title
97
+ * @returns {boolean}
98
+ */
99
+ function headingMatchesTitle(headingText, title) {
100
+ const stripped = headingText.replace(/\s*\(.*\)\s*$/, '').trim();
101
+ return stripped.toLowerCase() === title.toLowerCase();
102
+ }
103
+
104
+ /**
105
+ * Extract the RAW verbatim markdown block for a section by title.
106
+ *
107
+ * Finds the FIRST heading whose text matches `title` (top-level `##` or nested
108
+ * `###` form), then captures every line up to — but not including — the next
109
+ * heading of the SAME-OR-HIGHER level (i.e. heading.level <= openingLevel). The
110
+ * heading line itself is excluded; only the body content is returned. Trailing
111
+ * blank lines are trimmed; an empty/absent section yields ''.
112
+ *
113
+ * @param {string} contents - full STATE.md body (or whole file; headings are scanned linewise)
114
+ * @param {string} title
115
+ * @returns {string}
116
+ */
117
+ function extractSectionBlock(contents, title) {
118
+ if (typeof contents !== 'string' || contents.length === 0) return '';
119
+ const lines = contents.split('\n');
120
+
121
+ let openIdx = -1;
122
+ let openLevel = 0;
123
+ for (let i = 0; i < lines.length; i++) {
124
+ const h = matchHeading(lines[i]);
125
+ if (h && headingMatchesTitle(h.text, title)) {
126
+ openIdx = i;
127
+ openLevel = h.level;
128
+ break;
129
+ }
130
+ }
131
+ if (openIdx === -1) return '';
132
+
133
+ const collected = [];
134
+ for (let i = openIdx + 1; i < lines.length; i++) {
135
+ const h = matchHeading(lines[i]);
136
+ if (h && h.level <= openLevel) break; // next same-or-higher heading closes the block
137
+ collected.push(lines[i]);
138
+ }
139
+
140
+ // Trim leading/trailing blank lines but preserve interior structure verbatim.
141
+ while (collected.length && collected[0].trim() === '') collected.shift();
142
+ while (collected.length && collected[collected.length - 1].trim() === '') collected.pop();
143
+
144
+ return collected.join('\n');
145
+ }
146
+
147
+ // ── Pure extraction ──────────────────────────────────────────────────────────────
148
+
149
+ /**
150
+ * Extract the durable narrative from STATE.md contents.
151
+ *
152
+ * Returns raw verbatim section blocks plus the parsed mission-status rollup.
153
+ * Each section is the empty string when absent or empty. `missionStatus` is the
154
+ * frontmatter `mission-status:` array, or null when the key is absent (both real
155
+ * fixtures lack the frontmatter key — handled gracefully without crashing).
156
+ *
157
+ * Pure — no I/O. Never throws on malformed input (parseStateMd returns null).
158
+ *
159
+ * @param {string} stateMdContents
160
+ * @returns {{ waveHistory: string, deviations: string, whatNotToRetry: string, missionStatus: object[]|null }}
161
+ */
162
+ export function extractNarrative(stateMdContents) {
163
+ const safe = typeof stateMdContents === 'string' ? stateMdContents : '';
164
+
165
+ const parsed = parseStateMd(safe);
166
+ const frontmatter = parsed && parsed.frontmatter ? parsed.frontmatter : null;
167
+ // Section extraction runs over the whole file: headings live in the body, and
168
+ // scanning the raw string is robust whether or not frontmatter parsed cleanly.
169
+ const body = parsed && typeof parsed.body === 'string' ? parsed.body : safe;
170
+
171
+ const missionStatus = frontmatter ? parseMissionStatus(frontmatter) : null;
172
+
173
+ return {
174
+ waveHistory: extractSectionBlock(body, SECTION_TITLES.waveHistory),
175
+ deviations: extractSectionBlock(body, SECTION_TITLES.deviations),
176
+ whatNotToRetry: extractSectionBlock(body, SECTION_TITLES.whatNotToRetry),
177
+ missionStatus,
178
+ };
179
+ }
180
+
181
+ // ── Pure render ────────────────────────────────────────────────────────────────
182
+
183
+ /**
184
+ * Render a single mission-status rollup table from the parsed array.
185
+ *
186
+ * Tolerant of heterogeneous entry shapes (id/task/wave/status are read defensively).
187
+ *
188
+ * @param {object[]} missionStatus
189
+ * @returns {string}
190
+ */
191
+ function renderMissionTable(missionStatus) {
192
+ const lines = [];
193
+ lines.push('| ID | Task | Wave | Status |');
194
+ lines.push('|---|---|---|---|');
195
+ for (const entry of missionStatus) {
196
+ const e = entry && typeof entry === 'object' ? entry : {};
197
+ const id = e.id ?? '—';
198
+ // Escape pipes so a task description never breaks the table layout.
199
+ const task = String(e.task ?? '—').replace(/\|/g, '\\|');
200
+ const wave = e.wave ?? '—';
201
+ const status = e.status ?? '—';
202
+ lines.push(`| ${id} | ${task} | ${wave} | ${status} |`);
203
+ }
204
+ return lines.join('\n');
205
+ }
206
+
207
+ /**
208
+ * Render the full per-repo narrative markdown (frontmatter + body).
209
+ *
210
+ * Frontmatter ordering mirrors render-sessions.mjs conventions: id, type: session,
211
+ * title (double-quoted), created/updated (YYYY-MM-DD via toDate), repo, then
212
+ * `_generator` LAST. Body carries each verbatim section block (omitting empty
213
+ * sections from a "captured" claim but always emitting the heading so the file is
214
+ * structurally stable), plus a mission-status rollup table when present.
215
+ *
216
+ * Pure — no I/O.
217
+ *
218
+ * @param {{
219
+ * repo: string,
220
+ * narrative: { waveHistory: string, deviations: string, whatNotToRetry: string, missionStatus: object[]|null },
221
+ * now: Date,
222
+ * createdIso?: string,
223
+ * updatedPlaceholder?: string,
224
+ * }} opts
225
+ * @returns {string} full markdown
226
+ */
227
+ export function renderNarrative(opts) {
228
+ const { repo, narrative, now, createdIso, updatedPlaceholder } = opts;
229
+ const n = narrative ?? { waveHistory: '', deviations: '', whatNotToRetry: '', missionStatus: null };
230
+
231
+ const nowIso = (now instanceof Date ? now : new Date()).toISOString();
232
+ const createdValue = toDate(createdIso ?? nowIso);
233
+ const updatedValue = updatedPlaceholder ?? toDate(nowIso);
234
+
235
+ const repoSlug = subjectToSlug(String(repo ?? 'unknown')) || 'unknown';
236
+ const noteId = `${repoSlug}-session-narrative`;
237
+ const titleValue = `${repo ?? 'unknown'} — Session Narrative`;
238
+ const title = yamlQuoteIfNeeded(titleValue) === titleValue ? `"${titleValue}"` : yamlQuoteIfNeeded(titleValue);
239
+
240
+ const lines = [];
241
+
242
+ // Frontmatter — `_generator` LAST (markdown-writer / render-sessions convention).
243
+ lines.push('---');
244
+ lines.push(`id: ${noteId}`);
245
+ lines.push('type: session');
246
+ lines.push(`title: ${title}`);
247
+ lines.push(`created: ${createdValue}`);
248
+ lines.push(`updated: ${updatedValue}`);
249
+ lines.push(`repo: ${yamlQuoteIfNeeded(String(repo ?? 'unknown'))}`);
250
+ lines.push(`_generator: ${GENERATOR_MARKER}`);
251
+ lines.push('---');
252
+ lines.push('');
253
+
254
+ // Title + preamble
255
+ lines.push(`# ${repo ?? 'unknown'} — Session Narrative`);
256
+ lines.push('');
257
+ lines.push(
258
+ '> Durable per-repo narrative mirrored from `.claude/STATE.md` (Epic #673). ' +
259
+ 'What was done, what failed, and what not to retry — readable without opening the repo.',
260
+ );
261
+ lines.push('');
262
+
263
+ // Wave History
264
+ lines.push('## Wave History');
265
+ lines.push('');
266
+ lines.push(n.waveHistory && n.waveHistory.trim() ? n.waveHistory : '_(none recorded)_');
267
+ lines.push('');
268
+
269
+ // Deviations
270
+ lines.push('## Deviations');
271
+ lines.push('');
272
+ lines.push(n.deviations && n.deviations.trim() ? n.deviations : '_(none recorded)_');
273
+ lines.push('');
274
+
275
+ // What Not To Retry
276
+ lines.push('## What Not To Retry');
277
+ lines.push('');
278
+ lines.push(n.whatNotToRetry && n.whatNotToRetry.trim() ? n.whatNotToRetry : '_(none recorded)_');
279
+ lines.push('');
280
+
281
+ // Mission Status rollup — only when the frontmatter key was present + non-empty.
282
+ lines.push('## Mission Status');
283
+ lines.push('');
284
+ if (Array.isArray(n.missionStatus) && n.missionStatus.length > 0) {
285
+ lines.push(renderMissionTable(n.missionStatus));
286
+ } else {
287
+ lines.push('_(no mission-status rollup recorded)_');
288
+ }
289
+ lines.push('');
290
+
291
+ return lines.join('\n');
292
+ }
293
+
294
+ // ── Path resolution ──────────────────────────────────────────────────────────────
295
+
296
+ /**
297
+ * Resolve the generator-owned narrative file path inside the vault.
298
+ *
299
+ * @param {string} vaultDir
300
+ * @param {string} repoSlug
301
+ * @returns {string}
302
+ */
303
+ export function resolveNarrativePath(vaultDir, repoSlug) {
304
+ return path.join(vaultDir, '01-projects', repoSlug, '_session-narrative.md');
305
+ }
306
+
307
+ // ── Idempotent write ─────────────────────────────────────────────────────────────
308
+
309
+ /**
310
+ * Normalize a markdown string by replacing the `updated:` frontmatter line with a
311
+ * stable placeholder, enabling byte-for-byte noop comparison.
312
+ *
313
+ * @param {string} content
314
+ * @returns {string}
315
+ */
316
+ function normalizeUpdated(content) {
317
+ return content.replace(/^(updated:\s*)(.+)$/m, `$1${UPDATED_PLACEHOLDER}`);
318
+ }
319
+
320
+ /**
321
+ * Write the narrative file with idempotency guards (mirrors gitlab-portfolio
322
+ * markdown-writer.mjs contract).
323
+ *
324
+ * Guard order:
325
+ * 0. dry-run → never write, return { action: 'dry-run' }.
326
+ * 1. SAFETY (Epic #673 #1 risk): refuse if the target basename is `_overview.md`
327
+ * — that file is a hand-authored vault overview and must NEVER be clobbered by
328
+ * a generator. Return { action: 'skipped-handwritten' }.
329
+ * 2. skip-handwritten: existing file with no `_generator` or a FOREIGN `_generator`
330
+ * marker is hand-authored / owned by another generator → never overwrite.
331
+ * 3. skip-noop: rendered content (modulo the `updated:` timestamp) byte-identical
332
+ * to the existing content → no write.
333
+ * 4. otherwise write (mkdir -p the parent dir first).
334
+ *
335
+ * @param {{
336
+ * outputPath: string,
337
+ * content: string,
338
+ * dryRun?: boolean,
339
+ * fs?: { readFileSync?: Function, writeFileSync?: Function, mkdirSync?: Function, existsSync?: Function },
340
+ * }} opts
341
+ * @returns {{ action: 'written'|'skipped-handwritten'|'skipped-noop'|'dry-run', path: string }}
342
+ */
343
+ export function writeNarrative(opts) {
344
+ const { outputPath, content, dryRun = false, fs: injectedFs } = opts;
345
+
346
+ const fsReadFile = injectedFs?.readFileSync ?? readFileSync;
347
+ const fsWriteFile = injectedFs?.writeFileSync ?? writeFileSync;
348
+ const fsMkdir = injectedFs?.mkdirSync ?? mkdirSync;
349
+ const fsExists = injectedFs?.existsSync ?? existsSync;
350
+
351
+ // Dry-run: never write.
352
+ if (dryRun) {
353
+ return { action: 'dry-run', path: outputPath };
354
+ }
355
+
356
+ // SAFETY (Epic #673 #1 risk table): a generator must NEVER clobber the
357
+ // hand-authored vault `_overview.md`. Defense-in-depth alongside the
358
+ // dedicated `_session-narrative.md` target name and the marker guard below.
359
+ if (path.basename(outputPath) === '_overview.md') {
360
+ return { action: 'skipped-handwritten', path: outputPath };
361
+ }
362
+
363
+ if (fsExists(outputPath)) {
364
+ let existingContent;
365
+ try {
366
+ existingContent = fsReadFile(outputPath, 'utf8');
367
+ } catch {
368
+ existingContent = null; // unreadable → treat as fresh write
369
+ }
370
+
371
+ if (existingContent !== null) {
372
+ const fm = parseFrontmatter(existingContent);
373
+
374
+ // skip-handwritten: no _generator or a foreign marker.
375
+ if (!fm || !fm['_generator']) {
376
+ return { action: 'skipped-handwritten', path: outputPath };
377
+ }
378
+ if (fm['_generator'] !== GENERATOR_MARKER) {
379
+ return { action: 'skipped-handwritten', path: outputPath };
380
+ }
381
+
382
+ // skip-noop: content identical modulo the `updated:` timestamp.
383
+ if (normalizeUpdated(existingContent) === normalizeUpdated(content)) {
384
+ return { action: 'skipped-noop', path: outputPath };
385
+ }
386
+ }
387
+ }
388
+
389
+ fsMkdir(path.dirname(outputPath), { recursive: true });
390
+ fsWriteFile(outputPath, content, 'utf8');
391
+ return { action: 'written', path: outputPath };
392
+ }
393
+
394
+ // ── Loose-slug folder matching (issue #829 Finding 3) ────────────────────────────
395
+
396
+ /**
397
+ * Fold a slug candidate to a comparison-only form: lowercase, every
398
+ * non-alphanumeric character stripped. Used purely for equality comparison —
399
+ * never as a slug value itself. `subjectToSlug` (vault-mirror/utils.mjs) does
400
+ * NOT insert a hyphen at a camelCase boundary, so `GotzendorferV2` mints
401
+ * `gotzendorferv2` — a DIFFERENT folder than a hand-created `gotzendorfer-v2`.
402
+ * `looseSlug('gotzendorfer-v2') === looseSlug('gotzendorferv2')`, so the two
403
+ * forms can be reconciled without changing `subjectToSlug` itself.
404
+ *
405
+ * @param {string} s
406
+ * @returns {string}
407
+ */
408
+ function looseSlug(s) {
409
+ return String(s ?? '').toLowerCase().replace(/[^a-z0-9]/g, '');
410
+ }
411
+
412
+ /**
413
+ * Loose-match a freshly-minted candidate slug against EXISTING `01-projects/`
414
+ * folder names before falling back to minting a new one (issue #829 Finding
415
+ * 3). When EXACTLY ONE existing folder's {@link looseSlug} equals the
416
+ * candidate's, that folder's EXACT on-disk name is reused (auto-resolving
417
+ * casing/punctuation drift, e.g. `GotzendorferV2` → `gotzendorfer-v2`,
418
+ * `LeadPipeDACH` → `leadpipe-dach`). Zero or MORE THAN ONE matches are
419
+ * ambiguous (or there is genuinely no match) — the caller's `subjectToSlug`
420
+ * candidate is returned unchanged, preserving current behaviour.
421
+ *
422
+ * Best-effort by design: any read failure (missing `01-projects/` dir,
423
+ * permission error, first-ever write for this vault, …) falls through to the
424
+ * candidate slug rather than throwing — a listing failure must never block a
425
+ * narrative write.
426
+ *
427
+ * @param {string} vaultDir
428
+ * @param {string} candidateSlug
429
+ * @param {{ readdirSync?: Function }} [fsSeam] — injectable `readdirSync` (test seam).
430
+ * @returns {string}
431
+ */
432
+ function resolveLooseSlug(vaultDir, candidateSlug, fsSeam = {}) {
433
+ const readdir = fsSeam.readdirSync ?? readdirSync;
434
+ let entries;
435
+ try {
436
+ entries = readdir(path.join(vaultDir, '01-projects'), { withFileTypes: true });
437
+ } catch {
438
+ return candidateSlug;
439
+ }
440
+ if (!Array.isArray(entries)) return candidateSlug;
441
+
442
+ const candidateLoose = looseSlug(candidateSlug);
443
+ const matches = [];
444
+ for (const entry of entries) {
445
+ // Accept either a Dirent (real fs) or a plain string (a simplified test
446
+ // seam) — only Dirents that report as non-directories are excluded; a
447
+ // plain string is assumed to already denote a project folder.
448
+ const isDirent = entry && typeof entry === 'object' && typeof entry.name === 'string';
449
+ const name = isDirent ? entry.name : entry;
450
+ if (typeof name !== 'string' || name.length === 0) continue;
451
+ if (isDirent && typeof entry.isDirectory === 'function' && !entry.isDirectory()) continue;
452
+ if (looseSlug(name) === candidateLoose) matches.push(name);
453
+ }
454
+
455
+ return matches.length === 1 ? matches[0] : candidateSlug;
456
+ }
457
+
458
+ // ── Convenience orchestration ────────────────────────────────────────────────────
459
+
460
+ /**
461
+ * Read a repo's STATE.md, resolve the vault-dir from Session Config, render the
462
+ * narrative, and write it idempotently.
463
+ *
464
+ * Silently no-ops (returns { action: 'skipped-vault-disabled' }) when
465
+ * `vault-integration.enabled` is false/absent or the vault-dir is unset. The
466
+ * resolved vault path is validated to live inside the (home-expanded) vault root
467
+ * before any write.
468
+ *
469
+ * @param {{
470
+ * repoRoot: string,
471
+ * repo: string,
472
+ * now?: Date,
473
+ * dryRun?: boolean,
474
+ * fs?: object,
475
+ * hostPaths?: { env?: Record<string, string|undefined>, ownerConfig?: object },
476
+ * }} opts
477
+ * `hostPaths` is forwarded verbatim to {@link parseSessionConfig}'s `hostPaths` DI
478
+ * seam (issue #653). Tests MUST pass a hermetic ctx (e.g. `{ env: {}, ownerConfig:
479
+ * undefined }`) when asserting a fixture's committed `vault-dir` — omitting it reads
480
+ * the REAL host `owner.yaml`, whose `paths.vault-dir` override (if set) wins over the
481
+ * fixture value and bleeds into the assertion (issue #783). Production callers omit
482
+ * this — the default (real owner.yaml resolution) is the correct host-local behavior.
483
+ * @returns {Promise<{ action: string, path?: string }>}
484
+ */
485
+ export async function mirrorNarrative(opts) {
486
+ const { repoRoot, repo, now = new Date(), dryRun = false, fs: injectedFs, hostPaths } = opts;
487
+
488
+ if (typeof repoRoot !== 'string' || repoRoot.length === 0) {
489
+ return { action: 'skipped-vault-disabled' };
490
+ }
491
+
492
+ // Defense-in-depth: when the caller omits (or passes an empty) `repo`, derive
493
+ // it from the repoRoot basename rather than mis-filing the narrative under the
494
+ // 'unknown' slug. A missing repo name must never silently mis-file (#675 review).
495
+ const repoName = (typeof repo === 'string' && repo.trim().length > 0)
496
+ ? repo
497
+ : path.basename(path.resolve(repoRoot));
498
+
499
+ // Read Session Config (CLAUDE.md / AGENTS.md) and resolve vault settings.
500
+ let config;
501
+ try {
502
+ const configText = await readConfigFile(repoRoot);
503
+ config = parseSessionConfig(configText, { hostPaths });
504
+ } catch {
505
+ return { action: 'skipped-vault-disabled' };
506
+ }
507
+
508
+ const vaultIntegration = config?.['vault-integration'];
509
+ if (!vaultIntegration || vaultIntegration.enabled !== true) {
510
+ return { action: 'skipped-vault-disabled' };
511
+ }
512
+ const rawVaultDir = vaultIntegration['vault-dir'];
513
+ if (!rawVaultDir || typeof rawVaultDir !== 'string') {
514
+ return { action: 'skipped-vault-disabled' };
515
+ }
516
+
517
+ const vaultDir = path.resolve(expandHome(rawVaultDir));
518
+ const candidateSlug = subjectToSlug(repoName) || 'unknown';
519
+ // Loose-match against existing 01-projects/ folders before minting a new
520
+ // slug (issue #829 Finding 3) — see resolveLooseSlug for the ambiguity
521
+ // rules. Falls through to `candidateSlug` unchanged on any read failure.
522
+ const repoSlug = resolveLooseSlug(vaultDir, candidateSlug, { readdirSync: injectedFs?.readdirSync });
523
+ const outputPath = resolveNarrativePath(vaultDir, repoSlug);
524
+
525
+ // Defense-in-depth: ensure the resolved file stays inside the vault root.
526
+ const inside = validatePathInsideProject(path.relative(vaultDir, outputPath), vaultDir);
527
+ if (!inside.ok) {
528
+ return { action: 'skipped-invalid-path', path: outputPath };
529
+ }
530
+
531
+ // Read STATE.md (best-effort; absent STATE.md → nothing to mirror).
532
+ const stateMdPath = path.join(repoRoot, '.claude', 'STATE.md');
533
+ let stateContents;
534
+ try {
535
+ stateContents = await readFile(stateMdPath, 'utf8');
536
+ } catch {
537
+ return { action: 'skipped-no-statemd', path: outputPath };
538
+ }
539
+
540
+ const narrative = extractNarrative(stateContents);
541
+ const content = renderNarrative({ repo: repoName, narrative, now });
542
+
543
+ return writeNarrative({ outputPath, content, dryRun, fs: injectedFs });
544
+ }
@@ -0,0 +1,152 @@
1
+ /**
2
+ * vault-sync-baseline.mjs — Snapshot writer + diff comparator for vault-sync #327.
3
+ *
4
+ * Pure functions (no top-level side effects). The only function that writes to
5
+ * disk is `writeBaseline()`, which uses an atomic write-temp-then-rename pattern
6
+ * identical to the one used in scripts/lib/learnings.mjs.
7
+ *
8
+ * Exports:
9
+ * computeSchemaHash(schemaText) → string (8-char SHA-256 hex prefix)
10
+ * writeBaseline(filePath, payload) → void (atomic fs write)
11
+ * readBaseline(filePath) → object|null
12
+ * diffBaseline({ baselineErrors, currentErrors }) → DiffResult
13
+ *
14
+ * No external dependencies — Node 20+ stdlib (node:crypto, node:fs, node:path) only.
15
+ *
16
+ * Issue: #327 vault-sync baseline-diff reporting.
17
+ */
18
+
19
+ import { digestSha256Short } from './crypto-digest-utils.mjs';
20
+ import { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync } from 'node:fs';
21
+ import { dirname } from 'node:path';
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // computeSchemaHash
25
+ // ---------------------------------------------------------------------------
26
+
27
+ /**
28
+ * Compute a stable 8-character SHA-256 hex prefix of the given schema text.
29
+ *
30
+ * The caller is responsible for extracting the relevant text (e.g. the block
31
+ * between BEGIN/END GENERATED SCHEMA sentinels in validator.mjs).
32
+ *
33
+ * @param {string} schemaText
34
+ * @returns {string} 8-character lowercase hex prefix of SHA-256 digest
35
+ */
36
+ export function computeSchemaHash(schemaText) {
37
+ return digestSha256Short(schemaText);
38
+ }
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // writeBaseline
42
+ // ---------------------------------------------------------------------------
43
+
44
+ /**
45
+ * Serialize a baseline snapshot to disk using an atomic write-temp-then-rename
46
+ * pattern. Creates intermediate directories if needed.
47
+ *
48
+ * @param {string} filePath Absolute path to the target baseline JSON file.
49
+ * @param {{ errors: object[], warnings: object[], schemaHash: string, isoTimestamp: string, vaultDir: string }} payload
50
+ * @returns {void}
51
+ */
52
+ export function writeBaseline(filePath, { errors, warnings, schemaHash, isoTimestamp, vaultDir }) {
53
+ const doc = {
54
+ schema_hash: schemaHash,
55
+ generated_at: isoTimestamp,
56
+ vault_dir: vaultDir,
57
+ error_count: errors.length,
58
+ warning_count: warnings.length,
59
+ errors,
60
+ warnings,
61
+ };
62
+
63
+ const dir = dirname(filePath);
64
+ mkdirSync(dir, { recursive: true });
65
+
66
+ const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
67
+ writeFileSync(tmp, JSON.stringify(doc, null, 2) + '\n', 'utf8');
68
+ renameSync(tmp, filePath);
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // readBaseline
73
+ // ---------------------------------------------------------------------------
74
+
75
+ /**
76
+ * Read and parse a baseline snapshot file.
77
+ *
78
+ * Returns null (never throws) if the file is missing, unreadable, or
79
+ * contains malformed JSON or an unexpected shape.
80
+ *
81
+ * @param {string} filePath Absolute path to the baseline JSON file.
82
+ * @returns {{ schemaHash: string, errors: object[], warnings: object[], generated_at: string, vault_dir: string, error_count: number, warning_count: number } | null}
83
+ */
84
+ export function readBaseline(filePath) {
85
+ if (!existsSync(filePath)) return null;
86
+ try {
87
+ const raw = readFileSync(filePath, 'utf8');
88
+ const doc = JSON.parse(raw);
89
+ if (
90
+ typeof doc !== 'object' ||
91
+ doc === null ||
92
+ typeof doc.schema_hash !== 'string' ||
93
+ !Array.isArray(doc.errors) ||
94
+ !Array.isArray(doc.warnings)
95
+ ) {
96
+ return null;
97
+ }
98
+ return {
99
+ schemaHash: doc.schema_hash,
100
+ generated_at: doc.generated_at ?? '',
101
+ vault_dir: doc.vault_dir ?? '',
102
+ error_count: doc.error_count ?? doc.errors.length,
103
+ warning_count: doc.warning_count ?? doc.warnings.length,
104
+ errors: doc.errors,
105
+ warnings: doc.warnings,
106
+ };
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+
112
+ // ---------------------------------------------------------------------------
113
+ // diffBaseline
114
+ // ---------------------------------------------------------------------------
115
+
116
+ /**
117
+ * Compute a set-difference between a baseline error (or warning) array and the
118
+ * current run's array. Identity is determined by the triple (file, path, message).
119
+ *
120
+ * Call once for errors, once for warnings — the function is symmetric.
121
+ *
122
+ * @param {{ baselineErrors: object[], currentErrors: object[] }} opts
123
+ * @returns {{ newErrors: object[], resolvedErrors: object[], baselineCount: number, currentCount: number }}
124
+ */
125
+ export function diffBaseline({ baselineErrors, currentErrors }) {
126
+ /**
127
+ * Build a stable string key from an error/warning entry.
128
+ * Falls back gracefully when fields are missing.
129
+ *
130
+ * @param {object} e
131
+ * @returns {string}
132
+ */
133
+ function entryKey(e) {
134
+ const file = e.file ?? '';
135
+ const path = e.path ?? (e.type ?? '');
136
+ const message = e.message ?? '';
137
+ return `${file}\x00${path}\x00${message}`;
138
+ }
139
+
140
+ const baselineSet = new Set((baselineErrors ?? []).map(entryKey));
141
+ const currentSet = new Set((currentErrors ?? []).map(entryKey));
142
+
143
+ const newErrors = (currentErrors ?? []).filter((e) => !baselineSet.has(entryKey(e)));
144
+ const resolvedErrors = (baselineErrors ?? []).filter((e) => !currentSet.has(entryKey(e)));
145
+
146
+ return {
147
+ newErrors,
148
+ resolvedErrors,
149
+ baselineCount: (baselineErrors ?? []).length,
150
+ currentCount: (currentErrors ?? []).length,
151
+ };
152
+ }