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,160 @@
1
+ /**
2
+ * Package-manager detection for Node.js repos (issue #183).
3
+ *
4
+ * Consumed by bootstrap when generating .orchestrator/policy/quality-gates.json
5
+ * to pick sensible default test/typecheck/lint commands.
6
+ *
7
+ * Detection is synchronous. An explicit package.json packageManager field wins;
8
+ * otherwise, root lockfiles are considered when they are not explicitly ignored
9
+ * by the repo's root .gitignore.
10
+ */
11
+
12
+ import { existsSync, readFileSync } from 'node:fs';
13
+ import { join } from 'node:path';
14
+
15
+ const PACKAGE_MANAGERS = new Set(['pnpm', 'yarn', 'bun', 'npm']);
16
+
17
+ const LOCKFILES = [
18
+ ['pnpm-lock.yaml', 'pnpm'],
19
+ ['yarn.lock', 'yarn'],
20
+ ['bun.lockb', 'bun'],
21
+ ['package-lock.json', 'npm'],
22
+ ];
23
+
24
+ function packageManagerFromPackageJson(repoRoot) {
25
+ const path = join(repoRoot, 'package.json');
26
+ if (!existsSync(path)) return null;
27
+
28
+ try {
29
+ const parsed = JSON.parse(readFileSync(path, 'utf8'));
30
+ const value = parsed.packageManager;
31
+ if (typeof value !== 'string') return null;
32
+
33
+ const name = value.split('@')[0];
34
+ return PACKAGE_MANAGERS.has(name) ? name : null;
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ function escapeRegExp(value) {
41
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
42
+ }
43
+
44
+ function gitignorePatternMatchesRootFile(pattern, fileName) {
45
+ let normalized = pattern.trim();
46
+ if (!normalized || normalized.startsWith('#') || normalized.endsWith('/')) {
47
+ return false;
48
+ }
49
+
50
+ if (normalized.startsWith('/')) {
51
+ normalized = normalized.slice(1);
52
+ }
53
+
54
+ if (normalized.includes('/')) {
55
+ return normalized === fileName;
56
+ }
57
+
58
+ if (normalized.includes('*') || normalized.includes('?')) {
59
+ const source = normalized
60
+ .split('*')
61
+ .map((part) => part.split('?').map(escapeRegExp).join('.'))
62
+ .join('.*');
63
+ return new RegExp(`^${source}$`).test(fileName);
64
+ }
65
+
66
+ return normalized === fileName;
67
+ }
68
+
69
+ /**
70
+ * Returns true when `fileName` sits at the repo root AND is matched
71
+ * (non-negated) by a pattern in the repo's root `.gitignore`.
72
+ *
73
+ * Shared by sibling package-manager detectors (ecosystem-wizard's
74
+ * detectPackageManagerFromRoot, quality-gates-cache's computeDependencyHash)
75
+ * so a gitignored root lockfile is treated as absent everywhere, not just
76
+ * in detectPackageManager() below (issue #715 bug class).
77
+ *
78
+ * @param {string} repoRoot - absolute path to the repo root
79
+ * @param {string} fileName - root-level file name to test (e.g. "pnpm-lock.yaml")
80
+ * @returns {boolean}
81
+ */
82
+ export function isIgnoredRootFile(repoRoot, fileName) {
83
+ const path = join(repoRoot, '.gitignore');
84
+ if (!existsSync(path)) return false;
85
+
86
+ let ignored = false;
87
+ try {
88
+ const lines = readFileSync(path, 'utf8').split(/\r?\n/);
89
+ for (const rawLine of lines) {
90
+ const line = rawLine.trim();
91
+ if (!line || line.startsWith('#')) continue;
92
+
93
+ const negated = line.startsWith('!');
94
+ const pattern = negated ? line.slice(1) : line;
95
+ if (gitignorePatternMatchesRootFile(pattern, fileName)) {
96
+ ignored = !negated;
97
+ }
98
+ }
99
+ } catch {
100
+ return false;
101
+ }
102
+
103
+ return ignored;
104
+ }
105
+
106
+ /**
107
+ * Detects the package manager used in the given repo.
108
+ *
109
+ * @param {string} repoRoot - absolute path to the repo root
110
+ * @returns {"pnpm"|"yarn"|"bun"|"npm"|null}
111
+ */
112
+ export function detectPackageManager(repoRoot) {
113
+ const explicit = packageManagerFromPackageJson(repoRoot);
114
+ if (explicit) return explicit;
115
+
116
+ for (const [lockfile, packageManager] of LOCKFILES) {
117
+ if (
118
+ existsSync(join(repoRoot, lockfile)) &&
119
+ !isIgnoredRootFile(repoRoot, lockfile)
120
+ ) {
121
+ return packageManager;
122
+ }
123
+ }
124
+
125
+ return null;
126
+ }
127
+
128
+ const COMMANDS = {
129
+ pnpm: {
130
+ test: { command: 'pnpm test --run', required: true },
131
+ typecheck: { command: 'pnpm typecheck', required: true },
132
+ lint: { command: 'pnpm lint', required: true },
133
+ },
134
+ npm: {
135
+ test: { command: 'npm test', required: true },
136
+ typecheck: { command: 'npm run typecheck', required: true },
137
+ lint: { command: 'npm run lint', required: true },
138
+ },
139
+ yarn: {
140
+ test: { command: 'yarn test', required: true },
141
+ typecheck: { command: 'yarn typecheck', required: true },
142
+ lint: { command: 'yarn lint', required: true },
143
+ },
144
+ bun: {
145
+ test: { command: 'bun test', required: true },
146
+ typecheck: { command: 'bun run typecheck', required: true },
147
+ lint: { command: 'bun run lint', required: true },
148
+ },
149
+ };
150
+
151
+ /**
152
+ * Returns the default quality-gate commands for a package manager.
153
+ * Unknown / null → npm defaults (most portable).
154
+ *
155
+ * @param {"pnpm"|"yarn"|"bun"|"npm"|null|undefined} packageManager
156
+ * @returns {{test: {command: string, required: boolean}, typecheck: {command: string, required: boolean}, lint: {command: string, required: boolean}}}
157
+ */
158
+ export function defaultQualityGateCommands(packageManager) {
159
+ return COMMANDS[packageManager] || COMMANDS.npm;
160
+ }
@@ -0,0 +1,217 @@
1
+ /**
2
+ * path-utils.mjs — Path-traversal-safe helpers for cross-platform use.
3
+ *
4
+ * SECURITY-CRITICAL: Backs enforce-scope.mjs (CWE-23 protection).
5
+ *
6
+ * Most functions are pure (no filesystem I/O). The exception is
7
+ * validatePathInsideProject, which performs a two-phase lexical + realpath
8
+ * guard and calls realpathSync on Phase 2. All other exported helpers remain
9
+ * pure and do not follow symlinks.
10
+ *
11
+ * Part of v3.0.0 migration (Epic #124, issue #130).
12
+ */
13
+
14
+ import path from 'node:path';
15
+ import { realpathSync } from 'node:fs';
16
+
17
+ const IS_WINDOWS = process.platform === 'win32';
18
+
19
+ /**
20
+ * Documented attack patterns covered by isPathInside.
21
+ * Exported for test self-check (Wave 4) and audit documentation.
22
+ */
23
+ export const CWE_23_ATTACK_PATTERNS = [
24
+ 'relative-escape', // ../
25
+ 'deep-relative-escape', // ../../
26
+ 'absolute-escape', // /etc/passwd when parent is /home/user
27
+ 'prefix-match-confusion', // /home/userx vs /home/user (not a descendant)
28
+ 'windows-drive-escape', // C:\..\Windows
29
+ 'case-mismatch', // Windows Foo vs foo (locale-aware)
30
+ 'cross-drive', // C:\ vs D:\
31
+ 'unc-path', // \\server\share (Windows — unconditionally rejected)
32
+ 'null-byte-injection', // embedded \x00 (rejected via input guard)
33
+ ];
34
+
35
+ function _assertNonEmptyString(value, fnName, argName) {
36
+ if (typeof value !== 'string' || value.length === 0) {
37
+ throw new TypeError(`${fnName}: ${argName} must be a non-empty string`);
38
+ }
39
+ if (value.includes('\x00')) {
40
+ throw new TypeError(`${fnName}: ${argName} must not contain null bytes`);
41
+ }
42
+ }
43
+
44
+ function _normalizeForCompare(p) {
45
+ // Locale-stable lowercasing on Windows to match filesystem case-insensitivity.
46
+ // toLocaleLowerCase('en-US') avoids Turkish-I style divergence from toLowerCase().
47
+ return IS_WINDOWS ? p.toLocaleLowerCase('en-US') : p;
48
+ }
49
+
50
+ /**
51
+ * Returns true if child is strictly inside parent (descendant, not equal).
52
+ *
53
+ * Rejects: relative/absolute escapes, UNC paths on Windows, null bytes, empty inputs.
54
+ * Case-insensitive on Windows (locale-stable).
55
+ *
56
+ * @param {string} child — path to test
57
+ * @param {string} parent — containing path
58
+ * @returns {boolean}
59
+ * @throws {TypeError} on invalid input (empty, null-byte, non-string)
60
+ */
61
+ export function isPathInside(child, parent) {
62
+ _assertNonEmptyString(child, 'isPathInside', 'child');
63
+ _assertNonEmptyString(parent, 'isPathInside', 'parent');
64
+
65
+ // Unconditionally reject UNC paths on Windows as child input.
66
+ if (IS_WINDOWS && (child.startsWith('\\\\') || child.startsWith('//'))) {
67
+ return false;
68
+ }
69
+
70
+ const resolvedParent = path.resolve(parent);
71
+ const resolvedChild = path.resolve(child);
72
+ const pa = _normalizeForCompare(resolvedParent);
73
+ const ch = _normalizeForCompare(resolvedChild);
74
+ const rel = path.relative(pa, ch);
75
+
76
+ // Descendant iff: non-empty relative path, no leading '..', and not absolute.
77
+ return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
78
+ }
79
+
80
+ /**
81
+ * Returns relative path from root to fullPath.
82
+ *
83
+ * Return contract:
84
+ * - `'.'` → fullPath IS root (same directory)
85
+ * - `'<rel>'` → fullPath is strictly inside root
86
+ * - `null` → fullPath is outside root
87
+ *
88
+ * Callers MUST distinguish `null` from `'.'` — do not use falsy checks.
89
+ *
90
+ * @param {string} root
91
+ * @param {string} fullPath
92
+ * @returns {string|null}
93
+ */
94
+ export function relativeFromRoot(root, fullPath) {
95
+ _assertNonEmptyString(root, 'relativeFromRoot', 'root');
96
+ _assertNonEmptyString(fullPath, 'relativeFromRoot', 'fullPath');
97
+
98
+ const resolvedRoot = path.resolve(root);
99
+ const resolvedFull = path.resolve(fullPath);
100
+
101
+ if (_normalizeForCompare(resolvedRoot) === _normalizeForCompare(resolvedFull)) {
102
+ return '.';
103
+ }
104
+ if (!isPathInside(fullPath, root)) {
105
+ return null;
106
+ }
107
+ return path.relative(resolvedRoot, resolvedFull);
108
+ }
109
+
110
+ /**
111
+ * Lowercase on Windows (locale-stable), passthrough on POSIX.
112
+ *
113
+ * @param {string} p
114
+ * @returns {string}
115
+ */
116
+ export function normalizeCase(p) {
117
+ _assertNonEmptyString(p, 'normalizeCase', 'p');
118
+ return _normalizeForCompare(p);
119
+ }
120
+
121
+ /**
122
+ * Returns true if two paths reside on the same drive.
123
+ * On POSIX, always returns true (single-root filesystem).
124
+ *
125
+ * @param {string} a
126
+ * @param {string} b
127
+ * @returns {boolean}
128
+ */
129
+ export function sameDrive(a, b) {
130
+ _assertNonEmptyString(a, 'sameDrive', 'a');
131
+ _assertNonEmptyString(b, 'sameDrive', 'b');
132
+ if (!IS_WINDOWS) return true;
133
+ const rootA = path.parse(path.resolve(a)).root.toLowerCase();
134
+ const rootB = path.parse(path.resolve(b)).root.toLowerCase();
135
+ return rootA === rootB;
136
+ }
137
+
138
+ /**
139
+ * Two-phase path-traversal + symlink-escape guard.
140
+ * Phase 1: lexical isPathInside check (rejects ../ traversal).
141
+ * Phase 2: realpath resolution + isPathInside (rejects symlink escape).
142
+ * ENOENT in Phase 2 is swallowed — lexical check is sufficient when path does not yet exist.
143
+ *
144
+ * @param {string} input - User-supplied path (relative or absolute)
145
+ * @param {string} root - Trusted parent directory (absolute)
146
+ * @param {object} [opts] - Optional behaviour modifiers.
147
+ * @param {boolean} [opts.canonicalizeRoot=false] - When true and `root` exists on
148
+ * disk, canonicalize it via `realpathSync` BEFORE the Phase 1 lexical check.
149
+ * This is the documented workaround for macOS tmpdirs where `/var/...` is a
150
+ * symlink to `/private/var/...` — without canonicalization the Phase 2
151
+ * realpath check would reject legitimate descendants because the realpath
152
+ * namespace differs from the lexical namespace.
153
+ * DEFAULT IS `false` — the legacy two-phase behaviour is byte-identical when
154
+ * the option is omitted or set to anything other than `true`.
155
+ *
156
+ * ASYMMETRIC OPT-IN (#548 A3) — current callers split as follows:
157
+ * - opt-in (`canonicalizeRoot: true`):
158
+ * memory-proposals/sink.mjs × 3, memory-proposals/store.mjs × 1
159
+ * These run against `mkdtempSync` paths in tests on macOS and require
160
+ * the workaround to function on `/var → /private/var`.
161
+ * - default (`canonicalizeRoot` omitted): run-migrate-v2-cross-repo.mjs,
162
+ * promote-vault-strict.mjs × 2, vault-integration-watcher.mjs,
163
+ * config/test.mjs, playwright-driver/runner.mjs, profiles/schema.mjs,
164
+ * persona-panel/catalog-loader.mjs × 2, gitlab-portfolio/cli.mjs.
165
+ * Flipping the default to `true` is a behaviour change for the 10 default
166
+ * callers (root would be realpath-resolved before the lexical compare); it
167
+ * is tracked separately and intentionally NOT done here. New callers should
168
+ * pass `{ canonicalizeRoot: true }` when they operate against tmpdir roots.
169
+ * @returns {{ok: true, realPath: string|undefined, lexicalPath: string} | {ok: false, reason: 'lexical'|'symlink'|'input', error?: string}}
170
+ */
171
+ export function validatePathInsideProject(input, root, opts = {}) {
172
+ // 1. Input validation
173
+ if (typeof input !== 'string' || input.length === 0) {
174
+ return { ok: false, reason: 'input', error: 'input must be a non-empty string' };
175
+ }
176
+ if (input.includes('\0')) {
177
+ return { ok: false, reason: 'input', error: 'input contains null byte' };
178
+ }
179
+ // 1b. Optional root canonicalization (#544 M3 — opt-in for callers that
180
+ // operate against mkdtempSync paths on macOS). Strictly gated on
181
+ // opts.canonicalizeRoot === true so unconditional callers see byte-identical
182
+ // legacy behaviour.
183
+ //
184
+ // #548 A5-ENOENT: the previous `existsSync(root) && realpathSync(root)`
185
+ // pattern was a TOCTOU race — root could be deleted between the existsSync
186
+ // check and the realpathSync call. Drop the precheck and rely on the
187
+ // try/catch alone, narrowed to the two expected error codes (ENOENT for
188
+ // racy tmp-dir teardown, EACCES for unreadable paths). Other errors
189
+ // surface so genuine filesystem faults are not silently swallowed.
190
+ let effectiveRoot = root;
191
+ if (opts && opts.canonicalizeRoot === true) {
192
+ try {
193
+ effectiveRoot = realpathSync(root);
194
+ } catch (err) {
195
+ if (err && err.code !== 'ENOENT' && err.code !== 'EACCES') throw err;
196
+ // Best-effort: fall back to lexical root if realpath fails with the
197
+ // expected codes. ENOENT/EACCES are normal during tmp-dir tests and
198
+ // racy filesystem operations.
199
+ }
200
+ }
201
+ // 2. Phase 1: lexical
202
+ const lexicalPath = path.resolve(effectiveRoot, input);
203
+ if (!isPathInside(lexicalPath, effectiveRoot)) {
204
+ return { ok: false, reason: 'lexical' };
205
+ }
206
+ // 3. Phase 2: realpath (ENOENT swallowed; other errors propagate — SEC-Q2-LOW-1)
207
+ try {
208
+ const realPath = realpathSync(lexicalPath);
209
+ if (!isPathInside(realPath, effectiveRoot)) {
210
+ return { ok: false, reason: 'symlink' };
211
+ }
212
+ return { ok: true, realPath, lexicalPath };
213
+ } catch (err) {
214
+ if (err && err.code !== 'ENOENT') throw err;
215
+ return { ok: true, realPath: undefined, lexicalPath };
216
+ }
217
+ }
@@ -0,0 +1,310 @@
1
+ /**
2
+ * merger.mjs — Sentinel-region three-way merge for peer cards (USER.md / AGENT.md)
3
+ * for issue #503 (Wave 2 I8).
4
+ *
5
+ * Consumers:
6
+ * - /evolve --dialectic pass (future #506)
7
+ *
8
+ * Design notes
9
+ * ────────────
10
+ * • SENTINEL-REGION MERGE (Strategy A per #503 D3). Managed regions are delimited by
11
+ * HTML comment sentinels:
12
+ * <!-- BEGIN MANAGED: <section-name> -->
13
+ * ...AI-replaceable content...
14
+ * <!-- END MANAGED: <section-name> -->
15
+ * Content outside sentinels is hand-owned and preserved verbatim. Content inside
16
+ * sentinels is AI-replaceable.
17
+ * • Pure functions — no fs imports, no IO, deterministic. Same input → same output.
18
+ * • Conflict-surfacing — duplicate section names and orphan BEGIN sentinels become
19
+ * `conflicts[]` entries. The caller decides UX (warn vs prompt vs reject).
20
+ * • Idempotent — `mergePeerCard(body, {})` returns body byte-equivalent (modulo the
21
+ * serialize round-trip; the sentinel comments are re-emitted in the same form).
22
+ *
23
+ * Section-name grammar: `[A-Za-z0-9_-]+` (word characters + hyphen).
24
+ */
25
+
26
+ const SECTION_NAME = '[\\w-]+';
27
+ const SENTINEL_BEGIN_RE = new RegExp(`<!--\\s*BEGIN\\s+MANAGED:\\s*(${SECTION_NAME})\\s*-->`, 'g');
28
+
29
+ /**
30
+ * @typedef {Object} HandSection
31
+ * @property {'hand'} type
32
+ * @property {string} content — verbatim text between/around sentinels (may be empty)
33
+ * @property {number} startIdx — absolute char offset in original body
34
+ * @property {number} endIdx — absolute char offset (exclusive)
35
+ */
36
+
37
+ /**
38
+ * @typedef {Object} ManagedSection
39
+ * @property {'managed'} type
40
+ * @property {string} name — section name from the BEGIN sentinel
41
+ * @property {string} content — inner content (excludes the sentinel comments themselves)
42
+ * @property {number} startIdx — offset of the BEGIN sentinel; -1 for sections appended via merge
43
+ * @property {number} endIdx — offset just past the END sentinel; -1 for appended sections
44
+ */
45
+
46
+ /**
47
+ * @typedef {HandSection | ManagedSection} Section
48
+ */
49
+
50
+ /**
51
+ * @typedef {Object} Conflict
52
+ * @property {'duplicate-section' | 'orphan-begin'} type
53
+ * @property {string} [name] — section name (for duplicate-section and orphan-begin)
54
+ */
55
+
56
+ /**
57
+ * Parse a peer-card body into hand-owned and AI-managed sections.
58
+ *
59
+ * Behaviour
60
+ * ─────────
61
+ * • Text before the first BEGIN sentinel becomes a `hand` section.
62
+ * • Each well-formed BEGIN..END pair becomes a `managed` section. The `content` field
63
+ * excludes the sentinel comments themselves (so re-serialising wraps them back on).
64
+ * • An orphan BEGIN (no matching END) collapses the BEGIN sentinel and all remaining
65
+ * text into a single `hand` section — defensive: we never silently drop user text.
66
+ * Caller should detect this via the absence of the expected managed section in the
67
+ * parse result, OR via the `mergePeerCard` `conflicts[]` (which checks structure).
68
+ * • Trailing text after the last END becomes a `hand` section.
69
+ *
70
+ * @param {string} body
71
+ * @returns {{ sections: Section[] }}
72
+ */
73
+ function parseSections(body) {
74
+ if (typeof body !== 'string') {
75
+ throw new Error(`parseSections: body must be string (got ${typeof body}).`);
76
+ }
77
+
78
+ const sections = [];
79
+ const text = body;
80
+ let cursor = 0;
81
+
82
+ // Reset regex state (global regex carries lastIndex across calls)
83
+ SENTINEL_BEGIN_RE.lastIndex = 0;
84
+ const beginMatches = [...text.matchAll(SENTINEL_BEGIN_RE)];
85
+
86
+ for (const beginMatch of beginMatches) {
87
+ const sectionName = beginMatch[1];
88
+ const beginStart = beginMatch.index;
89
+
90
+ // If a previous orphan-BEGIN already consumed to EOF, skip remaining matches
91
+ if (beginStart < cursor) continue;
92
+
93
+ const beginEnd = beginStart + beginMatch[0].length;
94
+
95
+ // Hand region BEFORE this BEGIN
96
+ if (cursor < beginStart) {
97
+ sections.push({
98
+ type: 'hand',
99
+ content: text.slice(cursor, beginStart),
100
+ startIdx: cursor,
101
+ endIdx: beginStart,
102
+ });
103
+ }
104
+
105
+ // Find matching END (constructed per-section so names match exactly)
106
+ const endRegex = new RegExp(`<!--\\s*END\\s+MANAGED:\\s*${escapeRegex(sectionName)}\\s*-->`);
107
+ const endRest = text.slice(beginEnd);
108
+ const endMatch = endRest.match(endRegex);
109
+
110
+ if (!endMatch) {
111
+ // Orphan BEGIN — defensive: treat the BEGIN sentinel + rest of file as hand
112
+ sections.push({
113
+ type: 'hand',
114
+ content: text.slice(beginStart),
115
+ startIdx: beginStart,
116
+ endIdx: text.length,
117
+ });
118
+ cursor = text.length;
119
+ break;
120
+ }
121
+
122
+ const endStart = beginEnd + endMatch.index;
123
+ const endEnd = endStart + endMatch[0].length;
124
+
125
+ sections.push({
126
+ type: 'managed',
127
+ name: sectionName,
128
+ content: text.slice(beginEnd, endStart),
129
+ startIdx: beginStart,
130
+ endIdx: endEnd,
131
+ });
132
+ cursor = endEnd;
133
+ }
134
+
135
+ // Trailing hand region
136
+ if (cursor < text.length) {
137
+ sections.push({
138
+ type: 'hand',
139
+ content: text.slice(cursor),
140
+ startIdx: cursor,
141
+ endIdx: text.length,
142
+ });
143
+ }
144
+
145
+ return { sections };
146
+ }
147
+
148
+ /**
149
+ * Serialize sections back to a body string, wrapping managed regions in sentinels.
150
+ *
151
+ * @param {Section[]} sections
152
+ * @returns {string}
153
+ */
154
+ function serializeSections(sections) {
155
+ if (!Array.isArray(sections)) {
156
+ throw new Error(`serializeSections: sections must be array (got ${typeof sections}).`);
157
+ }
158
+
159
+ let out = '';
160
+ for (const s of sections) {
161
+ if (s.type === 'hand') {
162
+ out += s.content;
163
+ } else if (s.type === 'managed') {
164
+ out += `<!-- BEGIN MANAGED: ${s.name} -->`;
165
+ out += s.content;
166
+ out += `<!-- END MANAGED: ${s.name} -->`;
167
+ } else {
168
+ throw new Error(`serializeSections: unknown section type: ${JSON.stringify(s)}`);
169
+ }
170
+ }
171
+ return out;
172
+ }
173
+
174
+ /**
175
+ * @typedef {Object} MergeResult
176
+ * @property {string} body — the merged body
177
+ * @property {Conflict[]} conflicts — duplicate-section / orphan-begin issues for caller review
178
+ * @property {{ preserved: number, replaced: number, appended: number }} stats
179
+ */
180
+
181
+ /**
182
+ * Merge a dialectic-derived update into an existing peer-card body.
183
+ *
184
+ * Semantics
185
+ * ─────────
186
+ * • Hand-owned sections (outside sentinels): PRESERVED verbatim.
187
+ * • Managed sections matching `managedUpdates` keys: REPLACED with new content.
188
+ * • Managed sections in `managedUpdates` not in existing: APPENDED at end.
189
+ * • Managed sections in existing not in `managedUpdates`: KEPT (no auto-delete).
190
+ * • Idempotency: `mergePeerCard(body, {})` returns a body byte-equivalent to `body`
191
+ * (round-trip through parse/serialize, which preserves sentinel form).
192
+ *
193
+ * Conflicts
194
+ * ─────────
195
+ * • `duplicate-section` — same managed section name appears more than once in the
196
+ * existing body. We replace ALL occurrences with the update content (or keep all
197
+ * as-is if no update) but surface the conflict so the caller can resolve.
198
+ * • `orphan-begin` — BEGIN without matching END. Detected by re-parsing the merged
199
+ * body and comparing sentinel structure.
200
+ *
201
+ * @param {string} existingBody — the on-disk body (with sentinels)
202
+ * @param {Record<string, string>} managedUpdates — `{ <section-name>: <new-content> }`
203
+ * @returns {MergeResult}
204
+ */
205
+ export function mergePeerCard(existingBody, managedUpdates) {
206
+ if (typeof existingBody !== 'string') {
207
+ throw new Error(`mergePeerCard: existingBody must be string (got ${typeof existingBody}).`);
208
+ }
209
+ if (!managedUpdates || typeof managedUpdates !== 'object' || Array.isArray(managedUpdates)) {
210
+ throw new Error('mergePeerCard: managedUpdates must be a plain object.');
211
+ }
212
+
213
+ // Validate update keys against section-name grammar
214
+ for (const name of Object.keys(managedUpdates)) {
215
+ if (!/^[\w-]+$/.test(name)) {
216
+ throw new Error(`mergePeerCard: invalid section name "${name}" (allowed: [A-Za-z0-9_-]+).`);
217
+ }
218
+ if (typeof managedUpdates[name] !== 'string') {
219
+ throw new Error(`mergePeerCard: managedUpdates["${name}"] must be a string.`);
220
+ }
221
+ }
222
+
223
+ const { sections } = parseSections(existingBody);
224
+ const conflicts = [];
225
+ const stats = { preserved: 0, replaced: 0, appended: 0 };
226
+
227
+ // Detect orphan-begin: a BEGIN sentinel was found but no matching END (the parser
228
+ // collapsed it into a hand section). We re-scan the original text for BEGIN names
229
+ // whose presence in the parsed managed sections is missing — those are orphans.
230
+ const parsedManagedNames = new Set();
231
+ const seenForDuplicate = new Set();
232
+ for (const s of sections) {
233
+ if (s.type === 'managed') {
234
+ parsedManagedNames.add(s.name);
235
+ if (seenForDuplicate.has(s.name)) {
236
+ conflicts.push({ type: 'duplicate-section', name: s.name });
237
+ } else {
238
+ seenForDuplicate.add(s.name);
239
+ }
240
+ }
241
+ }
242
+
243
+ SENTINEL_BEGIN_RE.lastIndex = 0;
244
+ const allBeginNames = [...existingBody.matchAll(SENTINEL_BEGIN_RE)].map(m => m[1]);
245
+ const reportedOrphans = new Set();
246
+ for (const name of allBeginNames) {
247
+ if (!parsedManagedNames.has(name) && !reportedOrphans.has(name)) {
248
+ conflicts.push({ type: 'orphan-begin', name });
249
+ reportedOrphans.add(name);
250
+ }
251
+ }
252
+
253
+ // Apply updates: replace managed sections whose names match, preserve others
254
+ const updatedSections = sections.map(s => {
255
+ if (s.type === 'hand') {
256
+ stats.preserved++;
257
+ return s;
258
+ }
259
+ // s.type === 'managed'
260
+ if (Object.prototype.hasOwnProperty.call(managedUpdates, s.name)) {
261
+ stats.replaced++;
262
+ return { ...s, content: wrapManagedContent(managedUpdates[s.name]) };
263
+ }
264
+ // Managed section not in updates — keep as-is
265
+ return s;
266
+ });
267
+
268
+ // Append new managed sections from updates that didn't exist in original
269
+ for (const name of Object.keys(managedUpdates)) {
270
+ if (!parsedManagedNames.has(name)) {
271
+ stats.appended++;
272
+ updatedSections.push({
273
+ type: 'managed',
274
+ name,
275
+ content: wrapManagedContent(managedUpdates[name]),
276
+ startIdx: -1,
277
+ endIdx: -1,
278
+ });
279
+ }
280
+ }
281
+
282
+ return {
283
+ body: serializeSections(updatedSections),
284
+ conflicts,
285
+ stats,
286
+ };
287
+ }
288
+
289
+ // ── helpers ──────────────────────────────────────────────────────────────────
290
+
291
+ /**
292
+ * Normalise managed content so the serialised form always reads:
293
+ * <!-- BEGIN MANAGED: x -->\n<content>\n<!-- END MANAGED: x -->
294
+ * Trimming + adding newlines keeps managed regions visually distinct from sentinels.
295
+ * @param {string} raw
296
+ * @returns {string}
297
+ */
298
+ function wrapManagedContent(raw) {
299
+ return '\n' + raw.trim() + '\n';
300
+ }
301
+
302
+ /**
303
+ * Escape regex metacharacters in a section name. The grammar `[\w-]+` excludes most
304
+ * metacharacters, but defence-in-depth is cheap here.
305
+ * @param {string} s
306
+ * @returns {string}
307
+ */
308
+ function escapeRegex(s) {
309
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
310
+ }