Files
msd-core/gsd-core/workflows/new-milestone.md
Tom Boucher 463cffd894 chore(#604): rename get-shit-done/ runtime directory to gsd-core/ (#615)
* chore(#604): rename get-shit-done/ runtime directory to gsd-core/

Renames the installed runtime directory `get-shit-done/` to `gsd-core/` so the
on-disk name matches the package (`@opengsd/gsd-core`), repo, and binary
(`gsd-tools`). The npm package name and binary are unchanged; npx/npm consumers
are unaffected.

Mechanical (bulk, ~90% of the diff):
- `git mv get-shit-done gsd-core`
- Swept path/identifier references across the repo via
  `perl -pe 's/get-shit-done(?!-\w)/gsd-core/g'`. The negative lookahead
  preserves the five legitimate slug variants that are NOT the directory:
  get-shit-done-{OLD,cc,classic,cli,redux} (old package/repo names).
- Build/manifest wiring: package.json (bin, files, coverage globs),
  tsconfig.build.json (outDir), ~86 .gitignore build-output entries,
  stryker.config.mjs, scan-ignore files, install.js path strings.
- Frozen (not rewritten): CHANGELOG.md history; translated docs
  (README.<locale>.md and docs/{ja-JP,ko-KR,pt-BR,zh-CN}/).

New logic (review here):
- src/installer-migrations/003-rename-get-shit-done-to-gsd-core.cts: a proper
  ADR-0008 installer migration. On upgrade it walks the legacy
  `~/.claude/get-shit-done/` tree, classifies each file via the prior install
  manifest, and emits remove-managed / backup-and-remove for managed files
  while PRESERVING unknown user-added files. Symlink-safe (skips a symlinked
  root and symlinked entries; bounds-checks every path under configDir). The
  framework rolls back on install failure. Emptied dirs may remain (framework
  has no recursive dir-removal primitive) — documented.
- scripts/lint-legacy-dir-name.cjs: CI regression guard forbidding the bare
  `get-shit-done` directory token (split token to avoid self-match; case-
  insensitive; `(?!-\w)` lookahead allows the slug variants; allowlists
  CHANGELOG, translated docs, and `gsd-allow-legacy-name` marker lines).
  Wired into the lint-tests CI job.
- Restored scripts/lint-package-identity-drift.cjs detection regexes (the
  mechanical sweep had wrongly rewritten the old-name patterns it exists to
  detect) and marked them as intentional legacy references.
- TDD tests for the migration and the guard; do.md slash-command guard regex
  tightened so a `/gsd-core/bin` path segment is not mistaken for a command;
  changeset + docs/installer-migrations.md row added.

Breaking: the installed runtime path moves `~/.claude/get-shit-done/` ->
`~/.claude/gsd-core/`. Migration 003 removes the stale legacy dir's managed
files (preserving user files) on upgrade. Users with custom hooks/configs
hardcoding the old path must update them.

Closes #604

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): unsweep pending changesets + allowlist injection-example docs

CI fixes for the rename PR:
- Do not sweep pending .changeset/*.md (ephemeral release-note fragments,
  like CHANGELOG); reverted those body edits so 5 pre-existing malformed
  fragments (missing type/pr) no longer enter the PR diff and trip docs-lint.
  Allowlisted .changeset/ in the legacy-name guard accordingly.
- Allowlisted TEST-EXAMPLES.md and docs/explanation/security-model.md in
  prompt-injection-scan.sh: they contain intentional injection examples /
  security-model prose; the path-reference rewrites are kept.

CodeQL alerts on this PR are pre-existing (alert lines unchanged by this PR;
none in the new migration/guard) and are out of scope for the rename.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): resolve CodeQL alerts surfaced on this PR

The rename diff touched files carrying pre-existing CodeQL findings; per the
no-pre-existing-dismissal rule, fixing every surfaced alert rather than waving
them off. All behavior-preserving:

- scripts/ci-test-scope.cjs: build the config-path match from string
  .includes() instead of a RegExp over an arg-derived value (js/regex-injection).
- src/profile-output.cts: escape backslashes before pipe-escaping desc/safeName
  so the table-cell escape is complete (js/incomplete-sanitization).
- tests/{bug-2643,bug-2808,docs-parity-live-registry}: two-pass HTML-comment
  strip so a bare/unclosed `<!--` cannot survive (js/incomplete-multi-character-sanitization).
- tests/inline-plan-threshold: drop the no-op `\s`->`\s` identity replace,
  keep the meaningful POSIX-class conversion (js/identity-replacement).

Verified: build:lib green; the touched test files + ci-test-scope + profile-output
suites pass; lint:legacy-name clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): correctly resolve remaining CodeQL alerts (regex-injection + sanitization)

The prior commit's fixes for two alerts were ineffective:
- ci-test-scope.cjs js/regex-injection: the alert is the CLI-arg-derived `file`
  reaching static regex `.test(file)` calls (not the config rule). Removed ALL
  regex over file/t — startsWith/includes/=== string checks + an isWindowsHint
  helper — so there is no regex sink for the tainted value.
- js/incomplete-multi-character-sanitization (3 test files): a single
  `.replace(/<!--...-->/g,'')` can let `<!--` re-form. Replaced with a fixpoint
  loop (replace until stable) plus a final bare-opener strip.

Verified: no regex over file/t remains; ci-test-scope + the 3 test suites pass;
lint:legacy-name clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): make ci-test-scope + comment-strippers regex-free to clear CodeQL

CodeQL flags the regex PATTERNS syntactically (regex-injection on the
--files arg split; incomplete-multi-character-sanitization on the <!--...-->
replace), so loop fixes do not satisfy it. Made these paths regex-free:
- ci-test-scope.cjs splitFiles: char-by-char separator tokenizer (no /[,\\s]+/).
- 3 test files: indexOf/slice HTML-comment stripper (no .replace(/<!--/)).
Behavior preserved; ci-test-scope + the 3 suites pass; guard clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): unblock security base64 scan on the large rename diff

The security job hit its 10m timeout: base64-scan.sh choked on the binary
test fixture tests/feat-3594-parser-property-style.test.cjs (embedded NUL/
non-UTF8 bytes -> thousands of bogus blobs + "ignored null byte" warnings),
and the ~800-file rename diff is slow to scan regardless.

- scripts/base64-scan.sh: skip binary-by-content files (grep -Iq .) — they
  can't carry base64-obfuscated *text* and feeding NUL bytes through the
  per-line scanner is pathologically slow. collect_files already filtered
  binary *extensions*; this catches binary *content* in text extensions.
- .github/workflows/security-scan.yml: raise the security job timeout 10m->30m
  to accommodate very large diffs (the scan itself is unchanged).

Verified locally: scan skips the fixture, 0 "ignored null byte" warnings,
0 findings, exit 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): sweep get-shit-done refs introduced by merging next

The branch was updated with next (#614/#384/#618 etc.), which reference the
get-shit-done/ dir (still named that on next). Swept the stale references in
the merged files to gsd-core so the rename stays consistent and lint:legacy-name
passes:
- commands/gsd/discuss-phase.md (runtime-launcher shim paths)
- src/core.cts (getAgentsDir layout comments)
- tests/bug-384-agents-runtime-aware.test.cjs (require path to runtime lib)

Verified: guard 0 violations; build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): exclude gsd-core/ path segments from bug-3683 command cross-ref invariant

The #614 runtime-launcher shim added to discuss-phase.md references
`${_GSD_RUNTIME_ROOT}/gsd-core/bin/...`. bug-3683's REF_PATTERN excluded path-y
refs only via lookbehind, but `}` precedes `/gsd-core/` in the shim, so it
mis-read the directory path as a dangling `/gsd-core` command ref (same class as
the #604 bug-2954 fix). Added a trailing `(?![\w-]*\/)` so `/gsd-<x>/...` path
segments are not treated as slash-command references.

Verified locally on BOTH platforms before pushing:
- mac (node 26) full suite: 0 failures
- gsd-test-runner (linux, node22 image) full suite: 0 failures
- bug-3683 + bug-2954 pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): lazily resolve findProjectRoot in gsd-tools (harden flaky CI)

CI intermittently failed state.test's gsd-tools subprocess with
"findProjectRoot is not a function" (flip-flopping across legs; not reproducible
on mac full suite, gsd-test linux full suite, test:unit, or state.test x8).
findProjectRoot is a re-export from core.cjs (sourced from project-root.cjs);
binding it via destructure at module-load can be undefined under a load-ordering
edge. Resolve it lazily at call time via a small wrapper so the lookup happens
after core.cjs is fully initialized.

Verified green on BOTH platforms before pushing:
- mac (node 26) full suite: 0 failures
- gsd-test-runner (linux, node22) full suite: 0 failures
- state.test.cjs: 106/106; gsd-tools loads cleanly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#604): allowlist verification-patterns.md placeholder examples in secret scan

The rename git-mv'd references/verification-patterns.md into gsd-core/, pulling
it into the secret-scan diff. It documents stub/placeholder RED-FLAG env-var
examples (illustrative Stripe test-key / database-URL / API-key placeholders) —
not real credentials. Added it to .secretscanignore with the strict annotation,
mirroring the existing gsd-core/workflows/plan-phase.md exception.

Verified locally: secret-scan-lint --strict OK; secret-scan --diff origin/next
exits 0 with 0 findings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 18:35:29 -04:00

26 KiB
Raw Blame History

Start a new milestone cycle for an existing project. Loads project context, gathers milestone goals (from MILESTONE-CONTEXT.md or conversation), updates PROJECT.md and STATE.md, optionally runs parallel research, defines scoped requirements with REQ-IDs, spawns the roadmapper to create phased execution plan, and commits all artifacts. Brownfield equivalent of new-project.

<required_reading>

Read all files referenced by the invoking prompt's execution_context before starting.

</required_reading>

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

  • gsd-project-researcher — Researches project-level technical decisions
  • gsd-research-synthesizer — Synthesizes findings from parallel research agents
  • gsd-roadmapper — Creates phased execution roadmaps </available_agent_types>

1. Load Context

Parse $ARGUMENTS before doing anything else:

  • --reset-phase-numbers flag → opt into restarting roadmap phase numbering at 1
  • remaining text → use as milestone name if present

If the flag is absent, keep the current behavior of continuing phase numbering from the previous milestone.

  • Read PROJECT.md (existing project, validated requirements, decisions)
  • Read MILESTONES.md (what shipped previously)
  • Read STATE.md (pending todos, blockers)
  • Check for MILESTONE-CONTEXT.md (from /gsd-discuss-milestone)

2. Gather Milestone Goals

If MILESTONE-CONTEXT.md exists:

  • Use features and scope from discuss-milestone
  • Present summary for confirmation

If no context file:

  • Present what shipped in last milestone

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.

  • Ask inline (freeform, NOT AskUserQuestion): "What do you want to build next?"
  • Wait for their response, then use AskUserQuestion to probe specifics
  • If user selects "Other" at any point to provide freeform input, ask follow-up as plain text — not another AskUserQuestion

2.5. Scan Planted Seeds

Check .planning/seeds/ for seed files that match the milestone goals gathered in step 2.

ls .planning/seeds/SEED-*.md 2>/dev/null

If no seed files exist: Skip this step silently — do not print any message or prompt.

If seed files exist: Read each SEED-*.md file and extract from its frontmatter and body:

  • Idea — the seed title (heading after frontmatter, e.g. # SEED-001: <idea>)
  • Trigger conditions — the trigger_when frontmatter field and the "When to Surface" section's bullet list
  • Planted during — the planted_during frontmatter field (for context)

Compare each seed's trigger conditions against the milestone goals from step 2. A seed matches when its trigger conditions are relevant to any of the milestone's target features or goals.

If no seeds match: Skip silently — do not prompt the user.

If matching seeds found:

--auto mode: Auto-select ALL matching seeds. Log: [auto] Selected N matching seed(s): [list seed names]

Text mode (TEXT_MODE=true): Present matching seeds as a plain-text numbered list:

Seeds that match your milestone goals:
1. SEED-001: <idea> (trigger: <trigger_when>)
2. SEED-003: <idea> (trigger: <trigger_when>)

Enter numbers to include (comma-separated), or "none" to skip:

Normal mode: Present via AskUserQuestion:

AskUserQuestion(
  header: "Seeds",
  question: "These planted seeds match your milestone goals. Include any in this milestone's scope?",
  multiSelect: true,
  options: [
    { label: "SEED-001: <idea>", description: "Trigger: <trigger_when> | Planted during: <planted_during>" },
    ...
  ]
)

After selection:

  • Selected seeds become additional context for requirement definition in step 9. Store them in an accumulator (e.g. $SELECTED_SEEDS) so step 9 can reference the ideas and their "Why This Matters" sections when defining requirements.
  • Unselected seeds remain untouched in .planning/seeds/ — never delete or modify seed files during this workflow.

3. Determine Milestone Version

  • Parse last version from MILESTONES.md
  • Suggest next version (v1.0 → v1.1, or v2.0 for major)
  • Confirm with user

3.5. Verify Milestone Understanding

Before writing any files, present a summary of what was gathered and ask for confirmation.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 GSD ► MILESTONE SUMMARY
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

**Milestone v[X.Y]: [Name]**

**Goal:** [One sentence]

**Target features:**
- [Feature 1]
- [Feature 2]
- [Feature 3]

**Key context:** [Any important constraints, decisions, or notes from questioning]

AskUserQuestion:

  • header: "Confirm?"
  • question: "Does this capture what you want to build in this milestone?"
  • options:
    • "Looks good" — Proceed to write PROJECT.md
    • "Adjust" — Let me correct or add details

If "Adjust": Ask what needs changing (plain text, NOT AskUserQuestion). Incorporate changes, re-present the summary. Loop until "Looks good" is selected.

If "Looks good": Proceed to Step 4.

4. Update PROJECT.md

Add/update:

## 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.

Ensure the ## Evolution section exists in PROJECT.md. If missing (projects created before this feature), add it before the footer:

## Evolution

This document evolves at phase transitions and milestone boundaries.

**After each phase transition** (via `/gsd-transition`):
1. Requirements invalidated? → Move to Out of Scope with reason
2. Requirements validated? → Move to Validated with phase reference
3. New requirements emerged? → Add to Active
4. Decisions to log? → Add to Key Decisions
5. "What This Is" still accurate? → Update if drifted

**After each milestone** (via `/gsd:complete-milestone`):
1. Full review of all sections
2. Core Value check — still the right priority?
3. Audit Out of Scope — reasons still valid?
4. Update Context with current state

5. Update STATE.md

Reset STATE.md frontmatter AND body atomically via the SDK. This writes the new milestone version/name into the YAML frontmatter, resets status to planning, zeroes progress.* counters, and rewrites the ## Current Position section to the new-milestone template. Accumulated Context (decisions, blockers, todos) is preserved across the switch — symmetric with milestone.complete.

_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 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" "$@"; }; 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
gsd_run query state.milestone-switch --milestone "v[X.Y]" --name "[Name]"

The resulting Current Position section looks like:

## Current Position

Phase: Not started (defining requirements)
Plan: —
Status: Defining requirements
Last activity: [today] — Milestone v[X.Y] started

Bug #2630: a prior version of this workflow rewrote the Current Position body manually but left the frontmatter pointing at the previous milestone, so every downstream reader (state.json, getMilestoneInfo, progress bars) reported the stale milestone until the first phase advance forced a resync. Always use the SDK handler above — do not hand-edit STATE.md here.

6. Cleanup and Commit

Delete MILESTONE-CONTEXT.md if exists (consumed).

Clear leftover phase directories from the previous milestone:

gsd_run query phases.clear --confirm
gsd_run query commit "docs: start milestone v[X.Y] [Name]" --files .planning/PROJECT.md .planning/STATE.md

7. Load Context and Resolve Models

INIT=$(gsd_run query init.new-milestone)
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)
AGENT_SKILLS_ROADMAPPER=$(gsd_run query agent-skills gsd-roadmapper)

Extract from init JSON: researcher_model, synthesizer_model, roadmapper_model, commit_docs, research_enabled, current_milestone, project_exists, roadmap_exists, latest_completed_milestone, phase_dir_count, phase_archive_path, agents_installed, missing_agents.

If agents_installed is false: Display a warning before proceeding:

⚠ GSD agents not installed. The following agents are missing from your agents directory:
  {missing_agents joined with newline}

Subagent spawns (gsd-project-researcher, gsd-research-synthesizer, gsd-roadmapper) will fail
with "agent type not found". Run the installer with --global to make agents available:

  npx @opengsd/gsd-core@latest --global

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:

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}.

8. Research Decision

Check research_enabled from init JSON (loaded from config).

If research_enabled is true:

AskUserQuestion: "Research the domain ecosystem for new features before defining requirements?"

  • "Research first (Recommended)" — Discover patterns, features, architecture for NEW capabilities
  • "Skip research for this milestone" — Go straight to requirements (does not change your default)

If research_enabled is false:

AskUserQuestion: "Research the domain ecosystem for new features before defining requirements?"

  • "Skip research (current default)" — Go straight to requirements
  • "Research first" — Discover patterns, features, architecture for NEW capabilities

IMPORTANT: Do NOT persist this choice to config.json. The workflow.research setting is a persistent user preference that controls plan-phase behavior across the project. Changing it here would silently alter future /gsd:plan-phase behavior. To change the default, use /gsd:settings.

If user chose "Research first":

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 GSD ► RESEARCHING
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

◆ Spawning 4 researchers in parallel... (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)
  → Stack, Features, Architecture, Pitfalls
mkdir -p .planning/research

Spawn 4 parallel gsd-project-researcher agents. Each uses this template with dimension-specific fields:

Common structure for all 4 researchers:

Agent(prompt="
<research_type>Project Research — {DIMENSION} for [new features].</research_type>

<milestone_context>
SUBSEQUENT MILESTONE — Adding [target features] to existing app.
{EXISTING_CONTEXT}
Focus ONLY on what's needed for the NEW features.
</milestone_context>

<question>{QUESTION}</question>

<files_to_read>
- .planning/PROJECT.md (Project context)
</files_to_read>

${AGENT_SKILLS_RESEARCHER}

<downstream_consumer>{CONSUMER}</downstream_consumer>

<quality_gate>{GATES}</quality_gate>

<output>
Write to: .planning/research/{FILE}
Use template: ~/.claude/gsd-core/templates/research-project/{FILE}
</output>
", subagent_type="gsd-project-researcher", model="{researcher_model}", description="{DIMENSION} research")

Dimension-specific fields:

Field Stack Features Architecture Pitfalls
EXISTING_CONTEXT Existing validated capabilities (DO NOT re-research): [from PROJECT.md] Existing features (already built): [from PROJECT.md] Existing architecture: [from PROJECT.md or codebase map] Focus on common mistakes when ADDING these features to existing system
QUESTION What stack additions/changes are needed for [new features]? How do [target features] typically work? Expected behavior? How do [target features] integrate with existing architecture? Common mistakes when adding [target features] to [domain]?
CONSUMER Specific libraries with versions for NEW capabilities, integration points, what NOT to add Table stakes vs differentiators vs anti-features, complexity noted, dependencies on existing Integration points, new components, data flow changes, suggested build order Warning signs, prevention strategy, which phase should address it
GATES Versions current (verify with Context7), rationale explains WHY, integration considered Categories clear, complexity noted, dependencies identified Integration points identified, new vs modified explicit, build order considers deps Pitfalls specific to adding these features, integration pitfalls covered, prevention actionable
FILE STACK.md FEATURES.md ARCHITECTURE.md PITFALLS.md

ORCHESTRATOR RULE — CODEX RUNTIME: After calling all 4 researcher Agent() calls above, do NOT read research files or synthesize content independently while the subagents are active. Wait for all 4 researchers to complete before spawning the synthesizer. This prevents duplicate work and wasted context.

After all 4 complete, spawn synthesizer:

Agent(prompt="
Synthesize research outputs into SUMMARY.md.

<files_to_read>
- .planning/research/STACK.md
- .planning/research/FEATURES.md
- .planning/research/ARCHITECTURE.md
- .planning/research/PITFALLS.md
</files_to_read>

${AGENT_SKILLS_SYNTHESIZER}

Write to: .planning/research/SUMMARY.md
Use template: ~/.claude/gsd-core/templates/research-project/SUMMARY.md
Commit after writing.
", subagent_type="gsd-research-synthesizer", model="{synthesizer_model}", description="Synthesize research")

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.

Display key findings from SUMMARY.md:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 GSD ► RESEARCH COMPLETE ✓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

**Stack additions:** [from SUMMARY.md]
**Feature table stakes:** [from SUMMARY.md]
**Watch Out For:** [from SUMMARY.md]

If "Skip research": Continue to Step 9.

9. Define Requirements

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 GSD ► DEFINING REQUIREMENTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Read PROJECT.md: core value, current milestone goals, validated requirements (what exists).

If $SELECTED_SEEDS is non-empty (from step 2.5): Include selected seed ideas and their "Why This Matters" sections as additional input when defining requirements. Seeds provide user-validated feature ideas that should be incorporated into the requirement categories alongside research findings or conversation-gathered features.

If research exists: Read FEATURES.md, extract feature categories.

Present features by category:

## [Category 1]
**Table stakes:** Feature A, Feature B
**Differentiators:** Feature C, Feature D
**Research notes:** [any relevant notes]

If no research: Gather requirements through conversation. Ask: "What are the main things users need to do with [new features]?" Clarify, probe for related capabilities, group into categories.

Scope each category via AskUserQuestion (multiSelect: true, header max 12 chars):

  • "[Feature 1]" — [brief description]
  • "[Feature 2]" — [brief description]
  • "None for this milestone" — Defer entire category

Track: Selected → this milestone. Unselected table stakes → future. Unselected differentiators → out of scope.

Identify gaps via AskUserQuestion:

  • "No, research covered it" — Proceed
  • "Yes, let me add some" — Capture additions

Generate REQUIREMENTS.md:

  • v1 Requirements grouped by category (checkboxes, REQ-IDs)
  • Future Requirements (deferred)
  • Out of Scope (explicit exclusions with reasoning)
  • Traceability section (empty, filled by roadmap)

REQ-ID format: [CATEGORY]-[NUMBER] (AUTH-01, NOTIF-02). Continue numbering from existing.

Requirement quality criteria:

Good requirements are:

  • Specific and testable: "User can reset password via email link" (not "Handle password reset")
  • User-centric: "User can X" (not "System does Y")
  • Atomic: One capability per requirement (not "User can login and manage profile")
  • Independent: Minimal dependencies on other requirements

Present FULL requirements list for confirmation:

## Milestone v[X.Y] Requirements

### [Category 1]
- [ ] **CAT1-01**: User can do X
- [ ] **CAT1-02**: User can do Y

### [Category 2]
- [ ] **CAT2-01**: User can do Z

Does this capture what you're building? (yes / adjust)

If "adjust": Return to scoping.

Commit requirements:

gsd_run query commit "docs: define milestone v[X.Y] requirements" --files .planning/REQUIREMENTS.md

10. Create Roadmap

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 GSD ► CREATING ROADMAP
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

◆ Spawning roadmapper... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)

Starting phase number:

  • If --reset-phase-numbers is active, start at Phase 1
  • Otherwise, continue from the previous milestone's last phase number (v1.0 ended at phase 5 → v1.1 starts at phase 6)
Agent(prompt="
<planning_context>
<files_to_read>
- .planning/PROJECT.md
- .planning/REQUIREMENTS.md
- .planning/research/SUMMARY.md (if exists)
- .planning/config.json
- .planning/MILESTONES.md
</files_to_read>

${AGENT_SKILLS_ROADMAPPER}

</planning_context>

<instructions>
Create roadmap for milestone v[X.Y]:
1. Respect the selected numbering mode:
   - `--reset-phase-numbers` → start at Phase 1
   - default behavior → continue from the previous milestone's last phase number
2. Derive phases from THIS MILESTONE's requirements only
3. Map every requirement to exactly one phase
4. Derive 2-5 success criteria per phase (observable user behaviors)
5. Validate 100% coverage
6. Write files immediately (ROADMAP.md, STATE.md, update REQUIREMENTS.md traceability)
7. Return ROADMAP CREATED with summary

Write files first, then return.
</instructions>
", subagent_type="gsd-roadmapper", model="{roadmapper_model}", description="Create roadmap")

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 return:

If ## ROADMAP BLOCKED: Present blocker, work with user, re-spawn.

If ## ROADMAP CREATED: Read ROADMAP.md, present inline:

## Proposed Roadmap

**[N] phases** | **[X] requirements mapped** | All covered ✓

| # | Phase | Goal | Requirements | Success Criteria |
|---|-------|------|--------------|------------------|
| [N] | [Name] | [Goal] | [REQ-IDs] | [count] |

### Phase Details

**Phase [N]: [Name]**
Goal: [goal]
Requirements: [REQ-IDs]
Success criteria:
1. [criterion]
2. [criterion]

Ask for approval via AskUserQuestion:

  • "Approve" — Commit and continue
  • "Adjust phases" — Tell me what to change
  • "Review full file" — Show raw ROADMAP.md

If "Adjust": Get notes, re-spawn roadmapper with revision context, loop until approved. If "Review": Display raw ROADMAP.md, re-ask.

Commit roadmap (after approval):

gsd_run query commit "docs: create milestone v[X.Y] roadmap ([N] phases)" --files .planning/ROADMAP.md .planning/STATE.md .planning/REQUIREMENTS.md

After roadmap approval, scan pending todos against the newly approved phases. For each todo whose scope matches a phase, tag it with resolves_phase: N in its YAML frontmatter.

Check for pending todos:

PENDING_TODOS=$(ls .planning/todos/pending/*.md 2>/dev/null | head -50)

If no pending todos exist: Skip this step silently.

If pending todos exist:

Read the approved ROADMAP.md and extract the phase list: phase number, phase name, goal, and requirement IDs.

For each pending todo, compare:

  • The todo's title and area frontmatter fields
  • The todo body (Problem and Solution sections)

Against each phase's:

  • Phase goal
  • Requirement IDs and descriptions

Match criteria (best-effort — do not over-match): A todo is considered resolved by a phase if the phase's goal or requirements directly describe implementing the same feature, area, or capability as the todo. Narrow, specific todos with concrete scopes are the best candidates. Vague or cross-cutting todos should be left unlinked.

For each matched todo, add resolves_phase: [N] to the YAML frontmatter block (after the existing fields):

---
created: [existing]
title: [existing]
area: [existing]
resolves_phase: [N]
files: [existing]
---

Only modify todos that have a clear, confident match. Leave unmatched todos unmodified.

If any todos were linked:

gsd_run query commit "docs: tag [count] pending todos with resolves_phase after milestone v[X.Y] roadmap" --files .planning/todos/pending/*.md

Print a summary:

◆ Linked [N] pending todos to roadmap phases:
  → [todo title] → Phase [N]: [Phase Name]
  (Leave [M] unmatched todos in pending/)

11. Done

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 GSD ► MILESTONE INITIALIZED ✓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

**Milestone v[X.Y]: [Name]**

| Artifact       | Location                    |
|----------------|-----------------------------|
| Project        | `.planning/PROJECT.md`      |
| Research       | `.planning/research/`       |
| Requirements   | `.planning/REQUIREMENTS.md` |
| Roadmap        | `.planning/ROADMAP.md`      |

**[N] phases** | **[X] requirements** | Ready to build ✓

## ▶ Next Up — [${PROJECT_CODE}] ${PROJECT_TITLE}

**Phase [N]: [Phase Name]** — [Goal]

`/clear` then:

`/gsd:discuss-phase [N] ${GSD_WS}` — gather context and clarify approach

Also: `/gsd:plan-phase [N] ${GSD_WS}` — skip discussion, plan directly

<success_criteria>

  • PROJECT.md updated with Current Milestone section
  • STATE.md reset for new milestone
  • MILESTONE-CONTEXT.md consumed and deleted (if existed)
  • Research completed (if selected) — 4 parallel agents, milestone-aware
  • Requirements gathered and scoped per category
  • REQUIREMENTS.md created with REQ-IDs
  • gsd-roadmapper spawned with phase numbering context
  • Roadmap files written immediately (not draft)
  • User feedback incorporated (if any)
  • Phase numbering mode respected (continued or reset)
  • All commits made (if planning docs committed)
  • Pending todos scanned for phase matches; matched todos tagged with resolves_phase: N
  • User knows next step: /gsd:discuss-phase [N] ${GSD_WS}

Atomic commits: Each phase commits its artifacts immediately. </success_criteria>