Files
msd-core/gsd-core/workflows/docs-update.md
Tom Boucher eb49ff98df fix(#4728): stop presenting the retired Gemini CLI as a supported runtime (#4743)
* 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>
2026-09-14 16:49:52 -04:00

47 KiB
Raw Blame History

Generate, update, and verify all project documentation — both canonical doc types and existing hand-written docs. The orchestrator detects the project's doc structure, assembles a work manifest tracking every item, dispatches parallel doc-writer and doc-verifier agents across waves, reviews existing docs for accuracy, identifies documentation gaps, and fixes inaccuracies via a bounded fix loop. All state is persisted in a work manifest so no work item is lost between steps. Output: Complete, structure-aware documentation verified against the live codebase.

<available_agent_types> Valid GSD subagent types (use exact names — do not fall back to 'general-purpose'):

  • gsd-doc-writer — Writes and updates project documentation files
  • gsd-doc-verifier — Verifies factual claims in docs against the live codebase </available_agent_types>

Compact Content Gate. Read and follow gsd-core/references/compact-content-gate.md now — it states the workflow.compact_content check and the resolution rule this spine defers to. When it directs a Read, read gsd-core/workflows/docs-update/detail/elaboration.md in full before continuing past this point; its content elaborates on three steps below (sequential_generation, fix_loop, verify_only_report).

Load docs-update context:
_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
INIT=$(gsd_run query docs-init)
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
AGENT_SKILLS=$(gsd_run query agent-skills gsd-doc-writer)
# #2994: dedicated init.docs-update call — additive to docs-init above, carries
# only the section_manifest field (gates dispatch_monorepo_packages).
INIT_DOCS_UPDATE=$(gsd_run query init.docs-update)
if [[ "$INIT_DOCS_UPDATE" == @file:* ]]; then INIT_DOCS_UPDATE=$(cat "${INIT_DOCS_UPDATE#@file:}"); fi
DOC_VERIFIER_MODEL=$(gsd_run query resolve-model gsd-doc-verifier --raw)

Extract from init JSON:

  • doc_writer_model — model string for the doc-writer spawns (never hardcode a model name); the doc-verifier spawn resolves its own DOC_VERIFIER_MODEL
  • commit_docs — whether to commit generated files when done
  • existing_docs — array of {path, has_gsd_marker} objects for existing Markdown files
  • project_type — object with boolean signals: has_package_json, has_api_routes, has_cli_bin, is_open_source, has_deploy_config, is_monorepo, has_tests
  • doc_tooling — object with booleans: docusaurus, vitepress, mkdocs, storybook
  • monorepo_workspaces — array of workspace glob patterns (empty if not a monorepo)
  • section_manifest — parsed from INIT_DOCS_UPDATE (not INIT); gates the dispatch-monorepo-packages section below
  • project_root — absolute path to the project root
  • response_language — if set, present all user-facing output of this workflow in that language — narration between tool calls, status updates, progress notes, findings, questions, prompts, and explanations; technical terms, code, file paths, and subagent prompts stay in English
Map the `project_type` boolean signals from the init JSON to a primary type label and collect conditional doc signals.

Primary type classification (first match wins):

Condition primary_type
is_monorepo is true "monorepo"
has_cli_bin is true AND has_api_routes is false "cli-tool"
has_api_routes is true AND is_open_source is false "saas"
is_open_source is true AND has_api_routes is false "open-source-library"
(none of the above) "generic"

Conditional doc signals (D-02 union rule — check independently after primary classification):

After determining primary_type, check each signal independently regardless of the primary type. A CLI tool that is also open source with API routes still gets all three conditional docs.

Signal Conditional Doc
has_api_routes is true Queue API.md
is_open_source is true Queue CONTRIBUTING.md
has_deploy_config is true Queue DEPLOYMENT.md

Present the classification result:

Project type: {primary_type}
Conditional docs queued: {list or "none"}
Assemble the complete doc queue from always-on docs plus conditional docs from classify_project.

Always-on docs (queued for every project, no exceptions):

  1. README
  2. ARCHITECTURE
  3. GETTING-STARTED
  4. DEVELOPMENT
  5. TESTING
  6. CONFIGURATION

Conditional docs (add only if signal matched in classify_project):

  • API (if has_api_routes)
  • CONTRIBUTING (if is_open_source)
  • DEPLOYMENT (if has_deploy_config)

IMPORTANT: CHANGELOG.md is NEVER queued. The doc queue is built exclusively from the 9 known doc types listed above. Do not derive the queue from existing_docs directly — existing_docs is only used in the next step to determine create vs update mode.

Doc queue limit: Maximum 9 docs. Always-on (6) + up to 3 conditional = at most 9.

CONTRIBUTING.md confirmation (new file only):

If CONTRIBUTING.md is in the conditional queue AND does NOT appear in the existing_docs array from init JSON:

  1. If --force is present in $ARGUMENTS: skip this check, include CONTRIBUTING.md in the queue.

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. 2. Otherwise, use AskUserQuestion to confirm:

AskUserQuestion([{
  question: "This project appears to be open source (LICENSE file detected). CONTRIBUTING.md does not exist yet. Would you like to create one?",
  header: "Contributing",
  multiSelect: false,
  options: [
    { label: "Yes, create it", description: "Generate CONTRIBUTING.md with project guidelines" },
    { label: "No, skip it", description: "This project does not need a CONTRIBUTING.md" }
  ]
}])

If the user selects "No, skip it": remove CONTRIBUTING.md from the doc queue. If CONTRIBUTING.md already exists in existing_docs: skip this prompt entirely, include it for update.

Existing non-canonical docs (review queue):

After assembling the canonical doc queue above, scan the existing_docs array from init JSON for files that do NOT match any canonical path in the queue (neither primary nor fallback path from the resolve_modes table). These are hand-written docs like docs/api/endpoint-map.md or docs/frontend/pages/not-found.md.

For each non-canonical existing doc found:

  • Add to a separate review_queue
  • These will be passed to gsd-doc-verifier in the verify_docs step for accuracy checking
  • If inaccuracies are found, they will be dispatched to gsd-doc-writer in fix mode for surgical corrections

If non-canonical docs are found, display them in the queue presentation:

Existing docs queued for accuracy review:
  - docs/api/endpoint-map.md (hand-written)
  - docs/api/README.md (hand-written)
  - docs/frontend/pages/not-found.md (hand-written)

If none found, omit this section from the queue presentation.

Documentation gap detection (missing non-canonical docs):

After assembling the canonical and review queues, analyze the codebase to identify areas that should have documentation but don't. This ensures the command creates complete project documentation, not just the 9 canonical types.

  1. Scan the codebase for undocumented areas:

    • Use Glob/Grep to discover significant source directories (e.g., src/components/, src/pages/, src/services/, src/api/, lib/, routes/)
    • Compare against existing docs: for each major source directory, check if corresponding documentation exists in the docs tree
    • Look at the project's existing doc structure for patterns — if the project has docs/frontend/components/, docs/services/, etc., these indicate the project's documentation conventions
  2. Identify gaps based on project conventions:

    • If the project has a docs/ directory with grouped subdirectories, each source module area that has a corresponding docs subdirectory but is missing documentation files represents a gap
    • If the project has frontend components/pages but no component docs, flag this
    • If the project has service modules but no service docs, flag this
    • Skip areas that are already covered by canonical docs (e.g., don't flag missing API docs if docs/API.md is already in the canonical queue)
  3. Present discovered gaps to the user:

AskUserQuestion([{
  question: "Found {N} documentation gaps in the codebase. Which should be created?",
  header: "Doc gaps",
  multiSelect: true,
  options: [
    { label: "{area}", description: "{why it needs docs — e.g., '5 components in src/components/ with no docs'}" },
    ...up to 4 options (group related gaps if more than 4)
  ]
}])
  1. For each gap the user selects:
    • Add to the generation queue with mode = "create"
    • Set the output path to match the project's existing doc directory structure
    • The gsd-doc-writer will receive a doc_assignment with type: "custom" and a description of what to document, using the project's source files as content discovery targets

If no gaps are detected, omit this section entirely.

Present the assembled queue to the user before proceeding:

Present the mode resolution table from resolve_modes (shown above), followed by:

{If non-canonical docs found, show as a table:}

Existing docs queued for accuracy review:

| Path | Type |
|------|------|
| {path} | hand-written |
| ... | ... |

CHANGELOG.md: excluded (out of scope)

The mode resolution table IS the queue presentation — it shows every doc with its resolved path, mode, and source. Do not duplicate the list in a separate format.

Then confirm with AskUserQuestion:

AskUserQuestion([{
  question: "Doc queue assembled ({N} docs). Proceed with generation?",
  header: "Doc queue",
  multiSelect: false,
  options: [
    { label: "Proceed", description: "Generate all {N} docs in the queue" },
    { label: "Abort", description: "Cancel doc generation" }
  ]
}])

If the user selects "Abort": exit the workflow. Otherwise continue to resolve_modes.

For each doc in the assembled queue, determine whether to create (new file) or update (existing file).

Doc type to canonical path mapping (defaults):

Type Default Path Fallback Path
readme README.md —
architecture docs/ARCHITECTURE.md ARCHITECTURE.md
getting_started docs/GETTING-STARTED.md GETTING-STARTED.md
development docs/DEVELOPMENT.md DEVELOPMENT.md
testing docs/TESTING.md TESTING.md
api docs/API.md API.md
configuration docs/CONFIGURATION.md CONFIGURATION.md
deployment docs/DEPLOYMENT.md DEPLOYMENT.md
contributing CONTRIBUTING.md —

Structure-aware path resolution:

Before applying the default path table, inspect the project's existing docs directory structure to detect whether the project uses grouped subdirectories or flat files. This determines how ALL new docs are placed.

Step 1: Detect the project's docs organization pattern.

List subdirectories under docs/ from the existing_docs paths. If the project has 2+ subdirectories (e.g., docs/architecture/, docs/api/, docs/guides/, docs/frontend/), the project uses a grouped structure. If docs are only flat files directly in docs/ (e.g., docs/ARCHITECTURE.md), it uses a flat structure.

Step 2: Resolve paths based on the detected pattern.

If GROUPED structure detected:

Every doc type MUST be placed in an appropriate subdirectory — no doc should be left flat in docs/ when the project organizes into groups. Use the following resolution logic:

Type Subdirectory resolution (in priority order)
architecture existing docs/architecture/ → create docs/architecture/ if not present
getting_started existing docs/guides/ → existing docs/getting-started/ → create docs/guides/
development existing docs/guides/ → existing docs/development/ → create docs/guides/
testing existing docs/testing/ → existing docs/guides/ → create docs/testing/
api existing docs/api/ → create docs/api/ if not present
configuration existing docs/configuration/ → existing docs/guides/ → create docs/configuration/
deployment existing docs/deployment/ → existing docs/guides/ → create docs/deployment/

For each type, check the resolution chain left-to-right. Use the first existing subdirectory. If none exist, create the rightmost option.

The filename within the subdirectory should be contextual — e.g., docs/guides/getting-started.md, docs/architecture/overview.md, docs/api/reference.md — rather than docs/architecture/ARCHITECTURE.md. Match the naming style of existing files in that subdirectory (lowercase-kebab, UPPERCASE, etc.).

If FLAT structure detected (or no docs/ directory):

Use the default path table above as-is (e.g., docs/ARCHITECTURE.md, docs/TESTING.md).

Step 3: Store each resolved path and create directories.

For each doc type, store the resolved path as resolved_path. Then create all necessary directories:

mkdir -p {each unique directory from resolved paths}

Mode resolution logic:

For each doc type in the queue:

  1. Check if the resolved_path appears in the existing_docs array from the init JSON
  2. If not found at resolved path, check the default and fallback paths from the table
  3. If found at any path: mode = "update" — use the Read tool to load the current file content (will be passed as existing_content in the doc_assignment block). Use the found path as the output path (do not move existing docs).
  4. If not found: mode = "create" — no existing content to load. Use the resolved_path.

Ensure docs/ directory exists: Before proceeding to the next step, create the docs/ directory and any resolved subdirectories if they do not exist:

mkdir -p docs/

Output a mode resolution table:

Present a table showing the resolved path, mode, and source for every doc in the queue:

Mode resolution:

| Doc | Resolved Path | Mode | Source |
|-----|---------------|------|--------|
| readme | README.md | update | found at README.md |
| architecture | docs/architecture/overview.md | create | new directory |
| getting_started | docs/guides/getting-started.md | update | found, hand-written |
| development | docs/guides/development.md | create | matched docs/guides/ |
| contributing | docs/guides/contributing.md | create | matched docs/guides/ |
| configuration | docs/guides/configuration.md | create | matched docs/guides/ |
| api | docs/api/reference.md | create | new directory |
| deployment | docs/guides/deployment.md | update | found, hand-written |

This table MUST be shown to the user — it is the primary confirmation of where files will be written and whether existing files will be updated. It appears as part of the queue presentation BEFORE the AskUserQuestion confirmation.

Track the resolved mode and file path for each queued doc. For update-mode docs, store the loaded file content — it will be passed to the agent in the next steps.

CRITICAL: Persist the work manifest.

After resolve_modes completes, write ALL work items to .planning/tmp/docs-work-manifest.json. This is the single source of truth for every subsequent step — the orchestrator MUST read this file at each step instead of relying on memory.

mkdir -p .planning/tmp

Write the manifest using the Write tool:

{
  "canonical_queue": [
    {
      "type": "readme",
      "resolved_path": "README.md",
      "mode": "create|update|supplement",
      "preservation_mode": null,
      "wave": 1,
      "status": "pending"
    }
  ],
  "review_queue": [
    {
      "path": "docs/frontend/components/button.md",
      "type": "hand-written",
      "status": "pending_review"
    }
  ],
  "gap_queue": [
    {
      "description": "Frontend components in src/components/",
      "output_path": "docs/frontend/components/overview.md",
      "status": "pending"
    }
  ],
  "created_at": "{ISO timestamp}"
}

Every subsequent step (dispatch, collect, verify, fix_loop, report) MUST begin by reading .planning/tmp/docs-work-manifest.json and update the status field for items it processes. This prevents the orchestrator from "forgetting" any work item across the multi-step workflow.

Check for hand-written docs in the queue and gather user decisions before dispatch.

Skip conditions (check in order):

  1. If --force is present in $ARGUMENTS: treat all docs as mode: regenerate, skip to detect_runtime_capabilities.
  2. If --verify-only is present in $ARGUMENTS: skip to verify_only_report (do not continue to detect_runtime_capabilities).
  3. If no docs in the queue have has_gsd_marker: false in the existing_docs array: skip to detect_runtime_capabilities.

For each queued doc where has_gsd_marker is false (hand-written doc detected):

Present the following choice using AskUserQuestion if available, or inline prompt otherwise:

{filename} appears to be hand-written (no GSD marker found).

How should this file be handled?
  [1] preserve    -- Skip entirely. Leave unchanged.
  [2] supplement  -- Append only missing sections. Existing content untouched.
  [3] regenerate  -- Overwrite with a fresh GSD-generated doc.

Record each decision. Update the doc queue:

  • preserve decisions: remove the doc from the queue entirely
  • supplement decisions: set mode to supplement in the doc_assignment block; include existing_content (full file content)
  • regenerate decisions: set mode to create (treat as a fresh write)

Fallback when AskUserQuestion is unavailable: Default all hand-written docs to preserve (safest default). Display message:

AskUserQuestion unavailable — hand-written docs preserved by default.
Use --force to regenerate all docs, or re-run in Claude Code to get per-file prompts.

After all decisions recorded, continue to detect_runtime_capabilities.

**Read the work manifest first:** `Read .planning/tmp/docs-work-manifest.json` — use `canonical_queue` items with `wave: 1` for this step.

Spawn 3 parallel gsd-doc-writer agents for Wave 1 docs: README, ARCHITECTURE, CONFIGURATION (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze).

These are foundational docs with no cross-references needed, making them ideal for parallel generation.

Use run_in_background=true for all three to enable parallel execution.

Agent 1: README

Runtime-aware dispatch (#2508 Phase 4). GSD workflows dispatch specialized subagents by role. Before dispatching on a built-in-only runtime (kimi-code — three built-ins only), resolve the role to a built-in via gsd_run query resolve-dispatch-type --requested <role> --raw. On named-dispatch runtimes (Claude/OpenCode/…) the role is returned unchanged; on kimi-code it maps to coder/explore/plan by role-suffix. The persona rides ${AGENT_SKILLS_<ROLE>} (Phase 3) regardless. See @gsd-core/references/runtime-aware-dispatch.md.

Model omission (#2517). Omit the model parameter entirely when the value it would carry (doc_writer_model, DOC_VERIFIER_MODEL) is "inherit" or empty. An empty value 404s on runtimes without native tier aliases — the default on non-Claude runtimes. Omitting it inherits the orchestrator's model. See @gsd-core/references/model-profile-resolution.md.

Agent(
  subagent_type="gsd-doc-writer",
  model="{doc_writer_model}",
  run_in_background=true,
  description="Generate README.md for target project",
  prompt="<doc_assignment>
type: readme
mode: {create|update|supplement}
preservation_mode: {preserve|supplement|regenerate|null}
project_context: {INIT JSON}
{existing_content: | (include full file content here if mode is update or supplement, else omit this line)}
</doc_assignment>

{AGENT_SKILLS}

Write the doc file directly. Return confirmation only — do not return doc content."
)

Agent 2: ARCHITECTURE

Agent(
  subagent_type="gsd-doc-writer",
  model="{doc_writer_model}",
  run_in_background=true,
  description="Generate ARCHITECTURE.md for target project",
  prompt="<doc_assignment>
type: architecture
mode: {create|update|supplement}
preservation_mode: {preserve|supplement|regenerate|null}
project_context: {INIT JSON}
{existing_content: | (include full file content here if mode is update or supplement, else omit this line)}
</doc_assignment>

{AGENT_SKILLS}

Write the doc file directly. Return confirmation only — do not return doc content."
)

Agent 3: CONFIGURATION

Agent(
  subagent_type="gsd-doc-writer",
  model="{doc_writer_model}",
  run_in_background=true,
  description="Generate CONFIGURATION.md for target project",
  prompt="<doc_assignment>
type: configuration
mode: {create|update|supplement}
preservation_mode: {preserve|supplement|regenerate|null}
project_context: {INIT JSON}
{existing_content: | (include full file content here if mode is update or supplement, else omit this line)}
note: Apply VERIFY markers to any infrastructure claim not discoverable from the repository.
</doc_assignment>

{AGENT_SKILLS}

Write the doc file directly. Return confirmation only — do not return doc content."
)

CRITICAL: Agent prompts must contain ONLY the <doc_assignment> block, the ${AGENT_SKILLS} variable, and the return instruction. Do not include project planning context, workflow prose, or any internal tooling references in agent prompts.

ORCHESTRATOR RULE — CODEX RUNTIME: After calling all Wave 1 Agent() calls above with run_in_background=true, do NOT generate any documentation independently while the subagents are active. Wait for all Wave 1 agents to complete before proceeding. This prevents duplicate work and wasted context.

Continue to collect_wave_1.

**Read the work manifest first:** `Read .planning/tmp/docs-work-manifest.json` — update `status` to `"completed"` or `"failed"` for each Wave 1 item after collection. Write the updated manifest back to disk.

Wait for all 3 Wave 1 background agents to finish, then read each agent's output file to collect confirmations.

Each Agent(...) call above with run_in_background=true returns an async_launched result that carries an outputFile path (and canReadOutputFile: true). Each agent's completion arrives as a message in this conversation when it finishes — do NOT issue a separate blocking call to wait. Once all 3 agents have reported completion, read their output files in parallel (single message with 3 Read calls):

Read tool:
  file_path: "{outputFile from README agent result}"

Read tool:
  file_path: "{outputFile from ARCHITECTURE agent result}"

Read tool:
  file_path: "{outputFile from CONFIGURATION agent result}"

Allow up to 5 minutes (300000 ms) for the slowest agent to finish before treating it as failed.

Expected confirmation format from each agent:

## Doc Generation Complete
**Type:** {type}
**Mode:** {mode}
**File written:** `{path}` ({N} lines)
Ready for orchestrator summary.

After collection, verify the Wave 1 files exist on disk using the resolved_path from each manifest entry:

ls -la {resolved_path_1} {resolved_path_2} {resolved_path_3} 2>/dev/null

If any agent failed or its file is missing:

  • Note the failure
  • Continue with the successful docs (do NOT halt Wave 2 for a single failure)
  • The missing doc will be noted in the final report

Continue to dispatch_wave_2.

**Read the work manifest first:** `Read .planning/tmp/docs-work-manifest.json` — use `canonical_queue` items with `wave: 2` for this step.

Spawn agents for all queued Wave 2 docs: GETTING-STARTED, DEVELOPMENT, TESTING, and any conditional docs (API, DEPLOYMENT, CONTRIBUTING) that were queued in build_doc_queue.

Wave 2 agents can reference Wave 1 outputs for cross-referencing — include the wave_1_outputs field in each doc_assignment block.

Use run_in_background=true for all Wave 2 agents to enable parallel execution within the wave.

Agent: GETTING-STARTED

Agent(
  subagent_type="gsd-doc-writer",
  model="{doc_writer_model}",
  run_in_background=true,
  description="Generate GETTING-STARTED.md for target project",
  prompt="<doc_assignment>
type: getting_started
mode: {create|update|supplement}
preservation_mode: {preserve|supplement|regenerate|null}
project_context: {INIT JSON}
{existing_content: | (include full file content here if mode is update or supplement, else omit this line)}
wave_1_outputs:
  - README.md
  - docs/ARCHITECTURE.md
  - docs/CONFIGURATION.md
</doc_assignment>

{AGENT_SKILLS}

Write the doc file directly. Return confirmation only — do not return doc content."
)

Agent: DEVELOPMENT

Agent(
  subagent_type="gsd-doc-writer",
  model="{doc_writer_model}",
  run_in_background=true,
  description="Generate DEVELOPMENT.md for target project",
  prompt="<doc_assignment>
type: development
mode: {create|update|supplement}
preservation_mode: {preserve|supplement|regenerate|null}
project_context: {INIT JSON}
{existing_content: | (include full file content here if mode is update or supplement, else omit this line)}
wave_1_outputs:
  - README.md
  - docs/ARCHITECTURE.md
  - docs/CONFIGURATION.md
</doc_assignment>

{AGENT_SKILLS}

Write the doc file directly. Return confirmation only — do not return doc content."
)

Agent: TESTING

Agent(
  subagent_type="gsd-doc-writer",
  model="{doc_writer_model}",
  run_in_background=true,
  description="Generate TESTING.md for target project",
  prompt="<doc_assignment>
type: testing
mode: {create|update|supplement}
preservation_mode: {preserve|supplement|regenerate|null}
project_context: {INIT JSON}
{existing_content: | (include full file content here if mode is update or supplement, else omit this line)}
wave_1_outputs:
  - README.md
  - docs/ARCHITECTURE.md
  - docs/CONFIGURATION.md
</doc_assignment>

{AGENT_SKILLS}

Write the doc file directly. Return confirmation only — do not return doc content."
)

Conditional Agent: API (only if has_api_routes was true — spawn only if API.md was queued)

Agent(
  subagent_type="gsd-doc-writer",
  model="{doc_writer_model}",
  run_in_background=true,
  description="Generate API.md for target project",
  prompt="<doc_assignment>
type: api
mode: {create|update|supplement}
preservation_mode: {preserve|supplement|regenerate|null}
project_context: {INIT JSON}
{existing_content: | (include full file content here if mode is update or supplement, else omit this line)}
wave_1_outputs:
  - README.md
  - docs/ARCHITECTURE.md
  - docs/CONFIGURATION.md
</doc_assignment>

{AGENT_SKILLS}

Write the doc file directly. Return confirmation only — do not return doc content."
)

Conditional Agent: DEPLOYMENT (only if has_deploy_config was true — spawn only if DEPLOYMENT.md was queued)

Agent(
  subagent_type="gsd-doc-writer",
  model="{doc_writer_model}",
  run_in_background=true,
  description="Generate DEPLOYMENT.md for target project",
  prompt="<doc_assignment>
type: deployment
mode: {create|update|supplement}
preservation_mode: {preserve|supplement|regenerate|null}
project_context: {INIT JSON}
{existing_content: | (include full file content here if mode is update or supplement, else omit this line)}
note: Apply VERIFY markers to any infrastructure claim not discoverable from the repository.
wave_1_outputs:
  - README.md
  - docs/ARCHITECTURE.md
  - docs/CONFIGURATION.md
</doc_assignment>

{AGENT_SKILLS}

Write the doc file directly. Return confirmation only — do not return doc content."
)

Conditional Agent: CONTRIBUTING (only if is_open_source was true — spawn only if CONTRIBUTING.md was queued)

Agent(
  subagent_type="gsd-doc-writer",
  model="{doc_writer_model}",
  run_in_background=true,
  description="Generate CONTRIBUTING.md for target project",
  prompt="<doc_assignment>
type: contributing
mode: {create|update|supplement}
preservation_mode: {preserve|supplement|regenerate|null}
project_context: {INIT JSON}
{existing_content: | (include full file content here if mode is update or supplement, else omit this line)}
wave_1_outputs:
  - README.md
  - docs/ARCHITECTURE.md
  - docs/CONFIGURATION.md
</doc_assignment>

{AGENT_SKILLS}

Write the doc file directly. Return confirmation only — do not return doc content."
)

CRITICAL: Agent prompts must contain ONLY the <doc_assignment> block, the ${AGENT_SKILLS} variable, and the return instruction. Do not include project planning context, workflow prose, or any internal tooling references in agent prompts.

ORCHESTRATOR RULE — CODEX RUNTIME: After calling all Wave 2 Agent() calls above with run_in_background=true, do NOT generate any documentation independently while the subagents are active. Wait for all Wave 2 agents to complete before proceeding. This prevents duplicate work and wasted context.

Continue to collect_wave_2.

**Read the work manifest first:** `Read .planning/tmp/docs-work-manifest.json` — update `status` to `"completed"` or `"failed"` for each Wave 2 item after collection. Write the updated manifest back to disk.

Wait for all Wave 2 background agents to finish, then read each agent's output file to collect confirmations.

Each Agent(...) call above with run_in_background=true returns an async_launched result that carries an outputFile path (and canReadOutputFile: true). Each agent's completion arrives as a message in this conversation when it finishes — do NOT issue a separate blocking call to wait. Once all Wave 2 agents have reported completion, read their output files in parallel (single message with N Read calls — one per spawned Wave 2 agent):

Read tool:
  file_path: "{outputFile from GETTING-STARTED agent result}"

Read tool:
  file_path: "{outputFile from DEVELOPMENT agent result}"

Read tool:
  file_path: "{outputFile from TESTING agent result}"

# Add one Read call per conditional agent spawned (API, DEPLOYMENT, CONTRIBUTING)

Allow up to 5 minutes (300000 ms) for the slowest agent to finish before treating it as failed.

After collection, verify all Wave 2 files exist on disk using the resolved_path from each manifest entry:

ls -la {resolved_path for each wave 2 item} 2>/dev/null

If any agent failed or its file is missing, note the failure and continue. Missing docs will be reported in the final report.

Continue to dispatch_monorepo_packages (if monorepo_workspaces is non-empty) or commit_docs.

If section_manifest (from INIT_DOCS_UPDATE) is null or "dispatch-monorepo-packages" is in its included list: read and execute gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md. Otherwise skip — do not read the file; continue to commit_docs.

When the `Task` tool is unavailable, generate all queued docs sequentially in the current context instead of spawning subagents — this step replaces dispatch_wave_1, collect_wave_1, dispatch_wave_2, and collect_wave_2. Read `agents/gsd-doc-writer.md` once, then for each queued doc (Wave 1: README/ARCHITECTURE/CONFIGURATION, complete before Wave 2; Wave 2: GETTING-STARTED/DEVELOPMENT/TESTING plus any queued conditional docs, referencing Wave 1 outputs) construct the same doc_assignment fields the parallel path uses and write the file inline, using only file system tools (never browser-based tools). If `monorepo_workspaces` is non-empty, generate per-package READMEs sequentially afterward. Continue to verify_docs.

Exact per-doc construction and the monorepo per-package loop: gsd-core/workflows/docs-update/detail/elaboration.md § 1.

Verify factual claims in ALL docs — both canonical (generated) and non-canonical (existing hand-written) — against the live codebase.

CRITICAL: Read the work manifest first.

Read .planning/tmp/docs-work-manifest.json

Extract canonical_queue (items with status: "completed") and review_queue (items with status: "pending_review"). Both queues are verified in this step.

Skip condition: If --verify-only is present in $ARGUMENTS, this step was already handled by verify_only_report (early exit). Skip.

Phase 1: Verify canonical docs (generated/updated docs)

For each doc in canonical_queue that was successfully written to disk:

  1. Print: ◆ Spawning doc verifier for {doc_path}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze) Spawn the gsd-doc-verifier agent (or invoke sequentially if Task tool is unavailable) with a <verify_assignment> block:

    <verify_assignment>
    doc_path: {relative path to the doc file, e.g. README.md}
    project_root: {project_root from init JSON}
    </verify_assignment>
    
  2. After the verifier completes, read the result JSON from .planning/tmp/verify-{doc_filename}.json.

  3. Update the manifest: set status: "verified" for each canonical doc processed.

Phase 2: Verify non-canonical docs (existing hand-written docs)

This is NOT optional. Every doc in review_queue MUST be verified.

For each doc in review_queue from the manifest:

  1. Print: ◆ Spawning doc verifier for {doc_path}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze) Spawn the gsd-doc-verifier agent with the same <verify_assignment> block as above.
  2. Read the result JSON from .planning/tmp/verify-{doc_filename}.json.
  3. Update the manifest: set status: "verified" for each review_queue doc processed.

Non-canonical docs with failures ARE eligible for the fix_loop. When a non-canonical doc has claims_failed > 0, dispatch it to gsd-doc-writer in fix mode with the failures array — the writer's fix mode does surgical corrections on specific lines regardless of doc type (no template needed). The writer MUST NOT restructure, rephrase, or reformat any content beyond the failing claims.

Phase 3: Present combined verification summary

Collect ALL results (canonical + non-canonical) into a single verification_results array:

Verification results:

Canonical docs (generated):

| Doc                    | Claims | Passed | Failed |
|------------------------|--------|--------|--------|
| README.md              | 12     | 10     | 2      |
| docs/architecture/overview.md | 8 | 8   | 0      |

Existing docs (reviewed):

| Doc                    | Claims | Passed | Failed |
|------------------------|--------|--------|--------|
| docs/frontend/components/button.md | 5 | 4 | 1   |
| docs/services/api.md   | 8      | 8      | 0      |

Total: {total_checked} claims checked, {total_failed} failures

Write the updated manifest back to disk.

If all docs have claims_failed === 0: skip fix_loop, continue to scan_for_secrets. If any doc (canonical OR non-canonical) has claims_failed > 0: continue to fix_loop.

**Skip condition:** if every doc passed verification (no `claims_failed > 0`), skip this step entirely.

Otherwise, correct flagged inaccuracies by re-sending failing docs to gsd-doc-writer in fix mode (one spawn per doc, never batched), for at most 2 iterations (D-06). Each spawn carries a <doc_assignment> block: type (the doc's original type), mode: fix, doc_path, project_context, existing_content (current file content), and failures: — a structured array of {line, claim, expected, actual} objects, one per failed claim.

For each doc with a failure, per iteration: a. Read the current file content from disk. Record the pre-fix line count: bash PRE_FIX_LINES=$(wc -l < "{doc_path}" 2>/dev/null || echo 0) b. Spawn gsd-doc-writer with the <doc_assignment> block above. c. One agent spawn per doc with failures. Do not batch multiple docs into one spawn. d. Post-fix truncation guard: After the fix agent completes, check for file corruption: bash POST_FIX_LINES=$(wc -l < "{doc_path}" 2>/dev/null || echo 0) If POST_FIX_LINES is less than 10% of PRE_FIX_LINES (i.e. the file shrank by more than 90%), the fix agent corrupted the file via a full-file Write. Restore it immediately: - Write the existing_content captured in step 1a back to "{doc_path}" using the Write tool - Log: WARNING: Fix agent corrupted {doc_path} ({POST_FIX_LINES} lines after fix, was {PRE_FIX_LINES}). Restored from pre-fix content. Failures for this doc require manual correction. - Mark this doc as "fix-corrupted" in the manifest; it will appear in remaining failures at the end - Do NOT attempt to fix this doc again this iteration. It is still included in the step 2 re-verification (so its failures are counted) but no further fix agent will be dispatched for it in this iteration.

After each iteration's fix agents complete, re-verify ALL docs and check for regression (D-05): any doc that previously passed and now fails HALTS the loop immediately — remaining failures require manual review, no further fixes attempted. After 2 iterations with failures remaining, report them and continue.

Continue to scan_for_secrets either way.

Exact iteration bookkeeping and the regression-halt report wording: gsd-core/workflows/docs-update/detail/elaboration.md § 2.

**Reached when `--verify-only` is present in `$ARGUMENTS`** — an early-exit reporting mode: do not proceed to dispatch, generation, commit, or report steps after this step. Spawn `gsd-doc-verifier` (read-only) for every file in `existing_docs`, count `