Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 133 additions & 7 deletions bin/multiagent-safety.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ const TEMPLATE_ROOT = path.resolve(__dirname, '..', 'templates');
const TEMPLATE_FILES = [
'scripts/agent-branch-start.sh',
'scripts/agent-branch-finish.sh',
'scripts/agent-branch-merge.sh',
'scripts/codex-agent.sh',
'scripts/guardex-docker-loader.sh',
'scripts/review-bot-watch.sh',
Expand All @@ -112,6 +113,7 @@ const TEMPLATE_FILES = [
const REQUIRED_WORKFLOW_FILES = [
'scripts/agent-branch-start.sh',
'scripts/agent-branch-finish.sh',
'scripts/agent-branch-merge.sh',
'scripts/guardex-docker-loader.sh',
'scripts/agent-worktree-prune.sh',
'scripts/agent-file-locks.py',
Expand All @@ -126,6 +128,7 @@ const REQUIRED_PACKAGE_SCRIPTS = {
'agent:codex': 'bash ./scripts/codex-agent.sh',
'agent:branch:start': 'bash ./scripts/agent-branch-start.sh',
'agent:branch:finish': 'bash ./scripts/agent-branch-finish.sh',
'agent:branch:merge': 'bash ./scripts/agent-branch-merge.sh',
'agent:cleanup': 'gx cleanup',
'agent:hooks:install': 'bash ./scripts/install-agent-git-hooks.sh',
'agent:locks:claim': 'python3 ./scripts/agent-file-locks.py claim',
Expand All @@ -149,6 +152,7 @@ const REQUIRED_PACKAGE_SCRIPTS = {
const EXECUTABLE_RELATIVE_PATHS = new Set([
'scripts/agent-branch-start.sh',
'scripts/agent-branch-finish.sh',
'scripts/agent-branch-merge.sh',
'scripts/codex-agent.sh',
'scripts/guardex-docker-loader.sh',
'scripts/review-bot-watch.sh',
Expand All @@ -171,6 +175,7 @@ const CRITICAL_GUARDRAIL_PATHS = new Set([
'.githooks/post-checkout',
'scripts/agent-branch-start.sh',
'scripts/agent-branch-finish.sh',
'scripts/agent-branch-merge.sh',
'scripts/agent-worktree-prune.sh',
'scripts/codex-agent.sh',
'scripts/agent-file-locks.py',
Expand Down Expand Up @@ -233,6 +238,7 @@ const SUGGESTIBLE_COMMANDS = [
'setup',
'doctor',
'agents',
'merge',
'finish',
'report',
'protect',
Expand All @@ -257,6 +263,7 @@ const CLI_COMMAND_DESCRIPTIONS = [
['setup', 'Install, repair, and verify guardrails (flags: --repair, --install-only, --target)'],
['doctor', 'Repair drift + verify (auto-sandboxes on protected main)'],
['protect', 'Manage protected branches (list/add/remove/set/reset)'],
['merge', 'Create/reuse an integration lane and merge overlapping agent branches'],
['sync', 'Sync agent branches with origin/<base>'],
['finish', 'Commit + PR + merge completed agent branches (--all, --branch)'],
['cleanup', 'Prune merged/stale agent branches and worktrees'],
Expand Down Expand Up @@ -304,13 +311,14 @@ const AI_SETUP_PROMPT = `GitGuardex (gx) setup checklist for Codex/Claude in thi
3) Repair: gx doctor
4) Task loop: bash scripts/codex-agent.sh "<task>" "<agent>"
or branch-start -> python3 scripts/agent-file-locks.py claim -> branch-finish
5) Finish: gx finish --all
6) Cleanup: gx cleanup
7) OpenSpec: /opsx:propose -> /opsx:apply -> /opsx:archive
8) Optional: gx protect add release staging
9) Optional: gx sync --check && gx sync
10) Review bot: install https://github.com/apps/cr-gpt + set OPENAI_API_KEY
11) Fork sync: install https://github.com/apps/pull + cp .github/pull.yml.example .github/pull.yml
5) Integrate: gx merge --branch <agent-a> --branch <agent-b>
6) Finish: gx finish --all
7) Cleanup: gx cleanup
8) OpenSpec: /opsx:propose -> /opsx:apply -> /opsx:archive
9) Optional: gx protect add release staging
10) Optional: gx sync --check && gx sync
11) Review bot: install https://github.com/apps/cr-gpt + set OPENAI_API_KEY
12) Fork sync: install https://github.com/apps/pull + cp .github/pull.yml.example .github/pull.yml
`;

const AI_SETUP_COMMANDS = `npm i -g @imdeadpool/guardex
Expand All @@ -319,6 +327,7 @@ gx setup
gx doctor
bash scripts/codex-agent.sh "<task>" "<agent>"
python3 scripts/agent-file-locks.py claim --branch "<agent-branch>" <file...>
gx merge --branch "<agent-a>" --branch "<agent-b>"
gx finish --all
gx cleanup
gx protect add release staging
Expand Down Expand Up @@ -3543,6 +3552,82 @@ function parseCleanupArgs(rawArgs) {
return options;
}

function parseMergeArgs(rawArgs) {
const options = {
target: process.cwd(),
base: '',
into: '',
branches: [],
task: '',
agent: '',
};

for (let index = 0; index < rawArgs.length; index += 1) {
const arg = rawArgs[index];
if (arg === '--target') {
const next = rawArgs[index + 1];
if (!next) {
throw new Error('--target requires a path value');
}
options.target = next;
index += 1;
continue;
}
if (arg === '--base') {
const next = rawArgs[index + 1];
if (!next) {
throw new Error('--base requires a branch value');
}
options.base = next;
index += 1;
continue;
}
if (arg === '--into') {
const next = rawArgs[index + 1];
if (!next) {
throw new Error('--into requires an agent/* branch value');
}
options.into = next;
index += 1;
continue;
}
if (arg === '--branch') {
const next = rawArgs[index + 1];
if (!next) {
throw new Error('--branch requires an agent/* branch value');
}
options.branches.push(next);
index += 1;
continue;
}
if (arg === '--task') {
const next = rawArgs[index + 1];
if (!next) {
throw new Error('--task requires a task value');
}
options.task = next;
index += 1;
continue;
}
if (arg === '--agent') {
const next = rawArgs[index + 1];
if (!next) {
throw new Error('--agent requires an agent value');
}
options.agent = next;
index += 1;
continue;
}
throw new Error(`Unknown option: ${arg}`);
}

if (options.branches.length === 0) {
throw new Error('merge requires at least one --branch <agent/*> input');
}

return options;
}

function parseFinishArgs(rawArgs) {
const options = {
target: process.cwd(),
Expand Down Expand Up @@ -6587,6 +6672,46 @@ function cleanup(rawArgs) {
process.exitCode = 0;
}

function merge(rawArgs) {
const options = parseMergeArgs(rawArgs);
const repoRoot = resolveRepoRoot(options.target);
const mergeScript = path.join(repoRoot, 'scripts', 'agent-branch-merge.sh');

if (!fs.existsSync(mergeScript)) {
throw new Error(`Missing merge script: ${mergeScript}. Run '${SHORT_TOOL_NAME} setup' first.`);
}

const args = [mergeScript];
if (options.base) {
args.push('--base', options.base);
}
if (options.into) {
args.push('--into', options.into);
}
if (options.task) {
args.push('--task', options.task);
}
if (options.agent) {
args.push('--agent', options.agent);
}
for (const branch of options.branches) {
args.push('--branch', branch);
}

const mergeResult = run('bash', args, { cwd: repoRoot, stdio: 'pipe' });
if (mergeResult.stdout) {
process.stdout.write(mergeResult.stdout);
}
if (mergeResult.stderr) {
process.stderr.write(mergeResult.stderr);
}
if (mergeResult.status !== 0) {
throw new Error(`merge command failed with status ${mergeResult.status}`);
}

process.exitCode = 0;
}

function finish(rawArgs) {
const options = parseFinishArgs(rawArgs);
const repoRoot = resolveRepoRoot(options.target);
Expand Down Expand Up @@ -7073,6 +7198,7 @@ function main() {
if (command === 'prompt') return prompt(rest);
if (command === 'doctor') return doctor(rest);
if (command === 'agents') return agents(rest);
if (command === 'merge') return merge(rest);
if (command === 'finish') return finish(rest);
if (command === 'report') return report(rest);
if (command === 'protect') return protect(rest);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-04-21
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## Why

- GitGuardex currently finishes one `agent/*` branch into the base branch at a time, but it does not provide an integration workflow for the common case where multiple agent worktrees touched the same implementation files.
- In that situation users end up staring at several parallel worktrees with overlapping edits and no first-class Guardex command that creates the right integration branch/worktree, reports overlap, and gives a safe place to resolve conflicts.
- OpenSpec already treats implementation as owner/helper lanes with durable artifacts, so the merge workflow should preserve that model instead of pushing users back to ad hoc manual git work on the protected base.

## What Changes

- Add a first-class `gx merge` command plus a managed `scripts/agent-branch-merge.sh` workflow.
- Let the workflow either create a fresh integration `agent/*` branch/worktree from the configured base branch or merge helper branches into an existing owner branch via `--into`.
- Report overlapping changed files across the requested source branches before merging so users can see where collisions are expected, especially inside shared implementation files and OpenSpec surfaces.
- Merge branches in explicit order, stop on conflicts without touching the protected base branch, and print resumable instructions that keep conflict resolution inside the integration worktree.
- Ship the new script through the setup/templates/package metadata path so downstream repos get the same capability.

## Impact

- Affected surfaces: `gx` CLI command catalog, managed workflow scripts/templates, setup/doctor script expectations, and regression tests.
- Risk: merge automation is sensitive to dirty worktrees and stale branches, so the implementation needs strict preflight checks and clear conflict-stop behavior.
- Rollout: local CLI/script addition only; no data migration.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
## ADDED Requirements

### Requirement: integration merge runs inside an agent worktree
GitGuardex SHALL merge overlapping `agent/*` branches inside an integration `agent/*` branch/worktree instead of merging directly on the protected base branch.

#### Scenario: create a fresh integration lane
- **WHEN** a user runs `gx merge` with one or more `--branch agent/...` inputs and no `--into`
- **THEN** the system creates a new integration `agent/*` branch/worktree from the configured base branch
- **AND** all requested merges run inside that integration worktree
- **AND** the command prints the integration branch and worktree path.

#### Scenario: reuse an existing owner lane
- **WHEN** a user runs `gx merge --into <agent-branch>` with additional source branches
- **THEN** the system reuses that owner branch as the merge target
- **AND** it refuses to proceed if the target worktree has uncommitted changes or an in-progress merge operation.

### Requirement: overlapping file edits are reported before merge
GitGuardex SHALL detect and report files changed by more than one requested source branch before applying the merges.

#### Scenario: overlapping implementation files exist
- **WHEN** two or more requested source branches changed the same file relative to the merge base
- **THEN** the command prints each overlapping file
- **AND** it identifies the source branches that changed that file
- **AND** it still allows the user to continue into the integration lane unless another hard preflight check fails.

### Requirement: conflicts stop with resumable guidance
GitGuardex SHALL stop on merge conflicts inside the integration worktree and provide resumable next-step guidance without mutating the protected base branch.

#### Scenario: sequential merge hits a conflict
- **WHEN** `gx merge` successfully merges earlier source branches and then encounters a conflict on a later source branch
- **THEN** the command exits non-zero
- **AND** it prints the target integration branch/worktree, the source branch that conflicted, and the conflicting files
- **AND** it tells the user how to resolve or abort the conflict inside the integration worktree
- **AND** it prints the remaining branches so the merge sequence can be resumed intentionally afterward.

### Requirement: setup-managed repos receive the merge workflow
GitGuardex setup/doctor SHALL install the managed merge workflow files and package script entry needed to run `gx merge`.

#### Scenario: setup bootstraps a repo
- **WHEN** `gx setup` or `gx doctor --repair` installs managed workflow files
- **THEN** the repo contains `scripts/agent-branch-merge.sh`
- **AND** the repo package scripts include a stable merge entry point for the managed workflow.
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
## 1. Specification

- [x] 1.1 Finalize acceptance criteria for the overlapping-agent merge workflow.
- [x] 1.2 Define normative requirements for integration-branch creation, overlap reporting, and conflict-stop behavior.

## 2. Implementation

- [x] 2.1 Add a managed `agent-branch-merge` script that can create or reuse an integration worktree and merge multiple `agent/*` branches in order.
- [x] 2.2 Add `gx merge` CLI wiring, package metadata, and template/setup propagation for the new workflow.
- [x] 2.3 Keep the protected base branch untouched while merging and print resumable instructions for conflict resolution.

## 3. Verification

- [x] 3.1 Add/update focused regression coverage for clean merges, overlap reporting, and conflict-stop behavior.
- [ ] 3.2 Run `npm test`. BLOCKED: full suite produced early passing output but then stayed silent/hung in this environment; focused `node --test test/merge-workflow.test.js` passed.
- [x] 3.3 Run `node --check bin/multiagent-safety.js`.
- [x] 3.4 Run `openspec validate agent-codex-merge-overlapping-agent-branches-2026-04-21-14-28 --type change --strict`.
- [x] 3.5 Run `openspec validate --specs`.
- [x] 3.6 Run `git diff --check`.

## 4. Completion

- [ ] 4.1 Finish the agent branch via PR merge + cleanup (`gx finish --via-pr --wait-for-merge --cleanup` or `bash scripts/agent-branch-finish.sh --branch <agent-branch> --base <base-branch> --via-pr --wait-for-merge --cleanup`).
- [ ] 4.2 Record PR URL + final `MERGED` state in the completion handoff.
- [ ] 4.3 Confirm sandbox cleanup (`git worktree list`, `git branch -a`) or capture a `BLOCKED:` handoff if merge/cleanup is pending.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"agent:codex": "bash ./scripts/codex-agent.sh",
"agent:branch:start": "bash ./scripts/agent-branch-start.sh",
"agent:branch:finish": "bash ./scripts/agent-branch-finish.sh",
"agent:branch:merge": "bash ./scripts/agent-branch-merge.sh",
"agent:cleanup": "gx cleanup",
"agent:hooks:install": "bash ./scripts/install-agent-git-hooks.sh",
"agent:locks:claim": "python3 ./scripts/agent-file-locks.py claim",
Expand Down
Loading