Integrate lightweight thinking partner analysis at two workflow decision
points, controlled by features.thinking_partner config (default: false):
1. discuss-phase: when developer answers reveal competing priorities
(detected via keyword/structural signals), offers brief tradeoff
analysis before locking decisions
2. plan-phase: when plan-checker flags architectural tradeoffs, analyzes
options and recommends an approach aligned with phase goals before
entering the revision loop
The thinking partner is opt-in, skippable (No, I have decided),
and brief (3-5 bullets). A third integration point for /gsd-explore
will be added when #1729 lands.
Closes#1726
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(config): apply ~/.gsd/defaults.json as fallback for pre-project commands (#1683)
When .planning/config.json is missing (e.g., running GSD commands outside
a project), loadConfig() now checks ~/.gsd/defaults.json before returning
hardcoded defaults. This lets users set preferred model_profile,
context_window, subagent_timeout, and other settings globally.
Only whitelisted keys are merged — unknown keys in defaults.json are
silently ignored. If defaults.json is missing or contains invalid JSON,
the hardcoded defaults are returned as before.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(config): scope defaults.json fallback to pre-project context only
Only consult ~/.gsd/defaults.json when .planning/ does not exist (truly
pre-project). When .planning/ exists but config.json is missing, return
hardcoded defaults — avoids interference with tests and initialized
projects. Use GSD_HOME env var for test isolation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes#1707
Extracts config defaults from loadConfig() into an exported
CONFIG_DEFAULTS constant in core.cjs. config.cjs and verify.cjs
now reference CONFIG_DEFAULTS instead of duplicating values,
preventing future divergence.
Fixes#1692
spawnSync('sleep', ['0.1']) fails silently on Windows (ENOENT),
causing a tight busy-loop during lock contention. Atomics.wait()
provides a cross-platform 100ms blocking wait available in Node 22+.
* feat(i18n): add response_language config for cross-phase language consistency
Adds `response_language` config key that propagates through all init
outputs via withProjectRoot(). Workflows read this field and instruct
agents to present user-facing questions in the configured language,
solving the problem of language preference resetting at phase boundaries.
Usage: gsd-tools config-set response_language "Portuguese"
Closes#1399
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(security): allowlist discuss-phase.md for size threshold
discuss-phase.md legitimately exceeds 50K chars due to power mode
and i18n directives — not prompt stuffing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(phase-resolution): use exact token matching instead of prefix matches
Closes#1635
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(phase-resolution): add case-insensitive flag to project-code strip regex
The strip regex in phaseTokenMatches lacked the `i` flag, so lowercase
project-code prefixes (e.g. `ck-01-name`) were not stripped during the
fallback comparison. This made `phaseTokenMatches('ck-01-name', '01')`
return false when it should return true.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix(config): warn on unrecognized keys in config.json instead of silent drop (#1535)
loadConfig() silently ignores any config.json keys not in its known
set, leaving users confused when their settings have no effect. Add a
stderr warning listing unrecognized top-level keys so the problem
surfaces immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(config): derive known keys from VALID_CONFIG_KEYS instead of hardcoded set
Address review feedback: replace hardcoded KNOWN_CONFIG_KEYS with
programmatic derivation from config-set's VALID_CONFIG_KEYS (single
source of truth). New config keys added to config-set are automatically
recognized by loadConfig without a separate update. Add sync test
verifying all VALID_CONFIG_KEYS entries pass without warning.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On Windows, path.resolve returns whatever case the caller supplied while
fs.realpathSync.native returns the OS-canonical case. These produce
different SHA-1 hashes and therefore different session tmpdir slots —
the test checks one slot while the implementation writes to another,
causing pointer lifecycle assertions to always fail.
Fix: use realpathSync.native with a fallback to path.resolve when the
planning directory does not yet exist.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot installs agent files as gsd-*.agent.md (not gsd-*.md), so
checkAgentsInstalled() always returned agents_installed=false for Copilot.
- checkAgentsInstalled() now recognises both .md and .agent.md formats
- getAgentsDir() respects GSD_AGENTS_DIR env override for testability
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Explicitly check that the directory is empty before removing it rather
than relying on rmdirSync throwing ENOTEMPTY when siblings remain.
On Windows that error is not raised reliably, causing the session tmp
directory to be deleted prematurely when sibling pointer files exist.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds `manager.flags.{discuss,plan,execute}` config keys so users can
configure flags that /gsd:manager passes to each step when dispatching.
For example, `manager.flags.discuss: "--auto --analyze"` makes every
discuss dispatched from the manager include those flags.
Closes#1400
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire withPlanningLock into all ROADMAP.md write paths (phase add/insert/complete/remove, roadmap update-plan-progress) to prevent concurrent corruption when parallel agents modify planning files.
Extract acquireStateLock/releaseStateLock from writeStateMd and add readModifyWriteStateMd helper that holds the lock across the entire read-modify-write cycle, preventing lost updates.
Replace O(n^2) normalizeMd fence detection with single-pass O(n) pre-computed fence state array.
Warn on must_haves parse failure and stateReplaceFieldWithFallback field miss.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reject project/workstream names containing path separators or ..
components. Covers both GSD_PROJECT and GSD_WORKSTREAM. Adds 9 tests
for the full resolution matrix and traversal rejection cases.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The map-codebase workflow had a hardcoded 300000ms (5 minute) timeout
for parallel subagent tasks. On large codebases or with slower models
(e.g. GPT via Codex), subagents can need 10-20+ minutes, causing the
parent to kill still-working agents and fall back to sequential mode.
Changes:
- Add workflow.subagent_timeout config key (default: 300000ms)
- Register in VALID_CONFIG_KEYS (config.cjs)
- Add to loadConfig() defaults and return object (core.cjs)
- Emit in map-codebase init context (init.cjs)
- Update map-codebase.md to use config value instead of hardcoded 300000
- Document in planning-config.md reference
Users can now increase the timeout via:
/gsd:settings workflow.subagent_timeout 900000
Closes#1472
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds project-scoped planning directory resolution via GSD_PROJECT
environment variable. When set, planningDir() routes to
.planning/{project}/ instead of .planning/, enabling multiple
independent projects to coexist under a single .planning/ root.
Use case: shared workspaces (e.g., Obsidian vaults, monorepo knowledge
bases) where multiple projects are managed from one directory. Each
project keeps its own config.json, ROADMAP.md, STATE.md, and phases/
under .planning/{project-name}/.
GSD_PROJECT follows the same pattern as GSD_WORKSTREAM and can be
combined with it: .planning/{project}/workstreams/{ws}/
Also updates loadConfig() to read config.json from the project-scoped
directory when GSD_PROJECT is active.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When project_code is set (e.g., "CK"), phase directories are prefixed
with the code: CK-01-foundation, CK-02-api, etc. This disambiguates
phases across multiple GSD projects in the same session.
Changes:
- Add project_code to VALID_CONFIG_KEYS and buildNewProjectConfig defaults
- Add project_code to loadConfig in core.cjs
- Prepend prefix in cmdPhaseAdd and cmdPhaseInsert
- Update searchPhaseInDir, cmdFindPhase, comparePhaseNum, and
normalizePhaseName to strip prefix before matching/sorting
- Support {project} placeholder in git.phase_branch_template
- Add 4 tests covering prefixed add, null code, find, and sort
Closes#1019
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
add-backlog and thread commands called generate-slug without --raw,
capturing JSON output (with newlines) as the directory name. Also
cap slugs at 60 chars to prevent absurdly long directory names.
Fixesgsd-build/get-shit-done#1391
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When GSD agents are not installed in .claude/agents/, Task(subagent_type="gsd-*")
silently falls back to general-purpose, losing specialized instructions, structured
outputs, and verification protocols.
What changed:
- Added checkAgentsInstalled() to core.cjs that validates all expected agents exist on disk
- All init commands now include agents_installed and missing_agents in their output
- Health check (validate health) reports W010 when agents are missing or incomplete
- New validate agents subcommand for standalone agent installation diagnostics
Fixes#1371
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add agent_skills config section that maps agent types to skill directory
paths. At spawn time, workflows load configured skills and inject them
as <agent_skills> blocks in Task() prompts, giving subagents access to
project-specific skill files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When cwd is a git repo inside a GSD workspace, findProjectRoot() walked
up and returned the workspace parent (which also has .planning/) instead
of the cwd itself. This caused all init commands to resolve project_root
to the workspace root, making phase/roadmap lookups fail with "Phase not
found" errors.
The fix adds an early return: if startDir already contains a .planning/
directory, it is the project root — no need to walk up to a parent.
Fixes#1362
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Address 4 root causes of Windows + Claude Code reliability issues:
1. Workflow shell robustness: add || true guards to informational commands
(ls, grep, find, cat) that return non-zero on "no results", preventing
workflow step failures under strict execution models. Guard glob loops
with [ -e "$var" ] || continue to handle empty glob expansion.
2. Hook stdin handling: replace readFileSync('/dev/stdin') with async
process.stdin + timeout in agent templates (gsd-verifier.md). Existing
JS hooks already have timeout guards.
3. project_root detection: fix isInsideGitRepo() to check .git at the
candidate parent level (not just below it), enabling correct detection
when .git and .planning/ are siblings at the same directory level —
the common single-repo case from a subdirectory.
4. @file: handoff: add missing @file: handlers to autonomous.md and
manager.md workflows that call gsd-tools init but lacked the handler
for large output payloads.
Fixes#1343
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
resolveWorktreeRoot() unconditionally resolved linked worktrees to the main
repo root. When a linked worktree has its own independent .planning/ directory
(e.g., Conductor workspaces), all GSD commands read/wrote the wrong planning
state. Add an early return that checks for a local .planning/ before falling
through to main repo resolution.
The caller in gsd-tools.cjs already had this guard (added in #1283), but the
function itself should be correct regardless of call site. This is defense-in-
depth for any future callers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three non-blocking findings from the adversarial re-review of the
workstream namespacing PR, addressed as a follow-up:
1. setActiveWorkstream now validates names with the same regex used
at CLI entry and cmdWorkstreamSet — defense-in-depth so future
callers can't poison the active-workstream file
2. Replaced tautological test assertion (result.success || !result.success
was always true) with actual validation that cmdWorkstreamSet returns
invalid_name error for path traversal attempts. Added 8 new tests
for setActiveWorkstream's own validation.
3. Updated stale comment in copilot-install.test.cjs (said 31, actual 56)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- core.cjs: add filterPlanFiles, filterSummaryFiles, getPhaseFileStats,
readSubdirectories helpers; use them in searchPhaseInDir and getArchivedPhaseDirs
- gsd-tools.cjs: add parseNamedArgs() helper; replace ~50 repetitive indexOf/ternary
patterns in state record-metric, add-decision, add-blocker, record-session,
begin-phase, signal-waiting, template fill, and frontmatter subcommands
- phase.cjs: decompose 250-line cmdPhaseRemove into renameDecimalPhases(),
renameIntegerPhases(), and updateRoadmapAfterPhaseRemoval(); import readSubdirectories
- workstream.cjs: import stateExtractField from state.cjs and shared helpers from
core.cjs; replace all inline regex state parsing and readdirSync+filter+map patterns
All 1062 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Validates workstream name at all entry points — CLI --ws flag,
GSD_WORKSTREAM env var, active-workstream file, and cmdWorkstreamSet —
blocking names that don't match [a-zA-Z0-9_-]+. Also fixes
getActiveWorkstream to use planningRoot() consistently and validates
names read from the active-workstream file before using them in path
joins.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Enable multiple Claude Code instances to work on the same codebase
simultaneously by scoping .planning/ state into workstreams.
Core changes:
- planningDir(cwd, ws?) and planningPaths(cwd, ws?) are now workstream-aware
via GSD_WORKSTREAM env var (auto-detected from --ws flag or active-workstream file)
- All bin/lib modules use planningDir(cwd) for scoped paths (STATE.md, ROADMAP.md,
phases/, REQUIREMENTS.md) and planningRoot(cwd) for shared paths (milestones/,
PROJECT.md, config.json, codebase/)
- New workstream.cjs module: create, list, status, complete, set, get, progress
- gsd-tools.cjs: --ws flag parsing with priority chain
(--ws > GSD_WORKSTREAM env > active-workstream file > flat mode)
- Collision detection: transition.md checks for other active workstreams before
suggesting next-milestone continuation (prevents WS A from trampling WS B)
- ${GSD_WS} routing propagation across all 9 workflow files ensures workstream
scope chains automatically through the workflow lifecycle
New files:
- get-shit-done/bin/lib/workstream.cjs (CRUD + collision detection)
- get-shit-done/commands/gsd/workstreams.md (slash command)
- get-shit-done/references/workstream-flag.md (documentation)
- tests/workstream.test.cjs (20 tests covering CRUD, env var routing, --ws flag)
All 1062 tests passing (1042 existing + 20 new workstream tests).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend resolve_model_ids to accept "omit" value: returns empty string
so non-Claude runtimes (OpenCode, Codex, Gemini, etc.) use their
configured default model instead of unresolvable Claude aliases.
- resolve_model_ids: "omit" short-circuits before alias resolution
- model_overrides still respected (checked first) for explicit IDs
- Installer sets resolve_model_ids: "omit" in ~/.gsd/defaults.json
for non-Claude runtimes during install
- 4 new tests covering omit behavior and override passthrough
- Fix websearch test mocks for fs.writeSync output change (#1276)
Closes#1156
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
process.stdout.write() is async when stdout is a pipe. The
immediate process.exit(0) tears down the process before the
downstream reader (jq, python3, etc) consumes the buffer,
producing truncated JSON.
Replace with fs.writeSync(1, data) which blocks until the
kernel pipe buffer accepts the bytes, and drop process.exit(0)
on the success path so the event loop drains naturally.
Fixes#1275
Related: #493 (addressed >50KB case, this fixes <50KB)
Wire the --reviews flag through the full stack so plan-phase can
replan incorporating cross-AI review feedback from REVIEWS.md:
- core.cjs: add has_reviews detection in searchPhaseInDir
- init.cjs: wire has_reviews and reviews_path through all init functions
- plan-phase.md command: add --reviews to argument-hint and flags
- plan-phase.md workflow: add step 2.5 validation, skip research,
skip existing plans prompt, pass reviews_path to planner
- gsd-planner.md: add reviews_mode section for consuming review feedback
- COMMANDS.md: add --reviews and missing flags to docs
Closes the gap where --reviews was referenced in 6 places (review
workflow, review command, help workflow, COMMANDS.md, FEATURES.md)
but never implemented.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
core.cjs output() created gsd-*.json files in /tmp for large payloads
but never cleaned them up, causing unbounded disk usage (800+ GB
reported in #1251). Similarly, profile-pipeline.cjs created
gsd-pipeline-* and gsd-profile-* temp directories without cleanup.
Adds reapStaleTempFiles() that removes gsd-prefixed temp files/dirs
older than 5 minutes. Called opportunistically before each new temp
file/dir creation. Non-critical — cleanup failures never break output.
Closes#1251
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
loadConfig() defaulted commit_docs to true regardless of whether
.planning/ was gitignored. The documented auto-detection only existed
inside cmdCommit, so init commands returned commit_docs: true even
when .planning/ was in .gitignore. This caused LLM executors to
bypass the cmdCommit gate and re-commit planning files with raw git.
Now loadConfig() checks isGitIgnored(cwd, '.planning/') when no
explicit commit_docs value is set in config.json. If .planning/ is
gitignored, commit_docs defaults to false. An explicit commit_docs
value in config.json is always respected.
Added 5 regression tests covering auto-detection, explicit overrides,
and the no-config-file edge case.
Integrate Exa (semantic search) and Firecrawl (deep web scraping) as
MCP-based research tools, following the existing Brave Search pattern.
- Add tool declarations to all 3 researcher agents
- Add tool strategy sections with usage guidance and priority
- Add config detection for FIRECRAWL_API_KEY and EXA_API_KEY env vars
- Add firecrawl/exa_search config keys to core defaults and init output
- Update source priority hierarchy across all researchers
- findProjectRoot: use isInsideGitRepo() to walk up and find .git in
ancestor dirs, fixing nested paths like backend/src/modules/
- Add sub_repos to cmdInitExecutePhase output so execute-plan.md and
gsd-executor.md can route commits correctly
- Align new-project.md sub-repo detection to maxdepth 1 matching
detectSubRepos() behavior
- Add 3 nested path tests for .git heuristic, sub_repos, and multiRepo
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add support for workspaces with multiple independent git repositories.
When configured, GSD routes commits to the correct sub-repo and ensures
.planning/ stays at the project root.
Core features:
- detectSubRepos(): scans child directories for .git to discover repos
- findProjectRoot(): walks up from CWD to find the project root that
owns .planning/, preventing orphaned .planning/ in sub-repos
- loadConfig auto-syncs sub_repos when repos are added or removed
- Migrates legacy "multiRepo: true" to sub_repos array automatically
- All init commands include project_root in output
- cmdCommitToSubrepo: groups files by sub-repo prefix, commits independently
Zero impact on single-repo workflows — sub_repos defaults to empty array.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refactoring:
- Extract stateReplaceFieldWithFallback() to state.cjs as single source of truth
for the try-primary-then-fallback pattern that was duplicated inline across
phase.cjs, milestone.cjs, and state.cjs
- Replace all inline bold-only regex patterns in cmdPhaseComplete with shared
stateReplaceField/stateExtractField helpers — now supports both **Bold:**
and plain Field: STATE.md formats (fixes the same bug as #924)
- Replace inline bold-only regex patterns in cmdMilestoneComplete with shared helpers
- Replace inline bold-only regex for Completed Phases/Total Phases/Progress
counters in cmdPhaseComplete with stateExtractField/stateReplaceField
- Replace inline bold-only Total Phases regex in cmdPhaseRemove with shared helpers
Security:
- Fix command injection surface in isGitIgnored (core.cjs): replace execSync with
string concatenation with execFileSync using array arguments — prevents shell
interpretation of special characters in file paths
Tests (7 new):
- 5 tests for stateReplaceFieldWithFallback: primary field, fallback, neither,
preference, and plain format
- 1 regression test: phase complete with plain-format STATE.md fields
- 1 regression test: milestone complete with plain-format STATE.md fields
854 tests pass (was 847). No behavioral regressions.
- Add resolveWorktreeRoot() that detects linked worktrees via
git rev-parse --git-common-dir and resolves to the main worktree
where .planning/ lives
- Add withPlanningLock() file-based locking mechanism to prevent
concurrent worktrees from corrupting shared planning files
- Wire worktree root resolution into gsd-tools.cjs main entry point
- Add regression tests for resolveWorktreeRoot (non-git, normal repo)
and withPlanningLock (normal execution, error cleanup, stale lock
recovery)
The summary template puts the one-liner as a `**bold**` line after the
`# Phase N` heading, but `cmdSummaryExtract` and `cmdMilestoneComplete`
only checked frontmatter `one-liner` field — which is often empty.
Adds `extractOneLinerFromBody()` to core.cjs as a fallback that parses
the first `**...**` line after the heading.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add workflow.text_mode config option (default: false) that replaces
AskUserQuestion TUI menus with plain-text numbered lists
- Document --text flag and config-set workflow.text_mode true as the
fix for /rc remote sessions where the Claude App cannot forward TUI
menu selections
- Update discuss-phase.md with text mode parsing and answer_validation
fallback documentation
- Add text_mode to loadConfig defaults and VALID_CONFIG_KEYS
- Add regression tests for config-set and loadConfig