Files
msd-core/gsd-core/workflows/docs-update.md
Tom Boucher ff4a57b78c chore(#1671): migrate the remaining 13 LARGE/XL workflows to the fragment model — Phase 6.3 (#3030)
* chore(#2994): fragmentize progress.md forensic audit onto the fragment model

Extract the --forensic-gated forensic_audit step to
workflows/progress/steps/forensic-audit.md behind a section marker, and
repair progress.md's init line to forward --forensic so the atom is
actually true in production rather than only under direct CLI tests.

progress.md shrinks 32630 -> 27207 bytes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize the four manifest-wired workflows

new-project, quick, new-milestone and progress each already had a
dedicated cmdInit* entry point but zero marked sections. Extract nine
gated bodies to workflows/<wf>/steps/ behind section markers and repair
each init line to forward its flags.

Fold --full into the discuss/research/validate facts inside cmdInitQuick
so the when= grammar never sees an OR, per the chunked-mode precedent.

Fixes found while working, per the no-defer rule:
- cmdInitProgress passed no phase info to buildSectionManifestField, so
  state:phase-mvp-mode was permanently false — an atom in the vocabulary
  whose fact could never be computed.
- the quick init router folded flag tokens into the free-text
  description, which the new forwarding would have corrupted.
- a #2508 dispatch note was nested inside quick.md's Agent(prompt=)
  fence, leaking orchestrator guidance into the subagent prompt.
- progress.md had a 3-vs-4 backtick outer-fence imbalance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize verify-work.md and admit state:ui-phase-active

Wire cmdInitVerifyWork to buildSectionManifestField — it was a dedicated
entry point that never emitted a manifest — and mark two sections.

state:ui-phase-active folds (plan:pre hooks include an active ui step) OR
(the phase dir holds a *-UI-SPEC.md) into one boolean in init.cts, so the
grammar still sees a single operator-free atom. The inner Playwright-MCP
check stays as prose inside the fragment: it is live session state and no
init seam can precompute it.

The MVP false-branch note is a real fallback, not redundant prose, so it
sits outside the marker — gating it away would delete the text needed
precisely when MVP mode is off.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(#2994): follow moved workflow content in drift guards

Retarget every guard that asserted on content this branch moved into
workflows/<wf>/steps/, mirroring 815b3d897. Each retargeted assertion was
verified to still fail when its step file is blanked, so none was
weakened into vacuity.

Three assertions in verify-mvp-uat were genuinely red. Three more were
worse than red — passing for the wrong reason:
- quick-commit-boundary and worktree-cleanup anchored on indexOf('Step
  5.6'), which matched a later cross-reference and sliced 16069 chars
  that coincidentally held the asserted substrings. Replaced with an
  expandWorkflowSections helper that splices step content back in place.
- phase6-review-capabilities lost its end boundary and widened to EOF.
- playwright-ui-verify matched 'UI' in an unrelated bullet and 'fall
  back' in a subagent-dispatch line after the real content moved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize code-review and complete-milestone, admit three atoms

Add dedicated cmdInitCodeReview and cmdInitCompleteMilestone entry points
alongside the shared generic ones rather than modifying them — init.phase-op
and init.manager carry a CRITICAL blast radius (179 dependents, 24
processes) and stay byte-identical for their other callers.

Admit flag:--fix, state:fallow-enabled and state:git-create-tag, each with
a consuming section and a fact its own entry point computes.

Both sections had the resolver-in-body hazard: the fallow config-gate and
the git.create_tag check each sat inside the very block being gated, so
gating would have disabled the resolver that decides the gate. Both are
hoisted into init and the bodies now consume the resolved fact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(#2994): retarget code-review and milestone drift guards, fix two red tests

Retarget guards that asserted on content moved into steps/, proving
non-vacuity by blanking each step file and confirming failure.

Also fixes two genuinely red tests found while working, per the no-defer
rule:
- workflow-fragments' frozen-vocabulary lock was missing
  state:ui-phase-active, so commit 7ef7f8336 shipped red. Lint and build
  both passed over it, which is why neither is sufficient verification.
- code-review's quick.md capability-hook assertion carried a stale
  delimiter after the 18ff35d20 extraction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize autonomous.md and admit state:plan-strategy-converge

Five sections share one atom, the pattern plan-phase already uses for
flag:--research-phase. The atom folds --converge OR --cross-ai into a
single boolean in cmdInitAutonomous so the grammar stays operator-free.

cmdInitAutonomous is additive; init.milestone-op, init.manager and
init.phase-op are untouched and still consumed. The $PLAN_STRATEGY bash
resolver is deliberately retained — ungated local-planning bullets still
read it, so the init-side fact supplements it rather than replacing it.

converge-fail-fast required splitting one bash fence so the always-run
CONVERGENCE_ARGS construction stays outside the marker. All three
flag-absent fallbacks were left outside their markers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize review and discuss-phase-assumptions

Admit state:reviewer-instances-configured (two peripheral notes share it;
the core reviewer-lane dispatch stays unmarked — it is the workflow's
primary always-evaluated logic, not an optional branch) and
state:auto-advance-active, which folds --auto OR two config keys into one
boolean so the grammar stays operator-free.

discuss-phase-assumptions was the highest-risk edit in this PR. Its
auto_advance step is a full if/elif/else; gating it whole would have
deleted the flag-absent fallback needed exactly when --auto is off. Split
verified exact: resolvers 636-651 and the 'End here' fallback 668-669 both
stay outside the marker; only 653-667 is gated.

Adds emitted-drift acks for the two files that grew — review.md (+55 B)
and autonomous.md (+737 B from 80799211c, which had none and would have
red-gated the push.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): fragmentize docs-update, update, transition and new-milestone Part A

Completes the 13-workflow rollout. Three of these had no init call at all
and gained a dedicated entry point plus their first gsd_run query line.

Admits state:is-monorepo and adds state:next-channel, state:workstream-active
and state:flat-mode. Vocabulary 26 -> 30 atoms.

Part A of new-milestone applies when NO workstream is active — the negation
of state:workstream-active. Rather than teach the grammar negation, which is
the Greenspun drift the frozen list exists to prevent, it gets a separate
positively-phrased atom whose fact is the inverse. Part B, which always runs,
stays outside the marker.

flag:--verify-only is deliberately NOT admitted: docs-update has no
contiguous purely-additive region for it, and an atom without a consuming
section is dead vocabulary. Evidence recorded in the slice report.

update.md reuses its existing resolved $GSD_TOOLS rather than prepending the
canonical preamble, which would have clobbered it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): stop automated-ui-verification re-resolving its own gate, retire dead vocabulary

Two defects the new tests caught.

The automated-ui-verification step re-ran gsd_run loop render-hooks and
recomputed UI_PHASE_ACTIVE inside a body that is only read when that fact
is already true — the circular self-disabling pattern this design forbids,
introduced by 3c654b168. cmdInitVerifyWork now exposes ui_phase_active and
the step consumes it. Its launcher preamble goes too: no gsd_run remains.
The Playwright-MCP check stays as prose — that is live session state.

Dead vocabulary predating this PR: flag:--full and state:needs-codebase-map
were admitted with a gate-1 claim that never materialized. flag:--full is
removed, redundant once quick folds it into discuss/research/validate.
state:needs-codebase-map gets the real consumer it always lacked, gating
new-project's codebase-map offer. Vocabulary 30 -> 29, and no atom is now
without a consuming section.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(#2994): add the atom-admission, inversion and resolver-hoist gates

The two existing parity guards prove vocabulary/predicate symmetry but
never that a fact is computed — an atom no cmdInit* assembles evaluates
false forever. These close that hole:

- per-atom satisfiability for all 29 atoms, plus an anti-vacuity assertion
  so the loop cannot silently cover zero atoms
- dead-vocabulary check against the shipped manifest
- inversion guard: the flag-absent fallbacks in discuss-phase-assumptions
  and verify-work must stay outside their markers
- data-driven resolver-hoist guard over the shipped manifest, so a future
  extraction cannot reintroduce the circular class
- compound-fold coverage (--full, --cross-ai, --rc, config-only --auto)
- null-vs-[] degraded/computed distinction, and flag value shapes

Also repairs the frozen-vocabulary lock, which was stale and red for the
seven atoms earlier commits on this branch shipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(#2994): add changeset for the fragment-model rollout

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test(#2994): cite the issue on the two new allow-test-rule exemptions

ADR-456 requires an issue ref on the same line as the annotation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(#2994): correct the atom-count claims after retiring flag:--full

The vocabulary doc comments still said 30 entries; it is 29 since
flag:--full was removed as dead vocabulary.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): dedupe the phase-fallback block and harden --ws parsing

Review findings.

MAJOR: the three new init entry points each pasted a verbatim copy of the
guardedFindPhase/guardedGetRoadmapPhase fallback, taking the repo from four
copies to seven — DEFECT.GENERATIVE-FIX. Extracted applyRoadmapFallback and
folded six of the seven; each call site keeps its own field-set via a
closure. Duplication removed rather than papered over with a parity test.
cmdInitPhaseOp stays out: its fallback omits has_reviews, so it is not a
byte-identical copy, and it is CRITICAL-radius.

LOW, pre-existing: GSD_WS captured [^[:space:]]+ and expands unquoted, so a
workstream name holding glob metacharacters would expand against the
filesystem. Narrowed to [A-Za-z0-9._-]+. The unquoted expansion is kept —
it must word-split into two args and vanish when empty.

Also restores the vocabulary ordering convention, and fixes a masked test
bug the mandated run surfaced: the flag-forwarding guard checked only the
first init line per workflow, but new-milestone has two, so a real failure
was reporting exit 0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): drop the stale new-milestone emitted-drift ack

new-milestone.md was acked for a +406 B growth measured against an
intermediate commit. Net against origin/next it SHRANK by 8 bytes, so
nothing needed the ack and it explained nothing — which the differential
attribution check reports as a stale acknowledgment, not a pass.

update.md's entry stays: it genuinely grew +703 B.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): resolve the 15 failures from the full matrix run

All 15 were real and identical on both lanes.

REAL REGRESSION: autonomous.md hit 41479 chars against the #2196 guard's
40960 cap — a CHARS cap distinct from the LARGE tier byte cap, which the
five section stubs pushed it over. Extracted the 3a.5 UI Design Contract
body to references/; now 39968 chars, and the file nets -795 B vs base, so
its growth ack is deleted rather than left stale.

REAL DEFECT: docs referenced /gsd-transition, which is not a live
registered command. Reworded.

STALE FIXTURE: the emission byte-identity test hardcoded two marked
workflows; this branch legitimately marks fifteen. Fixture corrected — the
source was right.

The rest were drift guards over the eight workflows the earlier sweep did
not cover, retargeted at where the content now lives with non-vacuity
proven by blanking each step file and confirming failure. The GSD_WS
forwarding guard was checked as a possible real break and is not one: the
charclass narrowing is intact and forwarding works end to end.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): drop the ack for a newly-added reference file

A new file's emitted ripple is attributable to the diff that adds it, so
the acknowledgment explained nothing and the differential check reports it
as stale. Removing the last entry removes the fragment — an empty one
signals nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(#2994): retarget the UI-contract guards and clear two transitive advisories

The §3a.5 extraction that brought autonomous.md under the #2196 char cap
moved its body to references/autonomous-ui-design-contract.md, so ten
guards in autonomous-ui-steps and check-ui-safety-gate were asserting it
against the host. Retargeted via a combined read, each proven non-vacuous
by blanking the reference file and confirming failure.

This class had already bitten twice on this branch because each sweep was
scoped to the workflows touched at that moment, so this one was
exhaustive: ~70 test files across all 13 workflows, zero further broken or
vacuous assertions found.

Also clears two high transitive advisories the matrix flagged on one lane
— fast-uri GHSA-7p8r-x3mc-p8w7 and three ip-address SSRF/trust-boundary
issues. Both pre-date this branch: package-lock.json was untouched until
now, so the production tree was byte-identical to the base. Lockfile-only,
package.json unchanged, verified against a real npm ci install.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(#2994): backfill changeset pr number to 3030

---------

Co-authored-by: sim <sim@local>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 19:59:58 -04:00

54 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>
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}"; if [ -f "$GSD_TOOLS" ]; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif command -v gsd-tools >/dev/null 2>&1; then GSD_TOOLS="$(command -v gsd-tools)"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif [ -f "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; elif [ -f "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd-tools is not on PATH. Run: npx -y @opengsd/gsd-core@latest --claude --local" >&2; exit 1; fi; 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

Extract from init JSON:

  • doc_writer_model — model string to pass to each spawned agent (never hardcode a model name)
  • 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 questions, prompts, and explanations in this workflow in that language; 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, Gemini CLI, 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/ |
| testing | docs/guides/testing.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) 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.

**Read the work manifest first:** `Read .planning/tmp/docs-work-manifest.json` — use `canonical_queue` items for generation order. Update `status` after each doc is generated. Write the updated manifest back to disk after all docs are complete.

When the Task tool is unavailable, generate docs sequentially in the current context. This step replaces dispatch_wave_1, collect_wave_1, dispatch_wave_2, and collect_wave_2.

IMPORTANT: Do NOT use browser_subagent, Explore, or any browser-based tool. Use only file system tools (Read, Bash, Write, Grep, Glob, or equivalent tools available in your runtime).

Read agents/gsd-doc-writer.md instructions once before beginning. Follow the create_mode or update_mode instructions from that agent for each doc, using the same doc_assignment fields as the parallel path.

Wave 1 (sequential — complete all three before starting Wave 2):

For each Wave 1 doc, construct the equivalent doc_assignment block and generate the file inline:

  1. README — mode from resolve_modes; for update/supplement mode, include existing_content

    • Construct doc_assignment: type: readme, mode: {create|update|supplement}, preservation_mode: {value|null}, project_context: {INIT JSON}, existing_content: (if update/supplement)
    • Explore the codebase (Read, Grep, Glob, Bash) following gsd-doc-writer create_mode / update_mode instructions
    • Write the file to the resolved path (README.md)
  2. ARCHITECTURE — mode from resolve_modes; for update/supplement mode, include existing_content

    • Construct doc_assignment: type: architecture, mode: {create|update|supplement}, preservation_mode: {value|null}, project_context: {INIT JSON}, existing_content: (if update/supplement)
    • Explore the codebase following gsd-doc-writer instructions
    • Write the file to the resolved path (docs/ARCHITECTURE.md, or ARCHITECTURE.md if found at root as fallback)
  3. CONFIGURATION — mode from resolve_modes; for update/supplement mode, include existing_content

    • Construct doc_assignment: type: configuration, mode: {create|update|supplement}, preservation_mode: {value|null}, project_context: {INIT JSON}, existing_content: (if update/supplement)
    • Apply VERIFY markers to any infrastructure claim not discoverable from the repository
    • Explore the codebase following gsd-doc-writer instructions
    • Write the file to the resolved path (docs/CONFIGURATION.md, or CONFIGURATION.md if found at root as fallback)

Wave 2 (sequential — begin only after all Wave 1 docs are written):

Wave 2 docs can reference Wave 1 outputs since they are already written. Include wave_1_outputs in each doc_assignment.

  1. GETTING-STARTED — mode from resolve_modes; include wave_1_outputs: [README.md, docs/ARCHITECTURE.md, docs/CONFIGURATION.md]
  2. DEVELOPMENT — mode from resolve_modes; include wave_1_outputs
  3. TESTING — mode from resolve_modes; include wave_1_outputs
  4. API (only if queued) — mode from resolve_modes; include wave_1_outputs
  5. DEPLOYMENT (only if queued) — Apply VERIFY markers to any infrastructure claim not discoverable from the repository; include wave_1_outputs
  6. CONTRIBUTING (only if queued) — mode from resolve_modes; include wave_1_outputs

Monorepo per-package READMEs (only if monorepo_workspaces is non-empty):

After all 9 root-level docs are written, generate per-package READMEs sequentially:

For each resolved package directory (from workspace glob expansion) that contains a package.json:

  • Determine mode: if {package_dir}/README.md exists, mode = update; else mode = create
  • Construct doc_assignment: type: readme, mode: {create|update}, scope: per_package, package_dir: {absolute path}, project_context: {INIT JSON with project_root set to package directory}, existing_content: (if update)
  • Follow gsd-doc-writer instructions for per_package scope
  • Write the file to {package_dir}/README.md

Continue to verify_docs.

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.

**Read the work manifest first:** `Read .planning/tmp/docs-work-manifest.json` — identify ALL docs (canonical AND non-canonical) with `claims_failed > 0` from the verification results in `.planning/tmp/verify-*.json`. Both queues are eligible for fixes.

Correct flagged inaccuracies by re-sending failing docs to the doc-writer in fix mode. Per D-06, max 2 iterations. Per D-05, halt immediately on regression.

Skip condition: If all docs passed verification (no failures), skip this step.

Iteration tracking:

  • MAX_FIX_ITERATIONS = 2
  • iteration = 0
  • previous_passed_docs = set of doc_paths where claims_failed === 0 after initial verification

For each iteration (while iteration < MAX_FIX_ITERATIONS and there are docs with failures):

  1. For each doc with claims_failed > 0 in the latest verification_results: a. Read the current file content from disk. Record the pre-fix line count:

    PRE_FIX_LINES=$(wc -l < "{doc_path}" 2>/dev/null || echo 0)
    

    b. Spawn gsd-doc-writer agent (or invoke sequentially) with a fix assignment:

    <doc_assignment>
    type: {original doc type from the queue, e.g. readme}
    mode: fix
    doc_path: {relative path}
    project_context: {INIT JSON}
    existing_content: {current file content read from disk}
    failures:
      - line: {line}
        claim: "{claim}"
        expected: "{expected}"
        actual: "{actual}"
    </doc_assignment>
    

    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:

    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.
  2. After all fix agents complete, re-verify ALL docs (not just the ones that were fixed):

    • Re-run the same verification process as verify_docs step.
    • Read updated result JSONs from .planning/tmp/verify-{doc_filename}.json.
  3. Regression detection (D-05): For each doc in the new verification_results:

    • If this doc was in previous_passed_docs (passed in the prior round) AND now has claims_failed > 0, this is a REGRESSION.
    • If regression detected: HALT the loop immediately. Present:
      REGRESSION DETECTED -- halting fix loop.
      
      {doc_path} previously passed verification but now has {claims_failed} failures after fix iteration {iteration + 1}.
      
      This means the fix introduced new errors. Remaining failures require manual review.
      
      Continue to scan_for_secrets (do not attempt further fixes).
  4. Update previous_passed_docs with docs that now pass.

  5. Increment iteration.

After loop exhaustion (iteration === MAX_FIX_ITERATIONS and failures remain):

Present remaining failures:

Fix loop completed ({MAX_FIX_ITERATIONS} iterations). Remaining failures:

| Doc               | Failed Claims |
|-------------------|---------------|
| {doc_path}        | {count}       |

These failures require manual correction. Review the verification output in .planning/tmp/verify-*.json for details.

Continue to scan_for_secrets.

**Reached when `--verify-only` is present in `$ARGUMENTS`.** This is an early-exit step — do not proceed to dispatch, generation, commit, or report steps after this step.

Invoke the gsd-doc-verifier agent in read-only mode for each file in existing_docs from the init JSON:

  1. For each doc in existing_docs: a. Spawn gsd-doc-verifier (or invoke sequentially if Task tool is unavailable) with:

    <verify_assignment>
    doc_path: {doc.path}
    project_root: {project_root from init JSON}
    </verify_assignment>
    

    b. Read the result JSON from .planning/tmp/verify-{doc_filename}.json.

  2. Also count VERIFY markers in each doc: grep for <!-- VERIFY: in the file content.

Present a combined summary table:

--verify-only audit:

| File                     | Claims Checked | Passed | Failed | VERIFY Markers |
|--------------------------|----------------|--------|--------|----------------|
| README.md                | 12             | 10     | 2      | 0              |
| docs/ARCHITECTURE.md     | 8              | 8      | 0      | 0              |
| docs/CONFIGURATION.md    | 5              | 3      | 2      | 5              |
| ...                 | ...            | ...    | ...    | ...            |

Total: {total_checked} claims checked, {total_failed} failures, {total_markers} VERIFY markers requiring manual review

If any failures exist, show details:

Failed claims:
  README.md:34 - "src/cli/index.ts" (expected: file exists, actual: file not found)
  docs/CONFIGURATION.md:12 - "npm run deploy" (expected: script in package.json, actual: script not found)

Display note:

To fix failures automatically: /gsd:docs-update (runs generation + fix loop)
To regenerate all docs from scratch: /gsd:docs-update --force

Clean up temp files: remove .planning/tmp/verify-*.json files.

End workflow — do not proceed to any dispatch, commit, or report steps.

CRITICAL SECURITY CHECK: Scan all generated/updated doc files for accidentally leaked secrets before committing. Per D-07, this runs once after the fix loop completes, before commit_docs.

Build the file list from the generation queue -- include all docs that were written to disk (created, updated, supplemented, or fixed). Do not hardcode a static list; use the actual list of files that were generated or modified.

Run secret pattern detection:

# Check for common API key patterns in generated docs
grep -E '(sk-[a-zA-Z0-9]{20,}|sk_live_[a-zA-Z0-9]+|sk_test_[a-zA-Z0-9]+|ghp_[a-zA-Z0-9]{36}|gho_[a-zA-Z0-9]{36}|glpat-[a-zA-Z0-9_-]+|AKIA[A-Z0-9]{16}|xox[baprs]-[a-zA-Z0-9-]+|-----BEGIN.*PRIVATE KEY|eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.)' \
  {space-separated list of generated doc files} 2>/dev/null \
  && SECRETS_FOUND=true || SECRETS_FOUND=false

If SECRETS_FOUND=true:

SECURITY ALERT: Potential secrets detected in generated documentation!

Found patterns that look like API keys or tokens in:
{show grep output}

This would expose credentials if committed.

Action required:
1. Review the flagged lines above
2. Remove any real secrets from the doc files
3. Re-run /gsd:docs-update to regenerate clean docs

Then confirm with AskUserQuestion:

AskUserQuestion([{
  question: "Potential secrets detected in generated docs. How would you like to proceed?",
  header: "Security",
  multiSelect: false,
  options: [
    { label: "Safe to proceed", description: "I've reviewed the flagged lines — no real secrets, commit the docs" },
    { label: "Abort commit", description: "Skip committing — I'll clean up the docs first" }
  ]
}])

If the user selects "Abort commit": skip commit_docs and continue to report. If "Safe to proceed": continue to commit_docs.

If SECRETS_FOUND=false:

Continue to commit_docs.

Only run this step if `commit_docs` is `true` from the init JSON. If `commit_docs` is false, skip to report.

Assemble the list of files that were actually generated (do not include files that failed or were skipped):

gsd_run query commit "docs: generate project documentation" \
  --files README.md docs/ARCHITECTURE.md docs/CONFIGURATION.md docs/GETTING-STARTED.md docs/DEVELOPMENT.md docs/TESTING.md
# Append any conditional docs that were generated:
# --files ... docs/API.md docs/DEPLOYMENT.md CONTRIBUTING.md
# Append per-package READMEs if monorepo dispatch ran:
# --files ... packages/core/README.md packages/cli/README.md

Only include files that were successfully written to disk. Do not include failed or skipped docs.

Continue to report.

**Read the work manifest first:** `Read .planning/tmp/docs-work-manifest.json` — use the manifest to compile the complete report covering all canonical docs, review_queue results, and gap_queue results. The manifest is the source of truth for what was processed.

Present a completion summary to the user.

Summary format:

Documentation generation complete.

Project type: {primary_type}

Generated docs:
| File                     | Mode   | Lines |
|--------------------------|--------|-------|
| README.md                | create | 87    |
| docs/ARCHITECTURE.md     | update | 124   |
| docs/GETTING-STARTED.md  | create | 63    |
| docs/DEVELOPMENT.md      | create | 71    |
| docs/TESTING.md          | create | 58    |
| docs/CONFIGURATION.md    | create | 45    |
[conditional docs if generated]

{If monorepo per-package READMEs were generated:}
Per-package READMEs:
| Package             | Mode   | Lines |
|---------------------|--------|-------|
| packages/core       | create | 42    |
| packages/cli        | create | 38    |

{If any docs failed or were skipped:}
Skipped / failed:
  - docs/API.md: agent did not complete

{If preservation_check ran:}
Preservation decisions:
  - {filename}: {preserve|supplement|regenerate}

{If docs/DEPLOYMENT.md or docs/CONFIGURATION.md were generated:}
VERIFY markers: {N} markers placed in docs/DEPLOYMENT.md and/or docs/CONFIGURATION.md for infrastructure claims that require manual verification.

{If review_queue was non-empty:}

Existing doc accuracy review:

| Doc | Claims Checked | Passed | Failed | Fixed |
|-----|----------------|--------|--------|-------|
| docs/api/endpoint-map.md | 5 | 4 | 1 | 1 |

{For any remaining unfixed failures after fix_loop:}
Remaining inaccuracies could not be auto-corrected — manual review recommended for flagged items above.

{If commit_docs was true:}
All generated files committed.

Remind the user they can fact-check generated docs:

Run `/gsd:docs-update --verify-only` to fact-check generated docs against the codebase.

End workflow.

<success_criteria>

  • docs-init JSON loaded and all fields extracted
  • Project type correctly classified from project_type signals
  • Doc queue contains all always-on docs plus only the conditional docs matching project signals
  • CHANGELOG.md was NOT generated or queued
  • Each doc was generated in correct mode (create for new, update for existing)
  • Wave 1 docs (README, ARCHITECTURE, CONFIGURATION) completed before Wave 2 started
  • Generated docs contain zero GSD methodology content
  • docs/DEPLOYMENT.md and docs/CONFIGURATION.md use VERIFY markers for undiscoverable claims (if generated)
  • All generated files committed (if commit_docs is true)
  • Hand-written docs (no GSD marker) prompted for preserve/supplement/regenerate before dispatch (unless --force)
  • --force flag skipped preservation prompts and regenerated all docs
  • --verify-only flag reported doc status without generating files
  • Per-package READMEs generated for monorepo workspaces (if applicable)
  • verify_docs step checked all generated docs against the live codebase
  • fix_loop ran at most 2 iterations and halted on regression
  • scan_for_secrets ran before commit and blocked on detected patterns
  • --verify-only invokes gsd-doc-verifier for full fact-checking (not just VERIFY marker count) </success_criteria>