oh-my-copilot 4.13.2 → 4.13.21

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 (311) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +1 -1
  4. package/bridge/cli.cjs +2522 -949
  5. package/bridge/mcp-server.cjs +97 -9
  6. package/bridge/runtime-cli.cjs +1256 -133
  7. package/bridge/team.js +1241 -65
  8. package/dist/__tests__/auto-slash-aliases.test.js +91 -0
  9. package/dist/__tests__/auto-slash-aliases.test.js.map +1 -1
  10. package/dist/__tests__/hud/labels.test.d.ts +2 -0
  11. package/dist/__tests__/hud/labels.test.d.ts.map +1 -0
  12. package/dist/__tests__/hud/labels.test.js +158 -0
  13. package/dist/__tests__/hud/labels.test.js.map +1 -0
  14. package/dist/__tests__/hud/render-rate-limits-priority.test.js +26 -0
  15. package/dist/__tests__/hud/render-rate-limits-priority.test.js.map +1 -1
  16. package/dist/__tests__/hud-marketplace-resolution.test.js +34 -0
  17. package/dist/__tests__/hud-marketplace-resolution.test.js.map +1 -1
  18. package/dist/__tests__/installer-hud-skip.test.js +6 -0
  19. package/dist/__tests__/installer-hud-skip.test.js.map +1 -1
  20. package/dist/__tests__/installer-omc-reference.test.js +1 -1
  21. package/dist/__tests__/installer-omc-reference.test.js.map +1 -1
  22. package/dist/__tests__/mnemosyne/detector.test.js +2 -0
  23. package/dist/__tests__/mnemosyne/detector.test.js.map +1 -1
  24. package/dist/__tests__/notepad.test.js +2 -2
  25. package/dist/__tests__/notepad.test.js.map +1 -1
  26. package/dist/__tests__/pre-tool-enforcer.test.js +84 -1
  27. package/dist/__tests__/pre-tool-enforcer.test.js.map +1 -1
  28. package/dist/__tests__/ralph-prd-mandatory.test.js +11 -1
  29. package/dist/__tests__/ralph-prd-mandatory.test.js.map +1 -1
  30. package/dist/__tests__/ralph-prd.test.js +40 -2
  31. package/dist/__tests__/ralph-prd.test.js.map +1 -1
  32. package/dist/__tests__/skills.test.js +363 -4
  33. package/dist/__tests__/skills.test.js.map +1 -1
  34. package/dist/__tests__/state-root-resolution.test.js +56 -6
  35. package/dist/__tests__/state-root-resolution.test.js.map +1 -1
  36. package/dist/autoresearch/__tests__/runtime.test.js +225 -28
  37. package/dist/autoresearch/__tests__/runtime.test.js.map +1 -1
  38. package/dist/autoresearch/runtime.d.ts.map +1 -1
  39. package/dist/autoresearch/runtime.js +9 -5
  40. package/dist/autoresearch/runtime.js.map +1 -1
  41. package/dist/cli/autoresearch.d.ts +1 -1
  42. package/dist/cli/autoresearch.d.ts.map +1 -1
  43. package/dist/cli/autoresearch.js +4 -4
  44. package/dist/cli/commands/team.d.ts +1 -0
  45. package/dist/cli/commands/team.d.ts.map +1 -1
  46. package/dist/cli/commands/team.js +14 -2
  47. package/dist/cli/commands/team.js.map +1 -1
  48. package/dist/cli/index.js +3 -0
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/cli/team.d.ts +6 -0
  51. package/dist/cli/team.d.ts.map +1 -1
  52. package/dist/cli/team.js +23 -1
  53. package/dist/cli/team.js.map +1 -1
  54. package/dist/config/__tests__/loader.test.js.map +1 -1
  55. package/dist/features/boulder-state/constants.d.ts +1 -1
  56. package/dist/features/boulder-state/constants.d.ts.map +1 -1
  57. package/dist/features/builtin-skills/skills.d.ts.map +1 -1
  58. package/dist/features/builtin-skills/skills.js +11 -3
  59. package/dist/features/builtin-skills/skills.js.map +1 -1
  60. package/dist/features/builtin-skills/types.d.ts +7 -0
  61. package/dist/features/builtin-skills/types.d.ts.map +1 -1
  62. package/dist/features/magic-keywords.d.ts.map +1 -1
  63. package/dist/features/magic-keywords.js +30 -1
  64. package/dist/features/magic-keywords.js.map +1 -1
  65. package/dist/features/rate-limit-wait/tmux-detector.d.ts +4 -4
  66. package/dist/features/rate-limit-wait/tmux-detector.js +9 -9
  67. package/dist/features/rate-limit-wait/tmux-detector.js.map +1 -1
  68. package/dist/hooks/__tests__/bridge-routing.test.js +156 -0
  69. package/dist/hooks/__tests__/bridge-routing.test.js.map +1 -1
  70. package/dist/hooks/auto-slash-command/constants.d.ts.map +1 -1
  71. package/dist/hooks/auto-slash-command/constants.js +1 -0
  72. package/dist/hooks/auto-slash-command/constants.js.map +1 -1
  73. package/dist/hooks/auto-slash-command/executor.d.ts.map +1 -1
  74. package/dist/hooks/auto-slash-command/executor.js +29 -0
  75. package/dist/hooks/auto-slash-command/executor.js.map +1 -1
  76. package/dist/hooks/bridge.d.ts.map +1 -1
  77. package/dist/hooks/bridge.js +184 -1
  78. package/dist/hooks/bridge.js.map +1 -1
  79. package/dist/hooks/learner/detector.js +1 -1
  80. package/dist/hooks/mode-registry/__tests__/session-isolation.test.js +16 -0
  81. package/dist/hooks/mode-registry/__tests__/session-isolation.test.js.map +1 -1
  82. package/dist/hooks/notepad/index.d.ts +4 -1
  83. package/dist/hooks/notepad/index.d.ts.map +1 -1
  84. package/dist/hooks/notepad/index.js +7 -4
  85. package/dist/hooks/notepad/index.js.map +1 -1
  86. package/dist/hooks/persistent-mode/__tests__/ralph-verification-flow.test.js +70 -3
  87. package/dist/hooks/persistent-mode/__tests__/ralph-verification-flow.test.js.map +1 -1
  88. package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
  89. package/dist/hooks/persistent-mode/index.js +10 -9
  90. package/dist/hooks/persistent-mode/index.js.map +1 -1
  91. package/dist/hooks/persistent-mode/stop-hook-blocking.test.js +64 -9
  92. package/dist/hooks/persistent-mode/stop-hook-blocking.test.js.map +1 -1
  93. package/dist/hooks/project-memory/__tests__/integration.test.js +117 -3
  94. package/dist/hooks/project-memory/__tests__/integration.test.js.map +1 -1
  95. package/dist/hooks/project-memory/__tests__/storage.test.js +11 -11
  96. package/dist/hooks/project-memory/__tests__/storage.test.js.map +1 -1
  97. package/dist/hooks/project-memory/index.d.ts.map +1 -1
  98. package/dist/hooks/project-memory/index.js +24 -12
  99. package/dist/hooks/project-memory/index.js.map +1 -1
  100. package/dist/hooks/ralph/index.d.ts +1 -1
  101. package/dist/hooks/ralph/index.d.ts.map +1 -1
  102. package/dist/hooks/ralph/index.js +1 -1
  103. package/dist/hooks/ralph/index.js.map +1 -1
  104. package/dist/hooks/ralph/loop.d.ts +6 -6
  105. package/dist/hooks/ralph/loop.d.ts.map +1 -1
  106. package/dist/hooks/ralph/loop.js +18 -18
  107. package/dist/hooks/ralph/loop.js.map +1 -1
  108. package/dist/hooks/ralph/prd.d.ts +23 -12
  109. package/dist/hooks/ralph/prd.d.ts.map +1 -1
  110. package/dist/hooks/ralph/prd.js +88 -42
  111. package/dist/hooks/ralph/prd.js.map +1 -1
  112. package/dist/hooks/session-end/__tests__/mode-state-cleanup.test.js +20 -0
  113. package/dist/hooks/session-end/__tests__/mode-state-cleanup.test.js.map +1 -1
  114. package/dist/hooks/session-end/index.d.ts +9 -0
  115. package/dist/hooks/session-end/index.d.ts.map +1 -1
  116. package/dist/hooks/session-end/index.js +67 -0
  117. package/dist/hooks/session-end/index.js.map +1 -1
  118. package/dist/hooks/skill-bridge.cjs +6 -5
  119. package/dist/hooks/skill-state/index.d.ts.map +1 -1
  120. package/dist/hooks/skill-state/index.js +1 -0
  121. package/dist/hooks/skill-state/index.js.map +1 -1
  122. package/dist/hud/__tests__/git-status.test.js +18 -0
  123. package/dist/hud/__tests__/git-status.test.js.map +1 -1
  124. package/dist/hud/elements/background.d.ts +3 -3
  125. package/dist/hud/elements/background.d.ts.map +1 -1
  126. package/dist/hud/elements/background.js +5 -4
  127. package/dist/hud/elements/background.js.map +1 -1
  128. package/dist/hud/elements/call-counts.d.ts +2 -2
  129. package/dist/hud/elements/call-counts.d.ts.map +1 -1
  130. package/dist/hud/elements/call-counts.js +7 -6
  131. package/dist/hud/elements/call-counts.js.map +1 -1
  132. package/dist/hud/elements/context.d.ts +3 -3
  133. package/dist/hud/elements/context.d.ts.map +1 -1
  134. package/dist/hud/elements/context.js +5 -4
  135. package/dist/hud/elements/context.js.map +1 -1
  136. package/dist/hud/elements/git.d.ts +2 -1
  137. package/dist/hud/elements/git.d.ts.map +1 -1
  138. package/dist/hud/elements/git.js +7 -6
  139. package/dist/hud/elements/git.js.map +1 -1
  140. package/dist/hud/elements/ralph.d.ts +2 -2
  141. package/dist/hud/elements/ralph.d.ts.map +1 -1
  142. package/dist/hud/elements/ralph.js +3 -2
  143. package/dist/hud/elements/ralph.js.map +1 -1
  144. package/dist/hud/elements/thinking.d.ts +2 -2
  145. package/dist/hud/elements/thinking.d.ts.map +1 -1
  146. package/dist/hud/elements/thinking.js +3 -2
  147. package/dist/hud/elements/thinking.js.map +1 -1
  148. package/dist/hud/elements/token-usage.d.ts +2 -2
  149. package/dist/hud/elements/token-usage.d.ts.map +1 -1
  150. package/dist/hud/elements/token-usage.js +3 -2
  151. package/dist/hud/elements/token-usage.js.map +1 -1
  152. package/dist/hud/render.d.ts.map +1 -1
  153. package/dist/hud/render.js +11 -10
  154. package/dist/hud/render.js.map +1 -1
  155. package/dist/hud/state.d.ts.map +1 -1
  156. package/dist/hud/state.js +18 -1
  157. package/dist/hud/state.js.map +1 -1
  158. package/dist/hud/types.d.ts +26 -0
  159. package/dist/hud/types.d.ts.map +1 -1
  160. package/dist/hud/types.js +58 -0
  161. package/dist/hud/types.js.map +1 -1
  162. package/dist/installer/__tests__/hud-cache-wrapper.test.d.ts +2 -0
  163. package/dist/installer/__tests__/hud-cache-wrapper.test.d.ts.map +1 -0
  164. package/dist/installer/__tests__/hud-cache-wrapper.test.js +235 -0
  165. package/dist/installer/__tests__/hud-cache-wrapper.test.js.map +1 -0
  166. package/dist/installer/__tests__/standalone-hook-reconcile.test.js +2 -0
  167. package/dist/installer/__tests__/standalone-hook-reconcile.test.js.map +1 -1
  168. package/dist/installer/index.d.ts.map +1 -1
  169. package/dist/installer/index.js +19 -7
  170. package/dist/installer/index.js.map +1 -1
  171. package/dist/lib/__tests__/worktree-paths.test.js +162 -39
  172. package/dist/lib/__tests__/worktree-paths.test.js.map +1 -1
  173. package/dist/lib/worktree-paths.d.ts +87 -9
  174. package/dist/lib/worktree-paths.d.ts.map +1 -1
  175. package/dist/lib/worktree-paths.js +209 -27
  176. package/dist/lib/worktree-paths.js.map +1 -1
  177. package/dist/team/__tests__/auto-merge-restart.test.d.ts +2 -0
  178. package/dist/team/__tests__/auto-merge-restart.test.d.ts.map +1 -0
  179. package/dist/team/__tests__/auto-merge-restart.test.js +128 -0
  180. package/dist/team/__tests__/auto-merge-restart.test.js.map +1 -0
  181. package/dist/team/__tests__/auto-merge.perf.test.d.ts +2 -0
  182. package/dist/team/__tests__/auto-merge.perf.test.d.ts.map +1 -0
  183. package/dist/team/__tests__/auto-merge.perf.test.js +85 -0
  184. package/dist/team/__tests__/auto-merge.perf.test.js.map +1 -0
  185. package/dist/team/__tests__/conflict-mailbox.test.d.ts +2 -0
  186. package/dist/team/__tests__/conflict-mailbox.test.d.ts.map +1 -0
  187. package/dist/team/__tests__/conflict-mailbox.test.js +238 -0
  188. package/dist/team/__tests__/conflict-mailbox.test.js.map +1 -0
  189. package/dist/team/__tests__/helpers/git-fixture.d.ts +72 -0
  190. package/dist/team/__tests__/helpers/git-fixture.d.ts.map +1 -0
  191. package/dist/team/__tests__/helpers/git-fixture.js +252 -0
  192. package/dist/team/__tests__/helpers/git-fixture.js.map +1 -0
  193. package/dist/team/__tests__/leader-inbox.test.d.ts +2 -0
  194. package/dist/team/__tests__/leader-inbox.test.d.ts.map +1 -0
  195. package/dist/team/__tests__/leader-inbox.test.js +152 -0
  196. package/dist/team/__tests__/leader-inbox.test.js.map +1 -0
  197. package/dist/team/__tests__/merge-orchestrator.test.d.ts +2 -0
  198. package/dist/team/__tests__/merge-orchestrator.test.d.ts.map +1 -0
  199. package/dist/team/__tests__/merge-orchestrator.test.js +779 -0
  200. package/dist/team/__tests__/merge-orchestrator.test.js.map +1 -0
  201. package/dist/team/__tests__/rebase-smoke.test.d.ts +2 -0
  202. package/dist/team/__tests__/rebase-smoke.test.d.ts.map +1 -0
  203. package/dist/team/__tests__/rebase-smoke.test.js +272 -0
  204. package/dist/team/__tests__/rebase-smoke.test.js.map +1 -0
  205. package/dist/team/__tests__/runtime-cli.test.js +9 -1
  206. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  207. package/dist/team/__tests__/runtime-v2.dispatch.test.js +150 -1
  208. package/dist/team/__tests__/runtime-v2.dispatch.test.js.map +1 -1
  209. package/dist/team/__tests__/teardown-invariant.test.d.ts +2 -0
  210. package/dist/team/__tests__/teardown-invariant.test.d.ts.map +1 -0
  211. package/dist/team/__tests__/teardown-invariant.test.js +176 -0
  212. package/dist/team/__tests__/teardown-invariant.test.js.map +1 -0
  213. package/dist/team/__tests__/worker-commit-cadence.test.d.ts +2 -0
  214. package/dist/team/__tests__/worker-commit-cadence.test.d.ts.map +1 -0
  215. package/dist/team/__tests__/worker-commit-cadence.test.js +315 -0
  216. package/dist/team/__tests__/worker-commit-cadence.test.js.map +1 -0
  217. package/dist/team/__tests__/worktree-runtime-e2e.test.d.ts +2 -0
  218. package/dist/team/__tests__/worktree-runtime-e2e.test.d.ts.map +1 -0
  219. package/dist/team/__tests__/worktree-runtime-e2e.test.js +182 -0
  220. package/dist/team/__tests__/worktree-runtime-e2e.test.js.map +1 -0
  221. package/dist/team/conflict-mailbox.d.ts +49 -0
  222. package/dist/team/conflict-mailbox.d.ts.map +1 -0
  223. package/dist/team/conflict-mailbox.js +98 -0
  224. package/dist/team/conflict-mailbox.js.map +1 -0
  225. package/dist/team/leader-inbox.d.ts +28 -0
  226. package/dist/team/leader-inbox.d.ts.map +1 -0
  227. package/dist/team/leader-inbox.js +68 -0
  228. package/dist/team/leader-inbox.js.map +1 -0
  229. package/dist/team/merge-coordinator.d.ts +6 -0
  230. package/dist/team/merge-coordinator.d.ts.map +1 -1
  231. package/dist/team/merge-coordinator.js +6 -2
  232. package/dist/team/merge-coordinator.js.map +1 -1
  233. package/dist/team/merge-orchestrator.d.ts +41 -0
  234. package/dist/team/merge-orchestrator.d.ts.map +1 -0
  235. package/dist/team/merge-orchestrator.js +736 -0
  236. package/dist/team/merge-orchestrator.js.map +1 -0
  237. package/dist/team/runtime-cli.d.ts +1 -0
  238. package/dist/team/runtime-cli.d.ts.map +1 -1
  239. package/dist/team/runtime-cli.js +15 -2
  240. package/dist/team/runtime-cli.js.map +1 -1
  241. package/dist/team/runtime-flags.d.ts +5 -0
  242. package/dist/team/runtime-flags.d.ts.map +1 -0
  243. package/dist/team/runtime-flags.js +12 -0
  244. package/dist/team/runtime-flags.js.map +1 -0
  245. package/dist/team/runtime-v2.d.ts +7 -1
  246. package/dist/team/runtime-v2.d.ts.map +1 -1
  247. package/dist/team/runtime-v2.js +185 -8
  248. package/dist/team/runtime-v2.js.map +1 -1
  249. package/dist/team/worker-bootstrap.d.ts +4 -0
  250. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  251. package/dist/team/worker-bootstrap.js +10 -1
  252. package/dist/team/worker-bootstrap.js.map +1 -1
  253. package/dist/team/worker-commit-cadence.d.ts +70 -0
  254. package/dist/team/worker-commit-cadence.d.ts.map +1 -0
  255. package/dist/team/worker-commit-cadence.js +271 -0
  256. package/dist/team/worker-commit-cadence.js.map +1 -0
  257. package/dist/tools/__tests__/memory-tools.test.js +4 -4
  258. package/dist/tools/__tests__/memory-tools.test.js.map +1 -1
  259. package/dist/tools/__tests__/state-tools.test.js +13 -1
  260. package/dist/tools/__tests__/state-tools.test.js.map +1 -1
  261. package/docs/GETTING-STARTED.md +415 -0
  262. package/docs/MIGRATION.md +66 -66
  263. package/docs/REFERENCE.md +21 -4
  264. package/docs/company-context-interface.md +5 -16
  265. package/docs/copilot-instructions.md +1 -1
  266. package/package.json +1 -1
  267. package/scripts/lib/hud-cache-wrapper.sh +122 -0
  268. package/scripts/plugin-setup.mjs +10 -1
  269. package/scripts/post-tool-verifier.mjs +118 -5
  270. package/scripts/pre-tool-enforcer.mjs +54 -6
  271. package/scripts/session-start.mjs +171 -3
  272. package/skills/AGENTS.md +22 -91
  273. package/skills/ai-slop-cleaner/SKILL.md +118 -120
  274. package/skills/ask/SKILL.md +6 -8
  275. package/skills/autopilot/SKILL.md +1 -28
  276. package/skills/autoresearch/SKILL.md +5 -5
  277. package/skills/cancel/SKILL.md +13 -13
  278. package/skills/cccg/SKILL.md +1 -0
  279. package/skills/configure-notifications/SKILL.md +80 -215
  280. package/skills/debug/SKILL.md +3 -2
  281. package/skills/deep-dive/SKILL.md +20 -9
  282. package/skills/deep-interview/SKILL.md +32 -4
  283. package/skills/deepinit/SKILL.md +1 -0
  284. package/skills/external-context/SKILL.md +2 -1
  285. package/skills/hud/SKILL.md +22 -52
  286. package/skills/learner/SKILL.md +90 -57
  287. package/skills/mcp-setup/SKILL.md +45 -60
  288. package/skills/omc-doctor/SKILL.md +1 -1
  289. package/skills/omc-reference/SKILL.md +6 -6
  290. package/skills/omc-setup/SKILL.md +30 -74
  291. package/skills/omc-setup/phases/01-install-claude-md.md +97 -0
  292. package/skills/omc-setup/phases/02-configure.md +25 -40
  293. package/skills/omc-setup/phases/03-integrations.md +100 -69
  294. package/skills/omc-setup/phases/04-welcome.md +102 -44
  295. package/skills/omc-teams/SKILL.md +63 -26
  296. package/skills/plan/SKILL.md +11 -9
  297. package/skills/ralph/SKILL.md +42 -41
  298. package/skills/ralplan/SKILL.md +3 -10
  299. package/skills/release/SKILL.md +168 -57
  300. package/skills/remember/SKILL.md +2 -1
  301. package/skills/sciomc/SKILL.md +12 -11
  302. package/skills/self-improve/SKILL.md +4 -4
  303. package/skills/setup/SKILL.md +1 -0
  304. package/skills/skill/SKILL.md +21 -20
  305. package/skills/skillify/SKILL.md +34 -3
  306. package/skills/team/SKILL.md +62 -44
  307. package/skills/ultraqa/SKILL.md +1 -2
  308. package/skills/ultrawork/SKILL.md +28 -21
  309. package/skills/verify/SKILL.md +2 -1
  310. package/skills/writer-memory/SKILL.md +2 -1
  311. package/templates/hooks/pre-tool-use.mjs +144 -45
package/bridge/team.js CHANGED
@@ -3865,6 +3865,17 @@ async function composeInitialInbox(teamName, workerName, content, cwd, cliOutput
3865
3865
  ${cliOutputContract}` : content;
3866
3866
  await writeFile3(inboxPath, finalContent, "utf-8");
3867
3867
  }
3868
+ async function appendToInbox(teamName, workerName, message, cwd) {
3869
+ const safeTeam = sanitizeName(teamName);
3870
+ const safeWorker = sanitizeName(workerName);
3871
+ const inboxPath = join14(cwd, `.omcp/state/team/${safeTeam}/workers/${safeWorker}/inbox.md`);
3872
+ validateResolvedPath(inboxPath, cwd);
3873
+ await mkdir3(dirname7(inboxPath), { recursive: true });
3874
+ await appendFile2(inboxPath, `
3875
+
3876
+ ---
3877
+ ${message}`, "utf-8");
3878
+ }
3868
3879
  async function ensureWorkerStateDir(teamName, workerName, cwd) {
3869
3880
  const workerDir = join14(cwd, `.omcp/state/team/${teamName}/workers/${workerName}`);
3870
3881
  await mkdir3(workerDir, { recursive: true });
@@ -3887,6 +3898,8 @@ var init_worker_bootstrap = __esm({
3887
3898
  "use strict";
3888
3899
  init_prompt_helpers();
3889
3900
  init_omc_cli_rendering();
3901
+ init_tmux_session();
3902
+ init_fs_utils();
3890
3903
  init_model_contract();
3891
3904
  DEFAULT_INSTRUCTION_STATE_ROOT = ".omcp/state";
3892
3905
  }
@@ -4523,6 +4536,9 @@ function removeWorkerWorktree(teamName, workerName, repoRoot) {
4523
4536
  forgetMetadataUnlocked(repoRoot, teamName, workerName);
4524
4537
  });
4525
4538
  }
4539
+ function listTeamWorktrees(teamName, repoRoot) {
4540
+ return readMetadata(repoRoot, teamName);
4541
+ }
4526
4542
  function inspectTeamWorktreeCleanupSafety(teamName, repoRoot) {
4527
4543
  const metadata = readMetadataResult(repoRoot, teamName);
4528
4544
  const entries = metadata.entries;
@@ -4650,10 +4666,10 @@ async function readJsonSafe3(filePath) {
4650
4666
  }
4651
4667
  }
4652
4668
  async function writeAtomic2(filePath, data) {
4653
- const { writeFile: writeFile6 } = await import("fs/promises");
4669
+ const { writeFile: writeFile8 } = await import("fs/promises");
4654
4670
  await mkdir5(dirname10(filePath), { recursive: true });
4655
4671
  const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
4656
- await writeFile6(tmpPath, data, "utf-8");
4672
+ await writeFile8(tmpPath, data, "utf-8");
4657
4673
  const { rename: rename3 } = await import("fs/promises");
4658
4674
  await rename3(tmpPath, filePath);
4659
4675
  }
@@ -5385,6 +5401,991 @@ var init_cli_worker_contract = __esm({
5385
5401
  }
5386
5402
  });
5387
5403
 
5404
+ // src/team/runtime-flags.ts
5405
+ function isRuntimeV2Enabled(env = process.env) {
5406
+ const raw = env.OMC_RUNTIME_V2;
5407
+ if (!raw) return true;
5408
+ const normalized = raw.trim().toLowerCase();
5409
+ return !["0", "false", "no", "off"].includes(normalized);
5410
+ }
5411
+ var init_runtime_flags = __esm({
5412
+ "src/team/runtime-flags.ts"() {
5413
+ "use strict";
5414
+ }
5415
+ });
5416
+
5417
+ // src/team/merge-coordinator.ts
5418
+ import { execFileSync as execFileSync3 } from "node:child_process";
5419
+ function validateBranchName(branch) {
5420
+ if (!BRANCH_NAME_RE.test(branch)) {
5421
+ throw new Error(`Invalid branch name: "${branch}" \u2014 must match ${BRANCH_NAME_RE}`);
5422
+ }
5423
+ }
5424
+ function checkMergeConflicts(workerBranch, baseBranch, repoRoot) {
5425
+ validateBranchName(workerBranch);
5426
+ validateBranchName(baseBranch);
5427
+ try {
5428
+ execFileSync3(
5429
+ "git",
5430
+ ["merge-tree", "--write-tree", baseBranch, workerBranch],
5431
+ { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5432
+ );
5433
+ return [];
5434
+ } catch (err) {
5435
+ const error = err;
5436
+ if (error.status === 1 && typeof error.stdout === "string") {
5437
+ const lines = error.stdout.split("\n");
5438
+ const conflicts = [];
5439
+ for (const line of lines) {
5440
+ const match = line.match(/^CONFLICT\s.*?:\s+.*?\s+in\s+(.+)$/);
5441
+ if (match) {
5442
+ conflicts.push(match[1].trim());
5443
+ }
5444
+ }
5445
+ return conflicts.length > 0 ? conflicts : ["(merge-tree reported conflicts)"];
5446
+ }
5447
+ }
5448
+ const mergeBase = execFileSync3(
5449
+ "git",
5450
+ ["merge-base", baseBranch, workerBranch],
5451
+ { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5452
+ ).trim();
5453
+ const baseDiff = execFileSync3(
5454
+ "git",
5455
+ ["diff", "--name-only", mergeBase, baseBranch],
5456
+ { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5457
+ ).trim();
5458
+ const workerDiff = execFileSync3(
5459
+ "git",
5460
+ ["diff", "--name-only", mergeBase, workerBranch],
5461
+ { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
5462
+ ).trim();
5463
+ if (!baseDiff || !workerDiff) {
5464
+ return [];
5465
+ }
5466
+ const baseFiles = new Set(baseDiff.split("\n").filter((f) => f));
5467
+ const workerFiles = workerDiff.split("\n").filter((f) => f);
5468
+ return workerFiles.filter((f) => baseFiles.has(f));
5469
+ }
5470
+ function mergeWorkerBranch(workerBranch, baseBranch, repoRoot) {
5471
+ validateBranchName(workerBranch);
5472
+ validateBranchName(baseBranch);
5473
+ const workerName = workerBranch.split("/").pop() || workerBranch;
5474
+ try {
5475
+ try {
5476
+ execFileSync3("git", ["diff-index", "--quiet", "HEAD", "--"], {
5477
+ cwd: repoRoot,
5478
+ stdio: "pipe"
5479
+ });
5480
+ } catch {
5481
+ throw new Error("Working tree has uncommitted changes \u2014 commit or stash before merging");
5482
+ }
5483
+ execFileSync3("git", ["checkout", baseBranch], {
5484
+ cwd: repoRoot,
5485
+ stdio: "pipe"
5486
+ });
5487
+ execFileSync3("git", ["merge", "--no-ff", "-m", `Merge ${workerBranch} into ${baseBranch}`, workerBranch], {
5488
+ cwd: repoRoot,
5489
+ stdio: "pipe"
5490
+ });
5491
+ const mergeCommit = execFileSync3("git", ["rev-parse", "HEAD"], {
5492
+ cwd: repoRoot,
5493
+ encoding: "utf-8",
5494
+ stdio: "pipe"
5495
+ }).trim();
5496
+ return {
5497
+ workerName,
5498
+ branch: workerBranch,
5499
+ success: true,
5500
+ conflicts: [],
5501
+ mergeCommit
5502
+ };
5503
+ } catch (_err) {
5504
+ try {
5505
+ execFileSync3("git", ["merge", "--abort"], { cwd: repoRoot, stdio: "pipe" });
5506
+ } catch {
5507
+ }
5508
+ const conflicts = checkMergeConflicts(workerBranch, baseBranch, repoRoot);
5509
+ return {
5510
+ workerName,
5511
+ branch: workerBranch,
5512
+ success: false,
5513
+ conflicts
5514
+ };
5515
+ }
5516
+ }
5517
+ var BRANCH_NAME_RE;
5518
+ var init_merge_coordinator = __esm({
5519
+ "src/team/merge-coordinator.ts"() {
5520
+ "use strict";
5521
+ init_git_worktree();
5522
+ BRANCH_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9/_.-]*$/;
5523
+ }
5524
+ });
5525
+
5526
+ // src/team/leader-inbox.ts
5527
+ import { appendFile as appendFile4, mkdir as mkdir7, writeFile as writeFile5 } from "fs/promises";
5528
+ import { existsSync as existsSync16 } from "fs";
5529
+ import { dirname as dirname12, join as join19 } from "path";
5530
+ function leaderInboxPath(teamName, cwd) {
5531
+ const safe = sanitizeName(teamName);
5532
+ return join19(cwd, `.omcp/state/team/${safe}/leader/inbox.md`);
5533
+ }
5534
+ async function ensureLeaderInbox(teamName, cwd) {
5535
+ const inboxPath = leaderInboxPath(teamName, cwd);
5536
+ validateResolvedPath(inboxPath, cwd);
5537
+ await mkdir7(dirname12(inboxPath), { recursive: true });
5538
+ if (!existsSync16(inboxPath)) {
5539
+ await writeFile5(inboxPath, LEADER_INBOX_HEADER, "utf-8");
5540
+ }
5541
+ return inboxPath;
5542
+ }
5543
+ async function appendToLeaderInbox(teamName, message, cwd) {
5544
+ const inboxPath = leaderInboxPath(teamName, cwd);
5545
+ validateResolvedPath(inboxPath, cwd);
5546
+ await mkdir7(dirname12(inboxPath), { recursive: true });
5547
+ await appendFile4(inboxPath, `
5548
+
5549
+ ---
5550
+ ${message}`, "utf-8");
5551
+ }
5552
+ function extendLeaderBootstrapPrompt(teamName) {
5553
+ const safe = sanitizeName(teamName);
5554
+ const path4 = `.omcp/state/team/${safe}/leader/inbox.md`;
5555
+ return `Runtime notifications appear at ${path4} \u2014 check this file periodically and after long-running operations.`;
5556
+ }
5557
+ var LEADER_INBOX_HEADER;
5558
+ var init_leader_inbox = __esm({
5559
+ "src/team/leader-inbox.ts"() {
5560
+ "use strict";
5561
+ init_tmux_session();
5562
+ init_fs_utils();
5563
+ LEADER_INBOX_HEADER = `# Leader Inbox
5564
+
5565
+ Runtime notifications (merge conflicts, rebase events, etc.) appear here.
5566
+ Check this file periodically and after long-running operations.
5567
+
5568
+ ---
5569
+ `;
5570
+ }
5571
+ });
5572
+
5573
+ // src/team/conflict-mailbox.ts
5574
+ function sanitizeConflictPath(path4) {
5575
+ return path4.replace(/[`\r\n]/g, "?");
5576
+ }
5577
+ function formatMergeConflictForLeader(args) {
5578
+ const { workerName, workerBranch, leaderBranch, conflictingFiles, mergeBaseSha, observedAt } = args;
5579
+ const ts = new Date(observedAt).toISOString();
5580
+ const safeFiles = conflictingFiles.map(sanitizeConflictPath);
5581
+ const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
5582
+ return `### Merge conflict: ${workerName} \u2192 ${leaderBranch}
5583
+
5584
+ **Worker branch:** \`${workerBranch}\`
5585
+ **Leader branch:** \`${leaderBranch}\`
5586
+ **Merge base:** \`${mergeBaseSha}\`
5587
+ **Observed at:** ${ts}
5588
+
5589
+ **Conflicting files:**
5590
+ ${fileList}
5591
+
5592
+ **Leader: choose strategy.** To resolve, run:
5593
+
5594
+ \`\`\`sh
5595
+ git checkout ${leaderBranch} && git merge --no-ff ${workerBranch}
5596
+ # resolve conflicts in the files listed above
5597
+ git add ${safeFiles.join(" ")}
5598
+ git commit
5599
+ \`\`\`
5600
+
5601
+ Or abort with \`git merge --abort\` to defer resolution.`;
5602
+ }
5603
+ function formatRebaseConflictForWorker(args) {
5604
+ const { workerName, workerBranch, leaderBranch, conflictingFiles, baseSha, worktreePath, observedAt } = args;
5605
+ const ts = new Date(observedAt).toISOString();
5606
+ const safeFiles = conflictingFiles.map(sanitizeConflictPath);
5607
+ const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
5608
+ return `### Rebase conflict: ${workerName} onto ${leaderBranch}
5609
+
5610
+ **Worker branch:** \`${workerBranch}\`
5611
+ **Base branch:** \`${leaderBranch}\`
5612
+ **Base SHA:** \`${baseSha}\`
5613
+ **Worktree:** \`${worktreePath}\`
5614
+ **Observed at:** ${ts}
5615
+
5616
+ **Conflicting files:**
5617
+ ${fileList}
5618
+
5619
+ Resolve conflicts in your own pane, then \`git add <files>\` and \`git rebase --continue\`.
5620
+ Cadence stays paused until \`.git/rebase-merge\` is gone.
5621
+
5622
+ Or run \`git rebase --abort\` to bail and return to the pre-rebase state.`;
5623
+ }
5624
+ var init_conflict_mailbox = __esm({
5625
+ "src/team/conflict-mailbox.ts"() {
5626
+ "use strict";
5627
+ init_worker_bootstrap();
5628
+ init_leader_inbox();
5629
+ }
5630
+ });
5631
+
5632
+ // src/team/worker-commit-cadence.ts
5633
+ import { existsSync as existsSync17, watch as fsWatch } from "fs";
5634
+ import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir8, unlink as unlink2 } from "fs/promises";
5635
+ import { join as join20, dirname as dirname13 } from "path";
5636
+ import { exec as exec2 } from "child_process";
5637
+ function assertSafeWorkerName(workerName) {
5638
+ if (!WORKER_NAME_RE.test(workerName)) {
5639
+ throw new Error(
5640
+ `Invalid worker name for shell hook: "${workerName}" \u2014 must match ${WORKER_NAME_RE}`
5641
+ );
5642
+ }
5643
+ }
5644
+ function buildHookCommand(workerName) {
5645
+ assertSafeWorkerName(workerName);
5646
+ return `sh -c 'rebase_dir=$(git rev-parse --git-path rebase-merge 2>/dev/null || printf %s .git/rebase-merge); merge_head=$(git rev-parse --git-path MERGE_HEAD 2>/dev/null || printf %s .git/MERGE_HEAD); if [ -d "$rebase_dir" ] || [ -f "$merge_head" ] || [ -e ${SENTINEL_FILENAME} ]; then exit 0; fi; git add -A && (git diff --cached --quiet || git commit -m "auto-commit by worker ${workerName} at $(date -Iseconds)")'`;
5647
+ }
5648
+ async function mergeSettingsWithHook(settingsPath, hookCommand) {
5649
+ let existing = { hooks: { PostToolUse: [] } };
5650
+ try {
5651
+ const raw = await readFile9(settingsPath, "utf-8");
5652
+ const parsed = JSON.parse(raw);
5653
+ existing = {
5654
+ ...parsed,
5655
+ hooks: {
5656
+ PostToolUse: [],
5657
+ ...parsed.hooks ?? {}
5658
+ }
5659
+ };
5660
+ } catch {
5661
+ }
5662
+ const filteredHooks = (existing.hooks.PostToolUse ?? []).filter(
5663
+ (h) => h.matcher !== HOOK_MATCHER
5664
+ );
5665
+ const newEntry = {
5666
+ matcher: HOOK_MATCHER,
5667
+ hooks: [{ type: "command", command: hookCommand }]
5668
+ };
5669
+ return {
5670
+ ...existing,
5671
+ hooks: {
5672
+ ...existing.hooks,
5673
+ PostToolUse: [...filteredHooks, newEntry]
5674
+ }
5675
+ };
5676
+ }
5677
+ async function installPostToolUseHook(worktreePath, workerName) {
5678
+ assertSafeWorkerName(workerName);
5679
+ if (isHookPaused(worktreePath)) {
5680
+ return;
5681
+ }
5682
+ const claudeDir = join20(worktreePath, ".claude");
5683
+ await mkdir8(claudeDir, { recursive: true });
5684
+ const settingsPath = join20(claudeDir, "settings.json");
5685
+ const hookCommand = buildHookCommand(workerName);
5686
+ const merged = await mergeSettingsWithHook(settingsPath, hookCommand);
5687
+ await writeFile6(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
5688
+ }
5689
+ async function pauseHookViaSentinel(worktreePath) {
5690
+ const sentinelPath = join20(worktreePath, SENTINEL_FILENAME);
5691
+ await mkdir8(dirname13(sentinelPath), { recursive: true });
5692
+ await writeFile6(sentinelPath, "", "utf-8");
5693
+ }
5694
+ async function resumeHookViaSentinel(worktreePath) {
5695
+ const sentinelPath = join20(worktreePath, SENTINEL_FILENAME);
5696
+ try {
5697
+ await unlink2(sentinelPath);
5698
+ } catch {
5699
+ }
5700
+ }
5701
+ function isHookPaused(worktreePath) {
5702
+ return existsSync17(join20(worktreePath, SENTINEL_FILENAME));
5703
+ }
5704
+ function startFallbackPoller(worktreePath, workerName, opts) {
5705
+ assertSafeWorkerName(workerName);
5706
+ const debounceMs = opts?.intervalMs ?? DEFAULT_POLL_DEBOUNCE_MS;
5707
+ let debounceTimer = null;
5708
+ let stopped = false;
5709
+ const runAutoCommit = () => {
5710
+ if (stopped) return;
5711
+ if (isHookPaused(worktreePath)) return;
5712
+ const cmd = buildHookCommand(workerName);
5713
+ exec2(cmd, { cwd: worktreePath }, (_err) => {
5714
+ });
5715
+ };
5716
+ const scheduleDebounce = () => {
5717
+ if (stopped) return;
5718
+ if (debounceTimer !== null) {
5719
+ clearTimeout(debounceTimer);
5720
+ }
5721
+ debounceTimer = setTimeout(() => {
5722
+ debounceTimer = null;
5723
+ runAutoCommit();
5724
+ }, debounceMs);
5725
+ };
5726
+ const watcher = fsWatch(worktreePath, { recursive: true }, (eventType, filename) => {
5727
+ if (stopped) return;
5728
+ if (filename && (filename.startsWith(".git") || filename.startsWith(".git/"))) return;
5729
+ scheduleDebounce();
5730
+ });
5731
+ return {
5732
+ stop() {
5733
+ stopped = true;
5734
+ if (debounceTimer !== null) {
5735
+ clearTimeout(debounceTimer);
5736
+ debounceTimer = null;
5737
+ }
5738
+ watcher.close();
5739
+ }
5740
+ };
5741
+ }
5742
+ async function installCommitCadence(ctx) {
5743
+ if (!ctx.enabled) {
5744
+ return { method: "none" };
5745
+ }
5746
+ if (ctx.agentType === "claude") {
5747
+ await installPostToolUseHook(ctx.worktreePath, ctx.workerName);
5748
+ return { method: "hook" };
5749
+ }
5750
+ return { method: "fallback-poll" };
5751
+ }
5752
+ async function uninstallCommitCadence(ctx) {
5753
+ if (ctx.agentType !== "claude") return;
5754
+ const settingsPath = join20(ctx.worktreePath, ".claude", "settings.json");
5755
+ try {
5756
+ const raw = await readFile9(settingsPath, "utf-8");
5757
+ const parsed = JSON.parse(raw);
5758
+ const filtered = (parsed.hooks?.PostToolUse ?? []).filter(
5759
+ (h) => h.matcher !== HOOK_MATCHER
5760
+ );
5761
+ const updated = {
5762
+ ...parsed,
5763
+ hooks: {
5764
+ ...parsed.hooks,
5765
+ PostToolUse: filtered
5766
+ }
5767
+ };
5768
+ await writeFile6(settingsPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
5769
+ } catch {
5770
+ }
5771
+ }
5772
+ var SENTINEL_FILENAME, HOOK_MATCHER, DEFAULT_POLL_DEBOUNCE_MS, WORKER_NAME_RE;
5773
+ var init_worker_commit_cadence = __esm({
5774
+ "src/team/worker-commit-cadence.ts"() {
5775
+ "use strict";
5776
+ SENTINEL_FILENAME = ".hook-paused";
5777
+ HOOK_MATCHER = "Write|Edit|MultiEdit";
5778
+ DEFAULT_POLL_DEBOUNCE_MS = 3e3;
5779
+ WORKER_NAME_RE = /^[A-Za-z0-9_-]{1,50}$/;
5780
+ }
5781
+ });
5782
+
5783
+ // src/team/merge-orchestrator.ts
5784
+ import { execFileSync as execFileSync4 } from "node:child_process";
5785
+ import { existsSync as existsSync18 } from "node:fs";
5786
+ import { mkdir as mkdir9, appendFile as appendFile5 } from "node:fs/promises";
5787
+ import { dirname as dirname14, join as join21 } from "node:path";
5788
+ function mergerWorktreePathFor(repoRoot, teamName) {
5789
+ return join21(repoRoot, ".omcp", "team", sanitizeName(teamName), "merger");
5790
+ }
5791
+ function persistedStatePath(repoRoot, teamName) {
5792
+ return join21(
5793
+ repoRoot,
5794
+ ".omcp",
5795
+ "state",
5796
+ "team",
5797
+ sanitizeName(teamName),
5798
+ "auto-merge-state.json"
5799
+ );
5800
+ }
5801
+ function teardownAuditPath(repoRoot, teamName) {
5802
+ return join21(
5803
+ repoRoot,
5804
+ ".omcp",
5805
+ "state",
5806
+ "team",
5807
+ sanitizeName(teamName),
5808
+ "teardown-audit.jsonl"
5809
+ );
5810
+ }
5811
+ function orchestratorEventLogPath(repoRoot, teamName) {
5812
+ return join21(
5813
+ repoRoot,
5814
+ ".omcp",
5815
+ "state",
5816
+ "team",
5817
+ sanitizeName(teamName),
5818
+ "orchestrator-events.jsonl"
5819
+ );
5820
+ }
5821
+ function assertLeaderBranchAllowed(leaderBranch) {
5822
+ const stripped = leaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
5823
+ if (stripped === "main" || stripped === "master") {
5824
+ throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
5825
+ }
5826
+ }
5827
+ function assertRuntimeV2Gate() {
5828
+ if (!isRuntimeV2Enabled()) {
5829
+ throw new Error("auto-merge requires runtime v2 (OMC_RUNTIME_V2 is explicitly disabled).");
5830
+ }
5831
+ }
5832
+ async function appendEvent(repoRoot, teamName, event) {
5833
+ const path4 = orchestratorEventLogPath(repoRoot, teamName);
5834
+ await mkdir9(dirname14(path4), { recursive: true });
5835
+ const full = {
5836
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5837
+ team: teamName,
5838
+ ...event
5839
+ };
5840
+ await appendFile5(path4, `${JSON.stringify(full)}
5841
+ `, "utf-8");
5842
+ }
5843
+ function createMutex() {
5844
+ let lock = Promise.resolve();
5845
+ return (fn) => {
5846
+ const next = lock.then(fn, fn);
5847
+ lock = next.catch(() => void 0);
5848
+ return next;
5849
+ };
5850
+ }
5851
+ function gitRevParseHead(repoRoot, branch) {
5852
+ return execFileSync4("git", ["rev-parse", `refs/heads/${branch}`], {
5853
+ cwd: repoRoot,
5854
+ encoding: "utf-8",
5855
+ stdio: "pipe"
5856
+ }).trim();
5857
+ }
5858
+ function gitPath(worktreePath, gitPathName) {
5859
+ try {
5860
+ const resolved = execFileSync4("git", ["rev-parse", "--git-path", gitPathName], {
5861
+ cwd: worktreePath,
5862
+ encoding: "utf-8",
5863
+ stdio: "pipe"
5864
+ }).trim();
5865
+ if (resolved) return resolved;
5866
+ } catch {
5867
+ }
5868
+ return join21(worktreePath, ".git", gitPathName);
5869
+ }
5870
+ function isRebaseInProgress(worktreePath) {
5871
+ return existsSync18(gitPath(worktreePath, "rebase-merge"));
5872
+ }
5873
+ function isWorktreeRegistered(repoRoot, wtPath) {
5874
+ try {
5875
+ const out = execFileSync4("git", ["worktree", "list", "--porcelain"], {
5876
+ cwd: repoRoot,
5877
+ encoding: "utf-8",
5878
+ stdio: "pipe"
5879
+ });
5880
+ for (const line of out.split("\n")) {
5881
+ if (line.startsWith("worktree ")) {
5882
+ if (line.slice("worktree ".length).trim() === wtPath) return true;
5883
+ }
5884
+ }
5885
+ } catch {
5886
+ }
5887
+ return false;
5888
+ }
5889
+ function ensureMergerWorktree(repoRoot, mergerPath, leaderBranch) {
5890
+ ensureDirWithMode(dirname14(mergerPath));
5891
+ if (existsSync18(mergerPath) && isWorktreeRegistered(repoRoot, mergerPath)) {
5892
+ return;
5893
+ }
5894
+ execFileSync4("git", ["worktree", "add", "--force", mergerPath, leaderBranch], {
5895
+ cwd: repoRoot,
5896
+ stdio: "pipe"
5897
+ });
5898
+ }
5899
+ function preflightMergerWorktree(mergerPath, leaderBranch) {
5900
+ try {
5901
+ execFileSync4("git", ["fetch", "--no-tags", "origin", leaderBranch], {
5902
+ cwd: mergerPath,
5903
+ stdio: "pipe"
5904
+ });
5905
+ } catch {
5906
+ }
5907
+ execFileSync4("git", ["reset", "--hard", leaderBranch], {
5908
+ cwd: mergerPath,
5909
+ stdio: "pipe"
5910
+ });
5911
+ }
5912
+ function parseUUFiles(porcelainOutput) {
5913
+ const files = [];
5914
+ for (const line of porcelainOutput.split("\n")) {
5915
+ if (line.startsWith("UU ")) {
5916
+ files.push(line.slice(3).trim());
5917
+ } else if (line.startsWith("AA ") || line.startsWith("DD ")) {
5918
+ files.push(line.slice(3).trim());
5919
+ }
5920
+ }
5921
+ return files;
5922
+ }
5923
+ async function startMergeOrchestrator(config) {
5924
+ assertRuntimeV2Gate();
5925
+ assertLeaderBranchAllowed(config.leaderBranch);
5926
+ validateBranchName(config.leaderBranch);
5927
+ const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
5928
+ const drainTimeoutMs = config.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;
5929
+ const mergerPath = mergerWorktreePathFor(config.repoRoot, config.teamName);
5930
+ validateResolvedPath(mergerPath, config.repoRoot);
5931
+ ensureMergerWorktree(config.repoRoot, mergerPath, config.leaderBranch);
5932
+ await ensureLeaderInbox(config.teamName, config.cwd);
5933
+ const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
5934
+ let persisted = { lastShas: {} };
5935
+ if (existsSync18(persistedPath)) {
5936
+ try {
5937
+ const { readFileSync: readFileSync12 } = await import("node:fs");
5938
+ persisted = JSON.parse(readFileSync12(persistedPath, "utf-8"));
5939
+ } catch {
5940
+ persisted = { lastShas: {} };
5941
+ }
5942
+ }
5943
+ const workers = /* @__PURE__ */ new Map();
5944
+ const pausedWorkers = /* @__PURE__ */ new Set();
5945
+ const mutex = createMutex();
5946
+ let stopped = false;
5947
+ function persistState() {
5948
+ const payload = {
5949
+ lastShas: Object.fromEntries(
5950
+ Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
5951
+ )
5952
+ };
5953
+ atomicWriteJson(persistedPath, payload);
5954
+ }
5955
+ async function fanOutRebase(triggeringWorker) {
5956
+ for (const other of workers.values()) {
5957
+ if (other.workerName === triggeringWorker) continue;
5958
+ const wtPath = other.workerWorktreePath;
5959
+ if (isRebaseInProgress(wtPath)) {
5960
+ await appendEvent(config.repoRoot, config.teamName, {
5961
+ type: "rebase_skipped_in_progress",
5962
+ worker: other.workerName,
5963
+ reason: "rebase-already-in-progress"
5964
+ });
5965
+ continue;
5966
+ }
5967
+ await appendEvent(config.repoRoot, config.teamName, {
5968
+ type: "rebase_triggered",
5969
+ worker: other.workerName
5970
+ });
5971
+ await pauseHookViaSentinel(wtPath);
5972
+ pausedWorkers.add(other.workerName);
5973
+ try {
5974
+ execFileSync4("git", ["fetch", "--no-tags", "origin", config.leaderBranch], {
5975
+ cwd: wtPath,
5976
+ stdio: "pipe"
5977
+ });
5978
+ } catch {
5979
+ }
5980
+ try {
5981
+ execFileSync4("git", ["rebase", config.leaderBranch], {
5982
+ cwd: wtPath,
5983
+ stdio: "pipe"
5984
+ });
5985
+ await resumeHookViaSentinel(wtPath);
5986
+ pausedWorkers.delete(other.workerName);
5987
+ await appendEvent(config.repoRoot, config.teamName, {
5988
+ type: "rebase_succeeded",
5989
+ worker: other.workerName
5990
+ });
5991
+ } catch {
5992
+ let conflictingFiles = [];
5993
+ try {
5994
+ const status = execFileSync4("git", ["status", "--porcelain"], {
5995
+ cwd: wtPath,
5996
+ encoding: "utf-8",
5997
+ stdio: "pipe"
5998
+ });
5999
+ conflictingFiles = parseUUFiles(status);
6000
+ } catch {
6001
+ conflictingFiles = ["(rebase status unavailable)"];
6002
+ }
6003
+ const baseSha = (() => {
6004
+ try {
6005
+ return execFileSync4("git", ["rev-parse", `refs/heads/${config.leaderBranch}`], {
6006
+ cwd: config.repoRoot,
6007
+ encoding: "utf-8",
6008
+ stdio: "pipe"
6009
+ }).trim();
6010
+ } catch {
6011
+ return "unknown";
6012
+ }
6013
+ })();
6014
+ const message = formatRebaseConflictForWorker({
6015
+ workerName: other.workerName,
6016
+ workerBranch: other.workerBranch,
6017
+ leaderBranch: config.leaderBranch,
6018
+ conflictingFiles,
6019
+ baseSha,
6020
+ worktreePath: wtPath,
6021
+ observedAt: Date.now()
6022
+ });
6023
+ try {
6024
+ await appendToInbox(config.teamName, other.workerName, message, config.cwd);
6025
+ } catch {
6026
+ }
6027
+ await appendEvent(config.repoRoot, config.teamName, {
6028
+ type: "rebase_conflict",
6029
+ worker: other.workerName,
6030
+ data: { conflictingFiles }
6031
+ });
6032
+ }
6033
+ }
6034
+ }
6035
+ async function attemptMergeForWorker(entry) {
6036
+ await mutex(async () => {
6037
+ const targetSha = entry.lastObservedSha;
6038
+ await appendEvent(config.repoRoot, config.teamName, {
6039
+ type: "merge_attempted",
6040
+ worker: entry.workerName,
6041
+ data: { targetSha }
6042
+ });
6043
+ try {
6044
+ preflightMergerWorktree(mergerPath, config.leaderBranch);
6045
+ } catch (err) {
6046
+ const reason = err instanceof Error ? err.message : String(err);
6047
+ entry.consecutiveFailures += 1;
6048
+ await appendEvent(config.repoRoot, config.teamName, {
6049
+ type: "merge_conflict",
6050
+ worker: entry.workerName,
6051
+ reason: `preflight_failed:${reason}`
6052
+ });
6053
+ return;
6054
+ }
6055
+ const conflicts = checkMergeConflicts(
6056
+ entry.workerBranch,
6057
+ config.leaderBranch,
6058
+ mergerPath
6059
+ );
6060
+ if (conflicts.length > 0) {
6061
+ let mergeBaseSha = "unknown";
6062
+ try {
6063
+ mergeBaseSha = execFileSync4(
6064
+ "git",
6065
+ ["merge-base", config.leaderBranch, entry.workerBranch],
6066
+ { cwd: mergerPath, encoding: "utf-8", stdio: "pipe" }
6067
+ ).trim();
6068
+ } catch {
6069
+ }
6070
+ const message = formatMergeConflictForLeader({
6071
+ workerName: entry.workerName,
6072
+ workerBranch: entry.workerBranch,
6073
+ leaderBranch: config.leaderBranch,
6074
+ conflictingFiles: conflicts,
6075
+ mergeBaseSha,
6076
+ observedAt: Date.now()
6077
+ });
6078
+ try {
6079
+ await appendToLeaderInbox(config.teamName, message, config.cwd);
6080
+ } catch {
6081
+ }
6082
+ await appendEvent(config.repoRoot, config.teamName, {
6083
+ type: "merge_conflict",
6084
+ worker: entry.workerName,
6085
+ data: { conflictingFiles: conflicts, mergeBaseSha }
6086
+ });
6087
+ entry.consecutiveFailures += 1;
6088
+ return;
6089
+ }
6090
+ const result = mergeWorkerBranch(
6091
+ entry.workerBranch,
6092
+ config.leaderBranch,
6093
+ mergerPath
6094
+ );
6095
+ if (!result.success) {
6096
+ const message = formatMergeConflictForLeader({
6097
+ workerName: entry.workerName,
6098
+ workerBranch: entry.workerBranch,
6099
+ leaderBranch: config.leaderBranch,
6100
+ conflictingFiles: result.conflicts.length > 0 ? result.conflicts : ["(merge failed after clean check)"],
6101
+ mergeBaseSha: "unknown",
6102
+ observedAt: Date.now()
6103
+ });
6104
+ try {
6105
+ await appendToLeaderInbox(config.teamName, message, config.cwd);
6106
+ } catch {
6107
+ }
6108
+ await appendEvent(config.repoRoot, config.teamName, {
6109
+ type: "merge_conflict",
6110
+ worker: entry.workerName,
6111
+ data: { conflictingFiles: result.conflicts }
6112
+ });
6113
+ entry.consecutiveFailures += 1;
6114
+ return;
6115
+ }
6116
+ entry.lastMergedSha = targetSha;
6117
+ entry.consecutiveFailures = 0;
6118
+ await appendEvent(config.repoRoot, config.teamName, {
6119
+ type: "merge_succeeded",
6120
+ worker: entry.workerName,
6121
+ data: { mergeCommit: result.mergeCommit, targetSha }
6122
+ });
6123
+ if (stopped) return;
6124
+ await fanOutRebase(entry.workerName);
6125
+ });
6126
+ }
6127
+ async function runPollOnce() {
6128
+ if (stopped) return;
6129
+ for (const entry of workers.values()) {
6130
+ const skipModulo = Math.min(30, Math.pow(2, entry.consecutiveFailures));
6131
+ if (skipModulo > 1 && pollTickCount % skipModulo !== 0) {
6132
+ continue;
6133
+ }
6134
+ if (pausedWorkers.has(entry.workerName)) {
6135
+ if (!isRebaseInProgress(entry.workerWorktreePath)) {
6136
+ await handleRebaseResolution(entry);
6137
+ } else {
6138
+ continue;
6139
+ }
6140
+ }
6141
+ let currentSha;
6142
+ try {
6143
+ currentSha = gitRevParseHead(config.repoRoot, entry.workerBranch);
6144
+ } catch (err) {
6145
+ entry.consecutiveFailures += 1;
6146
+ const reason = err instanceof Error ? err.message : String(err);
6147
+ await appendEvent(config.repoRoot, config.teamName, {
6148
+ type: "commit_observed",
6149
+ worker: entry.workerName,
6150
+ reason: `rev_parse_failed:${reason}`
6151
+ });
6152
+ continue;
6153
+ }
6154
+ if (currentSha && currentSha !== entry.lastObservedSha) {
6155
+ entry.lastObservedSha = currentSha;
6156
+ try {
6157
+ persistState();
6158
+ } catch {
6159
+ }
6160
+ await appendEvent(config.repoRoot, config.teamName, {
6161
+ type: "commit_observed",
6162
+ worker: entry.workerName,
6163
+ data: { sha: currentSha }
6164
+ });
6165
+ try {
6166
+ await attemptMergeForWorker(entry);
6167
+ } catch (err) {
6168
+ entry.consecutiveFailures += 1;
6169
+ const reason = err instanceof Error ? err.message : String(err);
6170
+ await appendEvent(config.repoRoot, config.teamName, {
6171
+ type: "merge_conflict",
6172
+ worker: entry.workerName,
6173
+ reason: `merge_threw:${reason}`
6174
+ });
6175
+ }
6176
+ }
6177
+ }
6178
+ }
6179
+ async function handleRebaseResolution(entry) {
6180
+ pausedWorkers.delete(entry.workerName);
6181
+ try {
6182
+ const status = execFileSync4("git", ["status", "--porcelain"], {
6183
+ cwd: entry.workerWorktreePath,
6184
+ encoding: "utf-8",
6185
+ stdio: "pipe"
6186
+ }).trim();
6187
+ if (status.length > 0) {
6188
+ const dirtyFiles = status.split("\n").map((l) => l.trim().replace(/^\S+\s+/, "")).filter((s) => s.length > 0);
6189
+ const audit = `## Auto-commit audit: the following files were modified during rebase pause and will be folded into the next auto-commit:
6190
+ ${dirtyFiles.map((f) => `- \`${f}\``).join("\n")}`;
6191
+ try {
6192
+ await appendToInbox(config.teamName, entry.workerName, audit, config.cwd);
6193
+ } catch {
6194
+ }
6195
+ }
6196
+ } catch {
6197
+ }
6198
+ await resumeHookViaSentinel(entry.workerWorktreePath);
6199
+ await appendEvent(config.repoRoot, config.teamName, {
6200
+ type: "rebase_resolved",
6201
+ worker: entry.workerName
6202
+ });
6203
+ }
6204
+ let pollTickCount = 0;
6205
+ const interval = setInterval(() => {
6206
+ pollTickCount += 1;
6207
+ void runPollOnce().catch(() => {
6208
+ });
6209
+ }, pollIntervalMs);
6210
+ if (typeof interval.unref === "function") interval.unref();
6211
+ return {
6212
+ async registerWorker(workerName) {
6213
+ if (workers.has(workerName)) return;
6214
+ const workerBranch = getBranchName(config.teamName, workerName);
6215
+ validateBranchName(workerBranch);
6216
+ const wtPath = getWorktreePath(config.repoRoot, config.teamName, workerName);
6217
+ let seedSha = persisted.lastShas[workerName] ?? "";
6218
+ if (!seedSha) {
6219
+ try {
6220
+ seedSha = gitRevParseHead(config.repoRoot, workerBranch);
6221
+ } catch {
6222
+ seedSha = "";
6223
+ }
6224
+ }
6225
+ workers.set(workerName, {
6226
+ workerName,
6227
+ workerBranch,
6228
+ workerWorktreePath: wtPath,
6229
+ lastObservedSha: seedSha,
6230
+ lastMergedSha: seedSha,
6231
+ consecutiveFailures: 0
6232
+ });
6233
+ try {
6234
+ persistState();
6235
+ } catch {
6236
+ }
6237
+ },
6238
+ async unregisterWorker(workerName) {
6239
+ workers.delete(workerName);
6240
+ pausedWorkers.delete(workerName);
6241
+ try {
6242
+ persistState();
6243
+ } catch {
6244
+ }
6245
+ },
6246
+ async pollOnce() {
6247
+ await runPollOnce();
6248
+ },
6249
+ async drainAndStop() {
6250
+ stopped = true;
6251
+ clearInterval(interval);
6252
+ const start = Date.now();
6253
+ const unmerged = [];
6254
+ const candidates = Array.from(workers.values()).filter(
6255
+ (w) => w.lastObservedSha && w.lastObservedSha !== w.lastMergedSha
6256
+ );
6257
+ for (const entry of candidates) {
6258
+ const remaining = drainTimeoutMs - (Date.now() - start);
6259
+ if (remaining <= 0) {
6260
+ unmerged.push({ workerName: entry.workerName, reason: "drain-timeout" });
6261
+ continue;
6262
+ }
6263
+ const merged = await Promise.race([
6264
+ (async () => {
6265
+ try {
6266
+ await attemptMergeForWorker(entry);
6267
+ return true;
6268
+ } catch {
6269
+ return false;
6270
+ }
6271
+ })(),
6272
+ new Promise((resolve5) => {
6273
+ const t = setTimeout(() => resolve5(false), remaining);
6274
+ if (typeof t.unref === "function") t.unref();
6275
+ })
6276
+ ]);
6277
+ if (!merged || entry.lastMergedSha !== entry.lastObservedSha) {
6278
+ unmerged.push({
6279
+ workerName: entry.workerName,
6280
+ reason: merged ? "merge-conflict" : "drain-timeout"
6281
+ });
6282
+ }
6283
+ }
6284
+ if (unmerged.length > 0) {
6285
+ const auditPath = teardownAuditPath(config.repoRoot, config.teamName);
6286
+ await mkdir9(dirname14(auditPath), { recursive: true });
6287
+ for (const u of unmerged) {
6288
+ const row = JSON.stringify({
6289
+ type: "unmerged_at_shutdown",
6290
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
6291
+ team: config.teamName,
6292
+ worker: u.workerName,
6293
+ reason: u.reason
6294
+ });
6295
+ try {
6296
+ await appendFile5(auditPath, `${row}
6297
+ `, "utf-8");
6298
+ } catch {
6299
+ }
6300
+ }
6301
+ const message = `## Teardown audit: unmerged worker branches at shutdown
6302
+
6303
+ ${unmerged.map((u) => `- ${u.workerName}: ${u.reason}`).join("\n")}`;
6304
+ try {
6305
+ await appendToLeaderInbox(config.teamName, message, config.cwd);
6306
+ } catch {
6307
+ }
6308
+ }
6309
+ return { unmerged };
6310
+ },
6311
+ getState() {
6312
+ return {
6313
+ workers: Array.from(workers.keys()),
6314
+ lastShas: Object.fromEntries(
6315
+ Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
6316
+ ),
6317
+ mergerWorktreePath: mergerPath
6318
+ };
6319
+ }
6320
+ };
6321
+ }
6322
+ async function recoverFromRestart(config) {
6323
+ const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
6324
+ let persistedShasLoaded = 0;
6325
+ if (existsSync18(persistedPath)) {
6326
+ try {
6327
+ const { readFileSync: readFileSync12 } = await import("node:fs");
6328
+ const persisted = JSON.parse(readFileSync12(persistedPath, "utf-8"));
6329
+ persistedShasLoaded = Object.keys(persisted.lastShas ?? {}).length;
6330
+ } catch {
6331
+ persistedShasLoaded = 0;
6332
+ }
6333
+ }
6334
+ const orphanedRebases = [];
6335
+ let entries = [];
6336
+ try {
6337
+ entries = listTeamWorktrees(config.teamName, config.repoRoot).map((w) => ({
6338
+ workerName: w.workerName,
6339
+ path: w.path
6340
+ }));
6341
+ } catch {
6342
+ entries = [];
6343
+ }
6344
+ for (const entry of entries) {
6345
+ if (!isRebaseInProgress(entry.path)) continue;
6346
+ orphanedRebases.push(entry.workerName);
6347
+ const message = `### Runtime restart recovery \u2014 your branch is mid-rebase
6348
+
6349
+ Runtime restarted while your branch was mid-rebase onto \`${config.leaderBranch}\`.
6350
+
6351
+ **Worktree:** \`${entry.path}\`
6352
+
6353
+ Cadence remains paused. Resolve and \`git rebase --continue\`, or \`git rebase --abort\` to bail.
6354
+ Cadence resumes once the git rebase state is gone.`;
6355
+ try {
6356
+ await appendToInbox(config.teamName, entry.workerName, message, config.cwd);
6357
+ } catch {
6358
+ }
6359
+ }
6360
+ if (orphanedRebases.length > 0 || persistedShasLoaded > 0) {
6361
+ try {
6362
+ await appendEvent(config.repoRoot, config.teamName, {
6363
+ type: "restart_recovery",
6364
+ data: { orphanedRebases, persistedShasLoaded }
6365
+ });
6366
+ } catch {
6367
+ }
6368
+ }
6369
+ return { orphanedRebases, persistedShasLoaded };
6370
+ }
6371
+ var DEFAULT_POLL_INTERVAL_MS, DEFAULT_DRAIN_TIMEOUT_MS;
6372
+ var init_merge_orchestrator = __esm({
6373
+ "src/team/merge-orchestrator.ts"() {
6374
+ "use strict";
6375
+ init_fs_utils();
6376
+ init_runtime_flags();
6377
+ init_tmux_session();
6378
+ init_git_worktree();
6379
+ init_merge_coordinator();
6380
+ init_worker_bootstrap();
6381
+ init_leader_inbox();
6382
+ init_conflict_mailbox();
6383
+ init_worker_commit_cadence();
6384
+ DEFAULT_POLL_INTERVAL_MS = 1e3;
6385
+ DEFAULT_DRAIN_TIMEOUT_MS = 1e4;
6386
+ }
6387
+ });
6388
+
5388
6389
  // src/team/runtime-v2.ts
5389
6390
  var runtime_v2_exports = {};
5390
6391
  __export(runtime_v2_exports, {
@@ -5399,15 +6400,53 @@ __export(runtime_v2_exports, {
5399
6400
  startTeamV2: () => startTeamV2,
5400
6401
  writeWatchdogFailedMarker: () => writeWatchdogFailedMarker
5401
6402
  });
5402
- import { join as join19, resolve as resolve4 } from "path";
5403
- import { existsSync as existsSync16 } from "fs";
5404
- import { mkdir as mkdir7, readdir as readdir2, readFile as readFile9, rm as rm4, writeFile as writeFile5 } from "fs/promises";
6403
+ import { join as join22, resolve as resolve4 } from "path";
6404
+ import { existsSync as existsSync19 } from "fs";
6405
+ import { mkdir as mkdir10, readdir as readdir2, readFile as readFile10, rm as rm4, writeFile as writeFile7 } from "fs/promises";
5405
6406
  import { performance } from "perf_hooks";
5406
- function isRuntimeV2Enabled(env = process.env) {
5407
- const raw = env.OMC_RUNTIME_V2;
5408
- if (!raw) return true;
5409
- const normalized = raw.trim().toLowerCase();
5410
- return !["0", "false", "no", "off"].includes(normalized);
6407
+ import { execFileSync as execFileSync5 } from "node:child_process";
6408
+ function registerTeamOrchestrator(teamName, handle) {
6409
+ orchestratorByTeam.set(teamName, handle);
6410
+ }
6411
+ function getTeamOrchestrator(teamName) {
6412
+ return orchestratorByTeam.get(teamName);
6413
+ }
6414
+ function unregisterTeamOrchestrator(teamName) {
6415
+ orchestratorByTeam.delete(teamName);
6416
+ }
6417
+ function registerTeamCadence(teamName, context, poller) {
6418
+ const entry = cadenceByTeam.get(teamName) ?? { pollers: [], contexts: [] };
6419
+ entry.contexts.push(context);
6420
+ if (poller) entry.pollers.push(poller);
6421
+ cadenceByTeam.set(teamName, entry);
6422
+ }
6423
+ async function stopTeamCadence(teamName) {
6424
+ const entry = cadenceByTeam.get(teamName);
6425
+ if (!entry) return;
6426
+ cadenceByTeam.delete(teamName);
6427
+ for (const poller of entry.pollers) {
6428
+ try {
6429
+ poller.stop();
6430
+ } catch {
6431
+ }
6432
+ }
6433
+ for (const context of entry.contexts) {
6434
+ try {
6435
+ await uninstallCommitCadence(context);
6436
+ } catch {
6437
+ }
6438
+ }
6439
+ }
6440
+ function resolveLeaderBranch(cwd) {
6441
+ const out = execFileSync5("git", ["branch", "--show-current"], {
6442
+ cwd,
6443
+ encoding: "utf-8",
6444
+ stdio: ["pipe", "pipe", "pipe"]
6445
+ }).trim();
6446
+ if (!out) {
6447
+ throw new Error("auto-merge requires a non-detached leader branch (git branch --show-current returned empty)");
6448
+ }
6449
+ return out;
5411
6450
  }
5412
6451
  function resolveTaskAssignment(task, resolvedRouting, roleRoutingConfig, resolvedBinaryPaths, fallbackAgent) {
5413
6452
  const canonicalRoles = new Set(CANONICAL_TEAM_ROLES);
@@ -5550,7 +6589,7 @@ function hasWorkerStatusProgress(status, taskId) {
5550
6589
  }
5551
6590
  async function hasWorkerTaskClaimEvidence(teamName, workerName, cwd, taskId) {
5552
6591
  try {
5553
- const raw = await readFile9(absPath(cwd, TeamPaths.taskFile(teamName, taskId)), "utf-8");
6592
+ const raw = await readFile10(absPath(cwd, TeamPaths.taskFile(teamName, taskId)), "utf-8");
5554
6593
  const task = JSON.parse(raw);
5555
6594
  return task.owner === workerName && ["in_progress", "completed", "failed"].includes(task.status);
5556
6595
  } catch {
@@ -5649,6 +6688,18 @@ async function spawnV2Worker(opts) {
5649
6688
  if (usePromptMode) {
5650
6689
  launchArgs.push(...getPromptModeArgs(opts.agentType, promptModeStartupPrompt));
5651
6690
  }
6691
+ if (opts.autoMerge && opts.worktreePath) {
6692
+ const cadenceContext = {
6693
+ teamName: opts.teamName,
6694
+ workerName: opts.workerName,
6695
+ worktreePath: opts.worktreePath,
6696
+ agentType: opts.agentType,
6697
+ enabled: true
6698
+ };
6699
+ const cadence = await installCommitCadence(cadenceContext);
6700
+ const poller = cadence.method === "fallback-poll" ? startFallbackPoller(opts.worktreePath, opts.workerName) : void 0;
6701
+ registerTeamCadence(opts.teamName, cadenceContext, poller);
6702
+ }
5652
6703
  const paneConfig = {
5653
6704
  teamName: opts.teamName,
5654
6705
  workerName: opts.workerName,
@@ -5752,16 +6803,16 @@ async function rollbackUnpersistedNativeWorktreeStartup(teamName, cwd, cause) {
5752
6803
  await rm4(teamRoot, { recursive: true, force: true });
5753
6804
  return;
5754
6805
  }
5755
- await mkdir7(teamRoot, { recursive: true });
5756
- await writeFile5(join19(teamRoot, "startup-failure.json"), JSON.stringify({
6806
+ await mkdir10(teamRoot, { recursive: true });
6807
+ await writeFile7(join22(teamRoot, "startup-failure.json"), JSON.stringify({
5757
6808
  reason: "startup_failed_before_config_persisted",
5758
6809
  error: errorMessage,
5759
6810
  preserved: cleanup.preserved,
5760
6811
  recorded_at: (/* @__PURE__ */ new Date()).toISOString()
5761
6812
  }, null, 2), "utf-8");
5762
6813
  } catch (rollbackError) {
5763
- await mkdir7(teamRoot, { recursive: true });
5764
- await writeFile5(join19(teamRoot, "startup-failure.json"), JSON.stringify({
6814
+ await mkdir10(teamRoot, { recursive: true });
6815
+ await writeFile7(join22(teamRoot, "startup-failure.json"), JSON.stringify({
5765
6816
  reason: "startup_failed_before_config_persisted",
5766
6817
  error: errorMessage,
5767
6818
  rollback_error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
@@ -5791,9 +6842,23 @@ async function startTeamV2(config) {
5791
6842
  validateTeamName(sanitized);
5792
6843
  const pluginCfg = config.pluginConfig ?? loadConfig();
5793
6844
  const resolvedRouting = buildResolvedRoutingSnapshot(pluginCfg);
5794
- const worktreeMode = normalizeTeamWorktreeMode(
6845
+ let worktreeMode = normalizeTeamWorktreeMode(
5795
6846
  process.env.OMC_TEAM_WORKTREE_MODE ?? pluginCfg.team?.ops?.worktreeMode
5796
6847
  );
6848
+ let autoMergeLeaderBranch;
6849
+ if (config.autoMerge) {
6850
+ if (!isRuntimeV2Enabled()) {
6851
+ throw new Error("auto-merge requires OMC_RUNTIME_V2=1 (this feature is v2-only).");
6852
+ }
6853
+ autoMergeLeaderBranch = resolveLeaderBranch(leaderCwd);
6854
+ const stripped = autoMergeLeaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
6855
+ if (stripped === "main" || stripped === "master") {
6856
+ throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
6857
+ }
6858
+ if (worktreeMode !== "named") {
6859
+ worktreeMode = "named";
6860
+ }
6861
+ }
5797
6862
  const workspaceMode = worktreeMode === "disabled" ? "single" : "worktree";
5798
6863
  const agentTypes = config.agentTypes;
5799
6864
  const resolvedBinaryPaths = {};
@@ -5823,9 +6888,9 @@ async function startTeamV2(config) {
5823
6888
  } catch {
5824
6889
  }
5825
6890
  }
5826
- await mkdir7(absPath(leaderCwd, TeamPaths.tasks(sanitized)), { recursive: true });
5827
- await mkdir7(absPath(leaderCwd, TeamPaths.workers(sanitized)), { recursive: true });
5828
- await mkdir7(join19(leaderCwd, ".omcp", "state", "team", sanitized, "mailbox"), { recursive: true });
6891
+ await mkdir10(absPath(leaderCwd, TeamPaths.tasks(sanitized)), { recursive: true });
6892
+ await mkdir10(absPath(leaderCwd, TeamPaths.workers(sanitized)), { recursive: true });
6893
+ await mkdir10(join22(leaderCwd, ".omcp", "state", "team", sanitized, "mailbox"), { recursive: true });
5829
6894
  const missingBinaryLogFailure = createSwallowedErrorLogger(
5830
6895
  "team.runtime-v2.startTeamV2 cli_binary_missing event failed"
5831
6896
  );
@@ -5843,8 +6908,8 @@ async function startTeamV2(config) {
5843
6908
  for (let i = 0; i < config.tasks.length; i++) {
5844
6909
  const taskId = String(i + 1);
5845
6910
  const taskFilePath = absPath(leaderCwd, TeamPaths.taskFile(sanitized, taskId));
5846
- await mkdir7(join19(taskFilePath, ".."), { recursive: true });
5847
- await writeFile5(taskFilePath, JSON.stringify({
6911
+ await mkdir10(join22(taskFilePath, ".."), { recursive: true });
6912
+ await writeFile7(taskFilePath, JSON.stringify({
5848
6913
  id: taskId,
5849
6914
  subject: config.tasks[i].subject,
5850
6915
  description: config.tasks[i].description,
@@ -5916,7 +6981,7 @@ async function startTeamV2(config) {
5916
6981
  });
5917
6982
  const worktree = workerWorktrees.get(wName);
5918
6983
  if (worktree) {
5919
- const overlayContent = await readFile9(overlayPath, "utf-8");
6984
+ const overlayContent = await readFile10(overlayPath, "utf-8");
5920
6985
  installWorktreeRootAgents(sanitized, wName, leaderCwd, worktree.path, overlayContent);
5921
6986
  }
5922
6987
  }
@@ -6026,7 +7091,7 @@ async function startTeamV2(config) {
6026
7091
  next_worker_index: teamConfig.next_worker_index
6027
7092
  };
6028
7093
  try {
6029
- await writeFile5(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), "utf-8");
7094
+ await writeFile7(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), "utf-8");
6030
7095
  } catch (error) {
6031
7096
  await rollbackStartedNativeWorktreeStartup({
6032
7097
  teamName: sanitized,
@@ -6075,6 +7140,7 @@ async function startTeamV2(config) {
6075
7140
  cwd: leaderCwd,
6076
7141
  workerCwd: workersInfo[workerIndex]?.working_dir ?? leaderCwd,
6077
7142
  worktreePath: workersInfo[workerIndex]?.worktree_path,
7143
+ autoMerge: Boolean(config.autoMerge),
6078
7144
  resolvedBinaryPaths,
6079
7145
  ...assignment.model ? { model: assignment.model } : {},
6080
7146
  ...assignment.role ? { role: assignment.role } : {}
@@ -6137,6 +7203,51 @@ async function startTeamV2(config) {
6137
7203
  worker: "leader-fixed",
6138
7204
  reason: `start_team_v2: workers=${config.workerCount} tasks=${config.tasks.length} panes=${workerPaneIds.length}`
6139
7205
  }, leaderCwd).catch(logEventFailure);
7206
+ if (config.autoMerge && autoMergeLeaderBranch) {
7207
+ try {
7208
+ await ensureLeaderInbox(sanitized, leaderCwd);
7209
+ await appendToLeaderInbox(
7210
+ sanitized,
7211
+ extendLeaderBootstrapPrompt(sanitized),
7212
+ leaderCwd
7213
+ );
7214
+ try {
7215
+ await recoverFromRestart({
7216
+ teamName: sanitized,
7217
+ repoRoot: leaderCwd,
7218
+ leaderBranch: autoMergeLeaderBranch,
7219
+ cwd: leaderCwd
7220
+ });
7221
+ } catch (recErr) {
7222
+ process.stderr.write(`[team/runtime-v2] auto-merge recover-from-restart failed: ${recErr}
7223
+ `);
7224
+ }
7225
+ const orchestrator = await startMergeOrchestrator({
7226
+ teamName: sanitized,
7227
+ repoRoot: leaderCwd,
7228
+ leaderBranch: autoMergeLeaderBranch,
7229
+ cwd: leaderCwd
7230
+ });
7231
+ registerTeamOrchestrator(sanitized, orchestrator);
7232
+ for (const w of workersInfo) {
7233
+ await orchestrator.registerWorker(w.name);
7234
+ }
7235
+ } catch (orchErr) {
7236
+ await stopTeamCadence(sanitized);
7237
+ unregisterTeamOrchestrator(sanitized);
7238
+ await rollbackStartedNativeWorktreeStartup({
7239
+ teamName: sanitized,
7240
+ cwd: leaderCwd,
7241
+ cause: orchErr,
7242
+ sessionName: sessionName2,
7243
+ leaderPaneId,
7244
+ workerPaneIds,
7245
+ sessionMode: session.sessionMode
7246
+ });
7247
+ const reason = orchErr instanceof Error ? orchErr.message : String(orchErr);
7248
+ throw new Error(`auto-merge startup failed: ${reason}`);
7249
+ }
7250
+ }
6140
7251
  return {
6141
7252
  teamName: sanitized,
6142
7253
  sanitizedName: sanitized,
@@ -6147,16 +7258,16 @@ async function startTeamV2(config) {
6147
7258
  };
6148
7259
  }
6149
7260
  async function writeWatchdogFailedMarker(teamName, cwd, reason) {
6150
- const { writeFile: writeFile6 } = await import("fs/promises");
7261
+ const { writeFile: writeFile8 } = await import("fs/promises");
6151
7262
  const marker = {
6152
7263
  failedAt: Date.now(),
6153
7264
  reason,
6154
7265
  writtenBy: "runtime-v2"
6155
7266
  };
6156
7267
  const root = absPath(cwd, TeamPaths.root(sanitizeTeamName(teamName)));
6157
- const markerPath = join19(root, "watchdog-failed.json");
6158
- await mkdir7(root, { recursive: true });
6159
- await writeFile6(markerPath, JSON.stringify(marker, null, 2), "utf-8");
7268
+ const markerPath = join22(root, "watchdog-failed.json");
7269
+ await mkdir10(root, { recursive: true });
7270
+ await writeFile8(markerPath, JSON.stringify(marker, null, 2), "utf-8");
6160
7271
  }
6161
7272
  async function requeueDeadWorkerTasks(teamName, deadWorkerNames, cwd) {
6162
7273
  const logEventFailure = createSwallowedErrorLogger(
@@ -6176,9 +7287,9 @@ async function requeueDeadWorkerTasks(teamName, deadWorkerNames, cwd) {
6176
7287
  retryCount: 0,
6177
7288
  lastFailedAt: (/* @__PURE__ */ new Date()).toISOString()
6178
7289
  };
6179
- const { writeFile: writeFile6 } = await import("fs/promises");
6180
- await mkdir7(absPath(cwd, TeamPaths.tasks(sanitized)), { recursive: true });
6181
- await writeFile6(sidecarPath, JSON.stringify(sidecar, null, 2), "utf-8");
7290
+ const { writeFile: writeFile8 } = await import("fs/promises");
7291
+ await mkdir10(absPath(cwd, TeamPaths.tasks(sanitized)), { recursive: true });
7292
+ await writeFile8(sidecarPath, JSON.stringify(sidecar, null, 2), "utf-8");
6182
7293
  const taskPath2 = absPath(cwd, TeamPaths.taskFile(sanitized, task.id));
6183
7294
  try {
6184
7295
  const { readFileSync: readFileSync12, writeFileSync: writeFileSync4 } = await import("fs");
@@ -6227,7 +7338,7 @@ async function processCliWorkerVerdicts(teamName, cwd) {
6227
7338
  }
6228
7339
  let payload;
6229
7340
  try {
6230
- const raw = await readFile9(outputFile, "utf-8");
7341
+ const raw = await readFile10(outputFile, "utf-8");
6231
7342
  payload = parseCliWorkerVerdict(raw);
6232
7343
  } catch (err) {
6233
7344
  const reason = err instanceof Error ? err.message : String(err);
@@ -6510,6 +7621,39 @@ async function shutdownTeamV2(teamName, cwd, options = {}) {
6510
7621
  const timeoutMs = options.timeoutMs ?? 15e3;
6511
7622
  const sanitized = sanitizeTeamName(teamName);
6512
7623
  const config = await readTeamConfig(sanitized, cwd);
7624
+ const finalizeAutoMerge = async () => {
7625
+ const orchestrator = getTeamOrchestrator(sanitized);
7626
+ if (orchestrator) {
7627
+ try {
7628
+ const drainResult = await orchestrator.drainAndStop();
7629
+ if (drainResult.unmerged.length > 0) {
7630
+ await appendTeamEvent(sanitized, {
7631
+ type: "team_leader_nudge",
7632
+ worker: "leader-fixed",
7633
+ reason: `auto_merge_drain_unmerged:${drainResult.unmerged.map((u) => `${u.workerName}:${u.reason}`).join(",")}`
7634
+ }, cwd).catch(logEventFailure);
7635
+ }
7636
+ for (const w of config?.workers ?? []) {
7637
+ try {
7638
+ await orchestrator.unregisterWorker(w.name);
7639
+ } catch (err) {
7640
+ process.stderr.write(
7641
+ `[team/runtime-v2] orchestrator.unregisterWorker(${w.name}) failed: ${err}
7642
+ `
7643
+ );
7644
+ }
7645
+ }
7646
+ } catch (err) {
7647
+ process.stderr.write(`[team/runtime-v2] orchestrator drainAndStop: ${err}
7648
+ `);
7649
+ } finally {
7650
+ await stopTeamCadence(sanitized);
7651
+ unregisterTeamOrchestrator(sanitized);
7652
+ }
7653
+ } else {
7654
+ await stopTeamCadence(sanitized);
7655
+ }
7656
+ };
6513
7657
  if (!config) {
6514
7658
  const cleanupSafety = inspectTeamWorktreeCleanupSafety(sanitized, cwd);
6515
7659
  if (cleanupSafety.hasEvidence) {
@@ -6644,12 +7788,14 @@ Then exit your session.
6644
7788
  if (aliveWorkers.length > 0) {
6645
7789
  process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane(s) are still alive: ${aliveWorkers.join(", ")}
6646
7790
  `);
7791
+ await finalizeAutoMerge();
6647
7792
  return;
6648
7793
  }
6649
7794
  const unknownWorkers = liveness.filter(([, state]) => state === "unknown").map(([paneId]) => paneById.get(paneId) ?? paneId);
6650
7795
  if (unknownWorkers.length > 0) {
6651
7796
  process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane liveness is unknown: ${unknownWorkers.join(", ")}
6652
7797
  `);
7798
+ await finalizeAutoMerge();
6653
7799
  return;
6654
7800
  }
6655
7801
  } catch (err) {
@@ -6657,6 +7803,7 @@ Then exit your session.
6657
7803
  `);
6658
7804
  if (recordedWorkerPaneIds.length > 0) {
6659
7805
  process.stderr.write("[team/runtime-v2] preserving worktrees/state because tmux cleanup did not prove worker panes exited\n");
7806
+ await finalizeAutoMerge();
6660
7807
  return;
6661
7808
  }
6662
7809
  }
@@ -6671,6 +7818,7 @@ Then exit your session.
6671
7818
  reason: `ralph_cleanup_summary: total=${finalTasks.length} completed=${completed} failed=${failed} pending=${pending} force=${force}`
6672
7819
  }, cwd).catch(logEventFailure);
6673
7820
  }
7821
+ await finalizeAutoMerge();
6674
7822
  let preservedWorktrees = 0;
6675
7823
  try {
6676
7824
  const worktreeCleanup = cleanupTeamWorktrees(sanitized, cwd);
@@ -6707,8 +7855,8 @@ async function resumeTeamV2(teamName, cwd) {
6707
7855
  }
6708
7856
  }
6709
7857
  async function findActiveTeamsV2(cwd) {
6710
- const root = join19(cwd, ".omcp", "state", "team");
6711
- if (!existsSync16(root)) return [];
7858
+ const root = join22(cwd, ".omcp", "state", "team");
7859
+ if (!existsSync19(root)) return [];
6712
7860
  const entries = await readdir2(root, { withFileTypes: true });
6713
7861
  const active = [];
6714
7862
  for (const e of entries) {
@@ -6721,7 +7869,7 @@ async function findActiveTeamsV2(cwd) {
6721
7869
  }
6722
7870
  return active;
6723
7871
  }
6724
- var MONITOR_SIGNAL_STALE_MS, CIRCUIT_BREAKER_THRESHOLD, CircuitBreakerV2;
7872
+ var orchestratorByTeam, cadenceByTeam, MONITOR_SIGNAL_STALE_MS, CIRCUIT_BREAKER_THRESHOLD, CircuitBreakerV2;
6725
7873
  var init_runtime_v2 = __esm({
6726
7874
  "src/team/runtime-v2.ts"() {
6727
7875
  "use strict";
@@ -6746,6 +7894,13 @@ var init_runtime_v2 = __esm({
6746
7894
  init_role_router();
6747
7895
  init_types2();
6748
7896
  init_cli_worker_contract();
7897
+ init_merge_orchestrator();
7898
+ init_leader_inbox();
7899
+ init_runtime_flags();
7900
+ init_worker_commit_cadence();
7901
+ init_runtime_flags();
7902
+ orchestratorByTeam = /* @__PURE__ */ new Map();
7903
+ cadenceByTeam = /* @__PURE__ */ new Map();
6749
7904
  MONITOR_SIGNAL_STALE_MS = 3e4;
6750
7905
  CIRCUIT_BREAKER_THRESHOLD = 3;
6751
7906
  CircuitBreakerV2 = class {
@@ -6781,16 +7936,16 @@ var init_runtime_v2 = __esm({
6781
7936
  // src/cli/team.ts
6782
7937
  import { randomUUID as randomUUID6 } from "crypto";
6783
7938
  import { spawn } from "child_process";
6784
- import { existsSync as existsSync18, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
6785
- import { readFile as readFile10, readdir as readdir3, rm as rm5 } from "fs/promises";
7939
+ import { existsSync as existsSync21, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "fs";
7940
+ import { readFile as readFile11, readdir as readdir3, rm as rm5 } from "fs/promises";
6786
7941
  import { homedir as homedir3 } from "os";
6787
- import { dirname as dirname13, join as join21 } from "path";
7942
+ import { dirname as dirname16, join as join24 } from "path";
6788
7943
  import { fileURLToPath as fileURLToPath3 } from "url";
6789
7944
 
6790
7945
  // src/team/api-interop.ts
6791
7946
  init_contracts();
6792
- import { existsSync as existsSync17, readFileSync as readFileSync10 } from "node:fs";
6793
- import { dirname as dirname12, join as join20, resolve as resolvePath } from "node:path";
7947
+ import { existsSync as existsSync20, readFileSync as readFileSync10 } from "node:fs";
7948
+ import { dirname as dirname15, join as join23, resolve as resolvePath } from "node:path";
6794
7949
 
6795
7950
  // src/team/team-ops.ts
6796
7951
  init_state_paths();
@@ -7780,8 +8935,8 @@ function parseValidatedTaskIdArray(value, fieldName) {
7780
8935
  }
7781
8936
  function teamStateExists(teamName, candidateCwd) {
7782
8937
  if (!TEAM_NAME_SAFE_PATTERN.test(teamName)) return false;
7783
- const teamRoot = join20(candidateCwd, ".omcp", "state", "team", teamName);
7784
- return existsSync17(join20(teamRoot, "config.json")) || existsSync17(join20(teamRoot, "tasks")) || existsSync17(teamRoot);
8938
+ const teamRoot = join23(candidateCwd, ".omcp", "state", "team", teamName);
8939
+ return existsSync20(join23(teamRoot, "config.json")) || existsSync20(join23(teamRoot, "tasks")) || existsSync20(teamRoot);
7785
8940
  }
7786
8941
  function parseTeamWorkerEnv(raw) {
7787
8942
  if (typeof raw !== "string" || raw.trim() === "") return null;
@@ -7835,7 +8990,7 @@ async function executeTeamCleanupViaRuntime(teamName, cwd) {
7835
8990
  await teamCleanup(teamName, cwd);
7836
8991
  }
7837
8992
  function readTeamStateRootFromFile(path4) {
7838
- if (!existsSync17(path4)) return null;
8993
+ if (!existsSync20(path4)) return null;
7839
8994
  try {
7840
8995
  const parsed = JSON.parse(readFileSync10(path4, "utf8"));
7841
8996
  return typeof parsed.team_state_root === "string" && parsed.team_state_root.trim() !== "" ? parsed.team_state_root.trim() : null;
@@ -7851,7 +9006,7 @@ function stateRootToWorkingDirectory(stateRoot2) {
7851
9006
  if (idx >= 0) {
7852
9007
  const workspaceRoot = absolute.slice(0, idx);
7853
9008
  if (workspaceRoot && workspaceRoot !== "/") return workspaceRoot;
7854
- return dirname12(dirname12(dirname12(dirname12(absolute))));
9009
+ return dirname15(dirname15(dirname15(dirname15(absolute))));
7855
9010
  }
7856
9011
  }
7857
9012
  for (const marker of ["/.omcp/state", "/.omx/state"]) {
@@ -7859,22 +9014,22 @@ function stateRootToWorkingDirectory(stateRoot2) {
7859
9014
  if (idx >= 0) {
7860
9015
  const workspaceRoot = absolute.slice(0, idx);
7861
9016
  if (workspaceRoot && workspaceRoot !== "/") return workspaceRoot;
7862
- return dirname12(dirname12(absolute));
9017
+ return dirname15(dirname15(absolute));
7863
9018
  }
7864
9019
  }
7865
- return dirname12(dirname12(absolute));
9020
+ return dirname15(dirname15(absolute));
7866
9021
  }
7867
9022
  function resolveTeamWorkingDirectoryFromMetadata(teamName, candidateCwd, workerContext) {
7868
- const teamRoot = join20(candidateCwd, ".omcp", "state", "team", teamName);
7869
- if (!existsSync17(teamRoot)) return null;
9023
+ const teamRoot = join23(candidateCwd, ".omcp", "state", "team", teamName);
9024
+ if (!existsSync20(teamRoot)) return null;
7870
9025
  if (workerContext?.teamName === teamName) {
7871
- const workerRoot = readTeamStateRootFromFile(join20(teamRoot, "workers", workerContext.workerName, "identity.json"));
9026
+ const workerRoot = readTeamStateRootFromFile(join23(teamRoot, "workers", workerContext.workerName, "identity.json"));
7872
9027
  if (workerRoot) return stateRootToWorkingDirectory(workerRoot);
7873
9028
  }
7874
- const fromConfig = readTeamStateRootFromFile(join20(teamRoot, "config.json"));
9029
+ const fromConfig = readTeamStateRootFromFile(join23(teamRoot, "config.json"));
7875
9030
  if (fromConfig) return stateRootToWorkingDirectory(fromConfig);
7876
9031
  for (const manifestName of ["manifest.json", "manifest.v2.json"]) {
7877
- const fromManifest = readTeamStateRootFromFile(join20(teamRoot, manifestName));
9032
+ const fromManifest = readTeamStateRootFromFile(join23(teamRoot, manifestName));
7878
9033
  if (fromManifest) return stateRootToWorkingDirectory(fromManifest);
7879
9034
  }
7880
9035
  return null;
@@ -7901,7 +9056,7 @@ function resolveTeamWorkingDirectory(teamName, preferredCwd) {
7901
9056
  if (teamStateExists(normalizedTeamName, cursor)) {
7902
9057
  return resolveTeamWorkingDirectoryFromMetadata(normalizedTeamName, cursor, workerContext) ?? cursor;
7903
9058
  }
7904
- const parent = dirname12(cursor);
9059
+ const parent = dirname15(cursor);
7905
9060
  if (!parent || parent === cursor) break;
7906
9061
  cursor = parent;
7907
9062
  }
@@ -8503,31 +9658,31 @@ function assertTeamSpawnAllowed(env = process.env) {
8503
9658
  );
8504
9659
  }
8505
9660
  function resolveJobsDir(env = process.env) {
8506
- return env.OMC_JOBS_DIR || join21(homedir3(), ".omcp", "team-jobs");
9661
+ return env.OMC_JOBS_DIR || join24(homedir3(), ".omcp", "team-jobs");
8507
9662
  }
8508
9663
  function resolveRuntimeCliPath(env = process.env) {
8509
9664
  if (env.OMC_RUNTIME_CLI_PATH) {
8510
9665
  return env.OMC_RUNTIME_CLI_PATH;
8511
9666
  }
8512
- const moduleDir = dirname13(fileURLToPath3(import.meta.url));
8513
- return join21(moduleDir, "../../bridge/runtime-cli.cjs");
9667
+ const moduleDir = dirname16(fileURLToPath3(import.meta.url));
9668
+ return join24(moduleDir, "../../bridge/runtime-cli.cjs");
8514
9669
  }
8515
9670
  function ensureJobsDir(jobsDir) {
8516
- if (!existsSync18(jobsDir)) {
9671
+ if (!existsSync21(jobsDir)) {
8517
9672
  mkdirSync3(jobsDir, { recursive: true });
8518
9673
  }
8519
9674
  }
8520
9675
  function jobPath(jobsDir, jobId) {
8521
- return join21(jobsDir, `${jobId}.json`);
9676
+ return join24(jobsDir, `${jobId}.json`);
8522
9677
  }
8523
9678
  function resultArtifactPath(jobsDir, jobId) {
8524
- return join21(jobsDir, `${jobId}-result.json`);
9679
+ return join24(jobsDir, `${jobId}-result.json`);
8525
9680
  }
8526
9681
  function panesArtifactPath(jobsDir, jobId) {
8527
- return join21(jobsDir, `${jobId}-panes.json`);
9682
+ return join24(jobsDir, `${jobId}-panes.json`);
8528
9683
  }
8529
9684
  function teamStateRoot2(cwd, teamName) {
8530
- return join21(cwd, ".omcp", "state", "team", teamName);
9685
+ return join24(cwd, ".omcp", "state", "team", teamName);
8531
9686
  }
8532
9687
  function validateJobId(jobId) {
8533
9688
  if (!JOB_ID_PATTERN.test(jobId)) {
@@ -8542,7 +9697,7 @@ function parseJsonSafe(content) {
8542
9697
  }
8543
9698
  }
8544
9699
  async function resolveCleanupPaneEvidence(job, jobsDir, jobId) {
8545
- const paneArtifact = await readFile10(panesArtifactPath(jobsDir, jobId), "utf-8").then((content) => parseJsonSafe(content)).catch(() => null);
9700
+ const paneArtifact = await readFile11(panesArtifactPath(jobsDir, jobId), "utf-8").then((content) => parseJsonSafe(content)).catch(() => null);
8546
9701
  if (paneArtifact?.paneIds?.length) return { paneArtifact };
8547
9702
  const config = await readTeamConfig(job.teamName, job.cwd).catch(() => null);
8548
9703
  if (!config) {
@@ -8686,7 +9841,8 @@ async function startTeamJob(input) {
8686
9841
  newWindow: input.newWindow,
8687
9842
  pollIntervalMs: input.pollIntervalMs,
8688
9843
  sentinelGateTimeoutMs: input.sentinelGateTimeoutMs,
8689
- sentinelGatePollIntervalMs: input.sentinelGatePollIntervalMs
9844
+ sentinelGatePollIntervalMs: input.sentinelGatePollIntervalMs,
9845
+ autoMerge: input.autoMerge
8690
9846
  };
8691
9847
  child.stdin.write(JSON.stringify(payload));
8692
9848
  child.stdin.end();
@@ -8981,7 +10137,7 @@ async function teamCleanupCommand(jobId, cleanupOptions = {}, options = {}) {
8981
10137
  }
8982
10138
  var TEAM_USAGE = `
8983
10139
  Usage:
8984
- omcp team start --agent <claude|copilot|codex|gemini|cursor>[,<agent>...] --task "<task>" [--count N] [--name TEAM] [--cwd DIR] [--new-window] [--json]
10140
+ omcp team start --agent <claude|copilot|codex|gemini|cursor>[,<agent>...] --task "<task>" [--count N] [--name TEAM] [--cwd DIR] [--new-window] [--auto-merge] [--json]
8985
10141
  omcp team status <job_id|team_name> [--json] [--cwd DIR]
8986
10142
  omcp team wait <job_id> [--timeout-ms MS] [--json]
8987
10143
  omcp team cleanup <job_id> [--grace-ms MS] [--json]
@@ -8994,6 +10150,13 @@ Worktrees:
8994
10150
  Native per-worker git worktree mode is opt-in/config-gated with team.ops.worktreeMode or OMC_TEAM_WORKTREE_MODE=detached|named.
8995
10151
  Status JSON includes workspace_mode, worktree_mode, team_state_root, and per-worker worktree metadata.
8996
10152
 
10153
+ Auto-merge (v2-only):
10154
+ --auto-merge Enable per-commit auto-merge to leader and auto-rebase fanout.
10155
+ Each worker runs in a dedicated git worktree on omc-team/{team}/{worker}.
10156
+ Bursts of rapid worker commits coalesce to a single merge of HEAD.
10157
+ Requires OMC_RUNTIME_V2=1. Leader branch must not be 'main' or 'master'.
10158
+ Equivalent to OMC_TEAMS_AUTO_MERGE=1.
10159
+
8997
10160
  Examples:
8998
10161
  omcp team start --agent codex --count 2 --task "review auth flow" --new-window
8999
10162
  omcp team status omc-abc123
@@ -9015,6 +10178,7 @@ function parseStartArgs(args) {
9015
10178
  let pollIntervalMs;
9016
10179
  let sentinelGateTimeoutMs;
9017
10180
  let sentinelGatePollIntervalMs;
10181
+ let autoMerge = process.env.OMC_TEAMS_AUTO_MERGE === "1";
9018
10182
  for (let i = 0; i < args.length; i += 1) {
9019
10183
  const token = args[i];
9020
10184
  const next = args[i + 1];
@@ -9026,6 +10190,10 @@ function parseStartArgs(args) {
9026
10190
  newWindow = true;
9027
10191
  continue;
9028
10192
  }
10193
+ if (token === "--auto-merge") {
10194
+ autoMerge = true;
10195
+ continue;
10196
+ }
9029
10197
  if (token === "--agent") {
9030
10198
  if (!next) throw new Error("Missing value after --agent");
9031
10199
  agentValues.push(...next.split(",").map(normalizeAgentType));
@@ -9143,7 +10311,8 @@ function parseStartArgs(args) {
9143
10311
  ...newWindow ? { newWindow: true } : {},
9144
10312
  ...pollIntervalMs != null ? { pollIntervalMs } : {},
9145
10313
  ...sentinelGateTimeoutMs != null ? { sentinelGateTimeoutMs } : {},
9146
- ...sentinelGatePollIntervalMs != null ? { sentinelGatePollIntervalMs } : {}
10314
+ ...sentinelGatePollIntervalMs != null ? { sentinelGatePollIntervalMs } : {},
10315
+ ...autoMerge ? { autoMerge: true } : {}
9147
10316
  },
9148
10317
  json
9149
10318
  };
@@ -9334,6 +10503,7 @@ function parseLegacyStartAlias(args) {
9334
10503
  let json = false;
9335
10504
  let cwd = process.cwd();
9336
10505
  let newWindow = false;
10506
+ let autoMerge = process.env.OMC_TEAMS_AUTO_MERGE === "1";
9337
10507
  const taskParts = [];
9338
10508
  for (let i = index; i < args.length; i += 1) {
9339
10509
  const token = args[i];
@@ -9346,6 +10516,10 @@ function parseLegacyStartAlias(args) {
9346
10516
  newWindow = true;
9347
10517
  continue;
9348
10518
  }
10519
+ if (token === "--auto-merge") {
10520
+ autoMerge = true;
10521
+ continue;
10522
+ }
9349
10523
  if (token === "--cwd") {
9350
10524
  if (!next) throw new Error("Missing value after --cwd");
9351
10525
  cwd = next;
@@ -9369,7 +10543,8 @@ function parseLegacyStartAlias(args) {
9369
10543
  ralph,
9370
10544
  json,
9371
10545
  cwd,
9372
- ...newWindow ? { newWindow: true } : {}
10546
+ ...newWindow ? { newWindow: true } : {},
10547
+ ...autoMerge ? { autoMerge: true } : {}
9373
10548
  };
9374
10549
  }
9375
10550
  async function teamCommand(argv) {
@@ -9445,7 +10620,8 @@ async function teamCommand(argv) {
9445
10620
  agentTypes: Array.from({ length: legacy.workerCount }, () => legacy.agentType),
9446
10621
  tasks,
9447
10622
  cwd: legacy.cwd,
9448
- ...legacy.newWindow ? { newWindow: true } : {}
10623
+ ...legacy.newWindow ? { newWindow: true } : {},
10624
+ ...legacy.autoMerge ? { autoMerge: true } : {}
9449
10625
  });
9450
10626
  output(result, legacy.json);
9451
10627
  return;