* fix(#4728): stop presenting the retired Gemini CLI as a supported runtime
#1928 removed the Gemini CLI runtime after Google sunset it on 2026-06-18, and
updated the ENGLISH docs. The locale mirrors and the runtime-loaded workflow
prose were not updated in the same change, and no gate asserts the ABSENCE of a
retired runtime, so both drifted quietly for a year.
The finding that shaped this change: English is already correct. docs/
ARCHITECTURE.md, CONFIGURATION.md, USER-GUIDE.md, how-to/install-on-your-runtime.md
and CLI-TOOLS.md carry zero runtime-axis Gemini references; the only English hits
anywhere are a Gemini 2.5 Pro MODEL line, the GEMINI_API_KEY row, and prose that
correctly documents the retirement. So the docs half of this is translation lag,
not a content decision, and every locale edit here is parity with an existing
English line rather than new wording:
- install-on-your-runtime.md English has NO `### Gemini CLI` section -> deleted
- USER-GUIDE.md :843 "…, Antigravity CLI, Kilo)" -> substituted
- ARCHITECTURE.md English has NO Gemini CLI table row -> row deleted
- ARCHITECTURE.md :24 English holds `Kimi CLI` in that slot -> Kimi CLI
- context-monitor.md :3 "`AfterTool` for Antigravity CLI" -> substituted
- spike-and-sketch.md :93 "(Codex, Antigravity CLI, etc.)" -> substituted
- configure-model-profiles "Codex, OpenCode, Antigravity CLI, or Kilo" -> substituted
- COMMANDS.md English keeps only hyphen + Codex bullets -> colon bullet deleted
- FEATURES.md source docs/features/multi-runtime-support.md:10
lists no Gemini CLI -> name removed
ARCHITECTURE.md:24 is the clearest case for reading English rather than
substituting blind: Antigravity ALREADY appears later in that list, so replacing
Gemini CLI with Antigravity would have named it twice. English holds Kimi CLI
there, so that is what the locales get.
The largest single class was hand-duplicated boilerplate. A "Text mode" paragraph
repeated across 34 runtime-loaded workflow files ends "…required for non-Claude
runtimes (OpenAI Codex, Gemini CLI, etc.)". No lint enforces that sentence and no
script syncs it, so every copy was edited. These files are read by the agent at
runtime, so they steer behavior rather than only informing a reader — which is why
this class matters more than its word count suggests.
The slash-command-form section is restructured in all four languages to match
English, which had already dropped its colon-form bullet. That bullet claimed the
colon form is "Gemini CLI only", which was false on its own terms independent of
the retirement: `/gsd:…` is GSD's canonical AUTHORING token, rewritten per runtime
at install time, and NO runtime registers it — VALID_COMMAND_STYLES is
{slash-hyphen, shell-var} and 18 of 19 runtimes declare slash-hyphen. Substituting
the runtime name would have left the claim false with Antigravity's name in it, so
the claim is gone, matching English.
Two anchor regressions were caught and fixed while doing that. zh-CN lost its
explicit {#slash-command-forms-hyphen-vs-colon} anchor while its TOC still linked
it; the anchor is restored. ko-KR and pt-BR never had an explicit anchor and rely
on the slug generated from the heading text, so shortening the heading broke their
own TOC links; those links now point at the new slugs. English's heading lost its
anchor while its TOC still links the old one — that latent English bug is
deliberately NOT copied.
Preserved, because `gemini` is not one thing here and a blanket sweep breaks the
product: ~/.gemini/antigravity{,-ide,-cli} and ~/.gemini as their parent;
~/.gemini/config (#3738); GEMINI.md; hookEvents "gemini"; GEMINI_API_KEY in all
four locales; every gemini-* model id and the Gemini 2.5 Pro references in
ko-KR/pt-BR/zh-CN (ja-JP genuinely lacks that line — the locales have diverged, so
a uniform patch would be wrong); the hook-event dialect notes, which are
RE-ATTRIBUTED rather than deleted because Antigravity inherits that dialect;
reapply-patches.md:93's legacy-install note; host-integration-capability-matrix.md
:27 and :342, which correctly record the sunset and Antigravity's contract;
whats-new-1.7.0.md and FEATURES.md:3506, which document the retirement itself; and
the generated launcher preamble, which belongs to epic #4632 — zero
_GSD_SHIM_NAME lines appear in this diff.
Coverage: a #4728 block in tests/gemini-runtime-removed.test.cjs asserts the
retired name is gone from STRUCTURAL POSITIONS (a level-3 heading, a table row's
first cell, a runtime-example parenthetical) rather than asserting the string is
absent, which would be wrong. It pairs those with positive PRESERVE assertions
over the same files — Antigravity's heading, ~/.gemini/antigravity, GEMINI_API_KEY,
AfterTool — so a patch that deletes too much fails as loudly as one that deletes
too little. The model-axis test pins both the presence in three locales and the
absence in ja-JP, so a later uniform patch that "helpfully" adds it back fails.
The new docs/ reads tripped lint-docs-guard-registration for the first time in
this file, so the test is registered in scripts/docs-guard-registry.cjs.
Not covered here, by design: nothing above would catch a Gemini-as-runtime
reference appearing in a NEW file tomorrow. That is the repo-wide drift guard,
#4729, which must land last — written now it would red on the very references this
change removes.
Fixes #4728
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(#4728): fix four review blockers, including a vacuous test and my own duplicate
A full matrix run on 31f12d7943 FAILED with 3 real failures, and an isolated
adversarial review returned BLOCK on four blockers. All of it was correct.
1. I committed the exact error I claimed to have avoided. The commit message
boasted that ARCHITECTURE.md:24 proved the value of reading English rather
than substituting blind, because Antigravity already appeared later in that
list. Five hundred lines further down the SAME four files, my
`Gemini:` -> `Antigravity:` substitution produced TWO consecutive
`- Antigravity:` bullets, because an Antigravity bullet was already there.
English (ARCHITECTURE.md:827) merges them into one. Now merged in all four
locales, reusing each locale's existing words.
2. `--gemini` survived in the runtime-detection CLI flag list in all four
locale ARCHITECTURE.md files. English:817 holds `--kimi` in that slot and
already lists `--antigravity` later, so this is another place where
substituting Antigravity would have duplicated it. Now `--kimi`.
3. Two runtime-loaded workflow files still enumerated Gemini one line ABOVE the
line I had already corrected -- the "Adaptive (Recommended)" option in
settings.md:192 and new-project/steps/auto-mode-config.md:95.
4. THE NEW TEST WAS VACUOUS for two of its five files. It matched only
`non-Claude runtimes (` and `(e.g. `, and neither regex could reach the two
lines the change actually fixed: health.md:52 reads `non-Claude (Codex, ...)`
without the word "runtimes", and execute-phase.md:1028 has no parenthetical
at all. The reviewer proved it by re-introducing Gemini at both lines and
watching the assertion stay GREEN. That same blind spot is what hid finding 3.
Replaced with a case-sensitive `/\bGemini\b/` walk over every
`gsd-core/workflows/**/*.md`, which works because every LEGITIMATE gemini
reference in that tree is spelled differently and cannot match: Antigravity's
paths are lowercase with a slash (`~/.gemini/antigravity`), Google's model ids
are lowercase and hyphenated (`gemini-3.1-pro-preview`), and the env vars are
uppercase (`GEMINI_CONFIG_DIR`, `GEMINI_SESSION_ID`). A bare capitalised
`Gemini` there means the retired RUNTIME is being named. The walk asserts it
found at least 50 files so an empty walk cannot pass vacuously, and it now
covers the nested `new-project/steps/` directory where finding 3 lived.
Two allowlist entries, both by line CONTENT and both justified:
reapply-patches.md's `Legacy: ... pre-#1928` note, and settings-advanced.md's
`Known provider` menu. The second was escalated by the agent rather than
decided: Section 8 of that file says model policy is defined "independently"
of the runtime, so `(Claude / OpenAI / Gemini / Qwen)` is the PROVIDER axis --
the same axis as the lowercase model ids -- and must keep working.
Proven to fail, not just asserted: the predicate reports 0 offenders on the
real tree and exactly 2 on a /tmp copy with Gemini re-injected at
health.md:52 and execute-phase.md:1028.
Also from the review: a `| Gemini |` COLUMN survived in the locale FEATURES.md
comparison tables (English has none) -- removed from all three, with header,
separator and every body row kept aligned; two ENGLISH runtime-axis sites were
missed by my own parity standard (how-to/execute-a-phase.md:88 and
how-to/verify-and-ship.md:89, the latter doubly stale since #4716 retired the
Gemini reviewer lane); docs/USER-GUIDE.md:12 linked a dead anchor, which I had
found and deliberately left -- record-and-proceed on a known defect is exactly
what the rules forbid, so it is fixed; docs/COMMANDS.md:12 and all four mirrors
still claimed "the hyphen and colon forms are runtime-specific spellings" with
no colon form documented anywhere, so that false sentence is deleted; and ko-KR
had the installer rather than the user doing the targeting.
The other two matrix failures were the compact-content benchmark baseline, which
drifted because this PR changes byte counts, refreshed via the script's own
`--write` path rather than by hand; and this commit's emitted-drift-ack trailers.
Method note on the acks: the failing run measured growth against
origin/next@1110c3b4ee, which is the STALE LOCAL `next` ref -- gsd-test merges
into the local base branch, and this machine's `next` is seven commits behind
origin/next, which is checked out in the main worktree and so cannot be
fast-forwarded from here. The 32 trailers below are computed against the REAL
base (origin/next @ ca8d9d4459) by comparing each tracked file's blob size, which
is one more file than that run reported -- the extra is settings.md, grown again
by fix 3. docs-update.md and map-codebase.md are deliberately NOT acked: they
SHRANK, since there the fix deleted ", Gemini CLI" rather than substituting, and
acking a file no delta consumed is itself an error.
Refs #4728
Emitted-Drift-Ack-Growth: add-tests.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: add-todo.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: ai-integration-phase.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: check-todos.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: cleanup.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: complete-milestone.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: do.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: eval-review.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: execute-phase.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: execute-plan.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: health.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: import.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: inbox.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: manager.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: new-milestone.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: new-workspace.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: note.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: onboard.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: plant-seed.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: profile-user.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: quick.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: remove-workspace.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: secure-phase.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: settings.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: ship.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: smart-entry.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: ui-phase.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: ui-review.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: undo.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: update.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: validate-phase.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Emitted-Drift-Ack-Growth: verify-work.md — retiring the Gemini CLI runtime name; Antigravity is one byte longer
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore(#4728): add the changeset fragment
The PR body claimed one was present and it was not — caught by
scripts/changeset/lint.cjs reporting fail_missing_fragment, not by the
checklist, which is exactly why the lint exists.
Type Fixed: the diff is prose, and a docs-only fix uses Fixed since there is
no Documentation type.
Refs #4728
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
40 KiB
With --full flag: enables the complete quality pipeline — discussion + research + plan-checking + verification. One flag for everything.
With --validate flag: enables plan-checking (max 2 iterations) and post-execution verification only. Use when you want quality guarantees without discussion or research.
With --discuss flag: lightweight discussion phase before planning. Surfaces assumptions, clarifies gray areas, captures decisions in CONTEXT.md so the planner treats them as locked.
With --research flag: spawns a focused research agent before planning. Investigates implementation approaches, library options, and pitfalls. Use when you're unsure how to approach a task.
Granular flags are composable: --discuss --research --validate gives the same result as --full.
<required_reading> Read all files referenced by the invoking prompt's execution_context before starting. </required_reading>
<available_agent_types> Valid GSD subagent types (use exact names — do not fall back to 'general-purpose'):
- gsd-phase-researcher — Researches technical approaches for a phase
- gsd-planner — Creates detailed plans from phase scope
- gsd-plan-checker — Reviews plan quality before execution
- gsd-executor — Executes plan tasks, commits, creates SUMMARY.md
- gsd-verifier — Verifies phase completion, checks quality gates
- gsd-code-reviewer — Reviews source files for bugs, security issues, and code quality </available_agent_types>
Parse $ARGUMENTS for:
--fullflag → store$FULL_MODE=true,$DISCUSS_MODE=true,$RESEARCH_MODE=true,$VALIDATE_MODE=true--validateflag → store$VALIDATE_MODE=true--discussflag → store$DISCUSS_MODE=true--researchflag → store$RESEARCH_MODE=true- Remaining text → use as
$DESCRIPTIONif non-empty
After parsing, normalize: if $DISCUSS_MODE and $RESEARCH_MODE and $VALIDATE_MODE are all true, set $FULL_MODE=true. This ensures --discuss --research --validate is treated identically to --full.
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif _gsd_at "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd_run is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; case "$(gsd_run runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@opengsd/gsd-core"'*'}') GSD_IDENTITY_STATUS=ok;; esac; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @opengsd/gsd-core - it is either a different package or an @opengsd/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --raw --default "" 2>/dev/null || echo "")
If response_language is set: All user-facing output of this workflow — narration between tool calls, status updates, progress notes, findings, questions, prompts, and explanations — MUST be presented in {response_language}. Technical terms, code, file paths, and subagent prompts stay in English — only user-facing output is translated.
If $DESCRIPTION is empty after parsing, prompt user interactively:
Text mode (workflow.text_mode: true in config or --text flag): Set TEXT_MODE=true if --text is present in $ARGUMENTS OR text_mode from init JSON is true. When TEXT_MODE is active, replace every AskUserQuestion call with a plain-text numbered list and ask the user to type their choice number. This is required for non-Claude runtimes (OpenAI Codex, Antigravity, etc.) where AskUserQuestion is not available.
AskUserQuestion(
header: "Quick Task",
question: "What do you want to do?",
followUp: null
)
Store response as $DESCRIPTION.
If still empty, re-prompt: "Please provide a task description."
Display banner based on active flags:
If $FULL_MODE (all phases enabled — --full or all granular flags):
### GSD ► QUICK TASK (FULL)
◆ Discussion + research + plan checking + verification enabled
If $DISCUSS_MODE and $VALIDATE_MODE (no research):
### GSD ► QUICK TASK (DISCUSS + VALIDATE)
◆ Discussion + plan checking + verification enabled
If $DISCUSS_MODE and $RESEARCH_MODE (no validate):
### GSD ► QUICK TASK (DISCUSS + RESEARCH)
◆ Discussion + research enabled
If $RESEARCH_MODE and $VALIDATE_MODE (no discuss):
### GSD ► QUICK TASK (RESEARCH + VALIDATE)
◆ Research + plan checking + verification enabled
If $DISCUSS_MODE only:
### GSD ► QUICK TASK (DISCUSS)
◆ Discussion phase enabled — surfacing gray areas before planning
If $RESEARCH_MODE only:
### GSD ► QUICK TASK (RESEARCH)
◆ Research phase enabled — investigating approaches before planning
If $VALIDATE_MODE only:
### GSD ► QUICK TASK (VALIDATE)
◆ Plan checking + verification enabled
Step 2: Initialize
DISCUSS_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--discuss([[:space:]]|$) ]]; then DISCUSS_PARAM="--discuss"; fi
RESEARCH_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--research([[:space:]]|$) ]]; then RESEARCH_PARAM="--research"; fi
VALIDATE_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--validate([[:space:]]|$) ]]; then VALIDATE_PARAM="--validate"; fi
FULL_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--full([[:space:]]|$) ]]; then FULL_PARAM="--full"; fi
INIT=$(gsd_run query init.quick "$DESCRIPTION" $DISCUSS_PARAM $RESEARCH_PARAM $VALIDATE_PARAM $FULL_PARAM)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS_PLANNER=$(gsd_run query agent-skills gsd-planner)
AGENT_SKILLS_EXECUTOR=$(gsd_run query agent-skills gsd-executor)
AGENT_SKILLS_CHECKER=$(gsd_run query agent-skills gsd-plan-checker)
AGENT_SKILLS_VERIFIER=$(gsd_run query agent-skills gsd-verifier)
AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-phase-researcher)
Parse JSON for: planner_model, executor_model, checker_model, verifier_model, reviewer_model, researcher_model, commit_docs, branch_name, quick_id, slug, date, timestamp, quick_dir, task_dir, roadmap_exists, planning_exists, response_language.
init.quick does not emit dedicated state_path/project_path fields, so derive them from the already-absolute quick_dir (#2376 — files handed to a spawned subagent must resolve regardless of that subagent's own cwd):
STATE_PATH="$(dirname "${quick_dir}")/STATE.md"
PROJECT_PATH="$(dirname "${quick_dir}")/PROJECT.md"
USE_WORKTREES=$(gsd_run query config-get workflow.use_worktrees --raw 2>/dev/null || echo "true")
RUNTIME=$(gsd_run query config-get runtime --default claude --raw 2>/dev/null || echo "claude")
Resolve isolation now (#2584/#2652). Read @gsd-core/references/dispatch-isolation-gate.md
and run its Resolve ISOLATION, Single-agent dispatch sites, and Resolve the harness flag
blocks in order; they set ISOLATION/HARNESS_FLAG via query dispatch-isolation.
ISOLATION — not RUNTIME — gates every worktree decision below. Substitute {harnessFlag}
in Step 6's Agent() with $HARNESS_FLAG+comma when ISOLATION = "harness-worktree", else
empty. {harnessFlag}
is a template placeholder, not a shell variable.
If USE_WORKTREES is not "false", run a startup orphan sweep before spawning any executors. This reaps locked worktrees whose lock-owner process is dead, whose branch is merged into the default branch, and whose lock file mtime is older than 5 minutes. Running it at startup prevents accumulation of orphaned worktrees from prior sessions that exited without cleanup (#3707).
if [ "$USE_WORKTREES" != "false" ]; then
gsd_run query worktree.reap-orphans 2>/dev/null || true
fi
If the project uses git submodules, worktree isolation is unsafe only when the quick task touches a submodule path. The previous behavior unconditionally disabled worktree isolation whenever .gitmodules existed, which penalised every quick task in a submodule project even when the task was nowhere near a submodule. Parse submodule paths from .gitmodules so the executor can act on actual submodule paths rather than the mere file's existence:
# Parse submodule paths from .gitmodules once (empty if no .gitmodules).
# SUBMODULE_PATHS is a newline-separated list of repo-relative paths used as
# a fail-loud commit-time guard inside the quick-task executor — if the
# executor stages any path that falls inside SUBMODULE_PATHS, it must abort
# the commit and surface the conflict rather than silently corrupting the
# submodule state.
if [ -f .gitmodules ]; then
SUBMODULE_PATHS=$(git config --file .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null | awk '{print $2}')
else
SUBMODULE_PATHS=""
fi
Quick mode does not have a pre-declared files_modified list (the task is freeform), so use a fail-loud guard at commit time: when the executor stages files for the quick-task commit, if any staged path falls inside a SUBMODULE_PATHS entry, abort with a clear error explaining that worktree-isolated commits cannot safely span submodule boundaries — the user can re-run with workflow.use_worktrees=false to fall back to sequential execution on the main tree. If SUBMODULE_PATHS is empty (no .gitmodules in the repo), worktree isolation proceeds normally.
If roadmap_exists is false: Error — Quick mode requires an active project with ROADMAP.md. Run /gsd:new-project first.
Quick tasks can run mid-phase - validation only checks ROADMAP.md exists, not phase status.
Step 2.5: Handle quick-task branching
If branch_name is empty/null: Skip and continue on the current branch.
If branch_name is set: Check out the quick-task branch before any planning commits.
The new branch must fork off the project's default branch (origin/HEAD), not
off whatever HEAD happens to be checked out — otherwise consecutive quick tasks
compound on top of each other and stay unpushed (#2916). If $branch_name
already exists locally, reuse it as-is so resumed work is not rebased.
DEFAULT_BRANCH=$(gsd_run query git.base-branch 2>/dev/null \
|| git symbolic-ref --quiet --short refs/remotes/origin/HEAD 2>/dev/null | sed 's|^origin/||' \
|| echo main)
if git show-ref --verify --quiet "refs/heads/$branch_name"; then
git switch "$branch_name" \
|| { echo "ERROR: Could not switch to existing quick-task branch '$branch_name'." >&2; exit 1; }
else
# Fetch the default branch so origin/$DEFAULT_BRANCH is current. If the fetch
# fails (offline, no remote, auth failure) AND we have no local copy of
# origin/$DEFAULT_BRANCH to fall back on, abort — creating the branch off
# arbitrary HEAD is exactly the bug #2916 fixed.
if ! git fetch --quiet origin "$DEFAULT_BRANCH"; then
if ! git show-ref --verify --quiet "refs/remotes/origin/$DEFAULT_BRANCH"; then
echo "ERROR: Could not fetch origin/$DEFAULT_BRANCH and no local copy exists. Refusing to create '$branch_name' off the current HEAD (#2916). Resolve the remote/network issue and retry." >&2
exit 1
fi
echo "WARNING: git fetch origin $DEFAULT_BRANCH failed; using the local copy of origin/$DEFAULT_BRANCH as base." >&2
fi
if [ -n "$(git status --porcelain)" ]; then
echo "WARNING: Uncommitted changes present. Carrying them onto the new quick-task branch — they will be branched off origin/$DEFAULT_BRANCH (not the previous-task HEAD)."
else
# Best-effort: fast-forward the local default branch so subsequent local
# work sees the latest tip. Failure here is non-fatal because we always
# create the new branch directly from origin/$DEFAULT_BRANCH below.
git switch --quiet "$DEFAULT_BRANCH" 2>/dev/null \
&& git merge --ff-only --quiet "origin/$DEFAULT_BRANCH" 2>/dev/null \
|| true
fi
# Pin the new branch to origin/$DEFAULT_BRANCH so the start point is
# deterministic regardless of which branch we are currently on (#2916).
# On success HEAD is exactly at origin/$DEFAULT_BRANCH, so a post-creation
# merge-base / "ahead-of" guard would be unreachable — the explicit base
# argument here is the single source of correctness for #2916.
# --no-track: with the default branch.autoSetupMerge=true, checkout -b from a
# remote-tracking ref wires branch.<name>.merge to refs/heads/$DEFAULT_BRANCH
# (origin/master), so a GUI sync pushes quick-task commits straight onto
# origin/$DEFAULT_BRANCH, bypassing PR review (#2498).
git checkout -b "$branch_name" "origin/$DEFAULT_BRANCH" --no-track \
|| { echo "ERROR: Could not create '$branch_name' from origin/$DEFAULT_BRANCH (#2916)." >&2; exit 1; }
fi
All quick-task commits for this run stay on that branch. User handles merge/rebase afterward.
Step 3: Create task directory
mkdir -p "${task_dir}"
Step 4: Create quick task directory
Create the directory for this quick task:
QUICK_DIR="${task_dir}"
mkdir -p "$QUICK_DIR"
Report to user:
Creating quick task ${quick_id}: ${DESCRIPTION}
Directory: ${QUICK_DIR}
Store $QUICK_DIR for use in orchestration.
Step 4 ordering (#3894): Check whether workflow.research_before_questions is enabled in .planning/config.json (or the config from init context) — the same check /gsd:discuss-phase and /gsd:new-project already make. When enabled, execute the research-phase section BELOW BEFORE the discussion-phase section: a gray-area answer given without research is written to <quick_id>-CONTEXT.md as a locked decision downstream agents are told not to revisit, so the evidence must come first. When false or unset, keep the written order (discussion, then research) — behavior unchanged.
If section_manifest is null or "discussion-phase" is in its included list: read and execute gsd-core/workflows/quick/steps/discussion-phase.md. Otherwise skip — do not read the file.
If section_manifest is null or "research-phase" is in its included list: read and execute gsd-core/workflows/quick/steps/research-phase.md. Otherwise skip — do not read the file.
Step 5: Spawn planner (quick mode)
Capability gate:
PLAN_PRE_HOOKS_JSON=$(gsd_run loop render-hooks plan:pre --raw)
Contribution dispatch (#3778): read PLAN_PRE_HOOKS_JSON.activeHooks directly in context. In registry order, inject only active entries with kind == "contribution" and into == "planner" into each Quick planner prompt below, using fragment.inline verbatim plus resolved configValues. Do not paste rendered. Empty, inactive, incompatible, or non-planner entries inject nothing and do not error. Reuse this snapshot for revisions; do not render again.
If $VALIDATE_MODE: Use quick-full mode with stricter constraints.
If NOT $VALIDATE_MODE: Use standard quick mode.
Display: ◆ Spawning planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
Agent(
prompt="
<planning_context>
**Mode:** ${VALIDATE_MODE ? 'quick-full' : 'quick'}
**Directory:** ${QUICK_DIR}
**Description:** ${DESCRIPTION}
<required_reading>
- ${STATE_PATH} (Project State)
- ./CLAUDE.md or ./.claude/CLAUDE.md (if exists — follow project-specific guidelines)
${DISCUSS_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-CONTEXT.md (User decisions — locked, do not revisit)' : ''}
${RESEARCH_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-RESEARCH.md (Research findings — use to inform implementation choices)' : ''}
</required_reading>
${AGENT_SKILLS_PLANNER}
**Project skills:** Check .claude/skills/ or .agents/skills/ directory (if either exists) — read SKILL.md files, plans should account for project skill rules
{For each active entry in `PLAN_PRE_HOOKS_JSON` where `kind == "contribution"` and `into == "planner"` (in array order): inject the entry's `fragment.inline` verbatim here, plus its resolved `configValues` when the entry carries them. If no active planner contributions exist, omit this block entirely.}
</planning_context>
<constraints>
- Create a SINGLE plan with 1-3 focused tasks
- Quick tasks should be atomic and self-contained
- MUTABLE-SCOPE AUTHORITY (#3786): when concrete edit or verification scope depends on mutable external state (a merge index, PR/base diffs, the working tree), authorize scope ONLY from a live observation made at planning time — for conflict resolution that is the fresh merge index via `git diff --name-only --diff-filter=U` — or keep `files`/`verify` CONDITIONAL on that observation. Historical STATE.md entries, recovery notes, and cached PR/base diff paths may guide investigation only; they are never edit or verification authority, and a plan must not enumerate them as authorized files "pending replacement".
${RESEARCH_MODE ? '- Research findings are available — use them to inform library/pattern choices' : '- No research phase'}
${VALIDATE_MODE ? '- Target ~40% context usage (structured for verification)' : '- Target ~30% context usage (simple, focused)'}
${VALIDATE_MODE ? '- MUST generate `must_haves` in plan frontmatter (truths, artifacts, key_links)' : ''}
${VALIDATE_MODE ? '- Each task MUST have `files`, `action`, `verify`, `done` fields' : ''}
</constraints>
<output>
Write plan to: ${QUICK_DIR}/${quick_id}-PLAN.md
Return: ## PLANNING COMPLETE with plan path
</output>
",
subagent_type="gsd-planner",
model="{planner_model}",
description="Quick plan: ${DESCRIPTION}"
)
ORCHESTRATOR RULE — CODEX RUNTIME: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
After planner returns:
- Verify plan exists at
${QUICK_DIR}/${quick_id}-PLAN.md - Extract plan count (typically 1 for quick tasks)
- Report: "Plan created: ${QUICK_DIR}/${quick_id}-PLAN.md"
If plan not found, error: "Planner failed to create ${quick_id}-PLAN.md"
If section_manifest is null or "plan-checker-loop" is in its included list: read and execute gsd-core/workflows/quick/steps/plan-checker-loop.md. Otherwise skip — do not read the file.
If section_manifest is null or "worktree-pre-dispatch-commit" is in its included list: read and execute gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md. Otherwise skip — do not read the file.
Step 6: Spawn executor
Auto-degrade to sequential if HEAD has diverged from the worktree fork base (#1941, mirrors
execute-phase's #683/#1369 guard). Claude Code's isolation="worktree" forks new worktrees from
origin/HEAD, not the live local HEAD. If a prior quick task in this session (or the Step 5.6
pre-dispatch plan commit above) advanced local HEAD without an intervening git push,
origin/HEAD stays pinned to a stale ancestor and the executor's worktree_branch_check guard
halts with a base-mismatch fatal — potentially many commits behind, not just one. Run this check
immediately before capturing EXPECTED_BASE so it reflects the most current local state.
if [ "$ISOLATION" = "harness-worktree" ] && [ "${USE_WORKTREES:-true}" != "false" ]; then
_QUICK_SHOULD_DEGRADE=$(gsd_run query worktree.base-check --mode "$ISOLATION" --pick shouldDegrade 2>/dev/null || true)
if [ "$_QUICK_SHOULD_DEGRADE" = "true" ]; then
_QUICK_DEGRADE_MSG=$(gsd_run query worktree.base-check --mode "$ISOLATION" --pick message 2>/dev/null || true)
[ -n "$_QUICK_DEGRADE_MSG" ] && printf '%s\n' "$_QUICK_DEGRADE_MSG" >&2
echo "⚠ [#1941] Worktree fork base diverged from orchestrator HEAD — auto-degrading to sequential mode for this quick task to avoid a base-mismatch halt." >&2
USE_WORKTREES=false
ISOLATION=none
fi
fi
# Re-resolve (and, as a side effect, re-persist) now that the base-check
# auto-degrade above may have changed $ISOLATION since the Step 2 gate's
# `dispatch-isolation` call (#3045). That first call recorded the NATURALLY
# resolved mode into the run-scoped sentinel the isolation guard hooks read
# (hooks/gsd-agent-isolation-guard.js, hooks/gsd-cursor-subagent-start.js via
# hooks/lib/isolation-sentinel.js). The degrade above is decided HERE, in
# shell — the resolver cannot see it — so without this the sentinel still
# asserts `harness-worktree` while the dispatch below correctly omits the
# harness flag, and the guard denies the dispatch with exit 2. `--force-isolation`
# pushes the FINAL, shell-computed value through that SAME single write path
# (`none` also clears the stored harnessFlag, since none applies to sequential
# dispatch). Best-effort: a write failure here must never fail the task — the
# guards' own sentinel-absent fallback is safe, just less precise.
gsd_run query dispatch-isolation --raw --force-isolation "$ISOLATION" >/dev/null 2>&1 || true
Capture current HEAD before spawning (used for worktree branch check):
EXPECTED_BASE=$(git rev-parse HEAD)
if [ "$ISOLATION" = "harness-worktree" ]; then # keyed on ISOLATION like every other dispatch-coupled branch (#2652)
# BSD/macOS mktemp only randomizes XXXXXX when it is the final path component, so make a
# suffixless temp then append the extension — portable across BSD + GNU (#1520).
QUICK_WORKTREE_MANIFEST=$(mktemp "${TMPDIR:-/tmp}/gsd-quick-worktree-XXXXXX") && mv "$QUICK_WORKTREE_MANIFEST" "${QUICK_WORKTREE_MANIFEST}.json" && QUICK_WORKTREE_MANIFEST="${QUICK_WORKTREE_MANIFEST}.json" || exit 1
printf '{"worktrees":[]}\n' > "$QUICK_WORKTREE_MANIFEST"
export QUICK_WORKTREE_MANIFEST
fi
Spawn gsd-executor with plan reference:
Agent(
prompt="
Execute quick task ${quick_id}.
${ISOLATION === "harness-worktree" ? `
<worktree_branch_check>
ORCHESTRATOR build-time embed (NOT a sub-agent runtime step): before this dispatch, read \`gsd-core/references/worktree-branch-check.md\`, substitute \`{EXPECTED_BASE}\` with the base SHA captured above (${EXPECTED_BASE}), substitute \`{EXPECTED_BASE_ALTERNATE}\` with \`${QUICK_PLAN_PARENT}\` when it differs from \`${EXPECTED_BASE}\` (otherwise empty), and replace this note with that fragment's \`<worktree_branch_check>\` block so the dispatched prompt carries the runnable guard verbatim — do not pass this instruction through in its place.
</worktree_branch_check>
FIRST ACTION after the worktree branch check: ensure the quick PLAN.md exists at a worktree-rooted relative path before any Read/Edit/Write path can be primed. If \`${QUICK_DIR}/${quick_id}-PLAN.md\` is absent, materialize it from the shared git object store:
\`\`\`bash
QUICK_PLAN_COMMIT="${QUICK_PLAN_COMMIT}"
QUICK_PLAN_PATH="${QUICK_DIR}/${quick_id}-PLAN.md"
if [ ! -f "$QUICK_PLAN_PATH" ]; then
mkdir -p "$(dirname "$QUICK_PLAN_PATH")"
git show "${QUICK_PLAN_COMMIT}:${QUICK_PLAN_PATH}" > "$QUICK_PLAN_PATH" || {
echo "FATAL: unable to materialize quick plan from ${QUICK_PLAN_COMMIT}:${QUICK_PLAN_PATH}; refusing to continue." >&2
exit 42
}
fi
\`\`\`
` : ''}
<required_reading>
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan)
- ${STATE_PATH} (Project state)
- ./CLAUDE.md or ./.claude/CLAUDE.md (Project instructions, if exists)
- .claude/skills/ or .agents/skills/ (Project skills, if either exists — list skills, read SKILL.md for each, follow relevant rules during implementation)
</required_reading>
${AGENT_SKILLS_EXECUTOR}
<submodule_commit_guard>
SUBMODULE_PATHS for this project: ${SUBMODULE_PATHS}
If SUBMODULE_PATHS is non-empty, you MUST run this fail-loud guard immediately
before EVERY git commit you create during this quick task (after \`git add\`,
before \`git commit\`). Quick mode does not have a pre-declared files_modified
list, so the guard runs at commit time:
\`\`\`bash
SUBMODULE_PATHS=\"${SUBMODULE_PATHS}\"
if [ -n \"\$SUBMODULE_PATHS\" ]; then
STAGED=\$(git diff --cached --name-only)
for sm_raw in \$SUBMODULE_PATHS; do
sm=\"\${sm_raw#./}\"
sm=\"\${sm%/}\"
[ -z \"\$sm\" ] && continue
for f_raw in \$STAGED; do
f=\"\${f_raw#./}\"
f=\"\${f%/}\"
case \"\$f\" in
\"\$sm\"|\"\$sm\"/*)
echo \"ABORT: staged path \$f_raw falls inside submodule \$sm — worktree-isolated commits cannot safely span submodule boundaries. Re-run with workflow.use_worktrees=false.\" >&2
exit 1 ;;
esac
done
done
fi
\`\`\`
If the guard aborts, do NOT attempt the commit, do NOT remove the staged files,
and do NOT continue subsequent tasks. Surface the abort message in your
SUMMARY.md and stop — the user must rerun with worktrees disabled.
</submodule_commit_guard>
<constraints>
- Execute all tasks in the plan
- Commit each task atomically (code changes only)
- Run the <submodule_commit_guard> bash block before every \`git commit\` if SUBMODULE_PATHS is non-empty
- Create summary at: ${QUICK_DIR}/${quick_id}-SUMMARY.md with `status: complete` in SUMMARY frontmatter (required so the audit-open milestone-close scanner recognises the task as done, not [unknown])
- Do NOT commit docs artifacts (SUMMARY.md, STATE.md, PLAN.md) — the orchestrator handles the docs commit in Step 8
- Do NOT update ROADMAP.md (quick tasks are separate from planned phases)
</constraints>
",
subagent_type="gsd-executor",
model="{executor_model}",
{harnessFlag}
description="Execute: ${DESCRIPTION}"
)
ORCHESTRATOR RULE — CODEX RUNTIME: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
If the executor ran isolated (ISOLATION = "harness-worktree" at dispatch), append its returned {agent_id, worktree_path, branch, expected_base, allowed_bases} metadata to QUICK_WORKTREE_MANIFEST before cleanup. Set expected_base to ${EXPECTED_BASE} and allowed_bases to ["${EXPECTED_BASE}", "${QUICK_PLAN_PARENT}"] with duplicates removed. If any required field is unavailable, stop and ask for recovery; do not discover global worktrees.
After executor returns:
-
Worktree cleanup: If the executor ran isolated (
ISOLATION = "harness-worktree"at dispatch), merge the worktree branch back and clean up:QUICK_WORKTREE_MANIFEST=${QUICK_WORKTREE_MANIFEST:-$WAVE_WORKTREE_MANIFEST} [ -n "${QUICK_WORKTREE_MANIFEST:-}" ] && [ -f "$QUICK_WORKTREE_MANIFEST" ] || { echo "BLOCKED: missing QUICK_WORKTREE_MANIFEST; refusing broad worktree cleanup (#3384)." >&2 exit 1 } # Prefer the bounded cleanup helper. It verifies branch identity, expected # base, deletion diffs, merge result, and worktree removal before branch # deletion. If it blocks, resolve the reported manifest entry and rerun. # Fail closed: SDK refusal (safety guard #3174/#3384) must surface — do not swallow exit 1. gsd_run query worktree.cleanup-wave --manifest "$QUICK_WORKTREE_MANIFEST" || exit 1If
ISOLATIONwas not"harness-worktree"at dispatch (including a #1941 base-check degrade — that is this file's degrade; #2649 is thediagnose-issues.md/execute-plan.mdone), skip this step.ISOLATED-RUN RECOVERY — FAIL SAFE (#1292): When an isolated (worktree) run is rejected — the user declines to merge it, the orchestrator surfaces recovery guidance for a blocked/halted plan, or the run over-reached the requested scope — the worktree-isolation contract MUST hold through recovery. Do NOT propose continuing on
main/the primary checkout as the default or recommended recovery path. Default to a safe halt and offer: (a) re-attempt in a fresh, narrowly-scoped worktree, or (b) inspect or discard the rejected worktree without merging. Any path that edits the primary checkout requires an explicit, clearly-labeled confirmation from the user first — editingmaindirectly is never the proposed or default option for a run the user configured to be isolated. -
Verify summary exists at
${QUICK_DIR}/${quick_id}-SUMMARY.md -
Extract commit hash from executor output
-
Report completion status
Known Claude Code bug (classifyHandoffIfNeeded): If executor reports "failed" with error classifyHandoffIfNeeded is not defined, this is a Claude Code runtime bug — not a real failure. Check if summary file exists and git log shows commits. If so, treat as successful.
If summary not found, error: "Executor failed to create ${quick_id}-SUMMARY.md"
Note: For quick tasks producing multiple plans (rare), spawn executors in parallel waves per execute-phase patterns.
Step 6.25: Code review (auto)
Skip this step entirely if $FULL_MODE is false.
Capability gate:
EXECUTE_POST_HOOKS_JSON=$(gsd_run loop render-hooks execute:post --raw)
Generic step dispatch (#3606): dispatch every kind == "step" hook from EXECUTE_POST_HOOKS_JSON per @gsd-core/references/loop-hook-dispatch.md (skip silently when none); each step is advisory and best-effort. The code-review specialization below is one such hook, not a replacement for the generic dispatch.
Resolve active step hooks from EXECUTE_POST_HOOKS_JSON where kind == "step" and ref.skill == "code-review".
If no active code-review step hook exists, skip with message "Code review skipped (code-review capability inactive)" — after dispatching any other active step hooks above — and proceed.
Scope files from executor's commits:
# Find the diff base: last commit before quick task started
# Use git log to find commits referencing the quick task id, then take the parent of the oldest
QUICK_COMMITS=$(git log --oneline --format="%H" --grep="${quick_id}" 2>/dev/null)
if [ -n "$QUICK_COMMITS" ]; then
DIFF_BASE=$(echo "$QUICK_COMMITS" | tail -1)^
# Verify parent exists (guard against first commit in repo)
git rev-parse "${DIFF_BASE}" >/dev/null 2>&1 || DIFF_BASE=$(echo "$QUICK_COMMITS" | tail -1)
else
# No commits found for this quick task — skip review
DIFF_BASE=""
fi
if [ -n "$DIFF_BASE" ]; then
# #4466: bound the tip at the quick task's own last commit, not HEAD --
# QUICK_COMMITS is already the complete, newest-first list of this task's
# commits, so its first line is the correct tip. An unbounded `..HEAD`
# picks up any later commit landed on the same tree in the window between
# this task's commits and this review step (worktree merge-back, a shared
# tree, another session) and folds it into this task's own review scope.
QUICK_TIP=$(echo "$QUICK_COMMITS" | head -1)
CHANGED_FILES=$(git diff --name-only "${DIFF_BASE}..${QUICK_TIP}" -- . ':!.planning' 2>/dev/null | tr '\n' ' ')
else
CHANGED_FILES=""
fi
If CHANGED_FILES is empty, skip with "No source files changed — skipping code review."
Invoke review:
Agent(
prompt="Review these files for bugs, security issues, and code quality.
Files: ${CHANGED_FILES}
Output: ${QUICK_DIR}/${quick_id}-REVIEW.md
Depth: quick",
subagent_type="gsd-code-reviewer",
model="{reviewer_model}"
)
ORCHESTRATOR RULE — CODEX RUNTIME: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
If review produces findings, display advisory message. Error handling: Failures are non-blocking — catch and proceed.
If section_manifest is null or "quick-verification" is in its included list: read and execute gsd-core/workflows/quick/steps/quick-verification.md. Otherwise skip — do not read the file.
Step 7: Update STATE.md
Update STATE.md with quick task completion record.
7a. Check if "Quick Tasks Completed" section exists:
Read STATE.md and check for ### Quick Tasks Completed section.
7b. If section doesn't exist, create it:
Insert after ### Blockers/Concerns section:
If $VALIDATE_MODE:
### Quick Tasks Completed
| # | Description | Date | Commit | Status | Directory |
|---|-------------|------|--------|--------|-----------|
If NOT $VALIDATE_MODE:
### Quick Tasks Completed
| # | Description | Date | Commit | Directory |
|---|-------------|------|--------|-----------|
Note: If the table already exists in a legacy (pre-registry) column format, first run gsd_run quick-tasks-migrate — the maintainer-decided repair path (#3730) that rewrites the table onto the canonical schema, losslessly bucketing unmapped columns into Description. It is a silent no-op when the table is already canonical or the section is absent, so running it before the first append of every quick task migrates exactly once and never prompts otherwise. After migration, use the canonical column format below.
7c. Append new row to table:
Use date from init:
If $VALIDATE_MODE (or table has Status column):
| ${quick_id} | ${DESCRIPTION} | ${date} | ${commit_hash} | ${VERIFICATION_STATUS} | [${quick_id}-${slug}](./quick/${quick_id}-${slug}/) |
If NOT $VALIDATE_MODE (and table has no Status column):
| ${quick_id} | ${DESCRIPTION} | ${date} | ${commit_hash} | [${quick_id}-${slug}](./quick/${quick_id}-${slug}/) |
For a schema-safe append outside this workflow (e.g. from fast.md, which has neither a quick id nor a task directory), gsd_run quick-tasks-append --task <text> performs an equivalent-shape write via the shared, schema-backed appendQuickTaskRow helper (#2133, ADR-2143 §3/§7) — the # cell is a positional ordinal and Directory reads —, since no id/directory was supplied. A caller that DOES have a real ${quick_id} and task directory can pass --quick-id <id> --slug <slug> (or --directory <link> directly) to get the byte-identical row this step renders above (#3356).
7d. Update "Last activity" line:
Use date from init:
Last activity: ${date} - Completed quick task ${quick_id}: ${DESCRIPTION}
Use Edit tool to make these changes atomically
Step 8: Final commit and completion
Stage and commit quick task artifacts. This step MUST always run — even if the executor already committed some files (e.g. when running without worktree isolation). The gsd_run query commit command handles already-committed files gracefully.
Build file list:
${QUICK_DIR}/${quick_id}-PLAN.md${QUICK_DIR}/${quick_id}-SUMMARY.md.planning/STATE.md- If
$DISCUSS_MODEand context file exists:${QUICK_DIR}/${quick_id}-CONTEXT.md - If
$RESEARCH_MODEand research file exists:${QUICK_DIR}/${quick_id}-RESEARCH.md - If
$VALIDATE_MODEand verification file exists:${QUICK_DIR}/${quick_id}-VERIFICATION.md - If
${QUICK_DIR}/${quick_id}-deferred-items.mdexists:${QUICK_DIR}/${quick_id}-deferred-items.md
# Explicitly stage all artifacts before commit — PLAN.md may be untracked
# if the executor ran without worktree isolation and committed docs early
# Filter .planning/ files from staging if commit_docs is disabled (#1783)
COMMIT_DOCS=$(gsd_run query config-get commit_docs --raw 2>/dev/null || echo "true")
if [ "$COMMIT_DOCS" = "false" ]; then
file_list_filtered=$(echo "${file_list}" | tr ' ' '\n' | grep -v '^\.planning/' | tr '\n' ' ')
git add ${file_list_filtered} 2>/dev/null
else
git add ${file_list} 2>/dev/null
fi
gsd_run query commit "docs(quick-${quick_id}): ${DESCRIPTION}" --files ${file_list}
Get final commit hash:
commit_hash=$(git rev-parse --short HEAD)
Display completion output:
If $VALIDATE_MODE:
---
GSD > QUICK TASK COMPLETE (VALIDATED)
Quick Task ${quick_id}: ${DESCRIPTION}
${RESEARCH_MODE ? 'Research: ' + QUICK_DIR + '/' + quick_id + '-RESEARCH.md' : ''}
Summary: ${QUICK_DIR}/${quick_id}-SUMMARY.md
Verification: ${QUICK_DIR}/${quick_id}-VERIFICATION.md (${VERIFICATION_STATUS})
Commit: ${commit_hash}
---
Ready for next task: /gsd:quick ${GSD_WS}
If NOT $VALIDATE_MODE:
---
GSD > QUICK TASK COMPLETE
Quick Task ${quick_id}: ${DESCRIPTION}
${RESEARCH_MODE ? 'Research: ' + QUICK_DIR + '/' + quick_id + '-RESEARCH.md' : ''}
Summary: ${QUICK_DIR}/${quick_id}-SUMMARY.md
Commit: ${commit_hash}
---
Ready for next task: /gsd:quick ${GSD_WS}
<success_criteria>
- ROADMAP.md validation passes
- User provides task description
--full,--validate,--discuss, and--researchflags parsed from arguments when present--fullsets all booleans ($FULL_MODE,$DISCUSS_MODE,$RESEARCH_MODE,$VALIDATE_MODE)- Slug generated (lowercase, hyphens, max 40 chars)
- Quick ID generated (YYMMDD-xxx format, 2s Base36 precision)
- Directory created at
.planning/quick/YYMMDD-xxx-slug/ - (--discuss) Gray areas identified and presented, decisions captured in
${quick_id}-CONTEXT.md - (--research) Research agent spawned,
${quick_id}-RESEARCH.mdcreated ${quick_id}-PLAN.mdcreated by planner (honors CONTEXT.md decisions when --discuss, uses RESEARCH.md findings when --research)- (--validate) Plan checker validates plan, revision loop capped at 2
${quick_id}-SUMMARY.mdcreated by executor- (--validate)
${quick_id}-VERIFICATION.mdcreated by verifier - STATE.md updated with quick task row (Status column when --validate)
- Artifacts committed </success_criteria>