oh-my-copilot 4.13.2 → 4.13.45
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +9 -1
- package/agents/code-reviewer.agent.md +24 -6
- package/agents/designer.agent.md +12 -0
- package/bridge/cli.cjs +4069 -1732
- package/bridge/mcp-server.cjs +774 -269
- package/bridge/runtime-cli.cjs +1311 -141
- package/bridge/team.js +1778 -133
- package/dist/__tests__/agent-registry.test.js +92 -1
- package/dist/__tests__/agent-registry.test.js.map +1 -1
- package/dist/__tests__/auto-slash-aliases.test.js +91 -0
- package/dist/__tests__/auto-slash-aliases.test.js.map +1 -1
- package/dist/__tests__/bedrock-model-routing.test.js +18 -17
- package/dist/__tests__/bedrock-model-routing.test.js.map +1 -1
- package/dist/__tests__/delegation-enforcer.test.js +46 -6
- package/dist/__tests__/delegation-enforcer.test.js.map +1 -1
- package/dist/__tests__/hooks/session-end-cleanup.test.d.ts +2 -0
- package/dist/__tests__/hooks/session-end-cleanup.test.d.ts.map +1 -0
- package/dist/__tests__/hooks/session-end-cleanup.test.js +93 -0
- package/dist/__tests__/hooks/session-end-cleanup.test.js.map +1 -0
- package/dist/__tests__/hud/labels.test.d.ts +2 -0
- package/dist/__tests__/hud/labels.test.d.ts.map +1 -0
- package/dist/__tests__/hud/labels.test.js +158 -0
- package/dist/__tests__/hud/labels.test.js.map +1 -0
- package/dist/__tests__/hud/render-rate-limits-priority.test.js +26 -0
- package/dist/__tests__/hud/render-rate-limits-priority.test.js.map +1 -1
- package/dist/__tests__/hud/stdin.test.d.ts +2 -0
- package/dist/__tests__/hud/stdin.test.d.ts.map +1 -0
- package/dist/__tests__/hud/stdin.test.js +556 -0
- package/dist/__tests__/hud/stdin.test.js.map +1 -0
- package/dist/__tests__/hud-marketplace-resolution.test.js +34 -0
- package/dist/__tests__/hud-marketplace-resolution.test.js.map +1 -1
- package/dist/__tests__/installer-hud-skip.test.js +6 -0
- package/dist/__tests__/installer-hud-skip.test.js.map +1 -1
- package/dist/__tests__/installer-omc-reference.test.js +1 -1
- package/dist/__tests__/installer-omc-reference.test.js.map +1 -1
- package/dist/__tests__/mnemosyne/detector.test.js +2 -0
- package/dist/__tests__/mnemosyne/detector.test.js.map +1 -1
- package/dist/__tests__/model-routing-override-three-site-sync.test.d.ts +2 -0
- package/dist/__tests__/model-routing-override-three-site-sync.test.d.ts.map +1 -0
- package/dist/__tests__/model-routing-override-three-site-sync.test.js +75 -0
- package/dist/__tests__/model-routing-override-three-site-sync.test.js.map +1 -0
- package/dist/__tests__/non-claude-provider-detection.test.js +37 -0
- package/dist/__tests__/non-claude-provider-detection.test.js.map +1 -1
- package/dist/__tests__/notepad.test.js +2 -2
- package/dist/__tests__/notepad.test.js.map +1 -1
- package/dist/__tests__/package-dir-resolution-regression.test.js +25 -0
- package/dist/__tests__/package-dir-resolution-regression.test.js.map +1 -1
- package/dist/__tests__/pre-tool-enforcer.test.js +135 -1
- package/dist/__tests__/pre-tool-enforcer.test.js.map +1 -1
- package/dist/__tests__/ralph-prd-mandatory.test.js +11 -1
- package/dist/__tests__/ralph-prd-mandatory.test.js.map +1 -1
- package/dist/__tests__/ralph-prd.test.js +40 -2
- package/dist/__tests__/ralph-prd.test.js.map +1 -1
- package/dist/__tests__/session-start-script-context.test.js +121 -0
- package/dist/__tests__/session-start-script-context.test.js.map +1 -1
- package/dist/__tests__/setup-claude-md-script.test.js +44 -1
- package/dist/__tests__/setup-claude-md-script.test.js.map +1 -1
- package/dist/__tests__/skills.test.js +363 -4
- package/dist/__tests__/skills.test.js.map +1 -1
- package/dist/__tests__/state-root-resolution.test.js +56 -6
- package/dist/__tests__/state-root-resolution.test.js.map +1 -1
- package/dist/agents/definitions.d.ts.map +1 -1
- package/dist/agents/definitions.js +2 -1
- package/dist/agents/definitions.js.map +1 -1
- package/dist/autoresearch/__tests__/decide-outcome.test.d.ts +2 -0
- package/dist/autoresearch/__tests__/decide-outcome.test.d.ts.map +1 -0
- package/dist/autoresearch/__tests__/decide-outcome.test.js +69 -0
- package/dist/autoresearch/__tests__/decide-outcome.test.js.map +1 -0
- package/dist/autoresearch/__tests__/runtime.test.js +225 -28
- package/dist/autoresearch/__tests__/runtime.test.js.map +1 -1
- package/dist/autoresearch/runtime.d.ts.map +1 -1
- package/dist/autoresearch/runtime.js +25 -7
- package/dist/autoresearch/runtime.js.map +1 -1
- package/dist/cli/__tests__/launch.test.js +23 -0
- package/dist/cli/__tests__/launch.test.js.map +1 -1
- package/dist/cli/__tests__/self-improve-paths.test.js +3 -2
- package/dist/cli/__tests__/self-improve-paths.test.js.map +1 -1
- package/dist/cli/__tests__/team.test.js +100 -0
- package/dist/cli/__tests__/team.test.js.map +1 -1
- package/dist/cli/autoresearch.d.ts +1 -1
- package/dist/cli/autoresearch.d.ts.map +1 -1
- package/dist/cli/autoresearch.js +4 -4
- package/dist/cli/commands/__tests__/team-role-shorthand.test.js +30 -0
- package/dist/cli/commands/__tests__/team-role-shorthand.test.js.map +1 -1
- package/dist/cli/commands/__tests__/team.test.js +48 -1
- package/dist/cli/commands/__tests__/team.test.js.map +1 -1
- package/dist/cli/commands/team.d.ts +3 -0
- package/dist/cli/commands/team.d.ts.map +1 -1
- package/dist/cli/commands/team.js +23 -4
- package/dist/cli/commands/team.js.map +1 -1
- package/dist/cli/index.js +3 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/launch.d.ts.map +1 -1
- package/dist/cli/launch.js +7 -0
- package/dist/cli/launch.js.map +1 -1
- package/dist/cli/team.d.ts +6 -0
- package/dist/cli/team.d.ts.map +1 -1
- package/dist/cli/team.js +57 -4
- package/dist/cli/team.js.map +1 -1
- package/dist/config/__tests__/loader.test.js +35 -4
- package/dist/config/__tests__/loader.test.js.map +1 -1
- package/dist/config/__tests__/models.test.js +126 -3
- package/dist/config/__tests__/models.test.js.map +1 -1
- package/dist/config/models.d.ts +8 -0
- package/dist/config/models.d.ts.map +1 -1
- package/dist/config/models.js +119 -8
- package/dist/config/models.js.map +1 -1
- package/dist/features/boulder-state/constants.d.ts +1 -1
- package/dist/features/boulder-state/constants.d.ts.map +1 -1
- package/dist/features/builtin-skills/skills.d.ts.map +1 -1
- package/dist/features/builtin-skills/skills.js +11 -3
- package/dist/features/builtin-skills/skills.js.map +1 -1
- package/dist/features/builtin-skills/types.d.ts +7 -0
- package/dist/features/builtin-skills/types.d.ts.map +1 -1
- package/dist/features/delegation-enforcer.d.ts.map +1 -1
- package/dist/features/delegation-enforcer.js +10 -9
- package/dist/features/delegation-enforcer.js.map +1 -1
- package/dist/features/magic-keywords.d.ts.map +1 -1
- package/dist/features/magic-keywords.js +30 -1
- package/dist/features/magic-keywords.js.map +1 -1
- package/dist/features/rate-limit-wait/tmux-detector.d.ts +4 -4
- package/dist/features/rate-limit-wait/tmux-detector.js +9 -9
- package/dist/features/rate-limit-wait/tmux-detector.js.map +1 -1
- package/dist/hooks/__tests__/bridge-routing.test.js +157 -0
- package/dist/hooks/__tests__/bridge-routing.test.js.map +1 -1
- package/dist/hooks/auto-slash-command/constants.d.ts.map +1 -1
- package/dist/hooks/auto-slash-command/constants.js +1 -0
- package/dist/hooks/auto-slash-command/constants.js.map +1 -1
- package/dist/hooks/auto-slash-command/executor.d.ts.map +1 -1
- package/dist/hooks/auto-slash-command/executor.js +29 -0
- package/dist/hooks/auto-slash-command/executor.js.map +1 -1
- package/dist/hooks/autopilot/enforcement.d.ts.map +1 -1
- package/dist/hooks/autopilot/enforcement.js +19 -0
- package/dist/hooks/autopilot/enforcement.js.map +1 -1
- package/dist/hooks/bridge.d.ts.map +1 -1
- package/dist/hooks/bridge.js +210 -5
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/learner/detector.js +1 -1
- package/dist/hooks/mode-registry/__tests__/session-isolation.test.js +16 -0
- package/dist/hooks/mode-registry/__tests__/session-isolation.test.js.map +1 -1
- package/dist/hooks/notepad/index.d.ts +4 -1
- package/dist/hooks/notepad/index.d.ts.map +1 -1
- package/dist/hooks/notepad/index.js +7 -4
- package/dist/hooks/notepad/index.js.map +1 -1
- package/dist/hooks/persistent-mode/__tests__/ralph-verification-flow.test.js +70 -3
- package/dist/hooks/persistent-mode/__tests__/ralph-verification-flow.test.js.map +1 -1
- package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
- package/dist/hooks/persistent-mode/index.js +10 -9
- package/dist/hooks/persistent-mode/index.js.map +1 -1
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.js +101 -10
- package/dist/hooks/persistent-mode/stop-hook-blocking.test.js.map +1 -1
- package/dist/hooks/project-memory/__tests__/integration.test.js +117 -3
- package/dist/hooks/project-memory/__tests__/integration.test.js.map +1 -1
- package/dist/hooks/project-memory/__tests__/storage.test.js +11 -11
- package/dist/hooks/project-memory/__tests__/storage.test.js.map +1 -1
- package/dist/hooks/project-memory/index.d.ts.map +1 -1
- package/dist/hooks/project-memory/index.js +24 -12
- package/dist/hooks/project-memory/index.js.map +1 -1
- package/dist/hooks/ralph/index.d.ts +1 -1
- package/dist/hooks/ralph/index.d.ts.map +1 -1
- package/dist/hooks/ralph/index.js +1 -1
- package/dist/hooks/ralph/index.js.map +1 -1
- package/dist/hooks/ralph/loop.d.ts +6 -6
- package/dist/hooks/ralph/loop.d.ts.map +1 -1
- package/dist/hooks/ralph/loop.js +18 -18
- package/dist/hooks/ralph/loop.js.map +1 -1
- package/dist/hooks/ralph/prd.d.ts +23 -12
- package/dist/hooks/ralph/prd.d.ts.map +1 -1
- package/dist/hooks/ralph/prd.js +88 -42
- package/dist/hooks/ralph/prd.js.map +1 -1
- package/dist/hooks/session-end/__tests__/mode-state-cleanup.test.js +20 -0
- package/dist/hooks/session-end/__tests__/mode-state-cleanup.test.js.map +1 -1
- package/dist/hooks/session-end/index.d.ts +18 -2
- package/dist/hooks/session-end/index.d.ts.map +1 -1
- package/dist/hooks/session-end/index.js +157 -35
- package/dist/hooks/session-end/index.js.map +1 -1
- package/dist/hooks/skill-bridge.cjs +6 -5
- package/dist/hooks/skill-state/index.d.ts.map +1 -1
- package/dist/hooks/skill-state/index.js +1 -0
- package/dist/hooks/skill-state/index.js.map +1 -1
- package/dist/hud/__tests__/git-status.test.js +18 -0
- package/dist/hud/__tests__/git-status.test.js.map +1 -1
- package/dist/hud/elements/background.d.ts +3 -3
- package/dist/hud/elements/background.d.ts.map +1 -1
- package/dist/hud/elements/background.js +5 -4
- package/dist/hud/elements/background.js.map +1 -1
- package/dist/hud/elements/call-counts.d.ts +2 -2
- package/dist/hud/elements/call-counts.d.ts.map +1 -1
- package/dist/hud/elements/call-counts.js +7 -6
- package/dist/hud/elements/call-counts.js.map +1 -1
- package/dist/hud/elements/context.d.ts +3 -3
- package/dist/hud/elements/context.d.ts.map +1 -1
- package/dist/hud/elements/context.js +5 -4
- package/dist/hud/elements/context.js.map +1 -1
- package/dist/hud/elements/git.d.ts +2 -1
- package/dist/hud/elements/git.d.ts.map +1 -1
- package/dist/hud/elements/git.js +7 -6
- package/dist/hud/elements/git.js.map +1 -1
- package/dist/hud/elements/ralph.d.ts +2 -2
- package/dist/hud/elements/ralph.d.ts.map +1 -1
- package/dist/hud/elements/ralph.js +3 -2
- package/dist/hud/elements/ralph.js.map +1 -1
- package/dist/hud/elements/thinking.d.ts +2 -2
- package/dist/hud/elements/thinking.d.ts.map +1 -1
- package/dist/hud/elements/thinking.js +3 -2
- package/dist/hud/elements/thinking.js.map +1 -1
- package/dist/hud/elements/token-usage.d.ts +2 -2
- package/dist/hud/elements/token-usage.d.ts.map +1 -1
- package/dist/hud/elements/token-usage.js +3 -2
- package/dist/hud/elements/token-usage.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +11 -10
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +18 -1
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/stdin.d.ts +12 -1
- package/dist/hud/stdin.d.ts.map +1 -1
- package/dist/hud/stdin.js +123 -18
- package/dist/hud/stdin.js.map +1 -1
- package/dist/hud/types.d.ts +27 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js +58 -0
- package/dist/hud/types.js.map +1 -1
- package/dist/installer/__tests__/hud-cache-wrapper.test.d.ts +2 -0
- package/dist/installer/__tests__/hud-cache-wrapper.test.d.ts.map +1 -0
- package/dist/installer/__tests__/hud-cache-wrapper.test.js +235 -0
- package/dist/installer/__tests__/hud-cache-wrapper.test.js.map +1 -0
- package/dist/installer/__tests__/session-start-template.test.d.ts +2 -0
- package/dist/installer/__tests__/session-start-template.test.d.ts.map +1 -0
- package/dist/installer/__tests__/session-start-template.test.js +174 -0
- package/dist/installer/__tests__/session-start-template.test.js.map +1 -0
- package/dist/installer/__tests__/standalone-hook-reconcile.test.js +2 -0
- package/dist/installer/__tests__/standalone-hook-reconcile.test.js.map +1 -1
- package/dist/installer/index.d.ts.map +1 -1
- package/dist/installer/index.js +19 -7
- package/dist/installer/index.js.map +1 -1
- package/dist/lib/__tests__/mode-state-io.test.js +75 -12
- package/dist/lib/__tests__/mode-state-io.test.js.map +1 -1
- package/dist/lib/__tests__/worktree-paths.test.js +162 -39
- package/dist/lib/__tests__/worktree-paths.test.js.map +1 -1
- package/dist/lib/mode-state-io.d.ts +11 -0
- package/dist/lib/mode-state-io.d.ts.map +1 -1
- package/dist/lib/mode-state-io.js +64 -0
- package/dist/lib/mode-state-io.js.map +1 -1
- package/dist/lib/worktree-paths.d.ts +99 -9
- package/dist/lib/worktree-paths.d.ts.map +1 -1
- package/dist/lib/worktree-paths.js +284 -27
- package/dist/lib/worktree-paths.js.map +1 -1
- package/dist/planning/__tests__/artifacts.test.d.ts +2 -0
- package/dist/planning/__tests__/artifacts.test.d.ts.map +1 -0
- package/dist/planning/__tests__/artifacts.test.js +486 -0
- package/dist/planning/__tests__/artifacts.test.js.map +1 -0
- package/dist/planning/artifact-names.d.ts +13 -0
- package/dist/planning/artifact-names.d.ts.map +1 -0
- package/dist/planning/artifact-names.js +108 -0
- package/dist/planning/artifact-names.js.map +1 -0
- package/dist/planning/artifacts.d.ts +47 -0
- package/dist/planning/artifacts.d.ts.map +1 -0
- package/dist/planning/artifacts.js +221 -0
- package/dist/planning/artifacts.js.map +1 -0
- package/dist/team/__tests__/api-interop.compatibility.test.js +194 -5
- package/dist/team/__tests__/api-interop.compatibility.test.js.map +1 -1
- package/dist/team/__tests__/auto-merge-restart.test.d.ts +2 -0
- package/dist/team/__tests__/auto-merge-restart.test.d.ts.map +1 -0
- package/dist/team/__tests__/auto-merge-restart.test.js +128 -0
- package/dist/team/__tests__/auto-merge-restart.test.js.map +1 -0
- package/dist/team/__tests__/auto-merge.perf.test.d.ts +2 -0
- package/dist/team/__tests__/auto-merge.perf.test.d.ts.map +1 -0
- package/dist/team/__tests__/auto-merge.perf.test.js +85 -0
- package/dist/team/__tests__/auto-merge.perf.test.js.map +1 -0
- package/dist/team/__tests__/conflict-mailbox.test.d.ts +2 -0
- package/dist/team/__tests__/conflict-mailbox.test.d.ts.map +1 -0
- package/dist/team/__tests__/conflict-mailbox.test.js +238 -0
- package/dist/team/__tests__/conflict-mailbox.test.js.map +1 -0
- package/dist/team/__tests__/helpers/git-fixture.d.ts +72 -0
- package/dist/team/__tests__/helpers/git-fixture.d.ts.map +1 -0
- package/dist/team/__tests__/helpers/git-fixture.js +252 -0
- package/dist/team/__tests__/helpers/git-fixture.js.map +1 -0
- package/dist/team/__tests__/leader-inbox.test.d.ts +2 -0
- package/dist/team/__tests__/leader-inbox.test.d.ts.map +1 -0
- package/dist/team/__tests__/leader-inbox.test.js +152 -0
- package/dist/team/__tests__/leader-inbox.test.js.map +1 -0
- package/dist/team/__tests__/merge-orchestrator.test.d.ts +2 -0
- package/dist/team/__tests__/merge-orchestrator.test.d.ts.map +1 -0
- package/dist/team/__tests__/merge-orchestrator.test.js +779 -0
- package/dist/team/__tests__/merge-orchestrator.test.js.map +1 -0
- package/dist/team/__tests__/model-contract.test.js +92 -2
- package/dist/team/__tests__/model-contract.test.js.map +1 -1
- package/dist/team/__tests__/rebase-smoke.test.d.ts +2 -0
- package/dist/team/__tests__/rebase-smoke.test.d.ts.map +1 -0
- package/dist/team/__tests__/rebase-smoke.test.js +272 -0
- package/dist/team/__tests__/rebase-smoke.test.js.map +1 -0
- package/dist/team/__tests__/runtime-cli.test.js +9 -1
- package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
- package/dist/team/__tests__/runtime-v2.dispatch.test.js +180 -1
- package/dist/team/__tests__/runtime-v2.dispatch.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +39 -1
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/teardown-invariant.test.d.ts +2 -0
- package/dist/team/__tests__/teardown-invariant.test.d.ts.map +1 -0
- package/dist/team/__tests__/teardown-invariant.test.js +176 -0
- package/dist/team/__tests__/teardown-invariant.test.js.map +1 -0
- package/dist/team/__tests__/worker-bootstrap.test.js +6 -0
- package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
- package/dist/team/__tests__/worker-commit-cadence.test.d.ts +2 -0
- package/dist/team/__tests__/worker-commit-cadence.test.d.ts.map +1 -0
- package/dist/team/__tests__/worker-commit-cadence.test.js +315 -0
- package/dist/team/__tests__/worker-commit-cadence.test.js.map +1 -0
- package/dist/team/__tests__/worktree-runtime-e2e.test.d.ts +2 -0
- package/dist/team/__tests__/worktree-runtime-e2e.test.d.ts.map +1 -0
- package/dist/team/__tests__/worktree-runtime-e2e.test.js +182 -0
- package/dist/team/__tests__/worktree-runtime-e2e.test.js.map +1 -0
- package/dist/team/api-interop.d.ts.map +1 -1
- package/dist/team/api-interop.js +89 -2
- package/dist/team/api-interop.js.map +1 -1
- package/dist/team/conflict-mailbox.d.ts +49 -0
- package/dist/team/conflict-mailbox.d.ts.map +1 -0
- package/dist/team/conflict-mailbox.js +98 -0
- package/dist/team/conflict-mailbox.js.map +1 -0
- package/dist/team/delegation-evidence.d.ts +5 -0
- package/dist/team/delegation-evidence.d.ts.map +1 -0
- package/dist/team/delegation-evidence.js +29 -0
- package/dist/team/delegation-evidence.js.map +1 -0
- package/dist/team/leader-inbox.d.ts +28 -0
- package/dist/team/leader-inbox.d.ts.map +1 -0
- package/dist/team/leader-inbox.js +68 -0
- package/dist/team/leader-inbox.js.map +1 -0
- package/dist/team/merge-coordinator.d.ts +6 -0
- package/dist/team/merge-coordinator.d.ts.map +1 -1
- package/dist/team/merge-coordinator.js +6 -2
- package/dist/team/merge-coordinator.js.map +1 -1
- package/dist/team/merge-orchestrator.d.ts +41 -0
- package/dist/team/merge-orchestrator.d.ts.map +1 -0
- package/dist/team/merge-orchestrator.js +736 -0
- package/dist/team/merge-orchestrator.js.map +1 -0
- package/dist/team/model-contract.d.ts +8 -0
- package/dist/team/model-contract.d.ts.map +1 -1
- package/dist/team/model-contract.js +13 -0
- package/dist/team/model-contract.js.map +1 -1
- package/dist/team/runtime-cli.d.ts +1 -0
- package/dist/team/runtime-cli.d.ts.map +1 -1
- package/dist/team/runtime-cli.js +15 -2
- package/dist/team/runtime-cli.js.map +1 -1
- package/dist/team/runtime-flags.d.ts +5 -0
- package/dist/team/runtime-flags.d.ts.map +1 -0
- package/dist/team/runtime-flags.js +12 -0
- package/dist/team/runtime-flags.js.map +1 -0
- package/dist/team/runtime-v2.d.ts +9 -2
- package/dist/team/runtime-v2.d.ts.map +1 -1
- package/dist/team/runtime-v2.js +188 -9
- package/dist/team/runtime-v2.js.map +1 -1
- package/dist/team/state/tasks.d.ts +4 -1
- package/dist/team/state/tasks.d.ts.map +1 -1
- package/dist/team/state/tasks.js +38 -1
- package/dist/team/state/tasks.js.map +1 -1
- package/dist/team/team-ops.d.ts +4 -1
- package/dist/team/team-ops.d.ts.map +1 -1
- package/dist/team/team-ops.js +2 -2
- package/dist/team/team-ops.js.map +1 -1
- package/dist/team/types.d.ts +22 -1
- package/dist/team/types.d.ts.map +1 -1
- package/dist/team/types.js.map +1 -1
- package/dist/team/worker-bootstrap.d.ts +4 -0
- package/dist/team/worker-bootstrap.d.ts.map +1 -1
- package/dist/team/worker-bootstrap.js +16 -2
- package/dist/team/worker-bootstrap.js.map +1 -1
- package/dist/team/worker-commit-cadence.d.ts +70 -0
- package/dist/team/worker-commit-cadence.d.ts.map +1 -0
- package/dist/team/worker-commit-cadence.js +271 -0
- package/dist/team/worker-commit-cadence.js.map +1 -0
- package/dist/tools/__tests__/memory-tools.test.js +4 -4
- package/dist/tools/__tests__/memory-tools.test.js.map +1 -1
- package/dist/tools/__tests__/state-tools.test.js +335 -6
- package/dist/tools/__tests__/state-tools.test.js.map +1 -1
- package/dist/tools/__tests__/wiki-tools-working-directory.test.d.ts +2 -0
- package/dist/tools/__tests__/wiki-tools-working-directory.test.d.ts.map +1 -0
- package/dist/tools/__tests__/wiki-tools-working-directory.test.js +66 -0
- package/dist/tools/__tests__/wiki-tools-working-directory.test.js.map +1 -0
- package/dist/tools/state-tools.d.ts +3 -10
- package/dist/tools/state-tools.d.ts.map +1 -1
- package/dist/tools/state-tools.js +393 -91
- package/dist/tools/state-tools.js.map +1 -1
- package/dist/tools/wiki-tools.js +8 -8
- package/dist/tools/wiki-tools.js.map +1 -1
- package/docs/GETTING-STARTED.md +422 -0
- package/docs/MIGRATION.md +66 -66
- package/docs/REFERENCE.md +21 -4
- package/docs/company-context-interface.md +5 -16
- package/docs/copilot-instructions.md +1 -1
- package/package.json +1 -1
- package/scripts/lib/hud-cache-wrapper.sh +122 -0
- package/scripts/lib/model-routing-override-message.mjs +21 -0
- package/scripts/persistent-mode.cjs +36 -0
- package/scripts/persistent-mode.mjs +36 -0
- package/scripts/plugin-setup.mjs +10 -1
- package/scripts/post-tool-verifier.mjs +119 -5
- package/scripts/pre-tool-enforcer.mjs +114 -6
- package/scripts/session-start.mjs +234 -3
- package/scripts/setup-copilot-instructions.sh +12 -2
- package/skills/AGENTS.md +22 -91
- package/skills/ai-slop-cleaner/SKILL.md +118 -120
- package/skills/ask/SKILL.md +6 -8
- package/skills/autopilot/SKILL.md +1 -28
- package/skills/autoresearch/SKILL.md +5 -5
- package/skills/cancel/SKILL.md +13 -13
- package/skills/cccg/SKILL.md +1 -0
- package/skills/configure-notifications/SKILL.md +77 -212
- package/skills/debug/SKILL.md +3 -2
- package/skills/deep-dive/SKILL.md +20 -9
- package/skills/deep-interview/SKILL.md +32 -4
- package/skills/deepinit/SKILL.md +1 -0
- package/skills/external-context/SKILL.md +2 -1
- package/skills/hud/SKILL.md +22 -52
- package/skills/learner/SKILL.md +90 -57
- package/skills/omc-doctor/SKILL.md +1 -1
- package/skills/omc-reference/SKILL.md +6 -6
- package/skills/omc-setup/SKILL.md +29 -73
- package/skills/omc-setup/phases/01-install-claude-md.md +97 -0
- package/skills/omc-setup/phases/02-configure.md +25 -40
- package/skills/omc-setup/phases/03-integrations.md +100 -69
- package/skills/omc-setup/phases/04-welcome.md +104 -46
- package/skills/omc-teams/SKILL.md +63 -26
- package/skills/plan/SKILL.md +11 -9
- package/skills/project-session-manager/SKILL.md +2 -2
- package/skills/project-session-manager/psm.sh +1 -1
- package/skills/ralph/SKILL.md +42 -41
- package/skills/ralplan/SKILL.md +3 -10
- package/skills/release/SKILL.md +168 -57
- package/skills/remember/SKILL.md +2 -1
- package/skills/sciomc/SKILL.md +12 -11
- package/skills/self-improve/SKILL.md +4 -4
- package/skills/setup/SKILL.md +1 -0
- package/skills/skill/SKILL.md +21 -20
- package/skills/skillify/SKILL.md +34 -3
- package/skills/team/SKILL.md +62 -44
- package/skills/ultraqa/SKILL.md +1 -2
- package/skills/ultrawork/SKILL.md +28 -21
- package/skills/verify/SKILL.md +2 -1
- package/skills/writer-memory/SKILL.md +2 -1
- package/templates/hooks/lib/model-routing-override-message.mjs +21 -0
- package/templates/hooks/pre-tool-use.mjs +144 -45
- package/templates/hooks/session-start.mjs +62 -0
package/bridge/team.js
CHANGED
|
@@ -2105,15 +2105,42 @@ var init_ssrf_guard = __esm({
|
|
|
2105
2105
|
});
|
|
2106
2106
|
|
|
2107
2107
|
// src/config/models.ts
|
|
2108
|
+
function readEnvValue(key) {
|
|
2109
|
+
const value = process.env[key]?.trim();
|
|
2110
|
+
return value || void 0;
|
|
2111
|
+
}
|
|
2108
2112
|
function resolveTierModelFromEnv(tier) {
|
|
2109
2113
|
for (const key of TIER_ENV_KEYS[tier]) {
|
|
2110
|
-
const value =
|
|
2114
|
+
const value = readEnvValue(key);
|
|
2115
|
+
if (value) {
|
|
2116
|
+
return value;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
return void 0;
|
|
2120
|
+
}
|
|
2121
|
+
function getDirectModelEnvValue() {
|
|
2122
|
+
for (const key of DIRECT_MODEL_ENV_KEYS) {
|
|
2123
|
+
const value = readEnvValue(key);
|
|
2111
2124
|
if (value) {
|
|
2112
2125
|
return value;
|
|
2113
2126
|
}
|
|
2114
2127
|
}
|
|
2115
2128
|
return void 0;
|
|
2116
2129
|
}
|
|
2130
|
+
function getProviderDetectionModelEnvValues() {
|
|
2131
|
+
const directModel = getDirectModelEnvValue();
|
|
2132
|
+
if (directModel) {
|
|
2133
|
+
return [directModel];
|
|
2134
|
+
}
|
|
2135
|
+
const values = /* @__PURE__ */ new Set();
|
|
2136
|
+
for (const tier of INHERIT_TIER_PRIORITY) {
|
|
2137
|
+
const value = resolveTierModelFromEnv(tier);
|
|
2138
|
+
if (value) {
|
|
2139
|
+
values.add(value);
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
return [...values];
|
|
2143
|
+
}
|
|
2117
2144
|
function getDefaultModelHigh() {
|
|
2118
2145
|
return resolveTierModelFromEnv("HIGH") || BUILTIN_TIER_MODEL_DEFAULTS.HIGH;
|
|
2119
2146
|
}
|
|
@@ -2146,17 +2173,19 @@ function isBedrock() {
|
|
|
2146
2173
|
if (modelId && /^((us|eu|ap|global)\.anthropic\.|anthropic\.claude)/i.test(modelId)) {
|
|
2147
2174
|
return true;
|
|
2148
2175
|
}
|
|
2176
|
+
if (modelId && /^arn:aws(-[^:]+)?:bedrock:/i.test(modelId) && /:(inference-profile|application-inference-profile)\//i.test(modelId) && modelId.toLowerCase().includes("claude")) {
|
|
2177
|
+
return true;
|
|
2178
|
+
}
|
|
2149
2179
|
return false;
|
|
2150
2180
|
}
|
|
2151
2181
|
function isVertexAI() {
|
|
2152
2182
|
if (process.env.CLAUDE_CODE_USE_VERTEX === "1") {
|
|
2153
2183
|
return true;
|
|
2154
2184
|
}
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
return false;
|
|
2185
|
+
return hasVertexModelId(getProviderDetectionModelEnvValues());
|
|
2186
|
+
}
|
|
2187
|
+
function hasVertexModelId(modelIds) {
|
|
2188
|
+
return modelIds.some((modelId) => modelId.toLowerCase().startsWith("vertex_ai/"));
|
|
2160
2189
|
}
|
|
2161
2190
|
function isProviderSpecificModelId(modelId) {
|
|
2162
2191
|
return isBedrockModelId(modelId) || isVertexModelId(modelId);
|
|
@@ -2195,11 +2224,13 @@ function isNonCopilotProvider() {
|
|
|
2195
2224
|
}
|
|
2196
2225
|
return false;
|
|
2197
2226
|
}
|
|
2198
|
-
var TIER_ENV_KEYS, COPILOT_FAMILY_DEFAULTS, BUILTIN_TIER_MODEL_DEFAULTS, COPILOT_FAMILY_HIGH_VARIANTS, BUILTIN_EXTERNAL_MODEL_DEFAULTS;
|
|
2227
|
+
var DIRECT_MODEL_ENV_KEYS, INHERIT_TIER_PRIORITY, TIER_ENV_KEYS, COPILOT_FAMILY_DEFAULTS, BUILTIN_TIER_MODEL_DEFAULTS, COPILOT_FAMILY_HIGH_VARIANTS, BUILTIN_EXTERNAL_MODEL_DEFAULTS;
|
|
2199
2228
|
var init_models = __esm({
|
|
2200
2229
|
"src/config/models.ts"() {
|
|
2201
2230
|
"use strict";
|
|
2202
2231
|
init_ssrf_guard();
|
|
2232
|
+
DIRECT_MODEL_ENV_KEYS = ["CLAUDE_MODEL", "ANTHROPIC_MODEL"];
|
|
2233
|
+
INHERIT_TIER_PRIORITY = ["MEDIUM", "HIGH", "LOW"];
|
|
2203
2234
|
TIER_ENV_KEYS = {
|
|
2204
2235
|
LOW: [
|
|
2205
2236
|
"OMC_MODEL_LOW",
|
|
@@ -3192,6 +3223,7 @@ var init_definitions = __esm({
|
|
|
3192
3223
|
init_utils();
|
|
3193
3224
|
init_loader();
|
|
3194
3225
|
init_strict_mode_guidance();
|
|
3226
|
+
init_models();
|
|
3195
3227
|
init_architect();
|
|
3196
3228
|
init_designer();
|
|
3197
3229
|
init_writer();
|
|
@@ -3275,6 +3307,9 @@ var init_definitions = __esm({
|
|
|
3275
3307
|
|
|
3276
3308
|
// src/features/delegation-enforcer.ts
|
|
3277
3309
|
function normalizeToCcAlias(model) {
|
|
3310
|
+
if (isProviderSpecificModelId(model)) {
|
|
3311
|
+
return model;
|
|
3312
|
+
}
|
|
3278
3313
|
const family = resolveClaudeFamily(model);
|
|
3279
3314
|
return family ? CC_FAMILY_TO_ALIAS[family] ?? model : model;
|
|
3280
3315
|
}
|
|
@@ -3451,6 +3486,9 @@ function resolveCliBinaryPath(binary) {
|
|
|
3451
3486
|
resolvedPathCache.set(binary, resolvedPath);
|
|
3452
3487
|
return resolvedPath;
|
|
3453
3488
|
}
|
|
3489
|
+
function shouldUseClaudeBareMode(env = process.env) {
|
|
3490
|
+
return typeof env.ANTHROPIC_API_KEY === "string" && env.ANTHROPIC_API_KEY.trim().length > 0;
|
|
3491
|
+
}
|
|
3454
3492
|
function getContract(agentType) {
|
|
3455
3493
|
const contract = CONTRACTS[agentType];
|
|
3456
3494
|
if (!contract) {
|
|
@@ -3560,6 +3598,9 @@ var init_model_contract = __esm({
|
|
|
3560
3598
|
installInstructions: "Install Claude CLI: https://claude.ai/download",
|
|
3561
3599
|
buildLaunchArgs(model, extraFlags = []) {
|
|
3562
3600
|
const args = ["--dangerously-skip-permissions"];
|
|
3601
|
+
if (shouldUseClaudeBareMode() && !extraFlags.includes("--bare")) {
|
|
3602
|
+
args.push("--bare");
|
|
3603
|
+
}
|
|
3563
3604
|
if (model) {
|
|
3564
3605
|
const resolved = isProviderSpecificModelId(model) ? model : normalizeToCcAlias(model);
|
|
3565
3606
|
args.push("--model", resolved);
|
|
@@ -3749,7 +3790,7 @@ function generateWorkerOverlay(params) {
|
|
|
3749
3790
|
const shutdownAckPath = buildTeamStateInstructionPath(teamName, instructionStateRoot, "workers", workerName, "shutdown-ack.json");
|
|
3750
3791
|
const claimTaskCommand = formatOmcCliInvocation(`team api claim-task --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"worker\\":\\"${workerName}\\"}" --json`);
|
|
3751
3792
|
const sendAckCommand = formatOmcCliInvocation(`team api send-message --input "{\\"team_name\\":\\"${teamName}\\",\\"from_worker\\":\\"${workerName}\\",\\"to_worker\\":\\"leader-fixed\\",\\"body\\":\\"ACK: ${workerName} initialized\\"}" --json`);
|
|
3752
|
-
const completeTaskCommand = formatOmcCliInvocation(`team api transition-task-status --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"from\\":\\"in_progress\\",\\"to\\":\\"completed\\",\\"claim_token\\":\\"<claim_token>\\"}" --json`);
|
|
3793
|
+
const completeTaskCommand = formatOmcCliInvocation(`team api transition-task-status --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"from\\":\\"in_progress\\",\\"to\\":\\"completed\\",\\"claim_token\\":\\"<claim_token>\\",\\"result\\":\\"Summary: <what changed>\\\\nVerification: <tests/checks run>\\\\nSubagent skip reason: worker protocol forbids nested subagents; completed focused probe in-session\\"}" --json`);
|
|
3753
3794
|
const failTaskCommand = formatOmcCliInvocation(`team api transition-task-status --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"from\\":\\"in_progress\\",\\"to\\":\\"failed\\",\\"claim_token\\":\\"<claim_token>\\"}" --json`);
|
|
3754
3795
|
const readTaskCommand = formatOmcCliInvocation(`team api read-task --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\"}" --json`);
|
|
3755
3796
|
const releaseClaimCommand = formatOmcCliInvocation(`team api release-task-claim --input "{\\"team_name\\":\\"${teamName}\\",\\"task_id\\":\\"<id>\\",\\"claim_token\\":\\"<claim_token>\\",\\"worker\\":\\"${workerName}\\"}" --json`);
|
|
@@ -3802,6 +3843,11 @@ Use the CLI API for all task lifecycle operations. Do NOT directly edit task fil
|
|
|
3802
3843
|
- Complete task: \`${completeTaskCommand}\`
|
|
3803
3844
|
- Fail task: \`${failTaskCommand}\`
|
|
3804
3845
|
- Release claim (rollback): \`${releaseClaimCommand}\`
|
|
3846
|
+
- Delegation compliance evidence (required for broad delegated tasks):
|
|
3847
|
+
- The completion command MUST include a \`result\` string with summary and verification evidence.
|
|
3848
|
+
- Because worker protocol forbids nested sub-agents, use: \`Subagent skip reason: <why in-session execution was safer/sufficient>\`
|
|
3849
|
+
- Only if the leader explicitly grants an exception to spawn nested help, use: \`Subagent spawn evidence: <count, child task names/thread ids, and integrated findings>\`
|
|
3850
|
+
- Completion is rejected with \`missing_delegation_compliance_evidence\` when required evidence is absent.
|
|
3805
3851
|
|
|
3806
3852
|
## Canonical Team State Root
|
|
3807
3853
|
- Resolve the team state root in this order: \`OMC_TEAM_STATE_ROOT\` env -> worker identity \`team_state_root\` -> config/manifest \`team_state_root\` -> ${params.cwd}/.omcp/state/team/${teamName}.
|
|
@@ -3865,6 +3911,17 @@ async function composeInitialInbox(teamName, workerName, content, cwd, cliOutput
|
|
|
3865
3911
|
${cliOutputContract}` : content;
|
|
3866
3912
|
await writeFile3(inboxPath, finalContent, "utf-8");
|
|
3867
3913
|
}
|
|
3914
|
+
async function appendToInbox(teamName, workerName, message, cwd) {
|
|
3915
|
+
const safeTeam = sanitizeName(teamName);
|
|
3916
|
+
const safeWorker = sanitizeName(workerName);
|
|
3917
|
+
const inboxPath = join14(cwd, `.omcp/state/team/${safeTeam}/workers/${safeWorker}/inbox.md`);
|
|
3918
|
+
validateResolvedPath(inboxPath, cwd);
|
|
3919
|
+
await mkdir3(dirname7(inboxPath), { recursive: true });
|
|
3920
|
+
await appendFile2(inboxPath, `
|
|
3921
|
+
|
|
3922
|
+
---
|
|
3923
|
+
${message}`, "utf-8");
|
|
3924
|
+
}
|
|
3868
3925
|
async function ensureWorkerStateDir(teamName, workerName, cwd) {
|
|
3869
3926
|
const workerDir = join14(cwd, `.omcp/state/team/${teamName}/workers/${workerName}`);
|
|
3870
3927
|
await mkdir3(workerDir, { recursive: true });
|
|
@@ -3887,6 +3944,8 @@ var init_worker_bootstrap = __esm({
|
|
|
3887
3944
|
"use strict";
|
|
3888
3945
|
init_prompt_helpers();
|
|
3889
3946
|
init_omc_cli_rendering();
|
|
3947
|
+
init_tmux_session();
|
|
3948
|
+
init_fs_utils();
|
|
3890
3949
|
init_model_contract();
|
|
3891
3950
|
DEFAULT_INSTRUCTION_STATE_ROOT = ".omcp/state";
|
|
3892
3951
|
}
|
|
@@ -4523,6 +4582,9 @@ function removeWorkerWorktree(teamName, workerName, repoRoot) {
|
|
|
4523
4582
|
forgetMetadataUnlocked(repoRoot, teamName, workerName);
|
|
4524
4583
|
});
|
|
4525
4584
|
}
|
|
4585
|
+
function listTeamWorktrees(teamName, repoRoot) {
|
|
4586
|
+
return readMetadata(repoRoot, teamName);
|
|
4587
|
+
}
|
|
4526
4588
|
function inspectTeamWorktreeCleanupSafety(teamName, repoRoot) {
|
|
4527
4589
|
const metadata = readMetadataResult(repoRoot, teamName);
|
|
4528
4590
|
const entries = metadata.entries;
|
|
@@ -4650,10 +4712,10 @@ async function readJsonSafe3(filePath) {
|
|
|
4650
4712
|
}
|
|
4651
4713
|
}
|
|
4652
4714
|
async function writeAtomic2(filePath, data) {
|
|
4653
|
-
const { writeFile:
|
|
4715
|
+
const { writeFile: writeFile8 } = await import("fs/promises");
|
|
4654
4716
|
await mkdir5(dirname10(filePath), { recursive: true });
|
|
4655
4717
|
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now()}`;
|
|
4656
|
-
await
|
|
4718
|
+
await writeFile8(tmpPath, data, "utf-8");
|
|
4657
4719
|
const { rename: rename3 } = await import("fs/promises");
|
|
4658
4720
|
await rename3(tmpPath, filePath);
|
|
4659
4721
|
}
|
|
@@ -5334,54 +5396,1039 @@ function parseCliWorkerVerdict(raw) {
|
|
|
5334
5396
|
if (typeof summary !== "string") {
|
|
5335
5397
|
throw new Error("verdict_missing_summary");
|
|
5336
5398
|
}
|
|
5337
|
-
const findingsRaw = obj.findings;
|
|
5338
|
-
if (!Array.isArray(findingsRaw)) {
|
|
5339
|
-
throw new Error("verdict_findings_not_array");
|
|
5399
|
+
const findingsRaw = obj.findings;
|
|
5400
|
+
if (!Array.isArray(findingsRaw)) {
|
|
5401
|
+
throw new Error("verdict_findings_not_array");
|
|
5402
|
+
}
|
|
5403
|
+
const findings = findingsRaw.map((entry, idx) => {
|
|
5404
|
+
if (!entry || typeof entry !== "object") {
|
|
5405
|
+
throw new Error(`verdict_finding_${idx}_not_object`);
|
|
5406
|
+
}
|
|
5407
|
+
const f = entry;
|
|
5408
|
+
const severity = f.severity;
|
|
5409
|
+
if (typeof severity !== "string" || !VALID_SEVERITIES.has(severity)) {
|
|
5410
|
+
throw new Error(`verdict_finding_${idx}_invalid_severity:${String(severity)}`);
|
|
5411
|
+
}
|
|
5412
|
+
const message = f.message;
|
|
5413
|
+
if (typeof message !== "string" || !message) {
|
|
5414
|
+
throw new Error(`verdict_finding_${idx}_missing_message`);
|
|
5415
|
+
}
|
|
5416
|
+
const finding = {
|
|
5417
|
+
severity,
|
|
5418
|
+
message
|
|
5419
|
+
};
|
|
5420
|
+
if (typeof f.file === "string" && f.file) finding.file = f.file;
|
|
5421
|
+
if (typeof f.line === "number" && Number.isFinite(f.line)) finding.line = f.line;
|
|
5422
|
+
return finding;
|
|
5423
|
+
});
|
|
5424
|
+
return {
|
|
5425
|
+
role,
|
|
5426
|
+
task_id: taskId,
|
|
5427
|
+
verdict,
|
|
5428
|
+
summary,
|
|
5429
|
+
findings
|
|
5430
|
+
};
|
|
5431
|
+
}
|
|
5432
|
+
function cliWorkerOutputFilePath(teamStateRootAbs, workerName) {
|
|
5433
|
+
return `${teamStateRootAbs.replaceAll("\\", "/")}/workers/${workerName}/verdict.json`;
|
|
5434
|
+
}
|
|
5435
|
+
var CONTRACT_ROLES, VALID_VERDICTS, VALID_SEVERITIES;
|
|
5436
|
+
var init_cli_worker_contract = __esm({
|
|
5437
|
+
"src/team/cli-worker-contract.ts"() {
|
|
5438
|
+
"use strict";
|
|
5439
|
+
CONTRACT_ROLES = /* @__PURE__ */ new Set([
|
|
5440
|
+
"critic",
|
|
5441
|
+
"code-reviewer",
|
|
5442
|
+
"security-reviewer",
|
|
5443
|
+
"test-engineer"
|
|
5444
|
+
]);
|
|
5445
|
+
VALID_VERDICTS = /* @__PURE__ */ new Set(["approve", "revise", "reject"]);
|
|
5446
|
+
VALID_SEVERITIES = /* @__PURE__ */ new Set(["critical", "major", "minor", "nit"]);
|
|
5447
|
+
}
|
|
5448
|
+
});
|
|
5449
|
+
|
|
5450
|
+
// src/team/runtime-flags.ts
|
|
5451
|
+
function isRuntimeV2Enabled(env = process.env) {
|
|
5452
|
+
const raw = env.OMC_RUNTIME_V2;
|
|
5453
|
+
if (!raw) return true;
|
|
5454
|
+
const normalized = raw.trim().toLowerCase();
|
|
5455
|
+
return !["0", "false", "no", "off"].includes(normalized);
|
|
5456
|
+
}
|
|
5457
|
+
var init_runtime_flags = __esm({
|
|
5458
|
+
"src/team/runtime-flags.ts"() {
|
|
5459
|
+
"use strict";
|
|
5460
|
+
}
|
|
5461
|
+
});
|
|
5462
|
+
|
|
5463
|
+
// src/team/merge-coordinator.ts
|
|
5464
|
+
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
5465
|
+
function validateBranchName(branch) {
|
|
5466
|
+
if (!BRANCH_NAME_RE.test(branch)) {
|
|
5467
|
+
throw new Error(`Invalid branch name: "${branch}" \u2014 must match ${BRANCH_NAME_RE}`);
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
function checkMergeConflicts(workerBranch, baseBranch, repoRoot) {
|
|
5471
|
+
validateBranchName(workerBranch);
|
|
5472
|
+
validateBranchName(baseBranch);
|
|
5473
|
+
try {
|
|
5474
|
+
execFileSync3(
|
|
5475
|
+
"git",
|
|
5476
|
+
["merge-tree", "--write-tree", baseBranch, workerBranch],
|
|
5477
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5478
|
+
);
|
|
5479
|
+
return [];
|
|
5480
|
+
} catch (err) {
|
|
5481
|
+
const error = err;
|
|
5482
|
+
if (error.status === 1 && typeof error.stdout === "string") {
|
|
5483
|
+
const lines = error.stdout.split("\n");
|
|
5484
|
+
const conflicts = [];
|
|
5485
|
+
for (const line of lines) {
|
|
5486
|
+
const match = line.match(/^CONFLICT\s.*?:\s+.*?\s+in\s+(.+)$/);
|
|
5487
|
+
if (match) {
|
|
5488
|
+
conflicts.push(match[1].trim());
|
|
5489
|
+
}
|
|
5490
|
+
}
|
|
5491
|
+
return conflicts.length > 0 ? conflicts : ["(merge-tree reported conflicts)"];
|
|
5492
|
+
}
|
|
5493
|
+
}
|
|
5494
|
+
const mergeBase = execFileSync3(
|
|
5495
|
+
"git",
|
|
5496
|
+
["merge-base", baseBranch, workerBranch],
|
|
5497
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5498
|
+
).trim();
|
|
5499
|
+
const baseDiff = execFileSync3(
|
|
5500
|
+
"git",
|
|
5501
|
+
["diff", "--name-only", mergeBase, baseBranch],
|
|
5502
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5503
|
+
).trim();
|
|
5504
|
+
const workerDiff = execFileSync3(
|
|
5505
|
+
"git",
|
|
5506
|
+
["diff", "--name-only", mergeBase, workerBranch],
|
|
5507
|
+
{ cwd: repoRoot, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
5508
|
+
).trim();
|
|
5509
|
+
if (!baseDiff || !workerDiff) {
|
|
5510
|
+
return [];
|
|
5511
|
+
}
|
|
5512
|
+
const baseFiles = new Set(baseDiff.split("\n").filter((f) => f));
|
|
5513
|
+
const workerFiles = workerDiff.split("\n").filter((f) => f);
|
|
5514
|
+
return workerFiles.filter((f) => baseFiles.has(f));
|
|
5515
|
+
}
|
|
5516
|
+
function mergeWorkerBranch(workerBranch, baseBranch, repoRoot) {
|
|
5517
|
+
validateBranchName(workerBranch);
|
|
5518
|
+
validateBranchName(baseBranch);
|
|
5519
|
+
const workerName = workerBranch.split("/").pop() || workerBranch;
|
|
5520
|
+
try {
|
|
5521
|
+
try {
|
|
5522
|
+
execFileSync3("git", ["diff-index", "--quiet", "HEAD", "--"], {
|
|
5523
|
+
cwd: repoRoot,
|
|
5524
|
+
stdio: "pipe"
|
|
5525
|
+
});
|
|
5526
|
+
} catch {
|
|
5527
|
+
throw new Error("Working tree has uncommitted changes \u2014 commit or stash before merging");
|
|
5528
|
+
}
|
|
5529
|
+
execFileSync3("git", ["checkout", baseBranch], {
|
|
5530
|
+
cwd: repoRoot,
|
|
5531
|
+
stdio: "pipe"
|
|
5532
|
+
});
|
|
5533
|
+
execFileSync3("git", ["merge", "--no-ff", "-m", `Merge ${workerBranch} into ${baseBranch}`, workerBranch], {
|
|
5534
|
+
cwd: repoRoot,
|
|
5535
|
+
stdio: "pipe"
|
|
5536
|
+
});
|
|
5537
|
+
const mergeCommit = execFileSync3("git", ["rev-parse", "HEAD"], {
|
|
5538
|
+
cwd: repoRoot,
|
|
5539
|
+
encoding: "utf-8",
|
|
5540
|
+
stdio: "pipe"
|
|
5541
|
+
}).trim();
|
|
5542
|
+
return {
|
|
5543
|
+
workerName,
|
|
5544
|
+
branch: workerBranch,
|
|
5545
|
+
success: true,
|
|
5546
|
+
conflicts: [],
|
|
5547
|
+
mergeCommit
|
|
5548
|
+
};
|
|
5549
|
+
} catch (_err) {
|
|
5550
|
+
try {
|
|
5551
|
+
execFileSync3("git", ["merge", "--abort"], { cwd: repoRoot, stdio: "pipe" });
|
|
5552
|
+
} catch {
|
|
5553
|
+
}
|
|
5554
|
+
const conflicts = checkMergeConflicts(workerBranch, baseBranch, repoRoot);
|
|
5555
|
+
return {
|
|
5556
|
+
workerName,
|
|
5557
|
+
branch: workerBranch,
|
|
5558
|
+
success: false,
|
|
5559
|
+
conflicts
|
|
5560
|
+
};
|
|
5561
|
+
}
|
|
5562
|
+
}
|
|
5563
|
+
var BRANCH_NAME_RE;
|
|
5564
|
+
var init_merge_coordinator = __esm({
|
|
5565
|
+
"src/team/merge-coordinator.ts"() {
|
|
5566
|
+
"use strict";
|
|
5567
|
+
init_git_worktree();
|
|
5568
|
+
BRANCH_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9/_.-]*$/;
|
|
5569
|
+
}
|
|
5570
|
+
});
|
|
5571
|
+
|
|
5572
|
+
// src/team/leader-inbox.ts
|
|
5573
|
+
import { appendFile as appendFile4, mkdir as mkdir7, writeFile as writeFile5 } from "fs/promises";
|
|
5574
|
+
import { existsSync as existsSync16 } from "fs";
|
|
5575
|
+
import { dirname as dirname12, join as join19 } from "path";
|
|
5576
|
+
function leaderInboxPath(teamName, cwd) {
|
|
5577
|
+
const safe = sanitizeName(teamName);
|
|
5578
|
+
return join19(cwd, `.omcp/state/team/${safe}/leader/inbox.md`);
|
|
5579
|
+
}
|
|
5580
|
+
async function ensureLeaderInbox(teamName, cwd) {
|
|
5581
|
+
const inboxPath = leaderInboxPath(teamName, cwd);
|
|
5582
|
+
validateResolvedPath(inboxPath, cwd);
|
|
5583
|
+
await mkdir7(dirname12(inboxPath), { recursive: true });
|
|
5584
|
+
if (!existsSync16(inboxPath)) {
|
|
5585
|
+
await writeFile5(inboxPath, LEADER_INBOX_HEADER, "utf-8");
|
|
5586
|
+
}
|
|
5587
|
+
return inboxPath;
|
|
5588
|
+
}
|
|
5589
|
+
async function appendToLeaderInbox(teamName, message, cwd) {
|
|
5590
|
+
const inboxPath = leaderInboxPath(teamName, cwd);
|
|
5591
|
+
validateResolvedPath(inboxPath, cwd);
|
|
5592
|
+
await mkdir7(dirname12(inboxPath), { recursive: true });
|
|
5593
|
+
await appendFile4(inboxPath, `
|
|
5594
|
+
|
|
5595
|
+
---
|
|
5596
|
+
${message}`, "utf-8");
|
|
5597
|
+
}
|
|
5598
|
+
function extendLeaderBootstrapPrompt(teamName) {
|
|
5599
|
+
const safe = sanitizeName(teamName);
|
|
5600
|
+
const path4 = `.omcp/state/team/${safe}/leader/inbox.md`;
|
|
5601
|
+
return `Runtime notifications appear at ${path4} \u2014 check this file periodically and after long-running operations.`;
|
|
5602
|
+
}
|
|
5603
|
+
var LEADER_INBOX_HEADER;
|
|
5604
|
+
var init_leader_inbox = __esm({
|
|
5605
|
+
"src/team/leader-inbox.ts"() {
|
|
5606
|
+
"use strict";
|
|
5607
|
+
init_tmux_session();
|
|
5608
|
+
init_fs_utils();
|
|
5609
|
+
LEADER_INBOX_HEADER = `# Leader Inbox
|
|
5610
|
+
|
|
5611
|
+
Runtime notifications (merge conflicts, rebase events, etc.) appear here.
|
|
5612
|
+
Check this file periodically and after long-running operations.
|
|
5613
|
+
|
|
5614
|
+
---
|
|
5615
|
+
`;
|
|
5616
|
+
}
|
|
5617
|
+
});
|
|
5618
|
+
|
|
5619
|
+
// src/team/conflict-mailbox.ts
|
|
5620
|
+
function sanitizeConflictPath(path4) {
|
|
5621
|
+
return path4.replace(/[`\r\n]/g, "?");
|
|
5622
|
+
}
|
|
5623
|
+
function formatMergeConflictForLeader(args) {
|
|
5624
|
+
const { workerName, workerBranch, leaderBranch, conflictingFiles, mergeBaseSha, observedAt } = args;
|
|
5625
|
+
const ts = new Date(observedAt).toISOString();
|
|
5626
|
+
const safeFiles = conflictingFiles.map(sanitizeConflictPath);
|
|
5627
|
+
const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
|
|
5628
|
+
return `### Merge conflict: ${workerName} \u2192 ${leaderBranch}
|
|
5629
|
+
|
|
5630
|
+
**Worker branch:** \`${workerBranch}\`
|
|
5631
|
+
**Leader branch:** \`${leaderBranch}\`
|
|
5632
|
+
**Merge base:** \`${mergeBaseSha}\`
|
|
5633
|
+
**Observed at:** ${ts}
|
|
5634
|
+
|
|
5635
|
+
**Conflicting files:**
|
|
5636
|
+
${fileList}
|
|
5637
|
+
|
|
5638
|
+
**Leader: choose strategy.** To resolve, run:
|
|
5639
|
+
|
|
5640
|
+
\`\`\`sh
|
|
5641
|
+
git checkout ${leaderBranch} && git merge --no-ff ${workerBranch}
|
|
5642
|
+
# resolve conflicts in the files listed above
|
|
5643
|
+
git add ${safeFiles.join(" ")}
|
|
5644
|
+
git commit
|
|
5645
|
+
\`\`\`
|
|
5646
|
+
|
|
5647
|
+
Or abort with \`git merge --abort\` to defer resolution.`;
|
|
5648
|
+
}
|
|
5649
|
+
function formatRebaseConflictForWorker(args) {
|
|
5650
|
+
const { workerName, workerBranch, leaderBranch, conflictingFiles, baseSha, worktreePath, observedAt } = args;
|
|
5651
|
+
const ts = new Date(observedAt).toISOString();
|
|
5652
|
+
const safeFiles = conflictingFiles.map(sanitizeConflictPath);
|
|
5653
|
+
const fileList = safeFiles.map((f) => `- \`${f}\``).join("\n");
|
|
5654
|
+
return `### Rebase conflict: ${workerName} onto ${leaderBranch}
|
|
5655
|
+
|
|
5656
|
+
**Worker branch:** \`${workerBranch}\`
|
|
5657
|
+
**Base branch:** \`${leaderBranch}\`
|
|
5658
|
+
**Base SHA:** \`${baseSha}\`
|
|
5659
|
+
**Worktree:** \`${worktreePath}\`
|
|
5660
|
+
**Observed at:** ${ts}
|
|
5661
|
+
|
|
5662
|
+
**Conflicting files:**
|
|
5663
|
+
${fileList}
|
|
5664
|
+
|
|
5665
|
+
Resolve conflicts in your own pane, then \`git add <files>\` and \`git rebase --continue\`.
|
|
5666
|
+
Cadence stays paused until \`.git/rebase-merge\` is gone.
|
|
5667
|
+
|
|
5668
|
+
Or run \`git rebase --abort\` to bail and return to the pre-rebase state.`;
|
|
5669
|
+
}
|
|
5670
|
+
var init_conflict_mailbox = __esm({
|
|
5671
|
+
"src/team/conflict-mailbox.ts"() {
|
|
5672
|
+
"use strict";
|
|
5673
|
+
init_worker_bootstrap();
|
|
5674
|
+
init_leader_inbox();
|
|
5675
|
+
}
|
|
5676
|
+
});
|
|
5677
|
+
|
|
5678
|
+
// src/team/worker-commit-cadence.ts
|
|
5679
|
+
import { existsSync as existsSync17, watch as fsWatch } from "fs";
|
|
5680
|
+
import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir8, unlink as unlink2 } from "fs/promises";
|
|
5681
|
+
import { join as join20, dirname as dirname13 } from "path";
|
|
5682
|
+
import { exec as exec2 } from "child_process";
|
|
5683
|
+
function assertSafeWorkerName(workerName) {
|
|
5684
|
+
if (!WORKER_NAME_RE.test(workerName)) {
|
|
5685
|
+
throw new Error(
|
|
5686
|
+
`Invalid worker name for shell hook: "${workerName}" \u2014 must match ${WORKER_NAME_RE}`
|
|
5687
|
+
);
|
|
5688
|
+
}
|
|
5689
|
+
}
|
|
5690
|
+
function buildHookCommand(workerName) {
|
|
5691
|
+
assertSafeWorkerName(workerName);
|
|
5692
|
+
return `sh -c 'rebase_dir=$(git rev-parse --git-path rebase-merge 2>/dev/null || printf %s .git/rebase-merge); merge_head=$(git rev-parse --git-path MERGE_HEAD 2>/dev/null || printf %s .git/MERGE_HEAD); if [ -d "$rebase_dir" ] || [ -f "$merge_head" ] || [ -e ${SENTINEL_FILENAME} ]; then exit 0; fi; git add -A && (git diff --cached --quiet || git commit -m "auto-commit by worker ${workerName} at $(date -Iseconds)")'`;
|
|
5693
|
+
}
|
|
5694
|
+
async function mergeSettingsWithHook(settingsPath, hookCommand) {
|
|
5695
|
+
let existing = { hooks: { PostToolUse: [] } };
|
|
5696
|
+
try {
|
|
5697
|
+
const raw = await readFile9(settingsPath, "utf-8");
|
|
5698
|
+
const parsed = JSON.parse(raw);
|
|
5699
|
+
existing = {
|
|
5700
|
+
...parsed,
|
|
5701
|
+
hooks: {
|
|
5702
|
+
PostToolUse: [],
|
|
5703
|
+
...parsed.hooks ?? {}
|
|
5704
|
+
}
|
|
5705
|
+
};
|
|
5706
|
+
} catch {
|
|
5707
|
+
}
|
|
5708
|
+
const filteredHooks = (existing.hooks.PostToolUse ?? []).filter(
|
|
5709
|
+
(h) => h.matcher !== HOOK_MATCHER
|
|
5710
|
+
);
|
|
5711
|
+
const newEntry = {
|
|
5712
|
+
matcher: HOOK_MATCHER,
|
|
5713
|
+
hooks: [{ type: "command", command: hookCommand }]
|
|
5714
|
+
};
|
|
5715
|
+
return {
|
|
5716
|
+
...existing,
|
|
5717
|
+
hooks: {
|
|
5718
|
+
...existing.hooks,
|
|
5719
|
+
PostToolUse: [...filteredHooks, newEntry]
|
|
5720
|
+
}
|
|
5721
|
+
};
|
|
5722
|
+
}
|
|
5723
|
+
async function installPostToolUseHook(worktreePath, workerName) {
|
|
5724
|
+
assertSafeWorkerName(workerName);
|
|
5725
|
+
if (isHookPaused(worktreePath)) {
|
|
5726
|
+
return;
|
|
5727
|
+
}
|
|
5728
|
+
const claudeDir = join20(worktreePath, ".claude");
|
|
5729
|
+
await mkdir8(claudeDir, { recursive: true });
|
|
5730
|
+
const settingsPath = join20(claudeDir, "settings.json");
|
|
5731
|
+
const hookCommand = buildHookCommand(workerName);
|
|
5732
|
+
const merged = await mergeSettingsWithHook(settingsPath, hookCommand);
|
|
5733
|
+
await writeFile6(settingsPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
|
|
5734
|
+
}
|
|
5735
|
+
async function pauseHookViaSentinel(worktreePath) {
|
|
5736
|
+
const sentinelPath = join20(worktreePath, SENTINEL_FILENAME);
|
|
5737
|
+
await mkdir8(dirname13(sentinelPath), { recursive: true });
|
|
5738
|
+
await writeFile6(sentinelPath, "", "utf-8");
|
|
5739
|
+
}
|
|
5740
|
+
async function resumeHookViaSentinel(worktreePath) {
|
|
5741
|
+
const sentinelPath = join20(worktreePath, SENTINEL_FILENAME);
|
|
5742
|
+
try {
|
|
5743
|
+
await unlink2(sentinelPath);
|
|
5744
|
+
} catch {
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
function isHookPaused(worktreePath) {
|
|
5748
|
+
return existsSync17(join20(worktreePath, SENTINEL_FILENAME));
|
|
5749
|
+
}
|
|
5750
|
+
function startFallbackPoller(worktreePath, workerName, opts) {
|
|
5751
|
+
assertSafeWorkerName(workerName);
|
|
5752
|
+
const debounceMs = opts?.intervalMs ?? DEFAULT_POLL_DEBOUNCE_MS;
|
|
5753
|
+
let debounceTimer = null;
|
|
5754
|
+
let stopped = false;
|
|
5755
|
+
const runAutoCommit = () => {
|
|
5756
|
+
if (stopped) return;
|
|
5757
|
+
if (isHookPaused(worktreePath)) return;
|
|
5758
|
+
const cmd = buildHookCommand(workerName);
|
|
5759
|
+
exec2(cmd, { cwd: worktreePath }, (_err) => {
|
|
5760
|
+
});
|
|
5761
|
+
};
|
|
5762
|
+
const scheduleDebounce = () => {
|
|
5763
|
+
if (stopped) return;
|
|
5764
|
+
if (debounceTimer !== null) {
|
|
5765
|
+
clearTimeout(debounceTimer);
|
|
5766
|
+
}
|
|
5767
|
+
debounceTimer = setTimeout(() => {
|
|
5768
|
+
debounceTimer = null;
|
|
5769
|
+
runAutoCommit();
|
|
5770
|
+
}, debounceMs);
|
|
5771
|
+
};
|
|
5772
|
+
const watcher = fsWatch(worktreePath, { recursive: true }, (eventType, filename) => {
|
|
5773
|
+
if (stopped) return;
|
|
5774
|
+
if (filename && (filename.startsWith(".git") || filename.startsWith(".git/"))) return;
|
|
5775
|
+
scheduleDebounce();
|
|
5776
|
+
});
|
|
5777
|
+
return {
|
|
5778
|
+
stop() {
|
|
5779
|
+
stopped = true;
|
|
5780
|
+
if (debounceTimer !== null) {
|
|
5781
|
+
clearTimeout(debounceTimer);
|
|
5782
|
+
debounceTimer = null;
|
|
5783
|
+
}
|
|
5784
|
+
watcher.close();
|
|
5785
|
+
}
|
|
5786
|
+
};
|
|
5787
|
+
}
|
|
5788
|
+
async function installCommitCadence(ctx) {
|
|
5789
|
+
if (!ctx.enabled) {
|
|
5790
|
+
return { method: "none" };
|
|
5791
|
+
}
|
|
5792
|
+
if (ctx.agentType === "claude") {
|
|
5793
|
+
await installPostToolUseHook(ctx.worktreePath, ctx.workerName);
|
|
5794
|
+
return { method: "hook" };
|
|
5795
|
+
}
|
|
5796
|
+
return { method: "fallback-poll" };
|
|
5797
|
+
}
|
|
5798
|
+
async function uninstallCommitCadence(ctx) {
|
|
5799
|
+
if (ctx.agentType !== "claude") return;
|
|
5800
|
+
const settingsPath = join20(ctx.worktreePath, ".claude", "settings.json");
|
|
5801
|
+
try {
|
|
5802
|
+
const raw = await readFile9(settingsPath, "utf-8");
|
|
5803
|
+
const parsed = JSON.parse(raw);
|
|
5804
|
+
const filtered = (parsed.hooks?.PostToolUse ?? []).filter(
|
|
5805
|
+
(h) => h.matcher !== HOOK_MATCHER
|
|
5806
|
+
);
|
|
5807
|
+
const updated = {
|
|
5808
|
+
...parsed,
|
|
5809
|
+
hooks: {
|
|
5810
|
+
...parsed.hooks,
|
|
5811
|
+
PostToolUse: filtered
|
|
5812
|
+
}
|
|
5813
|
+
};
|
|
5814
|
+
await writeFile6(settingsPath, JSON.stringify(updated, null, 2) + "\n", "utf-8");
|
|
5815
|
+
} catch {
|
|
5816
|
+
}
|
|
5817
|
+
}
|
|
5818
|
+
var SENTINEL_FILENAME, HOOK_MATCHER, DEFAULT_POLL_DEBOUNCE_MS, WORKER_NAME_RE;
|
|
5819
|
+
var init_worker_commit_cadence = __esm({
|
|
5820
|
+
"src/team/worker-commit-cadence.ts"() {
|
|
5821
|
+
"use strict";
|
|
5822
|
+
SENTINEL_FILENAME = ".hook-paused";
|
|
5823
|
+
HOOK_MATCHER = "Write|Edit|MultiEdit";
|
|
5824
|
+
DEFAULT_POLL_DEBOUNCE_MS = 3e3;
|
|
5825
|
+
WORKER_NAME_RE = /^[A-Za-z0-9_-]{1,50}$/;
|
|
5826
|
+
}
|
|
5827
|
+
});
|
|
5828
|
+
|
|
5829
|
+
// src/team/merge-orchestrator.ts
|
|
5830
|
+
import { execFileSync as execFileSync4 } from "node:child_process";
|
|
5831
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
5832
|
+
import { mkdir as mkdir9, appendFile as appendFile5 } from "node:fs/promises";
|
|
5833
|
+
import { dirname as dirname14, join as join21 } from "node:path";
|
|
5834
|
+
function mergerWorktreePathFor(repoRoot, teamName) {
|
|
5835
|
+
return join21(repoRoot, ".omcp", "team", sanitizeName(teamName), "merger");
|
|
5836
|
+
}
|
|
5837
|
+
function persistedStatePath(repoRoot, teamName) {
|
|
5838
|
+
return join21(
|
|
5839
|
+
repoRoot,
|
|
5840
|
+
".omcp",
|
|
5841
|
+
"state",
|
|
5842
|
+
"team",
|
|
5843
|
+
sanitizeName(teamName),
|
|
5844
|
+
"auto-merge-state.json"
|
|
5845
|
+
);
|
|
5846
|
+
}
|
|
5847
|
+
function teardownAuditPath(repoRoot, teamName) {
|
|
5848
|
+
return join21(
|
|
5849
|
+
repoRoot,
|
|
5850
|
+
".omcp",
|
|
5851
|
+
"state",
|
|
5852
|
+
"team",
|
|
5853
|
+
sanitizeName(teamName),
|
|
5854
|
+
"teardown-audit.jsonl"
|
|
5855
|
+
);
|
|
5856
|
+
}
|
|
5857
|
+
function orchestratorEventLogPath(repoRoot, teamName) {
|
|
5858
|
+
return join21(
|
|
5859
|
+
repoRoot,
|
|
5860
|
+
".omcp",
|
|
5861
|
+
"state",
|
|
5862
|
+
"team",
|
|
5863
|
+
sanitizeName(teamName),
|
|
5864
|
+
"orchestrator-events.jsonl"
|
|
5865
|
+
);
|
|
5866
|
+
}
|
|
5867
|
+
function assertLeaderBranchAllowed(leaderBranch) {
|
|
5868
|
+
const stripped = leaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
|
|
5869
|
+
if (stripped === "main" || stripped === "master") {
|
|
5870
|
+
throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
function assertRuntimeV2Gate() {
|
|
5874
|
+
if (!isRuntimeV2Enabled()) {
|
|
5875
|
+
throw new Error("auto-merge requires runtime v2 (OMC_RUNTIME_V2 is explicitly disabled).");
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5878
|
+
async function appendEvent(repoRoot, teamName, event) {
|
|
5879
|
+
const path4 = orchestratorEventLogPath(repoRoot, teamName);
|
|
5880
|
+
await mkdir9(dirname14(path4), { recursive: true });
|
|
5881
|
+
const full = {
|
|
5882
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5883
|
+
team: teamName,
|
|
5884
|
+
...event
|
|
5885
|
+
};
|
|
5886
|
+
await appendFile5(path4, `${JSON.stringify(full)}
|
|
5887
|
+
`, "utf-8");
|
|
5888
|
+
}
|
|
5889
|
+
function createMutex() {
|
|
5890
|
+
let lock = Promise.resolve();
|
|
5891
|
+
return (fn) => {
|
|
5892
|
+
const next = lock.then(fn, fn);
|
|
5893
|
+
lock = next.catch(() => void 0);
|
|
5894
|
+
return next;
|
|
5895
|
+
};
|
|
5896
|
+
}
|
|
5897
|
+
function gitRevParseHead(repoRoot, branch) {
|
|
5898
|
+
return execFileSync4("git", ["rev-parse", `refs/heads/${branch}`], {
|
|
5899
|
+
cwd: repoRoot,
|
|
5900
|
+
encoding: "utf-8",
|
|
5901
|
+
stdio: "pipe"
|
|
5902
|
+
}).trim();
|
|
5903
|
+
}
|
|
5904
|
+
function gitPath(worktreePath, gitPathName) {
|
|
5905
|
+
try {
|
|
5906
|
+
const resolved = execFileSync4("git", ["rev-parse", "--git-path", gitPathName], {
|
|
5907
|
+
cwd: worktreePath,
|
|
5908
|
+
encoding: "utf-8",
|
|
5909
|
+
stdio: "pipe"
|
|
5910
|
+
}).trim();
|
|
5911
|
+
if (resolved) return resolved;
|
|
5912
|
+
} catch {
|
|
5913
|
+
}
|
|
5914
|
+
return join21(worktreePath, ".git", gitPathName);
|
|
5915
|
+
}
|
|
5916
|
+
function isRebaseInProgress(worktreePath) {
|
|
5917
|
+
return existsSync18(gitPath(worktreePath, "rebase-merge"));
|
|
5918
|
+
}
|
|
5919
|
+
function isWorktreeRegistered(repoRoot, wtPath) {
|
|
5920
|
+
try {
|
|
5921
|
+
const out = execFileSync4("git", ["worktree", "list", "--porcelain"], {
|
|
5922
|
+
cwd: repoRoot,
|
|
5923
|
+
encoding: "utf-8",
|
|
5924
|
+
stdio: "pipe"
|
|
5925
|
+
});
|
|
5926
|
+
for (const line of out.split("\n")) {
|
|
5927
|
+
if (line.startsWith("worktree ")) {
|
|
5928
|
+
if (line.slice("worktree ".length).trim() === wtPath) return true;
|
|
5929
|
+
}
|
|
5930
|
+
}
|
|
5931
|
+
} catch {
|
|
5932
|
+
}
|
|
5933
|
+
return false;
|
|
5934
|
+
}
|
|
5935
|
+
function ensureMergerWorktree(repoRoot, mergerPath, leaderBranch) {
|
|
5936
|
+
ensureDirWithMode(dirname14(mergerPath));
|
|
5937
|
+
if (existsSync18(mergerPath) && isWorktreeRegistered(repoRoot, mergerPath)) {
|
|
5938
|
+
return;
|
|
5939
|
+
}
|
|
5940
|
+
execFileSync4("git", ["worktree", "add", "--force", mergerPath, leaderBranch], {
|
|
5941
|
+
cwd: repoRoot,
|
|
5942
|
+
stdio: "pipe"
|
|
5943
|
+
});
|
|
5944
|
+
}
|
|
5945
|
+
function preflightMergerWorktree(mergerPath, leaderBranch) {
|
|
5946
|
+
try {
|
|
5947
|
+
execFileSync4("git", ["fetch", "--no-tags", "origin", leaderBranch], {
|
|
5948
|
+
cwd: mergerPath,
|
|
5949
|
+
stdio: "pipe"
|
|
5950
|
+
});
|
|
5951
|
+
} catch {
|
|
5952
|
+
}
|
|
5953
|
+
execFileSync4("git", ["reset", "--hard", leaderBranch], {
|
|
5954
|
+
cwd: mergerPath,
|
|
5955
|
+
stdio: "pipe"
|
|
5956
|
+
});
|
|
5957
|
+
}
|
|
5958
|
+
function parseUUFiles(porcelainOutput) {
|
|
5959
|
+
const files = [];
|
|
5960
|
+
for (const line of porcelainOutput.split("\n")) {
|
|
5961
|
+
if (line.startsWith("UU ")) {
|
|
5962
|
+
files.push(line.slice(3).trim());
|
|
5963
|
+
} else if (line.startsWith("AA ") || line.startsWith("DD ")) {
|
|
5964
|
+
files.push(line.slice(3).trim());
|
|
5965
|
+
}
|
|
5966
|
+
}
|
|
5967
|
+
return files;
|
|
5968
|
+
}
|
|
5969
|
+
async function startMergeOrchestrator(config) {
|
|
5970
|
+
assertRuntimeV2Gate();
|
|
5971
|
+
assertLeaderBranchAllowed(config.leaderBranch);
|
|
5972
|
+
validateBranchName(config.leaderBranch);
|
|
5973
|
+
const pollIntervalMs = config.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
5974
|
+
const drainTimeoutMs = config.drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS;
|
|
5975
|
+
const mergerPath = mergerWorktreePathFor(config.repoRoot, config.teamName);
|
|
5976
|
+
validateResolvedPath(mergerPath, config.repoRoot);
|
|
5977
|
+
ensureMergerWorktree(config.repoRoot, mergerPath, config.leaderBranch);
|
|
5978
|
+
await ensureLeaderInbox(config.teamName, config.cwd);
|
|
5979
|
+
const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
|
|
5980
|
+
let persisted = { lastShas: {} };
|
|
5981
|
+
if (existsSync18(persistedPath)) {
|
|
5982
|
+
try {
|
|
5983
|
+
const { readFileSync: readFileSync13 } = await import("node:fs");
|
|
5984
|
+
persisted = JSON.parse(readFileSync13(persistedPath, "utf-8"));
|
|
5985
|
+
} catch {
|
|
5986
|
+
persisted = { lastShas: {} };
|
|
5987
|
+
}
|
|
5988
|
+
}
|
|
5989
|
+
const workers = /* @__PURE__ */ new Map();
|
|
5990
|
+
const pausedWorkers = /* @__PURE__ */ new Set();
|
|
5991
|
+
const mutex = createMutex();
|
|
5992
|
+
let stopped = false;
|
|
5993
|
+
function persistState() {
|
|
5994
|
+
const payload = {
|
|
5995
|
+
lastShas: Object.fromEntries(
|
|
5996
|
+
Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
|
|
5997
|
+
)
|
|
5998
|
+
};
|
|
5999
|
+
atomicWriteJson(persistedPath, payload);
|
|
6000
|
+
}
|
|
6001
|
+
async function fanOutRebase(triggeringWorker) {
|
|
6002
|
+
for (const other of workers.values()) {
|
|
6003
|
+
if (other.workerName === triggeringWorker) continue;
|
|
6004
|
+
const wtPath = other.workerWorktreePath;
|
|
6005
|
+
if (isRebaseInProgress(wtPath)) {
|
|
6006
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6007
|
+
type: "rebase_skipped_in_progress",
|
|
6008
|
+
worker: other.workerName,
|
|
6009
|
+
reason: "rebase-already-in-progress"
|
|
6010
|
+
});
|
|
6011
|
+
continue;
|
|
6012
|
+
}
|
|
6013
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6014
|
+
type: "rebase_triggered",
|
|
6015
|
+
worker: other.workerName
|
|
6016
|
+
});
|
|
6017
|
+
await pauseHookViaSentinel(wtPath);
|
|
6018
|
+
pausedWorkers.add(other.workerName);
|
|
6019
|
+
try {
|
|
6020
|
+
execFileSync4("git", ["fetch", "--no-tags", "origin", config.leaderBranch], {
|
|
6021
|
+
cwd: wtPath,
|
|
6022
|
+
stdio: "pipe"
|
|
6023
|
+
});
|
|
6024
|
+
} catch {
|
|
6025
|
+
}
|
|
6026
|
+
try {
|
|
6027
|
+
execFileSync4("git", ["rebase", config.leaderBranch], {
|
|
6028
|
+
cwd: wtPath,
|
|
6029
|
+
stdio: "pipe"
|
|
6030
|
+
});
|
|
6031
|
+
await resumeHookViaSentinel(wtPath);
|
|
6032
|
+
pausedWorkers.delete(other.workerName);
|
|
6033
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6034
|
+
type: "rebase_succeeded",
|
|
6035
|
+
worker: other.workerName
|
|
6036
|
+
});
|
|
6037
|
+
} catch {
|
|
6038
|
+
let conflictingFiles = [];
|
|
6039
|
+
try {
|
|
6040
|
+
const status = execFileSync4("git", ["status", "--porcelain"], {
|
|
6041
|
+
cwd: wtPath,
|
|
6042
|
+
encoding: "utf-8",
|
|
6043
|
+
stdio: "pipe"
|
|
6044
|
+
});
|
|
6045
|
+
conflictingFiles = parseUUFiles(status);
|
|
6046
|
+
} catch {
|
|
6047
|
+
conflictingFiles = ["(rebase status unavailable)"];
|
|
6048
|
+
}
|
|
6049
|
+
const baseSha = (() => {
|
|
6050
|
+
try {
|
|
6051
|
+
return execFileSync4("git", ["rev-parse", `refs/heads/${config.leaderBranch}`], {
|
|
6052
|
+
cwd: config.repoRoot,
|
|
6053
|
+
encoding: "utf-8",
|
|
6054
|
+
stdio: "pipe"
|
|
6055
|
+
}).trim();
|
|
6056
|
+
} catch {
|
|
6057
|
+
return "unknown";
|
|
6058
|
+
}
|
|
6059
|
+
})();
|
|
6060
|
+
const message = formatRebaseConflictForWorker({
|
|
6061
|
+
workerName: other.workerName,
|
|
6062
|
+
workerBranch: other.workerBranch,
|
|
6063
|
+
leaderBranch: config.leaderBranch,
|
|
6064
|
+
conflictingFiles,
|
|
6065
|
+
baseSha,
|
|
6066
|
+
worktreePath: wtPath,
|
|
6067
|
+
observedAt: Date.now()
|
|
6068
|
+
});
|
|
6069
|
+
try {
|
|
6070
|
+
await appendToInbox(config.teamName, other.workerName, message, config.cwd);
|
|
6071
|
+
} catch {
|
|
6072
|
+
}
|
|
6073
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6074
|
+
type: "rebase_conflict",
|
|
6075
|
+
worker: other.workerName,
|
|
6076
|
+
data: { conflictingFiles }
|
|
6077
|
+
});
|
|
6078
|
+
}
|
|
6079
|
+
}
|
|
6080
|
+
}
|
|
6081
|
+
async function attemptMergeForWorker(entry) {
|
|
6082
|
+
await mutex(async () => {
|
|
6083
|
+
const targetSha = entry.lastObservedSha;
|
|
6084
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6085
|
+
type: "merge_attempted",
|
|
6086
|
+
worker: entry.workerName,
|
|
6087
|
+
data: { targetSha }
|
|
6088
|
+
});
|
|
6089
|
+
try {
|
|
6090
|
+
preflightMergerWorktree(mergerPath, config.leaderBranch);
|
|
6091
|
+
} catch (err) {
|
|
6092
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6093
|
+
entry.consecutiveFailures += 1;
|
|
6094
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6095
|
+
type: "merge_conflict",
|
|
6096
|
+
worker: entry.workerName,
|
|
6097
|
+
reason: `preflight_failed:${reason}`
|
|
6098
|
+
});
|
|
6099
|
+
return;
|
|
6100
|
+
}
|
|
6101
|
+
const conflicts = checkMergeConflicts(
|
|
6102
|
+
entry.workerBranch,
|
|
6103
|
+
config.leaderBranch,
|
|
6104
|
+
mergerPath
|
|
6105
|
+
);
|
|
6106
|
+
if (conflicts.length > 0) {
|
|
6107
|
+
let mergeBaseSha = "unknown";
|
|
6108
|
+
try {
|
|
6109
|
+
mergeBaseSha = execFileSync4(
|
|
6110
|
+
"git",
|
|
6111
|
+
["merge-base", config.leaderBranch, entry.workerBranch],
|
|
6112
|
+
{ cwd: mergerPath, encoding: "utf-8", stdio: "pipe" }
|
|
6113
|
+
).trim();
|
|
6114
|
+
} catch {
|
|
6115
|
+
}
|
|
6116
|
+
const message = formatMergeConflictForLeader({
|
|
6117
|
+
workerName: entry.workerName,
|
|
6118
|
+
workerBranch: entry.workerBranch,
|
|
6119
|
+
leaderBranch: config.leaderBranch,
|
|
6120
|
+
conflictingFiles: conflicts,
|
|
6121
|
+
mergeBaseSha,
|
|
6122
|
+
observedAt: Date.now()
|
|
6123
|
+
});
|
|
6124
|
+
try {
|
|
6125
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
6126
|
+
} catch {
|
|
6127
|
+
}
|
|
6128
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6129
|
+
type: "merge_conflict",
|
|
6130
|
+
worker: entry.workerName,
|
|
6131
|
+
data: { conflictingFiles: conflicts, mergeBaseSha }
|
|
6132
|
+
});
|
|
6133
|
+
entry.consecutiveFailures += 1;
|
|
6134
|
+
return;
|
|
6135
|
+
}
|
|
6136
|
+
const result = mergeWorkerBranch(
|
|
6137
|
+
entry.workerBranch,
|
|
6138
|
+
config.leaderBranch,
|
|
6139
|
+
mergerPath
|
|
6140
|
+
);
|
|
6141
|
+
if (!result.success) {
|
|
6142
|
+
const message = formatMergeConflictForLeader({
|
|
6143
|
+
workerName: entry.workerName,
|
|
6144
|
+
workerBranch: entry.workerBranch,
|
|
6145
|
+
leaderBranch: config.leaderBranch,
|
|
6146
|
+
conflictingFiles: result.conflicts.length > 0 ? result.conflicts : ["(merge failed after clean check)"],
|
|
6147
|
+
mergeBaseSha: "unknown",
|
|
6148
|
+
observedAt: Date.now()
|
|
6149
|
+
});
|
|
6150
|
+
try {
|
|
6151
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
6152
|
+
} catch {
|
|
6153
|
+
}
|
|
6154
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6155
|
+
type: "merge_conflict",
|
|
6156
|
+
worker: entry.workerName,
|
|
6157
|
+
data: { conflictingFiles: result.conflicts }
|
|
6158
|
+
});
|
|
6159
|
+
entry.consecutiveFailures += 1;
|
|
6160
|
+
return;
|
|
6161
|
+
}
|
|
6162
|
+
entry.lastMergedSha = targetSha;
|
|
6163
|
+
entry.consecutiveFailures = 0;
|
|
6164
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6165
|
+
type: "merge_succeeded",
|
|
6166
|
+
worker: entry.workerName,
|
|
6167
|
+
data: { mergeCommit: result.mergeCommit, targetSha }
|
|
6168
|
+
});
|
|
6169
|
+
if (stopped) return;
|
|
6170
|
+
await fanOutRebase(entry.workerName);
|
|
6171
|
+
});
|
|
6172
|
+
}
|
|
6173
|
+
async function runPollOnce() {
|
|
6174
|
+
if (stopped) return;
|
|
6175
|
+
for (const entry of workers.values()) {
|
|
6176
|
+
const skipModulo = Math.min(30, Math.pow(2, entry.consecutiveFailures));
|
|
6177
|
+
if (skipModulo > 1 && pollTickCount % skipModulo !== 0) {
|
|
6178
|
+
continue;
|
|
6179
|
+
}
|
|
6180
|
+
if (pausedWorkers.has(entry.workerName)) {
|
|
6181
|
+
if (!isRebaseInProgress(entry.workerWorktreePath)) {
|
|
6182
|
+
await handleRebaseResolution(entry);
|
|
6183
|
+
} else {
|
|
6184
|
+
continue;
|
|
6185
|
+
}
|
|
6186
|
+
}
|
|
6187
|
+
let currentSha;
|
|
6188
|
+
try {
|
|
6189
|
+
currentSha = gitRevParseHead(config.repoRoot, entry.workerBranch);
|
|
6190
|
+
} catch (err) {
|
|
6191
|
+
entry.consecutiveFailures += 1;
|
|
6192
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6193
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6194
|
+
type: "commit_observed",
|
|
6195
|
+
worker: entry.workerName,
|
|
6196
|
+
reason: `rev_parse_failed:${reason}`
|
|
6197
|
+
});
|
|
6198
|
+
continue;
|
|
6199
|
+
}
|
|
6200
|
+
if (currentSha && currentSha !== entry.lastObservedSha) {
|
|
6201
|
+
entry.lastObservedSha = currentSha;
|
|
6202
|
+
try {
|
|
6203
|
+
persistState();
|
|
6204
|
+
} catch {
|
|
6205
|
+
}
|
|
6206
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6207
|
+
type: "commit_observed",
|
|
6208
|
+
worker: entry.workerName,
|
|
6209
|
+
data: { sha: currentSha }
|
|
6210
|
+
});
|
|
6211
|
+
try {
|
|
6212
|
+
await attemptMergeForWorker(entry);
|
|
6213
|
+
} catch (err) {
|
|
6214
|
+
entry.consecutiveFailures += 1;
|
|
6215
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
6216
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6217
|
+
type: "merge_conflict",
|
|
6218
|
+
worker: entry.workerName,
|
|
6219
|
+
reason: `merge_threw:${reason}`
|
|
6220
|
+
});
|
|
6221
|
+
}
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6224
|
+
}
|
|
6225
|
+
async function handleRebaseResolution(entry) {
|
|
6226
|
+
pausedWorkers.delete(entry.workerName);
|
|
6227
|
+
try {
|
|
6228
|
+
const status = execFileSync4("git", ["status", "--porcelain"], {
|
|
6229
|
+
cwd: entry.workerWorktreePath,
|
|
6230
|
+
encoding: "utf-8",
|
|
6231
|
+
stdio: "pipe"
|
|
6232
|
+
}).trim();
|
|
6233
|
+
if (status.length > 0) {
|
|
6234
|
+
const dirtyFiles = status.split("\n").map((l) => l.trim().replace(/^\S+\s+/, "")).filter((s) => s.length > 0);
|
|
6235
|
+
const audit = `## Auto-commit audit: the following files were modified during rebase pause and will be folded into the next auto-commit:
|
|
6236
|
+
${dirtyFiles.map((f) => `- \`${f}\``).join("\n")}`;
|
|
6237
|
+
try {
|
|
6238
|
+
await appendToInbox(config.teamName, entry.workerName, audit, config.cwd);
|
|
6239
|
+
} catch {
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
} catch {
|
|
6243
|
+
}
|
|
6244
|
+
await resumeHookViaSentinel(entry.workerWorktreePath);
|
|
6245
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6246
|
+
type: "rebase_resolved",
|
|
6247
|
+
worker: entry.workerName
|
|
6248
|
+
});
|
|
6249
|
+
}
|
|
6250
|
+
let pollTickCount = 0;
|
|
6251
|
+
const interval = setInterval(() => {
|
|
6252
|
+
pollTickCount += 1;
|
|
6253
|
+
void runPollOnce().catch(() => {
|
|
6254
|
+
});
|
|
6255
|
+
}, pollIntervalMs);
|
|
6256
|
+
if (typeof interval.unref === "function") interval.unref();
|
|
6257
|
+
return {
|
|
6258
|
+
async registerWorker(workerName) {
|
|
6259
|
+
if (workers.has(workerName)) return;
|
|
6260
|
+
const workerBranch = getBranchName(config.teamName, workerName);
|
|
6261
|
+
validateBranchName(workerBranch);
|
|
6262
|
+
const wtPath = getWorktreePath(config.repoRoot, config.teamName, workerName);
|
|
6263
|
+
let seedSha = persisted.lastShas[workerName] ?? "";
|
|
6264
|
+
if (!seedSha) {
|
|
6265
|
+
try {
|
|
6266
|
+
seedSha = gitRevParseHead(config.repoRoot, workerBranch);
|
|
6267
|
+
} catch {
|
|
6268
|
+
seedSha = "";
|
|
6269
|
+
}
|
|
6270
|
+
}
|
|
6271
|
+
workers.set(workerName, {
|
|
6272
|
+
workerName,
|
|
6273
|
+
workerBranch,
|
|
6274
|
+
workerWorktreePath: wtPath,
|
|
6275
|
+
lastObservedSha: seedSha,
|
|
6276
|
+
lastMergedSha: seedSha,
|
|
6277
|
+
consecutiveFailures: 0
|
|
6278
|
+
});
|
|
6279
|
+
try {
|
|
6280
|
+
persistState();
|
|
6281
|
+
} catch {
|
|
6282
|
+
}
|
|
6283
|
+
},
|
|
6284
|
+
async unregisterWorker(workerName) {
|
|
6285
|
+
workers.delete(workerName);
|
|
6286
|
+
pausedWorkers.delete(workerName);
|
|
6287
|
+
try {
|
|
6288
|
+
persistState();
|
|
6289
|
+
} catch {
|
|
6290
|
+
}
|
|
6291
|
+
},
|
|
6292
|
+
async pollOnce() {
|
|
6293
|
+
await runPollOnce();
|
|
6294
|
+
},
|
|
6295
|
+
async drainAndStop() {
|
|
6296
|
+
stopped = true;
|
|
6297
|
+
clearInterval(interval);
|
|
6298
|
+
const start = Date.now();
|
|
6299
|
+
const unmerged = [];
|
|
6300
|
+
const candidates = Array.from(workers.values()).filter(
|
|
6301
|
+
(w) => w.lastObservedSha && w.lastObservedSha !== w.lastMergedSha
|
|
6302
|
+
);
|
|
6303
|
+
for (const entry of candidates) {
|
|
6304
|
+
const remaining = drainTimeoutMs - (Date.now() - start);
|
|
6305
|
+
if (remaining <= 0) {
|
|
6306
|
+
unmerged.push({ workerName: entry.workerName, reason: "drain-timeout" });
|
|
6307
|
+
continue;
|
|
6308
|
+
}
|
|
6309
|
+
const merged = await Promise.race([
|
|
6310
|
+
(async () => {
|
|
6311
|
+
try {
|
|
6312
|
+
await attemptMergeForWorker(entry);
|
|
6313
|
+
return true;
|
|
6314
|
+
} catch {
|
|
6315
|
+
return false;
|
|
6316
|
+
}
|
|
6317
|
+
})(),
|
|
6318
|
+
new Promise((resolve5) => {
|
|
6319
|
+
const t = setTimeout(() => resolve5(false), remaining);
|
|
6320
|
+
if (typeof t.unref === "function") t.unref();
|
|
6321
|
+
})
|
|
6322
|
+
]);
|
|
6323
|
+
if (!merged || entry.lastMergedSha !== entry.lastObservedSha) {
|
|
6324
|
+
unmerged.push({
|
|
6325
|
+
workerName: entry.workerName,
|
|
6326
|
+
reason: merged ? "merge-conflict" : "drain-timeout"
|
|
6327
|
+
});
|
|
6328
|
+
}
|
|
6329
|
+
}
|
|
6330
|
+
if (unmerged.length > 0) {
|
|
6331
|
+
const auditPath = teardownAuditPath(config.repoRoot, config.teamName);
|
|
6332
|
+
await mkdir9(dirname14(auditPath), { recursive: true });
|
|
6333
|
+
for (const u of unmerged) {
|
|
6334
|
+
const row = JSON.stringify({
|
|
6335
|
+
type: "unmerged_at_shutdown",
|
|
6336
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6337
|
+
team: config.teamName,
|
|
6338
|
+
worker: u.workerName,
|
|
6339
|
+
reason: u.reason
|
|
6340
|
+
});
|
|
6341
|
+
try {
|
|
6342
|
+
await appendFile5(auditPath, `${row}
|
|
6343
|
+
`, "utf-8");
|
|
6344
|
+
} catch {
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
const message = `## Teardown audit: unmerged worker branches at shutdown
|
|
6348
|
+
|
|
6349
|
+
${unmerged.map((u) => `- ${u.workerName}: ${u.reason}`).join("\n")}`;
|
|
6350
|
+
try {
|
|
6351
|
+
await appendToLeaderInbox(config.teamName, message, config.cwd);
|
|
6352
|
+
} catch {
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
return { unmerged };
|
|
6356
|
+
},
|
|
6357
|
+
getState() {
|
|
6358
|
+
return {
|
|
6359
|
+
workers: Array.from(workers.keys()),
|
|
6360
|
+
lastShas: Object.fromEntries(
|
|
6361
|
+
Array.from(workers.values()).map((w) => [w.workerName, w.lastObservedSha])
|
|
6362
|
+
),
|
|
6363
|
+
mergerWorktreePath: mergerPath
|
|
6364
|
+
};
|
|
6365
|
+
}
|
|
6366
|
+
};
|
|
6367
|
+
}
|
|
6368
|
+
async function recoverFromRestart(config) {
|
|
6369
|
+
const persistedPath = persistedStatePath(config.repoRoot, config.teamName);
|
|
6370
|
+
let persistedShasLoaded = 0;
|
|
6371
|
+
if (existsSync18(persistedPath)) {
|
|
6372
|
+
try {
|
|
6373
|
+
const { readFileSync: readFileSync13 } = await import("node:fs");
|
|
6374
|
+
const persisted = JSON.parse(readFileSync13(persistedPath, "utf-8"));
|
|
6375
|
+
persistedShasLoaded = Object.keys(persisted.lastShas ?? {}).length;
|
|
6376
|
+
} catch {
|
|
6377
|
+
persistedShasLoaded = 0;
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
const orphanedRebases = [];
|
|
6381
|
+
let entries = [];
|
|
6382
|
+
try {
|
|
6383
|
+
entries = listTeamWorktrees(config.teamName, config.repoRoot).map((w) => ({
|
|
6384
|
+
workerName: w.workerName,
|
|
6385
|
+
path: w.path
|
|
6386
|
+
}));
|
|
6387
|
+
} catch {
|
|
6388
|
+
entries = [];
|
|
5340
6389
|
}
|
|
5341
|
-
const
|
|
5342
|
-
if (!entry
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
6390
|
+
for (const entry of entries) {
|
|
6391
|
+
if (!isRebaseInProgress(entry.path)) continue;
|
|
6392
|
+
orphanedRebases.push(entry.workerName);
|
|
6393
|
+
const message = `### Runtime restart recovery \u2014 your branch is mid-rebase
|
|
6394
|
+
|
|
6395
|
+
Runtime restarted while your branch was mid-rebase onto \`${config.leaderBranch}\`.
|
|
6396
|
+
|
|
6397
|
+
**Worktree:** \`${entry.path}\`
|
|
6398
|
+
|
|
6399
|
+
Cadence remains paused. Resolve and \`git rebase --continue\`, or \`git rebase --abort\` to bail.
|
|
6400
|
+
Cadence resumes once the git rebase state is gone.`;
|
|
6401
|
+
try {
|
|
6402
|
+
await appendToInbox(config.teamName, entry.workerName, message, config.cwd);
|
|
6403
|
+
} catch {
|
|
5349
6404
|
}
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
6405
|
+
}
|
|
6406
|
+
if (orphanedRebases.length > 0 || persistedShasLoaded > 0) {
|
|
6407
|
+
try {
|
|
6408
|
+
await appendEvent(config.repoRoot, config.teamName, {
|
|
6409
|
+
type: "restart_recovery",
|
|
6410
|
+
data: { orphanedRebases, persistedShasLoaded }
|
|
6411
|
+
});
|
|
6412
|
+
} catch {
|
|
5353
6413
|
}
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
message
|
|
5357
|
-
};
|
|
5358
|
-
if (typeof f.file === "string" && f.file) finding.file = f.file;
|
|
5359
|
-
if (typeof f.line === "number" && Number.isFinite(f.line)) finding.line = f.line;
|
|
5360
|
-
return finding;
|
|
5361
|
-
});
|
|
5362
|
-
return {
|
|
5363
|
-
role,
|
|
5364
|
-
task_id: taskId,
|
|
5365
|
-
verdict,
|
|
5366
|
-
summary,
|
|
5367
|
-
findings
|
|
5368
|
-
};
|
|
5369
|
-
}
|
|
5370
|
-
function cliWorkerOutputFilePath(teamStateRootAbs, workerName) {
|
|
5371
|
-
return `${teamStateRootAbs.replaceAll("\\", "/")}/workers/${workerName}/verdict.json`;
|
|
6414
|
+
}
|
|
6415
|
+
return { orphanedRebases, persistedShasLoaded };
|
|
5372
6416
|
}
|
|
5373
|
-
var
|
|
5374
|
-
var
|
|
5375
|
-
"src/team/
|
|
6417
|
+
var DEFAULT_POLL_INTERVAL_MS, DEFAULT_DRAIN_TIMEOUT_MS;
|
|
6418
|
+
var init_merge_orchestrator = __esm({
|
|
6419
|
+
"src/team/merge-orchestrator.ts"() {
|
|
5376
6420
|
"use strict";
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
6421
|
+
init_fs_utils();
|
|
6422
|
+
init_runtime_flags();
|
|
6423
|
+
init_tmux_session();
|
|
6424
|
+
init_git_worktree();
|
|
6425
|
+
init_merge_coordinator();
|
|
6426
|
+
init_worker_bootstrap();
|
|
6427
|
+
init_leader_inbox();
|
|
6428
|
+
init_conflict_mailbox();
|
|
6429
|
+
init_worker_commit_cadence();
|
|
6430
|
+
DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
6431
|
+
DEFAULT_DRAIN_TIMEOUT_MS = 1e4;
|
|
5385
6432
|
}
|
|
5386
6433
|
});
|
|
5387
6434
|
|
|
@@ -5399,15 +6446,53 @@ __export(runtime_v2_exports, {
|
|
|
5399
6446
|
startTeamV2: () => startTeamV2,
|
|
5400
6447
|
writeWatchdogFailedMarker: () => writeWatchdogFailedMarker
|
|
5401
6448
|
});
|
|
5402
|
-
import { join as
|
|
5403
|
-
import { existsSync as
|
|
5404
|
-
import { mkdir as
|
|
6449
|
+
import { join as join22, resolve as resolve4 } from "path";
|
|
6450
|
+
import { existsSync as existsSync19 } from "fs";
|
|
6451
|
+
import { mkdir as mkdir10, readdir as readdir2, readFile as readFile10, rm as rm4, writeFile as writeFile7 } from "fs/promises";
|
|
5405
6452
|
import { performance } from "perf_hooks";
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
6453
|
+
import { execFileSync as execFileSync5 } from "node:child_process";
|
|
6454
|
+
function registerTeamOrchestrator(teamName, handle) {
|
|
6455
|
+
orchestratorByTeam.set(teamName, handle);
|
|
6456
|
+
}
|
|
6457
|
+
function getTeamOrchestrator(teamName) {
|
|
6458
|
+
return orchestratorByTeam.get(teamName);
|
|
6459
|
+
}
|
|
6460
|
+
function unregisterTeamOrchestrator(teamName) {
|
|
6461
|
+
orchestratorByTeam.delete(teamName);
|
|
6462
|
+
}
|
|
6463
|
+
function registerTeamCadence(teamName, context, poller) {
|
|
6464
|
+
const entry = cadenceByTeam.get(teamName) ?? { pollers: [], contexts: [] };
|
|
6465
|
+
entry.contexts.push(context);
|
|
6466
|
+
if (poller) entry.pollers.push(poller);
|
|
6467
|
+
cadenceByTeam.set(teamName, entry);
|
|
6468
|
+
}
|
|
6469
|
+
async function stopTeamCadence(teamName) {
|
|
6470
|
+
const entry = cadenceByTeam.get(teamName);
|
|
6471
|
+
if (!entry) return;
|
|
6472
|
+
cadenceByTeam.delete(teamName);
|
|
6473
|
+
for (const poller of entry.pollers) {
|
|
6474
|
+
try {
|
|
6475
|
+
poller.stop();
|
|
6476
|
+
} catch {
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
for (const context of entry.contexts) {
|
|
6480
|
+
try {
|
|
6481
|
+
await uninstallCommitCadence(context);
|
|
6482
|
+
} catch {
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
function resolveLeaderBranch(cwd) {
|
|
6487
|
+
const out = execFileSync5("git", ["branch", "--show-current"], {
|
|
6488
|
+
cwd,
|
|
6489
|
+
encoding: "utf-8",
|
|
6490
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
6491
|
+
}).trim();
|
|
6492
|
+
if (!out) {
|
|
6493
|
+
throw new Error("auto-merge requires a non-detached leader branch (git branch --show-current returned empty)");
|
|
6494
|
+
}
|
|
6495
|
+
return out;
|
|
5411
6496
|
}
|
|
5412
6497
|
function resolveTaskAssignment(task, resolvedRouting, roleRoutingConfig, resolvedBinaryPaths, fallbackAgent) {
|
|
5413
6498
|
const canonicalRoles = new Set(CANONICAL_TEAM_ROLES);
|
|
@@ -5499,7 +6584,7 @@ function buildV2TaskInstruction(teamName, workerName, task, taskId, cliOutputCon
|
|
|
5499
6584
|
{}
|
|
5500
6585
|
);
|
|
5501
6586
|
const completeTaskCommand = formatOmcCliInvocation(
|
|
5502
|
-
`team api transition-task-status --input '${JSON.stringify({ team_name: teamName, task_id: taskId, from: "in_progress", to: "completed", claim_token: "<claim_token>" })}' --json`
|
|
6587
|
+
`team api transition-task-status --input '${JSON.stringify({ team_name: teamName, task_id: taskId, from: "in_progress", to: "completed", claim_token: "<claim_token>", result: "Summary: <what changed>\\nVerification: <tests/checks run>\\nSubagent skip reason: worker protocol forbids nested subagents; completed focused probe in-session" })}' --json`
|
|
5503
6588
|
);
|
|
5504
6589
|
const failTaskCommand = formatOmcCliInvocation(
|
|
5505
6590
|
`team api transition-task-status --input '${JSON.stringify({ team_name: teamName, task_id: taskId, from: "in_progress", to: "failed", claim_token: "<claim_token>" })}' --json`
|
|
@@ -5514,6 +6599,7 @@ function buildV2TaskInstruction(teamName, workerName, task, taskId, cliOutputCon
|
|
|
5514
6599
|
`2. Do the work described below.`,
|
|
5515
6600
|
`3. On completion (use claim_token from step 1):`,
|
|
5516
6601
|
` ${completeTaskCommand}`,
|
|
6602
|
+
` The result field is required for completion evidence. For broad delegated tasks, include either "Subagent skip reason: <why no nested worker was needed/allowed>" or, only when explicitly allowed by the leader, "Subagent spawn evidence: <child task names/thread ids and integrated findings>".`,
|
|
5517
6603
|
`4. On failure (use claim_token from step 1):`,
|
|
5518
6604
|
` ${failTaskCommand}`,
|
|
5519
6605
|
`5. ACK/progress replies are not a stop signal. Keep executing your assigned or next feasible work until the task is actually complete or failed, then transition and exit.`,
|
|
@@ -5550,7 +6636,7 @@ function hasWorkerStatusProgress(status, taskId) {
|
|
|
5550
6636
|
}
|
|
5551
6637
|
async function hasWorkerTaskClaimEvidence(teamName, workerName, cwd, taskId) {
|
|
5552
6638
|
try {
|
|
5553
|
-
const raw = await
|
|
6639
|
+
const raw = await readFile10(absPath(cwd, TeamPaths.taskFile(teamName, taskId)), "utf-8");
|
|
5554
6640
|
const task = JSON.parse(raw);
|
|
5555
6641
|
return task.owner === workerName && ["in_progress", "completed", "failed"].includes(task.status);
|
|
5556
6642
|
} catch {
|
|
@@ -5649,6 +6735,18 @@ async function spawnV2Worker(opts) {
|
|
|
5649
6735
|
if (usePromptMode) {
|
|
5650
6736
|
launchArgs.push(...getPromptModeArgs(opts.agentType, promptModeStartupPrompt));
|
|
5651
6737
|
}
|
|
6738
|
+
if (opts.autoMerge && opts.worktreePath) {
|
|
6739
|
+
const cadenceContext = {
|
|
6740
|
+
teamName: opts.teamName,
|
|
6741
|
+
workerName: opts.workerName,
|
|
6742
|
+
worktreePath: opts.worktreePath,
|
|
6743
|
+
agentType: opts.agentType,
|
|
6744
|
+
enabled: true
|
|
6745
|
+
};
|
|
6746
|
+
const cadence = await installCommitCadence(cadenceContext);
|
|
6747
|
+
const poller = cadence.method === "fallback-poll" ? startFallbackPoller(opts.worktreePath, opts.workerName) : void 0;
|
|
6748
|
+
registerTeamCadence(opts.teamName, cadenceContext, poller);
|
|
6749
|
+
}
|
|
5652
6750
|
const paneConfig = {
|
|
5653
6751
|
teamName: opts.teamName,
|
|
5654
6752
|
workerName: opts.workerName,
|
|
@@ -5752,16 +6850,16 @@ async function rollbackUnpersistedNativeWorktreeStartup(teamName, cwd, cause) {
|
|
|
5752
6850
|
await rm4(teamRoot, { recursive: true, force: true });
|
|
5753
6851
|
return;
|
|
5754
6852
|
}
|
|
5755
|
-
await
|
|
5756
|
-
await
|
|
6853
|
+
await mkdir10(teamRoot, { recursive: true });
|
|
6854
|
+
await writeFile7(join22(teamRoot, "startup-failure.json"), JSON.stringify({
|
|
5757
6855
|
reason: "startup_failed_before_config_persisted",
|
|
5758
6856
|
error: errorMessage,
|
|
5759
6857
|
preserved: cleanup.preserved,
|
|
5760
6858
|
recorded_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5761
6859
|
}, null, 2), "utf-8");
|
|
5762
6860
|
} catch (rollbackError) {
|
|
5763
|
-
await
|
|
5764
|
-
await
|
|
6861
|
+
await mkdir10(teamRoot, { recursive: true });
|
|
6862
|
+
await writeFile7(join22(teamRoot, "startup-failure.json"), JSON.stringify({
|
|
5765
6863
|
reason: "startup_failed_before_config_persisted",
|
|
5766
6864
|
error: errorMessage,
|
|
5767
6865
|
rollback_error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
|
|
@@ -5791,9 +6889,23 @@ async function startTeamV2(config) {
|
|
|
5791
6889
|
validateTeamName(sanitized);
|
|
5792
6890
|
const pluginCfg = config.pluginConfig ?? loadConfig();
|
|
5793
6891
|
const resolvedRouting = buildResolvedRoutingSnapshot(pluginCfg);
|
|
5794
|
-
|
|
6892
|
+
let worktreeMode = normalizeTeamWorktreeMode(
|
|
5795
6893
|
process.env.OMC_TEAM_WORKTREE_MODE ?? pluginCfg.team?.ops?.worktreeMode
|
|
5796
6894
|
);
|
|
6895
|
+
let autoMergeLeaderBranch;
|
|
6896
|
+
if (config.autoMerge) {
|
|
6897
|
+
if (!isRuntimeV2Enabled()) {
|
|
6898
|
+
throw new Error("auto-merge requires OMC_RUNTIME_V2=1 (this feature is v2-only).");
|
|
6899
|
+
}
|
|
6900
|
+
autoMergeLeaderBranch = resolveLeaderBranch(leaderCwd);
|
|
6901
|
+
const stripped = autoMergeLeaderBranch.replace(/^refs\/heads\//i, "").toLowerCase();
|
|
6902
|
+
if (stripped === "main" || stripped === "master") {
|
|
6903
|
+
throw new Error("auto-merge refuses main/master leader branch \u2014 use a feature branch");
|
|
6904
|
+
}
|
|
6905
|
+
if (worktreeMode !== "named") {
|
|
6906
|
+
worktreeMode = "named";
|
|
6907
|
+
}
|
|
6908
|
+
}
|
|
5797
6909
|
const workspaceMode = worktreeMode === "disabled" ? "single" : "worktree";
|
|
5798
6910
|
const agentTypes = config.agentTypes;
|
|
5799
6911
|
const resolvedBinaryPaths = {};
|
|
@@ -5823,9 +6935,9 @@ async function startTeamV2(config) {
|
|
|
5823
6935
|
} catch {
|
|
5824
6936
|
}
|
|
5825
6937
|
}
|
|
5826
|
-
await
|
|
5827
|
-
await
|
|
5828
|
-
await
|
|
6938
|
+
await mkdir10(absPath(leaderCwd, TeamPaths.tasks(sanitized)), { recursive: true });
|
|
6939
|
+
await mkdir10(absPath(leaderCwd, TeamPaths.workers(sanitized)), { recursive: true });
|
|
6940
|
+
await mkdir10(join22(leaderCwd, ".omcp", "state", "team", sanitized, "mailbox"), { recursive: true });
|
|
5829
6941
|
const missingBinaryLogFailure = createSwallowedErrorLogger(
|
|
5830
6942
|
"team.runtime-v2.startTeamV2 cli_binary_missing event failed"
|
|
5831
6943
|
);
|
|
@@ -5843,14 +6955,15 @@ async function startTeamV2(config) {
|
|
|
5843
6955
|
for (let i = 0; i < config.tasks.length; i++) {
|
|
5844
6956
|
const taskId = String(i + 1);
|
|
5845
6957
|
const taskFilePath = absPath(leaderCwd, TeamPaths.taskFile(sanitized, taskId));
|
|
5846
|
-
await
|
|
5847
|
-
await
|
|
6958
|
+
await mkdir10(join22(taskFilePath, ".."), { recursive: true });
|
|
6959
|
+
await writeFile7(taskFilePath, JSON.stringify({
|
|
5848
6960
|
id: taskId,
|
|
5849
6961
|
subject: config.tasks[i].subject,
|
|
5850
6962
|
description: config.tasks[i].description,
|
|
5851
6963
|
status: "pending",
|
|
5852
6964
|
owner: null,
|
|
5853
6965
|
result: null,
|
|
6966
|
+
...config.tasks[i].delegation ? { delegation: config.tasks[i].delegation } : {},
|
|
5854
6967
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5855
6968
|
}, null, 2), "utf-8");
|
|
5856
6969
|
}
|
|
@@ -5916,7 +7029,7 @@ async function startTeamV2(config) {
|
|
|
5916
7029
|
});
|
|
5917
7030
|
const worktree = workerWorktrees.get(wName);
|
|
5918
7031
|
if (worktree) {
|
|
5919
|
-
const overlayContent = await
|
|
7032
|
+
const overlayContent = await readFile10(overlayPath, "utf-8");
|
|
5920
7033
|
installWorktreeRootAgents(sanitized, wName, leaderCwd, worktree.path, overlayContent);
|
|
5921
7034
|
}
|
|
5922
7035
|
}
|
|
@@ -6026,7 +7139,7 @@ async function startTeamV2(config) {
|
|
|
6026
7139
|
next_worker_index: teamConfig.next_worker_index
|
|
6027
7140
|
};
|
|
6028
7141
|
try {
|
|
6029
|
-
await
|
|
7142
|
+
await writeFile7(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), "utf-8");
|
|
6030
7143
|
} catch (error) {
|
|
6031
7144
|
await rollbackStartedNativeWorktreeStartup({
|
|
6032
7145
|
teamName: sanitized,
|
|
@@ -6075,6 +7188,7 @@ async function startTeamV2(config) {
|
|
|
6075
7188
|
cwd: leaderCwd,
|
|
6076
7189
|
workerCwd: workersInfo[workerIndex]?.working_dir ?? leaderCwd,
|
|
6077
7190
|
worktreePath: workersInfo[workerIndex]?.worktree_path,
|
|
7191
|
+
autoMerge: Boolean(config.autoMerge),
|
|
6078
7192
|
resolvedBinaryPaths,
|
|
6079
7193
|
...assignment.model ? { model: assignment.model } : {},
|
|
6080
7194
|
...assignment.role ? { role: assignment.role } : {}
|
|
@@ -6137,6 +7251,51 @@ async function startTeamV2(config) {
|
|
|
6137
7251
|
worker: "leader-fixed",
|
|
6138
7252
|
reason: `start_team_v2: workers=${config.workerCount} tasks=${config.tasks.length} panes=${workerPaneIds.length}`
|
|
6139
7253
|
}, leaderCwd).catch(logEventFailure);
|
|
7254
|
+
if (config.autoMerge && autoMergeLeaderBranch) {
|
|
7255
|
+
try {
|
|
7256
|
+
await ensureLeaderInbox(sanitized, leaderCwd);
|
|
7257
|
+
await appendToLeaderInbox(
|
|
7258
|
+
sanitized,
|
|
7259
|
+
extendLeaderBootstrapPrompt(sanitized),
|
|
7260
|
+
leaderCwd
|
|
7261
|
+
);
|
|
7262
|
+
try {
|
|
7263
|
+
await recoverFromRestart({
|
|
7264
|
+
teamName: sanitized,
|
|
7265
|
+
repoRoot: leaderCwd,
|
|
7266
|
+
leaderBranch: autoMergeLeaderBranch,
|
|
7267
|
+
cwd: leaderCwd
|
|
7268
|
+
});
|
|
7269
|
+
} catch (recErr) {
|
|
7270
|
+
process.stderr.write(`[team/runtime-v2] auto-merge recover-from-restart failed: ${recErr}
|
|
7271
|
+
`);
|
|
7272
|
+
}
|
|
7273
|
+
const orchestrator = await startMergeOrchestrator({
|
|
7274
|
+
teamName: sanitized,
|
|
7275
|
+
repoRoot: leaderCwd,
|
|
7276
|
+
leaderBranch: autoMergeLeaderBranch,
|
|
7277
|
+
cwd: leaderCwd
|
|
7278
|
+
});
|
|
7279
|
+
registerTeamOrchestrator(sanitized, orchestrator);
|
|
7280
|
+
for (const w of workersInfo) {
|
|
7281
|
+
await orchestrator.registerWorker(w.name);
|
|
7282
|
+
}
|
|
7283
|
+
} catch (orchErr) {
|
|
7284
|
+
await stopTeamCadence(sanitized);
|
|
7285
|
+
unregisterTeamOrchestrator(sanitized);
|
|
7286
|
+
await rollbackStartedNativeWorktreeStartup({
|
|
7287
|
+
teamName: sanitized,
|
|
7288
|
+
cwd: leaderCwd,
|
|
7289
|
+
cause: orchErr,
|
|
7290
|
+
sessionName: sessionName2,
|
|
7291
|
+
leaderPaneId,
|
|
7292
|
+
workerPaneIds,
|
|
7293
|
+
sessionMode: session.sessionMode
|
|
7294
|
+
});
|
|
7295
|
+
const reason = orchErr instanceof Error ? orchErr.message : String(orchErr);
|
|
7296
|
+
throw new Error(`auto-merge startup failed: ${reason}`);
|
|
7297
|
+
}
|
|
7298
|
+
}
|
|
6140
7299
|
return {
|
|
6141
7300
|
teamName: sanitized,
|
|
6142
7301
|
sanitizedName: sanitized,
|
|
@@ -6147,16 +7306,16 @@ async function startTeamV2(config) {
|
|
|
6147
7306
|
};
|
|
6148
7307
|
}
|
|
6149
7308
|
async function writeWatchdogFailedMarker(teamName, cwd, reason) {
|
|
6150
|
-
const { writeFile:
|
|
7309
|
+
const { writeFile: writeFile8 } = await import("fs/promises");
|
|
6151
7310
|
const marker = {
|
|
6152
7311
|
failedAt: Date.now(),
|
|
6153
7312
|
reason,
|
|
6154
7313
|
writtenBy: "runtime-v2"
|
|
6155
7314
|
};
|
|
6156
7315
|
const root = absPath(cwd, TeamPaths.root(sanitizeTeamName(teamName)));
|
|
6157
|
-
const markerPath =
|
|
6158
|
-
await
|
|
6159
|
-
await
|
|
7316
|
+
const markerPath = join22(root, "watchdog-failed.json");
|
|
7317
|
+
await mkdir10(root, { recursive: true });
|
|
7318
|
+
await writeFile8(markerPath, JSON.stringify(marker, null, 2), "utf-8");
|
|
6160
7319
|
}
|
|
6161
7320
|
async function requeueDeadWorkerTasks(teamName, deadWorkerNames, cwd) {
|
|
6162
7321
|
const logEventFailure = createSwallowedErrorLogger(
|
|
@@ -6176,15 +7335,15 @@ async function requeueDeadWorkerTasks(teamName, deadWorkerNames, cwd) {
|
|
|
6176
7335
|
retryCount: 0,
|
|
6177
7336
|
lastFailedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
6178
7337
|
};
|
|
6179
|
-
const { writeFile:
|
|
6180
|
-
await
|
|
6181
|
-
await
|
|
7338
|
+
const { writeFile: writeFile8 } = await import("fs/promises");
|
|
7339
|
+
await mkdir10(absPath(cwd, TeamPaths.tasks(sanitized)), { recursive: true });
|
|
7340
|
+
await writeFile8(sidecarPath, JSON.stringify(sidecar, null, 2), "utf-8");
|
|
6182
7341
|
const taskPath2 = absPath(cwd, TeamPaths.taskFile(sanitized, task.id));
|
|
6183
7342
|
try {
|
|
6184
|
-
const { readFileSync:
|
|
7343
|
+
const { readFileSync: readFileSync13, writeFileSync: writeFileSync4 } = await import("fs");
|
|
6185
7344
|
const { withFileLockSync: withFileLockSync2 } = await Promise.resolve().then(() => (init_file_lock(), file_lock_exports));
|
|
6186
7345
|
withFileLockSync2(taskPath2 + ".lock", () => {
|
|
6187
|
-
const raw =
|
|
7346
|
+
const raw = readFileSync13(taskPath2, "utf-8");
|
|
6188
7347
|
const taskData = JSON.parse(raw);
|
|
6189
7348
|
if (taskData.status === "in_progress") {
|
|
6190
7349
|
taskData.status = "pending";
|
|
@@ -6214,7 +7373,7 @@ async function processCliWorkerVerdicts(teamName, cwd) {
|
|
|
6214
7373
|
"team.runtime-v2.processCliWorkerVerdicts appendTeamEvent failed"
|
|
6215
7374
|
);
|
|
6216
7375
|
const { rename: rename3 } = await import("fs/promises");
|
|
6217
|
-
const { readFileSync:
|
|
7376
|
+
const { readFileSync: readFileSync13, writeFileSync: writeFileSync4, existsSync: fsExistsSync } = await import("fs");
|
|
6218
7377
|
const { withFileLockSync: withFileLockSync2 } = await Promise.resolve().then(() => (init_file_lock(), file_lock_exports));
|
|
6219
7378
|
for (const worker of config.workers) {
|
|
6220
7379
|
const outputFile = worker.output_file;
|
|
@@ -6227,7 +7386,7 @@ async function processCliWorkerVerdicts(teamName, cwd) {
|
|
|
6227
7386
|
}
|
|
6228
7387
|
let payload;
|
|
6229
7388
|
try {
|
|
6230
|
-
const raw = await
|
|
7389
|
+
const raw = await readFile10(outputFile, "utf-8");
|
|
6231
7390
|
payload = parseCliWorkerVerdict(raw);
|
|
6232
7391
|
} catch (err) {
|
|
6233
7392
|
const reason = err instanceof Error ? err.message : String(err);
|
|
@@ -6248,7 +7407,7 @@ async function processCliWorkerVerdicts(teamName, cwd) {
|
|
|
6248
7407
|
const taskPath2 = absPath(cwd, TeamPaths.taskFile(sanitized, taskId));
|
|
6249
7408
|
if (!fsExistsSync(taskPath2)) continue;
|
|
6250
7409
|
try {
|
|
6251
|
-
const taskRaw =
|
|
7410
|
+
const taskRaw = readFileSync13(taskPath2, "utf-8");
|
|
6252
7411
|
const taskData = JSON.parse(taskRaw);
|
|
6253
7412
|
if (taskData.owner === worker.name && taskData.status === "in_progress") {
|
|
6254
7413
|
targetTaskId = taskId;
|
|
@@ -6276,7 +7435,7 @@ async function processCliWorkerVerdicts(teamName, cwd) {
|
|
|
6276
7435
|
let transitionOk = false;
|
|
6277
7436
|
try {
|
|
6278
7437
|
withFileLockSync2(targetTaskPath + ".lock", () => {
|
|
6279
|
-
const raw =
|
|
7438
|
+
const raw = readFileSync13(targetTaskPath, "utf-8");
|
|
6280
7439
|
const taskData = JSON.parse(raw);
|
|
6281
7440
|
if (taskData.status !== "in_progress" || taskData.owner !== worker.name) {
|
|
6282
7441
|
return;
|
|
@@ -6510,6 +7669,39 @@ async function shutdownTeamV2(teamName, cwd, options = {}) {
|
|
|
6510
7669
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
6511
7670
|
const sanitized = sanitizeTeamName(teamName);
|
|
6512
7671
|
const config = await readTeamConfig(sanitized, cwd);
|
|
7672
|
+
const finalizeAutoMerge = async () => {
|
|
7673
|
+
const orchestrator = getTeamOrchestrator(sanitized);
|
|
7674
|
+
if (orchestrator) {
|
|
7675
|
+
try {
|
|
7676
|
+
const drainResult = await orchestrator.drainAndStop();
|
|
7677
|
+
if (drainResult.unmerged.length > 0) {
|
|
7678
|
+
await appendTeamEvent(sanitized, {
|
|
7679
|
+
type: "team_leader_nudge",
|
|
7680
|
+
worker: "leader-fixed",
|
|
7681
|
+
reason: `auto_merge_drain_unmerged:${drainResult.unmerged.map((u) => `${u.workerName}:${u.reason}`).join(",")}`
|
|
7682
|
+
}, cwd).catch(logEventFailure);
|
|
7683
|
+
}
|
|
7684
|
+
for (const w of config?.workers ?? []) {
|
|
7685
|
+
try {
|
|
7686
|
+
await orchestrator.unregisterWorker(w.name);
|
|
7687
|
+
} catch (err) {
|
|
7688
|
+
process.stderr.write(
|
|
7689
|
+
`[team/runtime-v2] orchestrator.unregisterWorker(${w.name}) failed: ${err}
|
|
7690
|
+
`
|
|
7691
|
+
);
|
|
7692
|
+
}
|
|
7693
|
+
}
|
|
7694
|
+
} catch (err) {
|
|
7695
|
+
process.stderr.write(`[team/runtime-v2] orchestrator drainAndStop: ${err}
|
|
7696
|
+
`);
|
|
7697
|
+
} finally {
|
|
7698
|
+
await stopTeamCadence(sanitized);
|
|
7699
|
+
unregisterTeamOrchestrator(sanitized);
|
|
7700
|
+
}
|
|
7701
|
+
} else {
|
|
7702
|
+
await stopTeamCadence(sanitized);
|
|
7703
|
+
}
|
|
7704
|
+
};
|
|
6513
7705
|
if (!config) {
|
|
6514
7706
|
const cleanupSafety = inspectTeamWorktreeCleanupSafety(sanitized, cwd);
|
|
6515
7707
|
if (cleanupSafety.hasEvidence) {
|
|
@@ -6644,12 +7836,14 @@ Then exit your session.
|
|
|
6644
7836
|
if (aliveWorkers.length > 0) {
|
|
6645
7837
|
process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane(s) are still alive: ${aliveWorkers.join(", ")}
|
|
6646
7838
|
`);
|
|
7839
|
+
await finalizeAutoMerge();
|
|
6647
7840
|
return;
|
|
6648
7841
|
}
|
|
6649
7842
|
const unknownWorkers = liveness.filter(([, state]) => state === "unknown").map(([paneId]) => paneById.get(paneId) ?? paneId);
|
|
6650
7843
|
if (unknownWorkers.length > 0) {
|
|
6651
7844
|
process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane liveness is unknown: ${unknownWorkers.join(", ")}
|
|
6652
7845
|
`);
|
|
7846
|
+
await finalizeAutoMerge();
|
|
6653
7847
|
return;
|
|
6654
7848
|
}
|
|
6655
7849
|
} catch (err) {
|
|
@@ -6657,6 +7851,7 @@ Then exit your session.
|
|
|
6657
7851
|
`);
|
|
6658
7852
|
if (recordedWorkerPaneIds.length > 0) {
|
|
6659
7853
|
process.stderr.write("[team/runtime-v2] preserving worktrees/state because tmux cleanup did not prove worker panes exited\n");
|
|
7854
|
+
await finalizeAutoMerge();
|
|
6660
7855
|
return;
|
|
6661
7856
|
}
|
|
6662
7857
|
}
|
|
@@ -6671,6 +7866,7 @@ Then exit your session.
|
|
|
6671
7866
|
reason: `ralph_cleanup_summary: total=${finalTasks.length} completed=${completed} failed=${failed} pending=${pending} force=${force}`
|
|
6672
7867
|
}, cwd).catch(logEventFailure);
|
|
6673
7868
|
}
|
|
7869
|
+
await finalizeAutoMerge();
|
|
6674
7870
|
let preservedWorktrees = 0;
|
|
6675
7871
|
try {
|
|
6676
7872
|
const worktreeCleanup = cleanupTeamWorktrees(sanitized, cwd);
|
|
@@ -6707,8 +7903,8 @@ async function resumeTeamV2(teamName, cwd) {
|
|
|
6707
7903
|
}
|
|
6708
7904
|
}
|
|
6709
7905
|
async function findActiveTeamsV2(cwd) {
|
|
6710
|
-
const root =
|
|
6711
|
-
if (!
|
|
7906
|
+
const root = join22(cwd, ".omcp", "state", "team");
|
|
7907
|
+
if (!existsSync19(root)) return [];
|
|
6712
7908
|
const entries = await readdir2(root, { withFileTypes: true });
|
|
6713
7909
|
const active = [];
|
|
6714
7910
|
for (const e of entries) {
|
|
@@ -6721,7 +7917,7 @@ async function findActiveTeamsV2(cwd) {
|
|
|
6721
7917
|
}
|
|
6722
7918
|
return active;
|
|
6723
7919
|
}
|
|
6724
|
-
var MONITOR_SIGNAL_STALE_MS, CIRCUIT_BREAKER_THRESHOLD, CircuitBreakerV2;
|
|
7920
|
+
var orchestratorByTeam, cadenceByTeam, MONITOR_SIGNAL_STALE_MS, CIRCUIT_BREAKER_THRESHOLD, CircuitBreakerV2;
|
|
6725
7921
|
var init_runtime_v2 = __esm({
|
|
6726
7922
|
"src/team/runtime-v2.ts"() {
|
|
6727
7923
|
"use strict";
|
|
@@ -6746,6 +7942,13 @@ var init_runtime_v2 = __esm({
|
|
|
6746
7942
|
init_role_router();
|
|
6747
7943
|
init_types2();
|
|
6748
7944
|
init_cli_worker_contract();
|
|
7945
|
+
init_merge_orchestrator();
|
|
7946
|
+
init_leader_inbox();
|
|
7947
|
+
init_runtime_flags();
|
|
7948
|
+
init_worker_commit_cadence();
|
|
7949
|
+
init_runtime_flags();
|
|
7950
|
+
orchestratorByTeam = /* @__PURE__ */ new Map();
|
|
7951
|
+
cadenceByTeam = /* @__PURE__ */ new Map();
|
|
6749
7952
|
MONITOR_SIGNAL_STALE_MS = 3e4;
|
|
6750
7953
|
CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
6751
7954
|
CircuitBreakerV2 = class {
|
|
@@ -6781,16 +7984,16 @@ var init_runtime_v2 = __esm({
|
|
|
6781
7984
|
// src/cli/team.ts
|
|
6782
7985
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
6783
7986
|
import { spawn } from "child_process";
|
|
6784
|
-
import { existsSync as
|
|
6785
|
-
import { readFile as
|
|
7987
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync3 } from "fs";
|
|
7988
|
+
import { readFile as readFile11, readdir as readdir3, rm as rm5 } from "fs/promises";
|
|
6786
7989
|
import { homedir as homedir3 } from "os";
|
|
6787
|
-
import { dirname as
|
|
7990
|
+
import { dirname as dirname16, join as join25 } from "path";
|
|
6788
7991
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6789
7992
|
|
|
6790
7993
|
// src/team/api-interop.ts
|
|
6791
7994
|
init_contracts();
|
|
6792
|
-
import { existsSync as
|
|
6793
|
-
import { dirname as
|
|
7995
|
+
import { existsSync as existsSync20, readFileSync as readFileSync10 } from "node:fs";
|
|
7996
|
+
import { dirname as dirname15, join as join23, resolve as resolvePath } from "node:path";
|
|
6794
7997
|
|
|
6795
7998
|
// src/team/team-ops.ts
|
|
6796
7999
|
init_state_paths();
|
|
@@ -6855,7 +8058,31 @@ async function claimTask(taskId, workerName, expectedVersion, deps) {
|
|
|
6855
8058
|
if (!lock.ok) return { ok: false, error: "claim_conflict" };
|
|
6856
8059
|
return lock.value;
|
|
6857
8060
|
}
|
|
6858
|
-
|
|
8061
|
+
function extractDelegationComplianceEvidence(task, terminalData) {
|
|
8062
|
+
const plan = task.delegation;
|
|
8063
|
+
if (!plan || plan.mode === "none") return null;
|
|
8064
|
+
if (plan.mode === "optional" && plan.required_parallel_probe !== true) return null;
|
|
8065
|
+
const result = typeof terminalData?.result === "string" ? terminalData.result : "";
|
|
8066
|
+
const spawnMatch = result.match(/^\s*Subagent spawn evidence:\s*(.+)$/im);
|
|
8067
|
+
if (spawnMatch?.[1]?.trim()) {
|
|
8068
|
+
const detail = spawnMatch[1].trim();
|
|
8069
|
+
if (!/^none\b|^0\b/i.test(detail)) {
|
|
8070
|
+
return { status: "spawned", source: "terminal_result", detail, recorded_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
8071
|
+
}
|
|
8072
|
+
}
|
|
8073
|
+
if (plan.skip_allowed_reason_required === true) {
|
|
8074
|
+
const skipMatch = result.match(/^\s*Subagent skip reason:\s*(.+)$/im);
|
|
8075
|
+
if (skipMatch?.[1]?.trim()) {
|
|
8076
|
+
return { status: "skipped", source: "terminal_result", detail: skipMatch[1].trim(), recorded_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
8077
|
+
}
|
|
8078
|
+
}
|
|
8079
|
+
return null;
|
|
8080
|
+
}
|
|
8081
|
+
function requiresDelegationComplianceEvidence(task) {
|
|
8082
|
+
const plan = task.delegation;
|
|
8083
|
+
return !!plan && (plan.mode === "auto" || plan.mode === "required" || plan.required_parallel_probe === true);
|
|
8084
|
+
}
|
|
8085
|
+
async function transitionTaskStatus(taskId, from, to, claimToken, terminalData, deps) {
|
|
6859
8086
|
if (!deps.canTransitionTaskStatus(from, to)) return { ok: false, error: "invalid_transition" };
|
|
6860
8087
|
const lock = await deps.withTaskClaimLock(deps.teamName, taskId, deps.cwd, async () => {
|
|
6861
8088
|
const current = await deps.readTask(deps.teamName, taskId, deps.cwd);
|
|
@@ -6868,10 +8095,19 @@ async function transitionTaskStatus(taskId, from, to, claimToken, deps) {
|
|
|
6868
8095
|
return { ok: false, error: "claim_conflict" };
|
|
6869
8096
|
}
|
|
6870
8097
|
if (new Date(v.claim.leased_until) <= /* @__PURE__ */ new Date()) return { ok: false, error: "lease_expired" };
|
|
8098
|
+
const normalizedResult = typeof terminalData?.result === "string" ? terminalData.result : void 0;
|
|
8099
|
+
const normalizedError = typeof terminalData?.error === "string" ? terminalData.error : void 0;
|
|
8100
|
+
const delegationCompliance = to === "completed" ? extractDelegationComplianceEvidence(v, terminalData) : null;
|
|
8101
|
+
if (to === "completed" && requiresDelegationComplianceEvidence(v) && !delegationCompliance) {
|
|
8102
|
+
return { ok: false, error: "missing_delegation_compliance_evidence" };
|
|
8103
|
+
}
|
|
6871
8104
|
const updated = {
|
|
6872
8105
|
...v,
|
|
6873
8106
|
status: to,
|
|
6874
8107
|
completed_at: to === "completed" ? (/* @__PURE__ */ new Date()).toISOString() : v.completed_at,
|
|
8108
|
+
result: to === "completed" ? normalizedResult : void 0,
|
|
8109
|
+
error: to === "failed" ? normalizedError : void 0,
|
|
8110
|
+
delegation_compliance: to === "completed" ? delegationCompliance ?? v.delegation_compliance : v.delegation_compliance,
|
|
6875
8111
|
claim: void 0,
|
|
6876
8112
|
version: v.version + 1
|
|
6877
8113
|
};
|
|
@@ -7231,8 +8467,8 @@ async function teamClaimTask(teamName, taskId, workerName, expectedVersion, cwd)
|
|
|
7231
8467
|
writeAtomic
|
|
7232
8468
|
});
|
|
7233
8469
|
}
|
|
7234
|
-
async function teamTransitionTaskStatus(teamName, taskId, from, to, claimToken, cwd) {
|
|
7235
|
-
return transitionTaskStatus(taskId, from, to, claimToken, {
|
|
8470
|
+
async function teamTransitionTaskStatus(teamName, taskId, from, to, claimToken, cwd, terminalData) {
|
|
8471
|
+
return transitionTaskStatus(taskId, from, to, claimToken, terminalData, {
|
|
7236
8472
|
teamName,
|
|
7237
8473
|
cwd,
|
|
7238
8474
|
readTask: teamReadTask,
|
|
@@ -7725,7 +8961,7 @@ async function resumeTeam(teamName, cwd) {
|
|
|
7725
8961
|
init_runtime_v2();
|
|
7726
8962
|
init_git_worktree();
|
|
7727
8963
|
init_swallowed_error();
|
|
7728
|
-
var TEAM_UPDATE_TASK_MUTABLE_FIELDS = /* @__PURE__ */ new Set(["subject", "description", "blocked_by", "requires_code_change"]);
|
|
8964
|
+
var TEAM_UPDATE_TASK_MUTABLE_FIELDS = /* @__PURE__ */ new Set(["subject", "description", "blocked_by", "requires_code_change", "delegation"]);
|
|
7729
8965
|
var TEAM_UPDATE_TASK_REQUEST_FIELDS = /* @__PURE__ */ new Set(["team_name", "task_id", "workingDirectory", ...TEAM_UPDATE_TASK_MUTABLE_FIELDS]);
|
|
7730
8966
|
var TEAM_API_OPERATIONS = [
|
|
7731
8967
|
"send-message",
|
|
@@ -7778,10 +9014,64 @@ function parseValidatedTaskIdArray(value, fieldName) {
|
|
|
7778
9014
|
}
|
|
7779
9015
|
return taskIds;
|
|
7780
9016
|
}
|
|
9017
|
+
function parseTaskDelegationPlan(value) {
|
|
9018
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
9019
|
+
throw new Error("delegation must be an object");
|
|
9020
|
+
}
|
|
9021
|
+
const raw = value;
|
|
9022
|
+
const mode = raw.mode;
|
|
9023
|
+
if (mode !== "none" && mode !== "optional" && mode !== "auto" && mode !== "required") {
|
|
9024
|
+
throw new Error("delegation.mode must be one of: none, optional, auto, required");
|
|
9025
|
+
}
|
|
9026
|
+
const plan = { mode };
|
|
9027
|
+
if ("max_parallel_subtasks" in raw) {
|
|
9028
|
+
if (!isFiniteInteger(raw.max_parallel_subtasks) || raw.max_parallel_subtasks < 1) {
|
|
9029
|
+
throw new Error("delegation.max_parallel_subtasks must be a positive integer when provided");
|
|
9030
|
+
}
|
|
9031
|
+
plan.max_parallel_subtasks = raw.max_parallel_subtasks;
|
|
9032
|
+
}
|
|
9033
|
+
if ("required_parallel_probe" in raw) {
|
|
9034
|
+
if (typeof raw.required_parallel_probe !== "boolean") throw new Error("delegation.required_parallel_probe must be a boolean when provided");
|
|
9035
|
+
plan.required_parallel_probe = raw.required_parallel_probe;
|
|
9036
|
+
}
|
|
9037
|
+
if ("spawn_before_serial_search_threshold" in raw) {
|
|
9038
|
+
if (!isFiniteInteger(raw.spawn_before_serial_search_threshold) || raw.spawn_before_serial_search_threshold < 1) {
|
|
9039
|
+
throw new Error("delegation.spawn_before_serial_search_threshold must be a positive integer when provided");
|
|
9040
|
+
}
|
|
9041
|
+
plan.spawn_before_serial_search_threshold = raw.spawn_before_serial_search_threshold;
|
|
9042
|
+
}
|
|
9043
|
+
if ("child_model_policy" in raw) {
|
|
9044
|
+
const policy = raw.child_model_policy;
|
|
9045
|
+
if (policy !== "standard" && policy !== "fast" && policy !== "inherit" && policy !== "frontier") {
|
|
9046
|
+
throw new Error("delegation.child_model_policy must be one of: standard, fast, inherit, frontier");
|
|
9047
|
+
}
|
|
9048
|
+
plan.child_model_policy = policy;
|
|
9049
|
+
}
|
|
9050
|
+
if ("child_model" in raw) {
|
|
9051
|
+
if (typeof raw.child_model !== "string") throw new Error("delegation.child_model must be a string when provided");
|
|
9052
|
+
plan.child_model = raw.child_model;
|
|
9053
|
+
}
|
|
9054
|
+
if ("subtask_candidates" in raw) {
|
|
9055
|
+
if (!Array.isArray(raw.subtask_candidates) || !raw.subtask_candidates.every((item) => typeof item === "string")) {
|
|
9056
|
+
throw new Error("delegation.subtask_candidates must be an array of strings when provided");
|
|
9057
|
+
}
|
|
9058
|
+
plan.subtask_candidates = raw.subtask_candidates;
|
|
9059
|
+
}
|
|
9060
|
+
if ("child_report_format" in raw) {
|
|
9061
|
+
const format = raw.child_report_format;
|
|
9062
|
+
if (format !== "bullets" && format !== "json") throw new Error("delegation.child_report_format must be bullets or json when provided");
|
|
9063
|
+
plan.child_report_format = format;
|
|
9064
|
+
}
|
|
9065
|
+
if ("skip_allowed_reason_required" in raw) {
|
|
9066
|
+
if (typeof raw.skip_allowed_reason_required !== "boolean") throw new Error("delegation.skip_allowed_reason_required must be a boolean when provided");
|
|
9067
|
+
plan.skip_allowed_reason_required = raw.skip_allowed_reason_required;
|
|
9068
|
+
}
|
|
9069
|
+
return plan;
|
|
9070
|
+
}
|
|
7781
9071
|
function teamStateExists(teamName, candidateCwd) {
|
|
7782
9072
|
if (!TEAM_NAME_SAFE_PATTERN.test(teamName)) return false;
|
|
7783
|
-
const teamRoot =
|
|
7784
|
-
return
|
|
9073
|
+
const teamRoot = join23(candidateCwd, ".omcp", "state", "team", teamName);
|
|
9074
|
+
return existsSync20(join23(teamRoot, "config.json")) || existsSync20(join23(teamRoot, "tasks")) || existsSync20(teamRoot);
|
|
7785
9075
|
}
|
|
7786
9076
|
function parseTeamWorkerEnv(raw) {
|
|
7787
9077
|
if (typeof raw !== "string" || raw.trim() === "") return null;
|
|
@@ -7835,7 +9125,7 @@ async function executeTeamCleanupViaRuntime(teamName, cwd) {
|
|
|
7835
9125
|
await teamCleanup(teamName, cwd);
|
|
7836
9126
|
}
|
|
7837
9127
|
function readTeamStateRootFromFile(path4) {
|
|
7838
|
-
if (!
|
|
9128
|
+
if (!existsSync20(path4)) return null;
|
|
7839
9129
|
try {
|
|
7840
9130
|
const parsed = JSON.parse(readFileSync10(path4, "utf8"));
|
|
7841
9131
|
return typeof parsed.team_state_root === "string" && parsed.team_state_root.trim() !== "" ? parsed.team_state_root.trim() : null;
|
|
@@ -7851,7 +9141,7 @@ function stateRootToWorkingDirectory(stateRoot2) {
|
|
|
7851
9141
|
if (idx >= 0) {
|
|
7852
9142
|
const workspaceRoot = absolute.slice(0, idx);
|
|
7853
9143
|
if (workspaceRoot && workspaceRoot !== "/") return workspaceRoot;
|
|
7854
|
-
return
|
|
9144
|
+
return dirname15(dirname15(dirname15(dirname15(absolute))));
|
|
7855
9145
|
}
|
|
7856
9146
|
}
|
|
7857
9147
|
for (const marker of ["/.omcp/state", "/.omx/state"]) {
|
|
@@ -7859,22 +9149,22 @@ function stateRootToWorkingDirectory(stateRoot2) {
|
|
|
7859
9149
|
if (idx >= 0) {
|
|
7860
9150
|
const workspaceRoot = absolute.slice(0, idx);
|
|
7861
9151
|
if (workspaceRoot && workspaceRoot !== "/") return workspaceRoot;
|
|
7862
|
-
return
|
|
9152
|
+
return dirname15(dirname15(absolute));
|
|
7863
9153
|
}
|
|
7864
9154
|
}
|
|
7865
|
-
return
|
|
9155
|
+
return dirname15(dirname15(absolute));
|
|
7866
9156
|
}
|
|
7867
9157
|
function resolveTeamWorkingDirectoryFromMetadata(teamName, candidateCwd, workerContext) {
|
|
7868
|
-
const teamRoot =
|
|
7869
|
-
if (!
|
|
9158
|
+
const teamRoot = join23(candidateCwd, ".omcp", "state", "team", teamName);
|
|
9159
|
+
if (!existsSync20(teamRoot)) return null;
|
|
7870
9160
|
if (workerContext?.teamName === teamName) {
|
|
7871
|
-
const workerRoot = readTeamStateRootFromFile(
|
|
9161
|
+
const workerRoot = readTeamStateRootFromFile(join23(teamRoot, "workers", workerContext.workerName, "identity.json"));
|
|
7872
9162
|
if (workerRoot) return stateRootToWorkingDirectory(workerRoot);
|
|
7873
9163
|
}
|
|
7874
|
-
const fromConfig = readTeamStateRootFromFile(
|
|
9164
|
+
const fromConfig = readTeamStateRootFromFile(join23(teamRoot, "config.json"));
|
|
7875
9165
|
if (fromConfig) return stateRootToWorkingDirectory(fromConfig);
|
|
7876
9166
|
for (const manifestName of ["manifest.json", "manifest.v2.json"]) {
|
|
7877
|
-
const fromManifest = readTeamStateRootFromFile(
|
|
9167
|
+
const fromManifest = readTeamStateRootFromFile(join23(teamRoot, manifestName));
|
|
7878
9168
|
if (fromManifest) return stateRootToWorkingDirectory(fromManifest);
|
|
7879
9169
|
}
|
|
7880
9170
|
return null;
|
|
@@ -7901,7 +9191,7 @@ function resolveTeamWorkingDirectory(teamName, preferredCwd) {
|
|
|
7901
9191
|
if (teamStateExists(normalizedTeamName, cursor)) {
|
|
7902
9192
|
return resolveTeamWorkingDirectoryFromMetadata(normalizedTeamName, cursor, workerContext) ?? cursor;
|
|
7903
9193
|
}
|
|
7904
|
-
const parent =
|
|
9194
|
+
const parent = dirname15(cursor);
|
|
7905
9195
|
if (!parent || parent === cursor) break;
|
|
7906
9196
|
cursor = parent;
|
|
7907
9197
|
}
|
|
@@ -8149,13 +9439,22 @@ async function executeTeamApiOperation(operation, args, fallbackCwd) {
|
|
|
8149
9439
|
const owner = args.owner;
|
|
8150
9440
|
const blockedBy = args.blocked_by;
|
|
8151
9441
|
const requiresCodeChange = args.requires_code_change;
|
|
9442
|
+
let delegation;
|
|
9443
|
+
if ("delegation" in args) {
|
|
9444
|
+
try {
|
|
9445
|
+
delegation = parseTaskDelegationPlan(args.delegation);
|
|
9446
|
+
} catch (error) {
|
|
9447
|
+
return { ok: false, operation, error: { code: "invalid_input", message: error.message } };
|
|
9448
|
+
}
|
|
9449
|
+
}
|
|
8152
9450
|
const task = await teamCreateTask(teamName, {
|
|
8153
9451
|
subject,
|
|
8154
9452
|
description,
|
|
8155
9453
|
status: "pending",
|
|
8156
9454
|
owner: owner || void 0,
|
|
8157
9455
|
blocked_by: blockedBy,
|
|
8158
|
-
requires_code_change: requiresCodeChange
|
|
9456
|
+
requires_code_change: requiresCodeChange,
|
|
9457
|
+
...delegation ? { delegation } : {}
|
|
8159
9458
|
}, cwd);
|
|
8160
9459
|
return { ok: true, operation, data: { task } };
|
|
8161
9460
|
}
|
|
@@ -8217,6 +9516,13 @@ async function executeTeamApiOperation(operation, args, fallbackCwd) {
|
|
|
8217
9516
|
return { ok: false, operation, error: { code: "invalid_input", message: error.message } };
|
|
8218
9517
|
}
|
|
8219
9518
|
}
|
|
9519
|
+
if ("delegation" in args) {
|
|
9520
|
+
try {
|
|
9521
|
+
updates.delegation = parseTaskDelegationPlan(args.delegation);
|
|
9522
|
+
} catch (error) {
|
|
9523
|
+
return { ok: false, operation, error: { code: "invalid_input", message: error.message } };
|
|
9524
|
+
}
|
|
9525
|
+
}
|
|
8220
9526
|
const task = await teamUpdateTask(teamName, taskId, updates, cwd);
|
|
8221
9527
|
return task ? { ok: true, operation, data: { task } } : { ok: false, operation, error: { code: "task_not_found", message: "task_not_found" } };
|
|
8222
9528
|
}
|
|
@@ -8240,6 +9546,8 @@ async function executeTeamApiOperation(operation, args, fallbackCwd) {
|
|
|
8240
9546
|
const from = String(args.from || "").trim();
|
|
8241
9547
|
const to = String(args.to || "").trim();
|
|
8242
9548
|
const claimToken = String(args.claim_token || "").trim();
|
|
9549
|
+
const transitionResult = args.result;
|
|
9550
|
+
const transitionError = args.error;
|
|
8243
9551
|
if (!teamName || !taskId || !from || !to || !claimToken) {
|
|
8244
9552
|
return { ok: false, operation, error: { code: "invalid_input", message: "team_name, task_id, from, to, claim_token are required" } };
|
|
8245
9553
|
}
|
|
@@ -8247,7 +9555,24 @@ async function executeTeamApiOperation(operation, args, fallbackCwd) {
|
|
|
8247
9555
|
if (!allowed.has(from) || !allowed.has(to)) {
|
|
8248
9556
|
return { ok: false, operation, error: { code: "invalid_input", message: "from and to must be valid task statuses" } };
|
|
8249
9557
|
}
|
|
8250
|
-
|
|
9558
|
+
if (transitionResult !== void 0 && typeof transitionResult !== "string") {
|
|
9559
|
+
return { ok: false, operation, error: { code: "invalid_input", message: "result must be a string when provided" } };
|
|
9560
|
+
}
|
|
9561
|
+
if (transitionError !== void 0 && typeof transitionError !== "string") {
|
|
9562
|
+
return { ok: false, operation, error: { code: "invalid_input", message: "error must be a string when provided" } };
|
|
9563
|
+
}
|
|
9564
|
+
const result = await teamTransitionTaskStatus(
|
|
9565
|
+
teamName,
|
|
9566
|
+
taskId,
|
|
9567
|
+
from,
|
|
9568
|
+
to,
|
|
9569
|
+
claimToken,
|
|
9570
|
+
cwd,
|
|
9571
|
+
{
|
|
9572
|
+
result: typeof transitionResult === "string" ? transitionResult : void 0,
|
|
9573
|
+
error: typeof transitionError === "string" ? transitionError : void 0
|
|
9574
|
+
}
|
|
9575
|
+
);
|
|
8251
9576
|
return { ok: true, operation, data: result };
|
|
8252
9577
|
}
|
|
8253
9578
|
case "release-task-claim": {
|
|
@@ -8470,6 +9795,278 @@ init_tmux_session();
|
|
|
8470
9795
|
init_team_name();
|
|
8471
9796
|
init_monitor();
|
|
8472
9797
|
init_process_utils();
|
|
9798
|
+
|
|
9799
|
+
// src/planning/artifacts.ts
|
|
9800
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync11, existsSync as existsSync21 } from "fs";
|
|
9801
|
+
import { join as join24 } from "path";
|
|
9802
|
+
|
|
9803
|
+
// src/planning/artifact-names.ts
|
|
9804
|
+
import { basename as basename7 } from "path";
|
|
9805
|
+
var PLANNING_ARTIFACT_TIMESTAMP_PATTERN = /^\d{8}T\d{6}Z$/;
|
|
9806
|
+
function legacyTestSpecSlug(fileNameOrPath) {
|
|
9807
|
+
const match = basename7(fileNameOrPath).match(/^test-?spec-(?<slug>.+)\.md$/i);
|
|
9808
|
+
return match?.groups?.slug ?? null;
|
|
9809
|
+
}
|
|
9810
|
+
function requiredTimestampedTestSpecFileName(prdArtifact) {
|
|
9811
|
+
return prdArtifact.kind === "prd" && prdArtifact.timestamp ? `test-spec-${prdArtifact.timestamp}-${prdArtifact.slug}.md` : null;
|
|
9812
|
+
}
|
|
9813
|
+
function splitTimestampPrefix(rawSlug) {
|
|
9814
|
+
const separatorIndex = rawSlug.indexOf("-");
|
|
9815
|
+
if (separatorIndex === -1) {
|
|
9816
|
+
return { slug: rawSlug };
|
|
9817
|
+
}
|
|
9818
|
+
const prefix = rawSlug.slice(0, separatorIndex);
|
|
9819
|
+
if (!PLANNING_ARTIFACT_TIMESTAMP_PATTERN.test(prefix)) {
|
|
9820
|
+
return { slug: rawSlug };
|
|
9821
|
+
}
|
|
9822
|
+
return {
|
|
9823
|
+
timestamp: prefix,
|
|
9824
|
+
slug: rawSlug.slice(separatorIndex + 1)
|
|
9825
|
+
};
|
|
9826
|
+
}
|
|
9827
|
+
function parsePlanningArtifactFileName(fileNameOrPath) {
|
|
9828
|
+
const fileName = basename7(fileNameOrPath);
|
|
9829
|
+
const autoresearchDeepInterviewMatch = fileName.match(
|
|
9830
|
+
/^deep-interview-autoresearch-(?<slug>.+)\.md$/i
|
|
9831
|
+
);
|
|
9832
|
+
if (autoresearchDeepInterviewMatch?.groups?.slug) {
|
|
9833
|
+
const parsedSlug = splitTimestampPrefix(autoresearchDeepInterviewMatch.groups.slug);
|
|
9834
|
+
if (!parsedSlug.slug) return null;
|
|
9835
|
+
return {
|
|
9836
|
+
kind: "deep-interview-autoresearch",
|
|
9837
|
+
...parsedSlug
|
|
9838
|
+
};
|
|
9839
|
+
}
|
|
9840
|
+
const deepInterviewMatch = fileName.match(/^deep-interview-(?<slug>.+)\.md$/i);
|
|
9841
|
+
if (deepInterviewMatch?.groups?.slug) {
|
|
9842
|
+
const parsedSlug = splitTimestampPrefix(deepInterviewMatch.groups.slug);
|
|
9843
|
+
if (!parsedSlug.slug) return null;
|
|
9844
|
+
return {
|
|
9845
|
+
kind: "deep-interview",
|
|
9846
|
+
...parsedSlug
|
|
9847
|
+
};
|
|
9848
|
+
}
|
|
9849
|
+
const prdMatch = fileName.match(/^prd-(?<slug>.+)\.md$/i);
|
|
9850
|
+
if (prdMatch?.groups?.slug) {
|
|
9851
|
+
const parsedSlug = splitTimestampPrefix(prdMatch.groups.slug);
|
|
9852
|
+
if (!parsedSlug.slug) return null;
|
|
9853
|
+
return {
|
|
9854
|
+
kind: "prd",
|
|
9855
|
+
...parsedSlug
|
|
9856
|
+
};
|
|
9857
|
+
}
|
|
9858
|
+
const testSpecMatch = fileName.match(/^test-?spec-(?<slug>.+)\.md$/i);
|
|
9859
|
+
if (testSpecMatch?.groups?.slug) {
|
|
9860
|
+
const parsedSlug = splitTimestampPrefix(testSpecMatch.groups.slug);
|
|
9861
|
+
if (!parsedSlug.slug) return null;
|
|
9862
|
+
return {
|
|
9863
|
+
kind: "test-spec",
|
|
9864
|
+
...parsedSlug
|
|
9865
|
+
};
|
|
9866
|
+
}
|
|
9867
|
+
return null;
|
|
9868
|
+
}
|
|
9869
|
+
function comparePlanningArtifactPaths(left, right) {
|
|
9870
|
+
const leftParsed = parsePlanningArtifactFileName(left);
|
|
9871
|
+
const rightParsed = parsePlanningArtifactFileName(right);
|
|
9872
|
+
if (leftParsed?.timestamp && rightParsed?.timestamp && leftParsed.timestamp !== rightParsed.timestamp) {
|
|
9873
|
+
return leftParsed.timestamp.localeCompare(rightParsed.timestamp);
|
|
9874
|
+
}
|
|
9875
|
+
if (leftParsed?.timestamp && !rightParsed?.timestamp) {
|
|
9876
|
+
return 1;
|
|
9877
|
+
}
|
|
9878
|
+
if (!leftParsed?.timestamp && rightParsed?.timestamp) {
|
|
9879
|
+
return -1;
|
|
9880
|
+
}
|
|
9881
|
+
return left.localeCompare(right);
|
|
9882
|
+
}
|
|
9883
|
+
function selectMatchingTestSpecsForPrd(prdPath, testSpecPaths) {
|
|
9884
|
+
if (!prdPath) {
|
|
9885
|
+
return [];
|
|
9886
|
+
}
|
|
9887
|
+
const prdArtifact = parsePlanningArtifactFileName(prdPath);
|
|
9888
|
+
if (prdArtifact?.kind !== "prd") {
|
|
9889
|
+
return [];
|
|
9890
|
+
}
|
|
9891
|
+
const requiredTimestampedFileName = requiredTimestampedTestSpecFileName(prdArtifact);
|
|
9892
|
+
return (requiredTimestampedFileName ? testSpecPaths.filter((path4) => basename7(path4) === requiredTimestampedFileName) : testSpecPaths.filter((path4) => legacyTestSpecSlug(path4) === prdArtifact.slug)).sort(comparePlanningArtifactPaths);
|
|
9893
|
+
}
|
|
9894
|
+
function selectLatestPlanningArtifactPath(paths) {
|
|
9895
|
+
return [...paths].sort(comparePlanningArtifactPaths).at(-1) ?? null;
|
|
9896
|
+
}
|
|
9897
|
+
|
|
9898
|
+
// src/planning/artifacts.ts
|
|
9899
|
+
function readFileSafe(path4) {
|
|
9900
|
+
try {
|
|
9901
|
+
return readFileSync11(path4, "utf-8");
|
|
9902
|
+
} catch {
|
|
9903
|
+
return null;
|
|
9904
|
+
}
|
|
9905
|
+
}
|
|
9906
|
+
function escapeRegex(value) {
|
|
9907
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9908
|
+
}
|
|
9909
|
+
function getSectionContent(markdown, heading) {
|
|
9910
|
+
const headingRe = new RegExp(
|
|
9911
|
+
`^##\\s+${escapeRegex(heading)}[ \\t]*$`,
|
|
9912
|
+
"im"
|
|
9913
|
+
);
|
|
9914
|
+
const headingMatch = headingRe.exec(markdown);
|
|
9915
|
+
if (!headingMatch || headingMatch.index === void 0) return null;
|
|
9916
|
+
const bodyStart = headingMatch.index + headingMatch[0].length;
|
|
9917
|
+
const rest = markdown.slice(bodyStart).replace(/^\r?\n/, "");
|
|
9918
|
+
const nextHeadingMatch = /\r?\n##\s+/.exec(rest);
|
|
9919
|
+
const body = (nextHeadingMatch ? rest.slice(0, nextHeadingMatch.index) : rest).trim();
|
|
9920
|
+
return body.length > 0 ? body : null;
|
|
9921
|
+
}
|
|
9922
|
+
function hasRequiredSections(markdown, headings) {
|
|
9923
|
+
return headings.every(
|
|
9924
|
+
(heading) => getSectionContent(markdown, heading) !== null
|
|
9925
|
+
);
|
|
9926
|
+
}
|
|
9927
|
+
function getPlansDirCandidates(cwd) {
|
|
9928
|
+
return [join24(cwd, ".omc", "plans"), join24(cwd, ".omx", "plans")];
|
|
9929
|
+
}
|
|
9930
|
+
function sortArtifactPathsDescending(paths) {
|
|
9931
|
+
return [...paths].sort((a, b) => comparePlanningArtifactPaths(b, a));
|
|
9932
|
+
}
|
|
9933
|
+
function hasCompletePlanningPair(prdPath, matchingTestSpecPaths) {
|
|
9934
|
+
if (matchingTestSpecPaths.length === 0) {
|
|
9935
|
+
return false;
|
|
9936
|
+
}
|
|
9937
|
+
const prd = readFileSafe(prdPath);
|
|
9938
|
+
const testSpec = readFileSafe(matchingTestSpecPaths[0]);
|
|
9939
|
+
if (!prd || !testSpec) {
|
|
9940
|
+
return false;
|
|
9941
|
+
}
|
|
9942
|
+
return hasRequiredSections(prd, [
|
|
9943
|
+
"Acceptance criteria",
|
|
9944
|
+
"Requirement coverage map"
|
|
9945
|
+
]) && hasRequiredSections(testSpec, [
|
|
9946
|
+
"Unit coverage",
|
|
9947
|
+
"Verification mapping"
|
|
9948
|
+
]);
|
|
9949
|
+
}
|
|
9950
|
+
function readPlanningArtifacts(cwd) {
|
|
9951
|
+
let entries;
|
|
9952
|
+
const prdPaths = [];
|
|
9953
|
+
const testSpecPaths = [];
|
|
9954
|
+
for (const plansDir of getPlansDirCandidates(cwd)) {
|
|
9955
|
+
if (!existsSync21(plansDir)) {
|
|
9956
|
+
continue;
|
|
9957
|
+
}
|
|
9958
|
+
try {
|
|
9959
|
+
entries = readdirSync5(plansDir);
|
|
9960
|
+
} catch {
|
|
9961
|
+
continue;
|
|
9962
|
+
}
|
|
9963
|
+
for (const entry of entries) {
|
|
9964
|
+
if (entry.startsWith("prd-") && entry.endsWith(".md")) {
|
|
9965
|
+
prdPaths.push(join24(plansDir, entry));
|
|
9966
|
+
} else if (entry.startsWith("test-spec-") && entry.endsWith(".md")) {
|
|
9967
|
+
testSpecPaths.push(join24(plansDir, entry));
|
|
9968
|
+
}
|
|
9969
|
+
}
|
|
9970
|
+
}
|
|
9971
|
+
return {
|
|
9972
|
+
prdPaths: sortArtifactPathsDescending(prdPaths),
|
|
9973
|
+
testSpecPaths: sortArtifactPathsDescending(testSpecPaths)
|
|
9974
|
+
};
|
|
9975
|
+
}
|
|
9976
|
+
function decodeQuotedValue(raw) {
|
|
9977
|
+
const normalized = raw.trim();
|
|
9978
|
+
if (!normalized) return null;
|
|
9979
|
+
try {
|
|
9980
|
+
return JSON.parse(normalized);
|
|
9981
|
+
} catch {
|
|
9982
|
+
if (normalized.startsWith('"') && normalized.endsWith('"') || normalized.startsWith("'") && normalized.endsWith("'")) {
|
|
9983
|
+
return normalized.slice(1, -1);
|
|
9984
|
+
}
|
|
9985
|
+
return null;
|
|
9986
|
+
}
|
|
9987
|
+
}
|
|
9988
|
+
function launchHintPattern(mode) {
|
|
9989
|
+
return mode === "team" ? /(?<command>(?:om[cx]\s+team|\$team)(?:\s+ralph)?(?:\s+(?<count>\d+)(?::(?<role>[a-z][a-z0-9-]*))?)?\s+(?<task>"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')(?<flags>(?:\s+--[\w-]+)*))/gi : /(?<command>(?:om[cx]\s+ralph|\$ralph)\s+(?<task>"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')(?<flags>(?:\s+--[\w-]+)*))/gi;
|
|
9990
|
+
}
|
|
9991
|
+
function collectLaunchHintMatches(content, mode) {
|
|
9992
|
+
return [...content.matchAll(launchHintPattern(mode))];
|
|
9993
|
+
}
|
|
9994
|
+
function selectLaunchHintMatch(matches, normalizedTask, normalizedCommand) {
|
|
9995
|
+
const decodedMatches = matches.flatMap((match) => {
|
|
9996
|
+
const command = match[0]?.trim();
|
|
9997
|
+
const task = match.groups?.task ? decodeQuotedValue(match.groups.task) : null;
|
|
9998
|
+
if (!command || task == null) return [];
|
|
9999
|
+
const flags = match.groups?.flags ?? "";
|
|
10000
|
+
const workerCount = match.groups?.count ? Number.parseInt(match.groups.count, 10) : void 0;
|
|
10001
|
+
return [{
|
|
10002
|
+
command,
|
|
10003
|
+
task,
|
|
10004
|
+
...workerCount == null ? {} : { workerCount },
|
|
10005
|
+
agentType: match.groups?.role || void 0,
|
|
10006
|
+
linkedRalph: /\sralph(?:\s|$)/.test(command) || parseFlags(flags).linkedRalph
|
|
10007
|
+
}];
|
|
10008
|
+
});
|
|
10009
|
+
const matchesToConsider = normalizedCommand ? decodedMatches.filter((match) => match.command === normalizedCommand) : normalizedTask ? decodedMatches.filter((match) => match.task.trim() === normalizedTask) : decodedMatches;
|
|
10010
|
+
if (matchesToConsider.length === 0) return { status: "no-match" };
|
|
10011
|
+
if (matchesToConsider.length > 1) return { status: "ambiguous" };
|
|
10012
|
+
return { status: "unique", ...matchesToConsider[0] };
|
|
10013
|
+
}
|
|
10014
|
+
function parseFlags(flagStr) {
|
|
10015
|
+
return {
|
|
10016
|
+
linkedRalph: /--linked-ralph/.test(flagStr)
|
|
10017
|
+
};
|
|
10018
|
+
}
|
|
10019
|
+
function readApprovedExecutionLaunchHintOutcome(cwd, mode, options = {}) {
|
|
10020
|
+
const artifacts = readPlanningArtifacts(cwd);
|
|
10021
|
+
if (artifacts.prdPaths.length === 0) return { status: "absent" };
|
|
10022
|
+
const prdPath = options.prdPath ? artifacts.prdPaths.includes(options.prdPath) ? options.prdPath : null : selectLatestPlanningArtifactPath(artifacts.prdPaths);
|
|
10023
|
+
const matchingTestSpecs = selectMatchingTestSpecsForPrd(
|
|
10024
|
+
prdPath,
|
|
10025
|
+
artifacts.testSpecPaths
|
|
10026
|
+
);
|
|
10027
|
+
if (!prdPath) return { status: "absent" };
|
|
10028
|
+
if (artifacts.testSpecPaths.length > 0 && matchingTestSpecs.length === 0) {
|
|
10029
|
+
return { status: "absent" };
|
|
10030
|
+
}
|
|
10031
|
+
const content = readFileSafe(prdPath);
|
|
10032
|
+
if (!content) return { status: "absent" };
|
|
10033
|
+
const selected = selectLaunchHintMatch(
|
|
10034
|
+
collectLaunchHintMatches(content, mode),
|
|
10035
|
+
options.task?.trim(),
|
|
10036
|
+
options.command?.trim()
|
|
10037
|
+
);
|
|
10038
|
+
if (selected.status === "ambiguous") return { status: "ambiguous" };
|
|
10039
|
+
if (selected.status !== "unique") return { status: "absent" };
|
|
10040
|
+
if (options.requirePlanningComplete && !hasCompletePlanningPair(prdPath, matchingTestSpecs)) {
|
|
10041
|
+
return { status: "incomplete" };
|
|
10042
|
+
}
|
|
10043
|
+
if (mode === "team") {
|
|
10044
|
+
return {
|
|
10045
|
+
status: "resolved",
|
|
10046
|
+
hint: {
|
|
10047
|
+
mode: "team",
|
|
10048
|
+
command: selected.command,
|
|
10049
|
+
task: selected.task,
|
|
10050
|
+
workerCount: selected.workerCount,
|
|
10051
|
+
agentType: selected.agentType,
|
|
10052
|
+
linkedRalph: selected.linkedRalph,
|
|
10053
|
+
sourcePath: prdPath
|
|
10054
|
+
}
|
|
10055
|
+
};
|
|
10056
|
+
}
|
|
10057
|
+
return {
|
|
10058
|
+
status: "resolved",
|
|
10059
|
+
hint: {
|
|
10060
|
+
mode: "ralph",
|
|
10061
|
+
command: selected.command,
|
|
10062
|
+
task: selected.task,
|
|
10063
|
+
linkedRalph: selected.linkedRalph,
|
|
10064
|
+
sourcePath: prdPath
|
|
10065
|
+
}
|
|
10066
|
+
};
|
|
10067
|
+
}
|
|
10068
|
+
|
|
10069
|
+
// src/cli/team.ts
|
|
8473
10070
|
var JOB_ID_PATTERN = /^omc-[a-z0-9]{1,16}$/;
|
|
8474
10071
|
var VALID_CLI_AGENT_TYPES = /* @__PURE__ */ new Set(["claude", "copilot", "codex", "gemini", "cursor"]);
|
|
8475
10072
|
var SUBCOMMANDS = /* @__PURE__ */ new Set(["start", "status", "wait", "cleanup", "resume", "shutdown", "api", "help", "--help", "-h"]);
|
|
@@ -8503,31 +10100,31 @@ function assertTeamSpawnAllowed(env = process.env) {
|
|
|
8503
10100
|
);
|
|
8504
10101
|
}
|
|
8505
10102
|
function resolveJobsDir(env = process.env) {
|
|
8506
|
-
return env.OMC_JOBS_DIR ||
|
|
10103
|
+
return env.OMC_JOBS_DIR || join25(homedir3(), ".omcp", "team-jobs");
|
|
8507
10104
|
}
|
|
8508
10105
|
function resolveRuntimeCliPath(env = process.env) {
|
|
8509
10106
|
if (env.OMC_RUNTIME_CLI_PATH) {
|
|
8510
10107
|
return env.OMC_RUNTIME_CLI_PATH;
|
|
8511
10108
|
}
|
|
8512
|
-
const moduleDir =
|
|
8513
|
-
return
|
|
10109
|
+
const moduleDir = dirname16(fileURLToPath3(import.meta.url));
|
|
10110
|
+
return join25(moduleDir, "../../bridge/runtime-cli.cjs");
|
|
8514
10111
|
}
|
|
8515
10112
|
function ensureJobsDir(jobsDir) {
|
|
8516
|
-
if (!
|
|
10113
|
+
if (!existsSync22(jobsDir)) {
|
|
8517
10114
|
mkdirSync3(jobsDir, { recursive: true });
|
|
8518
10115
|
}
|
|
8519
10116
|
}
|
|
8520
10117
|
function jobPath(jobsDir, jobId) {
|
|
8521
|
-
return
|
|
10118
|
+
return join25(jobsDir, `${jobId}.json`);
|
|
8522
10119
|
}
|
|
8523
10120
|
function resultArtifactPath(jobsDir, jobId) {
|
|
8524
|
-
return
|
|
10121
|
+
return join25(jobsDir, `${jobId}-result.json`);
|
|
8525
10122
|
}
|
|
8526
10123
|
function panesArtifactPath(jobsDir, jobId) {
|
|
8527
|
-
return
|
|
10124
|
+
return join25(jobsDir, `${jobId}-panes.json`);
|
|
8528
10125
|
}
|
|
8529
10126
|
function teamStateRoot2(cwd, teamName) {
|
|
8530
|
-
return
|
|
10127
|
+
return join25(cwd, ".omcp", "state", "team", teamName);
|
|
8531
10128
|
}
|
|
8532
10129
|
function validateJobId(jobId) {
|
|
8533
10130
|
if (!JOB_ID_PATTERN.test(jobId)) {
|
|
@@ -8542,7 +10139,7 @@ function parseJsonSafe(content) {
|
|
|
8542
10139
|
}
|
|
8543
10140
|
}
|
|
8544
10141
|
async function resolveCleanupPaneEvidence(job, jobsDir, jobId) {
|
|
8545
|
-
const paneArtifact = await
|
|
10142
|
+
const paneArtifact = await readFile11(panesArtifactPath(jobsDir, jobId), "utf-8").then((content) => parseJsonSafe(content)).catch(() => null);
|
|
8546
10143
|
if (paneArtifact?.paneIds?.length) return { paneArtifact };
|
|
8547
10144
|
const config = await readTeamConfig(job.teamName, job.cwd).catch(() => null);
|
|
8548
10145
|
if (!config) {
|
|
@@ -8567,7 +10164,7 @@ async function resolveCleanupPaneEvidence(job, jobsDir, jobId) {
|
|
|
8567
10164
|
}
|
|
8568
10165
|
function readJobFromDisk(jobId, jobsDir) {
|
|
8569
10166
|
try {
|
|
8570
|
-
const content =
|
|
10167
|
+
const content = readFileSync12(jobPath(jobsDir, jobId), "utf-8");
|
|
8571
10168
|
return parseJsonSafe(content);
|
|
8572
10169
|
} catch {
|
|
8573
10170
|
return null;
|
|
@@ -8596,7 +10193,7 @@ function generateJobId(now = Date.now()) {
|
|
|
8596
10193
|
}
|
|
8597
10194
|
function convergeWithResultArtifact(jobId, job, jobsDir) {
|
|
8598
10195
|
try {
|
|
8599
|
-
const artifactRaw =
|
|
10196
|
+
const artifactRaw = readFileSync12(resultArtifactPath(jobsDir, jobId), "utf-8");
|
|
8600
10197
|
const artifactParsed = parseJsonSafe(artifactRaw);
|
|
8601
10198
|
if (artifactParsed?.status === "completed" || artifactParsed?.status === "failed") {
|
|
8602
10199
|
return {
|
|
@@ -8686,7 +10283,8 @@ async function startTeamJob(input) {
|
|
|
8686
10283
|
newWindow: input.newWindow,
|
|
8687
10284
|
pollIntervalMs: input.pollIntervalMs,
|
|
8688
10285
|
sentinelGateTimeoutMs: input.sentinelGateTimeoutMs,
|
|
8689
|
-
sentinelGatePollIntervalMs: input.sentinelGatePollIntervalMs
|
|
10286
|
+
sentinelGatePollIntervalMs: input.sentinelGatePollIntervalMs,
|
|
10287
|
+
autoMerge: input.autoMerge
|
|
8690
10288
|
};
|
|
8691
10289
|
child.stdin.write(JSON.stringify(payload));
|
|
8692
10290
|
child.stdin.end();
|
|
@@ -8981,7 +10579,7 @@ async function teamCleanupCommand(jobId, cleanupOptions = {}, options = {}) {
|
|
|
8981
10579
|
}
|
|
8982
10580
|
var TEAM_USAGE = `
|
|
8983
10581
|
Usage:
|
|
8984
|
-
omcp team start --agent <claude|copilot|codex|gemini|cursor>[,<agent>...] --task "<task>" [--count N] [--name TEAM] [--cwd DIR] [--new-window] [--json]
|
|
10582
|
+
omcp team start --agent <claude|copilot|codex|gemini|cursor>[,<agent>...] --task "<task>" [--count N] [--name TEAM] [--cwd DIR] [--new-window] [--auto-merge] [--json]
|
|
8985
10583
|
omcp team status <job_id|team_name> [--json] [--cwd DIR]
|
|
8986
10584
|
omcp team wait <job_id> [--timeout-ms MS] [--json]
|
|
8987
10585
|
omcp team cleanup <job_id> [--grace-ms MS] [--json]
|
|
@@ -8994,6 +10592,13 @@ Worktrees:
|
|
|
8994
10592
|
Native per-worker git worktree mode is opt-in/config-gated with team.ops.worktreeMode or OMC_TEAM_WORKTREE_MODE=detached|named.
|
|
8995
10593
|
Status JSON includes workspace_mode, worktree_mode, team_state_root, and per-worker worktree metadata.
|
|
8996
10594
|
|
|
10595
|
+
Auto-merge (v2-only):
|
|
10596
|
+
--auto-merge Enable per-commit auto-merge to leader and auto-rebase fanout.
|
|
10597
|
+
Each worker runs in a dedicated git worktree on omc-team/{team}/{worker}.
|
|
10598
|
+
Bursts of rapid worker commits coalesce to a single merge of HEAD.
|
|
10599
|
+
Requires OMC_RUNTIME_V2=1. Leader branch must not be 'main' or 'master'.
|
|
10600
|
+
Equivalent to OMC_TEAMS_AUTO_MERGE=1.
|
|
10601
|
+
|
|
8997
10602
|
Examples:
|
|
8998
10603
|
omcp team start --agent codex --count 2 --task "review auth flow" --new-window
|
|
8999
10604
|
omcp team status omc-abc123
|
|
@@ -9015,6 +10620,7 @@ function parseStartArgs(args) {
|
|
|
9015
10620
|
let pollIntervalMs;
|
|
9016
10621
|
let sentinelGateTimeoutMs;
|
|
9017
10622
|
let sentinelGatePollIntervalMs;
|
|
10623
|
+
let autoMerge = process.env.OMC_TEAMS_AUTO_MERGE === "1";
|
|
9018
10624
|
for (let i = 0; i < args.length; i += 1) {
|
|
9019
10625
|
const token = args[i];
|
|
9020
10626
|
const next = args[i + 1];
|
|
@@ -9026,6 +10632,10 @@ function parseStartArgs(args) {
|
|
|
9026
10632
|
newWindow = true;
|
|
9027
10633
|
continue;
|
|
9028
10634
|
}
|
|
10635
|
+
if (token === "--auto-merge") {
|
|
10636
|
+
autoMerge = true;
|
|
10637
|
+
continue;
|
|
10638
|
+
}
|
|
9029
10639
|
if (token === "--agent") {
|
|
9030
10640
|
if (!next) throw new Error("Missing value after --agent");
|
|
9031
10641
|
agentValues.push(...next.split(",").map(normalizeAgentType));
|
|
@@ -9143,7 +10753,8 @@ function parseStartArgs(args) {
|
|
|
9143
10753
|
...newWindow ? { newWindow: true } : {},
|
|
9144
10754
|
...pollIntervalMs != null ? { pollIntervalMs } : {},
|
|
9145
10755
|
...sentinelGateTimeoutMs != null ? { sentinelGateTimeoutMs } : {},
|
|
9146
|
-
...sentinelGatePollIntervalMs != null ? { sentinelGatePollIntervalMs } : {}
|
|
10756
|
+
...sentinelGatePollIntervalMs != null ? { sentinelGatePollIntervalMs } : {},
|
|
10757
|
+
...autoMerge ? { autoMerge: true } : {}
|
|
9147
10758
|
},
|
|
9148
10759
|
json
|
|
9149
10760
|
};
|
|
@@ -9326,14 +10937,15 @@ function parseLegacyStartAlias(args) {
|
|
|
9326
10937
|
if (!spec) return null;
|
|
9327
10938
|
const match = spec.match(/^(\d+):([a-zA-Z0-9_-]+)(?::([a-zA-Z0-9_-]+))?$/);
|
|
9328
10939
|
if (!match) return null;
|
|
9329
|
-
|
|
10940
|
+
let workerCount = toInt(match[1], "worker-count");
|
|
9330
10941
|
if (workerCount < 1) throw new Error("worker-count must be >= 1");
|
|
9331
|
-
|
|
10942
|
+
let agentType = normalizeAgentType(match[2]);
|
|
9332
10943
|
const role = match[3] || void 0;
|
|
9333
10944
|
index += 1;
|
|
9334
10945
|
let json = false;
|
|
9335
10946
|
let cwd = process.cwd();
|
|
9336
10947
|
let newWindow = false;
|
|
10948
|
+
let autoMerge = process.env.OMC_TEAMS_AUTO_MERGE === "1";
|
|
9337
10949
|
const taskParts = [];
|
|
9338
10950
|
for (let i = index; i < args.length; i += 1) {
|
|
9339
10951
|
const token = args[i];
|
|
@@ -9346,6 +10958,10 @@ function parseLegacyStartAlias(args) {
|
|
|
9346
10958
|
newWindow = true;
|
|
9347
10959
|
continue;
|
|
9348
10960
|
}
|
|
10961
|
+
if (token === "--auto-merge") {
|
|
10962
|
+
autoMerge = true;
|
|
10963
|
+
continue;
|
|
10964
|
+
}
|
|
9349
10965
|
if (token === "--cwd") {
|
|
9350
10966
|
if (!next) throw new Error("Missing value after --cwd");
|
|
9351
10967
|
cwd = next;
|
|
@@ -9358,8 +10974,35 @@ function parseLegacyStartAlias(args) {
|
|
|
9358
10974
|
}
|
|
9359
10975
|
taskParts.push(token);
|
|
9360
10976
|
}
|
|
9361
|
-
|
|
10977
|
+
let task = taskParts.join(" ").trim();
|
|
9362
10978
|
if (!task) throw new Error("Legacy start alias requires a task string");
|
|
10979
|
+
const shortFollowup = ["team", "/team", "team please", "run team", "start team"].includes(task.toLowerCase());
|
|
10980
|
+
if (shortFollowup) {
|
|
10981
|
+
const approvedHintOutcome = readApprovedExecutionLaunchHintOutcome(cwd, "team", {
|
|
10982
|
+
requirePlanningComplete: true
|
|
10983
|
+
});
|
|
10984
|
+
if (approvedHintOutcome.status === "ambiguous") {
|
|
10985
|
+
throw new Error("approved_execution_hint_ambiguous:team");
|
|
10986
|
+
}
|
|
10987
|
+
if (approvedHintOutcome.status === "incomplete") {
|
|
10988
|
+
throw new Error("approved_execution_hint_incomplete:team");
|
|
10989
|
+
}
|
|
10990
|
+
if (approvedHintOutcome.status === "resolved") {
|
|
10991
|
+
task = approvedHintOutcome.hint.task;
|
|
10992
|
+
workerCount = approvedHintOutcome.hint.workerCount ?? workerCount;
|
|
10993
|
+
agentType = approvedHintOutcome.hint.agentType ? normalizeAgentType(approvedHintOutcome.hint.agentType) : agentType;
|
|
10994
|
+
ralph = approvedHintOutcome.hint.linkedRalph === true ? true : ralph;
|
|
10995
|
+
}
|
|
10996
|
+
} else {
|
|
10997
|
+
const command = `omc team ${ralph ? "ralph " : ""}${spec} ${JSON.stringify(task)}`;
|
|
10998
|
+
const approvedHintOutcome = readApprovedExecutionLaunchHintOutcome(cwd, "team", {
|
|
10999
|
+
task,
|
|
11000
|
+
command
|
|
11001
|
+
});
|
|
11002
|
+
if (approvedHintOutcome.status === "ambiguous") {
|
|
11003
|
+
throw new Error("approved_execution_hint_ambiguous:team");
|
|
11004
|
+
}
|
|
11005
|
+
}
|
|
9363
11006
|
return {
|
|
9364
11007
|
workerCount,
|
|
9365
11008
|
agentType,
|
|
@@ -9369,7 +11012,8 @@ function parseLegacyStartAlias(args) {
|
|
|
9369
11012
|
ralph,
|
|
9370
11013
|
json,
|
|
9371
11014
|
cwd,
|
|
9372
|
-
...newWindow ? { newWindow: true } : {}
|
|
11015
|
+
...newWindow ? { newWindow: true } : {},
|
|
11016
|
+
...autoMerge ? { autoMerge: true } : {}
|
|
9373
11017
|
};
|
|
9374
11018
|
}
|
|
9375
11019
|
async function teamCommand(argv) {
|
|
@@ -9445,7 +11089,8 @@ async function teamCommand(argv) {
|
|
|
9445
11089
|
agentTypes: Array.from({ length: legacy.workerCount }, () => legacy.agentType),
|
|
9446
11090
|
tasks,
|
|
9447
11091
|
cwd: legacy.cwd,
|
|
9448
|
-
...legacy.newWindow ? { newWindow: true } : {}
|
|
11092
|
+
...legacy.newWindow ? { newWindow: true } : {},
|
|
11093
|
+
...legacy.autoMerge ? { autoMerge: true } : {}
|
|
9449
11094
|
});
|
|
9450
11095
|
output(result, legacy.json);
|
|
9451
11096
|
return;
|