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,703 @@
1
+ /**
2
+ * session-lock.mjs — distributed session-lock with TTL (issue #330).
3
+ *
4
+ * Provides atomic acquire/release/inspect for a per-repo session.lock file
5
+ * stored at `.orchestrator/session.lock`. Prevents concurrent deep sessions
6
+ * from stomping each other's metrics writes and wave executor state.
7
+ *
8
+ * Design principles:
9
+ * - No-throw: every exported function catches filesystem errors and returns a
10
+ * structured failure object instead of propagating exceptions.
11
+ * - Atomic writes: acquire() writes to a temp file then renames, giving a
12
+ * single syscall that is either visible or not on POSIX systems.
13
+ * - Cross-host aware: PID liveness checks are skipped when the lock came from
14
+ * a different hostname (can't signal a remote process).
15
+ * - Decision deferred: acquire() reports stale locks but does NOT auto-clear
16
+ * them. session-start handles the recovery AUQ flow (W3-C3).
17
+ *
18
+ * BARREL CONTRACT (#630 A1 barrel-preserving split):
19
+ * This module bundled THREE orthogonal lock protocols. Two of them — the
20
+ * STATE.md write-lock and the staging-fence commit-mutex — were moved into
21
+ * dedicated modules under `scripts/lib/locks/`. This file STAYS the canonical
22
+ * barrel: it keeps the session-lock CORE protocol AND re-exports every moved
23
+ * symbol, so the original 22-symbol import surface is preserved EXACTLY and
24
+ * all 17 importers keep working UNCHANGED. The moved modules import shared
25
+ * primitives from leaf modules (file-lock.mjs, locks/lock-body.mjs) and NEVER
26
+ * from this file, so the barrel ↔ protocol-module edge is one-directional
27
+ * (no import cycle).
28
+ * - STATE.md write-lock → ./locks/state-md-lock.mjs
29
+ * - staging-fence mutex → ./locks/staging-fence-lock.mjs
30
+ *
31
+ * No external dependencies — Node 20+ stdlib only.
32
+ */
33
+
34
+ import fs from 'node:fs';
35
+ import os from 'node:os';
36
+ import path from 'node:path';
37
+ import crypto from 'node:crypto';
38
+ import { classifyMode } from './exclusivity-matrix.mjs';
39
+ import { isPidAliveOnHost } from './file-lock.mjs';
40
+
41
+ // isPidAliveOnHost moved into file-lock.mjs in #630 (the file-lock primitive
42
+ // owns it so the dependency edge points file-lock → io, never the reverse).
43
+ // Re-exported here so existing importers (agent-status historically,
44
+ // session-discovery's forensic note, memory-proposals historically, and any
45
+ // external caller) keep resolving `isPidAliveOnHost` from session-lock.mjs.
46
+ export { isPidAliveOnHost };
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Re-exports — STATE.md write-lock + staging-fence commit-mutex (#630 split).
50
+ // These two protocols live in dedicated modules now; the barrel re-exports
51
+ // their full public surface so importers of session-lock.mjs are unchanged.
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export {
55
+ STATE_LOCK_PATH,
56
+ DEFAULT_STATE_LOCK_TIMEOUT_MS,
57
+ STATE_LOCK_POLL_MS,
58
+ acquireStateLock,
59
+ releaseStateLock,
60
+ withStateMdLock,
61
+ } from './locks/state-md-lock.mjs';
62
+
63
+ export {
64
+ STAGING_FENCE_LOCK_PATH,
65
+ DEFAULT_STAGING_FENCE_LOCK_TIMEOUT_MS,
66
+ STAGING_FENCE_LOCK_POLL_MS,
67
+ acquireStagingFenceLock,
68
+ releaseStagingFenceLock,
69
+ withStagingFenceLock,
70
+ } from './locks/staging-fence-lock.mjs';
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Constants
74
+ // ---------------------------------------------------------------------------
75
+
76
+ export const DEFAULT_TTL_HOURS = 4;
77
+ export const LOCK_PATH = '.orchestrator/session.lock';
78
+
79
+ // ---------------------------------------------------------------------------
80
+ // Internal helpers
81
+ // ---------------------------------------------------------------------------
82
+
83
+ // isPidAliveOnHost lives in file-lock.mjs (#630) and is re-exported at the top
84
+ // of this module. It is a SAME-HOST PID liveness probe (POSIX signal-0). It is
85
+ // NOT the discovery-path liveness check — since Epic #583 the discovery
86
+ // decision tree uses heartbeat-age via {@link isLockLive} instead, because the
87
+ // `pid` recorded on a session.lock is the *ephemeral hook subprocess* PID.
88
+ // Same-host callers (`acquire`, `checkStale`, and the state-lock /
89
+ // staging-fence stale-override paths, now via the file-lock primitive) use it
90
+ // only for the short-lived stale-override path where the recorded PID IS the
91
+ // live writer's PID. See file-lock.mjs for the full @forensic + PID-recycle
92
+ // trade-off note.
93
+
94
+ /**
95
+ * Resolve the absolute path to the lock file.
96
+ * @param {string|undefined} repoRoot
97
+ * @returns {string}
98
+ */
99
+ function lockPathFor(repoRoot) {
100
+ return path.join(repoRoot ?? process.cwd(), LOCK_PATH);
101
+ }
102
+
103
+ /**
104
+ * Return the current time as an ISO-8601 string.
105
+ * @returns {string}
106
+ */
107
+ function nowIso() {
108
+ return new Date().toISOString();
109
+ }
110
+
111
+ /**
112
+ * Determine whether a lock's TTL has expired.
113
+ * @param {{ started_at: string, ttl_hours: number }} lock
114
+ * @returns {boolean}
115
+ */
116
+ function isTtlExpired(lock) {
117
+ const age = Date.now() - Date.parse(lock.started_at);
118
+ return age > lock.ttl_hours * 3600 * 1000;
119
+ }
120
+
121
+ /**
122
+ * Compute the age of a lock in fractional hours.
123
+ * Returns null if started_at is unparseable.
124
+ * @param {{ started_at: string }} lock
125
+ * @returns {number|null}
126
+ */
127
+ function lockAgeHours(lock) {
128
+ const ts = Date.parse(lock.started_at);
129
+ if (Number.isNaN(ts)) return null;
130
+ return (Date.now() - ts) / (3600 * 1000);
131
+ }
132
+
133
+ /**
134
+ * Parse lock file contents into an object. Returns null on any parse error.
135
+ *
136
+ * Schema v2 (Epic #583, W2-I3): adds `last_heartbeat` (optional, populated by
137
+ * `updateHeartbeat()`) + `semantic_session_id` (optional, populated when the
138
+ * `session_id` field carries a UUID and the caller wants to preserve the
139
+ * always-semantic id alongside it).
140
+ *
141
+ * Back-compat: v1 locks (no `last_heartbeat`) are normalised on read with
142
+ * `last_heartbeat = started_at`. The optional `semantic_session_id` field is
143
+ * left undefined when absent. This lets pre-#583 lockfiles flow through the
144
+ * new liveness rule transparently — the v1 lock's `started_at` becomes its
145
+ * effective heartbeat, so TTL freshness still rescues recent locks even when
146
+ * the writer process is dead (the D2/D5 production case).
147
+ *
148
+ * @param {string} raw
149
+ * @returns {object|null}
150
+ */
151
+ function parseLock(raw) {
152
+ try {
153
+ const obj = JSON.parse(raw);
154
+ if (
155
+ typeof obj === 'object' &&
156
+ obj !== null &&
157
+ typeof obj.session_id === 'string' &&
158
+ typeof obj.started_at === 'string' &&
159
+ typeof obj.mode === 'string' &&
160
+ typeof obj.pid === 'number' &&
161
+ typeof obj.host === 'string' &&
162
+ typeof obj.ttl_hours === 'number'
163
+ ) {
164
+ // Schema v1 → v2 normalisation: when `last_heartbeat` is absent or
165
+ // non-string, treat the lock as if it heartbeat-ed once at started_at.
166
+ const normalised = { ...obj };
167
+ if (typeof normalised.last_heartbeat !== 'string' || normalised.last_heartbeat.length === 0) {
168
+ normalised.last_heartbeat = normalised.started_at;
169
+ }
170
+ // semantic_session_id stays undefined when absent — callers that need it
171
+ // should fall back to session_id.
172
+ return normalised;
173
+ }
174
+ return null;
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Build a fresh lock object from caller-supplied fields.
182
+ *
183
+ * Schema v2 (Epic #583, W2-I3): `last_heartbeat` is seeded equal to
184
+ * `started_at`. Callers MUST call `updateHeartbeat()` on a known cadence
185
+ * (session-start, inter-wave, session-end) to keep the lock alive past TTL.
186
+ *
187
+ * @param {{ sessionId: string, mode: string, ttlHours: number, semanticSessionId?: string }} args
188
+ * @returns {object}
189
+ */
190
+ function buildLock({ sessionId, mode, ttlHours, semanticSessionId }) {
191
+ const startedAt = nowIso();
192
+ const lock = {
193
+ session_id: sessionId,
194
+ started_at: startedAt,
195
+ last_heartbeat: startedAt,
196
+ mode,
197
+ pid: process.pid,
198
+ host: os.hostname(),
199
+ ttl_hours: ttlHours,
200
+ };
201
+ if (typeof semanticSessionId === 'string' && semanticSessionId.length > 0) {
202
+ lock.semantic_session_id = semanticSessionId;
203
+ }
204
+ return lock;
205
+ }
206
+
207
+ /**
208
+ * Determine whether a lock is "live" based on its last_heartbeat freshness
209
+ * relative to TTL. Replaces PID-liveness as the primary discovery-time
210
+ * liveness check (Epic #583, W1-D1 + W1-D4 consensus): the writer-process
211
+ * PID is the *hook* PID, not the session PID, so PID-liveness incorrectly
212
+ * filtered out locks whose semantic owner (the Claude harness process) was
213
+ * still alive.
214
+ *
215
+ * Liveness rule: a lock is live when (now - last_heartbeat) < ttl_hours.
216
+ *
217
+ * @param {{ last_heartbeat: string, started_at: string, ttl_hours?: number }} lock
218
+ * @param {number} [nowMs]
219
+ * @returns {boolean}
220
+ */
221
+ export function isLockLive(lock, nowMs = Date.now()) {
222
+ if (!lock || typeof lock !== 'object') return false;
223
+ // Back-compat: prefer last_heartbeat; fall back to started_at when absent.
224
+ const hbStr = (typeof lock.last_heartbeat === 'string' && lock.last_heartbeat.length > 0)
225
+ ? lock.last_heartbeat
226
+ : lock.started_at;
227
+ const heartbeatMs = Date.parse(hbStr);
228
+ if (Number.isNaN(heartbeatMs)) return false;
229
+ const ttlHours = typeof lock.ttl_hours === 'number' ? lock.ttl_hours : DEFAULT_TTL_HOURS;
230
+ const ttlMs = ttlHours * 3600 * 1000;
231
+ return (nowMs - heartbeatMs) < ttlMs;
232
+ }
233
+
234
+ /**
235
+ * Atomically write a lock object to disk.
236
+ * Uses a tmp file in a mkdtemp directory + rename to avoid a partial-write race.
237
+ * @param {string} lockFile Absolute path to the lock file.
238
+ * @param {object} lock Lock object to serialize.
239
+ * @returns {{ ok: true } | { ok: false, reason: 'fs-error', error: string }}
240
+ */
241
+ function writeLockAtomic(lockFile, lock) {
242
+ try {
243
+ const dir = path.dirname(lockFile);
244
+ // Ensure .orchestrator/ directory exists.
245
+ fs.mkdirSync(dir, { recursive: true });
246
+
247
+ // Write to a uniquely-named temp file in the same directory so rename()
248
+ // is guaranteed to be an atomic same-filesystem operation on POSIX.
249
+ const tmpSuffix = crypto.randomBytes(6).toString('hex');
250
+ const tmpFile = path.join(dir, `.session.lock.tmp.${tmpSuffix}`);
251
+
252
+ fs.writeFileSync(tmpFile, JSON.stringify(lock, null, 2) + '\n', { encoding: 'utf8' });
253
+ fs.renameSync(tmpFile, lockFile);
254
+
255
+ return { ok: true };
256
+ } catch (err) {
257
+ return { ok: false, reason: 'fs-error', error: err.message };
258
+ }
259
+ }
260
+
261
+ /**
262
+ * Atomically create the session-lock file via tmp + hardlink (create-or-fail).
263
+ *
264
+ * TOCTOU fix (#590 Item 2): the previous fresh-acquire path used
265
+ * `writeLockAtomic` (tmp + renameSync), which is last-writer-wins — two
266
+ * concurrent SessionStart hooks that BOTH observed `readLock() === null` would
267
+ * BOTH rename their tmp file over the lock and BOTH believe they acquired it.
268
+ * `linkSync` is POSIX-atomic create-or-fail: exactly one concurrent caller wins
269
+ * the create, every other caller gets EEXIST. This is the same idiom used by
270
+ * the file-lock primitive (tryAcquireFileLock) backing the state-lock and
271
+ * staging-fence-lock modules.
272
+ *
273
+ * Used ONLY by the no-existing-lock branch of {@link acquire}. The
274
+ * intentional-overwrite paths (`forceAcquire`, `updateHeartbeat`) keep using
275
+ * `writeLockAtomic` because they MUST replace an existing lock, not fail on it.
276
+ *
277
+ * @param {string} lockFile Absolute path to .orchestrator/session.lock.
278
+ * @param {object} lock Lock object to serialize.
279
+ * @returns {{ ok: true } | { ok: false, reason: 'exists' } | { ok: false, reason: 'fs-error', error: string }}
280
+ */
281
+ function createSessionLockExclusive(lockFile, lock) {
282
+ const dir = path.dirname(lockFile);
283
+ let tmpFile;
284
+ try {
285
+ fs.mkdirSync(dir, { recursive: true });
286
+ const tmpSuffix = crypto.randomBytes(8).toString('hex');
287
+ tmpFile = path.join(dir, `.session.lock.create.tmp.${tmpSuffix}`);
288
+ fs.writeFileSync(tmpFile, JSON.stringify(lock, null, 2) + '\n', { encoding: 'utf8' });
289
+ } catch (err) {
290
+ if (tmpFile) {
291
+ try { fs.unlinkSync(tmpFile); } catch { /* best-effort */ }
292
+ }
293
+ return { ok: false, reason: 'fs-error', error: err.message };
294
+ }
295
+
296
+ try {
297
+ fs.linkSync(tmpFile, lockFile);
298
+ return { ok: true };
299
+ } catch (err) {
300
+ if (err.code === 'EEXIST') {
301
+ return { ok: false, reason: 'exists' };
302
+ }
303
+ return { ok: false, reason: 'fs-error', error: err.message };
304
+ } finally {
305
+ try { fs.unlinkSync(tmpFile); } catch { /* best-effort */ }
306
+ }
307
+ }
308
+
309
+ // ---------------------------------------------------------------------------
310
+ // Exported API
311
+ // ---------------------------------------------------------------------------
312
+
313
+ /**
314
+ * Read the lock file without modifying it.
315
+ * Returns the parsed lock object, or null if absent or unparseable.
316
+ * Never throws.
317
+ *
318
+ * @param {{ repoRoot?: string }} [opts]
319
+ * @returns {{ session_id: string, started_at: string, mode: string, pid: number, host: string, ttl_hours: number } | null}
320
+ */
321
+ export function readLock(opts = {}) {
322
+ const lockFile = lockPathFor(opts.repoRoot);
323
+ try {
324
+ const raw = fs.readFileSync(lockFile, 'utf8');
325
+ return parseLock(raw);
326
+ } catch {
327
+ return null;
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Atomically acquire the session lock.
333
+ *
334
+ * Consults the P1.1 exclusivity-matrix when `activeSessions` is provided.
335
+ * When omitted, falls back to the legacy local-lock-only logic (backward compat).
336
+ *
337
+ * @param {object} args
338
+ * @param {string} args.sessionId
339
+ * @param {string} args.mode
340
+ * @param {number} [args.ttlHours]
341
+ * @param {string} [args.repoRoot]
342
+ * @param {string} [args.semanticSessionId]
343
+ * Optional always-semantic session id (e.g., `<branch>-<date>-<mode>-<n>`).
344
+ * When `sessionId` is a UUID (Claude Code path) and a semantic id is also
345
+ * known, pass it here to be persisted alongside the UUID. Schema v2.
346
+ * @param {boolean} [args.quiet=false]
347
+ * When true, the unknown-mode classify path SKIPS the `console.warn`-to-stderr
348
+ * while still defaulting the caller-class to 'parallel-ok'. Added for #592
349
+ * MED-2: the SessionStart hook (lock-bootstrap.mjs) must keep stderr empty,
350
+ * and previously pre-sanitised the mode locally to dodge this warn. This flag
351
+ * lets callers opt into silent unknown-mode handling without duplicating the
352
+ * mode-mapping logic. PURELY ADDITIVE — default (warn) behaviour is unchanged,
353
+ * and the raw `mode` is still persisted on the lock body either way.
354
+ * @param {Array<{mode:string,pid:number,host:string,sessionId:string}>} [args.activeSessions]
355
+ * Optional pre-computed array from discoverActiveSessions(repoRoot). When omitted,
356
+ * matrix consultation is skipped (legacy behavior). Callers (worktree-pipeline.mjs,
357
+ * hooks/on-session-start.mjs) call discoverActiveSessions() themselves and pass the
358
+ * result — this keeps acquire() synchronous.
359
+ *
360
+ * Returns one of:
361
+ * { ok: true, lock, exclusivityClass? }
362
+ * — lock created
363
+ * { ok: false, reason: 'active', existingLock, exclusivityClass? }
364
+ * — local lock held (live TTL, live PID)
365
+ * { ok: false, reason: 'stale-pid-dead', existingLock, exclusivityClass? }
366
+ * — local lock stale (dead PID)
367
+ * { ok: false, reason: 'stale-pid-alive', existingLock, exclusivityClass? }
368
+ * — local lock stale (live PID, TTL expired)
369
+ * { ok: false, reason: 'fs-error', error, exclusivityClass? }
370
+ * — filesystem failure
371
+ * { ok: false, reason: 'active-incompatible-exclusive', allActiveSessions, blockingSession, exclusivityClass }
372
+ * — caller blocked by an active exclusive-class session (P1.2 #570)
373
+ * { ok: false, reason: 'active-compatible-parallel', allActiveSessions, exclusivityClass }
374
+ * — caller could create a parallel session; preamble offers Worktree-Auto-Promotion (P1.2 #570)
375
+ * { ok: false, reason: 'active-readonly-bypass', allActiveSessions, exclusivityClass: 'always-ok' }
376
+ * — caller is read-only-class; preamble passes through without AUQ (P1.2 #570).
377
+ * Callers for 'always-ok' modes SHOULD interpret this as "proceed without AUQ, no lock needed".
378
+ *
379
+ * The `exclusivityClass` field is optional (undefined when activeSessions is not passed)
380
+ * and is added to ALL return shapes so callers can always observe the caller's class.
381
+ *
382
+ * The caller (session-start) decides whether to invoke forceAcquire() after
383
+ * obtaining user consent.
384
+ */
385
+ export function acquire({ sessionId, mode, ttlHours = DEFAULT_TTL_HOURS, repoRoot, activeSessions, semanticSessionId, quiet = false } = {}) {
386
+ const lockFile = lockPathFor(repoRoot);
387
+
388
+ // -------------------------------------------------------------------------
389
+ // Safe classifyMode wrapper — unknown modes default to 'parallel-ok' (most
390
+ // permissive) rather than propagating an exception into the try/catch below
391
+ // where it would be silently turned into an 'fs-error'. A console.warn is
392
+ // emitted for visibility UNLESS the caller passes `quiet: true` (#592 MED-2 —
393
+ // lets lock-bootstrap.mjs keep stderr empty without pre-mapping the mode).
394
+ // This call is intentionally OUTSIDE the main try/catch so that only
395
+ // fs-errors reach the catch block.
396
+ // -------------------------------------------------------------------------
397
+ let callerClass;
398
+ try {
399
+ callerClass = classifyMode(mode);
400
+ } catch {
401
+ if (quiet !== true) {
402
+ console.warn(
403
+ `acquire: unknown mode "${mode}" — defaulting exclusivityClass to "parallel-ok". ` +
404
+ 'Add the mode to exclusivity-matrix.mjs if intentional.',
405
+ );
406
+ }
407
+ callerClass = 'parallel-ok';
408
+ }
409
+
410
+ // -------------------------------------------------------------------------
411
+ // P1.2 exclusivity-matrix consultation — only when activeSessions is provided.
412
+ // Run BEFORE local-lock check so parallel-session conflicts surface first.
413
+ // -------------------------------------------------------------------------
414
+ if (Array.isArray(activeSessions) && activeSessions.length > 0) {
415
+ let hasCompatibleParallel = false;
416
+
417
+ for (const entry of activeSessions) {
418
+ // Safe classify for each active session's mode.
419
+ let entryClass;
420
+ try {
421
+ entryClass = classifyMode(entry.mode);
422
+ } catch {
423
+ // Unknown active session mode — treat as parallel-ok (most permissive default).
424
+ entryClass = 'parallel-ok';
425
+ }
426
+
427
+ if (entryClass === 'exclusive' && callerClass !== 'always-ok') {
428
+ // An exclusive active session blocks all non-always-ok callers.
429
+ return {
430
+ ok: false,
431
+ reason: 'active-incompatible-exclusive',
432
+ exclusivityClass: callerClass,
433
+ allActiveSessions: activeSessions,
434
+ blockingSession: entry,
435
+ };
436
+ }
437
+
438
+ if (entryClass === 'parallel-ok' && callerClass === 'parallel-ok') {
439
+ hasCompatibleParallel = true;
440
+ }
441
+ }
442
+
443
+ // After loop: handle always-ok bypass (read-only caller).
444
+ if (callerClass === 'always-ok') {
445
+ return {
446
+ ok: false,
447
+ reason: 'active-readonly-bypass',
448
+ exclusivityClass: 'always-ok',
449
+ allActiveSessions: activeSessions,
450
+ };
451
+ }
452
+
453
+ // Parallel-compatible situation: preamble should offer Worktree-Auto-Promotion.
454
+ if (hasCompatibleParallel) {
455
+ return {
456
+ ok: false,
457
+ reason: 'active-compatible-parallel',
458
+ exclusivityClass: callerClass,
459
+ allActiveSessions: activeSessions,
460
+ };
461
+ }
462
+
463
+ // All active sessions are 'always-ok' and caller is non-always-ok, or
464
+ // no blocking condition was found — fall through to local-lock check.
465
+ }
466
+
467
+ // -------------------------------------------------------------------------
468
+ // Local lock check — unchanged logic from original acquire().
469
+ // -------------------------------------------------------------------------
470
+ try {
471
+ // Classify an existing lock into the correct failure result. Shared by the
472
+ // up-front readLock() check AND the create-race EEXIST-loser path below so
473
+ // both report identical active / stale-pid-dead / stale-pid-alive reasons.
474
+ const classifyExisting = (existing) => {
475
+ const sameHost = existing.host === os.hostname();
476
+ // PID liveness is only meaningful on the same host.
477
+ const pidAlive = sameHost ? isPidAliveOnHost(existing.pid) : null;
478
+
479
+ // Heartbeat-first liveness (#744): isLockLive is the SOLE active gate.
480
+ // A dead recorded PID must NOT veto a fresh last_heartbeat — the pid on
481
+ // a session.lock is the ephemeral hook subprocess PID, not the semantic
482
+ // session's own PID (Epic #583, W1-D1 + W1-D4 consensus). Likewise,
483
+ // isTtlExpired measures age from started_at, which wrongly flags a
484
+ // long-running-but-heartbeating session as expired. Both bugs together
485
+ // produced the #744 incident: a live heartbeating session was
486
+ // misclassified 'stale-pid-dead' mid-wave.
487
+ if (isLockLive(existing)) {
488
+ return { ok: false, reason: 'active', existingLock: existing, exclusivityClass: callerClass };
489
+ }
490
+
491
+ // Heartbeat expired — classify the stale variant. Cross-host locks never
492
+ // have a confirmable dead PID (pidAlive stays null), so they always land
493
+ // on 'stale-pid-alive' rather than 'stale-pid-dead'.
494
+ const reason = (pidAlive === false) ? 'stale-pid-dead' : 'stale-pid-alive';
495
+ return { ok: false, reason, existingLock: existing, exclusivityClass: callerClass };
496
+ };
497
+
498
+ const existing = readLock({ repoRoot });
499
+
500
+ if (existing !== null) {
501
+ // A lock is present — classify it.
502
+ return classifyExisting(existing);
503
+ }
504
+
505
+ // No existing lock — create one with a TOCTOU-safe create-or-fail (#590).
506
+ // Two concurrent SessionStart hooks can both reach this branch having each
507
+ // observed readLock() === null; linkSync guarantees exactly one wins.
508
+ const lock = buildLock({ sessionId, mode, ttlHours, semanticSessionId });
509
+ const createResult = createSessionLockExclusive(lockFile, lock);
510
+
511
+ if (createResult.ok) {
512
+ return { ok: true, lock, exclusivityClass: callerClass };
513
+ }
514
+ if (createResult.reason === 'fs-error') {
515
+ return { ok: false, reason: 'fs-error', error: createResult.error, exclusivityClass: callerClass };
516
+ }
517
+
518
+ // reason === 'exists' — we lost the create race. Re-read the now-present
519
+ // lock and classify it exactly as if we had seen it on the up-front check.
520
+ const raced = readLock({ repoRoot });
521
+ if (raced === null) {
522
+ // The EEXIST winner's lock vanished before we could re-read it (ENOENT /
523
+ // unparseable). Defensive fallback: report 'active' so the caller defers
524
+ // rather than racing again — mirrors tryAcquireStateLock's vanish-race
525
+ // handling (a lost-then-vanished race resolves conservatively).
526
+ return { ok: false, reason: 'active', existingLock: null, exclusivityClass: callerClass };
527
+ }
528
+ return classifyExisting(raced);
529
+ } catch (err) {
530
+ return { ok: false, reason: 'fs-error', error: err.message, exclusivityClass: callerClass };
531
+ }
532
+ }
533
+
534
+ /**
535
+ * Force-acquire the lock, unconditionally replacing any existing lock.
536
+ * Call only after the user has explicitly authorised stale-lock takeover.
537
+ *
538
+ * Returns:
539
+ * { ok: true, lock, replacedLock? } — lock written (replacedLock present if one was overwritten)
540
+ * { ok: false, reason: 'fs-error', ... } — filesystem failure
541
+ *
542
+ * @param {{ sessionId: string, mode: string, ttlHours?: number, repoRoot?: string, semanticSessionId?: string }} args
543
+ */
544
+ export function forceAcquire({ sessionId, mode, ttlHours = DEFAULT_TTL_HOURS, repoRoot, semanticSessionId } = {}) {
545
+ try {
546
+ const replacedLock = readLock({ repoRoot });
547
+ const lock = buildLock({ sessionId, mode, ttlHours, semanticSessionId });
548
+ const lockFile = lockPathFor(repoRoot);
549
+
550
+ const writeResult = writeLockAtomic(lockFile, lock);
551
+ if (!writeResult.ok) return writeResult;
552
+
553
+ const result = { ok: true, lock };
554
+ if (replacedLock !== null) result.replacedLock = replacedLock;
555
+ return result;
556
+ } catch (err) {
557
+ return { ok: false, reason: 'fs-error', error: err.message };
558
+ }
559
+ }
560
+
561
+ /**
562
+ * Release the lock IFF it belongs to the given session_id.
563
+ * Silent no-op when the lock belongs to a different session or does not exist.
564
+ * Never throws.
565
+ *
566
+ * Post-delete verify (#744 Fix 3, refined): after unlinking, re-reads the
567
+ * lock path to confirm the delete was durable. The retry is **ownership-
568
+ * scoped** — only OUR OWN lock reappearing (same `session_id`) warrants a
569
+ * bounded retry unlink, which covers a transient unlink/stat race where our
570
+ * just-deleted file is briefly still observable. A FOREIGN lock (a
571
+ * different `session_id`) present at the re-read means a sibling session
572
+ * legitimately `acquire()`d the path in the race window between our unlink
573
+ * and this re-read — OUR lock is already gone (the delete succeeded), so
574
+ * this is treated as verified and the foreign lock is left untouched. This
575
+ * is load-bearing for PSA-005: a releaser must never unlink a lock it does
576
+ * not own, even indirectly via a "still present, so retry-unlink" heuristic
577
+ * that does not check who the present lock belongs to.
578
+ *
579
+ * `verified` is computed as "our lock is no longer present" — a foreign
580
+ * lock present is fine (ours is gone); only our own lock still being
581
+ * observable after the bounded retry sets `verified: false`.
582
+ *
583
+ * @param {{ sessionId: string, repoRoot?: string }} args
584
+ * @returns {{ ok: true, deleted: boolean, reason?: string, verified?: boolean }}
585
+ */
586
+ export function release({ sessionId, repoRoot } = {}) {
587
+ const lockFile = lockPathFor(repoRoot);
588
+ try {
589
+ const existing = readLock({ repoRoot });
590
+
591
+ if (existing === null) {
592
+ return { ok: true, deleted: false, reason: 'no-lock' };
593
+ }
594
+
595
+ if (existing.session_id !== sessionId) {
596
+ return { ok: true, deleted: false, reason: 'session-mismatch' };
597
+ }
598
+
599
+ fs.unlinkSync(lockFile);
600
+
601
+ // Post-delete verify: only retry when OUR OWN lock is still observable.
602
+ // A foreign lock here belongs to a sibling that re-acquired in the race
603
+ // window — never touch it (PSA-005).
604
+ let after = readLock({ repoRoot });
605
+ if (after !== null && after.session_id === sessionId) {
606
+ try {
607
+ fs.unlinkSync(lockFile);
608
+ } catch {
609
+ // best-effort retry — fall through to the final re-check regardless.
610
+ }
611
+ after = readLock({ repoRoot });
612
+ }
613
+
614
+ const verified = after === null || after.session_id !== sessionId;
615
+ return { ok: true, deleted: true, verified };
616
+ } catch (err) {
617
+ return { ok: false, reason: 'fs-error', error: err.message };
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Refresh the `last_heartbeat` field on an existing lock, atomically.
623
+ *
624
+ * Schema v2 (Epic #583, W2-I3): the lock's liveness is determined by
625
+ * `(now - last_heartbeat) < ttl_hours`. Callers MUST invoke this on a known
626
+ * cadence (session-start, inter-wave, session-end) to keep the lock alive
627
+ * across long sessions.
628
+ *
629
+ * Same-session guard: refuses to update someone else's lock. Returns `false`
630
+ * when the lock is absent, malformed, or held by a different session_id.
631
+ * Returns `true` on a successful atomic update.
632
+ *
633
+ * Atomicity: same tmp + rename pattern as writeLockAtomic — single syscall
634
+ * visibility on POSIX. Never throws.
635
+ *
636
+ * @param {{ repoRoot?: string, sessionId: string }} opts
637
+ * @returns {boolean}
638
+ */
639
+ export function updateHeartbeat({ repoRoot, sessionId } = {}) {
640
+ if (typeof sessionId !== 'string' || sessionId.length === 0) return false;
641
+ const existing = readLock({ repoRoot });
642
+ if (existing === null) return false;
643
+ if (existing.session_id !== sessionId) return false;
644
+ const updated = { ...existing, last_heartbeat: nowIso() };
645
+ const lockFile = lockPathFor(repoRoot);
646
+ const writeResult = writeLockAtomic(lockFile, updated);
647
+ return writeResult.ok === true;
648
+ }
649
+
650
+ /**
651
+ * Inspect the lock file and compute staleness metadata.
652
+ *
653
+ * @param {{ repoRoot?: string }} [opts]
654
+ * @returns {{
655
+ * exists: boolean,
656
+ * lock: object|null,
657
+ * ageHours: number|null,
658
+ * ttlExpired: boolean,
659
+ * pidAlive: boolean|null,
660
+ * host: string|null,
661
+ * sameHost: boolean,
662
+ * isLive: boolean
663
+ * }}
664
+ */
665
+ export function checkStale({ repoRoot } = {}) {
666
+ const lock = readLock({ repoRoot });
667
+
668
+ if (lock === null) {
669
+ return {
670
+ exists: false,
671
+ lock: null,
672
+ ageHours: null,
673
+ ttlExpired: false,
674
+ pidAlive: null,
675
+ host: null,
676
+ sameHost: false,
677
+ isLive: false,
678
+ };
679
+ }
680
+
681
+ const ageHours = lockAgeHours(lock);
682
+ const ttlExpired = isTtlExpired(lock);
683
+ const sameHost = lock.host === os.hostname();
684
+ // Only attempt PID check when the lock was written on this machine.
685
+ const pidAlive = sameHost ? isPidAliveOnHost(lock.pid) : null;
686
+ // Heartbeat-based liveness (#744) — additive field alongside the pre-existing
687
+ // ttlExpired/pidAlive/sameHost fields (back-compat). This is the SAME check
688
+ // acquire()'s classifyExisting now uses as its sole active gate, surfaced
689
+ // here so callers of checkStale() (recovery-flow diagnostics) can observe
690
+ // when isLive diverges from the legacy pidAlive/ttlExpired signals.
691
+ const isLive = isLockLive(lock);
692
+
693
+ return {
694
+ exists: true,
695
+ lock,
696
+ ageHours,
697
+ ttlExpired,
698
+ pidAlive,
699
+ host: lock.host,
700
+ sameHost,
701
+ isLive,
702
+ };
703
+ }