diff --git a/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/.openspec.yaml b/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/.openspec.yaml new file mode 100644 index 0000000..4b8c565 --- /dev/null +++ b/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-04-21 diff --git a/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/proposal.md b/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/proposal.md new file mode 100644 index 0000000..67afd87 --- /dev/null +++ b/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/proposal.md @@ -0,0 +1,25 @@ +## Why + +- When `codex-agent` keeps a sandbox after the first lane exits early, the next + agent only gets generic merge/cleanup hints and has to reconstruct the exact + takeover flow by hand. +- Fresh OpenSpec change workspaces do not scaffold a copy-paste takeover note, + so usage-limit handoffs are inconsistent and easy to botch. + +## What Changes + +- Make `codex-agent` print a concrete takeover prompt with the existing branch, + sandbox path, OpenSpec artifact, and finish command whenever auto-finish does + not complete and the worktree stays alive. +- Teach `init-change-workspace.sh` to scaffold structured `Handoff:` and + `Copy prompt:` lines, and resolve the cleanup command base branch from repo + metadata instead of hardcoding `dev`. +- Add regression coverage for both the launcher handoff output and the scaffold + defaults. + +## Impact + +- Affects `scripts/codex-agent.sh`, `scripts/openspec/init-change-workspace.sh`, + and `test/install.test.js`. +- Keeps the existing finish pipeline intact while making quota-hit/manual + takeovers copy-pasteable instead of reconstructive. diff --git a/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/specs/improve-usage-limit-takeover-handoff/spec.md b/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/specs/improve-usage-limit-takeover-handoff/spec.md new file mode 100644 index 0000000..aaf4c90 --- /dev/null +++ b/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/specs/improve-usage-limit-takeover-handoff/spec.md @@ -0,0 +1,32 @@ +## ADDED Requirements + +### Requirement: `codex-agent` SHALL emit a takeover prompt when a sandbox is kept +`codex-agent` SHALL print a copy-paste takeover prompt whenever it leaves a +branch/worktree alive for manual follow-up. + +#### Scenario: incomplete run keeps the sandbox alive +- **GIVEN** `codex-agent` keeps the sandbox because auto-finish did not complete +- **WHEN** it reports the kept worktree +- **THEN** it SHALL print a takeover prompt that references the existing branch + and sandbox path +- **AND** the prompt SHALL tell the next agent to continue from the current + state instead of creating a new sandbox +- **AND** the prompt SHALL include the cleanup/finish command for + `agent-branch-finish.sh`. + +### Requirement: OpenSpec change scaffolds SHALL include structured takeover copy +OpenSpec change workspaces SHALL scaffold a structured handoff line plus a +copy-paste takeover prompt for usage-limit/manual handoffs. + +#### Scenario: standard change workspace scaffold +- **WHEN** `scripts/openspec/init-change-workspace.sh` creates a non-minimal + change workspace +- **THEN** `tasks.md` SHALL include a `Handoff:` line and a `Copy prompt:` line +- **AND** the generated cleanup command SHALL resolve the branch base from repo + metadata when available. + +#### Scenario: minimal notes workspace scaffold +- **WHEN** `scripts/openspec/init-change-workspace.sh` runs in minimal mode +- **THEN** `notes.md` SHALL include the same `Handoff:` and `Copy prompt:` flow +- **AND** the generated cleanup command SHALL resolve the branch base from repo + metadata when available. diff --git a/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/tasks.md b/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/tasks.md new file mode 100644 index 0000000..9701d8b --- /dev/null +++ b/openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/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-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30`; branch=`agent/codex/improve-usage-limit-takeover-handoff-2026-04-21-23-30`; scope=`OpenSpec change docs, codex-agent takeover output, change scaffold defaults, install regressions`; action=`emit a copy-paste takeover prompt and keep usage-limit/manual rescue flow inside the existing sandbox`. +- Copy prompt: Continue `agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30` on branch `agent/codex/improve-usage-limit-takeover-handoff-2026-04-21-23-30`. Work inside the existing sandbox, review `openspec/changes/agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30/tasks.md`, continue from the current state instead of creating a new sandbox, and when the work is done run `bash scripts/agent-branch-finish.sh --branch "agent/codex/improve-usage-limit-takeover-handoff-2026-04-21-23-30" --base main --via-pr --wait-for-merge --cleanup`. + +## 1. Specification + +- [x] 1.1 Finalize proposal scope and acceptance criteria for `agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30`. +- [x] 1.2 Define normative requirements in `specs/improve-usage-limit-takeover-handoff/spec.md`. + +## 2. Implementation + +- [x] 2.1 Implement scoped behavior changes in `scripts/codex-agent.sh` and `scripts/openspec/init-change-workspace.sh`, then mirror the same changes into the managed templates. +- [x] 2.2 Add/update focused regression coverage in `test/install.test.js` for the launcher takeover prompt and the scaffolded handoff/copy prompt defaults. + +## 3. Verification + +- [x] 3.1 Run targeted project verification commands. Result: `node --test --test-name-pattern "(codex-agent prints a takeover prompt when the sandbox is kept after an incomplete run|OpenSpec change workspace scaffold creates proposal/tasks/spec defaults|OpenSpec change workspace scaffold supports minimal T1 notes mode|critical runtime helper scripts stay in sync with templates)" test/install.test.js test/metadata.test.js` passed `4/4`; `bash -n scripts/codex-agent.sh scripts/openspec/init-change-workspace.sh templates/scripts/codex-agent.sh templates/scripts/openspec/init-change-workspace.sh` passed. +- [x] 3.2 Run `openspec validate agent-codex-improve-usage-limit-takeover-handoff-2026-04-21-23-30 --type change --strict`. Result: passed. +- [x] 3.3 Run `openspec validate --specs`. Result: `No items found to validate.` + +## 4. Cleanup (mandatory; run before claiming completion) + +- [ ] 4.1 Run the cleanup pipeline: `bash scripts/agent-branch-finish.sh --branch agent/codex/improve-usage-limit-takeover-handoff-2026-04-21-23-30 --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/scripts/codex-agent.sh b/scripts/codex-agent.sh index 8b509dd..1829d92 100755 --- a/scripts/codex-agent.sh +++ b/scripts/codex-agent.sh @@ -510,6 +510,31 @@ resolve_worktree_base_branch() { printf 'dev' } +print_takeover_prompt() { + local wt="$1" + local branch="$2" + local base_branch change_slug change_artifact finish_cmd + + base_branch="$(resolve_worktree_base_branch "$wt")" + if [[ -z "$base_branch" ]]; then + base_branch="dev" + fi + + change_slug="$(resolve_openspec_change_slug "$branch")" + change_artifact="openspec/changes/${change_slug}/tasks.md" + if [[ ! -f "${wt}/${change_artifact}" ]]; then + change_artifact="openspec/changes/${change_slug}/notes.md" + fi + if [[ ! -f "${wt}/${change_artifact}" ]]; then + change_artifact="openspec/changes/${change_slug}/" + fi + + finish_cmd="bash scripts/agent-branch-finish.sh --branch \"${branch}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup" + + echo "[codex-agent] Takeover sandbox: ${wt}" + echo "[codex-agent] Takeover prompt: Continue \`${change_slug}\` on branch \`${branch}\`. Work inside \`${wt}\`, review \`${change_artifact}\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`${finish_cmd}\`." +} + sync_worktree_with_base() { local wt="$1" if ! has_origin_remote; then @@ -952,6 +977,7 @@ else if [[ "$auto_finish_completed" -eq 1 ]]; then echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\"" else + print_takeover_prompt "$worktree_path" "$worktree_branch" echo "[codex-agent] If finished, merge with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge" echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\"" fi diff --git a/scripts/openspec/init-change-workspace.sh b/scripts/openspec/init-change-workspace.sh index 07f26c7..6f0930d 100755 --- a/scripts/openspec/init-change-workspace.sh +++ b/scripts/openspec/init-change-workspace.sh @@ -21,9 +21,27 @@ if [[ "$CAPABILITY_SLUG" =~ [^a-z0-9-] ]]; then exit 1 fi +resolve_base_branch() { + local branch="$1" + local base_branch="" + + if [[ -n "$branch" ]] && [[ "$branch" != "agent//" ]]; then + base_branch="$(git config --get "branch.${branch}.guardexBase" || true)" + fi + if [[ -z "$base_branch" ]]; then + base_branch="$(git config --get multiagent.baseBranch || true)" + fi + if [[ -z "$base_branch" ]]; then + base_branch="dev" + fi + + printf '%s' "$base_branch" +} + CHANGE_DIR="openspec/changes/${CHANGE_SLUG}" SPEC_DIR="${CHANGE_DIR}/specs/${CAPABILITY_SLUG}" TODAY="$(date -u +%Y-%m-%d)" +BASE_BRANCH="$(resolve_base_branch "$AGENT_BRANCH")" MINIMAL_RAW="${GUARDEX_OPENSPEC_MINIMAL:-0}" case "$(printf '%s' "$MINIMAL_RAW" | tr '[:upper:]' '[:lower:]')" in @@ -53,9 +71,14 @@ Branch: \`${AGENT_BRANCH}\` Describe the change in a sentence or two. Commit message is the spec of record. +## Handoff + +- Handoff: change=\`${CHANGE_SLUG}\`; branch=\`${AGENT_BRANCH}\`; scope=\`TODO\`; action=\`continue this sandbox or finish cleanup after a usage-limit/manual takeover\`. +- Copy prompt: Continue \`${CHANGE_SLUG}\` on branch \`${AGENT_BRANCH}\`. Work inside the existing sandbox, review \`openspec/changes/${CHANGE_SLUG}/notes.md\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`bash scripts/agent-branch-finish.sh --branch "${AGENT_BRANCH}" --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup\`. + ## Cleanup -- [ ] Run: \`bash scripts/agent-branch-finish.sh --branch ${AGENT_BRANCH} --base dev --via-pr --wait-for-merge --cleanup\` +- [ ] Run: \`bash scripts/agent-branch-finish.sh --branch "${AGENT_BRANCH}" --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup\` - [ ] Record PR URL + \`MERGED\` state in the completion handoff. - [ ] Confirm sandbox worktree is gone (\`git worktree list\`, \`git branch -a\`). NOTESEOF @@ -91,6 +114,11 @@ This change is complete only when **all** of the following are true: - 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=\`${CHANGE_SLUG}\`; branch=\`${AGENT_BRANCH}\`; scope=\`TODO\`; action=\`continue this sandbox or finish cleanup after a usage-limit/manual takeover\`. +- Copy prompt: Continue \`${CHANGE_SLUG}\` on branch \`${AGENT_BRANCH}\`. Work inside the existing sandbox, review \`openspec/changes/${CHANGE_SLUG}/tasks.md\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`bash scripts/agent-branch-finish.sh --branch "${AGENT_BRANCH}" --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup\`. + ## 1. Specification - [ ] 1.1 Finalize proposal scope and acceptance criteria for \`${CHANGE_SLUG}\`. @@ -109,7 +137,7 @@ This change is complete only when **all** of the following are true: ## 4. Cleanup (mandatory; run before claiming completion) -- [ ] 4.1 Run the cleanup pipeline: \`bash scripts/agent-branch-finish.sh --branch ${AGENT_BRANCH} --base dev --via-pr --wait-for-merge --cleanup\`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation. +- [ ] 4.1 Run the cleanup pipeline: \`bash scripts/agent-branch-finish.sh --branch "${AGENT_BRANCH}" --base ${BASE_BRANCH} --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). TASKSEOF diff --git a/templates/scripts/codex-agent.sh b/templates/scripts/codex-agent.sh index 8b509dd..1829d92 100755 --- a/templates/scripts/codex-agent.sh +++ b/templates/scripts/codex-agent.sh @@ -510,6 +510,31 @@ resolve_worktree_base_branch() { printf 'dev' } +print_takeover_prompt() { + local wt="$1" + local branch="$2" + local base_branch change_slug change_artifact finish_cmd + + base_branch="$(resolve_worktree_base_branch "$wt")" + if [[ -z "$base_branch" ]]; then + base_branch="dev" + fi + + change_slug="$(resolve_openspec_change_slug "$branch")" + change_artifact="openspec/changes/${change_slug}/tasks.md" + if [[ ! -f "${wt}/${change_artifact}" ]]; then + change_artifact="openspec/changes/${change_slug}/notes.md" + fi + if [[ ! -f "${wt}/${change_artifact}" ]]; then + change_artifact="openspec/changes/${change_slug}/" + fi + + finish_cmd="bash scripts/agent-branch-finish.sh --branch \"${branch}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup" + + echo "[codex-agent] Takeover sandbox: ${wt}" + echo "[codex-agent] Takeover prompt: Continue \`${change_slug}\` on branch \`${branch}\`. Work inside \`${wt}\`, review \`${change_artifact}\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`${finish_cmd}\`." +} + sync_worktree_with_base() { local wt="$1" if ! has_origin_remote; then @@ -952,6 +977,7 @@ else if [[ "$auto_finish_completed" -eq 1 ]]; then echo "[codex-agent] Branch kept intentionally. Cleanup on demand: gx cleanup --branch \"${worktree_branch}\"" else + print_takeover_prompt "$worktree_path" "$worktree_branch" echo "[codex-agent] If finished, merge with: bash scripts/agent-branch-finish.sh --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge" echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\"" fi diff --git a/templates/scripts/openspec/init-change-workspace.sh b/templates/scripts/openspec/init-change-workspace.sh index 07f26c7..6f0930d 100755 --- a/templates/scripts/openspec/init-change-workspace.sh +++ b/templates/scripts/openspec/init-change-workspace.sh @@ -21,9 +21,27 @@ if [[ "$CAPABILITY_SLUG" =~ [^a-z0-9-] ]]; then exit 1 fi +resolve_base_branch() { + local branch="$1" + local base_branch="" + + if [[ -n "$branch" ]] && [[ "$branch" != "agent//" ]]; then + base_branch="$(git config --get "branch.${branch}.guardexBase" || true)" + fi + if [[ -z "$base_branch" ]]; then + base_branch="$(git config --get multiagent.baseBranch || true)" + fi + if [[ -z "$base_branch" ]]; then + base_branch="dev" + fi + + printf '%s' "$base_branch" +} + CHANGE_DIR="openspec/changes/${CHANGE_SLUG}" SPEC_DIR="${CHANGE_DIR}/specs/${CAPABILITY_SLUG}" TODAY="$(date -u +%Y-%m-%d)" +BASE_BRANCH="$(resolve_base_branch "$AGENT_BRANCH")" MINIMAL_RAW="${GUARDEX_OPENSPEC_MINIMAL:-0}" case "$(printf '%s' "$MINIMAL_RAW" | tr '[:upper:]' '[:lower:]')" in @@ -53,9 +71,14 @@ Branch: \`${AGENT_BRANCH}\` Describe the change in a sentence or two. Commit message is the spec of record. +## Handoff + +- Handoff: change=\`${CHANGE_SLUG}\`; branch=\`${AGENT_BRANCH}\`; scope=\`TODO\`; action=\`continue this sandbox or finish cleanup after a usage-limit/manual takeover\`. +- Copy prompt: Continue \`${CHANGE_SLUG}\` on branch \`${AGENT_BRANCH}\`. Work inside the existing sandbox, review \`openspec/changes/${CHANGE_SLUG}/notes.md\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`bash scripts/agent-branch-finish.sh --branch "${AGENT_BRANCH}" --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup\`. + ## Cleanup -- [ ] Run: \`bash scripts/agent-branch-finish.sh --branch ${AGENT_BRANCH} --base dev --via-pr --wait-for-merge --cleanup\` +- [ ] Run: \`bash scripts/agent-branch-finish.sh --branch "${AGENT_BRANCH}" --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup\` - [ ] Record PR URL + \`MERGED\` state in the completion handoff. - [ ] Confirm sandbox worktree is gone (\`git worktree list\`, \`git branch -a\`). NOTESEOF @@ -91,6 +114,11 @@ This change is complete only when **all** of the following are true: - 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=\`${CHANGE_SLUG}\`; branch=\`${AGENT_BRANCH}\`; scope=\`TODO\`; action=\`continue this sandbox or finish cleanup after a usage-limit/manual takeover\`. +- Copy prompt: Continue \`${CHANGE_SLUG}\` on branch \`${AGENT_BRANCH}\`. Work inside the existing sandbox, review \`openspec/changes/${CHANGE_SLUG}/tasks.md\`, continue from the current state instead of creating a new sandbox, and when the work is done run \`bash scripts/agent-branch-finish.sh --branch "${AGENT_BRANCH}" --base ${BASE_BRANCH} --via-pr --wait-for-merge --cleanup\`. + ## 1. Specification - [ ] 1.1 Finalize proposal scope and acceptance criteria for \`${CHANGE_SLUG}\`. @@ -109,7 +137,7 @@ This change is complete only when **all** of the following are true: ## 4. Cleanup (mandatory; run before claiming completion) -- [ ] 4.1 Run the cleanup pipeline: \`bash scripts/agent-branch-finish.sh --branch ${AGENT_BRANCH} --base dev --via-pr --wait-for-merge --cleanup\`. This handles commit -> push -> PR create -> merge wait -> worktree prune in one invocation. +- [ ] 4.1 Run the cleanup pipeline: \`bash scripts/agent-branch-finish.sh --branch "${AGENT_BRANCH}" --base ${BASE_BRANCH} --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). TASKSEOF diff --git a/test/install.test.js b/test/install.test.js index 89c7c5f..65cd18f 100644 --- a/test/install.test.js +++ b/test/install.test.js @@ -3498,6 +3498,63 @@ exit 1 assert.match(launchedArgs, /--model gpt-5\.4-mini/); }); +test('codex-agent prints a takeover prompt when the sandbox is kept after an incomplete run', () => { + const repoDir = initRepo(); + seedCommit(repoDir); + + let result = runNode(['setup', '--target', repoDir, '--no-global-install'], repoDir); + assert.equal(result.status, 0, result.stderr || result.stdout); + result = runCmd('git', ['add', '.'], repoDir); + assert.equal(result.status, 0, result.stderr); + result = runCmd('git', ['commit', '-m', 'apply gx setup'], repoDir, { + ALLOW_COMMIT_ON_PROTECTED_BRANCH: '1', + }); + assert.equal(result.status, 0, result.stderr || result.stdout); + + const fakeCodexBin = fs.mkdtempSync(path.join(os.tmpdir(), 'guardex-fake-codex-takeover-')); + const fakeCodexPath = path.join(fakeCodexBin, 'codex'); + fs.writeFileSync( + fakeCodexPath, + '#!/usr/bin/env bash\n' + + 'pwd > "${GUARDEX_TEST_CODEX_CWD}"\n' + + 'echo "partial" > codex-partial.txt\n' + + 'exit 42\n', + 'utf8', + ); + fs.chmodSync(fakeCodexPath, 0o755); + + const cwdMarker = path.join(repoDir, '.codex-agent-cwd-takeover'); + const launch = runCmd( + 'bash', + ['scripts/codex-agent.sh', 'usage-limit-task', 'planner', 'dev'], + repoDir, + { + PATH: `${fakeCodexBin}:${process.env.PATH}`, + GUARDEX_TEST_CODEX_CWD: cwdMarker, + }, + ); + assert.equal(launch.status, 42, launch.stderr || launch.stdout); + + const combinedOutput = `${launch.stdout}\n${launch.stderr}`; + const launchedBranch = extractCreatedBranch(launch.stdout); + const changeSlug = launchedBranch.replace(/\//g, '-'); + assert.match(combinedOutput, /\[codex-agent\] Sandbox worktree kept:/); + assert.match(combinedOutput, new RegExp(`\\[codex-agent\\] Takeover sandbox: ${escapeRegexLiteral(fs.readFileSync(cwdMarker, 'utf8').trim())}`)); + assert.match( + combinedOutput, + new RegExp(`\\[codex-agent\\] Takeover prompt: Continue \`${escapeRegexLiteral(changeSlug)}\` on branch \`${escapeRegexLiteral(launchedBranch)}\``), + ); + assert.match(combinedOutput, /continue from the current state instead of creating a new sandbox/); + assert.match( + combinedOutput, + new RegExp(`openspec/changes/${escapeRegexLiteral(changeSlug)}/tasks\\.md`), + ); + assert.match( + combinedOutput, + new RegExp(`agent-branch-finish\\.sh --branch "${escapeRegexLiteral(launchedBranch)}" --base dev --via-pr --wait-for-merge --cleanup`), + ); +}); + test('codex-agent keeps the sandbox when base branch advances without a mergeable remote context', () => { const repoDir = initRepo(); seedCommit(repoDir); @@ -4176,6 +4233,9 @@ test('OpenSpec change workspace scaffold creates proposal/tasks/spec defaults', const tasksContent = fs.readFileSync(path.join(changeDir, 'tasks.md'), 'utf8'); assert.match(tasksContent, /## Definition of Done/); assert.match(tasksContent, /append a `BLOCKED:` line under section 4/); + assert.match(tasksContent, /## Handoff/); + assert.match(tasksContent, /Handoff: change=`change-workspace-smoke`/); + assert.match(tasksContent, /Copy prompt: Continue `change-workspace-smoke` on branch `agent\/\/`/); assert.match(tasksContent, /## 4\. Cleanup \(mandatory; run before claiming completion\)/); assert.match(tasksContent, /Run the cleanup pipeline:/); assert.match(tasksContent, /Record the PR URL and final merge state \(`MERGED`\)/); @@ -4187,6 +4247,8 @@ test('OpenSpec change workspace scaffold supports minimal T1 notes mode', () => const setupResult = runNode(['setup', '--target', repoDir, '--no-global-install'], repoDir); assert.equal(setupResult.status, 0, setupResult.stderr || setupResult.stdout); + let result = runCmd('git', ['config', 'multiagent.baseBranch', 'main'], repoDir); + assert.equal(result.status, 0, result.stderr || result.stdout); const changeSlug = 'change-workspace-minimal'; const capabilitySlug = 'runtime-migration'; @@ -4209,6 +4271,10 @@ test('OpenSpec change workspace scaffold supports minimal T1 notes mode', () => assert.match(notesContent, /minimal \/ T1/); assert.match(notesContent, new RegExp(agentBranch.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); assert.match(notesContent, /Commit message is the spec of record/); + assert.match(notesContent, /## Handoff/); + assert.match(notesContent, /Handoff: change=`change-workspace-minimal`/); + assert.match(notesContent, /Copy prompt: Continue `change-workspace-minimal` on branch `agent\/codex\/minimal-change`/); + assert.match(notesContent, /--base main --via-pr --wait-for-merge --cleanup/); assert.match(notesContent, /Record PR URL \+ `MERGED` state/); });