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,648 @@
1
+ /**
2
+ * lock-reaper.mjs — reconciliation for orphaned session.lock leases (Epic #724 C7).
3
+ *
4
+ * The complement to the deterministic release wired into hooks/on-session-end.mjs
5
+ * (W2). That release covers the clean-exit path (readLock → ownership-match →
6
+ * release). The reaper is the RECONCILIATION for leases that were left behind
7
+ * anyway — a crash before SessionEnd, an old fleet-wide backlog, a lock a
8
+ * different repo's process never cleaned up. Fleet evidence: 10 orphans (dead
9
+ * PIDs) across ~/Projects, one of which blocked this repo's own session-start
10
+ * this morning until it was force-taken-over by hand.
11
+ *
12
+ * Discovery reuses dispatcher/enumerate.mjs (host-wide candidate scan classifying
13
+ * each repo's lease free | in-progress | force-closed). `force-closed` — a lock
14
+ * present but with a heartbeat older than its TTL — is exactly the reap set.
15
+ *
16
+ * HARD SAFETY INVARIANTS (see reapStaleLocks / evaluateRepo):
17
+ * (a) NEVER reap a live lock (isLockLive true) — heartbeat-fresh means an
18
+ * active owner, regardless of anything else.
19
+ * (b) Defense-in-depth: own-host lock whose recorded PID is still alive is
20
+ * NEVER reaped, even past TTL (PID-recycle / genuinely-alive edge).
21
+ * (c) Cross-host locks are NEVER auto-reaped — we cannot signal a remote
22
+ * process, so they are only LISTED for an operator (reason
23
+ * 'cross-host-requires-operator'), independent of any flag.
24
+ * (d) A reap is an ARCHIVE-MOVE (write a copy to
25
+ * <repo>/.orchestrator/tmp/reaped-locks/<sessionId>-<ts>.json, then unlink
26
+ * the original) — never an unlink-only. The archive is the audit trail.
27
+ * (e) TOCTOU defense: immediately before the destructive unlink, the lease is
28
+ * RE-READ and re-verified (still dead + same session_id as the lock this
29
+ * evaluation started from). A session that bootstraps and writes a FRESH
30
+ * lock in the window between the initial read and the unlink must not
31
+ * have its lease destroyed. A changed/live lease aborts the reap with
32
+ * reason 'lock-changed-during-reap' and best-effort undoes the archive
33
+ * write that was already made.
34
+ *
35
+ * CURRENT-SESSION.JSON ORPHAN SWEEP (Epic #724 follow-up): `evaluateRepo` also
36
+ * sweeps an orphaned `.orchestrator/current-session.json` as part of the SAME
37
+ * lease decision — never as an independent check. The file's fate is folded
38
+ * into the per-repo result's `currentSession` field:
39
+ * { archived: true, archivePath } | { archived: false, reason }
40
+ * The sweep only ever runs once the LOCK side has already been confirmed
41
+ * dead/absent/reap-eligible (mirrors invariants a-c by construction — a live,
42
+ * re-entrant, cross-host, or pid-alive lock short-circuits with the SAME
43
+ * reason before the file is even read). Once eligible, the file itself is
44
+ * still guarded by:
45
+ * (b) re-entrancy — never touch a file that references `currentSessionId`.
46
+ * (c) fresh-preservation — a file whose own age (its `timestamp` field,
47
+ * falling back to mtime) is within `DEFAULT_TTL_HOURS` is preserved,
48
+ * protecting a repo mid-bootstrap that wrote current-session.json
49
+ * before its lock (mirrors the mtime fallback in session-registry.mjs
50
+ * sweepZombies).
51
+ * (d) archive-move (same reaped-locks directory, `current-session-` prefix).
52
+ * (e) TOCTOU re-check immediately before the destructive unlink — aborts
53
+ * with reason 'current-session-changed-during-reap' if the file changed.
54
+ *
55
+ * No-throw contract: every path catches and degrades. Per-repo failures land in
56
+ * `skipped` with an `error: …` reason; the sweep continues.
57
+ *
58
+ * Dependency-injected (the `deps` arg) exactly like enumerate.mjs so tests drive
59
+ * tmp fixtures + spy on emitEvent without touching the real ~/Projects fleet.
60
+ *
61
+ * Plain Node ESM. Named exports. No external deps.
62
+ */
63
+
64
+ import { readFileSync, writeFileSync, unlinkSync, mkdirSync, statSync } from 'node:fs';
65
+ import os from 'node:os';
66
+ import path from 'node:path';
67
+
68
+ import { enumerateCandidates } from './dispatcher/enumerate.mjs';
69
+ import { readLock, isLockLive, isPidAliveOnHost, LOCK_PATH, DEFAULT_TTL_HOURS } from './session-lock.mjs';
70
+ import { emitEvent } from './events.mjs';
71
+
72
+ const REAPED_ARCHIVE_SUBDIR = '.orchestrator/tmp/reaped-locks';
73
+ const EVENTS_RELPATH = '.orchestrator/metrics/events.jsonl';
74
+ const REAPED_EVENT = 'orchestrator.session.lock.reaped';
75
+ const CURRENT_SESSION_RELPATH = '.orchestrator/current-session.json';
76
+
77
+ /**
78
+ * Resolve the dependency bundle, defaulting every seam to the real
79
+ * implementation. Tests override the ones they need (hostname, isPidAliveOnHost,
80
+ * emitEvent, fs, and enumerateDeps.getCrossRepoProjects for a []-stubbed scan).
81
+ *
82
+ * @param {object} [deps]
83
+ * @returns {object}
84
+ */
85
+ function resolveDeps(deps = {}) {
86
+ const d = deps ?? {};
87
+ return {
88
+ enumerateCandidates: d.enumerateCandidates ?? enumerateCandidates,
89
+ readLock: d.readLock ?? readLock,
90
+ isLockLive: d.isLockLive ?? isLockLive,
91
+ isPidAliveOnHost: d.isPidAliveOnHost ?? isPidAliveOnHost,
92
+ hostname: d.hostname ?? (() => os.hostname()),
93
+ emitEvent: d.emitEvent ?? emitEvent,
94
+ fs: d.fs ?? { readFileSync, writeFileSync, unlinkSync, mkdirSync, statSync },
95
+ // Passed straight through to enumerateCandidates for its own fs/config DI.
96
+ // Tests set { getCrossRepoProjects: async () => [] } so the scan stays
97
+ // confined to the tmp fixture and never unions in real ~/Projects paths.
98
+ enumerateDeps: d.enumerateDeps,
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Absolute path to a repo's session.lock.
104
+ * @param {string} repoRoot
105
+ * @returns {string}
106
+ */
107
+ function lockPathFor(repoRoot) {
108
+ return path.join(repoRoot, LOCK_PATH);
109
+ }
110
+
111
+ /**
112
+ * Fractional hours since a lock's last heartbeat (the liveness basis). Falls
113
+ * back to started_at when last_heartbeat is absent (v1 lock). Returns null when
114
+ * neither parses.
115
+ *
116
+ * @param {object} lock
117
+ * @param {number} nowMs
118
+ * @returns {number|null}
119
+ */
120
+ function ageHoursOf(lock, nowMs) {
121
+ const hb = (typeof lock.last_heartbeat === 'string' && lock.last_heartbeat.length > 0)
122
+ ? lock.last_heartbeat
123
+ : lock.started_at;
124
+ const ms = Date.parse(hb);
125
+ if (Number.isNaN(ms)) return null;
126
+ return Number(((nowMs - ms) / (3600 * 1000)).toFixed(2));
127
+ }
128
+
129
+ /**
130
+ * The stable session identifier for archive/event purposes: prefer the semantic
131
+ * id, fall back to the UUID session_id, then a literal.
132
+ * @param {object} lock
133
+ * @returns {string}
134
+ */
135
+ function lockSessionId(lock) {
136
+ return lock.semantic_session_id ?? lock.session_id ?? 'unknown-session';
137
+ }
138
+
139
+ /**
140
+ * Archive-move a lock file (invariant d): copy the original bytes to the
141
+ * per-repo reaped-locks archive, THEN unlink the original. Never unlink-only —
142
+ * if the archive write fails, the original stays on disk and the caller reports
143
+ * an error instead of destroying the lease.
144
+ *
145
+ * TOCTOU re-check (invariant e): immediately before the destructive unlink, the
146
+ * lease is re-read via `D.readLock` and re-verified — still dead (per
147
+ * `D.isLockLive`) AND the same `session_id` as the `lock` this call started
148
+ * from. A session that bootstraps and writes a fresh lock in the window
149
+ * between evaluateRepo's initial read and this point must not be destroyed.
150
+ * On a changed/live lease, the archive write already made is best-effort
151
+ * undone and the caller reports `{ ok: false, reason: 'lock-changed-during-reap' }`
152
+ * instead of unlinking.
153
+ *
154
+ * @param {string} repoRoot
155
+ * @param {string} lockFile
156
+ * @param {object} lock — the parsed lock (fallback content if the raw re-read fails).
157
+ * @param {number} nowMs
158
+ * @param {object} fsDep — { readFileSync, writeFileSync, unlinkSync, mkdirSync }.
159
+ * @param {object} D — resolved deps (needs `readLock` + `isLockLive` for the re-check).
160
+ * @returns {{ ok: true, archivePath: string } | { ok: false, reason: string }}
161
+ */
162
+ function archiveLock(repoRoot, lockFile, lock, nowMs, fsDep, D) {
163
+ const safeId = String(lockSessionId(lock)).replace(/[^A-Za-z0-9._-]/g, '_');
164
+ const ts = new Date(nowMs).toISOString().replace(/[:.]/g, '-');
165
+ const archiveDir = path.join(repoRoot, REAPED_ARCHIVE_SUBDIR);
166
+ fsDep.mkdirSync(archiveDir, { recursive: true });
167
+ const archivePath = path.join(archiveDir, `${safeId}-${ts}.json`);
168
+
169
+ // Preserve the exact original bytes; fall back to the serialised parsed lock
170
+ // if the re-read fails (e.g. it vanished between readLock and here).
171
+ let raw;
172
+ try {
173
+ raw = fsDep.readFileSync(lockFile, 'utf8');
174
+ } catch {
175
+ raw = JSON.stringify(lock, null, 2) + '\n';
176
+ }
177
+ fsDep.writeFileSync(archivePath, raw, 'utf8');
178
+
179
+ // Invariant (e) — TOCTOU re-check, immediately before the destructive unlink.
180
+ let recheck;
181
+ try {
182
+ recheck = D.readLock({ repoRoot });
183
+ } catch {
184
+ recheck = null;
185
+ }
186
+ const sameLease = recheck !== null && recheck.session_id === lock.session_id;
187
+ const stillDead = recheck !== null && !D.isLockLive(recheck, nowMs);
188
+ if (!sameLease || !stillDead) {
189
+ // Best-effort undo of the archive write — the original lease is untouched
190
+ // either way, but leaving a stray archive copy around is misleading.
191
+ try { fsDep.unlinkSync(archivePath); } catch { /* best-effort */ }
192
+ return { ok: false, reason: 'lock-changed-during-reap' };
193
+ }
194
+
195
+ // Only after the archive is durably written AND the re-check confirms the
196
+ // lease is unchanged do we remove the original.
197
+ fsDep.unlinkSync(lockFile);
198
+ return { ok: true, archivePath };
199
+ }
200
+
201
+ /**
202
+ * Absolute path to a repo's current-session.json.
203
+ * @param {string} repoRoot
204
+ * @returns {string}
205
+ */
206
+ function currentSessionPathFor(repoRoot) {
207
+ return path.join(repoRoot, CURRENT_SESSION_RELPATH);
208
+ }
209
+
210
+ /**
211
+ * Fractional hours since a current-session.json's own `timestamp` field.
212
+ * Falls back to the file's mtime when `timestamp` is absent/unparseable —
213
+ * mirrors the malformed-entry mtime fallback in session-registry.mjs
214
+ * sweepZombies (fresh-preservation protects an in-flight write from being
215
+ * swept before its author finishes bootstrapping).
216
+ *
217
+ * @param {object} parsed — the parsed current-session.json content.
218
+ * @param {string} filePath
219
+ * @param {number} nowMs
220
+ * @param {object} fsDep — { statSync, … }.
221
+ * @returns {number|null} null when neither the timestamp nor the mtime resolve.
222
+ */
223
+ function currentSessionAgeHours(parsed, filePath, nowMs, fsDep) {
224
+ const ts = typeof parsed?.timestamp === 'string' ? parsed.timestamp : null;
225
+ const ms = ts ? Date.parse(ts) : NaN;
226
+ if (!Number.isNaN(ms)) {
227
+ return (nowMs - ms) / (3600 * 1000);
228
+ }
229
+ try {
230
+ const info = fsDep.statSync(filePath);
231
+ return (nowMs - info.mtimeMs) / (3600 * 1000);
232
+ } catch {
233
+ return null;
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Whether a parsed current-session.json references the same session as
239
+ * `lock` — either the UUID `session_id` or the `semantic_session_id`
240
+ * matches. Both sides must be non-empty strings to count as a match; two
241
+ * absent/undefined fields are never treated as "matching".
242
+ *
243
+ * @param {object} parsed
244
+ * @param {object} lock
245
+ * @returns {boolean}
246
+ */
247
+ function currentSessionReferencesLock(parsed, lock) {
248
+ const sameSessionId =
249
+ typeof parsed.session_id === 'string' && parsed.session_id.length > 0 &&
250
+ parsed.session_id === lock.session_id;
251
+ const sameSemanticId =
252
+ typeof parsed.semantic_session_id === 'string' && parsed.semantic_session_id.length > 0 &&
253
+ parsed.semantic_session_id === lock.semantic_session_id;
254
+ return sameSessionId || sameSemanticId;
255
+ }
256
+
257
+ /**
258
+ * Archive-move current-session.json (mirrors archiveLock's invariants d/e for
259
+ * the file sweep). Copies the original bytes into the SAME per-repo
260
+ * reaped-locks archive directory (prefixed `current-session-` to
261
+ * disambiguate from lock archives), then re-reads the live file immediately
262
+ * before the destructive unlink and aborts if it changed to reference a
263
+ * different session or the caller's own (fresh) session.
264
+ *
265
+ * @param {string} repoRoot
266
+ * @param {string} filePath
267
+ * @param {object} parsed — the content read at evaluation time.
268
+ * @param {number} nowMs
269
+ * @param {object} fsDep
270
+ * @param {string} [currentSessionId] — abort if the file now belongs to the caller.
271
+ * @returns {{ archived: true, archivePath: string } | { archived: false, reason: string }}
272
+ */
273
+ function archiveCurrentSessionFile(repoRoot, filePath, parsed, nowMs, fsDep, currentSessionId) {
274
+ const safeId = String(parsed.semantic_session_id ?? parsed.session_id ?? 'unknown-session')
275
+ .replace(/[^A-Za-z0-9._-]/g, '_');
276
+ const ts = new Date(nowMs).toISOString().replace(/[:.]/g, '-');
277
+ const archiveDir = path.join(repoRoot, REAPED_ARCHIVE_SUBDIR);
278
+ fsDep.mkdirSync(archiveDir, { recursive: true });
279
+ const archivePath = path.join(archiveDir, `current-session-${safeId}-${ts}.json`);
280
+
281
+ // Preserve the exact original bytes; fall back to the serialised parsed
282
+ // content if the re-read fails (e.g. it vanished between read and here).
283
+ let raw;
284
+ try {
285
+ raw = fsDep.readFileSync(filePath, 'utf8');
286
+ } catch {
287
+ raw = JSON.stringify(parsed, null, 2) + '\n';
288
+ }
289
+ fsDep.writeFileSync(archivePath, raw, 'utf8');
290
+
291
+ // TOCTOU re-check (invariant e) — re-read immediately before the unlink.
292
+ let recheck;
293
+ try {
294
+ recheck = JSON.parse(fsDep.readFileSync(filePath, 'utf8'));
295
+ } catch {
296
+ recheck = null;
297
+ }
298
+ const stillSame =
299
+ recheck !== null &&
300
+ recheck.session_id === parsed.session_id &&
301
+ recheck.semantic_session_id === parsed.semantic_session_id;
302
+ const nowReferencesCaller =
303
+ recheck !== null &&
304
+ typeof currentSessionId === 'string' && currentSessionId.length > 0 &&
305
+ (recheck.session_id === currentSessionId || recheck.semantic_session_id === currentSessionId);
306
+
307
+ if (!stillSame || nowReferencesCaller) {
308
+ // Best-effort undo of the archive write — the original file is untouched
309
+ // either way, but leaving a stray archive copy around is misleading.
310
+ try { fsDep.unlinkSync(archivePath); } catch { /* best-effort */ }
311
+ return { archived: false, reason: 'current-session-changed-during-reap' };
312
+ }
313
+
314
+ fsDep.unlinkSync(filePath);
315
+ return { archived: true, archivePath };
316
+ }
317
+
318
+ /**
319
+ * Evaluate + (under !dryRun) sweep an orphaned current-session.json for one
320
+ * repo. The caller only invokes this once the LOCK side of the SAME lease
321
+ * decision has already been confirmed dead/absent/reap-eligible — invariants
322
+ * (a) live-lease, cross-host, and own-host-pid-alive are therefore enforced
323
+ * by the CALLER (evaluateRepo) short-circuiting with a matching reason before
324
+ * this function is ever reached, not by a redundant check here.
325
+ *
326
+ * Orphan predicate: the file is orphaned when `lock` is null (no lease exists
327
+ * at all for the repo — subject only to the freshness guard below) OR the
328
+ * file references `lock` (its `session_id`/`semantic_session_id` matches the
329
+ * already-confirmed-dead lock this evaluation is running against).
330
+ *
331
+ * Guards:
332
+ * (b) re-entrancy — never touch a file that references `currentSessionId`
333
+ * (the caller's own fresh write).
334
+ * (c) fresh-preservation — a file whose own age (timestamp, falling back to
335
+ * mtime) is within `DEFAULT_TTL_HOURS` is preserved.
336
+ * (d) archive-move, never unlink-only.
337
+ * (e) TOCTOU re-check immediately before the destructive unlink.
338
+ *
339
+ * @param {string} repoRoot
340
+ * @param {object} args
341
+ * @param {number} args.nowMs
342
+ * @param {boolean} args.dryRun
343
+ * @param {string} [args.currentSessionId]
344
+ * @param {object|null} args.lock — the confirmed-dead lock this file is being
345
+ * evaluated against, or null when no lock exists at all for the repo.
346
+ * @param {object} args.fs — resolved fs dep bundle (readFileSync, statSync, …).
347
+ * @returns {{ archived: boolean, archivePath?: string, reason?: string }}
348
+ */
349
+ function evaluateCurrentSessionFile(repoRoot, { nowMs, dryRun, currentSessionId, lock, fs: fsDep }) {
350
+ const filePath = currentSessionPathFor(repoRoot);
351
+ let raw;
352
+ try {
353
+ raw = fsDep.readFileSync(filePath, 'utf8');
354
+ } catch {
355
+ return { archived: false, reason: 'no-file' };
356
+ }
357
+
358
+ let parsed;
359
+ try {
360
+ parsed = JSON.parse(raw);
361
+ } catch {
362
+ return { archived: false, reason: 'malformed' };
363
+ }
364
+ if (!parsed || typeof parsed !== 'object') {
365
+ return { archived: false, reason: 'malformed' };
366
+ }
367
+
368
+ // Guard (b): re-entrancy — the file itself belongs to the caller's own session.
369
+ if (
370
+ typeof currentSessionId === 'string' && currentSessionId.length > 0 &&
371
+ (parsed.session_id === currentSessionId || parsed.semantic_session_id === currentSessionId)
372
+ ) {
373
+ return { archived: false, reason: 'current-session' };
374
+ }
375
+
376
+ // Orphan predicate: when a (confirmed-dead) lock exists, the file must
377
+ // reference IT to count as orphaned. Unrelated content is left alone.
378
+ if (lock && !currentSessionReferencesLock(parsed, lock)) {
379
+ return { archived: false, reason: 'not-orphaned' };
380
+ }
381
+
382
+ // Guard (c): fresh-preservation — protects a repo mid-bootstrap that wrote
383
+ // current-session.json before its lock.
384
+ const ageHours = currentSessionAgeHours(parsed, filePath, nowMs, fsDep);
385
+ if (ageHours === null || ageHours < DEFAULT_TTL_HOURS) {
386
+ return { archived: false, reason: 'fresh' };
387
+ }
388
+
389
+ if (dryRun) {
390
+ return { archived: false, reason: 'candidate' };
391
+ }
392
+
393
+ try {
394
+ return archiveCurrentSessionFile(repoRoot, filePath, parsed, nowMs, fsDep, currentSessionId);
395
+ } catch (err) {
396
+ return { archived: false, reason: `error: ${err?.message ?? String(err)}` };
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Evaluate a single repo's lease and — under !dryRun — reap it if eligible.
402
+ * As part of the SAME lease decision, also evaluates + (under !dryRun) sweeps
403
+ * an orphaned `.orchestrator/current-session.json` (see evaluateCurrentSessionFile).
404
+ * Every branch below attaches a `currentSession` field to its result; a
405
+ * lock-side guard (live / lock-level re-entrancy / cross-host / pid-alive /
406
+ * lock-changed-during-reap) short-circuits the file sweep with the SAME
407
+ * reason, without even reading the file — the file is only actually
408
+ * evaluated once the lock side is dead/absent/reap-eligible.
409
+ *
410
+ * Returns exactly one of:
411
+ * { action: 'none', repo, reason, currentSession } — no lock present.
412
+ * { action: 'skipped', repo, reason, …, currentSession } — protected by an invariant.
413
+ * { action: 'candidate', repo, …, currentSession } — reap-eligible, dry-run (not touched).
414
+ * { action: 'reaped', repo, …, archivePath, currentSession } — reap-eligible, archived + emitted.
415
+ *
416
+ * @param {string} repoRoot
417
+ * @param {object} args
418
+ * @param {number} args.nowMs
419
+ * @param {boolean} args.dryRun
420
+ * @param {string} [args.currentSessionId] — never reap the caller's own session.
421
+ * @param {string} args.reapMode — event tag: 'cli' | 'auto-own-repo'.
422
+ * @param {object} args.D — resolved deps.
423
+ * @returns {Promise<object>}
424
+ */
425
+ async function evaluateRepo(repoRoot, { nowMs, dryRun, currentSessionId, reapMode, D }) {
426
+ const csArgs = { nowMs, dryRun, currentSessionId, fs: D.fs };
427
+
428
+ let lock;
429
+ try {
430
+ lock = D.readLock({ repoRoot });
431
+ } catch {
432
+ lock = null;
433
+ }
434
+ if (!lock || typeof lock !== 'object') {
435
+ // No lease at all — the current-session.json sweep is still eligible
436
+ // (predicate branch 2: no lock exists, subject only to the freshness guard).
437
+ const currentSession = evaluateCurrentSessionFile(repoRoot, { ...csArgs, lock: null });
438
+ return { action: 'none', repo: repoRoot, reason: 'no-lock', currentSession };
439
+ }
440
+
441
+ // Invariant (a): a live lock (fresh heartbeat) is NEVER reaped — and the
442
+ // current-session.json sweep is the SAME lease decision, so it is left
443
+ // entirely untouched too (never even read).
444
+ if (D.isLockLive(lock, nowMs)) {
445
+ return {
446
+ action: 'skipped',
447
+ repo: repoRoot,
448
+ reason: 'live',
449
+ currentSession: { archived: false, reason: 'live' },
450
+ };
451
+ }
452
+
453
+ // Re-entrancy guard: never reap the current session's own lease. Protects the
454
+ // hook splice, where lock-bootstrap just wrote our fresh lock in the same run.
455
+ if (
456
+ currentSessionId &&
457
+ (lock.session_id === currentSessionId || lock.semantic_session_id === currentSessionId)
458
+ ) {
459
+ return {
460
+ action: 'skipped',
461
+ repo: repoRoot,
462
+ reason: 'current-session',
463
+ currentSession: { archived: false, reason: 'current-session' },
464
+ };
465
+ }
466
+
467
+ const ownHost = lock.host === D.hostname();
468
+
469
+ // Invariant (c): cross-host leases are NEVER auto-reaped — only listed.
470
+ if (!ownHost) {
471
+ return {
472
+ action: 'skipped',
473
+ repo: repoRoot,
474
+ reason: 'cross-host-requires-operator',
475
+ host: lock.host ?? null,
476
+ currentSession: { archived: false, reason: 'cross-host-requires-operator' },
477
+ };
478
+ }
479
+
480
+ // Invariant (b): own-host + live PID → NEVER reap, even past TTL. The recorded
481
+ // pid is the ephemeral hook subprocess (dead in the common orphan case); a
482
+ // still-alive pid means either a genuinely-live owner or a recycled pid — the
483
+ // conservative choice is to defer to the operator rather than risk a false reap.
484
+ let pidAlive;
485
+ try {
486
+ pidAlive = D.isPidAliveOnHost(lock.pid) === true;
487
+ } catch {
488
+ pidAlive = false;
489
+ }
490
+ if (pidAlive) {
491
+ return {
492
+ action: 'skipped',
493
+ repo: repoRoot,
494
+ reason: 'own-host-pid-alive',
495
+ pid: lock.pid ?? null,
496
+ currentSession: { archived: false, reason: 'own-host-pid-alive' },
497
+ };
498
+ }
499
+
500
+ // Reap-eligible: own-host, dead lease (heartbeat past TTL), dead PID. The
501
+ // current-session.json sweep is now eligible too — same lease decision.
502
+ const ageHours = ageHoursOf(lock, nowMs);
503
+ const base = {
504
+ repo: repoRoot,
505
+ repoName: path.basename(repoRoot),
506
+ sessionId: lockSessionId(lock),
507
+ host: lock.host ?? null,
508
+ pid: lock.pid ?? null,
509
+ ageHours,
510
+ };
511
+
512
+ if (dryRun) {
513
+ const currentSession = evaluateCurrentSessionFile(repoRoot, { ...csArgs, lock });
514
+ return { action: 'candidate', ...base, currentSession };
515
+ }
516
+
517
+ // Apply: archive-move (invariant d) then best-effort event.
518
+ let archiveResult;
519
+ try {
520
+ archiveResult = archiveLock(repoRoot, lockPathFor(repoRoot), lock, nowMs, D.fs, D);
521
+ } catch (err) {
522
+ // The lock predicate was already confirmed dead/eligible above — the file
523
+ // sweep still runs; only the LOCK's own archive mechanics failed.
524
+ const currentSession = evaluateCurrentSessionFile(repoRoot, { ...csArgs, lock });
525
+ return { action: 'skipped', repo: repoRoot, reason: `error: ${err?.message ?? String(err)}`, currentSession };
526
+ }
527
+ if (!archiveResult.ok) {
528
+ // Invariant (e) — the re-check found a changed/live lease. Never unlink.
529
+ // A 'lock-changed-during-reap' means the TRUE state is no longer
530
+ // dead/absent, so the file sweep must NOT proceed either — a fresh
531
+ // session may already own both files.
532
+ const currentSession = archiveResult.reason === 'lock-changed-during-reap'
533
+ ? { archived: false, reason: 'lock-changed-during-reap' }
534
+ : evaluateCurrentSessionFile(repoRoot, { ...csArgs, lock });
535
+ return { action: 'skipped', repo: repoRoot, reason: archiveResult.reason, currentSession };
536
+ }
537
+ const archivePath = archiveResult.archivePath;
538
+
539
+ const currentSession = evaluateCurrentSessionFile(repoRoot, { ...csArgs, lock });
540
+
541
+ try {
542
+ await D.emitEvent(
543
+ REAPED_EVENT,
544
+ {
545
+ session_id: lock.session_id ?? null,
546
+ semantic_session_id: lock.semantic_session_id ?? null,
547
+ host: lock.host ?? null,
548
+ pid: lock.pid ?? null,
549
+ age_hours: ageHours,
550
+ reap_mode: reapMode,
551
+ current_session: currentSession,
552
+ },
553
+ { filePath: path.join(repoRoot, EVENTS_RELPATH) },
554
+ );
555
+ } catch {
556
+ // Observability is best-effort — the archive-move already succeeded.
557
+ }
558
+
559
+ return { action: 'reaped', ...base, archivePath, currentSession };
560
+ }
561
+
562
+ /**
563
+ * Host-wide reconciliation: enumerate candidate repos under `startDir`, and for
564
+ * each `force-closed` (dead-lease) repo evaluate + (under !dryRun) reap it.
565
+ *
566
+ * @param {object} [opts]
567
+ * @param {string} [opts.startDir] — scan root; defaults to the confinement root
568
+ * (via enumerateCandidates). Tests pass a tmp fixture dir.
569
+ * @param {number} [opts.now] — clock seam (ms).
570
+ * @param {boolean} [opts.dryRun=true] — SAFE DEFAULT: classify without mutating.
571
+ * @param {boolean} [opts.ownHostOnly=true] — reaping is restricted to own-host
572
+ * leases. Cross-host leases are NEVER reaped regardless (invariant c); this
573
+ * flag is echoed in the result for auditability and cannot loosen invariant c.
574
+ * @param {object} [opts.deps] — DI seam (see resolveDeps).
575
+ * @returns {Promise<{ scanned:number, candidates:object[], reaped:object[], skipped:object[], dryRun:boolean, ownHostOnly:boolean }>}
576
+ */
577
+ export async function reapStaleLocks({ startDir, now, dryRun = true, ownHostOnly = true, deps } = {}) {
578
+ const D = resolveDeps(deps);
579
+ const nowMs = typeof now === 'number' ? now : Date.now();
580
+
581
+ let enumerated;
582
+ try {
583
+ enumerated = await D.enumerateCandidates({ startDir, now: nowMs, deps: D.enumerateDeps });
584
+ } catch {
585
+ enumerated = [];
586
+ }
587
+ if (!Array.isArray(enumerated)) enumerated = [];
588
+
589
+ const scanned = enumerated.length;
590
+ const candidates = [];
591
+ const reaped = [];
592
+ const skipped = [];
593
+
594
+ const forceClosed = enumerated.filter((c) => c && c.status === 'force-closed' && typeof c.repoRoot === 'string');
595
+
596
+ for (const cand of forceClosed) {
597
+ let result;
598
+ try {
599
+ result = await evaluateRepo(cand.repoRoot, { nowMs, dryRun, reapMode: 'cli', D });
600
+ } catch (err) {
601
+ skipped.push({ repo: cand.repoRoot, reason: `error: ${err?.message ?? String(err)}` });
602
+ continue;
603
+ }
604
+ switch (result.action) {
605
+ case 'reaped':
606
+ reaped.push(result);
607
+ candidates.push(result);
608
+ break;
609
+ case 'candidate':
610
+ candidates.push(result);
611
+ break;
612
+ case 'skipped':
613
+ skipped.push({ repo: result.repo, reason: result.reason });
614
+ break;
615
+ // 'none' (no-lock) cannot occur for a force-closed candidate; ignore.
616
+ }
617
+ }
618
+
619
+ return { scanned, candidates, reaped, skipped, dryRun, ownHostOnly };
620
+ }
621
+
622
+ /**
623
+ * Single-repo reconciliation — the own-repo path used by the SessionStart hook.
624
+ * Reads ONE repo's lease directly (no host-wide scan → hook stays cheap) and
625
+ * reaps it only when eligible. The live-lock + current-session guards mean the
626
+ * caller's own freshly-bootstrapped lock is always left untouched.
627
+ *
628
+ * @param {object} opts
629
+ * @param {string} opts.repoRoot
630
+ * @param {number} [opts.now]
631
+ * @param {boolean} [opts.dryRun=true]
632
+ * @param {string} [opts.currentSessionId] — the caller's session id (guard).
633
+ * @param {string} [opts.reapMode='auto-own-repo']
634
+ * @param {object} [opts.deps]
635
+ * @returns {Promise<object>} a single evaluateRepo result.
636
+ */
637
+ export async function reapRepoLock({ repoRoot, now, dryRun = true, currentSessionId, reapMode = 'auto-own-repo', deps } = {}) {
638
+ if (typeof repoRoot !== 'string' || repoRoot.length === 0) {
639
+ return { action: 'none', repo: repoRoot ?? null, reason: 'no-repo-root' };
640
+ }
641
+ const D = resolveDeps(deps);
642
+ const nowMs = typeof now === 'number' ? now : Date.now();
643
+ try {
644
+ return await evaluateRepo(repoRoot, { nowMs, dryRun, currentSessionId, reapMode, D });
645
+ } catch (err) {
646
+ return { action: 'skipped', repo: repoRoot, reason: `error: ${err?.message ?? String(err)}` };
647
+ }
648
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * locks/index.mjs — convenience barrel for the short-lived lock protocols split
3
+ * out of session-lock.mjs in #630 (A1 barrel-preserving split).
4
+ *
5
+ * Re-exports both protocol modules so a consumer can `import { withStateMdLock,
6
+ * withStagingFenceLock } from '@lib/locks/index.mjs'` in one statement. This is
7
+ * a nice-to-have: session-lock.mjs remains the canonical barrel that preserves
8
+ * the original 22-symbol import surface, and the two protocol modules are also
9
+ * importable directly.
10
+ *
11
+ * Pure re-export leaf — imports only the two sibling modules, never
12
+ * session-lock.mjs, so there is no import cycle.
13
+ */
14
+
15
+ export {
16
+ STATE_LOCK_PATH,
17
+ DEFAULT_STATE_LOCK_TIMEOUT_MS,
18
+ STATE_LOCK_POLL_MS,
19
+ acquireStateLock,
20
+ releaseStateLock,
21
+ withStateMdLock,
22
+ } from './state-md-lock.mjs';
23
+
24
+ export {
25
+ STAGING_FENCE_LOCK_PATH,
26
+ DEFAULT_STAGING_FENCE_LOCK_TIMEOUT_MS,
27
+ STAGING_FENCE_LOCK_POLL_MS,
28
+ acquireStagingFenceLock,
29
+ releaseStagingFenceLock,
30
+ withStagingFenceLock,
31
+ } from './staging-fence-lock.mjs';