Skip to content

fix: resolve 9 CLI and config bugs (#734, #737, #739, #740, #741, #759, #786, #664, #670)#813

Closed
Tibsfox wants to merge 8 commits intogsd-build:mainfrom
Tibsfox:fix/cli-config-bugs
Closed

fix: resolve 9 CLI and config bugs (#734, #737, #739, #740, #741, #759, #786, #664, #670)#813
Tibsfox wants to merge 8 commits intogsd-build:mainfrom
Tibsfox:fix/cli-config-bugs

Conversation

@Tibsfox
Copy link
Contributor

@Tibsfox Tibsfox commented Feb 28, 2026

Summary

Resolves 8 distinct CLI and config bugs across gsd-tools core, agent templates, and workflow definitions. Fixes cover null field extraction (#734), missing config loading (#740), regex injection (#741), variable name mismatches (#737), stale skill discovery paths (#759), subagent path resolution failures (#786), split cache invalidation (#663, #664), and overly broad migration regex (#670).

50 files changed, 426 insertions, 198 deletions. 4 new tests added for canonical plan format parsing; full suite passes (423 tests, 0 failures).

Root Cause

# Issue Root Cause
1 #734 phase-plan-index null fields files_modified only checked hyphenated files-modified key (plans use underscore files_modified). objective only checked frontmatter (plans use <objective> XML tag). task_count only matched ## Task N markdown (plans use <task> XML tags). All three returned null/0 for canonical plan format.
2 #740 nyquist_validation not loaded loadConfig() in core.cjs had no entry for nyquist_validation -- the field was defined in config.json schema but never read, so plan-phase always saw it as undefined and skipped Nyquist validation even when enabled.
3 #741 regex metacharacters in stateExtractField stateExtractField() interpolated user-supplied field names directly into a RegExp constructor without escaping. Field names containing regex metacharacters (e.g., Progress (%), Status [draft]) produced invalid or incorrect regex patterns.
4 #737 resolve-model variable name mismatch debug.md assigned to DEBUGGER_MODEL (uppercase) but Task() calls referenced {debugger_model} (lowercase). Same for CHECKER_MODEL vs {integration_checker_model} in audit-milestone.md. Claude treated the mismatch as an unresolved placeholder and fell back to the parent session model, ignoring the configured GSD profile.
5 #759 skill discovery path hardcoded All agent templates and workflow files hardcoded .agents/skills/ as the only skill discovery path. After Claude Code SDK v1.0.17 moved skills to .claude/skills/, agents in projects using the new layout silently skipped all project skills.
6 #786 tilde in gsd-tools.cjs paths Every workflow and agent template used ~/.claude/get-shit-done/bin/gsd-tools.cjs with a bare tilde. Subagents spawned via Task() do not inherit the parent shell's tilde expansion, so ~ resolved literally, causing MODULE_NOT_FOUND errors in every subagent node invocation.
7 #663 #664 stale update cache The SessionStart hook (gsd-check-update.js) always writes the update-check cache to ~/.claude/cache/ via os.homedir(), regardless of install type. The update workflow only cleared the install-type-specific path (local: ./.claude/cache/, global: ~/.claude/cache/), leaving stale cache at the other path and a phantom "update available" statusline indicator.
8 #670 statusline migration regex too broad cleanupOrphanedHooks() in install.js matched any statusLine.command containing statusline.js (e.g., includes('statusline.js')) and renamed it to gsd-statusline.js. This clobbered third-party statusline scripts (e.g., ~/.config/my-tool/statusline.js) that happened to contain the substring.

Fix

# Issue Fix
1 #734 Accept both files_modified (underscore, canonical) and files-modified (hyphen, legacy). Add extractObjective() helper to parse <objective> XML tags from plan body. Count <task> XML tags first, fall back to ## Task N markdown.
2 #740 Add nyquist_validation to loadConfig() defaults (false) and config reader chain (workflow.nyquist_validation).
3 #741 Escape field names with fieldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') before interpolating into RegExp. Applied to both stateExtractField() and stateReplaceField().
4 #737 Rename DEBUGGER_MODEL to debugger_model and CHECKER_MODEL to integration_checker_model to match the lowercase placeholders in Task() template strings.
5 #759 Change all 8 agent/workflow files from .agents/skills/ to .claude/skills/ or .agents/skills/ with "if either exists" semantics, supporting both old and new SDK layouts.
6 #786 Replace all 166 occurrences of ~/.claude/get-shit-done/bin/gsd-tools.cjs with "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" (quoted, environment variable) across 44 files (agents, commands, workflows, references).
7 #663 #664 Clear both ./.claude/cache/gsd-update-check.json and ~/.claude/cache/gsd-update-check.json unconditionally, removing the install-type conditional logic.
8 #670 Narrow the detection regex from includes('statusline.js') to /hooks[\/\\]statusline\.js/ (must be inside a hooks/ directory). Narrow the replacement regex from /statusline\.js/ to /hooks([\/\\])statusline\.js/ with backreference hooks$1gsd-statusline.js, preserving the path separator.

Relationship to Other PRs

This is PR #6 of 6 from the dev-bugfix branch:

PR Branch Scope
#2 fix/milestone-completion-bugs Milestone completion bugs
#3 fix/cross-platform-windows-ci Cross-platform Windows CI fixes
#4 feat/agent-discuss-codex-enhancements Feature enhancements
#5 fix/agent-frontmatter-heredoc Agent frontmatter + heredoc fix
#6 (this) fix/cli-config-bugs CLI/config bug fixes (8 issues)
#1 fix/mcp-migration-helper MCP migration helper (targets dev-bugfix)

No code overlap between PRs; all can be merged independently. The $HOME path fix (#786) in this PR complements PR #1's MCP migration work -- both address subagent path resolution but through different mechanisms (environment variable expansion vs. connectivity pre-checks).

Testing

PR-specific tests (new in this PR)

Test File Validates
files_modified: underscore key is parsed correctly tests/phase.test.cjs #734 -- canonical files_modified frontmatter key
objective: extracted from <objective> XML tag tests/phase.test.cjs #734 -- XML tag extraction over frontmatter
task_count: counts <task> XML tags tests/phase.test.cjs #734 -- XML task counting
all three fields work together in canonical plan format tests/phase.test.cjs #734 -- integration of all three fixes

Full suite results

Test runner:  node --test tests/*.test.cjs
Total tests:  423
Pass:         423
Fail:         0
Skipped:      0
Duration:     ~4.5s
Test File Tests Pass Fail
commands.test.cjs 1 1 0
config.test.cjs 19 19 0
core.test.cjs 60 60 0
dispatcher.test.cjs 22 22 0
frontmatter-cli.test.cjs 20 20 0
frontmatter.test.cjs 32 32 0
init.test.cjs 47 47 0
milestone.test.cjs 14 14 0
phase.test.cjs 58 58 0
roadmap.test.cjs 24 24 0
state.test.cjs 63 63 0
verify-health.test.cjs 21 21 0
verify.test.cjs 42 42 0

Manual validation

  • npm test passes with all fixes applied
  • Regex escaping handles metacharacters in field names (parentheses, brackets, dots)
  • Statusline migration only matches GSD's own hooks/statusline.js path
  • $HOME expansion works in subagent contexts where tilde does not
  • Both .claude/skills/ and .agents/skills/ are discovered

Impact

Severity: High -- Six of these bugs cause silent failures in production workflows:

Scope: 50 files across agents (7), commands (2), workflows (27), references (4), core libs (3), installer (1), and tests (1). The $HOME fix alone touches 44 files with 166 path replacements.

Risk: Low -- All fixes are narrowly scoped (regex tightening, config key additions, variable renames, path quoting). No behavioral changes to happy-path logic. Full test suite passes with zero regressions.

ROMB and others added 8 commits February 28, 2026 02:23
…e, task_count (gsd-build#734)

cmdPhasePlanIndex had 3 mismatches with the canonical XML plan format
defined in templates/phase-prompt.md:

- files_modified: looked up fm['files-modified'] (hyphen) but plans use
  files_modified (underscore). Now checks underscore first, hyphen fallback.
- objective: read from YAML frontmatter but plans put it in <objective>
  XML tag. Now extracts first line from the tag, falls back to frontmatter.
- task_count: matched ## Task N markdown headings but plans use <task>
  XML tags. Now counts XML tags first, markdown fallback.

All three fixes preserve backward compat with legacy markdown-style plans.

Co-authored-by: Claude Opus 4.6 <[email protected]>
loadConfig() didn't include nyquist_validation in its return object, so
cmdInitPlanPhase always set nyquist_validation_enabled to undefined. The
plan-phase workflow could never detect whether Nyquist validation was
enabled or disabled via config.

Co-authored-by: Ethan Hurst <[email protected]>
stateReplaceField properly escaped fieldName before building the regex,
but stateExtractField did not. Field names containing regex metacharacters
could cause incorrect matches or regex errors.

Co-authored-by: Ethan Hurst <[email protected]>
…sd-build#737)

DEBUGGER_MODEL and CHECKER_MODEL used uppercase bash convention but the
Task() calls referenced {debugger_model} and {integration_checker_model}
(lowercase). The mismatch caused Claude to skip substitution and fall back
to the parent session model, ignoring the configured GSD profile.

Co-authored-by: Ethan Hurst <[email protected]>
…iscovery (gsd-build#759)

* fix: use `.claude/skills/` instead of `.agents/skills/` in agent and workflow skill references

Claude Code resolves project skills from `.claude/skills/` (project-level)
and `~/.claude/skills/` (user-level). The `.agents/skills/` path is the
universal/IDE-agnostic convention that Claude Code does not resolve, causing
project skills to be silently ignored by all affected agents and workflows.

Fixes gsd-build#758

Co-Authored-By: Claude Opus 4.6 <[email protected]>

* fix: support both `.claude/skills/` and `.agents/skills/` for cross-IDE compatibility

Instead of replacing `.agents/skills/` with `.claude/skills/`, reference both
paths so GSD works with Claude Code (`.claude/skills/`) and other IDE agents
like OpenCode (`.agents/skills/`).

Addresses review feedback from begna112 on gsd-build#758.

Co-Authored-By: Claude Opus 4.6 <[email protected]>

---------

Co-authored-by: Stephen Miller <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
…nt MODULE_NOT_FOUND (gsd-build#786)

Claude Code subagents sometimes rewrite ~/. paths to relative paths,
causing MODULE_NOT_FOUND when CWD is the project directory. $HOME is a
shell variable resolved at runtime, immune to model path rewriting.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
The SessionStart hook always writes to ~/.claude/cache/ via os.homedir()
regardless of install type. The update workflow previously only cleared
the install-type-specific path, leaving stale cache at the global path
for local installs.

Clear both ./.claude/cache/ and ~/.claude/cache/ unconditionally.

Co-authored-by: Claude Sonnet 4.5 <[email protected]>
…slines (gsd-build#670)

The gsd-build#330 migration that renames `statusline.js` → `gsd-statusline.js`
uses `.includes('statusline.js')` which matches ANY file containing
that substring. For example, a user's custom `ted-statusline.js` gets
silently rewritten to `ted-gsd-statusline.js` (which doesn't exist).

This happens inside `cleanupOrphanedHooks()` which runs before the
interactive "Keep existing / Replace" prompt, so even choosing "Keep
existing" doesn't prevent the damage.

Fix: narrow the regex to only match the specific old GSD path pattern
`hooks/statusline.js` (or `hooks\statusline.js` on Windows).

Co-authored-by: ddungan <[email protected]>
Co-authored-by: Claude Opus 4.6 <[email protected]>
@Tibsfox
Copy link
Contributor Author

Tibsfox commented Feb 28, 2026

These were all addressed during the productive Feb 27 merge session — wonderful to see the project moving so quickly! Closing this to keep the PR queue tidy. Excited to keep contributing where it helps most. 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants