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
@@ -207,7 +207,7 @@ function getDefaultShell() {
207
207
  return process.env.COMSPEC || "cmd.exe";
208
208
  }
209
209
  const shell = process.env.SHELL || "/bin/bash";
210
- const name = (0, import_path11.basename)(shell.replace(/\\/g, "/")).replace(/\.(exe|cmd|bat)$/i, "");
210
+ const name = (0, import_path10.basename)(shell.replace(/\\/g, "/")).replace(/\.(exe|cmd|bat)$/i, "");
211
211
  if (!SUPPORTED_POSIX_SHELLS.has(name)) {
212
212
  return "/bin/sh";
213
213
  }
@@ -217,7 +217,7 @@ function pathEntries(envPath) {
217
217
  return (envPath ?? "").split(process.platform === "win32" ? ";" : ":").map((entry) => entry.trim()).filter(Boolean);
218
218
  }
219
219
  function pathCandidateNames(candidatePath) {
220
- const base = (0, import_path11.basename)(candidatePath.replace(/\\/g, "/"));
220
+ const base = (0, import_path10.basename)(candidatePath.replace(/\\/g, "/"));
221
221
  const bare = base.replace(/\.(exe|cmd|bat)$/i, "");
222
222
  if (process.platform === "win32") {
223
223
  return Array.from(/* @__PURE__ */ new Set([`${bare}.exe`, `${bare}.cmd`, `${bare}.bat`, bare]));
@@ -227,7 +227,7 @@ function pathCandidateNames(candidatePath) {
227
227
  function resolveShellFromPath(candidatePath) {
228
228
  for (const dir of pathEntries(process.env.PATH)) {
229
229
  for (const name of pathCandidateNames(candidatePath)) {
230
- const full = (0, import_path11.join)(dir, name);
230
+ const full = (0, import_path10.join)(dir, name);
231
231
  if ((0, import_fs7.existsSync)(full)) return full;
232
232
  }
233
233
  }
@@ -243,7 +243,7 @@ function resolveShellFromCandidates(paths, rcFile) {
243
243
  }
244
244
  function resolveSupportedShellAffinity(shellPath) {
245
245
  if (!shellPath) return null;
246
- const name = (0, import_path11.basename)(shellPath.replace(/\\/g, "/")).replace(/\.(exe|cmd|bat)$/i, "");
246
+ const name = (0, import_path10.basename)(shellPath.replace(/\\/g, "/")).replace(/\.(exe|cmd|bat)$/i, "");
247
247
  if (name !== "zsh" && name !== "bash") return null;
248
248
  if (!(0, import_fs7.existsSync)(shellPath)) return null;
249
249
  const home = process.env.HOME ?? "";
@@ -269,7 +269,7 @@ function escapeForCmdSet(value) {
269
269
  return value.replace(/"/g, '""');
270
270
  }
271
271
  function shellNameFromPath(shellPath) {
272
- const shellName = (0, import_path11.basename)(shellPath.replace(/\\/g, "/"));
272
+ const shellName = (0, import_path10.basename)(shellPath.replace(/\\/g, "/"));
273
273
  return shellName.replace(/\.(exe|cmd|bat)$/i, "");
274
274
  }
275
275
  function shellEscape(value) {
@@ -281,7 +281,7 @@ function assertSafeEnvKey(key) {
281
281
  }
282
282
  }
283
283
  function isAbsoluteLaunchBinaryPath(value) {
284
- return (0, import_path11.isAbsolute)(value) || import_path11.win32.isAbsolute(value);
284
+ return (0, import_path10.isAbsolute)(value) || import_path10.win32.isAbsolute(value);
285
285
  }
286
286
  function assertSafeLaunchBinary(launchBinary) {
287
287
  if (launchBinary.trim().length === 0) {
@@ -794,9 +794,9 @@ async function isWorkerAlive(paneId) {
794
794
  async function killWorkerPanes(opts) {
795
795
  const { paneIds, leaderPaneId, teamName, cwd, graceMs = 1e4 } = opts;
796
796
  if (!paneIds.length) return;
797
- const shutdownPath = (0, import_path11.join)(cwd, ".omc", "state", "team", teamName, "shutdown.json");
797
+ const shutdownPath = (0, import_path10.join)(cwd, ".omc", "state", "team", teamName, "shutdown.json");
798
798
  try {
799
- await import_promises2.default.writeFile(shutdownPath, JSON.stringify({ requestedAt: Date.now() }));
799
+ await import_promises.default.writeFile(shutdownPath, JSON.stringify({ requestedAt: Date.now() }));
800
800
  const aliveChecks = await Promise.all(paneIds.map((id) => isWorkerAlive(id)));
801
801
  if (aliveChecks.some((alive) => alive)) {
802
802
  await sleep(graceMs);
@@ -873,13 +873,13 @@ async function killTeamSession(sessionName2, workerPaneIds, leaderPaneId, option
873
873
  } catch {
874
874
  }
875
875
  }
876
- var import_fs7, import_path11, import_promises2, sleep, TMUX_SESSION_PREFIX, SUPPORTED_POSIX_SHELLS, ZSH_CANDIDATES, BASH_CANDIDATES, DANGEROUS_LAUNCH_BINARY_CHARS;
876
+ var import_fs7, import_path10, import_promises, sleep, TMUX_SESSION_PREFIX, SUPPORTED_POSIX_SHELLS, ZSH_CANDIDATES, BASH_CANDIDATES, DANGEROUS_LAUNCH_BINARY_CHARS;
877
877
  var init_tmux_session = __esm({
878
878
  "src/team/tmux-session.ts"() {
879
879
  "use strict";
880
880
  import_fs7 = require("fs");
881
- import_path11 = require("path");
882
- import_promises2 = __toESM(require("fs/promises"), 1);
881
+ import_path10 = require("path");
882
+ import_promises = __toESM(require("fs/promises"), 1);
883
883
  init_team_name();
884
884
  init_tmux_utils();
885
885
  sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -1133,6 +1133,7 @@ var init_file_lock = __esm({
1133
1133
  // src/team/runtime-cli.ts
1134
1134
  var runtime_cli_exports = {};
1135
1135
  __export(runtime_cli_exports, {
1136
+ assertAutoMergeRuntimeSupported: () => assertAutoMergeRuntimeSupported,
1136
1137
  buildCliOutput: () => buildCliOutput,
1137
1138
  buildTerminalCliResult: () => buildTerminalCliResult,
1138
1139
  checkWatchdogFailedMarker: () => checkWatchdogFailedMarker,
@@ -1140,9 +1141,9 @@ __export(runtime_cli_exports, {
1140
1141
  writeResultArtifact: () => writeResultArtifact
1141
1142
  });
1142
1143
  module.exports = __toCommonJS(runtime_cli_exports);
1143
- var import_fs19 = require("fs");
1144
- var import_promises8 = require("fs/promises");
1145
- var import_path21 = require("path");
1144
+ var import_fs21 = require("fs");
1145
+ var import_promises11 = require("fs/promises");
1146
+ var import_path23 = require("path");
1146
1147
 
1147
1148
  // src/team/runtime.ts
1148
1149
  var import_promises3 = require("fs/promises");
@@ -2819,8 +2820,8 @@ function getPromptModeArgs(agentType, instruction) {
2819
2820
  init_team_name();
2820
2821
 
2821
2822
  // src/team/worker-bootstrap.ts
2822
- var import_promises = require("fs/promises");
2823
- var import_path10 = require("path");
2823
+ var import_promises2 = require("fs/promises");
2824
+ var import_path12 = require("path");
2824
2825
 
2825
2826
  // src/agents/prompt-helpers.ts
2826
2827
  var import_fs6 = require("fs");
@@ -2923,10 +2924,74 @@ function formatOmcCliInvocation(commandSuffix, options = {}) {
2923
2924
  return `${resolveInvocationPrefix(suffix, options)} ${suffix}`.trim();
2924
2925
  }
2925
2926
 
2927
+ // src/team/worker-bootstrap.ts
2928
+ init_tmux_session();
2929
+
2930
+ // src/team/fs-utils.ts
2931
+ var import_fs8 = require("fs");
2932
+ var import_path11 = require("path");
2933
+ function atomicWriteJson(filePath, data, mode = 384) {
2934
+ const dir = (0, import_path11.dirname)(filePath);
2935
+ if (!(0, import_fs8.existsSync)(dir)) (0, import_fs8.mkdirSync)(dir, { recursive: true, mode: 448 });
2936
+ const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
2937
+ const serialized = JSON.stringify(data, null, 2) + "\n";
2938
+ (0, import_fs8.writeFileSync)(tmpPath, serialized, { encoding: "utf-8", mode });
2939
+ try {
2940
+ (0, import_fs8.renameSync)(tmpPath, filePath);
2941
+ } catch (err) {
2942
+ if (process.platform === "win32" && err && typeof err === "object" && "code" in err && err.code === "EPERM") {
2943
+ try {
2944
+ (0, import_fs8.writeFileSync)(filePath, serialized, { encoding: "utf-8", mode });
2945
+ } finally {
2946
+ try {
2947
+ (0, import_fs8.unlinkSync)(tmpPath);
2948
+ } catch {
2949
+ }
2950
+ }
2951
+ } else {
2952
+ try {
2953
+ (0, import_fs8.unlinkSync)(tmpPath);
2954
+ } catch {
2955
+ }
2956
+ throw err;
2957
+ }
2958
+ }
2959
+ }
2960
+ function ensureDirWithMode(dirPath, mode = 448) {
2961
+ if (!(0, import_fs8.existsSync)(dirPath)) (0, import_fs8.mkdirSync)(dirPath, { recursive: true, mode });
2962
+ }
2963
+ function safeRealpath(p) {
2964
+ try {
2965
+ return (0, import_fs8.realpathSync)(p);
2966
+ } catch {
2967
+ const segments = [];
2968
+ let current = (0, import_path11.resolve)(p);
2969
+ while (!(0, import_fs8.existsSync)(current)) {
2970
+ segments.unshift((0, import_path11.basename)(current));
2971
+ const parent = (0, import_path11.dirname)(current);
2972
+ if (parent === current) break;
2973
+ current = parent;
2974
+ }
2975
+ try {
2976
+ return (0, import_path11.join)((0, import_fs8.realpathSync)(current), ...segments);
2977
+ } catch {
2978
+ return (0, import_path11.resolve)(p);
2979
+ }
2980
+ }
2981
+ }
2982
+ function validateResolvedPath(resolvedPath, expectedBase) {
2983
+ const absResolved = safeRealpath(resolvedPath);
2984
+ const absBase = safeRealpath(expectedBase);
2985
+ const rel = (0, import_path11.relative)(absBase, absResolved);
2986
+ if (rel.startsWith("..") || (0, import_path11.resolve)(absBase, rel) !== absResolved) {
2987
+ throw new Error(`Path traversal detected: "${resolvedPath}" escapes base "${expectedBase}"`);
2988
+ }
2989
+ }
2990
+
2926
2991
  // src/team/worker-bootstrap.ts
2927
2992
  var DEFAULT_INSTRUCTION_STATE_ROOT = ".omcp/state";
2928
2993
  function buildInstructionPath(...parts) {
2929
- return (0, import_path10.join)(...parts).replaceAll("\\", "/");
2994
+ return (0, import_path12.join)(...parts).replaceAll("\\", "/");
2930
2995
  }
2931
2996
  function buildTeamStateInstructionPath(teamName, instructionStateRoot, ...teamRelativeParts) {
2932
2997
  const baseParts = instructionStateRoot === DEFAULT_INSTRUCTION_STATE_ROOT ? [instructionStateRoot, "team", teamName] : [instructionStateRoot];
@@ -3105,26 +3170,37 @@ ${bootstrapInstructions}
3105
3170
  ` : ""}`;
3106
3171
  }
3107
3172
  async function composeInitialInbox(teamName, workerName2, content, cwd, cliOutputContract) {
3108
- const inboxPath = (0, import_path10.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}/inbox.md`);
3109
- await (0, import_promises.mkdir)((0, import_path10.dirname)(inboxPath), { recursive: true });
3173
+ const inboxPath = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}/inbox.md`);
3174
+ await (0, import_promises2.mkdir)((0, import_path12.dirname)(inboxPath), { recursive: true });
3110
3175
  const finalContent = cliOutputContract && !content.includes(cliOutputContract) ? `${content}
3111
3176
  ${cliOutputContract}` : content;
3112
- await (0, import_promises.writeFile)(inboxPath, finalContent, "utf-8");
3177
+ await (0, import_promises2.writeFile)(inboxPath, finalContent, "utf-8");
3178
+ }
3179
+ async function appendToInbox(teamName, workerName2, message, cwd) {
3180
+ const safeTeam = sanitizeName(teamName);
3181
+ const safeWorker = sanitizeName(workerName2);
3182
+ const inboxPath = (0, import_path12.join)(cwd, `.omcp/state/team/${safeTeam}/workers/${safeWorker}/inbox.md`);
3183
+ validateResolvedPath(inboxPath, cwd);
3184
+ await (0, import_promises2.mkdir)((0, import_path12.dirname)(inboxPath), { recursive: true });
3185
+ await (0, import_promises2.appendFile)(inboxPath, `
3186
+
3187
+ ---
3188
+ ${message}`, "utf-8");
3113
3189
  }
3114
3190
  async function ensureWorkerStateDir(teamName, workerName2, cwd) {
3115
- const workerDir = (0, import_path10.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}`);
3116
- await (0, import_promises.mkdir)(workerDir, { recursive: true });
3117
- const mailboxDir = (0, import_path10.join)(cwd, `.omcp/state/team/${teamName}/mailbox`);
3118
- await (0, import_promises.mkdir)(mailboxDir, { recursive: true });
3119
- const tasksDir = (0, import_path10.join)(cwd, `.omcp/state/team/${teamName}/tasks`);
3120
- await (0, import_promises.mkdir)(tasksDir, { recursive: true });
3191
+ const workerDir = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}`);
3192
+ await (0, import_promises2.mkdir)(workerDir, { recursive: true });
3193
+ const mailboxDir = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/mailbox`);
3194
+ await (0, import_promises2.mkdir)(mailboxDir, { recursive: true });
3195
+ const tasksDir = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/tasks`);
3196
+ await (0, import_promises2.mkdir)(tasksDir, { recursive: true });
3121
3197
  }
3122
3198
  async function writeWorkerOverlay(params) {
3123
3199
  const { teamName, workerName: workerName2, cwd } = params;
3124
3200
  const overlay = generateWorkerOverlay(params);
3125
- const overlayPath = (0, import_path10.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}/AGENTS.md`);
3126
- await (0, import_promises.mkdir)((0, import_path10.dirname)(overlayPath), { recursive: true });
3127
- await (0, import_promises.writeFile)(overlayPath, overlay, "utf-8");
3201
+ const overlayPath = (0, import_path12.join)(cwd, `.omcp/state/team/${teamName}/workers/${workerName2}/AGENTS.md`);
3202
+ await (0, import_promises2.mkdir)((0, import_path12.dirname)(overlayPath), { recursive: true });
3203
+ await (0, import_promises2.writeFile)(overlayPath, overlay, "utf-8");
3128
3204
  return overlayPath;
3129
3205
  }
3130
3206
 
@@ -3141,69 +3217,6 @@ init_tmux_session();
3141
3217
  var import_node_fs = require("node:fs");
3142
3218
  var import_node_path = require("node:path");
3143
3219
  var import_node_child_process = require("node:child_process");
3144
-
3145
- // src/team/fs-utils.ts
3146
- var import_fs8 = require("fs");
3147
- var import_path12 = require("path");
3148
- function atomicWriteJson(filePath, data, mode = 384) {
3149
- const dir = (0, import_path12.dirname)(filePath);
3150
- if (!(0, import_fs8.existsSync)(dir)) (0, import_fs8.mkdirSync)(dir, { recursive: true, mode: 448 });
3151
- const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
3152
- const serialized = JSON.stringify(data, null, 2) + "\n";
3153
- (0, import_fs8.writeFileSync)(tmpPath, serialized, { encoding: "utf-8", mode });
3154
- try {
3155
- (0, import_fs8.renameSync)(tmpPath, filePath);
3156
- } catch (err) {
3157
- if (process.platform === "win32" && err && typeof err === "object" && "code" in err && err.code === "EPERM") {
3158
- try {
3159
- (0, import_fs8.writeFileSync)(filePath, serialized, { encoding: "utf-8", mode });
3160
- } finally {
3161
- try {
3162
- (0, import_fs8.unlinkSync)(tmpPath);
3163
- } catch {
3164
- }
3165
- }
3166
- } else {
3167
- try {
3168
- (0, import_fs8.unlinkSync)(tmpPath);
3169
- } catch {
3170
- }
3171
- throw err;
3172
- }
3173
- }
3174
- }
3175
- function ensureDirWithMode(dirPath, mode = 448) {
3176
- if (!(0, import_fs8.existsSync)(dirPath)) (0, import_fs8.mkdirSync)(dirPath, { recursive: true, mode });
3177
- }
3178
- function safeRealpath(p) {
3179
- try {
3180
- return (0, import_fs8.realpathSync)(p);
3181
- } catch {
3182
- const segments = [];
3183
- let current = (0, import_path12.resolve)(p);
3184
- while (!(0, import_fs8.existsSync)(current)) {
3185
- segments.unshift((0, import_path12.basename)(current));
3186
- const parent = (0, import_path12.dirname)(current);
3187
- if (parent === current) break;
3188
- current = parent;
3189
- }
3190
- try {
3191
- return (0, import_path12.join)((0, import_fs8.realpathSync)(current), ...segments);
3192
- } catch {
3193
- return (0, import_path12.resolve)(p);
3194
- }
3195
- }
3196
- }
3197
- function validateResolvedPath(resolvedPath, expectedBase) {
3198
- const absResolved = safeRealpath(resolvedPath);
3199
- const absBase = safeRealpath(expectedBase);
3200
- const rel = (0, import_path12.relative)(absBase, absResolved);
3201
- if (rel.startsWith("..") || (0, import_path12.resolve)(absBase, rel) !== absResolved) {
3202
- throw new Error(`Path traversal detected: "${resolvedPath}" escapes base "${expectedBase}"`);
3203
- }
3204
- }
3205
-
3206
- // src/team/git-worktree.ts
3207
3220
  init_tmux_session();
3208
3221
  init_file_lock();
3209
3222
  function getWorktreePath(repoRoot, teamName, workerName2) {
@@ -3587,6 +3600,9 @@ function removeWorkerWorktree(teamName, workerName2, repoRoot) {
3587
3600
  forgetMetadataUnlocked(repoRoot, teamName, workerName2);
3588
3601
  });
3589
3602
  }
3603
+ function listTeamWorktrees(teamName, repoRoot) {
3604
+ return readMetadata(repoRoot, teamName);
3605
+ }
3590
3606
  function inspectTeamWorktreeCleanupSafety(teamName, repoRoot) {
3591
3607
  const metadata = readMetadataResult(repoRoot, teamName);
3592
3608
  const entries = metadata.entries;
@@ -4157,8 +4173,8 @@ function watchdogCliWorkers(runtime, intervalMs) {
4157
4173
  unresponsiveCounts.delete(wName);
4158
4174
  await markTaskFromDone(root, runtime.teamName, runtime.cwd, signal.taskId || active.taskId, signal.status, signal.summary);
4159
4175
  try {
4160
- const { unlink: unlink3 } = await import("fs/promises");
4161
- await unlink3(donePath);
4176
+ const { unlink: unlink4 } = await import("fs/promises");
4177
+ await unlink4(donePath);
4162
4178
  } catch {
4163
4179
  }
4164
4180
  await killWorkerPane(runtime, wName, active.paneId);
@@ -5056,9 +5072,9 @@ async function waitForSentinelReadiness(options = {}) {
5056
5072
 
5057
5073
  // src/team/runtime-v2.ts
5058
5074
  init_tmux_utils();
5059
- var import_path20 = require("path");
5060
- var import_fs18 = require("fs");
5061
- var import_promises7 = require("fs/promises");
5075
+ var import_path22 = require("path");
5076
+ var import_fs20 = require("fs");
5077
+ var import_promises10 = require("fs/promises");
5062
5078
  var import_perf_hooks = require("perf_hooks");
5063
5079
 
5064
5080
  // src/team/allocation-policy.ts
@@ -5271,10 +5287,10 @@ async function readJsonSafe2(filePath) {
5271
5287
  }
5272
5288
  }
5273
5289
  async function writeAtomic(filePath, data) {
5274
- const { writeFile: writeFile6 } = await import("fs/promises");
5290
+ const { writeFile: writeFile8 } = await import("fs/promises");
5275
5291
  await (0, import_promises5.mkdir)((0, import_path18.dirname)(filePath), { recursive: true });
5276
5292
  const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
5277
- await writeFile6(tmpPath, data, "utf-8");
5293
+ await writeFile8(tmpPath, data, "utf-8");
5278
5294
  const { rename: rename4 } = await import("fs/promises");
5279
5295
  await rename4(tmpPath, filePath);
5280
5296
  }
@@ -6198,13 +6214,996 @@ function cliWorkerOutputFilePath(teamStateRootAbs, workerName2) {
6198
6214
  return `${teamStateRootAbs.replaceAll("\\", "/")}/workers/${workerName2}/verdict.json`;
6199
6215
  }
6200
6216
 
6201
- // src/team/runtime-v2.ts
6217
+ // src/team/merge-orchestrator.ts
6218
+ var import_node_child_process3 = require("node:child_process");
6219
+ var import_node_fs2 = require("node:fs");
6220
+ var import_promises9 = require("node:fs/promises");
6221
+ var import_node_path2 = require("node:path");
6222
+
6223
+ // src/team/runtime-flags.ts
6202
6224
  function isRuntimeV2Enabled(env = process.env) {
6203
6225
  const raw = env.OMC_RUNTIME_V2;
6204
6226
  if (!raw) return true;
6205
6227
  const normalized = raw.trim().toLowerCase();
6206
6228
  return !["0", "false", "no", "off"].includes(normalized);
6207
6229
  }
6230
+
6231
+ // src/team/merge-orchestrator.ts
6232
+ init_tmux_session();
6233
+
6234
+ // src/team/merge-coordinator.ts
6235
+ var import_node_child_process2 = require("node:child_process");
6236
+ var BRANCH_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9/_.-]*$/;
6237
+ function validateBranchName(branch) {
6238
+ if (!BRANCH_NAME_RE.test(branch)) {
6239
+ throw new Error(`Invalid branch name: "${branch}" \u2014 must match ${BRANCH_NAME_RE}`);
6240
+ }
6241
+ }
6242
+ function checkMergeConflicts(workerBranch, baseBranch, repoRoot) {
6243
+ validateBranchName(workerBranch);
6244
+ validateBranchName(baseBranch);
6245
+ try {
6246
+ (0, import_node_child_process2.execFileSync)(
6247
+ "git",
6248
+ ["merge-tree", "--write-tree", baseBranch, workerBranch],
6249
+ { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6250
+ );
6251
+ return [];
6252
+ } catch (err) {
6253
+ const error = err;
6254
+ if (error.status === 1 && typeof error.stdout === "string") {
6255
+ const lines = error.stdout.split("\n");
6256
+ const conflicts = [];
6257
+ for (const line of lines) {
6258
+ const match = line.match(/^CONFLICT\s.*?:\s+.*?\s+in\s+(.+)$/);
6259
+ if (match) {
6260
+ conflicts.push(match[1].trim());
6261
+ }
6262
+ }
6263
+ return conflicts.length > 0 ? conflicts : ["(merge-tree reported conflicts)"];
6264
+ }
6265
+ }
6266
+ const mergeBase = (0, import_node_child_process2.execFileSync)(
6267
+ "git",
6268
+ ["merge-base", baseBranch, workerBranch],
6269
+ { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6270
+ ).trim();
6271
+ const baseDiff = (0, import_node_child_process2.execFileSync)(
6272
+ "git",
6273
+ ["diff", "--name-only", mergeBase, baseBranch],
6274
+ { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6275
+ ).trim();
6276
+ const workerDiff = (0, import_node_child_process2.execFileSync)(
6277
+ "git",
6278
+ ["diff", "--name-only", mergeBase, workerBranch],
6279
+ { cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
6280
+ ).trim();
6281
+ if (!baseDiff || !workerDiff) {
6282
+ return [];
6283
+ }
6284
+ const baseFiles = new Set(baseDiff.split("\n").filter((f) => f));
6285
+ const workerFiles = workerDiff.split("\n").filter((f) => f);
6286
+ return workerFiles.filter((f) => baseFiles.has(f));
6287
+ }
6288
+ function mergeWorkerBranch(workerBranch, baseBranch, repoRoot) {
6289
+ validateBranchName(workerBranch);
6290
+ validateBranchName(baseBranch);
6291
+ const workerName2 = workerBranch.split("/").pop() || workerBranch;
6292
+ try {
6293
+ try {
6294
+ (0, import_node_child_process2.execFileSync)("git", ["diff-index", "--quiet", "HEAD", "--"], {
6295
+ cwd: repoRoot,
6296
+ stdio: "pipe"
6297
+ });
6298
+ } catch {
6299
+ throw new Error("Working tree has uncommitted changes \u2014 commit or stash before merging");
6300
+ }
6301
+ (0, import_node_child_process2.execFileSync)("git", ["checkout", baseBranch], {
6302
+ cwd: repoRoot,
6303
+ stdio: "pipe"
6304
+ });
6305
+ (0, import_node_child_process2.execFileSync)("git", ["merge", "--no-ff", "-m", `Merge ${workerBranch} into ${baseBranch}`, workerBranch], {
6306
+ cwd: repoRoot,
6307
+ stdio: "pipe"
6308
+ });
6309
+ const mergeCommit = (0, import_node_child_process2.execFileSync)("git", ["rev-parse", "HEAD"], {
6310
+ cwd: repoRoot,
6311
+ encoding: "utf-8",
6312
+ stdio: "pipe"
6313
+ }).trim();
6314
+ return {
6315
+ workerName: workerName2,
6316
+ branch: workerBranch,
6317
+ success: true,
6318
+ conflicts: [],
6319
+ mergeCommit
6320
+ };
6321
+ } catch (_err) {
6322
+ try {
6323
+ (0, import_node_child_process2.execFileSync)("git", ["merge", "--abort"], { cwd: repoRoot, stdio: "pipe" });
6324
+ } catch {
6325
+ }
6326
+ const conflicts = checkMergeConflicts(workerBranch, baseBranch, repoRoot);
6327
+ return {
6328
+ workerName: workerName2,
6329
+ branch: workerBranch,
6330
+ success: false,
6331
+ conflicts
6332
+ };
6333
+ }
6334
+ }
6335
+
6336
+ // src/team/leader-inbox.ts
6337
+ var import_promises7 = require("fs/promises");
6338
+ var import_fs18 = require("fs");
6339
+ var import_path20 = require("path");
6340
+ init_tmux_session();
6341
+ var LEADER_INBOX_HEADER = `# Leader Inbox
6342
+
6343
+ Runtime notifications (merge conflicts, rebase events, etc.) appear here.
6344
+ Check this file periodically and after long-running operations.
6345
+
6346
+ ---
6347
+ `;
6348
+ function leaderInboxPath(teamName, cwd) {
6349
+ const safe = sanitizeName(teamName);
6350
+ return (0, import_path20.join)(cwd, `.omcp/state/team/${safe}/leader/inbox.md`);
6351
+ }
6352
+ async function ensureLeaderInbox(teamName, cwd) {
6353
+ const inboxPath = leaderInboxPath(teamName, cwd);
6354
+ validateResolvedPath(inboxPath, cwd);
6355
+ await (0, import_promises7.mkdir)((0, import_path20.dirname)(inboxPath), { recursive: true });
6356
+ if (!(0, import_fs18.existsSync)(inboxPath)) {
6357
+ await (0, import_promises7.writeFile)(inboxPath, LEADER_INBOX_HEADER, "utf-8");
6358
+ }
6359
+ return inboxPath;
6360
+ }
6361
+ async function appendToLeaderInbox(teamName, message, cwd) {
6362
+ const inboxPath = leaderInboxPath(teamName, cwd);
6363
+ validateResolvedPath(inboxPath, cwd);
6364
+ await (0, import_promises7.mkdir)((0, import_path20.dirname)(inboxPath), { recursive: true });
6365
+ await (0, import_promises7.appendFile)(inboxPath, `
6366
+
6367
+ ---
6368
+ ${message}`, "utf-8");
6369
+ }
6370
+ function extendLeaderBootstrapPrompt(teamName) {
6371
+ const safe = sanitizeName(teamName);
6372
+ const path4 = `.omcp/state/team/${safe}/leader/inbox.md`;
6373
+ return `Runtime notifications appear at ${path4} \u2014 check this file periodically and after long-running operations.`;
6374
+ }
6375
+
6376
+ // src/team/conflict-mailbox.ts
6377
+ function sanitizeConflictPath(path4) {
6378
+ return path4.replace(/[`\r\n]/g, "?");
6379
+ }
6380
+ function formatMergeConflictForLeader(args) {
6381
+ const { workerName: workerName2, workerBranch, leaderBranch, conflictingFiles, mergeBaseSha, observedAt } = args;
6382
+ const ts = new Date(observedAt).toISOString();
6383
+ const safeFiles = conflictingFiles.map(sanitizeConflictPath);
6384
+ const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
6385
+ return `### Merge conflict: ${workerName2} \u2192 ${leaderBranch}
6386
+
6387
+ **Worker branch:** \`${workerBranch}\`
6388
+ **Leader branch:** \`${leaderBranch}\`
6389
+ **Merge base:** \`${mergeBaseSha}\`
6390
+ **Observed at:** ${ts}
6391
+
6392
+ **Conflicting files:**
6393
+ ${fileList}
6394
+
6395
+ **Leader: choose strategy.** To resolve, run:
6396
+
6397
+ \`\`\`sh
6398
+ git checkout ${leaderBranch} && git merge --no-ff ${workerBranch}
6399
+ # resolve conflicts in the files listed above
6400
+ git add ${safeFiles.join(" ")}
6401
+ git commit
6402
+ \`\`\`
6403
+
6404
+ Or abort with \`git merge --abort\` to defer resolution.`;
6405
+ }
6406
+ function formatRebaseConflictForWorker(args) {
6407
+ const { workerName: workerName2, workerBranch, leaderBranch, conflictingFiles, baseSha, worktreePath, observedAt } = args;
6408
+ const ts = new Date(observedAt).toISOString();
6409
+ const safeFiles = conflictingFiles.map(sanitizeConflictPath);
6410
+ const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
6411
+ return `### Rebase conflict: ${workerName2} onto ${leaderBranch}
6412
+
6413
+ **Worker branch:** \`${workerBranch}\`
6414
+ **Base branch:** \`${leaderBranch}\`
6415
+ **Base SHA:** \`${baseSha}\`
6416
+ **Worktree:** \`${worktreePath}\`
6417
+ **Observed at:** ${ts}
6418
+
6419
+ **Conflicting files:**
6420
+ ${fileList}
6421
+
6422
+ Resolve conflicts in your own pane, then \`git add <files>\` and \`git rebase --continue\`.
6423
+ Cadence stays paused until \`.git/rebase-merge\` is gone.
6424
+
6425
+ Or run \`git rebase --abort\` to bail and return to the pre-rebase state.`;
6426
+ }
6427
+
6428
+ // src/team/worker-commit-cadence.ts
6429
+ var import_fs19 = require("fs");
6430
+ var import_promises8 = require("fs/promises");
6431
+ var import_path21 = require("path");
6432
+ var import_child_process5 = require("child_process");
6433
+ var SENTINEL_FILENAME = ".hook-paused";
6434
+ var HOOK_MATCHER = "Write|Edit|MultiEdit";
6435
+ var DEFAULT_POLL_DEBOUNCE_MS = 3e3;
6436
+ var WORKER_NAME_RE = /^[A-Za-z0-9_-]{1,50}$/;
6437
+ function assertSafeWorkerName(workerName2) {
6438
+ if (!WORKER_NAME_RE.test(workerName2)) {
6439
+ throw new Error(
6440
+ `Invalid worker name for shell hook: "${workerName2}" \u2014 must match ${WORKER_NAME_RE}`
6441
+ );
6442
+ }
6443
+ }
6444
+ function buildHookCommand(workerName2) {
6445
+ assertSafeWorkerName(workerName2);
6446
+ 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 ${workerName2} at $(date -Iseconds)")'`;
6447
+ }
6448
+ async function mergeSettingsWithHook(settingsPath, hookCommand) {
6449
+ let existing = { hooks: { PostToolUse: [] } };
6450
+ try {
6451
+ const raw = await (0, import_promises8.readFile)(settingsPath, "utf-8");
6452
+ const parsed = JSON.parse(raw);
6453
+ existing = {
6454
+ ...parsed,
6455
+ hooks: {
6456
+ PostToolUse: [],
6457
+ ...parsed.hooks ?? {}
6458
+ }
6459
+ };
6460
+ } catch {
6461
+ }
6462
+ const filteredHooks = (existing.hooks.PostToolUse ?? []).filter(
6463
+ (h) => h.matcher !== HOOK_MATCHER
6464
+ );
6465
+ const newEntry = {
6466
+ matcher: HOOK_MATCHER,
6467
+ hooks: [{ type: "command", command: hookCommand }]
6468
+ };
6469
+ return {
6470
+ ...existing,
6471
+ hooks: {
6472
+ ...existing.hooks,
6473
+ PostToolUse: [...filteredHooks, newEntry]
6474
+ }
6475
+ };
6476
+ }
6477
+ async function installPostToolUseHook(worktreePath, workerName2) {
6478
+ assertSafeWorkerName(workerName2);
6479
+ if (isHookPaused(worktreePath)) {
6480
+ return;
6481
+ }
6482
+ const claudeDir = (0, import_path21.join)(worktreePath, ".claude");
6483
+ await (0, import_promises8.mkdir)(claudeDir, { recursive: true });
6484
+ const settingsPath = (0, import_path21.join)(claudeDir, "settings.json");
6485
+ const hookCommand = buildHookCommand(workerName2);
6486
+ const merged = await mergeSettingsWithHook(settingsPath, hookCommand);
6487
+ await (0, import_promises8.writeFile)(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
6488
+ }
6489
+ async function pauseHookViaSentinel(worktreePath) {
6490
+ const sentinelPath = (0, import_path21.join)(worktreePath, SENTINEL_FILENAME);
6491
+ await (0, import_promises8.mkdir)((0, import_path21.dirname)(sentinelPath), { recursive: true });
6492
+ await (0, import_promises8.writeFile)(sentinelPath, "", "utf-8");
6493
+ }
6494
+ async function resumeHookViaSentinel(worktreePath) {
6495
+ const sentinelPath = (0, import_path21.join)(worktreePath, SENTINEL_FILENAME);
6496
+ try {
6497
+ await (0, import_promises8.unlink)(sentinelPath);
6498
+ } catch {
6499
+ }
6500
+ }
6501
+ function isHookPaused(worktreePath) {
6502
+ return (0, import_fs19.existsSync)((0, import_path21.join)(worktreePath, SENTINEL_FILENAME));
6503
+ }
6504
+ function startFallbackPoller(worktreePath, workerName2, opts) {
6505
+ assertSafeWorkerName(workerName2);
6506
+ const debounceMs = opts?.intervalMs ?? DEFAULT_POLL_DEBOUNCE_MS;
6507
+ let debounceTimer = null;
6508
+ let stopped = false;
6509
+ const runAutoCommit = () => {
6510
+ if (stopped) return;
6511
+ if (isHookPaused(worktreePath)) return;
6512
+ const cmd = buildHookCommand(workerName2);
6513
+ (0, import_child_process5.exec)(cmd, { cwd: worktreePath }, (_err) => {
6514
+ });
6515
+ };
6516
+ const scheduleDebounce = () => {
6517
+ if (stopped) return;
6518
+ if (debounceTimer !== null) {
6519
+ clearTimeout(debounceTimer);
6520
+ }
6521
+ debounceTimer = setTimeout(() => {
6522
+ debounceTimer = null;
6523
+ runAutoCommit();
6524
+ }, debounceMs);
6525
+ };
6526
+ const watcher = (0, import_fs19.watch)(worktreePath, { recursive: true }, (eventType, filename) => {
6527
+ if (stopped) return;
6528
+ if (filename && (filename.startsWith(".git") || filename.startsWith(".git/"))) return;
6529
+ scheduleDebounce();
6530
+ });
6531
+ return {
6532
+ stop() {
6533
+ stopped = true;
6534
+ if (debounceTimer !== null) {
6535
+ clearTimeout(debounceTimer);
6536
+ debounceTimer = null;
6537
+ }
6538
+ watcher.close();
6539
+ }
6540
+ };
6541
+ }
6542
+ async function installCommitCadence(ctx) {
6543
+ if (!ctx.enabled) {
6544
+ return { method: "none" };
6545
+ }
6546
+ if (ctx.agentType === "claude") {
6547
+ await installPostToolUseHook(ctx.worktreePath, ctx.workerName);
6548
+ return { method: "hook" };
6549
+ }
6550
+ return { method: "fallback-poll" };
6551
+ }
6552
+ async function uninstallCommitCadence(ctx) {
6553
+ if (ctx.agentType !== "claude") return;
6554
+ const settingsPath = (0, import_path21.join)(ctx.worktreePath, ".claude", "settings.json");
6555
+ try {
6556
+ const raw = await (0, import_promises8.readFile)(settingsPath, "utf-8");
6557
+ const parsed = JSON.parse(raw);
6558
+ const filtered = (parsed.hooks?.PostToolUse ?? []).filter(
6559
+ (h) => h.matcher !== HOOK_MATCHER
6560
+ );
6561
+ const updated = {
6562
+ ...parsed,
6563
+ hooks: {
6564
+ ...parsed.hooks,
6565
+ PostToolUse: filtered
6566
+ }
6567
+ };
6568
+ await (0, import_promises8.writeFile)(settingsPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
6569
+ } catch {
6570
+ }
6571
+ }
6572
+
6573
+ // src/team/merge-orchestrator.ts
6574
+ var DEFAULT_POLL_INTERVAL_MS = 1e3;
6575
+ var DEFAULT_DRAIN_TIMEOUT_MS = 1e4;
6576
+ function mergerWorktreePathFor(repoRoot, teamName) {
6577
+ return (0, import_node_path2.join)(repoRoot, ".omcp", "team", sanitizeName(teamName), "merger");
6578
+ }
6579
+ function persistedStatePath(repoRoot, teamName) {
6580
+ return (0, import_node_path2.join)(
6581
+ repoRoot,
6582
+ ".omcp",
6583
+ "state",
6584
+ "team",
6585
+ sanitizeName(teamName),
6586
+ "auto-merge-state.json"
6587
+ );
6588
+ }
6589
+ function teardownAuditPath(repoRoot, teamName) {
6590
+ return (0, import_node_path2.join)(
6591
+ repoRoot,
6592
+ ".omcp",
6593
+ "state",
6594
+ "team",
6595
+ sanitizeName(teamName),
6596
+ "teardown-audit.jsonl"
6597
+ );
6598
+ }
6599
+ function orchestratorEventLogPath(repoRoot, teamName) {
6600
+ return (0, import_node_path2.join)(
6601
+ repoRoot,
6602
+ ".omcp",
6603
+ "state",
6604
+ "team",
6605
+ sanitizeName(teamName),
6606
+ "orchestrator-events.jsonl"
6607
+ );
6608
+ }
6609
+ function assertLeaderBranchAllowed(leaderBranch) {
6610
+ const stripped = leaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
6611
+ if (stripped === "main" || stripped === "master") {
6612
+ throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
6613
+ }
6614
+ }
6615
+ function assertRuntimeV2Gate() {
6616
+ if (!isRuntimeV2Enabled()) {
6617
+ throw new Error("auto-merge requires runtime v2 (OMC_RUNTIME_V2 is explicitly disabled).");
6618
+ }
6619
+ }
6620
+ async function appendEvent(repoRoot, teamName, event) {
6621
+ const path4 = orchestratorEventLogPath(repoRoot, teamName);
6622
+ await (0, import_promises9.mkdir)((0, import_node_path2.dirname)(path4), { recursive: true });
6623
+ const full = {
6624
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
6625
+ team: teamName,
6626
+ ...event
6627
+ };
6628
+ await (0, import_promises9.appendFile)(path4, `${JSON.stringify(full)}
6629
+ `, "utf-8");
6630
+ }
6631
+ function createMutex() {
6632
+ let lock = Promise.resolve();
6633
+ return (fn) => {
6634
+ const next = lock.then(fn, fn);
6635
+ lock = next.catch(() => void 0);
6636
+ return next;
6637
+ };
6638
+ }
6639
+ function gitRevParseHead(repoRoot, branch) {
6640
+ return (0, import_node_child_process3.execFileSync)("git", ["rev-parse", `refs/heads/${branch}`], {
6641
+ cwd: repoRoot,
6642
+ encoding: "utf-8",
6643
+ stdio: "pipe"
6644
+ }).trim();
6645
+ }
6646
+ function gitPath(worktreePath, gitPathName) {
6647
+ try {
6648
+ const resolved = (0, import_node_child_process3.execFileSync)("git", ["rev-parse", "--git-path", gitPathName], {
6649
+ cwd: worktreePath,
6650
+ encoding: "utf-8",
6651
+ stdio: "pipe"
6652
+ }).trim();
6653
+ if (resolved) return resolved;
6654
+ } catch {
6655
+ }
6656
+ return (0, import_node_path2.join)(worktreePath, ".git", gitPathName);
6657
+ }
6658
+ function isRebaseInProgress(worktreePath) {
6659
+ return (0, import_node_fs2.existsSync)(gitPath(worktreePath, "rebase-merge"));
6660
+ }
6661
+ function isWorktreeRegistered(repoRoot, wtPath) {
6662
+ try {
6663
+ const out = (0, import_node_child_process3.execFileSync)("git", ["worktree", "list", "--porcelain"], {
6664
+ cwd: repoRoot,
6665
+ encoding: "utf-8",
6666
+ stdio: "pipe"
6667
+ });
6668
+ for (const line of out.split("\n")) {
6669
+ if (line.startsWith("worktree ")) {
6670
+ if (line.slice("worktree ".length).trim() === wtPath) return true;
6671
+ }
6672
+ }
6673
+ } catch {
6674
+ }
6675
+ return false;
6676
+ }
6677
+ function ensureMergerWorktree(repoRoot, mergerPath, leaderBranch) {
6678
+ ensureDirWithMode((0, import_node_path2.dirname)(mergerPath));
6679
+ if ((0, import_node_fs2.existsSync)(mergerPath) && isWorktreeRegistered(repoRoot, mergerPath)) {
6680
+ return;
6681
+ }
6682
+ (0, import_node_child_process3.execFileSync)("git", ["worktree", "add", "--force", mergerPath, leaderBranch], {
6683
+ cwd: repoRoot,
6684
+ stdio: "pipe"
6685
+ });
6686
+ }
6687
+ function preflightMergerWorktree(mergerPath, leaderBranch) {
6688
+ try {
6689
+ (0, import_node_child_process3.execFileSync)("git", ["fetch", "--no-tags", "origin", leaderBranch], {
6690
+ cwd: mergerPath,
6691
+ stdio: "pipe"
6692
+ });
6693
+ } catch {
6694
+ }
6695
+ (0, import_node_child_process3.execFileSync)("git", ["reset", "--hard", leaderBranch], {
6696
+ cwd: mergerPath,
6697
+ stdio: "pipe"
6698
+ });
6699
+ }
6700
+ function parseUUFiles(porcelainOutput) {
6701
+ const files = [];
6702
+ for (const line of porcelainOutput.split("\n")) {
6703
+ if (line.startsWith("UU ")) {
6704
+ files.push(line.slice(3).trim());
6705
+ } else if (line.startsWith("AA ") || line.startsWith("DD ")) {
6706
+ files.push(line.slice(3).trim());
6707
+ }
6708
+ }
6709
+ return files;
6710
+ }
6711
+ async function startMergeOrchestrator(config) {
6712
+ assertRuntimeV2Gate();
6713
+ assertLeaderBranchAllowed(config.leaderBranch);
6714
+ validateBranchName(config.leaderBranch);
6715
+ const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
6716
+ const drainTimeoutMs = config.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;
6717
+ const mergerPath = mergerWorktreePathFor(config.repoRoot, config.teamName);
6718
+ validateResolvedPath(mergerPath, config.repoRoot);
6719
+ ensureMergerWorktree(config.repoRoot, mergerPath, config.leaderBranch);
6720
+ await ensureLeaderInbox(config.teamName, config.cwd);
6721
+ const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
6722
+ let persisted = { lastShas: {} };
6723
+ if ((0, import_node_fs2.existsSync)(persistedPath)) {
6724
+ try {
6725
+ const { readFileSync: readFileSync12 } = await import("node:fs");
6726
+ persisted = JSON.parse(readFileSync12(persistedPath, "utf-8"));
6727
+ } catch {
6728
+ persisted = { lastShas: {} };
6729
+ }
6730
+ }
6731
+ const workers = /* @__PURE__ */ new Map();
6732
+ const pausedWorkers = /* @__PURE__ */ new Set();
6733
+ const mutex = createMutex();
6734
+ let stopped = false;
6735
+ function persistState() {
6736
+ const payload = {
6737
+ lastShas: Object.fromEntries(
6738
+ Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
6739
+ )
6740
+ };
6741
+ atomicWriteJson(persistedPath, payload);
6742
+ }
6743
+ async function fanOutRebase(triggeringWorker) {
6744
+ for (const other of workers.values()) {
6745
+ if (other.workerName === triggeringWorker) continue;
6746
+ const wtPath = other.workerWorktreePath;
6747
+ if (isRebaseInProgress(wtPath)) {
6748
+ await appendEvent(config.repoRoot, config.teamName, {
6749
+ type: "rebase_skipped_in_progress",
6750
+ worker: other.workerName,
6751
+ reason: "rebase-already-in-progress"
6752
+ });
6753
+ continue;
6754
+ }
6755
+ await appendEvent(config.repoRoot, config.teamName, {
6756
+ type: "rebase_triggered",
6757
+ worker: other.workerName
6758
+ });
6759
+ await pauseHookViaSentinel(wtPath);
6760
+ pausedWorkers.add(other.workerName);
6761
+ try {
6762
+ (0, import_node_child_process3.execFileSync)("git", ["fetch", "--no-tags", "origin", config.leaderBranch], {
6763
+ cwd: wtPath,
6764
+ stdio: "pipe"
6765
+ });
6766
+ } catch {
6767
+ }
6768
+ try {
6769
+ (0, import_node_child_process3.execFileSync)("git", ["rebase", config.leaderBranch], {
6770
+ cwd: wtPath,
6771
+ stdio: "pipe"
6772
+ });
6773
+ await resumeHookViaSentinel(wtPath);
6774
+ pausedWorkers.delete(other.workerName);
6775
+ await appendEvent(config.repoRoot, config.teamName, {
6776
+ type: "rebase_succeeded",
6777
+ worker: other.workerName
6778
+ });
6779
+ } catch {
6780
+ let conflictingFiles = [];
6781
+ try {
6782
+ const status = (0, import_node_child_process3.execFileSync)("git", ["status", "--porcelain"], {
6783
+ cwd: wtPath,
6784
+ encoding: "utf-8",
6785
+ stdio: "pipe"
6786
+ });
6787
+ conflictingFiles = parseUUFiles(status);
6788
+ } catch {
6789
+ conflictingFiles = ["(rebase status unavailable)"];
6790
+ }
6791
+ const baseSha = (() => {
6792
+ try {
6793
+ return (0, import_node_child_process3.execFileSync)("git", ["rev-parse", `refs/heads/${config.leaderBranch}`], {
6794
+ cwd: config.repoRoot,
6795
+ encoding: "utf-8",
6796
+ stdio: "pipe"
6797
+ }).trim();
6798
+ } catch {
6799
+ return "unknown";
6800
+ }
6801
+ })();
6802
+ const message = formatRebaseConflictForWorker({
6803
+ workerName: other.workerName,
6804
+ workerBranch: other.workerBranch,
6805
+ leaderBranch: config.leaderBranch,
6806
+ conflictingFiles,
6807
+ baseSha,
6808
+ worktreePath: wtPath,
6809
+ observedAt: Date.now()
6810
+ });
6811
+ try {
6812
+ await appendToInbox(config.teamName, other.workerName, message, config.cwd);
6813
+ } catch {
6814
+ }
6815
+ await appendEvent(config.repoRoot, config.teamName, {
6816
+ type: "rebase_conflict",
6817
+ worker: other.workerName,
6818
+ data: { conflictingFiles }
6819
+ });
6820
+ }
6821
+ }
6822
+ }
6823
+ async function attemptMergeForWorker(entry) {
6824
+ await mutex(async () => {
6825
+ const targetSha = entry.lastObservedSha;
6826
+ await appendEvent(config.repoRoot, config.teamName, {
6827
+ type: "merge_attempted",
6828
+ worker: entry.workerName,
6829
+ data: { targetSha }
6830
+ });
6831
+ try {
6832
+ preflightMergerWorktree(mergerPath, config.leaderBranch);
6833
+ } catch (err) {
6834
+ const reason = err instanceof Error ? err.message : String(err);
6835
+ entry.consecutiveFailures += 1;
6836
+ await appendEvent(config.repoRoot, config.teamName, {
6837
+ type: "merge_conflict",
6838
+ worker: entry.workerName,
6839
+ reason: `preflight_failed:${reason}`
6840
+ });
6841
+ return;
6842
+ }
6843
+ const conflicts = checkMergeConflicts(
6844
+ entry.workerBranch,
6845
+ config.leaderBranch,
6846
+ mergerPath
6847
+ );
6848
+ if (conflicts.length > 0) {
6849
+ let mergeBaseSha = "unknown";
6850
+ try {
6851
+ mergeBaseSha = (0, import_node_child_process3.execFileSync)(
6852
+ "git",
6853
+ ["merge-base", config.leaderBranch, entry.workerBranch],
6854
+ { cwd: mergerPath, encoding: "utf-8", stdio: "pipe" }
6855
+ ).trim();
6856
+ } catch {
6857
+ }
6858
+ const message = formatMergeConflictForLeader({
6859
+ workerName: entry.workerName,
6860
+ workerBranch: entry.workerBranch,
6861
+ leaderBranch: config.leaderBranch,
6862
+ conflictingFiles: conflicts,
6863
+ mergeBaseSha,
6864
+ observedAt: Date.now()
6865
+ });
6866
+ try {
6867
+ await appendToLeaderInbox(config.teamName, message, config.cwd);
6868
+ } catch {
6869
+ }
6870
+ await appendEvent(config.repoRoot, config.teamName, {
6871
+ type: "merge_conflict",
6872
+ worker: entry.workerName,
6873
+ data: { conflictingFiles: conflicts, mergeBaseSha }
6874
+ });
6875
+ entry.consecutiveFailures += 1;
6876
+ return;
6877
+ }
6878
+ const result = mergeWorkerBranch(
6879
+ entry.workerBranch,
6880
+ config.leaderBranch,
6881
+ mergerPath
6882
+ );
6883
+ if (!result.success) {
6884
+ const message = formatMergeConflictForLeader({
6885
+ workerName: entry.workerName,
6886
+ workerBranch: entry.workerBranch,
6887
+ leaderBranch: config.leaderBranch,
6888
+ conflictingFiles: result.conflicts.length > 0 ? result.conflicts : ["(merge failed after clean check)"],
6889
+ mergeBaseSha: "unknown",
6890
+ observedAt: Date.now()
6891
+ });
6892
+ try {
6893
+ await appendToLeaderInbox(config.teamName, message, config.cwd);
6894
+ } catch {
6895
+ }
6896
+ await appendEvent(config.repoRoot, config.teamName, {
6897
+ type: "merge_conflict",
6898
+ worker: entry.workerName,
6899
+ data: { conflictingFiles: result.conflicts }
6900
+ });
6901
+ entry.consecutiveFailures += 1;
6902
+ return;
6903
+ }
6904
+ entry.lastMergedSha = targetSha;
6905
+ entry.consecutiveFailures = 0;
6906
+ await appendEvent(config.repoRoot, config.teamName, {
6907
+ type: "merge_succeeded",
6908
+ worker: entry.workerName,
6909
+ data: { mergeCommit: result.mergeCommit, targetSha }
6910
+ });
6911
+ if (stopped) return;
6912
+ await fanOutRebase(entry.workerName);
6913
+ });
6914
+ }
6915
+ async function runPollOnce() {
6916
+ if (stopped) return;
6917
+ for (const entry of workers.values()) {
6918
+ const skipModulo = Math.min(30, Math.pow(2, entry.consecutiveFailures));
6919
+ if (skipModulo > 1 && pollTickCount % skipModulo !== 0) {
6920
+ continue;
6921
+ }
6922
+ if (pausedWorkers.has(entry.workerName)) {
6923
+ if (!isRebaseInProgress(entry.workerWorktreePath)) {
6924
+ await handleRebaseResolution(entry);
6925
+ } else {
6926
+ continue;
6927
+ }
6928
+ }
6929
+ let currentSha;
6930
+ try {
6931
+ currentSha = gitRevParseHead(config.repoRoot, entry.workerBranch);
6932
+ } catch (err) {
6933
+ entry.consecutiveFailures += 1;
6934
+ const reason = err instanceof Error ? err.message : String(err);
6935
+ await appendEvent(config.repoRoot, config.teamName, {
6936
+ type: "commit_observed",
6937
+ worker: entry.workerName,
6938
+ reason: `rev_parse_failed:${reason}`
6939
+ });
6940
+ continue;
6941
+ }
6942
+ if (currentSha && currentSha !== entry.lastObservedSha) {
6943
+ entry.lastObservedSha = currentSha;
6944
+ try {
6945
+ persistState();
6946
+ } catch {
6947
+ }
6948
+ await appendEvent(config.repoRoot, config.teamName, {
6949
+ type: "commit_observed",
6950
+ worker: entry.workerName,
6951
+ data: { sha: currentSha }
6952
+ });
6953
+ try {
6954
+ await attemptMergeForWorker(entry);
6955
+ } catch (err) {
6956
+ entry.consecutiveFailures += 1;
6957
+ const reason = err instanceof Error ? err.message : String(err);
6958
+ await appendEvent(config.repoRoot, config.teamName, {
6959
+ type: "merge_conflict",
6960
+ worker: entry.workerName,
6961
+ reason: `merge_threw:${reason}`
6962
+ });
6963
+ }
6964
+ }
6965
+ }
6966
+ }
6967
+ async function handleRebaseResolution(entry) {
6968
+ pausedWorkers.delete(entry.workerName);
6969
+ try {
6970
+ const status = (0, import_node_child_process3.execFileSync)("git", ["status", "--porcelain"], {
6971
+ cwd: entry.workerWorktreePath,
6972
+ encoding: "utf-8",
6973
+ stdio: "pipe"
6974
+ }).trim();
6975
+ if (status.length > 0) {
6976
+ const dirtyFiles = status.split("\n").map((l) => l.trim().replace(/^\S+\s+/, "")).filter((s) => s.length > 0);
6977
+ const audit = `## Auto-commit audit: the following files were modified during rebase pause and will be folded into the next auto-commit:
6978
+ ${dirtyFiles.map((f) => `- \`${f}\``).join("\n")}`;
6979
+ try {
6980
+ await appendToInbox(config.teamName, entry.workerName, audit, config.cwd);
6981
+ } catch {
6982
+ }
6983
+ }
6984
+ } catch {
6985
+ }
6986
+ await resumeHookViaSentinel(entry.workerWorktreePath);
6987
+ await appendEvent(config.repoRoot, config.teamName, {
6988
+ type: "rebase_resolved",
6989
+ worker: entry.workerName
6990
+ });
6991
+ }
6992
+ let pollTickCount = 0;
6993
+ const interval = setInterval(() => {
6994
+ pollTickCount += 1;
6995
+ void runPollOnce().catch(() => {
6996
+ });
6997
+ }, pollIntervalMs);
6998
+ if (typeof interval.unref === "function") interval.unref();
6999
+ return {
7000
+ async registerWorker(workerName2) {
7001
+ if (workers.has(workerName2)) return;
7002
+ const workerBranch = getBranchName(config.teamName, workerName2);
7003
+ validateBranchName(workerBranch);
7004
+ const wtPath = getWorktreePath(config.repoRoot, config.teamName, workerName2);
7005
+ let seedSha = persisted.lastShas[workerName2] ?? "";
7006
+ if (!seedSha) {
7007
+ try {
7008
+ seedSha = gitRevParseHead(config.repoRoot, workerBranch);
7009
+ } catch {
7010
+ seedSha = "";
7011
+ }
7012
+ }
7013
+ workers.set(workerName2, {
7014
+ workerName: workerName2,
7015
+ workerBranch,
7016
+ workerWorktreePath: wtPath,
7017
+ lastObservedSha: seedSha,
7018
+ lastMergedSha: seedSha,
7019
+ consecutiveFailures: 0
7020
+ });
7021
+ try {
7022
+ persistState();
7023
+ } catch {
7024
+ }
7025
+ },
7026
+ async unregisterWorker(workerName2) {
7027
+ workers.delete(workerName2);
7028
+ pausedWorkers.delete(workerName2);
7029
+ try {
7030
+ persistState();
7031
+ } catch {
7032
+ }
7033
+ },
7034
+ async pollOnce() {
7035
+ await runPollOnce();
7036
+ },
7037
+ async drainAndStop() {
7038
+ stopped = true;
7039
+ clearInterval(interval);
7040
+ const start = Date.now();
7041
+ const unmerged = [];
7042
+ const candidates = Array.from(workers.values()).filter(
7043
+ (w) => w.lastObservedSha && w.lastObservedSha !== w.lastMergedSha
7044
+ );
7045
+ for (const entry of candidates) {
7046
+ const remaining = drainTimeoutMs - (Date.now() - start);
7047
+ if (remaining <= 0) {
7048
+ unmerged.push({ workerName: entry.workerName, reason: "drain-timeout" });
7049
+ continue;
7050
+ }
7051
+ const merged = await Promise.race([
7052
+ (async () => {
7053
+ try {
7054
+ await attemptMergeForWorker(entry);
7055
+ return true;
7056
+ } catch {
7057
+ return false;
7058
+ }
7059
+ })(),
7060
+ new Promise((resolve6) => {
7061
+ const t = setTimeout(() => resolve6(false), remaining);
7062
+ if (typeof t.unref === "function") t.unref();
7063
+ })
7064
+ ]);
7065
+ if (!merged || entry.lastMergedSha !== entry.lastObservedSha) {
7066
+ unmerged.push({
7067
+ workerName: entry.workerName,
7068
+ reason: merged ? "merge-conflict" : "drain-timeout"
7069
+ });
7070
+ }
7071
+ }
7072
+ if (unmerged.length > 0) {
7073
+ const auditPath = teardownAuditPath(config.repoRoot, config.teamName);
7074
+ await (0, import_promises9.mkdir)((0, import_node_path2.dirname)(auditPath), { recursive: true });
7075
+ for (const u of unmerged) {
7076
+ const row = JSON.stringify({
7077
+ type: "unmerged_at_shutdown",
7078
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7079
+ team: config.teamName,
7080
+ worker: u.workerName,
7081
+ reason: u.reason
7082
+ });
7083
+ try {
7084
+ await (0, import_promises9.appendFile)(auditPath, `${row}
7085
+ `, "utf-8");
7086
+ } catch {
7087
+ }
7088
+ }
7089
+ const message = `## Teardown audit: unmerged worker branches at shutdown
7090
+
7091
+ ${unmerged.map((u) => `- ${u.workerName}: ${u.reason}`).join("\n")}`;
7092
+ try {
7093
+ await appendToLeaderInbox(config.teamName, message, config.cwd);
7094
+ } catch {
7095
+ }
7096
+ }
7097
+ return { unmerged };
7098
+ },
7099
+ getState() {
7100
+ return {
7101
+ workers: Array.from(workers.keys()),
7102
+ lastShas: Object.fromEntries(
7103
+ Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
7104
+ ),
7105
+ mergerWorktreePath: mergerPath
7106
+ };
7107
+ }
7108
+ };
7109
+ }
7110
+ async function recoverFromRestart(config) {
7111
+ const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
7112
+ let persistedShasLoaded = 0;
7113
+ if ((0, import_node_fs2.existsSync)(persistedPath)) {
7114
+ try {
7115
+ const { readFileSync: readFileSync12 } = await import("node:fs");
7116
+ const persisted = JSON.parse(readFileSync12(persistedPath, "utf-8"));
7117
+ persistedShasLoaded = Object.keys(persisted.lastShas ?? {}).length;
7118
+ } catch {
7119
+ persistedShasLoaded = 0;
7120
+ }
7121
+ }
7122
+ const orphanedRebases = [];
7123
+ let entries = [];
7124
+ try {
7125
+ entries = listTeamWorktrees(config.teamName, config.repoRoot).map((w) => ({
7126
+ workerName: w.workerName,
7127
+ path: w.path
7128
+ }));
7129
+ } catch {
7130
+ entries = [];
7131
+ }
7132
+ for (const entry of entries) {
7133
+ if (!isRebaseInProgress(entry.path)) continue;
7134
+ orphanedRebases.push(entry.workerName);
7135
+ const message = `### Runtime restart recovery \u2014 your branch is mid-rebase
7136
+
7137
+ Runtime restarted while your branch was mid-rebase onto \`${config.leaderBranch}\`.
7138
+
7139
+ **Worktree:** \`${entry.path}\`
7140
+
7141
+ Cadence remains paused. Resolve and \`git rebase --continue\`, or \`git rebase --abort\` to bail.
7142
+ Cadence resumes once the git rebase state is gone.`;
7143
+ try {
7144
+ await appendToInbox(config.teamName, entry.workerName, message, config.cwd);
7145
+ } catch {
7146
+ }
7147
+ }
7148
+ if (orphanedRebases.length > 0 || persistedShasLoaded > 0) {
7149
+ try {
7150
+ await appendEvent(config.repoRoot, config.teamName, {
7151
+ type: "restart_recovery",
7152
+ data: { orphanedRebases, persistedShasLoaded }
7153
+ });
7154
+ } catch {
7155
+ }
7156
+ }
7157
+ return { orphanedRebases, persistedShasLoaded };
7158
+ }
7159
+
7160
+ // src/team/runtime-v2.ts
7161
+ var import_node_child_process4 = require("node:child_process");
7162
+ var orchestratorByTeam = /* @__PURE__ */ new Map();
7163
+ var cadenceByTeam = /* @__PURE__ */ new Map();
7164
+ function registerTeamOrchestrator(teamName, handle) {
7165
+ orchestratorByTeam.set(teamName, handle);
7166
+ }
7167
+ function getTeamOrchestrator(teamName) {
7168
+ return orchestratorByTeam.get(teamName);
7169
+ }
7170
+ function unregisterTeamOrchestrator(teamName) {
7171
+ orchestratorByTeam.delete(teamName);
7172
+ }
7173
+ function registerTeamCadence(teamName, context, poller) {
7174
+ const entry = cadenceByTeam.get(teamName) ?? { pollers: [], contexts: [] };
7175
+ entry.contexts.push(context);
7176
+ if (poller) entry.pollers.push(poller);
7177
+ cadenceByTeam.set(teamName, entry);
7178
+ }
7179
+ async function stopTeamCadence(teamName) {
7180
+ const entry = cadenceByTeam.get(teamName);
7181
+ if (!entry) return;
7182
+ cadenceByTeam.delete(teamName);
7183
+ for (const poller of entry.pollers) {
7184
+ try {
7185
+ poller.stop();
7186
+ } catch {
7187
+ }
7188
+ }
7189
+ for (const context of entry.contexts) {
7190
+ try {
7191
+ await uninstallCommitCadence(context);
7192
+ } catch {
7193
+ }
7194
+ }
7195
+ }
7196
+ function resolveLeaderBranch(cwd) {
7197
+ const out = (0, import_node_child_process4.execFileSync)("git", ["branch", "--show-current"], {
7198
+ cwd,
7199
+ encoding: "utf-8",
7200
+ stdio: ["pipe", "pipe", "pipe"]
7201
+ }).trim();
7202
+ if (!out) {
7203
+ throw new Error("auto-merge requires a non-detached leader branch (git branch --show-current returned empty)");
7204
+ }
7205
+ return out;
7206
+ }
6208
7207
  var MONITOR_SIGNAL_STALE_MS = 3e4;
6209
7208
  function resolveTaskAssignment(task, resolvedRouting, roleRoutingConfig, resolvedBinaryPaths, fallbackAgent) {
6210
7209
  const canonicalRoles = new Set(CANONICAL_TEAM_ROLES);
@@ -6347,7 +7346,7 @@ function hasWorkerStatusProgress(status, taskId) {
6347
7346
  }
6348
7347
  async function hasWorkerTaskClaimEvidence(teamName, workerName2, cwd, taskId) {
6349
7348
  try {
6350
- const raw = await (0, import_promises7.readFile)(absPath(cwd, TeamPaths.taskFile(teamName, taskId)), "utf-8");
7349
+ const raw = await (0, import_promises10.readFile)(absPath(cwd, TeamPaths.taskFile(teamName, taskId)), "utf-8");
6351
7350
  const task = JSON.parse(raw);
6352
7351
  return task.owner === workerName2 && ["in_progress", "completed", "failed"].includes(task.status);
6353
7352
  } catch {
@@ -6446,6 +7445,18 @@ async function spawnV2Worker(opts) {
6446
7445
  if (usePromptMode) {
6447
7446
  launchArgs.push(...getPromptModeArgs(opts.agentType, promptModeStartupPrompt));
6448
7447
  }
7448
+ if (opts.autoMerge && opts.worktreePath) {
7449
+ const cadenceContext = {
7450
+ teamName: opts.teamName,
7451
+ workerName: opts.workerName,
7452
+ worktreePath: opts.worktreePath,
7453
+ agentType: opts.agentType,
7454
+ enabled: true
7455
+ };
7456
+ const cadence = await installCommitCadence(cadenceContext);
7457
+ const poller = cadence.method === "fallback-poll" ? startFallbackPoller(opts.worktreePath, opts.workerName) : void 0;
7458
+ registerTeamCadence(opts.teamName, cadenceContext, poller);
7459
+ }
6449
7460
  const paneConfig = {
6450
7461
  teamName: opts.teamName,
6451
7462
  workerName: opts.workerName,
@@ -6546,19 +7557,19 @@ async function rollbackUnpersistedNativeWorktreeStartup(teamName, cwd, cause) {
6546
7557
  try {
6547
7558
  const cleanup = cleanupTeamWorktrees(teamName, cwd);
6548
7559
  if (cleanup.preserved.length === 0) {
6549
- await (0, import_promises7.rm)(teamRoot, { recursive: true, force: true });
7560
+ await (0, import_promises10.rm)(teamRoot, { recursive: true, force: true });
6550
7561
  return;
6551
7562
  }
6552
- await (0, import_promises7.mkdir)(teamRoot, { recursive: true });
6553
- await (0, import_promises7.writeFile)((0, import_path20.join)(teamRoot, "startup-failure.json"), JSON.stringify({
7563
+ await (0, import_promises10.mkdir)(teamRoot, { recursive: true });
7564
+ await (0, import_promises10.writeFile)((0, import_path22.join)(teamRoot, "startup-failure.json"), JSON.stringify({
6554
7565
  reason: "startup_failed_before_config_persisted",
6555
7566
  error: errorMessage,
6556
7567
  preserved: cleanup.preserved,
6557
7568
  recorded_at: (/* @__PURE__ */ new Date()).toISOString()
6558
7569
  }, null, 2), "utf-8");
6559
7570
  } catch (rollbackError) {
6560
- await (0, import_promises7.mkdir)(teamRoot, { recursive: true });
6561
- await (0, import_promises7.writeFile)((0, import_path20.join)(teamRoot, "startup-failure.json"), JSON.stringify({
7571
+ await (0, import_promises10.mkdir)(teamRoot, { recursive: true });
7572
+ await (0, import_promises10.writeFile)((0, import_path22.join)(teamRoot, "startup-failure.json"), JSON.stringify({
6562
7573
  reason: "startup_failed_before_config_persisted",
6563
7574
  error: errorMessage,
6564
7575
  rollback_error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
@@ -6584,13 +7595,27 @@ async function rollbackStartedNativeWorktreeStartup(args) {
6584
7595
  }
6585
7596
  async function startTeamV2(config) {
6586
7597
  const sanitized = sanitizeTeamName(config.teamName);
6587
- const leaderCwd = (0, import_path20.resolve)(config.cwd);
7598
+ const leaderCwd = (0, import_path22.resolve)(config.cwd);
6588
7599
  validateTeamName(sanitized);
6589
7600
  const pluginCfg = config.pluginConfig ?? loadConfig();
6590
7601
  const resolvedRouting = buildResolvedRoutingSnapshot(pluginCfg);
6591
- const worktreeMode = normalizeTeamWorktreeMode(
7602
+ let worktreeMode = normalizeTeamWorktreeMode(
6592
7603
  process.env.OMC_TEAM_WORKTREE_MODE ?? pluginCfg.team?.ops?.worktreeMode
6593
7604
  );
7605
+ let autoMergeLeaderBranch;
7606
+ if (config.autoMerge) {
7607
+ if (!isRuntimeV2Enabled()) {
7608
+ throw new Error("auto-merge requires OMC_RUNTIME_V2=1 (this feature is v2-only).");
7609
+ }
7610
+ autoMergeLeaderBranch = resolveLeaderBranch(leaderCwd);
7611
+ const stripped = autoMergeLeaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
7612
+ if (stripped === "main" || stripped === "master") {
7613
+ throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
7614
+ }
7615
+ if (worktreeMode !== "named") {
7616
+ worktreeMode = "named";
7617
+ }
7618
+ }
6594
7619
  const workspaceMode = worktreeMode === "disabled" ? "single" : "worktree";
6595
7620
  const agentTypes = config.agentTypes;
6596
7621
  const resolvedBinaryPaths = {};
@@ -6620,9 +7645,9 @@ async function startTeamV2(config) {
6620
7645
  } catch {
6621
7646
  }
6622
7647
  }
6623
- await (0, import_promises7.mkdir)(absPath(leaderCwd, TeamPaths.tasks(sanitized)), { recursive: true });
6624
- await (0, import_promises7.mkdir)(absPath(leaderCwd, TeamPaths.workers(sanitized)), { recursive: true });
6625
- await (0, import_promises7.mkdir)((0, import_path20.join)(leaderCwd, ".omcp", "state", "team", sanitized, "mailbox"), { recursive: true });
7648
+ await (0, import_promises10.mkdir)(absPath(leaderCwd, TeamPaths.tasks(sanitized)), { recursive: true });
7649
+ await (0, import_promises10.mkdir)(absPath(leaderCwd, TeamPaths.workers(sanitized)), { recursive: true });
7650
+ await (0, import_promises10.mkdir)((0, import_path22.join)(leaderCwd, ".omcp", "state", "team", sanitized, "mailbox"), { recursive: true });
6626
7651
  const missingBinaryLogFailure = createSwallowedErrorLogger(
6627
7652
  "team.runtime-v2.startTeamV2 cli_binary_missing event failed"
6628
7653
  );
@@ -6640,8 +7665,8 @@ async function startTeamV2(config) {
6640
7665
  for (let i = 0; i < config.tasks.length; i++) {
6641
7666
  const taskId = String(i + 1);
6642
7667
  const taskFilePath = absPath(leaderCwd, TeamPaths.taskFile(sanitized, taskId));
6643
- await (0, import_promises7.mkdir)((0, import_path20.join)(taskFilePath, ".."), { recursive: true });
6644
- await (0, import_promises7.writeFile)(taskFilePath, JSON.stringify({
7668
+ await (0, import_promises10.mkdir)((0, import_path22.join)(taskFilePath, ".."), { recursive: true });
7669
+ await (0, import_promises10.writeFile)(taskFilePath, JSON.stringify({
6645
7670
  id: taskId,
6646
7671
  subject: config.tasks[i].subject,
6647
7672
  description: config.tasks[i].description,
@@ -6713,7 +7738,7 @@ async function startTeamV2(config) {
6713
7738
  });
6714
7739
  const worktree = workerWorktrees.get(wName);
6715
7740
  if (worktree) {
6716
- const overlayContent = await (0, import_promises7.readFile)(overlayPath, "utf-8");
7741
+ const overlayContent = await (0, import_promises10.readFile)(overlayPath, "utf-8");
6717
7742
  installWorktreeRootAgents(sanitized, wName, leaderCwd, worktree.path, overlayContent);
6718
7743
  }
6719
7744
  }
@@ -6823,7 +7848,7 @@ async function startTeamV2(config) {
6823
7848
  next_worker_index: teamConfig.next_worker_index
6824
7849
  };
6825
7850
  try {
6826
- await (0, import_promises7.writeFile)(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), "utf-8");
7851
+ await (0, import_promises10.writeFile)(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), "utf-8");
6827
7852
  } catch (error) {
6828
7853
  await rollbackStartedNativeWorktreeStartup({
6829
7854
  teamName: sanitized,
@@ -6872,6 +7897,7 @@ async function startTeamV2(config) {
6872
7897
  cwd: leaderCwd,
6873
7898
  workerCwd: workersInfo[workerIndex]?.working_dir ?? leaderCwd,
6874
7899
  worktreePath: workersInfo[workerIndex]?.worktree_path,
7900
+ autoMerge: Boolean(config.autoMerge),
6875
7901
  resolvedBinaryPaths,
6876
7902
  ...assignment.model ? { model: assignment.model } : {},
6877
7903
  ...assignment.role ? { role: assignment.role } : {}
@@ -6934,6 +7960,51 @@ async function startTeamV2(config) {
6934
7960
  worker: "leader-fixed",
6935
7961
  reason: `start_team_v2: workers=${config.workerCount} tasks=${config.tasks.length} panes=${workerPaneIds.length}`
6936
7962
  }, leaderCwd).catch(logEventFailure);
7963
+ if (config.autoMerge && autoMergeLeaderBranch) {
7964
+ try {
7965
+ await ensureLeaderInbox(sanitized, leaderCwd);
7966
+ await appendToLeaderInbox(
7967
+ sanitized,
7968
+ extendLeaderBootstrapPrompt(sanitized),
7969
+ leaderCwd
7970
+ );
7971
+ try {
7972
+ await recoverFromRestart({
7973
+ teamName: sanitized,
7974
+ repoRoot: leaderCwd,
7975
+ leaderBranch: autoMergeLeaderBranch,
7976
+ cwd: leaderCwd
7977
+ });
7978
+ } catch (recErr) {
7979
+ process.stderr.write(`[team/runtime-v2] auto-merge recover-from-restart failed: ${recErr}
7980
+ `);
7981
+ }
7982
+ const orchestrator = await startMergeOrchestrator({
7983
+ teamName: sanitized,
7984
+ repoRoot: leaderCwd,
7985
+ leaderBranch: autoMergeLeaderBranch,
7986
+ cwd: leaderCwd
7987
+ });
7988
+ registerTeamOrchestrator(sanitized, orchestrator);
7989
+ for (const w of workersInfo) {
7990
+ await orchestrator.registerWorker(w.name);
7991
+ }
7992
+ } catch (orchErr) {
7993
+ await stopTeamCadence(sanitized);
7994
+ unregisterTeamOrchestrator(sanitized);
7995
+ await rollbackStartedNativeWorktreeStartup({
7996
+ teamName: sanitized,
7997
+ cwd: leaderCwd,
7998
+ cause: orchErr,
7999
+ sessionName: sessionName2,
8000
+ leaderPaneId,
8001
+ workerPaneIds,
8002
+ sessionMode: session.sessionMode
8003
+ });
8004
+ const reason = orchErr instanceof Error ? orchErr.message : String(orchErr);
8005
+ throw new Error(`auto-merge startup failed: ${reason}`);
8006
+ }
8007
+ }
6937
8008
  return {
6938
8009
  teamName: sanitized,
6939
8010
  sanitizedName: sanitized,
@@ -6965,7 +8036,7 @@ async function processCliWorkerVerdicts(teamName, cwd) {
6965
8036
  }
6966
8037
  let payload;
6967
8038
  try {
6968
- const raw = await (0, import_promises7.readFile)(outputFile, "utf-8");
8039
+ const raw = await (0, import_promises10.readFile)(outputFile, "utf-8");
6969
8040
  payload = parseCliWorkerVerdict(raw);
6970
8041
  } catch (err) {
6971
8042
  const reason = err instanceof Error ? err.message : String(err);
@@ -7248,6 +8319,39 @@ async function shutdownTeamV2(teamName, cwd, options = {}) {
7248
8319
  const timeoutMs = options.timeoutMs ?? 15e3;
7249
8320
  const sanitized = sanitizeTeamName(teamName);
7250
8321
  const config = await readTeamConfig(sanitized, cwd);
8322
+ const finalizeAutoMerge = async () => {
8323
+ const orchestrator = getTeamOrchestrator(sanitized);
8324
+ if (orchestrator) {
8325
+ try {
8326
+ const drainResult = await orchestrator.drainAndStop();
8327
+ if (drainResult.unmerged.length > 0) {
8328
+ await appendTeamEvent(sanitized, {
8329
+ type: "team_leader_nudge",
8330
+ worker: "leader-fixed",
8331
+ reason: `auto_merge_drain_unmerged:${drainResult.unmerged.map((u) => `${u.workerName}:${u.reason}`).join(",")}`
8332
+ }, cwd).catch(logEventFailure);
8333
+ }
8334
+ for (const w of config?.workers ?? []) {
8335
+ try {
8336
+ await orchestrator.unregisterWorker(w.name);
8337
+ } catch (err) {
8338
+ process.stderr.write(
8339
+ `[team/runtime-v2] orchestrator.unregisterWorker(${w.name}) failed: ${err}
8340
+ `
8341
+ );
8342
+ }
8343
+ }
8344
+ } catch (err) {
8345
+ process.stderr.write(`[team/runtime-v2] orchestrator drainAndStop: ${err}
8346
+ `);
8347
+ } finally {
8348
+ await stopTeamCadence(sanitized);
8349
+ unregisterTeamOrchestrator(sanitized);
8350
+ }
8351
+ } else {
8352
+ await stopTeamCadence(sanitized);
8353
+ }
8354
+ };
7251
8355
  if (!config) {
7252
8356
  const cleanupSafety = inspectTeamWorktreeCleanupSafety(sanitized, cwd);
7253
8357
  if (cleanupSafety.hasEvidence) {
@@ -7382,12 +8486,14 @@ Then exit your session.
7382
8486
  if (aliveWorkers.length > 0) {
7383
8487
  process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane(s) are still alive: ${aliveWorkers.join(", ")}
7384
8488
  `);
8489
+ await finalizeAutoMerge();
7385
8490
  return;
7386
8491
  }
7387
8492
  const unknownWorkers = liveness.filter(([, state]) => state === "unknown").map(([paneId]) => paneById.get(paneId) ?? paneId);
7388
8493
  if (unknownWorkers.length > 0) {
7389
8494
  process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane liveness is unknown: ${unknownWorkers.join(", ")}
7390
8495
  `);
8496
+ await finalizeAutoMerge();
7391
8497
  return;
7392
8498
  }
7393
8499
  } catch (err) {
@@ -7395,6 +8501,7 @@ Then exit your session.
7395
8501
  `);
7396
8502
  if (recordedWorkerPaneIds.length > 0) {
7397
8503
  process.stderr.write("[team/runtime-v2] preserving worktrees/state because tmux cleanup did not prove worker panes exited\n");
8504
+ await finalizeAutoMerge();
7398
8505
  return;
7399
8506
  }
7400
8507
  }
@@ -7409,6 +8516,7 @@ Then exit your session.
7409
8516
  reason: `ralph_cleanup_summary: total=${finalTasks.length} completed=${completed} failed=${failed} pending=${pending} force=${force}`
7410
8517
  }, cwd).catch(logEventFailure);
7411
8518
  }
8519
+ await finalizeAutoMerge();
7412
8520
  let preservedWorktrees = 0;
7413
8521
  try {
7414
8522
  const worktreeCleanup = cleanupTeamWorktrees(sanitized, cwd);
@@ -7427,6 +8535,11 @@ Then exit your session.
7427
8535
  }
7428
8536
 
7429
8537
  // src/team/runtime-cli.ts
8538
+ function assertAutoMergeRuntimeSupported(useV2, autoMerge) {
8539
+ if (autoMerge && !useV2) {
8540
+ throw new Error("--auto-merge requires runtime v2; unset OMC_RUNTIME_V2=0 or disable --auto-merge");
8541
+ }
8542
+ }
7430
8543
  function getTerminalStatus(taskCounts, expectedTaskCount) {
7431
8544
  const active = taskCounts.pending + taskCounts.inProgress;
7432
8545
  const terminal = taskCounts.completed + taskCounts.failed;
@@ -7444,10 +8557,10 @@ function parseWatchdogFailedAt(marker) {
7444
8557
  throw new Error("watchdog marker missing valid failedAt");
7445
8558
  }
7446
8559
  async function checkWatchdogFailedMarker(stateRoot2, startTime) {
7447
- const markerPath = (0, import_path21.join)(stateRoot2, "watchdog-failed.json");
8560
+ const markerPath = (0, import_path23.join)(stateRoot2, "watchdog-failed.json");
7448
8561
  let raw;
7449
8562
  try {
7450
- raw = await (0, import_promises8.readFile)(markerPath, "utf-8");
8563
+ raw = await (0, import_promises11.readFile)(markerPath, "utf-8");
7451
8564
  } catch (err) {
7452
8565
  const code = err.code;
7453
8566
  if (code === "ENOENT") return { failed: false };
@@ -7469,21 +8582,21 @@ async function checkWatchdogFailedMarker(stateRoot2, startTime) {
7469
8582
  return { failed: true, reason: `Watchdog marked team failed at ${new Date(failedAt).toISOString()}` };
7470
8583
  }
7471
8584
  try {
7472
- await (0, import_promises8.unlink)(markerPath);
8585
+ await (0, import_promises11.unlink)(markerPath);
7473
8586
  } catch {
7474
8587
  }
7475
8588
  return { failed: false };
7476
8589
  }
7477
8590
  async function writeResultArtifact(output, finishedAt, jobId = process.env.OMC_JOB_ID, omcJobsDir = process.env.OMC_JOBS_DIR) {
7478
8591
  if (!jobId || !omcJobsDir) return;
7479
- const resultPath = (0, import_path21.join)(omcJobsDir, `${jobId}-result.json`);
8592
+ const resultPath = (0, import_path23.join)(omcJobsDir, `${jobId}-result.json`);
7480
8593
  const tmpPath = `${resultPath}.tmp`;
7481
- await (0, import_promises8.writeFile)(
8594
+ await (0, import_promises11.writeFile)(
7482
8595
  tmpPath,
7483
8596
  JSON.stringify({ ...output, finishedAt }),
7484
8597
  "utf-8"
7485
8598
  );
7486
- await (0, import_promises8.rename)(tmpPath, resultPath);
8599
+ await (0, import_promises11.rename)(tmpPath, resultPath);
7487
8600
  }
7488
8601
  function buildCliOutput(stateRoot2, teamName, status, workerCount, startTimeMs) {
7489
8602
  const taskResults = collectTaskResults(stateRoot2);
@@ -7508,20 +8621,20 @@ function buildTerminalCliResult(stateRoot2, teamName, phase, workerCount, startT
7508
8621
  async function writePanesFile(jobId, paneIds, leaderPaneId, sessionName2, ownsWindow) {
7509
8622
  const omcJobsDir = process.env.OMC_JOBS_DIR;
7510
8623
  if (!jobId || !omcJobsDir) return;
7511
- const panesPath = (0, import_path21.join)(omcJobsDir, `${jobId}-panes.json`);
7512
- await (0, import_promises8.writeFile)(
8624
+ const panesPath = (0, import_path23.join)(omcJobsDir, `${jobId}-panes.json`);
8625
+ await (0, import_promises11.writeFile)(
7513
8626
  panesPath + ".tmp",
7514
8627
  JSON.stringify({ paneIds: [...paneIds], leaderPaneId, sessionName: sessionName2, ownsWindow })
7515
8628
  );
7516
- await (0, import_promises8.rename)(panesPath + ".tmp", panesPath);
8629
+ await (0, import_promises11.rename)(panesPath + ".tmp", panesPath);
7517
8630
  }
7518
8631
  function collectTaskResults(stateRoot2) {
7519
- const tasksDir = (0, import_path21.join)(stateRoot2, "tasks");
8632
+ const tasksDir = (0, import_path23.join)(stateRoot2, "tasks");
7520
8633
  try {
7521
- const files = (0, import_fs19.readdirSync)(tasksDir).filter((f) => f.endsWith(".json"));
8634
+ const files = (0, import_fs21.readdirSync)(tasksDir).filter((f) => f.endsWith(".json"));
7522
8635
  return files.map((f) => {
7523
8636
  try {
7524
- const raw = (0, import_fs19.readFileSync)((0, import_path21.join)(tasksDir, f), "utf-8");
8637
+ const raw = (0, import_fs21.readFileSync)((0, import_path23.join)(tasksDir, f), "utf-8");
7525
8638
  const task = JSON.parse(raw);
7526
8639
  return {
7527
8640
  taskId: task.id ?? f.replace(".json", ""),
@@ -7572,10 +8685,11 @@ async function main() {
7572
8685
  newWindow = false,
7573
8686
  pollIntervalMs = 5e3,
7574
8687
  sentinelGateTimeoutMs = 3e4,
7575
- sentinelGatePollIntervalMs = 250
8688
+ sentinelGatePollIntervalMs = 250,
8689
+ autoMerge = false
7576
8690
  } = input;
7577
8691
  const workerCount = input.workerCount ?? agentTypes.length;
7578
- const stateRoot2 = (0, import_path21.join)(cwd, `.omc/state/team/${teamName}`);
8692
+ const stateRoot2 = (0, import_path23.join)(cwd, `.omcp/state/team/${teamName}`);
7579
8693
  const config = {
7580
8694
  teamName,
7581
8695
  workerCount,
@@ -7585,6 +8699,13 @@ async function main() {
7585
8699
  newWindow
7586
8700
  };
7587
8701
  const useV2 = isRuntimeV2Enabled();
8702
+ try {
8703
+ assertAutoMergeRuntimeSupported(useV2, autoMerge);
8704
+ } catch (err) {
8705
+ process.stderr.write(`[runtime-cli] ${err instanceof Error ? err.message : String(err)}
8706
+ `);
8707
+ process.exit(1);
8708
+ }
7588
8709
  let runtime = null;
7589
8710
  let finalStatus = "failed";
7590
8711
  let pollActive = true;
@@ -7649,7 +8770,8 @@ async function main() {
7649
8770
  agentTypes,
7650
8771
  tasks,
7651
8772
  cwd,
7652
- newWindow
8773
+ newWindow,
8774
+ autoMerge
7653
8775
  });
7654
8776
  const v2PaneIds = v2Runtime.config.workers.map((w) => w.pane_id).filter((p) => typeof p === "string");
7655
8777
  runtime = {
@@ -7769,7 +8891,7 @@ async function main() {
7769
8891
  if (snap.allTasksTerminal) {
7770
8892
  const hasFailures = snap.tasks.failed > 0;
7771
8893
  if (!hasFailures) {
7772
- const sentinelLogPath = (0, import_path21.join)(cwd, "sentinel_stop.jsonl");
8894
+ const sentinelLogPath = (0, import_path23.join)(cwd, "sentinel_stop.jsonl");
7773
8895
  const gateResult = await waitForSentinelReadiness({
7774
8896
  workspace: cwd,
7775
8897
  logPath: sentinelLogPath,
@@ -7846,7 +8968,7 @@ async function main() {
7846
8968
  mismatchStreak = 0;
7847
8969
  const terminalStatus = getTerminalStatus(snap.taskCounts, expectedTaskCount);
7848
8970
  if (terminalStatus === "completed") {
7849
- const sentinelLogPath = (0, import_path21.join)(cwd, "sentinel_stop.jsonl");
8971
+ const sentinelLogPath = (0, import_path23.join)(cwd, "sentinel_stop.jsonl");
7850
8972
  const gateResult = await waitForSentinelReadiness({
7851
8973
  workspace: cwd,
7852
8974
  logPath: sentinelLogPath,
@@ -7890,6 +9012,7 @@ if (require.main === module) {
7890
9012
  }
7891
9013
  // Annotate the CommonJS export names for ESM import in node:
7892
9014
  0 && (module.exports = {
9015
+ assertAutoMergeRuntimeSupported,
7893
9016
  buildCliOutput,
7894
9017
  buildTerminalCliResult,
7895
9018
  checkWatchdogFailedMarker,