Adds workflow.code_review_command config key that allows solo devs to
plug external AI review tools into the ship flow. When configured, the
ship workflow generates a diff, builds a review prompt with stats and
phase context, pipes it to the command via stdin, and parses JSON output
with verdict/confidence/issues. Handles timeout (120s) and failures
gracefully by falling through to the existing manual review flow.
Closes#1876
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
With --test-concurrency=4, bug-1834 and bug-1924 run build-hooks.js concurrently
with bug-1736. build-hooks.js creates hooks/dist/ empty first then copies files,
creating a window where bug-1736 sees an empty directory, install() fails with
"directory is empty", and process.exit(1) kills the test process.
Added the same before() pattern used by all other install tests.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add isQwen branch in copyWithPathReplacement for .md files converting
CLAUDE.md to QWEN.md and 'Claude Code' to 'Qwen Code'
- Add isQwen branch in copyWithPathReplacement for .js/.cjs files
converting .claude paths to .qwen equivalents
- Add Qwen Code program and command labels in finishInstall() so the
post-install message shows 'Qwen Code' instead of 'Claude Code'
Closes#2081
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Adds Qwen Code as a supported installation target. Users can now run
`npx get-shit-done-cc --qwen` to install all 68+ GSD commands as skills
to `~/.qwen/skills/gsd-*/SKILL.md`, following the same open standard as
Claude Code 2.1.88+.
Changes:
- `bin/install.js`: --qwen flag, getDirName/getGlobalDir/getConfigDirFromHome
support, QWEN_CONFIG_DIR env var, install/uninstall pipelines, interactive
picker option 12 (Trae→13, Windsurf→14, All→15), .qwen path replacements in
copyCommandsAsClaudeSkills and copyWithPathReplacement, legacy commands/gsd
cleanup, fix processAttribution hardcoded 'claude' → runtime-aware
- `README.md`: Qwen Code in tagline, runtime list, verification commands,
skills format NOTE, install/uninstall examples, flag reference, env vars
- `tests/qwen-install.test.cjs`: 13 tests covering directory mapping, env var
precedence, install/uninstall lifecycle, artifact preservation
- `tests/qwen-skills-migration.test.cjs`: 11 tests covering frontmatter
conversion, path replacement, stale skill cleanup, SKILL.md format validation
- `tests/multi-runtime-select.test.cjs`: Updated for new option numbering
Closes#2019
Co-authored-by: Muhammad <basirovmb1988@gmail.com>
Co-authored-by: Jonathan Lima <eezyjb@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Running git clean inside a worktree treats files committed on the feature
branch as untracked — from the worktree's perspective they were never staged.
The executor deletes them, then commits only its own deliverables; when the
worktree branch merges back the deletions land on the main branch, destroying
prior-wave work (documented across 8 incidents, including commit c6f4753
"Wave 2 executor incorrectly ran git-clean on the worktree").
- Add <destructive_git_prohibition> block to gsd-executor.md explaining
exactly why git clean is unsafe in worktree context and what to use instead
- Add regression tests (bug-2075-worktree-deletion-safeguards.test.cjs)
covering Failure Mode B (git clean prohibition), Failure Mode A
(worktree_branch_check presence audit across all worktree-spawning
workflows), and both defense-in-depth deletion checks from #1977
Failure Mode A and defense-in-depth checks (post-commit --diff-filter=D in
gsd-executor.md, pre-merge --diff-filter=D in execute-phase.md) were already
implemented — tests confirm they remain in place.
Fixes#2075
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a new command and CLI subcommand that converts a GSD-2 `.gsd/`
project back to GSD v1 `.planning/` format — the reverse of the forward
migration GSD-2 ships.
Closes#2069
Maps GSD-2's Milestone → Slice → Task hierarchy to v1's flat
Milestone sections → Phase → Plan structure. Slices are numbered
sequentially across all milestones; tasks become numbered plans within
their phase. Completion state, research files, and summaries are
preserved.
New files:
- `get-shit-done/bin/lib/gsd2-import.cjs` — parser, transformer, writer
- `commands/gsd/from-gsd2.md` — slash command definition
- `tests/gsd2-import.test.cjs` — 41 tests, 99.21% statement coverage
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes#2070
Two-layer fix for the bug where executor agents in worktree isolation mode
could leave SUMMARY.md uncommitted, then have it silently destroyed by
`git worktree remove --force` during post-wave cleanup.
Layer 1 — Clarify executor instruction (execute-phase.md):
Added explicit REQUIRED note to the <parallel_execution> block making
clear that SUMMARY.md MUST be committed before the agent returns,
and that the git_commit_metadata step in execute-plan.md handles the
SUMMARY.md-only commit path automatically in worktree mode.
Layer 2 — Orchestrator safety net (execute-phase.md):
Before force-removing each worktree, check for any uncommitted SUMMARY.md
files. If found, commit them on the worktree branch and re-merge into the
main branch before removal. This prevents data loss even when an executor
skips the commit step due to misinterpreting the "do not modify
orchestrator files" instruction.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes#1885
The upstream bug anthropics/claude-code#13898 causes Claude Code to strip all
inherited MCP tools from agents that declare a `tools:` frontmatter restriction,
making `mcp__context7__*` declarations in agent frontmatter completely inert.
Implements Fix 2 from issue #1885 (trek-e's chosen approach): replace the
`<mcp_tool_usage>` block in gsd-executor and gsd-planner with a
`<documentation_lookup>` block that checks for MCP availability first, then
falls back to the Context7 CLI via Bash (`npx --yes ctx7@latest`). Adds the
same `<documentation_lookup>` block to the six researcher agents that declare
MCP tools but lacked any fallback instruction.
Agents fixed (8 total):
- gsd-executor (had <mcp_tool_usage>, now <documentation_lookup> with CLI fallback)
- gsd-planner (had <mcp_tool_usage>, now compact <documentation_lookup>; stays under 45K limit)
- gsd-phase-researcher (new <documentation_lookup> block)
- gsd-project-researcher (new <documentation_lookup> block)
- gsd-ui-researcher (new <documentation_lookup> block)
- gsd-advisor-researcher (new <documentation_lookup> block)
- gsd-ai-researcher (new <documentation_lookup> block)
- gsd-domain-researcher (new <documentation_lookup> block)
When the upstream Claude Code bug is fixed, the MCP path in step 1 of the block
will become active automatically — no agent changes needed.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
When no in_progress todo is active, fill the middle slot of
gsd-statusline.js with GSD state read from .planning/STATE.md.
Format: <milestone> · <status> · <phase name> (N/total)
- Add readGsdState() — walks up from workspace dir looking for
.planning/STATE.md (bounded at 10 levels / home dir)
- Add parseStateMd() — reads YAML frontmatter (status, milestone,
milestone_name) and Phase line from body; falls back to body Status:
parsing for older STATE.md files without frontmatter
- Add formatGsdState() — joins available parts with ' · ', degrades
gracefully when fields are missing
- Wrap stdin handler in runStatusline() and export helpers so unit
tests can require the file without triggering the script behavior
Strictly additive: active todo wins the slot (unchanged); missing
STATE.md leaves the slot empty (unchanged). Only the "no active todo
AND STATE.md present" path is new.
Uses the YAML frontmatter added for #628, completing the statusline
display that issue originally proposed.
Closes#1989
* feat(review): add Qwen Code and Cursor CLI as peer reviewers (#1938, #1960)
Add qwen and cursor to the /gsd-review pipeline following the
established pattern from CodeRabbit and OpenCode integrations:
- CLI detection via command -v
- --qwen and --cursor flags
- Invocation blocks with empty-output fallback
- Install help URLs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(review): correct qwen/cursor invocations and add doc surfaces (#1966)
Address review feedback from trek-e, kturk, and lawsontaylor:
- Use positional form for qwen (qwen "prompt") — -p flag is deprecated
upstream and will be removed in a future version
- Fix cursor invocation to use cursor agent -p --mode ask --trust
instead of cursor --prompt which launches the editor GUI
- Add --qwen and --cursor flags to COMMANDS.md, FEATURES.md, help.md,
commands/gsd/review.md, and localized docs (ja-JP, ko-KR)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The deviation rules and task commit protocol were duplicated between
gsd-executor.md (agent definition) and execute-plan.md (workflow).
The copies had diverged: the agent had scope boundary and fix attempt
limits the workflow lacked; the workflow had 3 extra commit types
(perf, docs, style) the agent lacked.
Consolidate gsd-executor.md as the single source of truth:
- Add missing commit types (perf, docs, style) to gsd-executor.md
- Replace execute-plan.md's ~90 lines of duplicated content with
concise references to the agent definition
Saves ~1,600 tokens per workflow spawn and eliminates maintenance
drift between the two copies.
Closes#1968
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
`intel.enabled` is the documented opt-in for the intel subsystem
(see commands/gsd/intel.md and docs/CONFIGURATION.md), but it was
missing from VALID_CONFIG_KEYS in config.cjs, so the canonical
command failed:
$ gsd-tools config-set intel.enabled true
Error: Unknown config key: "intel.enabled"
Add the key to the whitelist, document it under a new "Intel Fields"
section in planning-config.md alongside the other namespaced fields,
and cover it with a config-set test.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(install): guard writeSettings against null settingsPath for cline runtime
Cline returns settingsPath: null from install() because it uses .clinerules
instead of settings.json. The finishInstall() guard was missing !isCline,
causing a crash with ERR_INVALID_ARG_TYPE when installing with the cline runtime.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* test(cline): add regression tests for ERR_INVALID_ARG_TYPE null settingsPath guard
Adds two regression tests to tests/cline-install.test.cjs for gsd-build/get-shit-done#2044:
- Assert install(false, 'cline') does not throw ERR_INVALID_ARG_TYPE
- Assert settings.json is not written for cline runtime
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* test(cline): fix regression tests to directly call finishInstall with null settingsPath
The previous regression tests called install() which returns early for cline
before reaching finishInstall(), so the crash was never exercised. Fix by:
- Exporting finishInstall from bin/install.js
- Calling finishInstall(null, null, ..., 'cline') directly so the null
settingsPath guard is actually tested
Tests now fail (ERR_INVALID_ARG_TYPE) without the fix and pass with it.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
* fix(autonomous): add Agent to allowed-tools in gsd-autonomous skill
Closes#2043
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(install): extend buildHookCommand to .sh hooks — absolute quoted paths
- Extend buildHookCommand() to branch on .sh suffix, using 'bash' runner
instead of 'node', so all hook paths go through the same quoted-path
construction: bash "/absolute/path/hooks/gsd-*.sh"
- Replace three manual 'bash ' + targetDir + '...' concatenations for
gsd-validate-commit.sh, gsd-session-state.sh, gsd-phase-boundary.sh
with buildHookCommand(targetDir, hookName) for the global-install branch
- Global .sh hook paths are now double-quoted, fixing invocation failure
when the config dir path contains spaces (Windows usernames, #2045)
- Adds regression tests in tests/sh-hook-paths.test.cjs
Closes#2045Closes#2046
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(workflow): offer recommendation instead of hard redirect when UI-SPEC.md missing
When plan-phase detects frontend indicators but no UI-SPEC.md, replace the
AskUserQuestion hard-exit block with an offer_next-style recommendation that
displays /gsd-ui-phase as the primary next step and /gsd-plan-phase --skip-ui
as the bypass option. Also registers --skip-ui as a parsed flag so it silently
bypasses the UI gate.
Closes#2011
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: retrigger CI — resolve stale macOS check
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
AskUserQuestion is a Claude Code-only tool. When running GSD on OpenAI Codex,
Gemini CLI, or other non-Claude runtimes, the model renders the tool call as a
markdown code block instead of executing it, so the interactive TUI never
appears and the session stalls without collecting user input.
The workflow.text_mode / --text flag mechanism already handles this in 5 of
the 37 affected workflows. This commit adds the same TEXT_MODE fallback
instruction to all remaining 32 workflows so that, when text_mode is enabled,
every AskUserQuestion call is replaced with a plain-text numbered list that
any runtime can handle.
Fixes#2012
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds ios-scaffold.md reference that explicitly prohibits Package.swift +
.executableTarget for iOS apps (produces macOS CLI, not iOS app bundle),
requires project.yml + xcodegen generate to create a proper .xcodeproj,
and documents SwiftUI API availability tiers (iOS 16 vs 17). Adds iOS
anti-patterns 28-29 to universal-anti-patterns.md and wires the reference
into gsd-executor.md so executors see the guidance during iOS plan execution.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(worktree): use reset --hard in worktree_branch_check to correctly set base (#2015)
The worktree_branch_check in execute-phase.md and quick.md used
git reset --soft as the fallback when EnterWorktree created a branch
from main/master instead of the current feature branch HEAD. --soft
moves the HEAD pointer but leaves working tree files from main unchanged,
so the executor worked against stale code and produced commits containing
the entire feature branch diff as deletions.
Fix: replace git reset --soft with git reset --hard in both workflow files.
--hard resets both the HEAD pointer and the working tree to the expected
base commit. It is safe in a fresh worktree that has no user changes.
Adds 4 regression tests (2 per workflow) verifying that the check uses
--hard and does not contain --soft.
* fix(worktree): executor deletion verification and pre-merge deletion block (#1977)
- Remove Windows-only qualifier from worktree_branch_check in execute-plan.md
(the EnterWorktree base-branch bug affects all platforms, not just Windows)
- Add post-commit --diff-filter=D deletion check to gsd-executor.md task_commit_protocol
so unexpected file deletions are flagged immediately after each task commit
- Add pre-merge --diff-filter=D deletion guard to execute-phase.md worktree cleanup
so worktree branches containing file deletions are blocked before fast-forward merge
- Add regression test tests/worktree-safety.test.cjs covering all three behaviors
Fixes#1977
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a mandatory Hunk Verification Table output to Step 4 (columns: file,
hunk_id, signature_line, line_count, verified) and a new Step 5 gate that
STOPs with an actionable error if any row shows verified: no or the table
is absent. Prevents the LLM from silently bypassing post-merge checks by
making the next step structurally dependent on the table's presence and
content. Adds four regression tests covering table presence, column
requirements, Step 5 reference, and the gate condition.
Fixes#1999
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
After complete_session in verify-work.md, when final_status==complete and
issues==0, the workflow now executes transition.md inline (mirroring the
execute-phase pattern) to mark the phase complete in ROADMAP.md and STATE.md.
Security gate still gates the transition: if enforcement is enabled and no
SECURITY.md exists, the workflow suggests /gsd-secure-phase instead.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The worktree_branch_check in execute-phase.md and quick.md used
git reset --soft as the fallback when EnterWorktree created a branch
from main/master instead of the current feature branch HEAD. --soft
moves the HEAD pointer but leaves working tree files from main unchanged,
so the executor worked against stale code and produced commits containing
the entire feature branch diff as deletions.
Fix: replace git reset --soft with git reset --hard in both workflow files.
--hard resets both the HEAD pointer and the working tree to the expected
base commit. It is safe in a fresh worktree that has no user changes.
Adds 4 regression tests (2 per workflow) verifying that the check uses
--hard and does not contain --soft.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
cmdPhaseAdd computed maxPhase from ROADMAP.md only, allowing orphan
directories on disk (untracked in roadmap) to silently collide with
newly added phases. The new phase's mkdirSync succeeded against the
existing directory, contaminating it with fresh content.
Fix: take max(roadmapMax, diskMax) where diskMax scans
.planning/phases/ and strips optional project_code prefix before
parsing the leading integer. Backlog orphans (>=999) are skipped.
Adds 3 regression tests covering:
- orphan dir with number higher than roadmap max
- prefixed orphan dirs (project_code-NN-slug)
- no collision when orphan number is lower than roadmap max
Fixes#2026
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Cline was documented as a supported runtime but was absent from
bin/install.js. This adds full Cline support:
- Registers --cline CLI flag and adds 'cline' to --all list
- Adds getDirName/getConfigDirFromHome/getGlobalDir entries (CLINE_CONFIG_DIR env var respected)
- Adds convertClaudeToCliineMarkdown() and convertClaudeAgentToClineAgent()
- Wires Cline into copyWithPathReplacement(), install(), writeManifest(), finishInstall()
- Local install writes to project root (like Claude Code), not .cline/ subdirectory
- Generates .clinerules at install root with GSD integration rules
- Installs get-shit-done engine and agents with path/brand replacement
- Adds Cline as option 4 in interactive menu (13-runtime menu, All = 14)
- Updates banner description to include Cline
- Exports convertClaudeToCliineMarkdown and convertClaudeAgentToClineAgent for testing
- Adds tests/cline-install.test.cjs with 17 regression tests
- Updates multi-runtime-select, copilot-install, kilo-install tests for new option numbers
Fixes#1991
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous implementation filtered ALL .planning/-only commits, including
milestone archive commits, STATE.md, ROADMAP.md, and PROJECT.md updates.
Merging the PR branch then left the target with inconsistent planning state.
Fixes by distinguishing two categories of .planning/ commits:
- Structural (STATE.md, ROADMAP.md, MILESTONES.md, PROJECT.md,
REQUIREMENTS.md, milestones/**): INCLUDED in PR branch
- Transient (phases/, quick/, research/, threads/, todos/, debug/,
seeds/, codebase/, ui-reviews/): EXCLUDED from PR branch
The git rm in create_pr_branch is now scoped to transient subdirectories
only, so structural files survive cherry-pick into the PR branch.
Adds regression test asserting structural file handling is documented.
Closes#2004
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
When a phase completes, the offer_next step now checks whether CONTEXT.md
already exists for the next phase before presenting options.
- If CONTEXT.md is absent: /gsd-discuss-phase is the recommended first step
- If CONTEXT.md exists: /gsd-plan-phase is the recommended first step
Adds regression test asserting conditional routing is present.
Closes#2002
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
replaceInCurrentMilestone() locates content by finding the last </details>
in the ROADMAP and only operates on text after that boundary. When the
current (in-progress) milestone section is itself wrapped in a <details>
block (the standard /gsd-new-project layout), the phase section's
**Plans:** counter lives INSIDE that block. The replacement target ends up
in the empty space after the block's closing </details>, so the regex never
matches and the plan count stays at 0/N permanently.
Fix: switch the plan count update to use direct .replace() on the full
roadmapContent, consistent with the checkbox and progress table updates
that already use this pattern. The phase-scoped heading regex
(### Phase N: ...) is specific enough to avoid matching archived phases.
Adds two regression tests covering: (1) plan count updates inside a
<details>-wrapped current milestone, and (2) phase 2 plan count is not
corrupted when completing phase 1.
W006 (Phase in ROADMAP.md but no directory on disk) fired for every phase
listed in ROADMAP.md that lacked a phase directory, including future phases
that haven't been started yet. This produced false DEGRADED health status
on any project with more than one phase planned.
Fix: before emitting W006, check the ROADMAP summary list for a
'- [ ] **Phase N:**' unchecked checkbox. Phases explicitly marked as not
yet started are intentionally absent from disk -- skip W006 for them.
Phases with a checked checkbox ([x]) or with no summary entry still
trigger W006 as before.
Adds two regression tests: one verifying W006 is suppressed for unchecked
phases, and one verifying W006 still fires for checked phases with no disk
directory.
When gsd-tools commit is invoked with --files and one of the listed files
does not exist on disk, the previous code called git rm --cached which
staged and committed a deletion. This silently removed tracked planning
files (STATE.md, ROADMAP.md) from the repository whenever they were
temporarily absent on disk.
Fix: when explicit --files are provided, skip files that do not exist
rather than staging their deletion. Only the default (.planning/ staging
path) retains the git rm --cached behavior so genuinely removed planning
files are not left dangling in the index.
Adds regression tests verifying that missing files in an explicit --files
list are never staged as deletions.
* fix(hooks): skip read-guard advisory on Claude Code runtime (#1984)
Claude Code natively enforces read-before-edit at the runtime level,
so the gsd-read-guard.js advisory is redundant — it wastes ~80 tokens
per Write/Edit call and clutters tool flow with system-reminder noise.
Add early exit when CLAUDE_SESSION_ID is set (standard Claude Code
session env var). Non-Claude runtimes (OpenCode, Gemini, etc.) that
lack native read-before-edit enforcement continue to receive the
advisory as before.
Closes#1984
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(hooks): sanitize runHook env to prevent test failures in Claude Code
The runHook() test helper now blanks CLAUDE_SESSION_ID so positive-path
tests pass even when the test suite runs inside a Claude Code session.
The new skip test passes the env var explicitly via envOverrides.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
cmdPhaseComplete used replaceInCurrentMilestone() to update the overview
bullet checkbox (- [ ] → - [x]), but that function scopes replacements
to content after the last </details> tag. The current milestone's
overview bullets appear before any <details> blocks, so the replacement
never matched.
Switch to direct .replace() which correctly finds and updates the first
matching unchecked checkbox. This is safe because unchecked checkboxes
([ ]) only exist in the current milestone — archived phases have [x].
Closes#1998
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The convertSlashCommandsToCodexSkillMentions function only converted
colon-style skill invocations (/gsd:command) but not hyphen-style
command references (/gsd-command) used in workflow output templates
(Next Up blocks, phase completion messages, etc.). This caused Codex
users to see /gsd- prefixed commands instead of $gsd- in chat output.
- Add regex to convert /gsd-command → $gsd-command with negative
lookbehind to exclude file paths (e.g. bin/gsd-tools.cjs)
- Strip /clear references in Codex output (no Codex equivalent)
- Add 5 regression tests covering command conversion, path
preservation, and /clear removal
Co-authored-by: Lakshman <lakshman@lakshman-GG9LQ90J61.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(config): route CRUD through planningDir to honor GSD_PROJECT
PR #1484 added planningDir(cwd) and the GSD_PROJECT env var so a workspace
can host multiple projects under .planning/{project}/. loadConfig() in
core.cjs (line 256) was migrated at the time, but the four CRUD entry points
in config.cjs and the planningPaths() helper in core.cjs were left resolving
against planningRoot(cwd).
The result was a silent split-brain in any multi-project workspace:
- cmdConfigGet, setConfigValue, ensureConfigFile, cmdConfigNewProject
all wrote to and read from .planning/config.json
- loadConfig read from .planning/{GSD_PROJECT}/config.json
So `gsd-tools config-get workflow.discuss_mode` returned "unset" even when
the value was correctly stored in the project-routed file, because the
reader and writer pointed at different paths.
planningPaths() carried a comment that "Shared paths (project, config)
always resolve to the root .planning/" which described the original intent,
but loadConfig() already contradicted that intent for config.json. project
and config now both resolve through planningDir() so the contract matches
the only function that successfully read config.json in the multi-project
case.
Single-project users (no GSD_PROJECT set) are unaffected: planningRoot()
and planningDir() return the same path when no project is configured.
Verification: in a workspace with .planning/projectA/config.json and
GSD_PROJECT=projectA, `gsd-tools config-get workflow.discuss_mode` now
returns the value instead of "Error: Key not found". Backward compat
verified by running the same command without GSD_PROJECT in a
single-project layout.
Affected sites:
- get-shit-done/bin/lib/config.cjs cmdConfigNewProject (line 199)
- get-shit-done/bin/lib/config.cjs ensureConfigFile (line 244)
- get-shit-done/bin/lib/config.cjs setConfigValue (line 294)
- get-shit-done/bin/lib/config.cjs cmdConfigGet (line 367)
- get-shit-done/bin/lib/core.cjs planningPaths.config (line 706)
- get-shit-done/bin/lib/core.cjs planningPaths.project (line 705)
* fix(template): emit project-aware references in template fill plan
The template fill plan body hardcoded `@.planning/PROJECT.md`,
`@.planning/ROADMAP.md`, and `@.planning/STATE.md` references. In a
multi-project workspace these resolve to nothing because the actual
project, roadmap, and state files live under .planning/{GSD_PROJECT}/.
`gsd-tools verify references` reports them as missing on every PLAN.md
generated by template fill in any GSD_PROJECT-routed workspace.
Fix: route the references through planningDir(cwd), normalize via the
existing toPosixPath helper for cross-platform path consistency, and
embed them as `@<relative-path>` matching the phase-relative reference
pattern used elsewhere in the file.
Single-project users (no GSD_PROJECT set) get exactly the same output
as before because planningDir() falls back to .planning/ when no project
is active.
Affected site: get-shit-done/bin/lib/template.cjs cmdTemplateFill plan
branch (lines 142-145, the @.planning/ refs in the Context section).
* fix(verify): planningDir for cmdValidateHealth and regenerateState
cmdValidateHealth resolved projectPath and configPath via planningRoot(cwd)
while ROADMAP/STATE/phases/requirements went through planningDir(cwd). The
inconsistency reported "missing PROJECT.md" and "missing config.json" in
multi-project layouts even when the project-routed copies existed and the
config CRUD writers (now also routed by the previous commit in this PR)
were writing to them.
regenerateState (the /gsd:health --repair STATE.md regeneration path)
hardcoded `See: .planning/PROJECT.md` in the generated body, which fails
the same reference check it just regenerated for in any GSD_PROJECT-routed
workspace.
Fix: route both sites through planningDir(cwd). For regenerateState, derive
a POSIX-style relative reference from the resolved path so the reference
matches verify references' resolution rules. Also dropped the planningRoot
import from verify.cjs since it is no longer used after this change.
Single-project users (no GSD_PROJECT set) get the same paths as before:
planningDir() falls back to .planning/ when no project is configured.
Affected sites:
- get-shit-done/bin/lib/verify.cjs cmdValidateHealth (lines 536-541)
- get-shit-done/bin/lib/verify.cjs regenerateState repair (line 865)
- get-shit-done/bin/lib/verify.cjs core.cjs import (line 8, dropped unused
planningRoot)
* fix(worktree): use hard reset to correct file tree when branch base is wrong (#1981)
The worktree_branch_check mitigation detects when EnterWorktree creates
branches from main instead of the current feature branch, but used
git reset --soft to correct it. This only fixed the commit pointer —
the working tree still contained main's files, causing silent data loss
on merge-back when the agent's commits overwrote feature branch code.
Changed to git reset --hard which safely corrects both pointer and file
tree (the check runs before any agent work, so no changes to lose).
Also removed the broken rebase --onto attempt in execute-phase.md that
could replay main's commits onto the feature branch, and added post-reset
verification that aborts if the correction fails.
Updated documentation from "Windows" to "all platforms" since the
upstream EnterWorktree bug affects macOS, Linux, and Windows alike.
Closes#1981
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(worktree): update settings.md worktree description to say cross-platform
Aligns with the workflow file updates — the EnterWorktree base-branch
bug affects all platforms, not just Windows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Update actions/checkout from v4.2.2 to v6.0.2 in release.yml and
hotfix.yml (prevents breakage after June 2026 Node.js 20 deprecation)
- Update actions/setup-node from v4.1.0 to v6.3.0 in both workflows
- Add release/** and hotfix/** to test.yml push triggers
- Add release/** and hotfix/** to security-scan.yml PR triggers
test.yml already used v6 pins — this aligns the release pipelines.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(core): preserve letter suffix case in normalizePhaseName (#1962)
normalizePhaseName uppercased letter suffixes (e.g., "16c" → "16C"),
causing directory/roadmap mismatches on case-sensitive filesystems.
init progress couldn't match directory "16C-name" to roadmap "16c".
Preserve original case — comparePhaseNum still uppercases for sorting
(correct), but normalizePhaseName is used for display and directory
creation where case must match the roadmap.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(phase): update existing test to expect preserved letter case
The 'uppercases letters' test asserted the old behavior (3a → 03A).
With normalizePhaseName now preserving case, update expectations to
match (3a → 03a) and rename the test to 'preserves letter case'.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The existing MANDATORY acceptance_criteria instruction is purely advisory —
executor agents read it and silently skip criteria when they run low on
context or hit complexity. This causes planned work to be dropped without
any signal to the orchestrator or verifier.
Changes:
- Replace advisory text with a structured 5-step verification loop
- Each criterion must be proven via grep/file-check/CLI command
- Agent is BLOCKED from next task until all criteria pass
- Failed criteria after 2 fix attempts logged as deviation (not silent skip)
- Self-check step now re-runs ALL acceptance criteria before SUMMARY
- Self-check also re-runs plan-level verification commands
Closes#1958
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(review): add per-CLI model selection via config
- Add review.models.<cli> dynamic config keys to VALID_CONFIG_KEYS
- Update review.md to read model preferences via config-get at runtime
- Null/missing values fall back to CLI defaults (backward compatible)
- Add key suggestion for common typo (review.model)
- Update planning-config reference doc
Closes#1849
* fix(review): handle absent and null model config gracefully
Address PR #1859 review feedback from @trek-e:
1. Add `|| true` to all four config-get subshell invocations in
review.md so that an absent review.models.<cli> key does not
produce a non-zero exit from the subshell. cmdConfigGet calls
error() (process.exit(1)) when the key path is missing; the
2>/dev/null suppresses the message but the exit code was still
discarded silently. The || true makes the fall-through explicit
and survives future set -e adoption.
2. Add `&& [ "$VAR" != "null" ]` to all four if guards. cmdConfigSet
does not parse the literal 'null' as JSON null — it stores the
string 'null' — and cmdConfigGet --raw returns the literal text
'null' for that value. Without the extra guard the workflow would
pass `-m "null"` to the CLI, which crashes. The issue spec
documents null as the "fall back to CLI default" sentinel, so this
restores the contract.
3. Add tests/review-model-config.test.cjs covering all five cases
trek-e listed:
- isValidConfigKey accepts review.models.gemini (via config-set)
- isValidConfigKey accepts review.models.codex (via config-set)
- review.model is rejected and suggests review.models.<cli-name>
- config-set then config-get round-trip with a model ID
- config-set then config-get round-trip with null (returns "null")
Tests follow the node:test + node:assert/strict pattern from
tests/agent-skills.test.cjs and use runGsdTools from helpers.cjs.
Closes#1849
* feat: harness engineering improvements — post-merge test gate, shared file isolation, behavioral verification
Three improvements inspired by Anthropic's harness engineering research
(March 2026) and real-world pain points from parallel worktree execution:
1. Post-merge test gate (execute-phase.md)
- Run project test suite after merging each wave's worktrees
- Catches cross-plan integration failures that individual Self-Checks miss
- Addresses the Generator self-evaluation blind spot (agents praise own work)
2. Shared file isolation (execute-phase.md)
- Executors no longer modify STATE.md or ROADMAP.md in parallel mode
- Orchestrator updates tracking files centrally after merge
- Eliminates the #1 source of merge conflicts in parallel execution
3. Behavioral verification (verify-phase.md)
- Verifier runs project test suite and CLI commands, not just grep
- Follows Anthropic's Generator/Evaluator separation principle
- Tests actual behavior against success criteria, not just file existence
Real-world evidence: In a session executing 37 plans across 8 phases with
parallel worktrees, we observed:
- 4 test failures after merge that all Self-Checks missed (models.py type loss)
- STATE.md/ROADMAP.md conflicts on every single parallel merge
- Verifier reporting PASSED while merged code had broken imports
References:
- Anthropic Engineering Blog: Harness Design for Long-Running Apps (2026-03-24)
- Issue #1451: Massive git worktree problem
- Issue #1413: Autonomous execution without manual context clearing
* fix: address review feedback — test runner detection, parallel isolation, edge cases
- Replace hardcoded jest/vitest with `npm test` (reads project's scripts.test)
- Add Go detection to post-merge test gate (was only in verify-phase)
- Add 5-minute timeout to post-merge test gate to prevent pipeline stalls
- Track cumulative wave failures via WAVE_FAILURE_COUNT for cross-wave awareness
- Guard orchestrator tracking commit against unchanged files (prevent empty commits)
- Align execute-plan.md with parallel isolation model (skip STATE.md/ROADMAP.md
updates when running in parallel mode, orchestrator handles centrally)
- Scope behavioral verification CLI checks: skip when no fixtures/test data exist,
mark as NEEDS HUMAN instead of inventing inputs
* fix: pass PARALLEL_MODE to executor agents to activate shared file isolation
The executor spawn prompt in execute-phase.md instructed agents not to
modify STATE.md/ROADMAP.md, but execute-plan.md gates this behavior on
PARALLEL_MODE which was never defined in the executor context. This adds
the variable to the spawn prompt and wraps all three shared-file steps
(update_current_position, update_roadmap, git_commit_metadata) with
explicit conditional guards.
* fix: replace unreliable PARALLEL_MODE env var with git worktree auto-detection
Address PR #1486 review feedback (trek-e):
1. PARALLEL_MODE was never reliably set — the <env> block instructed the LLM
to export a bash variable, but each Bash tool call runs in a fresh shell
so the variable never persisted. Replace with self-contained worktree
detection: `[ -f .git ]` returns true in worktrees (.git is a file) and
false in main repos (.git is a directory). Each bash block detects
independently with no external state dependency.
2. TEST_EXIT only checked for timeout (124) — test failures (non-zero,
non-124) were silently ignored, making the "If tests fail" prose
unreachable. Add full if/elif/else handling: 0=pass, 124=timeout,
else=fail with WAVE_FAILURE_COUNT increment.
3. Add Go detection to regression_gate (was missing go.mod check).
Replace hardcoded npx jest/vitest with npm test for consistency.
4. Renumber steps from 4/4b/4c/5/5/5b to 4a/4b/4c/4d/5/6/7/8/9.
* fix: address remaining review blockers — timeout, tracking guard, shell safety
- verify-phase.md: wrap behavioral_verification test suite in timeout 300
- execute-phase.md: gate tracking update on TEST_EXIT=0, skip on failure/timeout
- Quote all TEST_EXIT variables, add default initialization
- Add else branch for unrecognized project types
- Renumber steps to align with upstream (5.x series)
* fix: rephrase worktree success_criteria to satisfy substring test guard
The worktree mode success_criteria line literally contained "STATE.md"
and "ROADMAP.md" inside a prohibition ("No modifications to..."), but
the test guard in execute-phase-worktree-artifacts.test.cjs uses a
substring check and cannot distinguish prohibition from requirement.
Rephrase to "shared orchestrator artifacts" so the substring check
passes while preserving the same intent.
The Next Up block always suggested /gsd-plan-phase, but plan-phase
redirects to discuss-phase when CONTEXT.md doesn't exist. This caused
a confusing two-step redirect ~90% of the time since ui-phase doesn't
create CONTEXT.md.
Conditionally suggest discuss-phase or plan-phase based on CONTEXT.md
existence, matching the logic in progress.md Route B.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Workflows used bash-specific `if [[ "$INIT" == @file:* ]]` to detect
when large JSON was written to a temp file. This syntax breaks on
PowerShell and other non-bash shells.
Intercept stdout in gsd-tools.cjs to transparently resolve @file:
references before they reach the caller, matching the existing --pick
path behavior. The bash checks in workflow files become harmless
no-ops and can be removed over time.
Co-authored-by: Tibsfox <tibsfox@tibsfox.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backlog phases use 999.x numbering and should not be counted when
calculating the next sequential phase ID. Without this fix, having
backlog phases causes the next phase to be numbered 1000+.
Co-authored-by: gg <grgbrasil@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cmdStateUpdateProgress, cmdStateAddDecision, cmdStateAddBlocker,
cmdStateResolveBlocker, cmdStateRecordSession, and cmdStateBeginPhase from
bare readFileSync+writeStateMd to readModifyWriteStateMd, eliminating the
TOCTOU window where two concurrent callers read the same content and the
second write clobbers the first.
Atomics.wait(), matching the pattern already used in withPlanningLock in
core.cjs.
and core.cjs and register a process.on('exit') handler to unlink them on
process exit. The exit event fires even when process.exit(1) is called
inside a locked region, eliminating stale lock files after errors.
read-modify-write body of setConfigValue in a planning lock, preventing
concurrent config-set calls from losing each other's writes.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(core): resolve @file: references in gsd-tools stdout (#1891)
Workflows used bash-specific `if [[ "$INIT" == @file:* ]]` to detect
when large JSON was written to a temp file. This syntax breaks on
PowerShell and other non-bash shells.
Intercept stdout in gsd-tools.cjs to transparently resolve @file:
references before they reach the caller, matching the existing --pick
path behavior. The bash checks in workflow files become harmless
no-ops and can be removed over time.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* docs(config): add missing config fields to planning-config.md (#1880)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Tibsfox <tibsfox@tibsfox.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Running gsd-update (re-running the installer) silently deleted two
user-generated files:
- get-shit-done/USER-PROFILE.md (created by /gsd-profile-user)
- commands/gsd/dev-preferences.md (created by /gsd-profile-user)
Root causes:
1. copyWithPathReplacement() calls fs.rmSync(destDir, {recursive:true})
before copying, wiping USER-PROFILE.md with no preserve allowlist.
2. The legacy commands/gsd/ cleanup at ~line 5211 rmSync'd the entire
directory, wiping dev-preferences.md.
3. The backup path in profile-user.md pointed to the same directory
that gets wiped, so the backup was also lost.
Fix:
- Add preserveUserArtifacts(destDir, fileNames) and restoreUserArtifacts()
helpers that save/restore listed files around destructive wipes.
- Call them in install() before the get-shit-done/ copy (preserves
USER-PROFILE.md) and before the legacy commands/gsd/ cleanup
(preserves dev-preferences.md).
- Fix profile-user.md backup path from ~/.claude/get-shit-done/USER-PROFILE.backup.md
to ~/.claude/USER-PROFILE.backup.md (outside the wiped directory).
Closes#1924
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces direct fs.writeFileSync calls for STATE.md, ROADMAP.md, and
config.json with write-to-temp-then-rename so a process killed mid-write
cannot leave an unparseable truncated file. Falls back to direct write if
rename fails (e.g. cross-device). Adds regression tests for the helper.
Closes#1915
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reorder reorganize_roadmap_and_delete_originals to commit archive files
as a safety checkpoint BEFORE removing any originals (fixes#1913)
- Use overwrite-in-place for ROADMAP.md instead of delete-then-recreate
- Use git rm for REQUIREMENTS.md to stage deletion atomically with history
- Add 3-step Backlog preservation protocol: extract before rewrite, re-append
after, skip silently if absent (fixes#1914)
- Update success_criteria and archival_behavior to reflect new ordering