Files
msd-core/gsd-core/workflows/spec-phase.md
Jeremy McSpadden fb37fa7dd5 fix(#725): route Codex gsd-tools calls through shim (#731)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 14:44:43 -04:00

15 KiB
Raw Blame History

Clarify WHAT a phase delivers through a Socratic interview loop with quantitative ambiguity scoring. Produces a SPEC.md with falsifiable requirements that discuss-phase treats as locked decisions.

This workflow handles "what" and "why" — discuss-phase handles "how".

<ambiguity_model> Score each dimension 0.0 (completely unclear) to 1.0 (crystal clear):

Dimension Weight Minimum What it measures
Goal Clarity 35% 0.75 Is the outcome specific and measurable?
Boundary Clarity 25% 0.70 What's in scope vs out of scope?
Constraint Clarity 20% 0.65 Performance, compatibility, data requirements?
Acceptance Criteria 20% 0.70 How do we know it's done?

Ambiguity score = 1.0 − (0.35×goal + 0.25×boundary + 0.20×constraint + 0.20×acceptance)

Gate: ambiguity ≤ 0.20 AND all dimensions ≥ their minimums → ready to write SPEC.md.

A score of 0.20 means 80% weighted clarity — enough precision that the planner won't silently make wrong assumptions. </ambiguity_model>

<interview_perspectives> Rotate through these perspectives — each naturally surfaces different blindspots:

Researcher (rounds 1–2): Ground the discussion in current reality.

  • "What exists in the codebase today related to this phase?"
  • "What's the delta between today and the target state?"
  • "What triggers this work — what's broken or missing?"

Simplifier (round 2): Surface minimum viable scope.

  • "What's the simplest version that solves the core problem?"
  • "If you had to cut 50%, what's the irreducible core?"
  • "What would make this phase a success even without the nice-to-haves?"

Boundary Keeper (round 3): Lock the perimeter.

  • "What explicitly will NOT be done in this phase?"
  • "What adjacent problems is it tempting to solve but shouldn't?"
  • "What does 'done' look like — what's the final deliverable?"

Failure Analyst (round 4): Find the edge cases that invalidate requirements.

  • "What's the worst thing that could go wrong if we get the requirements wrong?"
  • "What does a broken version of this look like?"
  • "What would cause a verifier to reject the output?"

Seed Closer (rounds 5–6): Lock remaining undecided territory.

  • "We have [dimension] at [score] — what would make it completely clear?"
  • "The remaining ambiguity is in [area] — can we make a decision now?"
  • "Is there anything you'd regret not specifying before planning starts?" </interview_perspectives>

Step 1: Initialize

_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 "$HOME/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" ]; then GSD_TOOLS="$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
INIT=$(gsd_run init phase-op "${PHASE}")
if [[ "$INIT" == @file:* ]]; then INIT=$(cat "${INIT#@file:}"); fi

Parse JSON for: phase_found, phase_dir, phase_number, phase_name, phase_slug, padded_phase, state_path, requirements_path, roadmap_path, planning_path, response_language, commit_docs.

If response_language is set: All user-facing text in this workflow MUST be in {response_language}. Technical terms, code, and file paths stay in English.

If phase_found is false:

Phase [X] not found in roadmap.
Use /gsd:progress to see available phases.

Exit.

Check for existing SPEC.md:

ls ${phase_dir}/*-SPEC.md 2>/dev/null | grep -v AI-SPEC | head -1 || true

If SPEC.md already exists:

If --auto: Auto-select "Update it". Log: [auto] SPEC.md exists — updating.

Otherwise: Use AskUserQuestion:

  • header: "Spec"
  • question: "Phase [X] already has a SPEC.md. What do you want to do?"
  • options:
    • "Update it" — Revise and re-score
    • "View it" — Show current spec
    • "Skip" — Exit (use existing spec as-is)

If "View": Display SPEC.md, then offer Update/Skip. If "Skip": Exit with message: "Existing SPEC.md unchanged. Run /gsd:discuss-phase [X] to continue." If "Update": Load existing SPEC.md, continue to Step 3.

Step 2: Scout Codebase

Read these files before any questions:

  • {requirements_path} — Project requirements
  • {state_path} — Decisions already made, current phase, blockers
  • ROADMAP.md phase entry — Phase description, goals, canonical refs

Grep the codebase for code/files relevant to this phase goal. Look for:

  • Existing implementations of similar functionality
  • Integration points where new code will connect
  • Test coverage gaps relevant to the phase
  • Prior phase artifacts (SUMMARY.md, VERIFICATION.md) that inform current state

Synthesize current state — the grounded baseline for the interview:

  • What exists today related to this phase
  • The gap between current state and the phase goal
  • The primary deliverable: what file/behavior/capability does NOT exist yet?

Confirm your current state synthesis internally. Do not present it to the user yet — you'll use it to ask precise, grounded questions.

Step 3: First Ambiguity Assessment

Before questioning begins, score the phase's current ambiguity based only on what ROADMAP.md and REQUIREMENTS.md say:

Goal Clarity:       [score 0.0–1.0]
Boundary Clarity:   [score 0.0–1.0]
Constraint Clarity: [score 0.0–1.0]
Acceptance Criteria:[score 0.0–1.0]

Ambiguity: [score] ([calculate])

If --auto and initial ambiguity already ≤ 0.20 with all minimums met: Skip interview — derive SPEC.md directly from roadmap + requirements. Log: [auto] Phase requirements are already sufficiently clear — generating SPEC.md from existing context. Jump to Step 6.

Otherwise: Continue to Step 4.

Step 4: Socratic Interview Loop

Max 6 rounds. Each round: 2–3 questions max. End round after user responds.

Round selection by perspective:

  • Round 1: Researcher
  • Round 2: Researcher + Simplifier
  • Round 3: Boundary Keeper
  • Round 4: Failure Analyst
  • Rounds 5–6: Seed Closer (focus on lowest-scoring dimensions)

After each round:

  1. Update all 4 dimension scores from the user's answers
  2. Calculate new ambiguity score
  3. Display the updated scoring:
After round [N]:
  Goal Clarity:       [score] (min 0.75) [✓ or ↑ needed]
  Boundary Clarity:   [score] (min 0.70) [✓ or ↑ needed]
  Constraint Clarity: [score] (min 0.65) [✓ or ↑ needed]
  Acceptance Criteria:[score] (min 0.70) [✓ or ↑ needed]
  Ambiguity: [score] (gate: ≤ 0.20)

Gate check after each round:

If gate passes (ambiguity ≤ 0.20 AND all minimums met):

If --auto: Jump to Step 6.

Otherwise: AskUserQuestion:

  • header: "Spec Gate Passed"
  • question: "Ambiguity is [score] — requirements are clear enough to write SPEC.md. Proceed?"
  • options:
    • "Yes — write SPEC.md" → Jump to Step 6
    • "One more round" → Continue interview
    • "Done talking — write it" → Jump to Step 6

If max rounds reached (6) and gate not passed:

If --auto: Write SPEC.md anyway — flag unresolved dimensions. Log: [auto] Max rounds reached. Writing SPEC.md with [N] dimensions below minimum. Planner will need to treat these as assumptions.

Otherwise: AskUserQuestion:

  • header: "Max Rounds"
  • question: "After 6 rounds, ambiguity is [score]. [List dimensions still below minimum.] What would you like to do?"
  • options:
    • "Write SPEC.md anyway — flag gaps" → Write SPEC.md, mark unresolved dimensions in Ambiguity Report
    • "Keep talking" → Continue (no round limit from here)
    • "Abandon" → Exit without writing

If --auto mode throughout: Replace all AskUserQuestion calls above with Claude's recommended choice. Log decisions inline. Apply the same logic as --auto in discuss-phase.

Text mode (workflow.text_mode: true or --text flag): Use plain-text numbered lists instead of AskUserQuestion TUI menus.

Step 5: (covered inline — ambiguity scoring is per-round)

Step 6: Generate SPEC.md

Use the SPEC.md template from @~/.claude/gsd-core/templates/spec.md.

Requirements for every requirement entry:

  • One specific, testable statement
  • Current state (what exists now)
  • Target state (what it should become)
  • Acceptance criterion (how to verify it was met)

Vague requirements are rejected:

  • ✗ "The system should be fast"
  • ✗ "Improve user experience"
  • ✓ "API endpoint responds in < 200ms at p95 under 100 concurrent requests"
  • ✓ "CLI command exits with code 1 and prints to stderr on invalid input"

Count requirements. The display in discuss-phase reads: "Found SPEC.md — {N} requirements locked."

Boundaries must be explicit lists:

  • "In scope" — what this phase produces
  • "Out of scope" — what it explicitly does NOT do (with brief reasoning)

Acceptance criteria must be pass/fail checkboxes — no "should feel good" or "looks reasonable."

If any dimensions are below minimum, mark them in the Ambiguity Report with: ⚠ Below minimum — planner must treat as assumption.

Write to: {phase_dir}/{padded_phase}-SPEC.md

Step 7: Commit

git add "${phase_dir}/${padded_phase}-SPEC.md"
git commit -m "spec(phase-${phase_number}): add SPEC.md for ${phase_name} — ${requirement_count} requirements (#2213)"

If commit_docs is false: Skip commit. Note that SPEC.md was written but not committed.

Step 8: Wrap Up

Display:

SPEC.md written — {N} requirements locked.

  Phase {X}: {name}
  Ambiguity: {final_score} (gate: ≤ 0.20)

Next: /gsd:discuss-phase {X}
  discuss-phase will detect SPEC.md and focus on implementation decisions only.

<critical_rules>

  • Every requirement MUST have current state, target state, and acceptance criterion
  • Boundaries section is MANDATORY — cannot be empty
  • "In scope" and "Out of scope" must be explicit lists, not narrative prose
  • Acceptance criteria must be pass/fail — no subjective criteria
  • SPEC.md is NEVER written if the user selects "Abandon"
  • Do NOT ask about HOW to implement — that is discuss-phase territory
  • Scout the codebase BEFORE the first question — grounded questions only
  • Max 2–3 questions per round — do not frontload all questions at once </critical_rules>

<success_criteria>

  • Codebase scouted and current state understood before questioning
  • All 4 dimensions scored after every round
  • Gate passed OR user explicitly chose to write despite gaps
  • SPEC.md contains only falsifiable requirements
  • Boundaries are explicit (in scope / out of scope with reasoning)
  • Acceptance criteria are pass/fail checkboxes
  • SPEC.md committed atomically (when commit_docs is true)
  • User directed to /gsd:discuss-phase as next step </success_criteria>