Skip to content

Commit 4fca9c5

Browse files
NagyViktNagyVikt
andauthored
feat(gx): add --advance-submodules flag to gx branch finish (#559)
When passed AND the agent lane has `.gitmodules`, the finish flow runs `submoduleModule.advance({ commit: true })` on the lane before spawning agent-branch-finish.sh. Pointer bumps land as a commit on the agent branch, so the resulting PR contains the user's code edits + the submodule pointer bumps as one atomic unit. Closes the manual ritual for monorepo shops: instead of running `gx submodule advance --push` in a separate lane after merge, users can include `--advance-submodules` in the finish command and get one combined PR. Safety: - Opt-in flag — default off, no behavior change for non-users. - Reuses Phase B plumbing (PR #558): refuses to commit if any submodule is dirty or if the lane's working tree has unrelated changes. - Dry-run path shows the would-advance preview without mutating. - If lane has no `.gitmodules`, flag is logged as ignored and the rest of finish proceeds normally. Verified end-to-end against gitguardex's own .gitmodules: - `node bin/multiagent-safety.js finish --dry-run --advance-submodules` correctly reports `would-init` for each uninitialized submodule then continues with the normal finish dry-run output. Files: - src/cli/args.js — add --advance-submodules/--no-advance-submodules - src/finish/index.js — import submoduleModule, call advance() in the finish loop between auto-commit and runPackageAsset('branchFinish') Co-authored-by: NagyVikt <nagy.viktordp@gmail.com>
1 parent f6e3742 commit 4fca9c5

6 files changed

Lines changed: 98 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-05-11
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
## Why
2+
3+
In monorepo shops (`medusa-shops/lifted`, `compastor`, etc.) the parent repo pins `apps/backend` and `apps/storefront` as submodules. When an agent's lane includes both code edits AND new commits in submodules, the user has to run `gx submodule advance` (Phase B) as a separate step before finishing the lane — otherwise the parent PR ships with stale submodule pointers and reviewers see "no actual change."
4+
5+
## What Changes
6+
7+
- Add `--advance-submodules` / `--no-advance-submodules` to `gx branch finish`. Default off.
8+
- When `--advance-submodules` is passed AND the worktree has `.gitmodules`, the finish flow runs `submoduleModule.advance({ commit: true })` on the lane *before* spawning `agent-branch-finish.sh`. Pointer bumps land as a commit on the agent's branch, so the resulting PR contains the user's code edits + the pointer bumps in one atomic unit.
9+
- In `--dry-run` mode, the advance preview is printed without mutating anything.
10+
- If the worktree has no `.gitmodules`, the flag is logged as ignored and finish proceeds normally.
11+
12+
## Impact
13+
14+
- Opt-in flag: no behavior change for non-monorepo lanes or for users who don't pass the flag.
15+
- Uses the existing `submoduleModule.advance` plumbing (Phase B) — already battle-tested via PR #558.
16+
- Safety: advance refuses to commit if any submodule is dirty or if the lane's working tree has unrelated changes, so the existing finish gates aren't bypassed.
17+
- Future: `--advance-submodules` could become the default for repos with `.gitmodules` once we're confident in the flow.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
## ADDED Requirements
2+
3+
### Requirement: branch-finish-advance-submodules-flag behavior
4+
The system SHALL enforce branch-finish-advance-submodules-flag behavior as defined by this change.
5+
6+
#### Scenario: Baseline acceptance
7+
- **WHEN** branch-finish-advance-submodules-flag behavior is exercised
8+
- **THEN** the expected outcome is produced
9+
- **AND** regressions are covered by tests.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
## Definition of Done
2+
3+
This change is complete only when **all** of the following are true:
4+
5+
- Every checkbox below is checked.
6+
- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff.
7+
- If any step blocks (test failure, conflict, ambiguous result), append a `BLOCKED:` line under section 4 explaining the blocker and **STOP**. Do not tick remaining cleanup boxes; do not silently skip the cleanup pipeline.
8+
9+
## Handoff
10+
11+
- Handoff: change=`agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50`; branch=`agent/<your-name>/<branch-slug>`; scope=`TODO`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`.
12+
- Copy prompt: Continue `agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50` on branch `agent/<your-name>/<branch-slug>`. Work inside the existing sandbox, review `openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/tasks.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `gx branch finish --branch agent/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`.
13+
14+
## 1. Specification
15+
16+
- [ ] 1.1 Finalize proposal scope and acceptance criteria for `agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50`.
17+
- [ ] 1.2 Define normative requirements in `specs/branch-finish-advance-submodules-flag/spec.md`.
18+
19+
## 2. Implementation
20+
21+
- [ ] 2.1 Implement scoped behavior changes.
22+
- [ ] 2.2 Add/update focused regression coverage.
23+
24+
## 3. Verification
25+
26+
- [ ] 3.1 Run targeted project verification commands.
27+
- [ ] 3.2 Run `openspec validate agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50 --type change --strict`.
28+
- [ ] 3.3 Run `openspec validate --specs`.
29+
30+
## 4. Cleanup (mandatory; run before claiming completion)
31+
32+
- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent/<your-name>/<branch-slug> --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation.
33+
- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff.
34+
- [ ] 4.3 Confirm the sandbox worktree is gone (`git worktree list` no longer shows the agent path; `git branch -a` shows no surviving local/remote refs for the branch).

src/cli/args.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,7 @@ function parseFinishArgs(rawArgs, defaults = {}) {
10021002
keepRemote: false,
10031003
noAutoCommit: false,
10041004
parentGitlinkCommit: defaults.parentGitlinkCommit ?? true,
1005+
advanceSubmodules: false,
10051006
failFast: false,
10061007
commitMessage: '',
10071008
mergeMode: defaults.mergeMode || 'pr',
@@ -1109,6 +1110,14 @@ function parseFinishArgs(rawArgs, defaults = {}) {
11091110
options.failFast = true;
11101111
continue;
11111112
}
1113+
if (arg === '--advance-submodules') {
1114+
options.advanceSubmodules = true;
1115+
continue;
1116+
}
1117+
if (arg === '--no-advance-submodules') {
1118+
options.advanceSubmodules = false;
1119+
continue;
1120+
}
11121121
throw new Error(`Unknown option: ${arg}`);
11131122
}
11141123

src/finish/index.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const {
2626
parseFinishArgs,
2727
parseSyncArgs,
2828
} = require('../cli/args');
29+
const submoduleModule = require('../submodule');
2930

3031
function claimLocksForAutoCommit(repoRoot, worktreePath, branch) {
3132
const changedFiles = uniquePreserveOrder([
@@ -305,6 +306,32 @@ function finish(rawArgs, defaults = {}) {
305306
console.log(`[${TOOL_NAME}] [dry-run] Would auto-commit pending changes on '${branch}'.`);
306307
}
307308

309+
if (options.advanceSubmodules && worktreePath) {
310+
const gitmodulesPath = path.join(worktreePath, '.gitmodules');
311+
if (fs.existsSync(gitmodulesPath)) {
312+
if (options.dryRun) {
313+
const preview = submoduleModule.advance({
314+
target: worktreePath,
315+
push: false,
316+
commit: false,
317+
dryRun: true,
318+
});
319+
console.log(`[${TOOL_NAME}] [dry-run] Would advance submodules for '${branch}':`);
320+
submoduleModule.printAdvanceResult(preview);
321+
} else {
322+
const advanceResult = submoduleModule.advance({
323+
target: worktreePath,
324+
push: false,
325+
commit: true,
326+
dryRun: false,
327+
});
328+
submoduleModule.printAdvanceResult(advanceResult);
329+
}
330+
} else {
331+
console.log(`[${TOOL_NAME}] --advance-submodules ignored: '${branch}' has no .gitmodules.`);
332+
}
333+
}
334+
308335
const finishArgs = [
309336
'--branch',
310337
branch,

0 commit comments

Comments
 (0)