* 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>
This commit is contained in:
@@ -44,6 +44,15 @@ PLAN_STRATEGY="local"
|
||||
if echo "$ARGUMENTS" | grep -qE '(^|[[:space:]])\-\-(converge|cross-ai)([[:space:]]|$)'; then
|
||||
PLAN_STRATEGY="converge"
|
||||
fi
|
||||
|
||||
CONVERGE_PARAM=""
|
||||
if echo "$ARGUMENTS" | grep -qE '(^|[[:space:]])\-\-converge([[:space:]]|$)'; then
|
||||
CONVERGE_PARAM="--converge"
|
||||
fi
|
||||
CROSS_AI_PARAM=""
|
||||
if echo "$ARGUMENTS" | grep -qE '(^|[[:space:]])\-\-cross-ai([[:space:]]|$)'; then
|
||||
CROSS_AI_PARAM="--cross-ai"
|
||||
fi
|
||||
```
|
||||
|
||||
When `--only` is set, also set `FROM_PHASE` to the same value so existing filter logic applies.
|
||||
@@ -58,8 +67,12 @@ Bootstrap via milestone-level init:
|
||||
_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 init.milestone-op)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
INIT_AUTONOMOUS=$(gsd_run query init.autonomous $CONVERGE_PARAM $CROSS_AI_PARAM)
|
||||
if [[ "$INIT_AUTONOMOUS" == @file:* ]]; then INIT_AUTONOMOUS=$(cat "${INIT_AUTONOMOUS#@file:}"); fi
|
||||
```
|
||||
|
||||
Extract `section_manifest` from `INIT_AUTONOMOUS` (used by the `converge-*` sections below and in step 3).
|
||||
|
||||
If `PLAN_STRATEGY` is `converge`, fail fast unless the existing convergence feature gate is enabled:
|
||||
|
||||
```bash
|
||||
@@ -79,23 +92,12 @@ if echo "$ARGUMENTS" | grep -qE '\-\-max-cycles\s+[0-9]+'; then
|
||||
MAX_CYCLES_ARG=$(echo "$ARGUMENTS" | grep -oE '\-\-max-cycles\s+[0-9]+' | awk '{print $2}')
|
||||
CONVERGENCE_ARGS="${CONVERGENCE_ARGS} --max-cycles ${MAX_CYCLES_ARG}"
|
||||
fi
|
||||
|
||||
if [ "$PLAN_STRATEGY" = "converge" ]; then
|
||||
CONVERGENCE_ENABLED=$(gsd_run query config-get workflow.plan_review_convergence 2>/dev/null || echo "false")
|
||||
if [ "$CONVERGENCE_ENABLED" != "true" ]; then
|
||||
printf '%s\n' \
|
||||
'gsd-autonomous --converge is disabled (workflow.plan_review_convergence=false).' \
|
||||
'' \
|
||||
'Enable plan convergence with:' \
|
||||
'' \
|
||||
' gsd config-set workflow.plan_review_convergence true' \
|
||||
'' \
|
||||
'Then re-run the autonomous command with --converge.'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
<!-- gsd:section id="converge-fail-fast" when="state:plan-strategy-converge" -->
|
||||
If `section_manifest` is `null` or `"converge-fail-fast"` is in its `included` list: read and execute `gsd-core/workflows/autonomous/steps/converge-fail-fast.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
Parse JSON for: `milestone_version`, `milestone_name`, `phase_count`, `completed_phases`, `roadmap_exists`, `state_exists`, `commit_docs`.
|
||||
|
||||
**If `roadmap_exists` is false:** Error — "No ROADMAP.md found. Run `/gsd:new-milestone` first."
|
||||
@@ -116,7 +118,9 @@ If `ONLY_PHASE` is set, display: `Single phase mode: Phase ${ONLY_PHASE}`
|
||||
Else if `FROM_PHASE` is set, display: `Starting from phase ${FROM_PHASE}`
|
||||
If `TO_PHASE` is set, display: `Stopping after phase ${TO_PHASE}`
|
||||
If `INTERACTIVE` is set, display: `Mode: Interactive (discuss inline, plan+execute inline — background on Codex only)`
|
||||
If `PLAN_STRATEGY` is `converge`, display: `Planning: Plan-review convergence enabled`
|
||||
<!-- gsd:section id="converge-banner" when="state:plan-strategy-converge" -->
|
||||
If `section_manifest` is `null` or `"converge-banner"` is in its `included` list: read and execute `gsd-core/workflows/autonomous/steps/converge-banner.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
**Agent skills (delegated agents self-load):** This workflow delegates plan/execute/review via flat `Skill()` invocations rather than resolving `agent_skills` itself. Each consumer agent (`gsd-planner`, `gsd-executor`, `gsd-plan-checker`, `gsd-verifier`, …) self-loads its configured `.planning/config.json` `agent_skills` in its own mandatory init step per `@~/.claude/gsd-core/references/agent-skills-bootstrap.md`. This is the durable path that works on every runtime — including Cursor, where `Skill()`-delegated workflow bash init does not reliably execute. No per-delegation injection is needed here. See open-gsd/gsd-core#1866.
|
||||
|
||||
@@ -332,42 +336,11 @@ Check `has_context`. If false → go to handle_blocker: "Discuss for phase ${PHA
|
||||
|
||||
**3a.5. UI Design Contract (Frontend Phases)**
|
||||
|
||||
Resolve active `plan:pre` hooks:
|
||||
> Full instructions are in `gsd-core/references/autonomous-ui-design-contract.md`. Read that file now and follow it exactly.
|
||||
|
||||
```bash
|
||||
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
|
||||
HOOKS_JSON=$(gsd_run loop render-hooks plan:pre --raw)
|
||||
```
|
||||
**Inputs:** `PHASE_NUM`, `PHASE_DIR` from execute_phase. Resolves whether the phase needs a UI-SPEC.md generated before planning via active `plan:pre` step hooks. Always non-blocking — proceeds to 3b regardless of outcome.
|
||||
|
||||
Read the `activeHooks` array directly from `HOOKS_JSON` (in-context — do NOT invoke a shell pipeline). **Compute the active UI step hooks** = entries from `activeHooks` where `kind == "step"` and `ref.skill` is set. **If there are NO active step hooks → skip silently to 3b.** (This covers `workflow.ui_phase=false` — including configurations where only a gate-only entry is present, e.g. `ui_phase=false` + `ui_safety_gate=true` produces `activeHooks=[{kind:"gate"}]`. Autonomous never runs the plan:pre gate — it is always pipeline mode — so a gate-only active set is equivalent to no active step and is silently skipped here. This matches OLD §3a.5 behaviour.)
|
||||
|
||||
(At least one active step hook ⇒ `workflow.ui_phase` is on.) Run the UI-SPEC gate:
|
||||
|
||||
```bash
|
||||
GATE=$(gsd_run check ui-plan-gate "${PHASE_NUM}" --raw)
|
||||
```
|
||||
|
||||
Read `frontend` and `hasUiSpec` from `GATE` (in-context).
|
||||
|
||||
**If `frontend` is false:** Skip silently to 3b.
|
||||
|
||||
**If `hasUiSpec` is true (UI-SPEC already exists):** Skip silently to 3b.
|
||||
|
||||
**Otherwise (frontend phase + no UI-SPEC):** For each active step hook (the `kind == "step"` set from above, in array order):
|
||||
|
||||
```
|
||||
Skill(skill="gsd-${ref.skill}", args="${PHASE_NUM}")
|
||||
```
|
||||
|
||||
(Prepend `gsd-` to `ref.skill` — so `ui-phase` → `gsd-ui-phase`. Bare `${PHASE_NUM}` args — autonomous style, same pattern as the verify:post dispatch.) Entries where `kind == "gate"` are silently ignored — autonomous is always pipeline mode, there is no blocking gate here.
|
||||
|
||||
After all step hooks return, re-read:
|
||||
|
||||
```bash
|
||||
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
|
||||
```
|
||||
|
||||
**If `UI_SPEC_FILE` is still empty:** Display warning `Phase ${PHASE_NUM}: UI-SPEC generation did not produce output — continuing without design contract.` and proceed to 3b. NON-BLOCKING.
|
||||
Read and execute: `$HOME/.claude/gsd-core/references/autonomous-ui-design-contract.md`
|
||||
|
||||
**3b. Plan**
|
||||
|
||||
@@ -379,15 +352,9 @@ FLATTEN=$(gsd_run query dispatch-should-flatten --raw 2>/dev/null || echo "true"
|
||||
|
||||
- **If `FLATTEN` is `false`:** Dispatch plan as a background agent to keep the main context lean. While plan runs, the workflow can immediately start discussing the next phase (see step 4).
|
||||
|
||||
- If `PLAN_STRATEGY=converge`, print: `◆ Spawning background plan-convergence loop for phase ${PHASE_NUM}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
|
||||
|
||||
```
|
||||
Agent(
|
||||
description="Plan convergence phase ${PHASE_NUM}: ${PHASE_NAME}",
|
||||
run_in_background=true,
|
||||
prompt="Run plan convergence for phase ${PHASE_NUM}: Skill(skill=\"gsd-plan-review-convergence\", args=\"${PHASE_NUM} ${CONVERGENCE_ARGS}\")"
|
||||
)
|
||||
```
|
||||
<!-- gsd:section id="converge-dispatch-bg" when="state:plan-strategy-converge" -->
|
||||
If `section_manifest` is `null` or `"converge-dispatch-bg"` is in its `included` list: read and execute `gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
- Otherwise, print: `◆ Spawning background planner for phase ${PHASE_NUM}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
|
||||
|
||||
@@ -403,11 +370,9 @@ FLATTEN=$(gsd_run query dispatch-should-flatten --raw 2>/dev/null || echo "true"
|
||||
|
||||
- **Otherwise (`FLATTEN` is `true` — run inline):** Run plan **inline** (do NOT background) so the plan-checker runs. The next phase's discuss does not overlap planning here — correctness over overlap.
|
||||
|
||||
- If `PLAN_STRATEGY=converge`:
|
||||
|
||||
```
|
||||
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
|
||||
```
|
||||
<!-- gsd:section id="converge-dispatch-inline" when="state:plan-strategy-converge" -->
|
||||
If `section_manifest` is `null` or `"converge-dispatch-inline"` is in its `included` list: read and execute `gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
- Otherwise (local planning):
|
||||
|
||||
@@ -417,11 +382,9 @@ FLATTEN=$(gsd_run query dispatch-should-flatten --raw 2>/dev/null || echo "true"
|
||||
|
||||
**If `INTERACTIVE` is NOT set (default):** Run plan inline.
|
||||
|
||||
If `PLAN_STRATEGY=converge`, run the convergence loop:
|
||||
|
||||
```
|
||||
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
|
||||
```
|
||||
<!-- gsd:section id="converge-loop" when="state:plan-strategy-converge" -->
|
||||
If `section_manifest` is `null` or `"converge-loop"` is in its `included` list: read and execute `gsd-core/workflows/autonomous/steps/converge-loop.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
If `PLAN_STRATEGY=local`, run the regular planner:
|
||||
|
||||
|
||||
1
gsd-core/workflows/autonomous/steps/converge-banner.md
Normal file
1
gsd-core/workflows/autonomous/steps/converge-banner.md
Normal file
@@ -0,0 +1 @@
|
||||
If `PLAN_STRATEGY` is `converge`, display: `Planning: Plan-review convergence enabled`
|
||||
11
gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md
Normal file
11
gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md
Normal file
@@ -0,0 +1,11 @@
|
||||
## Converge Dispatch (Background)
|
||||
|
||||
If `PLAN_STRATEGY=converge`, print: `◆ Spawning background plan-convergence loop for phase ${PHASE_NUM}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
|
||||
|
||||
```
|
||||
Agent(
|
||||
description="Plan convergence phase ${PHASE_NUM}: ${PHASE_NAME}",
|
||||
run_in_background=true,
|
||||
prompt="Run plan convergence for phase ${PHASE_NUM}: Skill(skill=\"gsd-plan-review-convergence\", args=\"${PHASE_NUM} ${CONVERGENCE_ARGS}\")"
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,7 @@
|
||||
## Converge Dispatch (Inline)
|
||||
|
||||
If `PLAN_STRATEGY=converge`:
|
||||
|
||||
```
|
||||
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
|
||||
```
|
||||
21
gsd-core/workflows/autonomous/steps/converge-fail-fast.md
Normal file
21
gsd-core/workflows/autonomous/steps/converge-fail-fast.md
Normal file
@@ -0,0 +1,21 @@
|
||||
## Converge Fail-Fast
|
||||
|
||||
When `PLAN_STRATEGY` is `converge`, fail fast unless the existing convergence feature gate is enabled:
|
||||
|
||||
```bash
|
||||
_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
|
||||
if [ "$PLAN_STRATEGY" = "converge" ]; then
|
||||
CONVERGENCE_ENABLED=$(gsd_run query config-get workflow.plan_review_convergence 2>/dev/null || echo "false")
|
||||
if [ "$CONVERGENCE_ENABLED" != "true" ]; then
|
||||
printf '%s\n' \
|
||||
'gsd-autonomous --converge is disabled (workflow.plan_review_convergence=false).' \
|
||||
'' \
|
||||
'Enable plan convergence with:' \
|
||||
'' \
|
||||
' gsd config-set workflow.plan_review_convergence true' \
|
||||
'' \
|
||||
'Then re-run the autonomous command with --converge.'
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
```
|
||||
7
gsd-core/workflows/autonomous/steps/converge-loop.md
Normal file
7
gsd-core/workflows/autonomous/steps/converge-loop.md
Normal file
@@ -0,0 +1,7 @@
|
||||
## Converge Loop
|
||||
|
||||
If `PLAN_STRATEGY=converge`, run the convergence loop:
|
||||
|
||||
```
|
||||
Skill(skill="gsd-plan-review-convergence", args="${PHASE_NUM} ${CONVERGENCE_ARGS}")
|
||||
```
|
||||
@@ -19,7 +19,33 @@ Parse arguments and load project state:
|
||||
```bash
|
||||
_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
|
||||
PHASE_ARG="${1}"
|
||||
INIT=$(gsd_run query init.phase-op "${PHASE_ARG}")
|
||||
|
||||
# Parse all code-review flags into a structured IR via code-review-flags.cjs.
|
||||
# This is the canonical flag-parsing surface — do not replicate inline bash parsing
|
||||
# for --fix/--all/--auto here; the module handles all flag extraction and implication
|
||||
# logic (e.g., --all and --auto imply --fix). Resolved BEFORE the init call below so
|
||||
# the section-manifest gate forwards the RESOLVED (post-implication) fix decision, not
|
||||
# just a literal --fix token check.
|
||||
FLAGS_JSON=$(node -e "
|
||||
const { parseCodeReviewFlags } = require('./gsd-core/bin/lib/code-review-flags.cjs');
|
||||
const flags = parseCodeReviewFlags(process.argv.slice(1));
|
||||
process.stdout.write(JSON.stringify(flags));
|
||||
" -- "$@" 2>/dev/null)
|
||||
|
||||
# Extract individual flag values from the IR
|
||||
FIX_FLAG=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).fix))")
|
||||
FIX_ALL=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).all))")
|
||||
FIX_AUTO=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).auto))")
|
||||
DEPTH_OVERRIDE=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).depth)")
|
||||
FILES_OVERRIDE=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).files)")
|
||||
|
||||
# Forward the resolved fix decision (--fix itself, or --all/--auto implying it) to
|
||||
# init.code-review so section_manifest's flag:--fix gating (dispatch-fix section)
|
||||
# matches code-review-flags.cjs's own implication logic rather than a raw token scan.
|
||||
FIX_PARAM=""
|
||||
if [ "$FIX_FLAG" = "true" ]; then FIX_PARAM="--fix"; fi
|
||||
|
||||
INIT=$(gsd_run query init.code-review "${PHASE_ARG}" $FIX_PARAM)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_REVIEWER=$(gsd_run query agent-skills gsd-code-reviewer)
|
||||
# #2072: resolve the routed model so model_overrides / models.verification are honored
|
||||
@@ -27,7 +53,7 @@ AGENT_SKILLS_REVIEWER=$(gsd_run query agent-skills gsd-code-reviewer)
|
||||
REVIEWER_MODEL=$(gsd_run query resolve-model gsd-code-reviewer --raw)
|
||||
```
|
||||
|
||||
Parse from init JSON: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`.
|
||||
Parse from init JSON: `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `padded_phase`, `commit_docs`, `fallow_enabled`, `fallow_scope`, `fallow_profile`, `fallow_mcp`, `fallow_max_crap`.
|
||||
|
||||
**Input sanitization (defense-in-depth):**
|
||||
```bash
|
||||
@@ -46,27 +72,6 @@ Error: Phase ${PHASE_ARG} not found. Run /gsd:progress to see available phases.
|
||||
|
||||
This runs BEFORE config gate check so user errors are surfaced immediately regardless of config state.
|
||||
|
||||
Parse optional flags from $ARGUMENTS using the typed flag parser:
|
||||
|
||||
```bash
|
||||
# Parse all code-review flags into a structured IR via code-review-flags.cjs.
|
||||
# This is the canonical flag-parsing surface — do not replicate inline bash parsing
|
||||
# for --fix/--all/--auto here; the module handles all flag extraction and implication
|
||||
# logic (e.g., --all and --auto imply --fix).
|
||||
FLAGS_JSON=$(node -e "
|
||||
const { parseCodeReviewFlags } = require('./gsd-core/bin/lib/code-review-flags.cjs');
|
||||
const flags = parseCodeReviewFlags(process.argv.slice(1));
|
||||
process.stdout.write(JSON.stringify(flags));
|
||||
" -- "$@" 2>/dev/null)
|
||||
|
||||
# Extract individual flag values from the IR
|
||||
FIX_FLAG=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).fix))")
|
||||
FIX_ALL=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).all))")
|
||||
FIX_AUTO=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(String(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).auto))")
|
||||
DEPTH_OVERRIDE=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).depth)")
|
||||
FILES_OVERRIDE=$(echo "$FLAGS_JSON" | node -e "process.stdout.write(JSON.parse(require('fs').readFileSync('/dev/stdin','utf-8')).files)")
|
||||
```
|
||||
|
||||
If FILES_OVERRIDE is set, split by comma into array:
|
||||
```bash
|
||||
if [ -n "$FILES_OVERRIDE" ]; then
|
||||
@@ -389,20 +394,7 @@ Exit workflow. Do NOT spawn agent or create REVIEW.md.
|
||||
<step name="structural_pre_pass">
|
||||
Optional structural cross-module pass powered by fallow.
|
||||
|
||||
Read fallow config gates:
|
||||
```bash
|
||||
FALLOW_ENABLED=$(gsd_run query config-get code_quality.fallow.enabled 2>/dev/null || echo "false")
|
||||
FALLOW_SCOPE=$(gsd_run query config-get code_quality.fallow.scope 2>/dev/null || echo "phase")
|
||||
FALLOW_PROFILE=$(gsd_run query config-get code_quality.fallow.profile 2>/dev/null || echo "standard")
|
||||
FALLOW_MCP=$(gsd_run query config-get code_quality.fallow.mcp 2>/dev/null || echo "false")
|
||||
# profile maps to a --max-crap threshold since fallow has no native profile concept.
|
||||
# minimal=50 (more lenient), standard=30 (default), strict=15 (tighter).
|
||||
case "$FALLOW_PROFILE" in
|
||||
minimal) FALLOW_MAX_CRAP=50 ;;
|
||||
strict) FALLOW_MAX_CRAP=15 ;;
|
||||
*) FALLOW_MAX_CRAP=30 ;; # standard (default)
|
||||
esac
|
||||
```
|
||||
Parse `fallow_enabled`, `fallow_scope`, `fallow_profile`, `fallow_mcp`, `fallow_max_crap` from the init JSON as `FALLOW_ENABLED`, `FALLOW_SCOPE`, `FALLOW_PROFILE`, `FALLOW_MCP`, `FALLOW_MAX_CRAP`. These are resolved once by `init.code-review` at init time — consuming the pre-resolved values here (instead of a `config-get` call inside this step) avoids gating this section's own inclusion on a fact its own body would otherwise compute (see `state:fallow-enabled` in docs/reference/workflow-fragments.md).
|
||||
|
||||
Defaults are fail-closed and opt-in:
|
||||
- `enabled=false` (skip entirely)
|
||||
@@ -410,88 +402,9 @@ Defaults are fail-closed and opt-in:
|
||||
- `profile=standard` (maps to `--max-crap 30`; minimal=50, standard=30, strict=15 — fallow has no native profile concept)
|
||||
- `mcp=false`
|
||||
|
||||
When `FALLOW_ENABLED=true`:
|
||||
|
||||
1) Resolve binary via PATH first, then `node_modules/.bin/fallow`.
|
||||
```bash
|
||||
FALLOW_BIN=$(FALLOW_CWD="$(pwd)" node -e "
|
||||
const { resolveFallowBinary } = require('./gsd-core/bin/lib/fallow-runner.cjs');
|
||||
const resolved = resolveFallowBinary({ cwd: process.env.FALLOW_CWD });
|
||||
if (resolved) process.stdout.write(resolved);
|
||||
")
|
||||
```
|
||||
|
||||
2) If binary is missing, fail with actionable message:
|
||||
```bash
|
||||
if [ -z \"$FALLOW_BIN\" ]; then
|
||||
echo \"Error: fallow is enabled but no binary was found.\"
|
||||
echo \"Install fallow via \`npm install -D fallow\` or \`cargo install fallow\`.\"
|
||||
# Exit workflow
|
||||
fi
|
||||
```
|
||||
|
||||
3) Execute structural pass and persist JSON (bounded at 120s). Note: `fallow audit` exits 0 when clean and 1 when issues are found — BOTH are successful runs. Only a timeout (124), usage error (2), or crash yields no usable JSON; success is decided by whether the output parses as a valid fallow report, not by exit code:
|
||||
```bash
|
||||
FALLOW_JSON_PATH="${PHASE_DIR}/FALLOW.json"
|
||||
FALLOW_STDERR_TMP=$(mktemp)
|
||||
|
||||
# Phase scope uses fallow's native changed-files scoping (--changed-since <base>).
|
||||
# Derive the phase base commit; if none is found, fall back to repo scope (fallow
|
||||
# auto-detects the base branch).
|
||||
FALLOW_SCOPE_ARGS=()
|
||||
if [ \"$FALLOW_SCOPE\" = \"phase\" ]; then
|
||||
FALLOW_PHASE_COMMITS=$(git log --oneline --all --grep=\"${PADDED_PHASE}\" --format=\"%H\" 2>/dev/null)
|
||||
if [ -n \"$FALLOW_PHASE_COMMITS\" ]; then
|
||||
FALLOW_BASE=$(echo \"$FALLOW_PHASE_COMMITS\" | tail -1)^
|
||||
FALLOW_SCOPE_ARGS=(--changed-since \"$FALLOW_BASE\")
|
||||
fi
|
||||
fi
|
||||
|
||||
gsd_run run-with-timeout 120 -- \"$FALLOW_BIN\" audit --format json --quiet --max-crap \"$FALLOW_MAX_CRAP\" \"${FALLOW_SCOPE_ARGS[@]+\"${FALLOW_SCOPE_ARGS[@]}\"}\" > \"${FALLOW_JSON_PATH}.tmp\" 2>\"$FALLOW_STDERR_TMP\"
|
||||
FALLOW_EXIT=$?
|
||||
|
||||
# fallow exits 0 (clean) or 1 (issues found) — BOTH are successful runs that produce a
|
||||
# valid JSON report. Only a timeout (124), usage error (2), or crash yields no usable JSON.
|
||||
# Decide success by whether the output parses as a fallow report, not by exit code.
|
||||
FALLOW_OK=$(FALLOW_TMP=\"${FALLOW_JSON_PATH}.tmp\" node -e \"
|
||||
try {
|
||||
const fs = require('fs');
|
||||
const txt = fs.readFileSync(process.env.FALLOW_TMP, 'utf8');
|
||||
const o = JSON.parse(txt);
|
||||
process.stdout.write(o && typeof o === 'object' && 'verdict' in o ? '1' : '0');
|
||||
} catch { process.stdout.write('0'); }
|
||||
\")
|
||||
if [ \"$FALLOW_OK\" != \"1\" ]; then
|
||||
FALLOW_STDERR_SUMMARY=$(head -5 \"$FALLOW_STDERR_TMP\")
|
||||
rm -f \"${FALLOW_JSON_PATH}.tmp\" \"$FALLOW_STDERR_TMP\"
|
||||
# #2667: distinguish a hard EXECUTION failure (the binary was found at step 1
|
||||
# but would not run) from the binary-missing path (step 2). Exit 124 = timeout,
|
||||
# 2 = usage error, 125 = spawn failure (e.g. Windows EINVAL on a .cmd shim —
|
||||
# CVE-2024-27980, now mediated by run-with-timeout), 126/127 = not executable /
|
||||
# not found. A non-zero exit here with a resolved binary means fallow is
|
||||
# installed but did not produce a report — surface that loudly so a Windows
|
||||
# user does not mistake it for "fallow absent".
|
||||
case \"$FALLOW_EXIT\" in
|
||||
124) FALLOW_FAIL_KIND=\"timed out\" ;;
|
||||
2) FALLOW_FAIL_KIND=\"usage error\" ;;
|
||||
125) FALLOW_FAIL_KIND=\"spawn failure (the binary was found but did not start — e.g. a Windows .cmd shim; run-with-timeout mediates this)\" ;;
|
||||
126) FALLOW_FAIL_KIND=\"not executable\" ;;
|
||||
127) FALLOW_FAIL_KIND=\"not found\" ;;
|
||||
*) FALLOW_FAIL_KIND=\"crashed\" ;;
|
||||
esac
|
||||
echo \"WARNING: fallow structural pre-pass failed (${FALLOW_FAIL_KIND}, exit ${FALLOW_EXIT}): ${FALLOW_STDERR_SUMMARY}\"
|
||||
FALLOW_JSON_PATH=\"\"
|
||||
else
|
||||
mv \"${FALLOW_JSON_PATH}.tmp\" \"$FALLOW_JSON_PATH\"
|
||||
rm -f \"$FALLOW_STDERR_TMP\"
|
||||
fi
|
||||
```
|
||||
|
||||
On any failure of the structural pre-pass (binary missing at step 2, or an execution failure here — timeout, spawn failure, crash, empty output, or unparseable JSON), the workflow continues with no `<structural_findings>` injection; the reviewer agent receives a normal review request. The WARNING above names the failure KIND so a hard execution failure (e.g. a Windows `.cmd` spawn failure) is not mistaken for an absent optional dependency.
|
||||
|
||||
4) Optional MCP bridge path (runtime-dependent):
|
||||
- If `FALLOW_MCP=true`, set reviewer input mode to MCP-backed structural findings.
|
||||
- Otherwise pass static JSON findings from `FALLOW.json`.
|
||||
<!-- gsd:section id="structural-pre-pass" when="state:fallow-enabled" -->
|
||||
If `section_manifest` is `null` or `"structural-pre-pass"` is in its `included` list: read and execute `gsd-core/workflows/code-review/steps/structural-pre-pass.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
When disabled, set:
|
||||
```bash
|
||||
@@ -636,45 +549,9 @@ fi
|
||||
```
|
||||
</step>
|
||||
|
||||
<step name="dispatch_fix">
|
||||
If the `--fix` flag was passed (`FIX_FLAG=true`), delegate to the `code-review-fix.md` workflow
|
||||
to auto-apply findings from the REVIEW.md that was just written (or that already existed).
|
||||
|
||||
This step runs AFTER `commit_review` so REVIEW.md is guaranteed to be on disk before the fixer
|
||||
is invoked. If REVIEW.md was not created (agent failed, scope was empty, etc.), the `code-review-fix.md`
|
||||
workflow handles the missing-review error and exits cleanly.
|
||||
|
||||
```bash
|
||||
if [ "$FIX_FLAG" = "true" ]; then
|
||||
echo ""
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo " --fix: delegating to code-review-fix.md"
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
|
||||
# Build the fix sub-arguments: pass phase arg plus any --all/--auto flags
|
||||
FIX_ARGS="${PHASE_ARG}"
|
||||
if [ "$FIX_ALL" = "true" ]; then
|
||||
FIX_ARGS="${FIX_ARGS} --all"
|
||||
fi
|
||||
if [ "$FIX_AUTO" = "true" ]; then
|
||||
FIX_ARGS="${FIX_ARGS} --auto"
|
||||
fi
|
||||
|
||||
# Load and execute the code-review-fix workflow.
|
||||
# The fix workflow is the canonical implementation for all fix logic:
|
||||
# gsd-code-fixer agent dispatch, --auto iteration loop, REVIEW-FIX.md commit,
|
||||
# and result presentation. Do not duplicate that logic here.
|
||||
Workflow(workflow="gsd-core/workflows/code-review-fix.md", args="${FIX_ARGS}")
|
||||
|
||||
# Exit after fix workflow completes — present_results is for review-only output.
|
||||
# The fix workflow has its own present_results step.
|
||||
# Exit workflow.
|
||||
fi
|
||||
```
|
||||
|
||||
If `FIX_FLAG` is false, skip this step entirely and proceed to `present_results`.
|
||||
</step>
|
||||
<!-- gsd:section id="dispatch-fix" when="flag:--fix" -->
|
||||
If `section_manifest` is `null` or `"dispatch-fix"` is in its `included` list: read and execute `gsd-core/workflows/code-review/steps/dispatch-fix.md`. Otherwise skip — do not read the file; proceed to `present_results`.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
<step name="present_results">
|
||||
Read the REVIEW.md YAML frontmatter to extract finding counts.
|
||||
|
||||
39
gsd-core/workflows/code-review/steps/dispatch-fix.md
Normal file
39
gsd-core/workflows/code-review/steps/dispatch-fix.md
Normal file
@@ -0,0 +1,39 @@
|
||||
<step name="dispatch_fix">
|
||||
If the `--fix` flag was passed (`FIX_FLAG=true`), delegate to the `code-review-fix.md` workflow
|
||||
to auto-apply findings from the REVIEW.md that was just written (or that already existed).
|
||||
|
||||
This step runs AFTER `commit_review` so REVIEW.md is guaranteed to be on disk before the fixer
|
||||
is invoked. If REVIEW.md was not created (agent failed, scope was empty, etc.), the `code-review-fix.md`
|
||||
workflow handles the missing-review error and exits cleanly.
|
||||
|
||||
```bash
|
||||
if [ "$FIX_FLAG" = "true" ]; then
|
||||
echo ""
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo " --fix: delegating to code-review-fix.md"
|
||||
echo "─────────────────────────────────────────────────────────────────"
|
||||
echo ""
|
||||
|
||||
# Build the fix sub-arguments: pass phase arg plus any --all/--auto flags
|
||||
FIX_ARGS="${PHASE_ARG}"
|
||||
if [ "$FIX_ALL" = "true" ]; then
|
||||
FIX_ARGS="${FIX_ARGS} --all"
|
||||
fi
|
||||
if [ "$FIX_AUTO" = "true" ]; then
|
||||
FIX_ARGS="${FIX_ARGS} --auto"
|
||||
fi
|
||||
|
||||
# Load and execute the code-review-fix workflow.
|
||||
# The fix workflow is the canonical implementation for all fix logic:
|
||||
# gsd-code-fixer agent dispatch, --auto iteration loop, REVIEW-FIX.md commit,
|
||||
# and result presentation. Do not duplicate that logic here.
|
||||
Workflow(workflow="gsd-core/workflows/code-review-fix.md", args="${FIX_ARGS}")
|
||||
|
||||
# Exit after fix workflow completes — present_results is for review-only output.
|
||||
# The fix workflow has its own present_results step.
|
||||
# Exit workflow.
|
||||
fi
|
||||
```
|
||||
|
||||
If `FIX_FLAG` is false, skip this step entirely and proceed to `present_results`.
|
||||
</step>
|
||||
83
gsd-core/workflows/code-review/steps/structural-pre-pass.md
Normal file
83
gsd-core/workflows/code-review/steps/structural-pre-pass.md
Normal file
@@ -0,0 +1,83 @@
|
||||
When `FALLOW_ENABLED=true`:
|
||||
|
||||
1) Resolve binary via PATH first, then `node_modules/.bin/fallow`.
|
||||
```bash
|
||||
FALLOW_BIN=$(FALLOW_CWD="$(pwd)" node -e "
|
||||
const { resolveFallowBinary } = require('./gsd-core/bin/lib/fallow-runner.cjs');
|
||||
const resolved = resolveFallowBinary({ cwd: process.env.FALLOW_CWD });
|
||||
if (resolved) process.stdout.write(resolved);
|
||||
")
|
||||
```
|
||||
|
||||
2) If binary is missing, fail with actionable message:
|
||||
```bash
|
||||
if [ -z \"$FALLOW_BIN\" ]; then
|
||||
echo \"Error: fallow is enabled but no binary was found.\"
|
||||
echo \"Install fallow via \`npm install -D fallow\` or \`cargo install fallow\`.\"
|
||||
# Exit workflow
|
||||
fi
|
||||
```
|
||||
|
||||
3) Execute structural pass and persist JSON (bounded at 120s). Note: `fallow audit` exits 0 when clean and 1 when issues are found — BOTH are successful runs. Only a timeout (124), usage error (2), or crash yields no usable JSON; success is decided by whether the output parses as a valid fallow report, not by exit code:
|
||||
```bash
|
||||
_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
|
||||
FALLOW_JSON_PATH="${PHASE_DIR}/FALLOW.json"
|
||||
FALLOW_STDERR_TMP=$(mktemp)
|
||||
|
||||
# Phase scope uses fallow's native changed-files scoping (--changed-since <base>).
|
||||
# Derive the phase base commit; if none is found, fall back to repo scope (fallow
|
||||
# auto-detects the base branch).
|
||||
FALLOW_SCOPE_ARGS=()
|
||||
if [ \"$FALLOW_SCOPE\" = \"phase\" ]; then
|
||||
FALLOW_PHASE_COMMITS=$(git log --oneline --all --grep=\"${PADDED_PHASE}\" --format=\"%H\" 2>/dev/null)
|
||||
if [ -n \"$FALLOW_PHASE_COMMITS\" ]; then
|
||||
FALLOW_BASE=$(echo \"$FALLOW_PHASE_COMMITS\" | tail -1)^
|
||||
FALLOW_SCOPE_ARGS=(--changed-since \"$FALLOW_BASE\")
|
||||
fi
|
||||
fi
|
||||
|
||||
gsd_run run-with-timeout 120 -- \"$FALLOW_BIN\" audit --format json --quiet --max-crap \"$FALLOW_MAX_CRAP\" \"${FALLOW_SCOPE_ARGS[@]+\"${FALLOW_SCOPE_ARGS[@]}\"}\" > \"${FALLOW_JSON_PATH}.tmp\" 2>\"$FALLOW_STDERR_TMP\"
|
||||
FALLOW_EXIT=$?
|
||||
|
||||
# fallow exits 0 (clean) or 1 (issues found) — BOTH are successful runs that produce a
|
||||
# valid JSON report. Only a timeout (124), usage error (2), or crash yields no usable JSON.
|
||||
# Decide success by whether the output parses as a fallow report, not by exit code.
|
||||
FALLOW_OK=$(FALLOW_TMP=\"${FALLOW_JSON_PATH}.tmp\" node -e \"
|
||||
try {
|
||||
const fs = require('fs');
|
||||
const txt = fs.readFileSync(process.env.FALLOW_TMP, 'utf8');
|
||||
const o = JSON.parse(txt);
|
||||
process.stdout.write(o && typeof o === 'object' && 'verdict' in o ? '1' : '0');
|
||||
} catch { process.stdout.write('0'); }
|
||||
\")
|
||||
if [ \"$FALLOW_OK\" != \"1\" ]; then
|
||||
FALLOW_STDERR_SUMMARY=$(head -5 \"$FALLOW_STDERR_TMP\")
|
||||
rm -f \"${FALLOW_JSON_PATH}.tmp\" \"$FALLOW_STDERR_TMP\"
|
||||
# #2667: distinguish a hard EXECUTION failure (the binary was found at step 1
|
||||
# but would not run) from the binary-missing path (step 2). Exit 124 = timeout,
|
||||
# 2 = usage error, 125 = spawn failure (e.g. Windows EINVAL on a .cmd shim —
|
||||
# CVE-2024-27980, now mediated by run-with-timeout), 126/127 = not executable /
|
||||
# not found. A non-zero exit here with a resolved binary means fallow is
|
||||
# installed but did not produce a report — surface that loudly so a Windows
|
||||
# user does not mistake it for "fallow absent".
|
||||
case \"$FALLOW_EXIT\" in
|
||||
124) FALLOW_FAIL_KIND=\"timed out\" ;;
|
||||
2) FALLOW_FAIL_KIND=\"usage error\" ;;
|
||||
125) FALLOW_FAIL_KIND=\"spawn failure (the binary was found but did not start — e.g. a Windows .cmd shim; run-with-timeout mediates this)\" ;;
|
||||
126) FALLOW_FAIL_KIND=\"not executable\" ;;
|
||||
127) FALLOW_FAIL_KIND=\"not found\" ;;
|
||||
*) FALLOW_FAIL_KIND=\"crashed\" ;;
|
||||
esac
|
||||
echo \"WARNING: fallow structural pre-pass failed (${FALLOW_FAIL_KIND}, exit ${FALLOW_EXIT}): ${FALLOW_STDERR_SUMMARY}\"
|
||||
FALLOW_JSON_PATH=\"\"
|
||||
else
|
||||
mv \"${FALLOW_JSON_PATH}.tmp\" \"$FALLOW_JSON_PATH\"
|
||||
rm -f \"$FALLOW_STDERR_TMP\"
|
||||
fi
|
||||
```
|
||||
|
||||
On any failure of the structural pre-pass (binary missing at step 2, or an execution failure here — timeout, spawn failure, crash, empty output, or unparseable JSON), the workflow continues with no `<structural_findings>` injection; the reviewer agent receives a normal review request. The WARNING above names the failure KIND so a hard execution failure (e.g. a Windows `.cmd` spawn failure) is not mistaken for an absent optional dependency.
|
||||
|
||||
4) Optional MCP bridge path (runtime-dependent):
|
||||
- If `FALLOW_MCP=true`, set reviewer input mode to MCP-backed structural findings.
|
||||
- Otherwise pass static JSON findings from `FALLOW.json`.
|
||||
@@ -592,9 +592,11 @@ Use `init milestone-op` for context, or load config directly:
|
||||
```bash
|
||||
INIT=$(gsd_run query init.execute-phase "1")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
INIT_CM=$(gsd_run query init.complete-milestone)
|
||||
if [[ "$INIT_CM" == @file:* ]]; then INIT_CM=$(cat "${INIT_CM#@file:}"); fi
|
||||
```
|
||||
|
||||
Extract `branching_strategy`, `phase_branch_template`, `milestone_branch_template`, and `commit_docs` from init JSON.
|
||||
Extract `branching_strategy`, `phase_branch_template`, `milestone_branch_template`, and `commit_docs` from init JSON. Extract `git_create_tag` and `section_manifest` from `INIT_CM` (used by the `git_tag` step below).
|
||||
|
||||
Detect base branch:
|
||||
```bash
|
||||
@@ -711,40 +713,9 @@ fi
|
||||
|
||||
</step>
|
||||
|
||||
<step name="git_tag">
|
||||
|
||||
<config-check>
|
||||
Read `git.create_tag` via `gsd-tools.cjs query config-get git.create_tag 2>/dev/null || echo "true"`.
|
||||
If the result is `false` → skip this step entirely and proceed to `git_commit_milestone`.
|
||||
</config-check>
|
||||
|
||||
Create git tag:
|
||||
|
||||
```bash
|
||||
# Pre-check: skip if tag already exists (prevents silent failure on retry)
|
||||
if git rev-parse "v[X.Y]" >/dev/null 2>&1; then echo "Tag v[X.Y] already exists, skipping"; exit 0; fi
|
||||
git tag -a v[X.Y] -m "v[X.Y] [Name]
|
||||
|
||||
Delivered: [One sentence]
|
||||
|
||||
Key accomplishments:
|
||||
- [Item 1]
|
||||
- [Item 2]
|
||||
- [Item 3]
|
||||
|
||||
See .planning/MILESTONES.md for full details."
|
||||
```
|
||||
|
||||
Confirm: "Tagged: v[X.Y]"
|
||||
|
||||
Ask: "Push tag to remote? (y/n)"
|
||||
|
||||
If yes:
|
||||
```bash
|
||||
git push origin v[X.Y]
|
||||
```
|
||||
|
||||
</step>
|
||||
<!-- gsd:section id="git-tag" when="state:git-create-tag" -->
|
||||
If `section_manifest` is `null` or `"git-tag"` is in its `included` list: read and execute `gsd-core/workflows/complete-milestone/steps/git-tag.md`. Otherwise skip — do not read the file; proceed to `git_commit_milestone`.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
<step name="git_commit_milestone">
|
||||
|
||||
|
||||
29
gsd-core/workflows/complete-milestone/steps/git-tag.md
Normal file
29
gsd-core/workflows/complete-milestone/steps/git-tag.md
Normal file
@@ -0,0 +1,29 @@
|
||||
<step name="git_tag">
|
||||
|
||||
Create git tag:
|
||||
|
||||
```bash
|
||||
# Pre-check: skip if tag already exists (prevents silent failure on retry)
|
||||
if git rev-parse "v[X.Y]" >/dev/null 2>&1; then echo "Tag v[X.Y] already exists, skipping"; exit 0; fi
|
||||
git tag -a v[X.Y] -m "v[X.Y] [Name]
|
||||
|
||||
Delivered: [One sentence]
|
||||
|
||||
Key accomplishments:
|
||||
- [Item 1]
|
||||
- [Item 2]
|
||||
- [Item 3]
|
||||
|
||||
See .planning/MILESTONES.md for full details."
|
||||
```
|
||||
|
||||
Confirm: "Tagged: v[X.Y]"
|
||||
|
||||
Ask: "Push tag to remote? (y/n)"
|
||||
|
||||
If yes:
|
||||
```bash
|
||||
git push origin v[X.Y]
|
||||
```
|
||||
|
||||
</step>
|
||||
@@ -64,7 +64,8 @@ Phase number from argument (required).
|
||||
```bash
|
||||
_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
|
||||
RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/dev/null || echo "")
|
||||
INIT=$(gsd_run query init.phase-op "${PHASE}")
|
||||
AUTO_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--auto([[:space:]]|$) ]]; then AUTO_PARAM="--auto"; fi
|
||||
INIT=$(gsd_run query init.discuss-phase-assumptions "${PHASE}" $AUTO_PARAM)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_ANALYZER=$(gsd_run query agent-skills gsd-assumptions-analyzer)
|
||||
# #2072: resolve the routed model so model_overrides / models.discuss are honored
|
||||
@@ -649,21 +650,9 @@ Check for auto-advance trigger:
|
||||
gsd_run query config-set workflow._auto_chain_active true
|
||||
```
|
||||
|
||||
**If `--auto` flag present OR `AUTO_MODE` is true:**
|
||||
|
||||
Display banner:
|
||||
```text
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► AUTO-ADVANCING TO PLAN
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Context captured (assumptions mode). Launching plan-phase...
|
||||
```
|
||||
|
||||
Launch: `Skill(skill="gsd-plan-phase", args="${PHASE} --auto")`
|
||||
|
||||
Handle return: PHASE COMPLETE / PLANNING COMPLETE / INCONCLUSIVE / GAPS FOUND
|
||||
(identical handling to discuss-phase.md auto_advance step)
|
||||
<!-- gsd:section id="auto-advance-dispatch" when="state:auto-advance-active" -->
|
||||
If `section_manifest` is `null` or `"auto-advance-dispatch"` is in its `included` list: read and execute `gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
**If neither `--auto` nor config enabled:**
|
||||
End here — `confirm_creation` already ran; do not route back to it.
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
**If `--auto` flag present OR `AUTO_MODE` is true:**
|
||||
|
||||
Display banner:
|
||||
```text
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► AUTO-ADVANCING TO PLAN
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Context captured (assumptions mode). Launching plan-phase...
|
||||
```
|
||||
|
||||
Launch: `Skill(skill="gsd-plan-phase", args="${PHASE} --auto")`
|
||||
|
||||
Handle return: PHASE COMPLETE / PLANNING COMPLETE / INCONCLUSIVE / GAPS FOUND
|
||||
(identical handling to discuss-phase.md auto_advance step)
|
||||
@@ -18,6 +18,10 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
|
||||
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:
|
||||
@@ -27,6 +31,7 @@ Extract from init JSON:
|
||||
- `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
|
||||
</step>
|
||||
@@ -710,57 +715,9 @@ If any agent failed or its file is missing, note the failure and continue. Missi
|
||||
Continue to dispatch_monorepo_packages (if monorepo_workspaces is non-empty) or commit_docs.
|
||||
</step>
|
||||
|
||||
<step name="dispatch_monorepo_packages" condition="monorepo_workspaces is non-empty">
|
||||
After Wave 2 collection, generate per-package READMEs for each monorepo workspace.
|
||||
|
||||
**Condition:** Only run this step if `monorepo_workspaces` from the init JSON is non-empty.
|
||||
|
||||
**Resolve workspace packages from glob patterns:**
|
||||
|
||||
```bash
|
||||
# Expand workspace globs to actual package directories
|
||||
for pattern in {monorepo_workspaces}; do
|
||||
ls -d $pattern 2>/dev/null
|
||||
done
|
||||
```
|
||||
|
||||
**For each resolved directory that contains a `package.json`:**
|
||||
|
||||
Determine mode:
|
||||
- If `{package_dir}/README.md` exists: mode = `update`, read existing content
|
||||
- Else: mode = `create`
|
||||
|
||||
Spawn a `gsd-doc-writer` agent with `run_in_background=true`:
|
||||
|
||||
```
|
||||
Agent(
|
||||
subagent_type="gsd-doc-writer",
|
||||
model="{doc_writer_model}",
|
||||
run_in_background=true,
|
||||
description="Generate per-package README for {package_dir}",
|
||||
prompt="<doc_assignment>
|
||||
type: readme
|
||||
mode: {create|update}
|
||||
scope: per_package
|
||||
package_dir: {absolute path to package directory}
|
||||
project_context: {INIT JSON with project_root set to package directory}
|
||||
{existing_content: | (include full README.md content here if mode is update, else omit)}
|
||||
</doc_assignment>
|
||||
|
||||
{AGENT_SKILLS}
|
||||
|
||||
Write {package_dir}/README.md directly. Return confirmation only — do not return doc content."
|
||||
)
|
||||
```
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling all per-package Agent() calls above with `run_in_background=true`, do NOT generate any package READMEs independently while the subagents are active. Wait for all agents to complete before proceeding. This prevents duplicate work and wasted context.
|
||||
|
||||
Collect confirmations by reading each package agent's `outputFile` once it reports completion — each `run_in_background=true` Agent call returns an `async_launched` result carrying an `outputFile` path (with `canReadOutputFile: true`). Note failures in the final report.
|
||||
|
||||
**Fallback when Task tool is unavailable:** Generate per-package READMEs sequentially inline after the `sequential_generation` step. For each package directory with a `package.json`, construct the equivalent `doc_assignment` block and generate the README following gsd-doc-writer instructions.
|
||||
|
||||
Continue to commit_docs.
|
||||
</step>
|
||||
<!-- gsd:section id="dispatch-monorepo-packages" when="state:is-monorepo" -->
|
||||
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.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
<step name="sequential_generation" condition="Task tool is NOT available (e.g. Antigravity, Gemini CLI, Codex, Copilot)">
|
||||
**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.
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<step name="dispatch_monorepo_packages" condition="monorepo_workspaces is non-empty">
|
||||
After Wave 2 collection, generate per-package READMEs for each monorepo workspace.
|
||||
|
||||
**Condition:** Only run this step if `monorepo_workspaces` from the init JSON is non-empty.
|
||||
|
||||
**Resolve workspace packages from glob patterns:**
|
||||
|
||||
```bash
|
||||
# Expand workspace globs to actual package directories
|
||||
for pattern in {monorepo_workspaces}; do
|
||||
ls -d $pattern 2>/dev/null
|
||||
done
|
||||
```
|
||||
|
||||
**For each resolved directory that contains a `package.json`:**
|
||||
|
||||
Determine mode:
|
||||
- If `{package_dir}/README.md` exists: mode = `update`, read existing content
|
||||
- Else: mode = `create`
|
||||
|
||||
Spawn a `gsd-doc-writer` agent with `run_in_background=true`:
|
||||
|
||||
```
|
||||
Agent(
|
||||
subagent_type="gsd-doc-writer",
|
||||
model="{doc_writer_model}",
|
||||
run_in_background=true,
|
||||
description="Generate per-package README for {package_dir}",
|
||||
prompt="<doc_assignment>
|
||||
type: readme
|
||||
mode: {create|update}
|
||||
scope: per_package
|
||||
package_dir: {absolute path to package directory}
|
||||
project_context: {INIT JSON with project_root set to package directory}
|
||||
{existing_content: | (include full README.md content here if mode is update, else omit)}
|
||||
</doc_assignment>
|
||||
|
||||
{AGENT_SKILLS}
|
||||
|
||||
Write {package_dir}/README.md directly. Return confirmation only — do not return doc content."
|
||||
)
|
||||
```
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling all per-package Agent() calls above with `run_in_background=true`, do NOT generate any package READMEs independently while the subagents are active. Wait for all agents to complete before proceeding. This prevents duplicate work and wasted context.
|
||||
|
||||
Collect confirmations by reading each package agent's `outputFile` once it reports completion — each `run_in_background=true` Agent call returns an `async_launched` result carrying an `outputFile` path (with `canReadOutputFile: true`). Note failures in the final report.
|
||||
|
||||
**Fallback when Task tool is unavailable:** Generate per-package READMEs sequentially inline after the `sequential_generation` step. For each package directory with a `package.json`, construct the equivalent `doc_assignment` block and generate the README following gsd-doc-writer instructions.
|
||||
|
||||
Continue to commit_docs.
|
||||
</step>
|
||||
@@ -32,9 +32,20 @@ Parse `GSD_WS` and `MILESTONE_ARG` using the established idiom (see `verify-work
|
||||
```bash
|
||||
_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
|
||||
GSD_WS=""
|
||||
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[^[:space:]]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[^[:space:]]+')
|
||||
MILESTONE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[^[:space:]]+//g' | xargs)
|
||||
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
|
||||
MILESTONE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
|
||||
RESPONSE_LANGUAGE=$(gsd_run query config-get response_language --default "" 2>/dev/null || echo "")
|
||||
# #2994: EARLY, section-manifest-only init.new-milestone call — needed here
|
||||
# (before Step 4) to gate the project-md-milestone-write section. This is
|
||||
# DELIBERATELY separate from Step 7's full init.new-milestone call below,
|
||||
# which must stay AFTER Step 6's phase archival/phases.clear so its
|
||||
# phase_dir_count / roadmap_exists / latest_completed_milestone fields
|
||||
# reflect POST-archival state — moving that call here would compute those
|
||||
# fields too early and corrupt the roadmapper's phase-numbering context.
|
||||
# init.new-milestone is a pure read (no mutation), so calling it twice is
|
||||
# safe; only `section_manifest` is consumed from this early call.
|
||||
INIT_EARLY=$(gsd_run query init.new-milestone)
|
||||
if [[ "$INIT_EARLY" == @file:* ]]; then INIT_EARLY=$(cat "${INIT_EARLY#@file:}"); fi
|
||||
```
|
||||
|
||||
`GSD_WS` must chain to every downstream routing suggestion in this workflow (Step 4's shared-file guard, and the `/gsd:discuss-phase`/`/gsd:plan-phase` routing hints below) per the routing-propagation contract in `references/workstream-flag.md` — never let it silently drop.
|
||||
@@ -151,22 +162,9 @@ AskUserQuestion:
|
||||
|
||||
PROJECT.md is shared across workstreams (`references/workstream-flag.md` marks it `# Shared` in the directory diagram). This step has two independently-scoped parts — only Part A is workstream-guarded.
|
||||
|
||||
**Part A — milestone-state write (skip when a workstream is active).** Skip Part A if `GSD_WS` is non-empty (parsed in Step 1). The active workstream's own `.planning/workstreams/<name>/STATE.md`/`ROADMAP.md`/`REQUIREMENTS.md` already carry this milestone's state. Writing a `## Current Milestone` heading here would clobber the shared file, and with parallel milestones across workstreams, whichever workstream runs `new-milestone` last would silently win the shared heading (#2308). In flat mode (`GSD_WS` empty), run Part A exactly as before:
|
||||
|
||||
Add/update:
|
||||
|
||||
```markdown
|
||||
## Current Milestone: v[X.Y] [Name]
|
||||
|
||||
**Goal:** [One sentence describing milestone focus]
|
||||
|
||||
**Target features:**
|
||||
- [Feature 1]
|
||||
- [Feature 2]
|
||||
- [Feature 3]
|
||||
```
|
||||
|
||||
Update Active requirements section and "Last updated" footer.
|
||||
<!-- gsd:section id="project-md-milestone-write" when="state:flat-mode" -->
|
||||
If `section_manifest` (from `INIT_EARLY`) is `null` or `"project-md-milestone-write"` is in its `included` list: read and execute `gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md`. Otherwise (a workstream is active) skip — do not read the file; Part B below still runs regardless of `GSD_WS`.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
**Part B — Evolution structural repair (always runs, regardless of `GSD_WS`).** `## Evolution` is a shared, idempotent structural section, not workstream state — a pre-Evolution project must be backfilled whether or not a workstream is active, so this part is NOT covered by Part A's skip. Ensure the `## Evolution` section exists in PROJECT.md. If missing (projects created before this feature), add it before the footer:
|
||||
|
||||
@@ -273,7 +271,8 @@ gsd_run query commit "docs: start milestone v[X.Y] [Name]" --files .planning/PRO
|
||||
## 7. Load Context and Resolve Models
|
||||
|
||||
```bash
|
||||
INIT=$(gsd_run query init.new-milestone)
|
||||
RESET_PHASE_NUMBERS_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--reset-phase-numbers([[:space:]]|$) ]]; then RESET_PHASE_NUMBERS_PARAM="--reset-phase-numbers"; fi
|
||||
INIT=$(gsd_run query init.new-milestone $RESET_PHASE_NUMBERS_PARAM)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-project-researcher)
|
||||
AGENT_SKILLS_SYNTHESIZER=$(gsd_run query agent-skills gsd-research-synthesizer)
|
||||
@@ -296,25 +295,9 @@ Proceeding without research subagents — roadmap will be generated inline.
|
||||
```
|
||||
Skip the parallel research spawn step and generate the roadmap inline.
|
||||
|
||||
## 7.5 Reset-phase safety (only when `--reset-phase-numbers`)
|
||||
|
||||
If `--reset-phase-numbers` is active:
|
||||
|
||||
1. Set starting phase number to `1` for the upcoming roadmap.
|
||||
2. If `phase_dir_count > 0`, archive the old phase directories before roadmapping so new `01-*` / `02-*` directories cannot collide with stale milestone directories.
|
||||
|
||||
If `phase_dir_count > 0` and `phase_archive_path` is available:
|
||||
|
||||
```bash
|
||||
mkdir -p "${phase_archive_path}"
|
||||
find .planning/phases -mindepth 1 -maxdepth 1 -type d -exec mv {} "${phase_archive_path}/" \;
|
||||
```
|
||||
|
||||
Then verify `.planning/phases/` no longer contains old milestone directories before continuing.
|
||||
|
||||
If `phase_dir_count > 0` but `phase_archive_path` is missing:
|
||||
- Stop and explain that reset numbering is unsafe without a completed milestone archive target.
|
||||
- Tell the user to complete/archive the previous milestone first, then rerun `/gsd:new-milestone --reset-phase-numbers ${GSD_WS}`.
|
||||
<!-- gsd:section id="reset-phase-safety" when="flag:--reset-phase-numbers" -->
|
||||
If `section_manifest` is `null` or `"reset-phase-safety"` is in its `included` list: read and execute `gsd-core/workflows/new-milestone/steps/reset-phase-safety.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
## 8. Research Decision
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
**Part A — milestone-state write (skip when a workstream is active).** Skip Part A if `GSD_WS` is non-empty (parsed in Step 1). The active workstream's own `.planning/workstreams/<name>/STATE.md`/`ROADMAP.md`/`REQUIREMENTS.md` already carry this milestone's state. Writing a `## Current Milestone` heading here would clobber the shared file, and with parallel milestones across workstreams, whichever workstream runs `new-milestone` last would silently win the shared heading (#2308). In flat mode (`GSD_WS` empty), run Part A exactly as before:
|
||||
|
||||
Add/update:
|
||||
|
||||
```markdown
|
||||
## Current Milestone: v[X.Y] [Name]
|
||||
|
||||
**Goal:** [One sentence describing milestone focus]
|
||||
|
||||
**Target features:**
|
||||
- [Feature 1]
|
||||
- [Feature 2]
|
||||
- [Feature 3]
|
||||
```
|
||||
|
||||
Update Active requirements section and "Last updated" footer.
|
||||
19
gsd-core/workflows/new-milestone/steps/reset-phase-safety.md
Normal file
19
gsd-core/workflows/new-milestone/steps/reset-phase-safety.md
Normal file
@@ -0,0 +1,19 @@
|
||||
## 7.5 Reset-phase safety (only when `--reset-phase-numbers`)
|
||||
|
||||
If `--reset-phase-numbers` is active:
|
||||
|
||||
1. Set starting phase number to `1` for the upcoming roadmap.
|
||||
2. If `phase_dir_count > 0`, archive the old phase directories before roadmapping so new `01-*` / `02-*` directories cannot collide with stale milestone directories.
|
||||
|
||||
If `phase_dir_count > 0` and `phase_archive_path` is available:
|
||||
|
||||
```bash
|
||||
mkdir -p "${phase_archive_path}"
|
||||
find .planning/phases -mindepth 1 -maxdepth 1 -type d -exec mv {} "${phase_archive_path}/" \;
|
||||
```
|
||||
|
||||
Then verify `.planning/phases/` no longer contains old milestone directories before continuing.
|
||||
|
||||
If `phase_dir_count > 0` but `phase_archive_path` is missing:
|
||||
- Stop and explain that reset numbering is unsafe without a completed milestone archive target.
|
||||
- Tell the user to complete/archive the previous milestone first, then rerun `/gsd:new-milestone --reset-phase-numbers ${GSD_WS}`.
|
||||
@@ -15,38 +15,9 @@ Valid GSD subagent types (use exact names — do not fall back to 'general-purpo
|
||||
|
||||
<auto_mode>
|
||||
|
||||
## Auto Mode Detection
|
||||
|
||||
Check if `--auto` flag is present in $ARGUMENTS.
|
||||
|
||||
**If auto mode:**
|
||||
|
||||
- Skip brownfield mapping offer (assume greenfield)
|
||||
- Skip deep questioning (extract context from provided document)
|
||||
- Config: YOLO mode is implicit (skip that question), but ask granularity/git/agents FIRST (Step 2a)
|
||||
- After config: run Steps 6-9 automatically with smart defaults:
|
||||
- Research: Always yes
|
||||
- Requirements: Include all table stakes + features from provided document
|
||||
- Requirements approval: Auto-approve
|
||||
- Roadmap approval: Auto-approve
|
||||
|
||||
**Document requirement:**
|
||||
Auto mode requires an idea document — either:
|
||||
|
||||
- File reference: `/gsd:new-project --auto @prd.md`
|
||||
- Pasted/written text in the prompt
|
||||
|
||||
If no document content provided, error:
|
||||
|
||||
```
|
||||
Error: --auto requires an idea document.
|
||||
|
||||
Usage:
|
||||
/gsd:new-project --auto @your-idea.md
|
||||
/gsd:new-project --auto [paste or write your idea here]
|
||||
|
||||
The document should describe what you want to build.
|
||||
```
|
||||
<!-- gsd:section id="auto-mode-detection" when="flag:--auto" -->
|
||||
If `section_manifest` is `null` or `"auto-mode-detection"` is in its `included` list: read and execute `gsd-core/workflows/new-project/steps/auto-mode-detection.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
</auto_mode>
|
||||
|
||||
@@ -58,7 +29,8 @@ The document should describe what you want to build.
|
||||
|
||||
```bash
|
||||
_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 init.new-project)
|
||||
AUTO_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--auto([[:space:]]|$) ]]; then AUTO_PARAM="--auto"; fi
|
||||
INIT=$(gsd_run query init.new-project $AUTO_PARAM)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_RESEARCHER=$(gsd_run query agent-skills gsd-project-researcher)
|
||||
AGENT_SKILLS_SYNTHESIZER=$(gsd_run query agent-skills gsd-research-synthesizer)
|
||||
@@ -132,202 +104,15 @@ All subsequent references to the project instruction file use `$INSTRUCTION_FILE
|
||||
|
||||
**If auto mode:** Skip to Step 4 (assume greenfield, synthesize PROJECT.md from provided document).
|
||||
|
||||
**If `needs_codebase_map` is true** (from init — existing code detected but no codebase map):
|
||||
|
||||
**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.
|
||||
Use AskUserQuestion:
|
||||
|
||||
- header: "Codebase"
|
||||
- question: "I detected existing code in this directory. Would you like to map the codebase first?"
|
||||
- options:
|
||||
- "Map codebase first" — Run /gsd:map-codebase to understand existing architecture (Recommended)
|
||||
- "Skip mapping" — Proceed with project initialization
|
||||
|
||||
**If "Map codebase first":**
|
||||
|
||||
```
|
||||
Run `/gsd:map-codebase` first, then return to `/gsd:new-project`
|
||||
```
|
||||
|
||||
Exit command.
|
||||
<!-- gsd:section id="codebase-map-offer" when="state:needs-codebase-map" -->
|
||||
If `section_manifest` is `null` or `"codebase-map-offer"` is in its `included` list: read and execute `gsd-core/workflows/new-project/steps/codebase-map-offer.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
**If "Skip mapping" OR `needs_codebase_map` is false:** Continue to Step 3.
|
||||
|
||||
## 2a. Auto Mode Config (auto mode only)
|
||||
|
||||
**If auto mode:** Collect config settings upfront before processing the idea document.
|
||||
|
||||
YOLO mode is implicit (auto = YOLO). Ask remaining config questions:
|
||||
|
||||
**Round 1 — Core settings (3 questions, no Mode question):**
|
||||
|
||||
```
|
||||
AskUserQuestion([
|
||||
{
|
||||
header: "Granularity",
|
||||
question: "How finely should scope be sliced into phases?",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Coarse (Recommended)", description: "Fewer, broader phases (3-5 phases, 1-3 plans each)" },
|
||||
{ label: "Standard", description: "Balanced phase size (5-8 phases, 3-5 plans each)" },
|
||||
{ label: "Fine", description: "Many focused phases (8-12 phases, 5-10 plans each)" }
|
||||
]
|
||||
},
|
||||
{
|
||||
header: "Execution",
|
||||
question: "Run plans in parallel?",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Parallel (Recommended)", description: "Independent plans run simultaneously" },
|
||||
{ label: "Sequential", description: "One plan at a time" }
|
||||
]
|
||||
},
|
||||
{
|
||||
header: "Git Tracking",
|
||||
question: "Commit planning docs to git?",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (Recommended)", description: "Planning docs tracked in version control" },
|
||||
{ label: "No", description: "Keep .planning/ local-only (add to .gitignore)" }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
**Round 2 — Workflow agents (same as Step 5):**
|
||||
|
||||
```
|
||||
AskUserQuestion([
|
||||
{
|
||||
header: "Research",
|
||||
question: "Research before planning each phase? (adds tokens/time)",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (Recommended)", description: "Investigate domain, find patterns, surface gotchas" },
|
||||
{ label: "No", description: "Plan directly from requirements" }
|
||||
]
|
||||
},
|
||||
{
|
||||
header: "Plan Check",
|
||||
question: "Verify plans will achieve their goals? (adds tokens/time)",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (Recommended)", description: "Catch gaps before execution starts" },
|
||||
{ label: "No", description: "Execute plans without verification" }
|
||||
]
|
||||
},
|
||||
{
|
||||
header: "Verifier",
|
||||
question: "Verify work satisfies requirements after each phase? (adds tokens/time)",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (Recommended)", description: "Confirm deliverables match phase goals" },
|
||||
{ label: "No", description: "Trust execution, skip verification" }
|
||||
]
|
||||
},
|
||||
{
|
||||
header: "Drift Guard",
|
||||
question: "Enable the plan drift-guard? It verifies that symbols your plans cite (decorators, classes, functions, CLI flags) actually exist in your source at review time, catching hallucinated names before execution. [Y/n]",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (Recommended)", description: "Resolve symbol references against live source during plan review — catches hallucinated names before execution" },
|
||||
{ label: "No", description: "Skip symbol grounding — plan review proceeds without source verification" }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
// Model profile uses a two-question split because AskUserQuestion enforces a hard
|
||||
// 4-option cap and there are 5 valid profiles (quality, balanced, budget, adaptive,
|
||||
// inherit). Q1 routes between adaptive/standard-tier/inherit; Q2 (shown only when
|
||||
// Q1 = "Standard tier…") picks among the three standard profiles. Mirrors the
|
||||
// /gsd:settings split (#3784, #1516).
|
||||
AskUserQuestion([
|
||||
{
|
||||
header: "AI Models",
|
||||
question: "Which AI models for planning agents?",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Adaptive (Recommended)", description: "Role-based cost optimization: heavy roles use the highest-tier model available on the active runtime, light roles use the cheapest. Best balance of quality and cost across all supported runtimes (Claude, Codex, Gemini, OpenRouter, local)." },
|
||||
{ label: "Standard tier…", description: "Choose Quality, Balanced, or Budget — flat tier applied to all agents" },
|
||||
{ label: "Inherit", description: "Use the current session model for all agents (required for non-Claude runtimes: Codex, Gemini CLI, OpenCode /model, OpenRouter, local models)" }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
**Conditional visibility — model_profile (Q2):**
|
||||
Only ask this question when Q1's answer is "Standard tier…".
|
||||
If Q1 = "Adaptive (Recommended)" → write model_profile=adaptive and SKIP Q2.
|
||||
If Q1 = "Inherit" → write model_profile=inherit and SKIP Q2.
|
||||
If user cancels Q2 after picking "Standard tier…" → leave existing model_profile value unchanged.
|
||||
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Which standard profile? (Quality / Balanced / Budget)",
|
||||
header: "Model Tier",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Quality", description: "Opus everywhere except verification (highest cost) — Claude only" },
|
||||
{ label: "Balanced", description: "Opus for planning, Sonnet for research/execution/verification — Claude only" },
|
||||
{ label: "Budget", description: "Sonnet for writing, Haiku for research/verification (lowest cost) — Claude only" }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
// Map UI choices → config values:
|
||||
// Q1 "Adaptive (Recommended)" → model_profile = "adaptive"
|
||||
// Q1 "Inherit" → model_profile = "inherit"
|
||||
// Q1 "Standard tier…" + Q2 "Quality" → model_profile = "quality"
|
||||
// Q1 "Standard tier…" + Q2 "Balanced" → model_profile = "balanced"
|
||||
// Q1 "Standard tier…" + Q2 "Budget" → model_profile = "budget"
|
||||
```
|
||||
|
||||
**Round 3 — PR body onboarding:**
|
||||
|
||||
Ask which optional PRD-style sections `/gsd:ship` should append to generated PR bodies. These map to `ship.pr_body_sections`; selected sections are written with `"enabled": true`, unselected seeded sections are written with `"enabled": false` so the project can enable them later without editing `ship.md`.
|
||||
|
||||
Prefer lean/agile PRD sections that make the delivered increment clear: user stories, acceptance criteria, Definition of Done or release criteria, risks, dependencies, and stakeholder review.
|
||||
|
||||
```
|
||||
AskUserQuestion([
|
||||
{
|
||||
header: "PR Body",
|
||||
question: "Which optional PRD-style sections should /gsd:ship include in PR bodies?",
|
||||
multiSelect: true,
|
||||
options: [
|
||||
{ label: "User Stories & Acceptance Criteria", description: "Append user-facing stories and acceptance checks from REQUIREMENTS.md" },
|
||||
{ label: "Risks & Dependencies", description: "Append rollout risks, dependencies, and rollback notes from PLAN.md" },
|
||||
{ label: "Success Metrics & Release Criteria", description: "Append measurable Definition of Done and release checks for stakeholder review" },
|
||||
{ label: "Stakeholder Review & Approval", description: "Append approval checklist for projects that need sign-off traceability" }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
Build `ship.pr_body_sections` from those choices. For selected options, set `enabled: true`; for seeded but unselected options, set `enabled: false`. If the user selects none, use `"ship":{"pr_body_sections":[]}`.
|
||||
|
||||
Create `.planning/config.json` with all settings (CLI fills in remaining defaults automatically):
|
||||
|
||||
```bash
|
||||
mkdir -p .planning
|
||||
gsd_run query config-new-project '{"mode":"yolo","granularity":"[selected]","parallelization":true|false,"commit_docs":true|false,"model_profile":"quality|balanced|budget|adaptive|inherit","workflow":{"research":true|false,"plan_check":true|false,"verifier":true|false,"nyquist_validation":true|false,"auto_advance":true},"plan_review":{"source_grounding":true|false},"ship":{"pr_body_sections":[{"heading":"User Stories & Acceptance Criteria","enabled":true|false,"source":"REQUIREMENTS.md ## User Stories || REQUIREMENTS.md ## Acceptance Criteria","fallback":"- Acceptance criteria are covered by the linked requirements and verification evidence."},{"heading":"Risks & Dependencies","enabled":true|false,"source":"PLAN.md ## Risks || PLAN.md ## Dependencies","fallback":"- No known high-risk rollout dependencies."},{"heading":"Success Metrics & Release Criteria","enabled":true|false,"source":"REQUIREMENTS.md ## Definition of Done || VERIFICATION.md ## Release Criteria","fallback":"- Release when automated verification and required manual checks pass."},{"heading":"Stakeholder Review & Approval","enabled":true|false,"template":"- Product owner approval pending for {phase_name}."}]}}'
|
||||
```
|
||||
|
||||
**If commit_docs = No:** Add `.planning/` to `.gitignore`.
|
||||
|
||||
**Commit config.json:**
|
||||
|
||||
```bash
|
||||
mkdir -p .planning
|
||||
gsd_run query commit "chore: add project config" --files .planning/config.json
|
||||
```
|
||||
|
||||
**Persist auto-advance chain flag to config (survives context compaction):**
|
||||
|
||||
```bash
|
||||
gsd_run query config-set workflow._auto_chain_active true
|
||||
```
|
||||
|
||||
Proceed to Step 4 (skip Steps 3 and 5).
|
||||
<!-- gsd:section id="auto-mode-config" when="flag:--auto" -->
|
||||
If `section_manifest` is `null` or `"auto-mode-config"` is in its `included` list: read and execute `gsd-core/workflows/new-project/steps/auto-mode-config.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
## 2b. Prior Spike/Sketch Detection
|
||||
|
||||
|
||||
176
gsd-core/workflows/new-project/steps/auto-mode-config.md
Normal file
176
gsd-core/workflows/new-project/steps/auto-mode-config.md
Normal file
@@ -0,0 +1,176 @@
|
||||
## 2a. Auto Mode Config (auto mode only)
|
||||
|
||||
**If auto mode:** Collect config settings upfront before processing the idea document.
|
||||
|
||||
YOLO mode is implicit (auto = YOLO). Ask remaining config questions:
|
||||
|
||||
**Round 1 — Core settings (3 questions, no Mode question):**
|
||||
|
||||
```
|
||||
AskUserQuestion([
|
||||
{
|
||||
header: "Granularity",
|
||||
question: "How finely should scope be sliced into phases?",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Coarse (Recommended)", description: "Fewer, broader phases (3-5 phases, 1-3 plans each)" },
|
||||
{ label: "Standard", description: "Balanced phase size (5-8 phases, 3-5 plans each)" },
|
||||
{ label: "Fine", description: "Many focused phases (8-12 phases, 5-10 plans each)" }
|
||||
]
|
||||
},
|
||||
{
|
||||
header: "Execution",
|
||||
question: "Run plans in parallel?",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Parallel (Recommended)", description: "Independent plans run simultaneously" },
|
||||
{ label: "Sequential", description: "One plan at a time" }
|
||||
]
|
||||
},
|
||||
{
|
||||
header: "Git Tracking",
|
||||
question: "Commit planning docs to git?",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (Recommended)", description: "Planning docs tracked in version control" },
|
||||
{ label: "No", description: "Keep .planning/ local-only (add to .gitignore)" }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
**Round 2 — Workflow agents (same as Step 5):**
|
||||
|
||||
```
|
||||
AskUserQuestion([
|
||||
{
|
||||
header: "Research",
|
||||
question: "Research before planning each phase? (adds tokens/time)",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (Recommended)", description: "Investigate domain, find patterns, surface gotchas" },
|
||||
{ label: "No", description: "Plan directly from requirements" }
|
||||
]
|
||||
},
|
||||
{
|
||||
header: "Plan Check",
|
||||
question: "Verify plans will achieve their goals? (adds tokens/time)",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (Recommended)", description: "Catch gaps before execution starts" },
|
||||
{ label: "No", description: "Execute plans without verification" }
|
||||
]
|
||||
},
|
||||
{
|
||||
header: "Verifier",
|
||||
question: "Verify work satisfies requirements after each phase? (adds tokens/time)",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (Recommended)", description: "Confirm deliverables match phase goals" },
|
||||
{ label: "No", description: "Trust execution, skip verification" }
|
||||
]
|
||||
},
|
||||
{
|
||||
header: "Drift Guard",
|
||||
question: "Enable the plan drift-guard? It verifies that symbols your plans cite (decorators, classes, functions, CLI flags) actually exist in your source at review time, catching hallucinated names before execution. [Y/n]",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Yes (Recommended)", description: "Resolve symbol references against live source during plan review — catches hallucinated names before execution" },
|
||||
{ label: "No", description: "Skip symbol grounding — plan review proceeds without source verification" }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
// Model profile uses a two-question split because AskUserQuestion enforces a hard
|
||||
// 4-option cap and there are 5 valid profiles (quality, balanced, budget, adaptive,
|
||||
// inherit). Q1 routes between adaptive/standard-tier/inherit; Q2 (shown only when
|
||||
// Q1 = "Standard tier…") picks among the three standard profiles. Mirrors the
|
||||
// /gsd:settings split (#3784, #1516).
|
||||
AskUserQuestion([
|
||||
{
|
||||
header: "AI Models",
|
||||
question: "Which AI models for planning agents?",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Adaptive (Recommended)", description: "Role-based cost optimization: heavy roles use the highest-tier model available on the active runtime, light roles use the cheapest. Best balance of quality and cost across all supported runtimes (Claude, Codex, Gemini, OpenRouter, local)." },
|
||||
{ label: "Standard tier…", description: "Choose Quality, Balanced, or Budget — flat tier applied to all agents" },
|
||||
{ label: "Inherit", description: "Use the current session model for all agents (required for non-Claude runtimes: Codex, Gemini CLI, OpenCode /model, OpenRouter, local models)" }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
**Conditional visibility — model_profile (Q2):**
|
||||
Only ask this question when Q1's answer is "Standard tier…".
|
||||
If Q1 = "Adaptive (Recommended)" → write model_profile=adaptive and SKIP Q2.
|
||||
If Q1 = "Inherit" → write model_profile=inherit and SKIP Q2.
|
||||
If user cancels Q2 after picking "Standard tier…" → leave existing model_profile value unchanged.
|
||||
|
||||
AskUserQuestion([
|
||||
{
|
||||
question: "Which standard profile? (Quality / Balanced / Budget)",
|
||||
header: "Model Tier",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Quality", description: "Opus everywhere except verification (highest cost) — Claude only" },
|
||||
{ label: "Balanced", description: "Opus for planning, Sonnet for research/execution/verification — Claude only" },
|
||||
{ label: "Budget", description: "Sonnet for writing, Haiku for research/verification (lowest cost) — Claude only" }
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
// Map UI choices → config values:
|
||||
// Q1 "Adaptive (Recommended)" → model_profile = "adaptive"
|
||||
// Q1 "Inherit" → model_profile = "inherit"
|
||||
// Q1 "Standard tier…" + Q2 "Quality" → model_profile = "quality"
|
||||
// Q1 "Standard tier…" + Q2 "Balanced" → model_profile = "balanced"
|
||||
// Q1 "Standard tier…" + Q2 "Budget" → model_profile = "budget"
|
||||
```
|
||||
|
||||
**Round 3 — PR body onboarding:**
|
||||
|
||||
Ask which optional PRD-style sections `/gsd:ship` should append to generated PR bodies. These map to `ship.pr_body_sections`; selected sections are written with `"enabled": true`, unselected seeded sections are written with `"enabled": false` so the project can enable them later without editing `ship.md`.
|
||||
|
||||
Prefer lean/agile PRD sections that make the delivered increment clear: user stories, acceptance criteria, Definition of Done or release criteria, risks, dependencies, and stakeholder review.
|
||||
|
||||
```
|
||||
AskUserQuestion([
|
||||
{
|
||||
header: "PR Body",
|
||||
question: "Which optional PRD-style sections should /gsd:ship include in PR bodies?",
|
||||
multiSelect: true,
|
||||
options: [
|
||||
{ label: "User Stories & Acceptance Criteria", description: "Append user-facing stories and acceptance checks from REQUIREMENTS.md" },
|
||||
{ label: "Risks & Dependencies", description: "Append rollout risks, dependencies, and rollback notes from PLAN.md" },
|
||||
{ label: "Success Metrics & Release Criteria", description: "Append measurable Definition of Done and release checks for stakeholder review" },
|
||||
{ label: "Stakeholder Review & Approval", description: "Append approval checklist for projects that need sign-off traceability" }
|
||||
]
|
||||
}
|
||||
])
|
||||
```
|
||||
|
||||
Build `ship.pr_body_sections` from those choices. For selected options, set `enabled: true`; for seeded but unselected options, set `enabled: false`. If the user selects none, use `"ship":{"pr_body_sections":[]}`.
|
||||
|
||||
Create `.planning/config.json` with all settings (CLI fills in remaining defaults automatically):
|
||||
|
||||
```bash
|
||||
_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
|
||||
mkdir -p .planning
|
||||
gsd_run query config-new-project '{"mode":"yolo","granularity":"[selected]","parallelization":true|false,"commit_docs":true|false,"model_profile":"quality|balanced|budget|adaptive|inherit","workflow":{"research":true|false,"plan_check":true|false,"verifier":true|false,"nyquist_validation":true|false,"auto_advance":true},"plan_review":{"source_grounding":true|false},"ship":{"pr_body_sections":[{"heading":"User Stories & Acceptance Criteria","enabled":true|false,"source":"REQUIREMENTS.md ## User Stories || REQUIREMENTS.md ## Acceptance Criteria","fallback":"- Acceptance criteria are covered by the linked requirements and verification evidence."},{"heading":"Risks & Dependencies","enabled":true|false,"source":"PLAN.md ## Risks || PLAN.md ## Dependencies","fallback":"- No known high-risk rollout dependencies."},{"heading":"Success Metrics & Release Criteria","enabled":true|false,"source":"REQUIREMENTS.md ## Definition of Done || VERIFICATION.md ## Release Criteria","fallback":"- Release when automated verification and required manual checks pass."},{"heading":"Stakeholder Review & Approval","enabled":true|false,"template":"- Product owner approval pending for {phase_name}."}]}}'
|
||||
```
|
||||
|
||||
**If commit_docs = No:** Add `.planning/` to `.gitignore`.
|
||||
|
||||
**Commit config.json:**
|
||||
|
||||
```bash
|
||||
mkdir -p .planning
|
||||
gsd_run query commit "chore: add project config" --files .planning/config.json
|
||||
```
|
||||
|
||||
**Persist auto-advance chain flag to config (survives context compaction):**
|
||||
|
||||
```bash
|
||||
gsd_run query config-set workflow._auto_chain_active true
|
||||
```
|
||||
|
||||
Proceed to Step 4 (skip Steps 3 and 5).
|
||||
32
gsd-core/workflows/new-project/steps/auto-mode-detection.md
Normal file
32
gsd-core/workflows/new-project/steps/auto-mode-detection.md
Normal file
@@ -0,0 +1,32 @@
|
||||
## Auto Mode Detection
|
||||
|
||||
Check if `--auto` flag is present in $ARGUMENTS.
|
||||
|
||||
**If auto mode:**
|
||||
|
||||
- Skip brownfield mapping offer (assume greenfield)
|
||||
- Skip deep questioning (extract context from provided document)
|
||||
- Config: YOLO mode is implicit (skip that question), but ask granularity/git/agents FIRST (Step 2a)
|
||||
- After config: run Steps 6-9 automatically with smart defaults:
|
||||
- Research: Always yes
|
||||
- Requirements: Include all table stakes + features from provided document
|
||||
- Requirements approval: Auto-approve
|
||||
- Roadmap approval: Auto-approve
|
||||
|
||||
**Document requirement:**
|
||||
Auto mode requires an idea document — either:
|
||||
|
||||
- File reference: `/gsd:new-project --auto @prd.md`
|
||||
- Pasted/written text in the prompt
|
||||
|
||||
If no document content provided, error:
|
||||
|
||||
```
|
||||
Error: --auto requires an idea document.
|
||||
|
||||
Usage:
|
||||
/gsd:new-project --auto @your-idea.md
|
||||
/gsd:new-project --auto [paste or write your idea here]
|
||||
|
||||
The document should describe what you want to build.
|
||||
```
|
||||
18
gsd-core/workflows/new-project/steps/codebase-map-offer.md
Normal file
18
gsd-core/workflows/new-project/steps/codebase-map-offer.md
Normal file
@@ -0,0 +1,18 @@
|
||||
**If `needs_codebase_map` is true** (from init — existing code detected but no codebase map):
|
||||
|
||||
**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.
|
||||
Use AskUserQuestion:
|
||||
|
||||
- header: "Codebase"
|
||||
- question: "I detected existing code in this directory. Would you like to map the codebase first?"
|
||||
- options:
|
||||
- "Map codebase first" — Run /gsd:map-codebase to understand existing architecture (Recommended)
|
||||
- "Skip mapping" — Proceed with project initialization
|
||||
|
||||
**If "Map codebase first":**
|
||||
|
||||
```
|
||||
Run `/gsd:map-codebase` first, then return to `/gsd:new-project`
|
||||
```
|
||||
|
||||
Exit command.
|
||||
@@ -13,11 +13,12 @@ Read all files referenced by the invoking prompt's execution_context before star
|
||||
|
||||
```bash
|
||||
_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 init.progress)
|
||||
FORENSIC_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--forensic([[:space:]]|$) ]]; then FORENSIC_PARAM="--forensic"; fi
|
||||
INIT=$(gsd_run query init.progress $FORENSIC_PARAM)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
```
|
||||
|
||||
Extract from init JSON: `project_exists`, `roadmap_exists`, `state_exists`, `phases`, `current_phase`, `next_phase`, `milestone_version`, `completed_count`, `phase_count`, `paused_at`, `state_path`, `roadmap_path`, `project_path`, `config_path`.
|
||||
Extract from init JSON: `project_exists`, `roadmap_exists`, `state_exists`, `phases`, `current_phase`, `next_phase`, `milestone_version`, `completed_count`, `phase_count`, `paused_at`, `state_path`, `roadmap_path`, `project_path`, `config_path`, `phase_mvp_mode`.
|
||||
|
||||
```bash
|
||||
DISCUSS_MODE=$(gsd_run query config-get workflow.discuss_mode 2>/dev/null || echo "discuss")
|
||||
@@ -104,7 +105,7 @@ PROGRESS_BAR=$(gsd_run query progress.bar --raw)
|
||||
|
||||
Present:
|
||||
|
||||
```
|
||||
````
|
||||
# [Project Name]
|
||||
|
||||
**Progress:** {PROGRESS_BAR}
|
||||
@@ -150,34 +151,13 @@ Render `Open Windows` only when `$WINDOWS_OPEN` is greater than `0` (or `$WINDOW
|
||||
|
||||
## What's Next
|
||||
[Next phase/plan objective from roadmap analyze]
|
||||
```
|
||||
````
|
||||
|
||||
</step>
|
||||
|
||||
<step name="mvp_display">
|
||||
**MVP-mode display (when phase has `**Mode:** mvp` in ROADMAP.md).**
|
||||
|
||||
Resolve `MVP_MODE` per phase via the centralized resolver. progress has no `--mvp` CLI flag (mode is inherited from the planned phase), so we omit `--cli-flag`:
|
||||
|
||||
```bash
|
||||
MVP_MODE=$(gsd_run query phase.mvp-mode "${PHASE_NUMBER}" --pick active)
|
||||
```
|
||||
|
||||
When `MVP_MODE=true`, the per-phase progress block adds a **user-flow status** sub-block sourced from the phase's PLAN.md task names. Each task whose name reads like a user-visible capability (e.g., "Register flow", "Login flow", "Password reset") is rendered as a status line:
|
||||
|
||||
```
|
||||
Phase 1 — User Auth MVP
|
||||
✅ Walking Skeleton complete ← from SKELETON.md existence
|
||||
✅ Register flow working ← from PLAN.md task with summary
|
||||
✅ Login flow working ← from PLAN.md task with summary
|
||||
🔄 Password reset (in progress) ← from PLAN.md task without summary
|
||||
⬜ Email verification ← from PLAN.md task not yet started
|
||||
```
|
||||
|
||||
**User-flow filter:** Tasks whose names are technical-sounding ("Wire DB schema", "Create migration", "Bump deps") are NOT rendered as user-flow status lines. Heuristic: a task name is user-flow-shaped if it ends in "flow", "page", "screen", or starts with a verb the user would recognize ("Register", "Login", "Upload", "View"). Tasks that fail the heuristic still count toward the standard task progress total but don't appear in the user-flow sub-block.
|
||||
|
||||
When `MVP_MODE=false` (mode is null, absent, or the phase has no `**Mode:**` line), fall back to the standard display path — no behavioral change.
|
||||
</step>
|
||||
<!-- gsd:section id="mvp-display" when="state:phase-mvp-mode" -->
|
||||
If `section_manifest` is `null` or `"mvp-display"` is in its `included` list: read and execute `gsd-core/workflows/progress/steps/mvp-display.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
<step name="route">
|
||||
**Determine next action based on verified counts.**
|
||||
@@ -678,131 +658,9 @@ Ready to plan the next milestone.
|
||||
- Handoff file exists → mention it, offer `/gsd:resume-work ${GSD_WS}`
|
||||
</step>
|
||||
|
||||
<step name="forensic_audit">
|
||||
**Forensic Integrity Audit** — only runs when `--forensic` is present in ARGUMENTS.
|
||||
|
||||
If `--forensic` is NOT present in ARGUMENTS: skip this step entirely. Default progress behavior (standard report + routing) is unchanged.
|
||||
|
||||
If `--forensic` IS present: after the standard report and routing suggestion have been displayed, append the following audit section.
|
||||
|
||||
---
|
||||
|
||||
## Forensic Integrity Audit
|
||||
|
||||
Running 7 deep checks against project state...
|
||||
|
||||
Run each check in order. For each check, emit ✓ (pass) or ⚠ (warning) with concrete evidence when a problem is found.
|
||||
|
||||
**Check 1 — STATE vs artifact consistency**
|
||||
|
||||
Read STATE.md `status` / `stopped_at` fields (from the STATE snapshot already loaded). Compare against the artifact count from the roadmap analysis. If STATE.md claims the current phase is pending/mid-flight but the artifact count shows it as complete (all PLAN.md files have matching SUMMARY.md files), flag inconsistency. Emit:
|
||||
- ✓ `STATE.md consistent with artifact count` — if both agree
|
||||
- ⚠ `STATE.md claims [status] but artifact count shows phase complete` — with the specific values
|
||||
|
||||
**Check 2 — Orphaned handoff files**
|
||||
|
||||
Check for existence of:
|
||||
```bash
|
||||
ls .planning/HANDOFF.json .planning/phases/*/.continue-here.md .planning/phases/*/*HANDOFF*.md 2>/dev/null || true
|
||||
```
|
||||
Also check `.planning/continue-here.md`.
|
||||
|
||||
Emit:
|
||||
- ✓ `No orphaned handoff files` — if none found
|
||||
- ⚠ `Orphaned handoff files found` — list each file path, add: `→ Work was paused mid-flight. Read the handoff before continuing.`
|
||||
|
||||
**Check 3 — Deferred scope drift**
|
||||
|
||||
Search phase artifacts (CONTEXT.md, DISCUSSION-LOG.md, BUG-BRIEF.md, VERIFICATION.md, SUMMARY.md, HANDOFF.md files under `.planning/phases/`) for patterns:
|
||||
```bash
|
||||
grep -rl "defer to Phase\|future phase\|out of scope Phase\|deferred to Phase" .planning/phases/ 2>/dev/null || true
|
||||
```
|
||||
|
||||
For each match, extract the referenced phase number. Cross-reference against ROADMAP.md phase list. If the referenced phase number is NOT in ROADMAP.md, flag as deferred scope not captured.
|
||||
|
||||
Emit:
|
||||
- ✓ `All deferred scope captured in ROADMAP` — if no mismatches
|
||||
- ⚠ `Deferred scope references phase(s) not in ROADMAP` — list: file, reference text, missing phase number
|
||||
|
||||
**Check 4 — Memory-flagged pending work**
|
||||
|
||||
Check if `.planning/MEMORY.md` or `.planning/memory/` exists:
|
||||
```bash
|
||||
ls .planning/MEMORY.md .planning/memory/*.md 2>/dev/null || true
|
||||
```
|
||||
|
||||
If found, grep for entries containing: `pending`, `status`, `deferred`, `not yet run`, `backfill`, `blocking`.
|
||||
|
||||
Emit:
|
||||
- ✓ `No memory entries flagging pending work` — if none found or no MEMORY.md
|
||||
- ⚠ `Memory entries flag pending/deferred work` — list the matching lines (max 5, truncated at 80 chars)
|
||||
|
||||
**Check 5 — Blocking operational todos**
|
||||
|
||||
Check for pending todos:
|
||||
```bash
|
||||
ls .planning/todos/pending/*.md 2>/dev/null || true
|
||||
```
|
||||
|
||||
For files found, scan for keywords indicating operational blockers: `script`, `credential`, `API key`, `manual`, `verification`, `setup`, `configure`, `run `.
|
||||
|
||||
Emit:
|
||||
- ✓ `No blocking operational todos` — if no pending todos or none match operational keywords
|
||||
- ⚠ `Blocking operational todos found` — list the file names and matching keywords (max 5)
|
||||
|
||||
**Check 6 — Uncommitted code**
|
||||
|
||||
```bash
|
||||
git status --porcelain 2>/dev/null | grep -v "^??" | grep -v "^.planning\/" | grep -v "^\.\." | head -10
|
||||
```
|
||||
|
||||
If output is non-empty (modified/staged files outside `.planning/`), flag as uncommitted code.
|
||||
|
||||
Emit:
|
||||
- ✓ `Working tree clean` — if no modified files outside `.planning/`
|
||||
- ⚠ `Uncommitted changes in source files` — list up to 10 file paths
|
||||
|
||||
**Check 7 — Unresolved deferred items**
|
||||
|
||||
Glob every phase directory's SCOPE BOUNDARY log (executor writes out-of-scope discoveries here per `agents/gsd-executor.md`):
|
||||
```bash
|
||||
ls .planning/phases/*/deferred-items.md 2>/dev/null || true
|
||||
```
|
||||
|
||||
For each `deferred-items.md` found, read its entries (bullet list, one entry per top-level `- ` line, continuation lines indented beneath it). An entry is RESOLVED only if it carries an explicit `status: resolved` field (case-insensitive) on one of its lines; every other entry — including one with no `status:` field at all — is UNRESOLVED and must be surfaced (fail-safe: never silently drop a possibly-open item).
|
||||
|
||||
Emit:
|
||||
- ✓ `No unresolved deferred items` — if no `deferred-items.md` files exist, or every entry in every file is `status: resolved`
|
||||
- ⚠ `Unresolved deferred items found` — list each file's phase directory and its unresolved entry text (max 5 per file, truncated at 80 chars)
|
||||
|
||||
---
|
||||
|
||||
After all 7 checks, display the verdict:
|
||||
|
||||
**If all 7 checks passed:**
|
||||
```
|
||||
### Verdict: CLEAN
|
||||
|
||||
The standard progress report is trustworthy — proceed with the routing suggestion above.
|
||||
```
|
||||
|
||||
**If 1 or more checks failed:**
|
||||
```
|
||||
### Verdict: N INTEGRITY ISSUE(S) FOUND
|
||||
|
||||
The standard progress report may not reflect true project state.
|
||||
Review the flagged items above before acting on the routing suggestion.
|
||||
```
|
||||
|
||||
Then for each failed check, add a concrete next action:
|
||||
- Check 2 (orphaned handoff): `Read the handoff file(s) and resume from where work was paused: /gsd:resume-work ${GSD_WS}`
|
||||
- Check 3 (deferred scope): `Add the missing phases to ROADMAP.md or update the deferred references`
|
||||
- Check 4 (memory pending): `Review the flagged memory entries and resolve or clear them`
|
||||
- Check 5 (blocking todos): `Complete the operational steps in .planning/todos/pending/ before continuing`
|
||||
- Check 6 (uncommitted code): `Commit or stash the uncommitted changes before advancing`
|
||||
- Check 7 (unresolved deferred items): `Address each deferred item and mark it status: resolved in its deferred-items.md, or fold it into the roadmap`
|
||||
- Check 1 (STATE inconsistency): `Run /gsd:verify-work ${PHASE} ${GSD_WS} to reconcile state`
|
||||
</step>
|
||||
<!-- gsd:section id="forensic-audit" when="flag:--forensic" -->
|
||||
If `section_manifest` is `null` or `"forensic-audit"` is in its `included` list: read and execute `gsd-core/workflows/progress/steps/forensic-audit.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
</process>
|
||||
|
||||
|
||||
125
gsd-core/workflows/progress/steps/forensic-audit.md
Normal file
125
gsd-core/workflows/progress/steps/forensic-audit.md
Normal file
@@ -0,0 +1,125 @@
|
||||
<step name="forensic_audit">
|
||||
**Forensic Integrity Audit** — only runs when `--forensic` is present in ARGUMENTS.
|
||||
|
||||
If `--forensic` is NOT present in ARGUMENTS: skip this step entirely. Default progress behavior (standard report + routing) is unchanged.
|
||||
|
||||
If `--forensic` IS present: after the standard report and routing suggestion have been displayed, append the following audit section.
|
||||
|
||||
---
|
||||
|
||||
## Forensic Integrity Audit
|
||||
|
||||
Running 7 deep checks against project state...
|
||||
|
||||
Run each check in order. For each check, emit ✓ (pass) or ⚠ (warning) with concrete evidence when a problem is found.
|
||||
|
||||
**Check 1 — STATE vs artifact consistency**
|
||||
|
||||
Read STATE.md `status` / `stopped_at` fields (from the STATE snapshot already loaded). Compare against the artifact count from the roadmap analysis. If STATE.md claims the current phase is pending/mid-flight but the artifact count shows it as complete (all PLAN.md files have matching SUMMARY.md files), flag inconsistency. Emit:
|
||||
- ✓ `STATE.md consistent with artifact count` — if both agree
|
||||
- ⚠ `STATE.md claims [status] but artifact count shows phase complete` — with the specific values
|
||||
|
||||
**Check 2 — Orphaned handoff files**
|
||||
|
||||
Check for existence of:
|
||||
```bash
|
||||
ls .planning/HANDOFF.json .planning/phases/*/.continue-here.md .planning/phases/*/*HANDOFF*.md 2>/dev/null || true
|
||||
```
|
||||
Also check `.planning/continue-here.md`.
|
||||
|
||||
Emit:
|
||||
- ✓ `No orphaned handoff files` — if none found
|
||||
- ⚠ `Orphaned handoff files found` — list each file path, add: `→ Work was paused mid-flight. Read the handoff before continuing.`
|
||||
|
||||
**Check 3 — Deferred scope drift**
|
||||
|
||||
Search phase artifacts (CONTEXT.md, DISCUSSION-LOG.md, BUG-BRIEF.md, VERIFICATION.md, SUMMARY.md, HANDOFF.md files under `.planning/phases/`) for patterns:
|
||||
```bash
|
||||
grep -rl "defer to Phase\|future phase\|out of scope Phase\|deferred to Phase" .planning/phases/ 2>/dev/null || true
|
||||
```
|
||||
|
||||
For each match, extract the referenced phase number. Cross-reference against ROADMAP.md phase list. If the referenced phase number is NOT in ROADMAP.md, flag as deferred scope not captured.
|
||||
|
||||
Emit:
|
||||
- ✓ `All deferred scope captured in ROADMAP` — if no mismatches
|
||||
- ⚠ `Deferred scope references phase(s) not in ROADMAP` — list: file, reference text, missing phase number
|
||||
|
||||
**Check 4 — Memory-flagged pending work**
|
||||
|
||||
Check if `.planning/MEMORY.md` or `.planning/memory/` exists:
|
||||
```bash
|
||||
ls .planning/MEMORY.md .planning/memory/*.md 2>/dev/null || true
|
||||
```
|
||||
|
||||
If found, grep for entries containing: `pending`, `status`, `deferred`, `not yet run`, `backfill`, `blocking`.
|
||||
|
||||
Emit:
|
||||
- ✓ `No memory entries flagging pending work` — if none found or no MEMORY.md
|
||||
- ⚠ `Memory entries flag pending/deferred work` — list the matching lines (max 5, truncated at 80 chars)
|
||||
|
||||
**Check 5 — Blocking operational todos**
|
||||
|
||||
Check for pending todos:
|
||||
```bash
|
||||
ls .planning/todos/pending/*.md 2>/dev/null || true
|
||||
```
|
||||
|
||||
For files found, scan for keywords indicating operational blockers: `script`, `credential`, `API key`, `manual`, `verification`, `setup`, `configure`, `run `.
|
||||
|
||||
Emit:
|
||||
- ✓ `No blocking operational todos` — if no pending todos or none match operational keywords
|
||||
- ⚠ `Blocking operational todos found` — list the file names and matching keywords (max 5)
|
||||
|
||||
**Check 6 — Uncommitted code**
|
||||
|
||||
```bash
|
||||
git status --porcelain 2>/dev/null | grep -v "^??" | grep -v "^.planning\/" | grep -v "^\.\." | head -10
|
||||
```
|
||||
|
||||
If output is non-empty (modified/staged files outside `.planning/`), flag as uncommitted code.
|
||||
|
||||
Emit:
|
||||
- ✓ `Working tree clean` — if no modified files outside `.planning/`
|
||||
- ⚠ `Uncommitted changes in source files` — list up to 10 file paths
|
||||
|
||||
**Check 7 — Unresolved deferred items**
|
||||
|
||||
Glob every phase directory's SCOPE BOUNDARY log (executor writes out-of-scope discoveries here per `agents/gsd-executor.md`):
|
||||
```bash
|
||||
ls .planning/phases/*/deferred-items.md 2>/dev/null || true
|
||||
```
|
||||
|
||||
For each `deferred-items.md` found, read its entries (bullet list, one entry per top-level `- ` line, continuation lines indented beneath it). An entry is RESOLVED only if it carries an explicit `status: resolved` field (case-insensitive) on one of its lines; every other entry — including one with no `status:` field at all — is UNRESOLVED and must be surfaced (fail-safe: never silently drop a possibly-open item).
|
||||
|
||||
Emit:
|
||||
- ✓ `No unresolved deferred items` — if no `deferred-items.md` files exist, or every entry in every file is `status: resolved`
|
||||
- ⚠ `Unresolved deferred items found` — list each file's phase directory and its unresolved entry text (max 5 per file, truncated at 80 chars)
|
||||
|
||||
---
|
||||
|
||||
After all 7 checks, display the verdict:
|
||||
|
||||
**If all 7 checks passed:**
|
||||
```
|
||||
### Verdict: CLEAN
|
||||
|
||||
The standard progress report is trustworthy — proceed with the routing suggestion above.
|
||||
```
|
||||
|
||||
**If 1 or more checks failed:**
|
||||
```
|
||||
### Verdict: N INTEGRITY ISSUE(S) FOUND
|
||||
|
||||
The standard progress report may not reflect true project state.
|
||||
Review the flagged items above before acting on the routing suggestion.
|
||||
```
|
||||
|
||||
Then for each failed check, add a concrete next action:
|
||||
- Check 2 (orphaned handoff): `Read the handoff file(s) and resume from where work was paused: /gsd:resume-work ${GSD_WS}`
|
||||
- Check 3 (deferred scope): `Add the missing phases to ROADMAP.md or update the deferred references`
|
||||
- Check 4 (memory pending): `Review the flagged memory entries and resolve or clear them`
|
||||
- Check 5 (blocking todos): `Complete the operational steps in .planning/todos/pending/ before continuing`
|
||||
- Check 6 (uncommitted code): `Commit or stash the uncommitted changes before advancing`
|
||||
- Check 7 (unresolved deferred items): `Address each deferred item and mark it status: resolved in its deferred-items.md, or fold it into the roadmap`
|
||||
- Check 1 (STATE inconsistency): `Run /gsd:verify-work ${PHASE} ${GSD_WS} to reconcile state`
|
||||
</step>
|
||||
18
gsd-core/workflows/progress/steps/mvp-display.md
Normal file
18
gsd-core/workflows/progress/steps/mvp-display.md
Normal file
@@ -0,0 +1,18 @@
|
||||
<step name="mvp_display">
|
||||
**MVP-mode display (when phase has `**Mode:** mvp` in ROADMAP.md).**
|
||||
|
||||
`init.progress` already resolved `phase_mvp_mode` for the current phase (progress has no `--mvp` CLI flag — mode is inherited from the planned phase), and this step is only read at all when that same fact is `true` (`section_manifest`'s `mvp-display` inclusion gates on it) — so there is no need to re-resolve `MVP_MODE` here via a fresh `gsd_run query phase.mvp-mode` call; that would gate this section on a fact its own body recomputes, which is circular and self-disabling. Simply consume `$phase_mvp_mode` (already extracted from init JSON in `init_context`) as `true` for the remainder of this step.
|
||||
|
||||
The per-phase progress block adds a **user-flow status** sub-block sourced from the phase's PLAN.md task names. Each task whose name reads like a user-visible capability (e.g., "Register flow", "Login flow", "Password reset") is rendered as a status line:
|
||||
|
||||
```
|
||||
Phase 1 — User Auth MVP
|
||||
✅ Walking Skeleton complete ← from SKELETON.md existence
|
||||
✅ Register flow working ← from PLAN.md task with summary
|
||||
✅ Login flow working ← from PLAN.md task with summary
|
||||
🔄 Password reset (in progress) ← from PLAN.md task without summary
|
||||
⬜ Email verification ← from PLAN.md task not yet started
|
||||
```
|
||||
|
||||
**User-flow filter:** Tasks whose names are technical-sounding ("Wire DB schema", "Create migration", "Bump deps") are NOT rendered as user-flow status lines. Heuristic: a task name is user-flow-shaped if it ends in "flow", "page", "screen", or starts with a verb the user would recognize ("Register", "Login", "Upload", "View"). Tasks that fail the heuristic still count toward the standard task progress total but don't appear in the user-flow sub-block.
|
||||
</step>
|
||||
@@ -131,7 +131,11 @@ If `$VALIDATE_MODE` only:
|
||||
**Step 2: Initialize**
|
||||
|
||||
```bash
|
||||
INIT=$(gsd_run query init.quick "$DESCRIPTION")
|
||||
DISCUSS_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--discuss([[:space:]]|$) ]]; then DISCUSS_PARAM="--discuss"; fi
|
||||
RESEARCH_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--research([[:space:]]|$) ]]; then RESEARCH_PARAM="--research"; fi
|
||||
VALIDATE_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--validate([[:space:]]|$) ]]; then VALIDATE_PARAM="--validate"; fi
|
||||
FULL_PARAM=""; if [[ "$ARGUMENTS" =~ (^|[[:space:]])--full([[:space:]]|$) ]]; then FULL_PARAM="--full"; fi
|
||||
INIT=$(gsd_run query init.quick "$DESCRIPTION" $DISCUSS_PARAM $RESEARCH_PARAM $VALIDATE_PARAM $FULL_PARAM)
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
AGENT_SKILLS_PLANNER=$(gsd_run query agent-skills gsd-planner)
|
||||
AGENT_SKILLS_EXECUTOR=$(gsd_run query agent-skills gsd-executor)
|
||||
@@ -276,205 +280,15 @@ Store `$QUICK_DIR` for use in orchestration.
|
||||
|
||||
---
|
||||
|
||||
**Step 4.5: Discussion phase (only when `$DISCUSS_MODE`)**
|
||||
|
||||
Skip this step entirely if NOT `$DISCUSS_MODE`.
|
||||
|
||||
Display banner:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► DISCUSSING QUICK TASK
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
◆ Surfacing gray areas for: ${DESCRIPTION}
|
||||
```
|
||||
|
||||
**4.5a. Identify gray areas**
|
||||
|
||||
Analyze `$DESCRIPTION` to identify 2-4 gray areas — implementation decisions that would change the outcome and that the user should weigh in on.
|
||||
|
||||
Use the domain-aware heuristic to generate phase-specific (not generic) gray areas:
|
||||
- Something users **SEE** → layout, density, interactions, states
|
||||
- Something users **CALL** → responses, errors, auth, versioning
|
||||
- Something users **RUN** → output format, flags, modes, error handling
|
||||
- Something users **READ** → structure, tone, depth, flow
|
||||
- Something being **ORGANIZED** → criteria, grouping, naming, exceptions
|
||||
|
||||
Each gray area should be a concrete decision point, not a vague category. Example: "Loading behavior" not "UX".
|
||||
|
||||
**4.5b. Present gray areas**
|
||||
|
||||
```
|
||||
AskUserQuestion(
|
||||
header: "Gray Areas",
|
||||
question: "Which areas need clarification before planning?",
|
||||
options: [
|
||||
{ label: "${area_1}", description: "${why_it_matters_1}" },
|
||||
{ label: "${area_2}", description: "${why_it_matters_2}" },
|
||||
{ label: "${area_3}", description: "${why_it_matters_3}" },
|
||||
{ label: "All clear", description: "Skip discussion — I know what I want" }
|
||||
],
|
||||
multiSelect: true
|
||||
)
|
||||
```
|
||||
|
||||
If user selects "All clear" → skip to Step 5 (no CONTEXT.md written).
|
||||
|
||||
**4.5c. Discuss selected areas**
|
||||
|
||||
For each selected area, ask 1-2 focused questions via AskUserQuestion:
|
||||
|
||||
```
|
||||
AskUserQuestion(
|
||||
header: "${area_name}",
|
||||
question: "${specific_question_about_this_area}",
|
||||
options: [
|
||||
{ label: "${concrete_choice_1}", description: "${what_this_means}" },
|
||||
{ label: "${concrete_choice_2}", description: "${what_this_means}" },
|
||||
{ label: "${concrete_choice_3}", description: "${what_this_means}" },
|
||||
{ label: "You decide", description: "Claude's discretion" }
|
||||
],
|
||||
multiSelect: false
|
||||
)
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Options must be concrete choices, not abstract categories
|
||||
- Highlight recommended choice where you have a clear opinion
|
||||
- If user selects "Other" with freeform text, switch to plain text follow-up (per questioning.md freeform rule)
|
||||
- If user selects "You decide", capture as Claude's Discretion in CONTEXT.md
|
||||
- Max 2 questions per area — this is lightweight, not a deep dive
|
||||
|
||||
Collect all decisions into `$DECISIONS`.
|
||||
|
||||
**4.5d. Write CONTEXT.md**
|
||||
|
||||
Write `${QUICK_DIR}/${quick_id}-CONTEXT.md` using the standard context template structure:
|
||||
|
||||
```markdown
|
||||
# Quick Task ${quick_id}: ${DESCRIPTION} - Context
|
||||
|
||||
**Gathered:** ${date}
|
||||
**Status:** Ready for planning
|
||||
|
||||
<domain>
|
||||
## Task Boundary
|
||||
|
||||
${DESCRIPTION}
|
||||
|
||||
</domain>
|
||||
|
||||
<decisions>
|
||||
## Implementation Decisions
|
||||
|
||||
### ${area_1_name}
|
||||
- ${decision_from_discussion}
|
||||
|
||||
### ${area_2_name}
|
||||
- ${decision_from_discussion}
|
||||
|
||||
### Claude's Discretion
|
||||
${areas_where_user_said_you_decide_or_areas_not_discussed}
|
||||
|
||||
</decisions>
|
||||
|
||||
<specifics>
|
||||
## Specific Ideas
|
||||
|
||||
${any_specific_references_or_examples_from_discussion}
|
||||
|
||||
[If none: "No specific requirements — open to standard approaches"]
|
||||
|
||||
</specifics>
|
||||
|
||||
<canonical_refs>
|
||||
## Canonical References
|
||||
|
||||
${any_specs_adrs_or_docs_referenced_during_discussion}
|
||||
|
||||
[If none: "No external specs — requirements fully captured in decisions above"]
|
||||
|
||||
</canonical_refs>
|
||||
```
|
||||
|
||||
Note: Quick task CONTEXT.md omits `<code_context>` and `<deferred>` sections (no codebase scouting, no phase scope to defer to). Keep it lean. The `<canonical_refs>` section is included when external docs were referenced — omit it only if no external docs apply.
|
||||
|
||||
Report: `Context captured: ${QUICK_DIR}/${quick_id}-CONTEXT.md`
|
||||
<!-- gsd:section id="discussion-phase" when="flag:--discuss" -->
|
||||
If `section_manifest` is `null` or `"discussion-phase"` is in its `included` list: read and execute `gsd-core/workflows/quick/steps/discussion-phase.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
---
|
||||
|
||||
**Step 4.75: Research phase (only when `$RESEARCH_MODE`)**
|
||||
|
||||
Skip this step entirely if NOT `$RESEARCH_MODE`.
|
||||
|
||||
Display banner:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► RESEARCHING QUICK TASK
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
◆ Investigating approaches for: ${DESCRIPTION} (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
|
||||
```
|
||||
|
||||
Spawn a single focused researcher (not 4 parallel researchers like full phases — quick tasks need targeted research, not broad domain surveys):
|
||||
|
||||
<!-- #2517 model-omit-on-inherit -->
|
||||
|
||||
> **Model omission (#2517).** Omit the `model` parameter entirely when the value it would carry (`planner_model`, `checker_model`, `executor_model`, `reviewer_model`, `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(
|
||||
prompt="
|
||||
<research_context>
|
||||
|
||||
**Mode:** quick-task
|
||||
**Task:** ${DESCRIPTION}
|
||||
**Output:** ${QUICK_DIR}/${quick_id}-RESEARCH.md
|
||||
|
||||
<files_to_read>
|
||||
- ${STATE_PATH} (Project state — what's already built)
|
||||
- ${PROJECT_PATH} (Project context)
|
||||
- ./CLAUDE.md or ./.claude/CLAUDE.md (if exists — project-specific guidelines)
|
||||
${DISCUSS_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-CONTEXT.md (User decisions — research should align with these)' : ''}
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
</research_context>
|
||||
|
||||
<focus>
|
||||
This is a quick task, not a full phase. Research should be concise and targeted:
|
||||
1. Best libraries/patterns for this specific task
|
||||
2. Common pitfalls and how to avoid them
|
||||
3. Integration points with existing codebase
|
||||
4. Any constraints or gotchas worth knowing before planning
|
||||
|
||||
Do NOT produce a full domain survey. Target 1-2 pages of actionable findings.
|
||||
</focus>
|
||||
|
||||
<!-- #2508 runtime-aware-dispatch -->
|
||||
|
||||
> **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.
|
||||
|
||||
<output>
|
||||
Write research to: ${QUICK_DIR}/${quick_id}-RESEARCH.md
|
||||
Use standard research format but keep it lean — skip sections that don't apply.
|
||||
Return: ## RESEARCH COMPLETE with file path
|
||||
</output>
|
||||
",
|
||||
subagent_type="gsd-phase-researcher",
|
||||
model="{planner_model}",
|
||||
description="Research: ${DESCRIPTION}"
|
||||
)
|
||||
```
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
|
||||
|
||||
After researcher returns:
|
||||
1. Verify research exists at `${QUICK_DIR}/${quick_id}-RESEARCH.md`
|
||||
2. Report: "Research complete: ${QUICK_DIR}/${quick_id}-RESEARCH.md"
|
||||
|
||||
If research file not found, warn but continue: "Research agent did not produce output — proceeding to planning without research."
|
||||
<!-- gsd:section id="research-phase" when="flag:--research" -->
|
||||
If `section_manifest` is `null` or `"research-phase"` is in its `included` list: read and execute `gsd-core/workflows/quick/steps/research-phase.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
---
|
||||
|
||||
@@ -539,156 +353,15 @@ If plan not found, error: "Planner failed to create ${quick_id}-PLAN.md"
|
||||
|
||||
---
|
||||
|
||||
**Step 5.5: Plan-checker loop (only when `$VALIDATE_MODE`)**
|
||||
|
||||
Skip this step entirely if NOT `$VALIDATE_MODE`.
|
||||
|
||||
Display banner:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► CHECKING PLAN
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
|
||||
```
|
||||
|
||||
Checker prompt:
|
||||
|
||||
```markdown
|
||||
<verification_context>
|
||||
**Mode:** quick-full
|
||||
**Task Description:** ${DESCRIPTION}
|
||||
|
||||
<files_to_read>
|
||||
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan to verify)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_CHECKER}
|
||||
|
||||
**Scope:** This is a quick task, not a full phase. Skip checks that require a ROADMAP phase goal.
|
||||
</verification_context>
|
||||
|
||||
<check_dimensions>
|
||||
- Requirement coverage: Does the plan address the task description?
|
||||
- Task completeness: Do tasks have files, action, verify, done fields?
|
||||
- Key links: Are referenced files real?
|
||||
- Scope sanity: Is this appropriately sized for a quick task (1-3 tasks)?
|
||||
- must_haves derivation: Are must_haves traceable to the task description?
|
||||
|
||||
Skip: cross-plan deps (single plan), ROADMAP alignment
|
||||
${DISCUSS_MODE ? '- Context compliance: Does the plan honor locked decisions from CONTEXT.md?' : '- Skip: context compliance (no CONTEXT.md)'}
|
||||
</check_dimensions>
|
||||
|
||||
<expected_output>
|
||||
- ## VERIFICATION PASSED — all checks pass
|
||||
- ## ISSUES FOUND — structured issue list
|
||||
</expected_output>
|
||||
```
|
||||
|
||||
```
|
||||
Agent(
|
||||
prompt=checker_prompt,
|
||||
subagent_type="gsd-plan-checker",
|
||||
model="{checker_model}",
|
||||
description="Check quick plan: ${DESCRIPTION}"
|
||||
)
|
||||
```
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
|
||||
|
||||
**Handle checker return:**
|
||||
|
||||
- **`## VERIFICATION PASSED`:** Display confirmation, proceed to step 6.
|
||||
- **`## ISSUES FOUND`:** Display issues, check iteration count, enter revision loop.
|
||||
|
||||
**Revision loop (max 2 iterations):**
|
||||
|
||||
Track `iteration_count` (starts at 1 after initial plan + check).
|
||||
|
||||
**If iteration_count < 2:**
|
||||
|
||||
Display: `Sending back to planner for revision... (iteration ${N}/2)`
|
||||
|
||||
Revision prompt:
|
||||
|
||||
```markdown
|
||||
<revision_context>
|
||||
**Mode:** quick-full (revision)
|
||||
|
||||
<files_to_read>
|
||||
- ${QUICK_DIR}/${quick_id}-PLAN.md (Existing plan)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
**Checker issues:** ${structured_issues_from_checker}
|
||||
|
||||
</revision_context>
|
||||
|
||||
<instructions>
|
||||
Make targeted updates to address checker issues.
|
||||
Do NOT replan from scratch unless issues are fundamental.
|
||||
Return what changed.
|
||||
</instructions>
|
||||
```
|
||||
|
||||
```
|
||||
Agent(
|
||||
prompt=revision_prompt,
|
||||
subagent_type="gsd-planner",
|
||||
model="{planner_model}",
|
||||
description="Revise quick plan: ${DESCRIPTION}"
|
||||
)
|
||||
```
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
|
||||
|
||||
After planner returns → spawn checker again, increment iteration_count.
|
||||
|
||||
**If iteration_count >= 2:**
|
||||
|
||||
Display: `Max iterations reached. ${N} issues remain:` + issue list
|
||||
|
||||
Offer: 1) Force proceed, 2) Abort
|
||||
<!-- gsd:section id="plan-checker-loop" when="flag:--validate" -->
|
||||
If `section_manifest` is `null` or `"plan-checker-loop"` is in its `included` list: read and execute `gsd-core/workflows/quick/steps/plan-checker-loop.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
---
|
||||
|
||||
**Step 5.6: Pre-dispatch plan commit (worktree mode only)**
|
||||
|
||||
When `USE_WORKTREES !== "false"`, commit PLAN.md to the current branch **before** spawning the executor. This ensures the worktree inherits PLAN.md at its branch HEAD so the executor can read it via a worktree-rooted path — avoiding the main-repo path priming that triggers CC #36182 path-resolution drift.
|
||||
|
||||
Skip this step entirely if `USE_WORKTREES === "false"` (non-worktree mode: PLAN.md is committed in Step 8 as usual).
|
||||
|
||||
```bash
|
||||
QUICK_PLAN_PARENT=""
|
||||
QUICK_PLAN_COMMIT=""
|
||||
if [ "${USE_WORKTREES}" != "false" ]; then
|
||||
QUICK_PLAN_PARENT=$(git rev-parse HEAD)
|
||||
COMMIT_DOCS=$(gsd_run query config-get commit_docs 2>/dev/null || echo "true")
|
||||
if [ "$COMMIT_DOCS" != "false" ]; then
|
||||
git add "${QUICK_DIR}/${quick_id}-PLAN.md"
|
||||
# No-op skip if nothing actually staged (idempotent re-runs).
|
||||
if git diff --cached --quiet -- "${QUICK_DIR}/${quick_id}-PLAN.md"; then
|
||||
echo "ℹ Pre-dispatch PLAN.md commit skipped (no staged changes)"
|
||||
else
|
||||
# Run hooks normally (#2924). If a project opts out via
|
||||
# workflow.worktree_skip_hooks=true, honor that opt-in only.
|
||||
SKIP_HOOKS=$(gsd_run query config-get workflow.worktree_skip_hooks 2>/dev/null || echo "false")
|
||||
if [ "$SKIP_HOOKS" = "true" ]; then
|
||||
git commit --no-verify -m "docs(${quick_id}): pre-dispatch plan for ${DESCRIPTION}" -- "${QUICK_DIR}/${quick_id}-PLAN.md" \
|
||||
|| { echo "ERROR: pre-dispatch PLAN.md commit failed (--no-verify path). Aborting before executor dispatch." >&2; exit 1; }
|
||||
else
|
||||
git commit -m "docs(${quick_id}): pre-dispatch plan for ${DESCRIPTION}" -- "${QUICK_DIR}/${quick_id}-PLAN.md" \
|
||||
|| { echo "ERROR: pre-dispatch PLAN.md commit failed — likely a pre-commit hook failure. Fix the hook output above (or set workflow.worktree_skip_hooks=true to bypass) and re-run." >&2; exit 1; }
|
||||
fi
|
||||
QUICK_PLAN_COMMIT=$(git rev-parse HEAD)
|
||||
fi
|
||||
fi
|
||||
if [ -z "$QUICK_PLAN_COMMIT" ]; then
|
||||
QUICK_PLAN_COMMIT=$(git rev-parse HEAD)
|
||||
fi
|
||||
fi
|
||||
```
|
||||
<!-- gsd:section id="worktree-pre-dispatch-commit" when="state:worktrees-enabled" -->
|
||||
If `section_manifest` is `null` or `"worktree-pre-dispatch-commit"` is in its `included` list: read and execute `gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
---
|
||||
|
||||
@@ -901,52 +574,9 @@ If review produces findings, display advisory message. **Error handling:** Failu
|
||||
|
||||
---
|
||||
|
||||
**Step 6.5: Verification (only when `$VALIDATE_MODE`)**
|
||||
|
||||
Skip this step entirely if NOT `$VALIDATE_MODE`.
|
||||
|
||||
Display banner:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► VERIFYING RESULTS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
◆ Spawning verifier... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
|
||||
```
|
||||
|
||||
```
|
||||
Agent(
|
||||
prompt="Verify quick task goal achievement.
|
||||
Task directory: ${QUICK_DIR}
|
||||
Task goal: ${DESCRIPTION}
|
||||
|
||||
<files_to_read>
|
||||
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_VERIFIER}
|
||||
|
||||
Check must_haves against actual codebase. Create VERIFICATION.md at ${QUICK_DIR}/${quick_id}-VERIFICATION.md.",
|
||||
subagent_type="gsd-verifier",
|
||||
model="{verifier_model}",
|
||||
description="Verify: ${DESCRIPTION}"
|
||||
)
|
||||
```
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
|
||||
|
||||
Read verification status:
|
||||
```bash
|
||||
grep "^status:" "${QUICK_DIR}/${quick_id}-VERIFICATION.md" | cut -d: -f2 | tr -d ' '
|
||||
```
|
||||
|
||||
Store as `$VERIFICATION_STATUS`.
|
||||
|
||||
| Status | Action |
|
||||
|--------|--------|
|
||||
| `passed` | Store `$VERIFICATION_STATUS = "Verified"`, continue to step 7 |
|
||||
| `human_needed` | Display items needing manual check, store `$VERIFICATION_STATUS = "Needs Review"`, continue |
|
||||
| `gaps_found` | Display gap summary, offer: 1) Re-run executor to fix gaps, 2) Accept as-is. Store `$VERIFICATION_STATUS = "Gaps"` |
|
||||
<!-- gsd:section id="quick-verification" when="flag:--validate" -->
|
||||
If `section_manifest` is `null` or `"quick-verification"` is in its `included` list: read and execute `gsd-core/workflows/quick/steps/quick-verification.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
---
|
||||
|
||||
|
||||
124
gsd-core/workflows/quick/steps/discussion-phase.md
Normal file
124
gsd-core/workflows/quick/steps/discussion-phase.md
Normal file
@@ -0,0 +1,124 @@
|
||||
**Step 4.5: Discussion phase (only when `$DISCUSS_MODE`)**
|
||||
|
||||
Skip this step entirely if NOT `$DISCUSS_MODE`.
|
||||
|
||||
Display banner:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► DISCUSSING QUICK TASK
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
◆ Surfacing gray areas for: ${DESCRIPTION}
|
||||
```
|
||||
|
||||
**4.5a. Identify gray areas**
|
||||
|
||||
Analyze `$DESCRIPTION` to identify 2-4 gray areas — implementation decisions that would change the outcome and that the user should weigh in on.
|
||||
|
||||
Use the domain-aware heuristic to generate phase-specific (not generic) gray areas:
|
||||
- Something users **SEE** → layout, density, interactions, states
|
||||
- Something users **CALL** → responses, errors, auth, versioning
|
||||
- Something users **RUN** → output format, flags, modes, error handling
|
||||
- Something users **READ** → structure, tone, depth, flow
|
||||
- Something being **ORGANIZED** → criteria, grouping, naming, exceptions
|
||||
|
||||
Each gray area should be a concrete decision point, not a vague category. Example: "Loading behavior" not "UX".
|
||||
|
||||
**4.5b. Present gray areas**
|
||||
|
||||
```
|
||||
AskUserQuestion(
|
||||
header: "Gray Areas",
|
||||
question: "Which areas need clarification before planning?",
|
||||
options: [
|
||||
{ label: "${area_1}", description: "${why_it_matters_1}" },
|
||||
{ label: "${area_2}", description: "${why_it_matters_2}" },
|
||||
{ label: "${area_3}", description: "${why_it_matters_3}" },
|
||||
{ label: "All clear", description: "Skip discussion — I know what I want" }
|
||||
],
|
||||
multiSelect: true
|
||||
)
|
||||
```
|
||||
|
||||
If user selects "All clear" → skip to Step 5 (no CONTEXT.md written).
|
||||
|
||||
**4.5c. Discuss selected areas**
|
||||
|
||||
For each selected area, ask 1-2 focused questions via AskUserQuestion:
|
||||
|
||||
```
|
||||
AskUserQuestion(
|
||||
header: "${area_name}",
|
||||
question: "${specific_question_about_this_area}",
|
||||
options: [
|
||||
{ label: "${concrete_choice_1}", description: "${what_this_means}" },
|
||||
{ label: "${concrete_choice_2}", description: "${what_this_means}" },
|
||||
{ label: "${concrete_choice_3}", description: "${what_this_means}" },
|
||||
{ label: "You decide", description: "Claude's discretion" }
|
||||
],
|
||||
multiSelect: false
|
||||
)
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Options must be concrete choices, not abstract categories
|
||||
- Highlight recommended choice where you have a clear opinion
|
||||
- If user selects "Other" with freeform text, switch to plain text follow-up (per questioning.md freeform rule)
|
||||
- If user selects "You decide", capture as Claude's Discretion in CONTEXT.md
|
||||
- Max 2 questions per area — this is lightweight, not a deep dive
|
||||
|
||||
Collect all decisions into `$DECISIONS`.
|
||||
|
||||
**4.5d. Write CONTEXT.md**
|
||||
|
||||
Write `${QUICK_DIR}/${quick_id}-CONTEXT.md` using the standard context template structure:
|
||||
|
||||
```markdown
|
||||
# Quick Task ${quick_id}: ${DESCRIPTION} - Context
|
||||
|
||||
**Gathered:** ${date}
|
||||
**Status:** Ready for planning
|
||||
|
||||
<domain>
|
||||
## Task Boundary
|
||||
|
||||
${DESCRIPTION}
|
||||
|
||||
</domain>
|
||||
|
||||
<decisions>
|
||||
## Implementation Decisions
|
||||
|
||||
### ${area_1_name}
|
||||
- ${decision_from_discussion}
|
||||
|
||||
### ${area_2_name}
|
||||
- ${decision_from_discussion}
|
||||
|
||||
### Claude's Discretion
|
||||
${areas_where_user_said_you_decide_or_areas_not_discussed}
|
||||
|
||||
</decisions>
|
||||
|
||||
<specifics>
|
||||
## Specific Ideas
|
||||
|
||||
${any_specific_references_or_examples_from_discussion}
|
||||
|
||||
[If none: "No specific requirements — open to standard approaches"]
|
||||
|
||||
</specifics>
|
||||
|
||||
<canonical_refs>
|
||||
## Canonical References
|
||||
|
||||
${any_specs_adrs_or_docs_referenced_during_discussion}
|
||||
|
||||
[If none: "No external specs — requirements fully captured in decisions above"]
|
||||
|
||||
</canonical_refs>
|
||||
```
|
||||
|
||||
Note: Quick task CONTEXT.md omits `<code_context>` and `<deferred>` sections (no codebase scouting, no phase scope to defer to). Keep it lean. The `<canonical_refs>` section is included when external docs were referenced — omit it only if no external docs apply.
|
||||
|
||||
Report: `Context captured: ${QUICK_DIR}/${quick_id}-CONTEXT.md`
|
||||
111
gsd-core/workflows/quick/steps/plan-checker-loop.md
Normal file
111
gsd-core/workflows/quick/steps/plan-checker-loop.md
Normal file
@@ -0,0 +1,111 @@
|
||||
**Step 5.5: Plan-checker loop (only when `$VALIDATE_MODE`)**
|
||||
|
||||
Skip this step entirely if NOT `$VALIDATE_MODE`.
|
||||
|
||||
Display banner:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► CHECKING PLAN
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
|
||||
```
|
||||
|
||||
Checker prompt:
|
||||
|
||||
```markdown
|
||||
<verification_context>
|
||||
**Mode:** quick-full
|
||||
**Task Description:** ${DESCRIPTION}
|
||||
|
||||
<files_to_read>
|
||||
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan to verify)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_CHECKER}
|
||||
|
||||
**Scope:** This is a quick task, not a full phase. Skip checks that require a ROADMAP phase goal.
|
||||
</verification_context>
|
||||
|
||||
<check_dimensions>
|
||||
- Requirement coverage: Does the plan address the task description?
|
||||
- Task completeness: Do tasks have files, action, verify, done fields?
|
||||
- Key links: Are referenced files real?
|
||||
- Scope sanity: Is this appropriately sized for a quick task (1-3 tasks)?
|
||||
- must_haves derivation: Are must_haves traceable to the task description?
|
||||
|
||||
Skip: cross-plan deps (single plan), ROADMAP alignment
|
||||
${DISCUSS_MODE ? '- Context compliance: Does the plan honor locked decisions from CONTEXT.md?' : '- Skip: context compliance (no CONTEXT.md)'}
|
||||
</check_dimensions>
|
||||
|
||||
<expected_output>
|
||||
- ## VERIFICATION PASSED — all checks pass
|
||||
- ## ISSUES FOUND — structured issue list
|
||||
</expected_output>
|
||||
```
|
||||
|
||||
```
|
||||
Agent(
|
||||
prompt=checker_prompt,
|
||||
subagent_type="gsd-plan-checker",
|
||||
model="{checker_model}",
|
||||
description="Check quick plan: ${DESCRIPTION}"
|
||||
)
|
||||
```
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
|
||||
|
||||
**Handle checker return:**
|
||||
|
||||
- **`## VERIFICATION PASSED`:** Display confirmation, proceed to step 6.
|
||||
- **`## ISSUES FOUND`:** Display issues, check iteration count, enter revision loop.
|
||||
|
||||
**Revision loop (max 2 iterations):**
|
||||
|
||||
Track `iteration_count` (starts at 1 after initial plan + check).
|
||||
|
||||
**If iteration_count < 2:**
|
||||
|
||||
Display: `Sending back to planner for revision... (iteration ${N}/2)`
|
||||
|
||||
Revision prompt:
|
||||
|
||||
```markdown
|
||||
<revision_context>
|
||||
**Mode:** quick-full (revision)
|
||||
|
||||
<files_to_read>
|
||||
- ${QUICK_DIR}/${quick_id}-PLAN.md (Existing plan)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
**Checker issues:** ${structured_issues_from_checker}
|
||||
|
||||
</revision_context>
|
||||
|
||||
<instructions>
|
||||
Make targeted updates to address checker issues.
|
||||
Do NOT replan from scratch unless issues are fundamental.
|
||||
Return what changed.
|
||||
</instructions>
|
||||
```
|
||||
|
||||
```
|
||||
Agent(
|
||||
prompt=revision_prompt,
|
||||
subagent_type="gsd-planner",
|
||||
model="{planner_model}",
|
||||
description="Revise quick plan: ${DESCRIPTION}"
|
||||
)
|
||||
```
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
|
||||
|
||||
After planner returns → spawn checker again, increment iteration_count.
|
||||
|
||||
**If iteration_count >= 2:**
|
||||
|
||||
Display: `Max iterations reached. ${N} issues remain:` + issue list
|
||||
|
||||
Offer: 1) Force proceed, 2) Abort
|
||||
46
gsd-core/workflows/quick/steps/quick-verification.md
Normal file
46
gsd-core/workflows/quick/steps/quick-verification.md
Normal file
@@ -0,0 +1,46 @@
|
||||
**Step 6.5: Verification (only when `$VALIDATE_MODE`)**
|
||||
|
||||
Skip this step entirely if NOT `$VALIDATE_MODE`.
|
||||
|
||||
Display banner:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► VERIFYING RESULTS
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
◆ Spawning verifier... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
|
||||
```
|
||||
|
||||
```
|
||||
Agent(
|
||||
prompt="Verify quick task goal achievement.
|
||||
Task directory: ${QUICK_DIR}
|
||||
Task goal: ${DESCRIPTION}
|
||||
|
||||
<files_to_read>
|
||||
- ${QUICK_DIR}/${quick_id}-PLAN.md (Plan)
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_VERIFIER}
|
||||
|
||||
Check must_haves against actual codebase. Create VERIFICATION.md at ${QUICK_DIR}/${quick_id}-VERIFICATION.md.",
|
||||
subagent_type="gsd-verifier",
|
||||
model="{verifier_model}",
|
||||
description="Verify: ${DESCRIPTION}"
|
||||
)
|
||||
```
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
|
||||
|
||||
Read verification status:
|
||||
```bash
|
||||
grep "^status:" "${QUICK_DIR}/${quick_id}-VERIFICATION.md" | cut -d: -f2 | tr -d ' '
|
||||
```
|
||||
|
||||
Store as `$VERIFICATION_STATUS`.
|
||||
|
||||
| Status | Action |
|
||||
|--------|--------|
|
||||
| `passed` | Store `$VERIFICATION_STATUS = "Verified"`, continue to step 7 |
|
||||
| `human_needed` | Display items needing manual check, store `$VERIFICATION_STATUS = "Needs Review"`, continue |
|
||||
| `gaps_found` | Display gap summary, offer: 1) Re-run executor to fix gaps, 2) Accept as-is. Store `$VERIFICATION_STATUS = "Gaps"` |
|
||||
72
gsd-core/workflows/quick/steps/research-phase.md
Normal file
72
gsd-core/workflows/quick/steps/research-phase.md
Normal file
@@ -0,0 +1,72 @@
|
||||
**Step 4.75: Research phase (only when `$RESEARCH_MODE`)**
|
||||
|
||||
Skip this step entirely if NOT `$RESEARCH_MODE`.
|
||||
|
||||
Display banner:
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► RESEARCHING QUICK TASK
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
◆ Investigating approaches for: ${DESCRIPTION} (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
|
||||
```
|
||||
|
||||
Spawn a single focused researcher (not 4 parallel researchers like full phases — quick tasks need targeted research, not broad domain surveys):
|
||||
|
||||
<!-- #2517 model-omit-on-inherit -->
|
||||
|
||||
> **Model omission (#2517).** Omit the `model` parameter entirely when the value it would carry (`planner_model`, `checker_model`, `executor_model`, `reviewer_model`, `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.
|
||||
|
||||
<!-- #2508 runtime-aware-dispatch -->
|
||||
|
||||
> **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.
|
||||
|
||||
```
|
||||
Agent(
|
||||
prompt="
|
||||
<research_context>
|
||||
|
||||
**Mode:** quick-task
|
||||
**Task:** ${DESCRIPTION}
|
||||
**Output:** ${QUICK_DIR}/${quick_id}-RESEARCH.md
|
||||
|
||||
<files_to_read>
|
||||
- ${STATE_PATH} (Project state — what's already built)
|
||||
- ${PROJECT_PATH} (Project context)
|
||||
- ./CLAUDE.md or ./.claude/CLAUDE.md (if exists — project-specific guidelines)
|
||||
${DISCUSS_MODE ? '- ' + QUICK_DIR + '/' + quick_id + '-CONTEXT.md (User decisions — research should align with these)' : ''}
|
||||
</files_to_read>
|
||||
|
||||
${AGENT_SKILLS_PLANNER}
|
||||
|
||||
</research_context>
|
||||
|
||||
<focus>
|
||||
This is a quick task, not a full phase. Research should be concise and targeted:
|
||||
1. Best libraries/patterns for this specific task
|
||||
2. Common pitfalls and how to avoid them
|
||||
3. Integration points with existing codebase
|
||||
4. Any constraints or gotchas worth knowing before planning
|
||||
|
||||
Do NOT produce a full domain survey. Target 1-2 pages of actionable findings.
|
||||
</focus>
|
||||
|
||||
<output>
|
||||
Write research to: ${QUICK_DIR}/${quick_id}-RESEARCH.md
|
||||
Use standard research format but keep it lean — skip sections that don't apply.
|
||||
Return: ## RESEARCH COMPLETE with file path
|
||||
</output>
|
||||
",
|
||||
subagent_type="gsd-phase-researcher",
|
||||
model="{planner_model}",
|
||||
description="Research: ${DESCRIPTION}"
|
||||
)
|
||||
```
|
||||
|
||||
> **ORCHESTRATOR RULE — CODEX RUNTIME**: After calling Agent() above, stop working on this task immediately. Do not read more files, edit code, or run tests related to this task while the subagent is active. Wait for the subagent to return its result. This prevents duplicate work, conflicting edits, and wasted context. Only resume when the subagent result is available.
|
||||
|
||||
After researcher returns:
|
||||
1. Verify research exists at `${QUICK_DIR}/${quick_id}-RESEARCH.md`
|
||||
2. Report: "Research complete: ${QUICK_DIR}/${quick_id}-RESEARCH.md"
|
||||
|
||||
If research file not found, warn but continue: "Research agent did not produce output — proceeding to planning without research."
|
||||
@@ -0,0 +1,37 @@
|
||||
**Step 5.6: Pre-dispatch plan commit (worktree mode only)**
|
||||
|
||||
When `USE_WORKTREES !== "false"`, commit PLAN.md to the current branch **before** spawning the executor. This ensures the worktree inherits PLAN.md at its branch HEAD so the executor can read it via a worktree-rooted path — avoiding the main-repo path priming that triggers CC #36182 path-resolution drift.
|
||||
|
||||
Skip this step entirely if `USE_WORKTREES === "false"` (non-worktree mode: PLAN.md is committed in Step 8 as usual).
|
||||
|
||||
```bash
|
||||
_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
|
||||
QUICK_PLAN_PARENT=""
|
||||
QUICK_PLAN_COMMIT=""
|
||||
if [ "${USE_WORKTREES}" != "false" ]; then
|
||||
QUICK_PLAN_PARENT=$(git rev-parse HEAD)
|
||||
COMMIT_DOCS=$(gsd_run query config-get commit_docs 2>/dev/null || echo "true")
|
||||
if [ "$COMMIT_DOCS" != "false" ]; then
|
||||
git add "${QUICK_DIR}/${quick_id}-PLAN.md"
|
||||
# No-op skip if nothing actually staged (idempotent re-runs).
|
||||
if git diff --cached --quiet -- "${QUICK_DIR}/${quick_id}-PLAN.md"; then
|
||||
echo "ℹ Pre-dispatch PLAN.md commit skipped (no staged changes)"
|
||||
else
|
||||
# Run hooks normally (#2924). If a project opts out via
|
||||
# workflow.worktree_skip_hooks=true, honor that opt-in only.
|
||||
SKIP_HOOKS=$(gsd_run query config-get workflow.worktree_skip_hooks 2>/dev/null || echo "false")
|
||||
if [ "$SKIP_HOOKS" = "true" ]; then
|
||||
git commit --no-verify -m "docs(${quick_id}): pre-dispatch plan for ${DESCRIPTION}" -- "${QUICK_DIR}/${quick_id}-PLAN.md" \
|
||||
|| { echo "ERROR: pre-dispatch PLAN.md commit failed (--no-verify path). Aborting before executor dispatch." >&2; exit 1; }
|
||||
else
|
||||
git commit -m "docs(${quick_id}): pre-dispatch plan for ${DESCRIPTION}" -- "${QUICK_DIR}/${quick_id}-PLAN.md" \
|
||||
|| { echo "ERROR: pre-dispatch PLAN.md commit failed — likely a pre-commit hook failure. Fix the hook output above (or set workflow.worktree_skip_hooks=true to bypass) and re-run." >&2; exit 1; }
|
||||
fi
|
||||
QUICK_PLAN_COMMIT=$(git rev-parse HEAD)
|
||||
fi
|
||||
fi
|
||||
if [ -z "$QUICK_PLAN_COMMIT" ]; then
|
||||
QUICK_PLAN_COMMIT=$(git rev-parse HEAD)
|
||||
fi
|
||||
fi
|
||||
```
|
||||
@@ -105,10 +105,9 @@ user who wants a preferred set has `review.default_reviewers`. Both stay lenient
|
||||
preference evaluated across many hosts, so a subset being present is expected, not an error
|
||||
- If all configured reviewers are unavailable, fail with an actionable message
|
||||
|
||||
**Reviewer instances (#1517, optional):** if `review.reviewer_instances` is configured,
|
||||
instance names in `review.default_reviewers` run as independent identities. Resolution rules
|
||||
are in `gsd-core/references/reviewer-instances.md` — load it lazily only when instances are
|
||||
configured. Unconfigured → default path unchanged.
|
||||
<!-- gsd:section id="reviewer-instances-note-1" when="state:reviewer-instances-configured" -->
|
||||
If `section_manifest` is `null` or `"reviewer-instances-note-1"` is in its `included` list: read and execute `gsd-core/workflows/review/steps/reviewer-instances-note-1.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
If no CLIs are available:
|
||||
```
|
||||
@@ -156,7 +155,7 @@ Rules:
|
||||
Collect phase artifacts for the review prompt:
|
||||
|
||||
```bash
|
||||
INIT=$(gsd_run query init.phase-op "${PHASE_ARG}")
|
||||
INIT=$(gsd_run query init.review "${PHASE_ARG}")
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
|
||||
# #2358: ONE run-scoped temp dir (portable via ${TMPDIR:-/tmp}) so overlapping
|
||||
@@ -306,9 +305,9 @@ An environment that genuinely hits an untrusted-hook prompt surfaces through the
|
||||
the empty-output stub as a dropped lane with diagnosable stderr, not silent attrition. Do not
|
||||
reintroduce the flag (even spelled out in prose — a regression test bans the literal file-wide).
|
||||
|
||||
**Reviewer instances (#1517, optional):** instances resolve *through* a lane and are not lanes
|
||||
themselves (ADR-2782 D8). Each selected instance invokes its base `cli` with its own `model`/`agent`
|
||||
as opaque argv. Exact invocation in `gsd-core/references/reviewer-instances.md`.
|
||||
<!-- gsd:section id="reviewer-instances-note-2" when="state:reviewer-instances-configured" -->
|
||||
If `section_manifest` is `null` or `"reviewer-instances-note-2"` is in its `included` list: read and execute `gsd-core/workflows/review/steps/reviewer-instances-note-2.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
Lanes run **sequentially, not in parallel** — concurrent invocation trips provider rate limits.
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
**Reviewer instances (#1517, optional):** if `review.reviewer_instances` is configured,
|
||||
instance names in `review.default_reviewers` run as independent identities. Resolution rules
|
||||
are in `gsd-core/references/reviewer-instances.md` — load it lazily only when instances are
|
||||
configured. Unconfigured → default path unchanged.
|
||||
@@ -0,0 +1,3 @@
|
||||
**Reviewer instances (#1517, optional):** instances resolve *through* a lane and are not lanes
|
||||
themselves (ADR-2782 D8). Each selected instance invokes its base `cli` with its own `model`/`agent`
|
||||
as opaque argv. Exact invocation in `gsd-core/references/reviewer-instances.md`.
|
||||
@@ -1,5 +1,65 @@
|
||||
{
|
||||
"workflows": {
|
||||
"autonomous": [
|
||||
{
|
||||
"id": "converge-fail-fast",
|
||||
"when": "state:plan-strategy-converge",
|
||||
"read": "gsd-core/workflows/autonomous/steps/converge-fail-fast.md"
|
||||
},
|
||||
{
|
||||
"id": "converge-banner",
|
||||
"when": "state:plan-strategy-converge",
|
||||
"read": "gsd-core/workflows/autonomous/steps/converge-banner.md"
|
||||
},
|
||||
{
|
||||
"id": "converge-dispatch-bg",
|
||||
"when": "state:plan-strategy-converge",
|
||||
"read": "gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md"
|
||||
},
|
||||
{
|
||||
"id": "converge-dispatch-inline",
|
||||
"when": "state:plan-strategy-converge",
|
||||
"read": "gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md"
|
||||
},
|
||||
{
|
||||
"id": "converge-loop",
|
||||
"when": "state:plan-strategy-converge",
|
||||
"read": "gsd-core/workflows/autonomous/steps/converge-loop.md"
|
||||
}
|
||||
],
|
||||
"code-review": [
|
||||
{
|
||||
"id": "structural-pre-pass",
|
||||
"when": "state:fallow-enabled",
|
||||
"read": "gsd-core/workflows/code-review/steps/structural-pre-pass.md"
|
||||
},
|
||||
{
|
||||
"id": "dispatch-fix",
|
||||
"when": "flag:--fix",
|
||||
"read": "gsd-core/workflows/code-review/steps/dispatch-fix.md"
|
||||
}
|
||||
],
|
||||
"complete-milestone": [
|
||||
{
|
||||
"id": "git-tag",
|
||||
"when": "state:git-create-tag",
|
||||
"read": "gsd-core/workflows/complete-milestone/steps/git-tag.md"
|
||||
}
|
||||
],
|
||||
"discuss-phase-assumptions": [
|
||||
{
|
||||
"id": "auto-advance-dispatch",
|
||||
"when": "state:auto-advance-active",
|
||||
"read": "gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md"
|
||||
}
|
||||
],
|
||||
"docs-update": [
|
||||
{
|
||||
"id": "dispatch-monorepo-packages",
|
||||
"when": "state:is-monorepo",
|
||||
"read": "gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md"
|
||||
}
|
||||
],
|
||||
"execute-phase": [
|
||||
{
|
||||
"id": "partial-wave",
|
||||
@@ -17,6 +77,35 @@
|
||||
"read": "gsd-core/workflows/execute-phase/steps/regression-gate.md"
|
||||
}
|
||||
],
|
||||
"new-milestone": [
|
||||
{
|
||||
"id": "project-md-milestone-write",
|
||||
"when": "state:flat-mode",
|
||||
"read": "gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md"
|
||||
},
|
||||
{
|
||||
"id": "reset-phase-safety",
|
||||
"when": "flag:--reset-phase-numbers",
|
||||
"read": "gsd-core/workflows/new-milestone/steps/reset-phase-safety.md"
|
||||
}
|
||||
],
|
||||
"new-project": [
|
||||
{
|
||||
"id": "auto-mode-detection",
|
||||
"when": "flag:--auto",
|
||||
"read": "gsd-core/workflows/new-project/steps/auto-mode-detection.md"
|
||||
},
|
||||
{
|
||||
"id": "codebase-map-offer",
|
||||
"when": "state:needs-codebase-map",
|
||||
"read": "gsd-core/workflows/new-project/steps/codebase-map-offer.md"
|
||||
},
|
||||
{
|
||||
"id": "auto-mode-config",
|
||||
"when": "flag:--auto",
|
||||
"read": "gsd-core/workflows/new-project/steps/auto-mode-config.md"
|
||||
}
|
||||
],
|
||||
"plan-phase": [
|
||||
{
|
||||
"id": "reviews-prerequisite",
|
||||
@@ -48,6 +137,83 @@
|
||||
"when": "state:chunked-mode",
|
||||
"read": "gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md"
|
||||
}
|
||||
],
|
||||
"progress": [
|
||||
{
|
||||
"id": "mvp-display",
|
||||
"when": "state:phase-mvp-mode",
|
||||
"read": "gsd-core/workflows/progress/steps/mvp-display.md"
|
||||
},
|
||||
{
|
||||
"id": "forensic-audit",
|
||||
"when": "flag:--forensic",
|
||||
"read": "gsd-core/workflows/progress/steps/forensic-audit.md"
|
||||
}
|
||||
],
|
||||
"quick": [
|
||||
{
|
||||
"id": "discussion-phase",
|
||||
"when": "flag:--discuss",
|
||||
"read": "gsd-core/workflows/quick/steps/discussion-phase.md"
|
||||
},
|
||||
{
|
||||
"id": "research-phase",
|
||||
"when": "flag:--research",
|
||||
"read": "gsd-core/workflows/quick/steps/research-phase.md"
|
||||
},
|
||||
{
|
||||
"id": "plan-checker-loop",
|
||||
"when": "flag:--validate",
|
||||
"read": "gsd-core/workflows/quick/steps/plan-checker-loop.md"
|
||||
},
|
||||
{
|
||||
"id": "worktree-pre-dispatch-commit",
|
||||
"when": "state:worktrees-enabled",
|
||||
"read": "gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md"
|
||||
},
|
||||
{
|
||||
"id": "quick-verification",
|
||||
"when": "flag:--validate",
|
||||
"read": "gsd-core/workflows/quick/steps/quick-verification.md"
|
||||
}
|
||||
],
|
||||
"review": [
|
||||
{
|
||||
"id": "reviewer-instances-note-1",
|
||||
"when": "state:reviewer-instances-configured",
|
||||
"read": "gsd-core/workflows/review/steps/reviewer-instances-note-1.md"
|
||||
},
|
||||
{
|
||||
"id": "reviewer-instances-note-2",
|
||||
"when": "state:reviewer-instances-configured",
|
||||
"read": "gsd-core/workflows/review/steps/reviewer-instances-note-2.md"
|
||||
}
|
||||
],
|
||||
"transition": [
|
||||
{
|
||||
"id": "workstream-collision-check",
|
||||
"when": "state:workstream-active",
|
||||
"read": "gsd-core/workflows/transition/steps/workstream-collision-check.md"
|
||||
}
|
||||
],
|
||||
"update": [
|
||||
{
|
||||
"id": "channel-banner",
|
||||
"when": "state:next-channel",
|
||||
"read": "gsd-core/workflows/update/steps/channel-banner.md"
|
||||
}
|
||||
],
|
||||
"verify-work": [
|
||||
{
|
||||
"id": "automated-ui-verification",
|
||||
"when": "state:ui-phase-active",
|
||||
"read": "gsd-core/workflows/verify-work/steps/automated-ui-verification.md"
|
||||
},
|
||||
{
|
||||
"id": "mvp-uat-framing",
|
||||
"when": "state:phase-mvp-mode",
|
||||
"read": "gsd-core/workflows/verify-work/steps/mvp-uat-framing.md"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,33 +417,20 @@ ROADMAP=$(gsd_run query roadmap.analyze)
|
||||
|
||||
This returns all phases with goals, disk status, and completion info.
|
||||
|
||||
---
|
||||
|
||||
**Workstream collision check (when `is_last_phase: true`):**
|
||||
|
||||
Before routing to Route B, check whether other workstreams are still active.
|
||||
This prevents one workstream from advancing or completing the milestone while
|
||||
other workstreams are still working on their phases.
|
||||
|
||||
**Skip this check if NOT in workstream mode** (i.e., `GSD_WORKSTREAM` is not set / flat mode).
|
||||
In flat mode, go directly to **Route B**.
|
||||
**Section-manifest gate (#2994):** `gsd_run` is already established above (`update_roadmap_and_state` step) — fetch the dedicated `init.transition` bundle for the workstream-collision-check gate below:
|
||||
|
||||
```bash
|
||||
# Only check if we're in workstream mode
|
||||
if [ -n "$GSD_WORKSTREAM" ]; then
|
||||
WS_LIST=$(gsd_run query workstream.list --raw)
|
||||
fi
|
||||
INIT_TRANSITION=$(gsd_run query init.transition)
|
||||
if [[ "$INIT_TRANSITION" == @file:* ]]; then INIT_TRANSITION=$(cat "${INIT_TRANSITION#@file:}"); fi
|
||||
```
|
||||
|
||||
Parse the JSON result. The output has `{ mode, workstreams: [...] }`.
|
||||
Each workstream entry has: `name`, `status`, `current_phase`, `phase_count`, `completed_phases`.
|
||||
Extract from `INIT_TRANSITION`: `other_active_workstreams`, `section_manifest`.
|
||||
|
||||
Filter out the current workstream (`$GSD_WORKSTREAM`) and any workstreams with
|
||||
status containing "milestone complete" or "archived" (case-insensitive).
|
||||
The remaining entries are **other active workstreams**.
|
||||
---
|
||||
|
||||
- **If other active workstreams exist** → Go to **Route B1**
|
||||
- **If NO other active workstreams** (or flat mode) → Go to **Route B**
|
||||
<!-- gsd:section id="workstream-collision-check" when="state:workstream-active" -->
|
||||
If `section_manifest` (from `INIT_TRANSITION`) is `null` or `"workstream-collision-check"` is in its `included` list: read and execute `gsd-core/workflows/transition/steps/workstream-collision-check.md`. Otherwise (flat mode) skip — do not read the file; go directly to **Route B**.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
**Workstream collision check (when `is_last_phase: true`):**
|
||||
|
||||
Before routing to Route B, check whether other workstreams are still active.
|
||||
This prevents one workstream from advancing or completing the milestone while
|
||||
other workstreams are still working on their phases.
|
||||
|
||||
**Skip this check if NOT in workstream mode** (i.e., `GSD_WORKSTREAM` is not set / flat mode).
|
||||
In flat mode, go directly to **Route B**.
|
||||
|
||||
Parse `other_active_workstreams` from `INIT_TRANSITION` (already fetched above — no
|
||||
`gsd_run` call needed here). `init.transition` pre-filters this list exactly as this
|
||||
check requires: it excludes the current workstream (`$GSD_WORKSTREAM`) and any
|
||||
workstream whose status contains "milestone complete" or "archived"
|
||||
(case-insensitive). Each remaining entry has `name` and `status`.
|
||||
|
||||
- **If `other_active_workstreams` is non-empty** → Go to **Route B1**
|
||||
- **If `other_active_workstreams` is empty** (or flat mode) → Go to **Route B**
|
||||
@@ -102,6 +102,21 @@ esac
|
||||
```
|
||||
|
||||
`TAG` is restricted to `latest`/`next` by `check-latest-version.cjs` (it rejects any other value with exit 2), so no arbitrary dist-tag can leak through. Omitting `--next`/`--rc` reproduces the prior behavior exactly: `TAG=latest`.
|
||||
|
||||
**Section-manifest gate (#2994):** reuse the `$GSD_TOOLS` already resolved by `get_installed_version` above — do NOT copy the canonical launcher preamble here, it assigns the SAME `$GSD_TOOLS` variable via a different (fixed-candidate) resolution and would silently override the value `backup_custom_files`/`restore_custom_files` (later steps) still depend on. Forward `--next`/`--rc` from `$ARGUMENTS` so `init.update`'s `state:next-channel` fact matches this step's own case-statement:
|
||||
|
||||
```bash
|
||||
INIT_UPDATE=""
|
||||
if [ -n "$GSD_TOOLS" ]; then
|
||||
case "$GSD_TOOLS" in
|
||||
*.cjs) INIT_UPDATE="$(node "$GSD_TOOLS" query init.update $([ "$TAG" = "next" ] && echo --next) 2>/dev/null)" ;;
|
||||
*) INIT_UPDATE="$("$GSD_TOOLS" query init.update $([ "$TAG" = "next" ] && echo --next) 2>/dev/null)" ;;
|
||||
esac
|
||||
fi
|
||||
if [[ "$INIT_UPDATE" == @file:* ]]; then INIT_UPDATE=$(cat "${INIT_UPDATE#@file:}"); fi
|
||||
```
|
||||
|
||||
Extract `section_manifest` from `INIT_UPDATE` — gates the `channel-banner` section in `compare_versions` below.
|
||||
</step>
|
||||
|
||||
<step name="check_latest_version">
|
||||
@@ -152,13 +167,9 @@ Exit.
|
||||
<step name="compare_versions">
|
||||
Compare installed vs latest:
|
||||
|
||||
**Only when `TAG=next`** (the user passed `--next`/`--rc`), prepend a channel banner so they know they are leaving the stable line — add this line immediately after the `**Latest:**` line in whichever output block renders:
|
||||
|
||||
**Channel:** {CHANNEL_LABEL}
|
||||
|
||||
On the default stable channel (`TAG=latest`), do NOT add a channel line — the output must match the prior stable behavior exactly.
|
||||
|
||||
When `TAG=next`, the "latest" value is the release candidate published under `@next` (e.g. `1.4.0-rc.1`). Apply standard semver precedence for prereleases (`1.4.0-rc.1` is newer than `1.3.1` but older than the final `1.4.0`). Do NOT treat an `-rc.N` suffix as a dev install or as "behind" — offer it as an available update.
|
||||
<!-- gsd:section id="channel-banner" when="state:next-channel" -->
|
||||
If `section_manifest` (from `INIT_UPDATE`) is `null` or `"channel-banner"` is in its `included` list: read and execute `gsd-core/workflows/update/steps/channel-banner.md`. Otherwise (default stable channel) skip — do not read the file; the output must match the prior stable behavior exactly, with no channel line.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
**If installed == latest:**
|
||||
```
|
||||
|
||||
7
gsd-core/workflows/update/steps/channel-banner.md
Normal file
7
gsd-core/workflows/update/steps/channel-banner.md
Normal file
@@ -0,0 +1,7 @@
|
||||
**Only when `TAG=next`** (the user passed `--next`/`--rc`), prepend a channel banner so they know they are leaving the stable line — add this line immediately after the `**Latest:**` line in whichever output block renders:
|
||||
|
||||
**Channel:** {CHANNEL_LABEL}
|
||||
|
||||
On the default stable channel (`TAG=latest`), do NOT add a channel line — the output must match the prior stable behavior exactly.
|
||||
|
||||
When `TAG=next`, the "latest" value is the release candidate published under `@next` (e.g. `1.4.0-rc.1`). Apply standard semver precedence for prereleases (`1.4.0-rc.1` is newer than `1.3.1` but older than the final `1.4.0`). Do NOT treat an `-rc.N` suffix as a dev install or as "behind" — offer it as an available update.
|
||||
@@ -39,8 +39,8 @@ If $ARGUMENTS contains a phase number, load context:
|
||||
```bash
|
||||
_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
|
||||
GSD_WS=""
|
||||
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[^[:space:]]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[^[:space:]]+')
|
||||
PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[^[:space:]]+//g' | xargs)
|
||||
echo "$ARGUMENTS" | grep -qE -- '--ws[[:space:]]+[A-Za-z0-9._-]+' && GSD_WS=$(echo "$ARGUMENTS" | grep -oE -- '--ws[[:space:]]+[A-Za-z0-9._-]+')
|
||||
PHASE_ARG=$(echo "$ARGUMENTS" | sed -E 's/--ws[[:space:]]+[A-Za-z0-9._-]+//g' | xargs)
|
||||
|
||||
INIT=$(gsd_run query init.verify-work "${PHASE_ARG}" ${GSD_WS})
|
||||
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi
|
||||
@@ -145,42 +145,9 @@ Provide a phase number to start testing (e.g., /gsd:verify-work 4)
|
||||
Continue to `create_uat_file`.
|
||||
</step>
|
||||
|
||||
<step name="automated_ui_verification">
|
||||
**Automated UI Verification (when Playwright-MCP is available)**
|
||||
|
||||
Before UAT, check UI capability activation and whether Playwright/Puppeteer MCP tools are available.
|
||||
|
||||
```bash
|
||||
PLAN_HOOKS_JSON=$(gsd_run loop render-hooks plan:pre --raw)
|
||||
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
|
||||
```
|
||||
|
||||
Set `UI_PHASE_ACTIVE=true` when `PLAN_HOOKS_JSON.activeHooks` contains an active `ui` step hook.
|
||||
|
||||
**If Playwright-MCP tools are available in this session (`mcp__playwright__*` tools
|
||||
respond to tool calls) AND (`UI_PHASE_ACTIVE` is `true` OR `UI_SPEC_FILE` is non-empty):**
|
||||
|
||||
For each UI checkpoint listed in the phase's UI-SPEC.md (or inferred from SUMMARY.md):
|
||||
|
||||
1. Use `mcp__playwright__navigate` (or equivalent) to open the component's URL.
|
||||
2. Use `mcp__playwright__screenshot` to capture a screenshot.
|
||||
3. Compare the screenshot visually against the spec's stated requirements
|
||||
(dimensions, color, layout, spacing).
|
||||
4. Automatically mark checkpoints as **passed** or **needs review** based on the
|
||||
visual comparison — no manual question required for items that clearly match.
|
||||
5. Flag items that require human judgment (subjective aesthetics, content accuracy)
|
||||
and present only those as manual UAT questions.
|
||||
|
||||
If automated verification is not available, fall back to the standard manual
|
||||
checkpoint questions defined in this workflow unchanged. This step is entirely
|
||||
conditional: if Playwright-MCP is not configured, behavior is unchanged from today.
|
||||
|
||||
**Display summary line before proceeding:**
|
||||
```
|
||||
UI checkpoints: {N} auto-verified, {M} queued for manual review
|
||||
```
|
||||
|
||||
</step>
|
||||
<!-- gsd:section id="automated-ui-verification" when="state:ui-phase-active" -->
|
||||
If `section_manifest` is `null` or `"automated-ui-verification"` is in its `included` list: read and execute `gsd-core/workflows/verify-work/steps/automated-ui-verification.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
<step name="find_summaries">
|
||||
**Find what to test:**
|
||||
@@ -195,29 +162,12 @@ Read each SUMMARY.md to extract testable deliverables.
|
||||
</step>
|
||||
|
||||
<step name="extract_tests">
|
||||
**MVP-mode UAT framing.** When `MVP_MODE=true`, follow the rules in `@~/.claude/gsd-core/references/verify-mvp-mode.md`. Briefly:
|
||||
|
||||
1. Generate the UAT script in three ordered sections: (a) user-flow walk-through derived from the phase's user-story goal, (b) technical checks (deferred — only run after user flow passes), (c) coverage check (goal-backward, narrowed to the user story's outcome clause).
|
||||
2. **User-flow steps run first.** Each step is one user action: open, fill, click, type, observe. No HTTP verbs, no JSON shapes, no error codes in user-flow steps.
|
||||
3. **Technical checks are deferred.** They run AFTER the user flow passes — same checks as non-MVP mode (endpoint schemas, error states, edge cases), just reordered.
|
||||
4. **If user-flow step N fails, do not advance.** The verdict is FAIL; technical checks do not run. The user can re-run after fixing the underlying flow.
|
||||
<!-- gsd:section id="mvp-uat-framing" when="state:phase-mvp-mode" -->
|
||||
If `section_manifest` is `null` or `"mvp-uat-framing"` is in its `included` list: read and execute `gsd-core/workflows/verify-work/steps/mvp-uat-framing.md`. Otherwise skip — do not read the file.
|
||||
<!-- /gsd:section -->
|
||||
|
||||
When `MVP_MODE=false` (mode is null, absent, or the phase has no `**Mode:**` line in ROADMAP.md), fall back to the standard UAT generation path — no behavioral change.
|
||||
|
||||
**User-story format guard.** When `MVP_MODE=true`, also verify the phase's goal is in User Story format via the centralized validator:
|
||||
|
||||
```bash
|
||||
PHASE_GOAL=$(gsd_run query roadmap.get-phase "${phase_number}" ${GSD_WS} --pick goal)
|
||||
USER_STORY_VALID=$(gsd_run query user-story.validate --story "$PHASE_GOAL" --pick valid)
|
||||
if [ "$USER_STORY_VALID" != "true" ]; then
|
||||
echo "Phase ${phase_number} has '**Mode:** mvp' in ROADMAP.md but the **Goal:** is not in user-story format."
|
||||
echo "Run /gsd mvp-phase ${phase_number} to set a user-story goal before verifying."
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
The verb owns the canonical regex `/^As a .+, I want to .+, so that .+\.$/` and returns slot extractions plus per-error guidance when invalid. Halt UAT generation on failure — never attempt to derive user-flow steps from a non-User-Story goal (low-quality UAT).
|
||||
|
||||
**Coverage-aware deterministic classification (#1602).** Before deriving checkpoints from prose, classify each SUMMARY's structured `coverage:` block. For each `*-SUMMARY.md`:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<step name="automated_ui_verification">
|
||||
**Automated UI Verification (when Playwright-MCP is available)**
|
||||
|
||||
Before UAT, check whether Playwright/Puppeteer MCP tools are available. UI-phase
|
||||
activation itself (`ui_phase_active`) is already resolved at init time — this
|
||||
section is only reached when that fact is `true` (see the `state:ui-phase-active`
|
||||
gate above), so re-deriving it here would be redundant.
|
||||
|
||||
```bash
|
||||
UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
|
||||
```
|
||||
|
||||
**If Playwright-MCP tools are available in this session (`mcp__playwright__*` tools
|
||||
respond to tool calls):**
|
||||
|
||||
For each UI checkpoint listed in the phase's UI-SPEC.md (or inferred from SUMMARY.md):
|
||||
|
||||
1. Use `mcp__playwright__navigate` (or equivalent) to open the component's URL.
|
||||
2. Use `mcp__playwright__screenshot` to capture a screenshot.
|
||||
3. Compare the screenshot visually against the spec's stated requirements
|
||||
(dimensions, color, layout, spacing).
|
||||
4. Automatically mark checkpoints as **passed** or **needs review** based on the
|
||||
visual comparison — no manual question required for items that clearly match.
|
||||
5. Flag items that require human judgment (subjective aesthetics, content accuracy)
|
||||
and present only those as manual UAT questions.
|
||||
|
||||
If automated verification is not available, fall back to the standard manual
|
||||
checkpoint questions defined in this workflow unchanged. This step is entirely
|
||||
conditional: if Playwright-MCP is not configured, behavior is unchanged from today.
|
||||
|
||||
**Display summary line before proceeding:**
|
||||
```
|
||||
UI checkpoints: {N} auto-verified, {M} queued for manual review
|
||||
```
|
||||
|
||||
</step>
|
||||
21
gsd-core/workflows/verify-work/steps/mvp-uat-framing.md
Normal file
21
gsd-core/workflows/verify-work/steps/mvp-uat-framing.md
Normal file
@@ -0,0 +1,21 @@
|
||||
**MVP-mode UAT framing.** When `MVP_MODE=true`, follow the rules in `@~/.claude/gsd-core/references/verify-mvp-mode.md`. Briefly:
|
||||
|
||||
1. Generate the UAT script in three ordered sections: (a) user-flow walk-through derived from the phase's user-story goal, (b) technical checks (deferred — only run after user flow passes), (c) coverage check (goal-backward, narrowed to the user story's outcome clause).
|
||||
2. **User-flow steps run first.** Each step is one user action: open, fill, click, type, observe. No HTTP verbs, no JSON shapes, no error codes in user-flow steps.
|
||||
3. **Technical checks are deferred.** They run AFTER the user flow passes — same checks as non-MVP mode (endpoint schemas, error states, edge cases), just reordered.
|
||||
4. **If user-flow step N fails, do not advance.** The verdict is FAIL; technical checks do not run. The user can re-run after fixing the underlying flow.
|
||||
|
||||
**User-story format guard.** When `MVP_MODE=true`, also verify the phase's goal is in User Story format via the centralized validator:
|
||||
|
||||
```bash
|
||||
_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
|
||||
PHASE_GOAL=$(gsd_run query roadmap.get-phase "${phase_number}" ${GSD_WS} --pick goal)
|
||||
USER_STORY_VALID=$(gsd_run query user-story.validate --story "$PHASE_GOAL" --pick valid)
|
||||
if [ "$USER_STORY_VALID" != "true" ]; then
|
||||
echo "Phase ${phase_number} has '**Mode:** mvp' in ROADMAP.md but the **Goal:** is not in user-story format."
|
||||
echo "Run /gsd mvp-phase ${phase_number} to set a user-story goal before verifying."
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
The verb owns the canonical regex `/^As a .+, I want to .+, so that .+\.$/` and returns slot extractions plus per-error guidance when invalid. Halt UAT generation on failure — never attempt to derive user-flow steps from a non-User-Story goal (low-quality UAT).
|
||||
Reference in New Issue
Block a user