From f73734aaeed06398198b929e5a2ac64d550958d0 Mon Sep 17 00:00:00 2001 From: NagyVikt Date: Thu, 7 May 2026 20:42:36 +0200 Subject: [PATCH] Make Guardex resume hints harder to misuse Guardex startup output was mixing status, worktree paths, and finish guidance into long human sentences. This commit keeps parser-stable status lines intact while giving agents a scannable branch/worktree/next block and making cleanup part of every printed finish command. Constraint: Existing consumers parse the Created branch and Worktree lines, so those lines stay unchanged. Rejected: Reword all startup output | too much compatibility risk for a small guidance fix Confidence: high Scope-risk: narrow Directive: Keep scripts and templates in parity when changing launcher output. Tested: bash -n scripts/agent-branch-start.sh scripts/codex-agent.sh templates/scripts/agent-branch-start.sh templates/scripts/codex-agent.sh Tested: git diff --check Tested: diff -u scripts/agent-branch-start.sh templates/scripts/agent-branch-start.sh Tested: diff -u scripts/codex-agent.sh templates/scripts/codex-agent.sh Tested: node --test test/branch.test.js test/sandbox.test.js Not-tested: Full npm test; known unrelated test/metadata.test.js cosign v4.1.1/v4.1.2 baseline drift. --- .../notes.md | 19 +++++++++++ scripts/agent-branch-start.sh | 32 +++++++++++++------ scripts/codex-agent.sh | 17 +++++++--- templates/scripts/agent-branch-start.sh | 32 +++++++++++++------ templates/scripts/codex-agent.sh | 17 +++++++--- test/branch.test.js | 7 ++++ test/sandbox.test.js | 8 +++-- 7 files changed, 101 insertions(+), 31 deletions(-) create mode 100644 openspec/changes/agent-codex-improve-guard-startup-output-design-2026-05-07-20-25/notes.md diff --git a/openspec/changes/agent-codex-improve-guard-startup-output-design-2026-05-07-20-25/notes.md b/openspec/changes/agent-codex-improve-guard-startup-output-design-2026-05-07-20-25/notes.md new file mode 100644 index 00000000..90b0b32f --- /dev/null +++ b/openspec/changes/agent-codex-improve-guard-startup-output-design-2026-05-07-20-25/notes.md @@ -0,0 +1,19 @@ +# Improve Guard startup output design + +## Problem + +Guardex startup and takeover hints were hard to scan: machine-readable status, +human next steps, and finish/cleanup guidance were mixed into long lines. + +## Scope + +- Keep existing parser-stable `[agent-branch-start] Created branch` and + `[agent-branch-start] Worktree` lines. +- Replace the human next-step block with aligned branch/worktree/next fields. +- Replace the long Codex takeover sentence with a scannable resume block. +- Include `--cleanup` in finish guidance. + +## Verification + +- `bash -n scripts/agent-branch-start.sh scripts/codex-agent.sh templates/scripts/agent-branch-start.sh templates/scripts/codex-agent.sh` +- `node --test test/branch.test.js test/sandbox.test.js test/metadata.test.js` diff --git a/scripts/agent-branch-start.sh b/scripts/agent-branch-start.sh index a9816423..5b961a70 100755 --- a/scripts/agent-branch-start.sh +++ b/scripts/agent-branch-start.sh @@ -389,11 +389,27 @@ print_reused_agent_worktree() { echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}" echo "[agent-branch-start] OpenSpec change: existing worktree" echo "[agent-branch-start] OpenSpec plan: existing worktree" - echo "[agent-branch-start] Next steps:" - echo " cd \"${worktree_path}\"" - echo " gx locks claim --branch \"${branch_name}\" " - echo " # continue work in this existing sandbox" - echo " gx branch finish --branch \"${branch_name}\" --via-pr --wait-for-merge" + print_agent_next_steps "$branch_name" "$worktree_path" "continue work in this existing sandbox" "$BASE_BRANCH" +} + +print_agent_next_steps() { + local branch_name="$1" + local worktree_path="$2" + local work_step="$3" + local base_branch="${4:-main}" + + if [[ -z "$base_branch" ]]; then + base_branch="main" + fi + + echo "[agent-branch-start] Ready:" + echo " branch: ${branch_name}" + echo " worktree: ${worktree_path}" + echo " next:" + echo " cd \"${worktree_path}\"" + echo " gx locks claim --branch \"${branch_name}\" " + echo " # ${work_step}" + echo " gx branch finish --branch \"${branch_name}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup" } has_local_changes() { @@ -842,8 +858,4 @@ if [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]]; then else echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}" fi -echo "[agent-branch-start] Next steps:" -echo " cd \"${worktree_path}\"" -echo " gx locks claim --branch \"${branch_name}\" " -echo " # implement + commit" -echo " gx branch finish --branch \"${branch_name}\" --base ${BASE_BRANCH} --via-pr --wait-for-merge" +print_agent_next_steps "$branch_name" "$worktree_path" "implement + commit" "$BASE_BRANCH" diff --git a/scripts/codex-agent.sh b/scripts/codex-agent.sh index d7dcc7ac..683bf362 100755 --- a/scripts/codex-agent.sh +++ b/scripts/codex-agent.sh @@ -735,9 +735,14 @@ print_takeover_prompt() { finish_cmd="gx branch finish --branch \"${branch}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup" - echo "[codex-agent] Takeover sandbox: ${wt}" - echo "[codex-agent] Takeover routing: $(describe_task_routing) (${TASK_ROUTING_REASON})" - 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}\`." + echo "[codex-agent] Resume this sandbox:" + echo " change: ${change_slug}" + echo " branch: ${branch}" + echo " worktree: ${wt}" + echo " spec: ${change_artifact}" + echo " routing: $(describe_task_routing) (${TASK_ROUTING_REASON})" + echo " rule: continue current state; do not create a new sandbox" + echo " finish: ${finish_cmd}" } sync_worktree_with_base() { @@ -1161,7 +1166,11 @@ else 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: gx branch finish --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge" + finish_base_branch="$(resolve_worktree_base_branch "$worktree_path")" + if [[ -z "$finish_base_branch" ]]; then + finish_base_branch="dev" + fi + echo "[codex-agent] If finished, merge with: gx branch finish --branch \"${worktree_branch}\" --base ${finish_base_branch} --via-pr --wait-for-merge --cleanup" echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\"" fi fi diff --git a/templates/scripts/agent-branch-start.sh b/templates/scripts/agent-branch-start.sh index a9816423..5b961a70 100755 --- a/templates/scripts/agent-branch-start.sh +++ b/templates/scripts/agent-branch-start.sh @@ -389,11 +389,27 @@ print_reused_agent_worktree() { echo "[agent-branch-start] OpenSpec tier: ${OPENSPEC_TIER}" echo "[agent-branch-start] OpenSpec change: existing worktree" echo "[agent-branch-start] OpenSpec plan: existing worktree" - echo "[agent-branch-start] Next steps:" - echo " cd \"${worktree_path}\"" - echo " gx locks claim --branch \"${branch_name}\" " - echo " # continue work in this existing sandbox" - echo " gx branch finish --branch \"${branch_name}\" --via-pr --wait-for-merge" + print_agent_next_steps "$branch_name" "$worktree_path" "continue work in this existing sandbox" "$BASE_BRANCH" +} + +print_agent_next_steps() { + local branch_name="$1" + local worktree_path="$2" + local work_step="$3" + local base_branch="${4:-main}" + + if [[ -z "$base_branch" ]]; then + base_branch="main" + fi + + echo "[agent-branch-start] Ready:" + echo " branch: ${branch_name}" + echo " worktree: ${worktree_path}" + echo " next:" + echo " cd \"${worktree_path}\"" + echo " gx locks claim --branch \"${branch_name}\" " + echo " # ${work_step}" + echo " gx branch finish --branch \"${branch_name}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup" } has_local_changes() { @@ -842,8 +858,4 @@ if [[ "$OPENSPEC_SKIP_PLAN" -eq 1 ]]; then else echo "[agent-branch-start] OpenSpec plan: openspec/plan/${openspec_plan_slug}" fi -echo "[agent-branch-start] Next steps:" -echo " cd \"${worktree_path}\"" -echo " gx locks claim --branch \"${branch_name}\" " -echo " # implement + commit" -echo " gx branch finish --branch \"${branch_name}\" --base ${BASE_BRANCH} --via-pr --wait-for-merge" +print_agent_next_steps "$branch_name" "$worktree_path" "implement + commit" "$BASE_BRANCH" diff --git a/templates/scripts/codex-agent.sh b/templates/scripts/codex-agent.sh index d7dcc7ac..683bf362 100755 --- a/templates/scripts/codex-agent.sh +++ b/templates/scripts/codex-agent.sh @@ -735,9 +735,14 @@ print_takeover_prompt() { finish_cmd="gx branch finish --branch \"${branch}\" --base ${base_branch} --via-pr --wait-for-merge --cleanup" - echo "[codex-agent] Takeover sandbox: ${wt}" - echo "[codex-agent] Takeover routing: $(describe_task_routing) (${TASK_ROUTING_REASON})" - 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}\`." + echo "[codex-agent] Resume this sandbox:" + echo " change: ${change_slug}" + echo " branch: ${branch}" + echo " worktree: ${wt}" + echo " spec: ${change_artifact}" + echo " routing: $(describe_task_routing) (${TASK_ROUTING_REASON})" + echo " rule: continue current state; do not create a new sandbox" + echo " finish: ${finish_cmd}" } sync_worktree_with_base() { @@ -1161,7 +1166,11 @@ else 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: gx branch finish --branch \"${worktree_branch}\" --base dev --via-pr --wait-for-merge" + finish_base_branch="$(resolve_worktree_base_branch "$worktree_path")" + if [[ -z "$finish_base_branch" ]]; then + finish_base_branch="dev" + fi + echo "[codex-agent] If finished, merge with: gx branch finish --branch \"${worktree_branch}\" --base ${finish_base_branch} --via-pr --wait-for-merge --cleanup" echo "[codex-agent] Cleanup on demand: gx cleanup --branch \"${worktree_branch}\"" fi fi diff --git a/test/branch.test.js b/test/branch.test.js index ba68ea1b..a8a2a2c0 100644 --- a/test/branch.test.js +++ b/test/branch.test.js @@ -439,6 +439,13 @@ test('agent-branch-start honors T1 notes-only OpenSpec scaffolding', () => { assert.equal(result.status, 0, result.stderr || result.stdout); assert.match(result.stdout, /\[agent-branch-start\] OpenSpec tier: T1/); assert.match(result.stdout, /\[agent-branch-start\] OpenSpec plan: skipped by tier T1/); + assert.match(result.stdout, /\[agent-branch-start\] Ready:/); + assert.match(result.stdout, / branch: agent\/codex\/simple-tighten-copy-/); + assert.match(result.stdout, / next:\n cd "/); + assert.match( + result.stdout, + /gx branch finish --branch "agent\/codex\/simple-tighten-copy-[^"]+" --base dev --via-pr --wait-for-merge --cleanup/, + ); const createdWorktree = extractCreatedWorktree(result.stdout); const changeSlug = extractOpenSpecChangeSlug(result.stdout); diff --git a/test/sandbox.test.js b/test/sandbox.test.js index 8f23a7d8..ddbbdae2 100644 --- a/test/sandbox.test.js +++ b/test/sandbox.test.js @@ -580,12 +580,14 @@ test('codex-agent prints a takeover prompt when the sandbox is kept after an inc 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, /\[codex-agent\] Resume this sandbox:/); + assert.match(combinedOutput, new RegExp(` worktree: ${escapeRegexLiteral(fs.readFileSync(cwdMarker, 'utf8').trim())}`)); assert.match( combinedOutput, - new RegExp(`\\[codex-agent\\] Takeover prompt: Continue \`${escapeRegexLiteral(changeSlug)}\` on branch \`${escapeRegexLiteral(launchedBranch)}\``), + new RegExp(` change: ${escapeRegexLiteral(changeSlug)}`), ); - assert.match(combinedOutput, /continue from the current state instead of creating a new sandbox/); + assert.match(combinedOutput, new RegExp(` branch: ${escapeRegexLiteral(launchedBranch)}`)); + assert.match(combinedOutput, /rule: continue current state; do not create a new sandbox/); assert.match( combinedOutput, new RegExp(`openspec/changes/${escapeRegexLiteral(changeSlug)}/tasks\\.md`),