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>
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>
The requirement marking function used test() then replace() on the
same global-flag regex. test() advances lastIndex, so replace() starts
from the wrong position and can miss the first match.
Replace with direct replace() + string comparison to detect changes.
Also drop unnecessary global flag from done-check patterns that only
need existence testing, and eliminate the duplicate regex construction
for the table pattern.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cmdPhaseComplete and cmdPhasesRemove read STATE.md outside the lock
then wrote inside. A crash between the ROADMAP update (locked) and
the STATE write left them inconsistent. Wrap both STATE.md updates in
readModifyWriteStateMd to hold the lock across read-modify-write.
Also exports readModifyWriteStateMd from state.cjs for cross-module use.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When --default <value> is passed, config-get returns the default value
(exit 0) instead of erroring (exit 1) when the key is absent or
config.json doesn't exist. When the key IS present, --default is
ignored and the real value returned.
This lets workflows express optional config reads without defensive
`2>/dev/null || true` boilerplate that obscures intent and is fragile
under `set -e`.
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cmdInitManager called fs.readdirSync(phasesDir) and compiled a new
RegExp inside the per-phase while loop. At 50 phases this produced
50 redundant directory scans and 50 regex compilations with full
ROADMAP content scans.
Move the directory listing before the loop and pre-extract all
checkbox states via a single matchAll pass. This reduces both
patterns from O(N^2) to O(N).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
cmdRoadmapAnalyze called fs.readdirSync(phasesDir) inside the
per-phase while loop, causing O(N^2) directory reads for N phases.
At 50 phases this produced 100 redundant syscalls; at 100 phases, 200.
Move the directory listing before the loop and build a lookup array
that is reused for each phase match. This reduces the pattern from
O(N^2) to O(N) directory reads.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
loadConfig() calls isGitIgnored() which spawns a git check-ignore
subprocess. The result is stable for the process lifetime but was
being recomputed on every call. With 28+ loadConfig call sites, this
could spawn multiple redundant git subprocesses per CLI invocation.
A module-level Map cache keyed on (cwd, targetPath) ensures the
subprocess fires at most once per unique pair per process.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
next-decimal and insert-phase only scanned directory names in
.planning/phases/ when calculating the next available decimal number.
When agents added backlog items by writing ROADMAP.md entries and
creating directories without calling next-decimal, the function would
not see those entries and return a number that was already in use.
Both functions now union directory names AND ROADMAP.md phase headers
(e.g. ### Phase 999.3: ...) before computing max + 1. This follows the
same pattern already used by cmdPhaseComplete (lines 791-834) which
scans ROADMAP.md as a fallback for phases defined but not yet
scaffolded to disk.
Additional hardening:
- Use escapeRegex() on normalized phase names in regex construction
- Support optional project-code prefix in directory pattern matching
- Handle edge cases: missing ROADMAP.md, empty/missing phases dir,
leading-zero padded phase numbers in ROADMAP.md
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(agents): auto-inject relevant global learnings into planner context
* fix(agents): address review feedback for learnings planner injection
- Add features.global_learnings to VALID_CONFIG_KEYS for explicit validation
- Fix error message in cmdConfigSet to mention features.<feature_name> pattern
- Clarify tag syntax in planner injection step (frontmatter tags or objective keywords)
* fix(tests): allowlist execute-phase.md in prompt-injection scan
execute-phase.md grew to ~51K chars after the code-review gate step
was added in #1630, tripping the 50K size heuristic in the injection
scanner. The limit is calibrated for user-supplied input — trusted
workflow source files that legitimately exceed it are allowlisted
individually, following the same pattern as discuss-phase.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(security): improve prompt injection scanner with 4 detection layers (#1838)
- Layer 1: Unicode tag block U+E0000–U+E007F detection in strict mode (2025 supply-chain attack vector)
- Layer 2: Character-spacing obfuscation, delimiter injection (<system>/<assistant>/<user>/<human>), and long hex sequence patterns
- Layer 3: validatePromptStructure() — validates XML tag structure of agent/workflow files against known-valid tag set
- Layer 4: scanEntropyAnomalies() — Shannon entropy analysis flagging high-entropy paragraphs (>5.5 bits/char)
All layers implemented TDD (RED→GREEN): 31 new tests written first, verified failing, then implemented.
Full suite: 2559 tests, 0 failures. security.cjs: 99.6% stmt coverage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(config): add execution context profiles for mode-specific agent output
* fix(config): add enum validation for context config key
Validate context values against allowed enum (dev, research, review)
in cmdConfigSet before writing to config.json, matching the pattern
used for model_profile validation. Add rejection test for invalid
context values.
* feat(tools): add global learnings store with CRUD library and CLI support
* fix(tools): address review feedback for global learnings store
- Validate learning IDs against path traversal in learningsRead, learningsDelete, and cmdLearningsDelete
- Fix total invariant in learningsCopyFromProject (total = created + skipped)
- Wrap cmdLearningsPrune in try/catch to handle invalid duration format
- Rename raw -> content in readLearningFile to avoid variable shadowing
- Add CLI integration tests for list, query, prune error, and unknown subcommand
phases clear now checks for phase dirs before deleting. If any exist and
--confirm is absent, the command exits non-zero with a message showing the
count and how to proceed. Empty phases dir (nothing to delete) succeeds
without --confirm unchanged.
Updates new-milestone.md workflow to pass --confirm (intentional programmatic
caller). Updates existing new-milestone-clear-phases tests to match new API.
Closes#1826
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Add a fourth model profile preset that assigns models by agent role:
opus for planning and debugging (reasoning-critical), sonnet for
execution and research (follows instructions), haiku for mapping and
checking (high volume, structured output).
This gives solo developers on paid API tiers a cost-effective middle
ground — quality where it matters most (planning) without overspending
on mechanical tasks (mapping, checking).
Per-agent overrides via model_overrides continue to take precedence
over any profile preset, including adaptive.
Closes#1713
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* test: add stale /gsd: colon reference regression guard
Fixes#1748
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: replace 39 stale /gsd: colon references with /gsd- hyphen format
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
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#1694
The inline array parser used .split(',') which ignored quote boundaries,
splitting "a, b" into two items. Replaced with a quote-aware splitter
that tracks single/double quote state.
Updated REG-04 test to assert correct behavior and added coverage for
single-quoted and mixed-quote inline arrays.
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(state): add programmatic gates for STATE.md consistency
Adds four enforcement gates to prevent STATE.md drift:
- `state validate`: detects drift between STATE.md and filesystem
- `state sync`: reconstructs STATE.md from actual project state
- `state planned-phase`: records state after plan-phase completes
- Performance Metrics update in `phase complete`
Also fixes ghost `state update-position` command reference in
execute-phase.md (command didn't exist in CLI dispatcher).
Closes#1627
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(state): By Phase table regex ate next section when table body was empty
The lazy [\s\S]*? with a $ lookahead in byPhaseTablePattern would
match past blank lines and capture the next ## section header as table
body when no data rows existed. Replaced with a precise row-matching
pattern ((?:[ \t]*\|[^\n]*\n)*) that only captures pipe-delimited
lines. Added regression assertion to verify row placement.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* 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: replace /gsd: command format with /gsd- skill format in all suggestions
All next-step suggestions shown to users were still using the old colon
format (/gsd:xxx) which cannot be copy-pasted as skills. Migrated all
occurrences across agents/, commands/, get-shit-done/, docs/, README files,
bin/install.js (hardcoded defaults for claude runtime), and
get-shit-done/bin/lib/*.cjs (generate-claude-md templates and error messages).
Updated tests to assert new hyphen format instead of old colon format.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: migrate remaining /gsd: format to /gsd- in hooks, workflows, and sdk
Addresses remaining user-facing occurrences missed in the initial migration:
- hooks/: fix 4 user-facing messages (pause-work, update, fast, quick)
and 2 comments in gsd-workflow-guard.js
- get-shit-done/workflows/: fix 21 Skill() literal calls that Claude
executes directly (installer does not transform workflow content)
- sdk/prompt-sanitizer.ts: update regex to strip /gsd- format in addition
to legacy /gsd: format; update JSDoc comment
- tests/: update autonomous-ui-steps, prompt-sanitizer to assert new format
Note: commands/gsd/*.md frontmatter (name: gsd:xxx) intentionally unchanged
— installer derives skillName from directory path, not the name field.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(plan-phase): preserve --chain flag in auto-advance sync and handle ui-phase gate in chain mode
Bug 1: step 15 sync-flag check only guarded against --auto, causing
_auto_chain_active to be cleared when plan-phase is invoked without
--auto in ARGUMENTS even though a --chain pipeline was active. Added
--chain to the guard condition, matching discuss-phase behaviour.
Bug 2: UI Design Contract gate (step 5.6) always exited the workflow
when UI-SPEC was missing, breaking the discuss --chain pipeline
silently. When _auto_chain_active is true, the gate now auto-invokes
gsd-ui-phase --auto via Skill() and continues to step 6 without
prompting. Manual invocations retain the existing AskUserQuestion flow.
* fix: remove <sub>/clear</sub> pattern and duplicate old-format command in discuss-phase.md
---------
Co-authored-by: Claude Opus 4.6 <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(roadmap): fall back to full ROADMAP.md for backlog and planned phases
Closes#1634
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(roadmap): prevent checklist-only match from blocking full header fallback
When the current milestone had a checklist reference to a phase (e.g.
`- [ ] **Phase 50: Cleanup**`) but the full `### Phase 50:` header
existed in a different milestone, the malformed_roadmap result from the
first searchPhaseInContent call short-circuited the `||` operator and
prevented the fallback to the full roadmap content.
Now a malformed_roadmap result is deferred so the full content search
can find the actual header match.
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>
* feat(planner): add reachability_check step to prevent unreachable code
Closes#1495
* fix: trim gsd-planner.md below 50000-char limit after rebase
The reachability_check addition pushed the file to 50,275 chars when
merged with the assign_waves additions from #1600. Condense both sections
while preserving all logic; file is now 49,859 chars.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(security): normalize CRLF before prompt-stuffing length check
On Windows, git checks out files with CRLF line endings. JavaScript's
String.length counts \r characters, so a 49,859-byte file measures as
51,126 chars on Windows — falsely tripping the 50,000-char security
scanner. Normalize CRLF → LF before measuring in security.cjs and in
the reachability-check test.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: trim gsd-planner.md to stay under 50000-char limit after merge with main
After rebasing onto main (which added mcp_tool_usage block), combined content
reached 50031 chars. Remove suggested log format from assign_waves rule to
bring file to 49972 chars, well under the 50000-char security scanner limit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <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>
* fix: correct STATE.md progress counter fields during plan/phase completion (#1589)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: re-run CI with Windows pointer lifecycle fix in main
---------
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>
* fix: clear phases directory when creating new milestone (#1588)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: re-run CI with Windows pointer lifecycle fix in main
---------
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>
- fix(#1572): phase complete now marks bold-wrapped plan checkboxes in ROADMAP.md
(`- [ ] **01-01**` format) by allowing optional `**` around plan IDs in the
planCheckboxPattern regex in both phase.cjs and roadmap.cjs
- fix(#1569): manager init no longer recommends 999.x (BACKLOG) phases as next
actions; add guard in cmdManagerInit that skips phases matching /^999(?:\.|$)/
- fix(#1568): add regression tests confirming init execute-phase respects
model_overrides for executor_model, including when resolve_model_ids is 'omit'
- fix(#1533): reject session_id values containing path traversal sequences
(../, /, \) in gsd-context-monitor and gsd-statusline before constructing
/tmp file paths; add security tests covering both hooks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Detect runtime from execution_context path or env vars at workflow start,
then set INSTRUCTION_FILE (AGENTS.md for Codex, CLAUDE.md for all others).
Pass --output $INSTRUCTION_FILE to generate-claude-md so the helper writes
to the correct file instead of always defaulting to CLAUDE.md.
Also add .codex to skipDirs in init.cjs so Codex runtime directories are
not mistaken for project content during brownfield codebase analysis.
Closes#1521
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Address review feedback: sanitize manager.flags values to allow only
CLI-safe tokens (--flag patterns and alphanumeric values). Invalid
tokens are dropped with a stderr warning. Prevents prompt injection
via compromised config.json.
Co-Authored-By: Claude Opus 4.6 (1M context) <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>