diff --git a/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/.openspec.yaml b/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/.openspec.yaml new file mode 100644 index 0000000..81cd71f --- /dev/null +++ b/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-11 diff --git a/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/proposal.md b/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/proposal.md new file mode 100644 index 0000000..0cae9e7 --- /dev/null +++ b/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/proposal.md @@ -0,0 +1,17 @@ +## Why + +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." + +## What Changes + +- Add `--advance-submodules` / `--no-advance-submodules` to `gx branch finish`. Default off. +- 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. +- In `--dry-run` mode, the advance preview is printed without mutating anything. +- If the worktree has no `.gitmodules`, the flag is logged as ignored and finish proceeds normally. + +## Impact + +- Opt-in flag: no behavior change for non-monorepo lanes or for users who don't pass the flag. +- Uses the existing `submoduleModule.advance` plumbing (Phase B) — already battle-tested via PR #558. +- 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. +- Future: `--advance-submodules` could become the default for repos with `.gitmodules` once we're confident in the flow. diff --git a/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/specs/branch-finish-advance-submodules-flag/spec.md b/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/specs/branch-finish-advance-submodules-flag/spec.md new file mode 100644 index 0000000..95e921c --- /dev/null +++ b/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/specs/branch-finish-advance-submodules-flag/spec.md @@ -0,0 +1,9 @@ +## ADDED Requirements + +### Requirement: branch-finish-advance-submodules-flag behavior +The system SHALL enforce branch-finish-advance-submodules-flag behavior as defined by this change. + +#### Scenario: Baseline acceptance +- **WHEN** branch-finish-advance-submodules-flag behavior is exercised +- **THEN** the expected outcome is produced +- **AND** regressions are covered by tests. diff --git a/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/tasks.md b/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/tasks.md new file mode 100644 index 0000000..558b9fa --- /dev/null +++ b/openspec/changes/agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50/tasks.md @@ -0,0 +1,34 @@ +## Definition of Done + +This change is complete only when **all** of the following are true: + +- Every checkbox below is checked. +- The agent branch reaches `MERGED` state on `origin` and the PR URL + state are recorded in the completion handoff. +- 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. + +## Handoff + +- Handoff: change=`agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50`; branch=`agent//`; scope=`TODO`; action=`continue this sandbox or finish cleanup after a usage-limit/manual takeover`. +- Copy prompt: Continue `agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50` on branch `agent//`. 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// --base dev --via-pr --wait-for-merge --cleanup`. + +## 1. Specification + +- [ ] 1.1 Finalize proposal scope and acceptance criteria for `agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50`. +- [ ] 1.2 Define normative requirements in `specs/branch-finish-advance-submodules-flag/spec.md`. + +## 2. Implementation + +- [ ] 2.1 Implement scoped behavior changes. +- [ ] 2.2 Add/update focused regression coverage. + +## 3. Verification + +- [ ] 3.1 Run targeted project verification commands. +- [ ] 3.2 Run `openspec validate agent-claude-branch-finish-advance-submodules-flag-2026-05-11-13-50 --type change --strict`. +- [ ] 3.3 Run `openspec validate --specs`. + +## 4. Cleanup (mandatory; run before claiming completion) + +- [ ] 4.1 Run the cleanup pipeline: `gx branch finish --branch agent// --base dev --via-pr --wait-for-merge --cleanup`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation. +- [ ] 4.2 Record the PR URL and final merge state (`MERGED`) in the completion handoff. +- [ ] 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). diff --git a/src/cli/args.js b/src/cli/args.js index 07ce734..0a531b5 100644 --- a/src/cli/args.js +++ b/src/cli/args.js @@ -1002,6 +1002,7 @@ function parseFinishArgs(rawArgs, defaults = {}) { keepRemote: false, noAutoCommit: false, parentGitlinkCommit: defaults.parentGitlinkCommit ?? true, + advanceSubmodules: false, failFast: false, commitMessage: '', mergeMode: defaults.mergeMode || 'pr', @@ -1109,6 +1110,14 @@ function parseFinishArgs(rawArgs, defaults = {}) { options.failFast = true; continue; } + if (arg === '--advance-submodules') { + options.advanceSubmodules = true; + continue; + } + if (arg === '--no-advance-submodules') { + options.advanceSubmodules = false; + continue; + } throw new Error(`Unknown option: ${arg}`); } diff --git a/src/finish/index.js b/src/finish/index.js index fb48425..0f1635c 100644 --- a/src/finish/index.js +++ b/src/finish/index.js @@ -26,6 +26,7 @@ const { parseFinishArgs, parseSyncArgs, } = require('../cli/args'); +const submoduleModule = require('../submodule'); function claimLocksForAutoCommit(repoRoot, worktreePath, branch) { const changedFiles = uniquePreserveOrder([ @@ -305,6 +306,32 @@ function finish(rawArgs, defaults = {}) { console.log(`[${TOOL_NAME}] [dry-run] Would auto-commit pending changes on '${branch}'.`); } + if (options.advanceSubmodules && worktreePath) { + const gitmodulesPath = path.join(worktreePath, '.gitmodules'); + if (fs.existsSync(gitmodulesPath)) { + if (options.dryRun) { + const preview = submoduleModule.advance({ + target: worktreePath, + push: false, + commit: false, + dryRun: true, + }); + console.log(`[${TOOL_NAME}] [dry-run] Would advance submodules for '${branch}':`); + submoduleModule.printAdvanceResult(preview); + } else { + const advanceResult = submoduleModule.advance({ + target: worktreePath, + push: false, + commit: true, + dryRun: false, + }); + submoduleModule.printAdvanceResult(advanceResult); + } + } else { + console.log(`[${TOOL_NAME}] --advance-submodules ignored: '${branch}' has no .gitmodules.`); + } + } + const finishArgs = [ '--branch', branch,