Revert 12 PRs merged without authorization

Reverts:
- #413 fix(install): build hooks/dist on the fly for dev installs
- #396 refactor(statusline): display git branch in status line
- #543 Add in-progress state for todos
- #532 feat: add git_tag config option
- #171 Add OpenCode installer package
- #519 feat: Add Mistral Vibe CLI Support
- #389 docs: use @latest for uninstall commands
- #288 fix(install): auto-migrate renamed statusline.js reference
- #502 fix(#501): verify-work defers diagnosis/planning
- #497 fix: move resolved debug sessions to resolved/ folder
- #512 fix: create feature branch before first commit
- #545 fix: add Write tool to gsd-verifier

These were merged without owner approval.
This commit is contained in:
Lex Christopherson
2026-02-14 14:25:26 -06:00
parent e146b0846a
commit 9d815d31db
26 changed files with 424 additions and 1383 deletions

View File

@@ -6,9 +6,6 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
### Fixed
- `gsd-verifier` agent now declares the Write tool, avoiding Bash heredoc fallbacks that could corrupt editor permission files when generating VERIFICATION.md reports.
## [1.18.0] - 2026-02-08
### Added
@@ -295,7 +292,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Fixed
- Installation on WSL2/non-TTY terminals now works correctly - detects non-interactive stdin and falls back to global install automatically
- Installation now verifies files were actually copied before showing success checkmarks
- Orphaned `gsd-notify.sh` hook from previous versions is ahora automatically removed during install (both file and settings.json registration)
- Orphaned `gsd-notify.sh` hook from previous versions is now automatically removed during install (both file and settings.json registration)
## [1.6.3] - 2025-01-17
@@ -330,21 +327,21 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- **BREAKING:** `/gsd:research-project` — integrated into project/milestone flows
### Added
- `/gsd:verify-work` ahora includes next-step routing after verification completes
- `/gsd:verify-work` now includes next-step routing after verification completes
## [1.5.30] - 2026-01-17
### Fixed
- Output templates in `plan-phase`, `execute-phase`, and `audit-milestone` now render markdown correctly instead of showing literal backticks
- Next-step suggestions ahora consistently recommend `/gsd:discuss-phase` before `/gsd:plan-phase` across all routing paths
- Next-step suggestions now consistently recommend `/gsd:discuss-phase` before `/gsd:plan-phase` across all routing paths
## [1.5.29] - 2025-01-16
### Changed
- Discuss-phase ahora uses domain-aware questioning with deeper probing for gray areas
- Discuss-phase now uses domain-aware questioning with deeper probing for gray areas
### Fixed
- Windows hooks ahora work via Node.js conversion (statusline, update-check)
- Windows hooks now work via Node.js conversion (statusline, update-check)
- Phase input normalization at command entry points
- Removed blocking notification popups (gsd-notify) on all platforms
@@ -356,36 +353,36 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- **BREAKING:** Removed `/gsd:execute-plan` command (use `/gsd:execute-phase` instead)
### Fixed
- Phase directory matching ahora handles both zero-padded (05-*) and unpadded (5-*) folder names
- Phase directory matching now handles both zero-padded (05-*) and unpadded (5-*) folder names
- Map-codebase agent output collection
## [1.5.27] - 2026-01-16
### Fixed
- Orchestrator corrections between executor completions are ahora committed (previously left uncommitted when orchestrator made small fixes between waves)
- Orchestrator corrections between executor completions are now committed (previously left uncommitted when orchestrator made small fixes between waves)
## [1.5.26] - 2026-01-16
### Fixed
- Revised plans ahora get committed after checker feedback (previously sólo initial plans were committed, leaving revisions uncommitted)
- Revised plans now get committed after checker feedback (previously only initial plans were committed, leaving revisions uncommitted)
## [1.5.25] - 2026-01-16
### Fixed
- Stop notification hook no longer shows stale project state (now uses session-scoped todos only)
- Researcher agent ahora reliably loads CONTEXT.md from discuss-phase
- Researcher agent now reliably loads CONTEXT.md from discuss-phase
## [1.5.24] - 2026-01-16
### Fixed
- Stop notification hook ahora correctly parses STATE.md fields (was always showing "Ready for input")
- Planner agent ahora reliably loads CONTEXT.md and RESEARCH.md files
- Stop notification hook now correctly parses STATE.md fields (was always showing "Ready for input")
- Planner agent now reliably loads CONTEXT.md and RESEARCH.md files
## [1.5.23] - 2025-01-16
### Added
- Cross-platform completion notification hook (Mac/Linux/Windows alerts when Claude stops)
- Phase researcher ahora loads CONTEXT.md from discuss-phase to focus research on user decisions
- Phase researcher now loads CONTEXT.md from discuss-phase to focus research on user decisions
### Fixed
- Consistent zero-padding for phase directories (01-name, not 1-name)
@@ -399,7 +396,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Statusline update indicator — shows `⬆ /gsd:update` when a new version is available
### Fixed
- Planner ahora updates ROADMAP.md placeholders after planning completes
- Planner now updates ROADMAP.md placeholders after planning completes
## [1.5.21] - 2026-01-16
@@ -408,12 +405,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Research synthesizer agent that consolidates parallel research into SUMMARY.md
### Changed
- **Unified `/gsd:new-project` flow** — Single command ahora handles questions → research → requirements → roadmap (~10 min)
- **Unified `/gsd:new-project` flow** — Single command now handles questions → research → requirements → roadmap (~10 min)
- Simplified README to reflect streamlined workflow: new-project → plan-phase → execute-phase
- Added optional `/gsd:discuss-phase` documentation for UI/UX/behavior decisions before planning
### Fixed
- verify-work ahora shows clear checkpoint box with action prompt ("Type 'pass' or describe what's wrong")
- verify-work now shows clear checkpoint box with action prompt ("Type 'pass' or describe what's wrong")
- Planner uses correct `{phase}-{plan}-PLAN.md` naming convention
- Planner no longer surfaces internal `user_setup` in output
- Research synthesizer commits all research files together (not individually)
@@ -423,7 +420,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [1.5.20] - 2026-01-16
### Fixed
- Research no longer skipped based on premature "Research: Unlikely" predictions made during roadmap creation. The `--skip-research` flag provides explicit control cuando needed.
- Research no longer skipped based on premature "Research: Unlikely" predictions made during roadmap creation. The `--skip-research` flag provides explicit control when needed.
### Removed
- `Research: Likely/Unlikely` fields from roadmap phase template
@@ -436,13 +433,13 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- `/gsd:discuss-phase` redesigned with intelligent gray area analysis — analyzes phase to identify discussable areas (UI, UX, Behavior, etc.), presents multi-select for user control, deep-dives each area with focused questioning
- Explicit scope guardrail prevents scope creep during discussion — captures deferred ideas without acting on them
- CONTEXT.md template restructured for decisions (domain boundary, decisions by category, Claude's discretion, deferred ideas)
- Downstream awareness: discuss-phase ahora explicitly documents that CONTEXT.md feeds researcher and planner agents
- `/gsd:plan-phase` ahora integrates research — spawns `gsd-phase-researcher` before planning unless research exists or `--skip-research` flag used
- Downstream awareness: discuss-phase now explicitly documents that CONTEXT.md feeds researcher and planner agents
- `/gsd:plan-phase` now integrates research — spawns `gsd-phase-researcher` before planning unless research exists or `--skip-research` flag used
## [1.5.18] - 2026-01-16
### Added
- **Plan verification loop** — Plans are ahora verified before execution with a planner → checker → revise cycle
- **Plan verification loop** — Plans are now verified before execution with a planner → checker → revise cycle
- New `gsd-plan-checker` agent (744 lines) validates plans will achieve phase goals
- Six verification dimensions: requirement coverage, task completeness, dependency correctness, key links, scope sanity, must_haves derivation
- Max 3 revision iterations before user escalation
@@ -481,10 +478,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Research subagent prompt template for context-only spawning
### Changed
- `/gsd:research-phase` refactored to thin orchestrator — ahora injects rich context (key insight framing, downstream consumer info, quality gates) to gsd-researcher agent
- `/gsd:research-phase` refactored to thin orchestrator — now injects rich context (key insight framing, downstream consumer info, quality gates) to gsd-researcher agent
- `/gsd:research-project` refactored to spawn 4 parallel gsd-researcher agents with milestone-aware context (greenfield vs v1.1+) and roadmap implications guidance
- `/gsd:debug` refactored to thin orchestrator (149 lines) — spawns gsd-debugger agent with full debugging expertise
- `/gsd:new-milestone` ahora explicitly references MILESTONE-CONTEXT.md
- `/gsd:new-milestone` now explicitly references MILESTONE-CONTEXT.md
### Deprecated
- `workflows/research-phase.md` — consolidated into gsd-researcher agent
@@ -497,36 +494,36 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [1.5.15] - 2025-01-15
### Fixed
- **Agents ahora install correctly** — The `agents/` folder (gsd-executor, gsd-verifier, gsd-integration-checker, gsd-milestone-auditor) was missing from npm package, now included
- **Agents now install correctly** — The `agents/` folder (gsd-executor, gsd-verifier, gsd-integration-checker, gsd-milestone-auditor) was missing from npm package, now included
### Changed
- Consolidated `/gsd:plan-fix` into `/gsd:plan-phase --gaps` for simpler workflow
- UAT file writes ahora batched instead of per-response for better performance
- UAT file writes now batched instead of per-response for better performance
## [1.5.14] - 2025-01-15
### Fixed
- Plan-phase ahora always routes to `/gsd:execute-phase` after planning, even for single-plan phases
- Plan-phase now always routes to `/gsd:execute-phase` after planning, even for single-plan phases
## [1.5.13] - 2026-01-15
### Fixed
- `/gsd:new-milestone` ahora presents research and requirements paths as equal options, matching `/gsd:new-project` format
- `/gsd:new-milestone` now presents research and requirements paths as equal options, matching `/gsd:new-project` format
## [1.5.12] - 2025-01-15
### Changed
- **Milestone cycle reworked for proper requirements flow:**
- `complete-milestone` ahora archives AND deletes ROADMAP.md and REQUIREMENTS.md (fresh for next milestone)
- `new-milestone` is ahora a "brownfield new-project" — updates PROJECT.md with new goals, routes to define-requirements
- `discuss-milestone` is ahora required before `new-milestone` (creates context file)
- `complete-milestone` now archives AND deletes ROADMAP.md and REQUIREMENTS.md (fresh for next milestone)
- `new-milestone` is now a "brownfield new-project" — updates PROJECT.md with new goals, routes to define-requirements
- `discuss-milestone` is now required before `new-milestone` (creates context file)
- `research-project` is milestone-aware — focuses on new features, ignores already-validated requirements
- `create-roadmap` continues phase numbering from previous milestone
- Flow: complete → discuss → new-milestone → research → requirements → roadmap
### Fixed
- `MILESTONE-AUDIT.md` ahora versioned as `v{version}-MILESTONE-AUDIT.md` and archived on completion
- `progress` ahora correctly routes to `/gsd:discuss-milestone` when between milestones (Route F)
- `MILESTONE-AUDIT.md` now versioned as `v{version}-MILESTONE-AUDIT.md` and archived on completion
- `progress` now correctly routes to `/gsd:discuss-milestone` when between milestones (Route F)
## [1.5.11] - 2025-01-15
@@ -536,10 +533,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [1.5.10] - 2025-01-15
### Changed
- Milestone audit ahora reads existing phase VERIFICATION.md files instead of re-verifying each phase, aggregates tech debt and deferred gaps, adds `tech_debt` status for non-blocking accumulated debt
- Milestone audit now reads existing phase VERIFICATION.md files instead of re-verifying each phase, aggregates tech debt and deferred gaps, adds `tech_debt` status for non-blocking accumulated debt
### Fixed
- VERIFICATION.md ahora included in phase completion commit alongside ROADMAP.md, STATE.md, and REQUIREMENTS.md
- VERIFICATION.md now included in phase completion commit alongside ROADMAP.md, STATE.md, and REQUIREMENTS.md
## [1.5.9] - 2025-01-15
@@ -549,7 +546,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Changed
- Checkpoint display format improved with box headers and unmissable "→ YOUR ACTION:" prompts
- Subagent colors updated (executor: yellow, integration-checker: blue)
- Execute-phase ahora recommends `/gsd:audit-milestone` when milestone completes
- Execute-phase now recommends `/gsd:audit-milestone` when milestone completes
### Fixed
- Research-phase no longer gatekeeps by domain type
@@ -574,11 +571,11 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Goal-backward planning reference: Documentation for deriving must-haves from goals
### Changed
- execute-plan and execute-phase ahora spawn `gsd-executor` subagent instead of using inline workflow
- execute-plan and execute-phase now spawn `gsd-executor` subagent instead of using inline workflow
- Roadmap and planning workflows enhanced with goal-backward analysis
### Removed
- Obsolete templates (`checkpoint-resume.md`, `subagent-task-prompt.md`) — logic ahora lives in subagents
- Obsolete templates (`checkpoint-resume.md`, `subagent-task-prompt.md`) — logic now lives in subagents
### Fixed
- Updated remaining `general-purpose` subagent references to use `gsd-executor`
@@ -590,25 +587,25 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- README: Research recommended for quality; skip only for speed
### Fixed
- execute-phase: Phase metadata (timing, wave info) ahora bundled into single commit instead of separate commits
- execute-phase: Phase metadata (timing, wave info) now bundled into single commit instead of separate commits
## [1.5.5] - 2025-01-15
### Changed
- README ahora documents the `research-project` → `define-requirements` flow (optional but recommended before `create-roadmap`)
- README now documents the `research-project` → `define-requirements` flow (optional but recommended before `create-roadmap`)
- Commands section reorganized into 7 grouped tables (Setup, Execution, Verification, Milestones, Phase Management, Session, Utilities) for easier scanning
- Context Engineering table ahora includes `research/` and `REQUIREMENTS.md`
- Context Engineering table now includes `research/` and `REQUIREMENTS.md`
## [1.5.4] - 2025-01-15
### Changed
- Research phase ahora loads REQUIREMENTS.md to focus research on concrete requirements (e.g., "email verification") rather than just high-level roadmap descriptions
- Research phase now loads REQUIREMENTS.md to focus research on concrete requirements (e.g., "email verification") rather than just high-level roadmap descriptions
## [1.5.3] - 2025-01-15
### Changed
- **execute-phase narration**: Orchestrator ahora describes what each wave builds before spawning agents, and summarizes what was built after completion. No more staring at opaque status updates.
- **new-project flow**: Ahora offers two paths — research first (recommended) or define requirements directly (fast path for familiar domains)
- **execute-phase narration**: Orchestrator now describes what each wave builds before spawning agents, and summarizes what was built after completion. No more staring at opaque status updates.
- **new-project flow**: Now offers two paths — research first (recommended) or define requirements directly (fast path for familiar domains)
- **define-requirements**: Works without prior research. Gathers requirements through conversation when FEATURES.md doesn't exist.
### Removed
@@ -619,17 +616,17 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [1.5.2] - 2026-01-15
### Added
- Requirements traceability: roadmap phases ahora include `Requirements:` field listing which REQ-IDs they cover
- Requirements traceability: roadmap phases now include `Requirements:` field listing which REQ-IDs they cover
- plan-phase loads REQUIREMENTS.md and shows phase-specific requirements before planning
- Requirements automatically marked Complete cuando phase finishes
- Requirements automatically marked Complete when phase finishes
### Changed
- Workflow preferences (mode, depth, parallelization) ahora asked in single prompt instead of 3 separate questions
- Workflow preferences (mode, depth, parallelization) now asked in single prompt instead of 3 separate questions
- define-requirements shows full requirements list inline before commit (not just counts)
- Research-project and workflow aligned to both point to define-requirements as next step
### Fixed
- Requirements status ahora updated by orchestrator (commands) instead of subagent workflow, which couldn't determine phase completion
- Requirements status now updated by orchestrator (commands) instead of subagent workflow, which couldn't determine phase completion
## [1.5.1] - 2026-01-14
@@ -642,11 +639,11 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Added
- New `/gsd:research-project` command for pre-roadmap ecosystem research — spawns parallel agents to investigate stack, features, architecture, and pitfalls before you commit to a roadmap
- New `/gsd:define-requirements` command for scoping v1 requirements from research findings — transforms "what exists in this domain" into "what we're building"
- Requirements traceability: phases ahora map to specific requirement IDs with 100% coverage validation
- Requirements traceability: phases now map to specific requirement IDs with 100% coverage validation
### Changed
- **BREAKING:** New project flow is ahora: `new-project → research-project → define-requirements → create-roadmap`
- Roadmap creation ahora requires REQUIREMENTS.md and validates all v1 requirements are mapped to phases
- **BREAKING:** New project flow is now: `new-project → research-project → define-requirements → create-roadmap`
- Roadmap creation now requires REQUIREMENTS.md and validates all v1 requirements are mapped to phases
- Simplified questioning in new-project to four essentials (vision, core priority, boundaries, constraints)
## [1.4.29] - 2026-01-14
@@ -659,7 +656,10 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Fixed
- Restored comprehensive checkpoint documentation with full examples for verification, decisions, and auth gates
- Fixed execute-plan command to use fresh continuation agents instead of broken resume pattern
- Rich checkpoint presentation formats ahora documented for all three checkpoint types
- Rich checkpoint presentation formats now documented for all three checkpoint types
### Changed
- Slimmed execute-phase command to properly delegate checkpoint handling to workflow
## [1.4.27] - 2025-01-14
@@ -676,7 +676,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Added
- New `/gsd:whats-new` command shows changes since your installed version
- VERSION file written during installation for version tracking
- CHANGELOG.md ahora included in package installation
- CHANGELOG.md now included in package installation
## [1.4.24] - 2026-01-14
@@ -708,7 +708,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Fixed
- Standardized debug file naming convention
- Debug workflow ahora invokes execute-plan correctly
- Debug workflow now invokes execute-plan correctly
## [1.4.19] - 2026-01-14
@@ -739,7 +739,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [1.4.14] - 2026-01-13
### Changed
- YOLO mode is ahora recommended default in new-project
- YOLO mode is now recommended default in new-project
## [1.4.13] - 2026-01-13
@@ -750,12 +750,12 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [1.4.12] - 2026-01-13
### Changed
- execute-phase is ahora recommended as primary execution command
- execute-phase is now recommended as primary execution command
## [1.4.11] - 2026-01-13
### Fixed
- Checkpoints ahora use fresh continuation agents instead of resume
- Checkpoints now use fresh continuation agents instead of resume
## [1.4.10] - 2026-01-13
@@ -825,7 +825,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Changed
- Renamed `execute-phase.md` workflow to `execute-plan.md` for clarity
- Plan frontmatter ahora includes `wave`, `depends_on`, `files_modified`, `autonomous`
- Plan frontmatter now includes `wave`, `depends_on`, `files_modified`, `autonomous`
## [1.4.0] - 2026-01-12
@@ -836,7 +836,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Agent history schema v1.2 with parallel execution support
### Changed
- Plans ahora puede specify wave numbers and dependencies
- Plans can now specify wave numbers and dependencies
- execute-phase orchestrates multiple subagents in waves
## [1.3.34] - 2026-01-11
@@ -898,7 +898,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Claude Code marketplace plugin support
### Fixed
- Phase artifacts ahora committed when created
- Phase artifacts now committed when created
## [1.3.25] - 2026-01-06
@@ -1042,7 +1042,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Fixed
- Permission errors for non-DSP users (removed shell context)
- First question is ahora freeform, not AskUserQuestion
- First question is now freeform, not AskUserQuestion
## [1.2.13] - 2025-12-17
@@ -1117,7 +1117,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [1.1.2] - 2025-12-15
### Fixed
- YOLO mode ahora skips confirmation gates in plan-phase
- YOLO mode now skips confirmation gates in plan-phase
## [1.1.1] - 2025-12-15

View File

@@ -607,19 +607,14 @@ To remove GSD completely:
```bash
# Global installs
npx get-shit-done-cc@latest --claude --global --uninstall
npx get-shit-done-cc@latest --opencode --global --uninstall
npx get-shit-done-cc@latest --gemini --global --uninstall
npx get-shit-done-cc --claude --global --uninstall
npx get-shit-done-cc --opencode --global --uninstall
# Local installs (current project)
npx get-shit-done-cc@latest --claude --local --uninstall
npx get-shit-done-cc@latest --opencode --local --uninstall
npx get-shit-done-cc@latest --gemini --local --uninstall
npx get-shit-done-cc --claude --local --uninstall
npx get-shit-done-cc --opencode --local --uninstall
```
> [!IMPORTANT]
> Always use `@latest` when uninstalling. If you installed an older version, npx may have cached it, and that version might not support the `--uninstall` flag.
This removes all GSD commands, agents, hooks, and settings while preserving your other configurations.
---

View File

@@ -1,11 +0,0 @@
---
name: general-purpose
description: A flexible agent that adopts personas based on task instructions. Used as a base for dynamic subagents.
tools: Read, Write, Bash, List, Grep, AskUser
color: grey
---
You are a helpful general-purpose assistant.
You are often used as a base agent that reads specific instructions from a file to adopt a new persona.
Always follow the instructions provided in the task prompt.
When asked to read a file for instructions, read it first and then execute the task as that role.

View File

@@ -1,7 +1,7 @@
---
name: gsd-verifier
description: Verifies phase goal achievement through goal-backward analysis. Checks codebase delivers what phase promised, not just that tasks completed. Creates VERIFICATION.md report.
tools: Read, Write, Bash, Grep, Glob
tools: Read, Bash, Grep, Glob
color: green
---
@@ -254,7 +254,7 @@ Run anti-pattern detection on each file:
grep -n -E "TODO|FIXME|XXX|HACK|PLACEHOLDER" "$file" 2>/dev/null
grep -n -E "placeholder|coming soon|will be here" "$file" -i 2>/dev/null
# Empty implementations
grep -n -E "return null|return \{}|return \[]|=> \{}" "$file" 2>/dev/null
grep -n -E "return null|return \{\}|return \[\]|=> \{\}" "$file" 2>/dev/null
# Console.log only implementations
grep -n -B 2 -A 2 "console\.log" "$file" 2>/dev/null | grep -E "^\s*(const|function|=>)"
```

View File

@@ -1,208 +0,0 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
const { execSync } = require('child_process');
const VIBE_HOME = process.env.VIBE_HOME || path.join(os.homedir(), '.vibe');
const GSD_SRC = path.resolve(__dirname, '..');
const GSD_DEST = path.join(VIBE_HOME, 'get-shit-done');
// Directories to create
const DIRS = [
path.join(VIBE_HOME, 'skills'),
path.join(VIBE_HOME, 'agents'),
path.join(VIBE_HOME, 'prompts'),
GSD_DEST,
path.join(GSD_DEST, 'bin'),
path.join(GSD_DEST, 'workflows'),
path.join(GSD_DEST, 'templates'),
path.join(GSD_DEST, 'references'),
];
// Helper to copy directory recursively
function copyDir(src, dest) {
if (!fs.existsSync(src)) return;
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
const entries = fs.readdirSync(src, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(src, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
copyDir(srcPath, destPath);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
// 1. Setup Directories
function ensureDirectories() {
console.log('Setting up directories...');
DIRS.forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
console.log(`Created ${dir}`);
}
});
}
// 2. Copy Core Files
function copyCoreFiles() {
console.log('Copying core GSD files...');
copyDir(path.join(GSD_SRC, 'get-shit-done/bin'), path.join(GSD_DEST, 'bin'));
copyDir(path.join(GSD_SRC, 'get-shit-done/templates'), path.join(GSD_DEST, 'templates'));
copyDir(path.join(GSD_SRC, 'get-shit-done/references'), path.join(GSD_DEST, 'references'));
// Make bin executable
try {
if (fs.existsSync(path.join(GSD_DEST, 'bin'))) {
const binFiles = fs.readdirSync(path.join(GSD_DEST, 'bin'));
binFiles.forEach(f => fs.chmodSync(path.join(GSD_DEST, 'bin', f), '755'));
}
} catch (e) {
console.warn('Could not chmod bin files (expected on Windows).');
}
}
// 3. Convert Commands to Skills
function convertCommandsToSkills() {
console.log('Converting commands to skills...');
const commandsDir = path.join(GSD_SRC, 'commands/gsd');
if (fs.existsSync(commandsDir)) {
const files = fs.readdirSync(commandsDir).filter(f => f.endsWith('.md'));
files.forEach(file => {
const content = fs.readFileSync(path.join(commandsDir, file), 'utf8');
const slug = file.replace('.md', '');
const skillName = `gsd-${slug}`;
const skillDir = path.join(VIBE_HOME, 'skills', skillName);
if (!fs.existsSync(skillDir)) fs.mkdirSync(skillDir, { recursive: true });
// Transform content
let newContent = content
.replace(/~\/\.claude/g, '~/.vibe')
.replace(/name: gsd:([\w-]+)/, `name: gsd-$1\nuser-invocable: true`)
.replace(/Task\(prompt="(?<prompt>[\s\S]*?)", subagent_type="(?<agent>[\w-]+)",.*?\)/g, (match, prompt, agent) => {
return `task(task="${prompt.replace(/"/g, '\\"')}", agent="${agent}")`;
});
// Fix allowed tools
const toolMap = {
'Read': 'read_file',
'Write': 'write_file',
'Bash': 'bash',
'Glob': 'list_dir',
'Grep': 'grep',
'AskUserQuestion': 'ask_user_question',
'Task': 'task'
};
newContent = newContent.replace(/allowed-tools:\s*\n((?:\s+-\s+\w+\n?)+)/, (match, toolsBlock) => {
const tools = toolsBlock.split('\n')
.map(t => t.trim().replace('- ', ''))
.filter(t => t)
.map(t => toolMap[t] || t)
.map(t => ` - ${t}`)
.join('\n');
return `allowed-tools:\n${tools}\n`;
});
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), newContent);
console.log(`Created skill: ${skillName}`);
});
}
}
// 4. Convert Agents
function convertAgents() {
console.log('Converting agents...');
const agentsDir = path.join(GSD_SRC, 'agents');
if (fs.existsSync(agentsDir)) {
const files = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md'));
files.forEach(file => {
const content = fs.readFileSync(path.join(agentsDir, file), 'utf8');
const agentName = file.replace('.md', '');
// 4.1 Write System Prompt
const promptPath = path.join(VIBE_HOME, 'prompts', `${agentName}.md`);
// Remove frontmatter for the prompt file
const promptContent = content.replace(/^---\n[\s\S]+?\n---\n/, '');
fs.writeFileSync(promptPath, promptContent);
// 4.2 Write Agent TOML
const tomlPath = path.join(VIBE_HOME, 'agents', `${agentName}.toml`);
const tomlContent = `
system_prompt_id = "${agentName}"
active_model = "mistral-large-latest"
agent_type = "subagent"
[tools]
read_file.permission = "always"
write_file.permission = "always"
bash.permission = "always"
grep.permission = "always"
list_dir.permission = "always"
ask_user_question.permission = "always"
`;
fs.writeFileSync(tomlPath, tomlContent.trim());
console.log(`Created agent: ${agentName}`);
});
}
}
// 5. Patch Workflows
function patchWorkflows() {
console.log('Patching workflows...');
const workflowsSrc = path.join(GSD_SRC, 'get-shit-done/workflows');
const workflowsDest = path.join(GSD_DEST, 'workflows');
if (fs.existsSync(workflowsSrc)) {
const files = fs.readdirSync(workflowsSrc).filter(f => f.endsWith('.md'));
files.forEach(file => {
let content = fs.readFileSync(path.join(workflowsSrc, file), 'utf8');
// Patch paths
content = content.replace(/~\/\.claude/g, '~/.vibe');
// Patch Task calls
content = content.replace(/Task\(\s*prompt\s*=\s*"([\s\S]*?)",\s*subagent_type\s*=\s*"([^"]+)"(?:,\s*model\s*=\s*"[^"]+")?\s*\)/g, (match, prompt, agent) => {
return `task(task="${prompt}", agent="${agent}")`;
});
// Fix path to agent prompts (which are in prompts/ dir, not agents/ dir)
content = content.replace(/~\/\.vibe\/agents\/([a-zA-Z0-9_-]+\.md)/g, '~/.vibe/prompts/$1');
fs.writeFileSync(path.join(workflowsDest, file), content);
console.log(`Patched workflow: ${file}`);
});
}
}
function install() {
console.log('Starting GSD Vibe installation...');
ensureDirectories();
copyCoreFiles();
convertCommandsToSkills();
convertAgents();
patchWorkflows();
console.log('Installation complete! Please restart Vibe CLI.');
return { runtime: 'vibe', success: true };
}
module.exports = { install };
if (require.main === module) {
try {
install();
} catch (err) {
console.error('Installation failed:', err);
process.exit(1);
}
}

View File

@@ -15,7 +15,6 @@ const reset = '\x1b[0m';
// Get version from package.json
const pkg = require('../package.json');
const { install: installVibe } = require('./install-vibe');
// Parse args
const args = process.argv.slice(2);
@@ -24,7 +23,6 @@ const hasLocal = args.includes('--local') || args.includes('-l');
const hasOpencode = args.includes('--opencode');
const hasClaude = args.includes('--claude');
const hasGemini = args.includes('--gemini');
const hasVibe = args.includes('--vibe');
const hasBoth = args.includes('--both'); // Legacy flag, keeps working
const hasAll = args.includes('--all');
const hasUninstall = args.includes('--uninstall') || args.includes('-u');
@@ -32,21 +30,19 @@ const hasUninstall = args.includes('--uninstall') || args.includes('-u');
// Runtime selection - can be set by flags or interactive prompt
let selectedRuntimes = [];
if (hasAll) {
selectedRuntimes = ['claude', 'opencode', 'gemini', 'vibe'];
selectedRuntimes = ['claude', 'opencode', 'gemini'];
} else if (hasBoth) {
selectedRuntimes = ['claude', 'opencode'];
} else {
if (hasOpencode) selectedRuntimes.push('opencode');
if (hasClaude) selectedRuntimes.push('claude');
if (hasGemini) selectedRuntimes.push('gemini');
if (hasVibe) selectedRuntimes.push('vibe');
}
// Helper to get directory name for a runtime (used for local/project installs)
function getDirName(runtime) {
if (runtime === 'opencode') return '.opencode';
if (runtime === 'gemini') return '.gemini';
if (runtime === 'vibe') return '.vibe';
return '.claude';
}
@@ -60,17 +56,17 @@ function getOpencodeGlobalDir() {
if (process.env.OPENCODE_CONFIG_DIR) {
return expandTilde(process.env.OPENCODE_CONFIG_DIR);
}
// 2. OPENCODE_CONFIG env var (use its directory)
if (process.env.OPENCODE_CONFIG) {
return path.dirname(expandTilde(process.env.OPENCODE_CONFIG));
}
// 3. XDG_CONFIG_HOME/opencode
if (process.env.XDG_CONFIG_HOME) {
return path.join(expandTilde(process.env.XDG_CONFIG_HOME), 'opencode');
}
// 4. Default: ~/.config/opencode (XDG default)
return path.join(os.homedir(), '.config', 'opencode');
}
@@ -88,7 +84,7 @@ function getGlobalDir(runtime, explicitDir = null) {
}
return getOpencodeGlobalDir();
}
if (runtime === 'gemini') {
// Gemini: --config-dir > GEMINI_CONFIG_DIR > ~/.gemini
if (explicitDir) {
@@ -99,14 +95,7 @@ function getGlobalDir(runtime, explicitDir = null) {
}
return path.join(os.homedir(), '.gemini');
}
if (runtime === 'vibe') {
if (process.env.VIBE_HOME) {
return expandTilde(process.env.VIBE_HOME);
}
return path.join(os.homedir(), '.vibe');
}
// Claude Code: --config-dir > CLAUDE_CONFIG_DIR > ~/.claude
if (explicitDir) {
return expandTilde(explicitDir);
@@ -571,7 +560,7 @@ function convertClaudeToGeminiToml(content) {
const frontmatter = content.substring(3, endIndex).trim();
const body = content.substring(endIndex + 3).trim();
// Extract description from frontmatter
let description = '';
const lines = frontmatter.split('\n');
@@ -588,9 +577,9 @@ function convertClaudeToGeminiToml(content) {
if (description) {
toml += `description = ${JSON.stringify(description)}\n`;
}
toml += `prompt = ${JSON.stringify(body)}\n`;
return toml;
}
@@ -609,7 +598,7 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
if (!fs.existsSync(srcDir)) {
return;
}
// Remove old gsd-*.md files before copying new ones
if (fs.existsSync(destDir)) {
for (const file of fs.readdirSync(destDir)) {
@@ -620,12 +609,12 @@ function copyFlattenedCommands(srcDir, destDir, prefix, pathPrefix, runtime) {
} else {
fs.mkdirSync(destDir, { recursive: true });
}
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(srcDir, entry.name);
if (entry.isDirectory()) {
// Recurse into subdirectories, adding to prefix
// e.g., commands/gsd/debug/start.md -> command/gsd-debug-start.md
@@ -764,8 +753,8 @@ function cleanupOrphanedHooks(settings) {
// Fix #330: Update statusLine if it points to old statusline.js path
if (settings.statusLine && settings.statusLine.command &&
settings.statusLine.command.includes('statusline.js') &&
!settings.statusLine.command.includes('gsd-statusline.js')) {
settings.statusLine.command.includes('statusline.js') &&
!settings.statusLine.command.includes('gsd-statusline.js')) {
// Replace old path with new path
settings.statusLine.command = settings.statusLine.command.replace(
/statusline\.js/,
@@ -886,7 +875,7 @@ function uninstall(isGlobal, runtime = 'claude') {
// Remove GSD statusline if it references our hook
if (settings.statusLine && settings.statusLine.command &&
settings.statusLine.command.includes('gsd-statusline')) {
settings.statusLine.command.includes('gsd-statusline')) {
delete settings.statusLine;
settingsModified = true;
console.log(` ${green}✓${reset} Removed GSD statusline from settings`);
@@ -1076,7 +1065,7 @@ function configureOpencodePermissions() {
const gsdPath = opencodeConfigDir === defaultConfigDir
? '~/.config/opencode/get-shit-done/*'
: `${opencodeConfigDir.replace(/\\/g, '/')}/get-shit-done/*`;
let modified = false;
// Configure read permission
@@ -1320,7 +1309,7 @@ function install(isGlobal, runtime = 'claude') {
// OpenCode: flat structure in command/ directory
const commandDir = path.join(targetDir, 'command');
fs.mkdirSync(commandDir, { recursive: true });
// Copy commands/gsd/*.md as command/gsd-*.md (flatten structure)
const gsdSrc = path.join(src, 'commands', 'gsd');
copyFlattenedCommands(gsdSrc, commandDir, 'gsd', pathPrefix, runtime);
@@ -1334,7 +1323,7 @@ function install(isGlobal, runtime = 'claude') {
// Claude Code & Gemini: nested structure in commands/ directory
const commandsDir = path.join(targetDir, 'commands');
fs.mkdirSync(commandsDir, { recursive: true });
const gsdSrc = path.join(src, 'commands', 'gsd');
const gsdDest = path.join(commandsDir, 'gsd');
copyWithPathReplacement(gsdSrc, gsdDest, pathPrefix, runtime);
@@ -1417,46 +1406,20 @@ function install(isGlobal, runtime = 'claude') {
}
// Copy hooks from dist/ (bundled with dependencies)
// Dev installs from a git clone may not have hooks/dist yet.
// In that case, build it on the fly so hooks still install.
const hooksDist = path.join(src, 'hooks', 'dist');
const hooksDir = path.join(src, 'hooks');
if (!fs.existsSync(hooksDist) && fs.existsSync(hooksDir)) {
try {
const buildScript = path.join(src, 'scripts', 'build-hooks.js');
if (fs.existsSync(buildScript)) {
require(buildScript);
}
} catch (e) {
// fall through, we'll attempt a direct copy below
}
// Fallback: copy known hooks into dist if they exist
if (!fs.existsSync(hooksDist)) {
fs.mkdirSync(hooksDist, { recursive: true });
for (const hookName of ['gsd-check-update.js', 'gsd-statusline.js']) {
const inRepoHook = path.join(hooksDir, hookName);
if (fs.existsSync(inRepoHook)) {
fs.copyFileSync(inRepoHook, path.join(hooksDist, hookName));
}
}
}
}
if (fs.existsSync(hooksDist)) {
const hooksSrc = path.join(src, 'hooks', 'dist');
if (fs.existsSync(hooksSrc)) {
const hooksDest = path.join(targetDir, 'hooks');
fs.mkdirSync(hooksDest, { recursive: true });
const hookEntries = fs.readdirSync(hooksDist);
const hookEntries = fs.readdirSync(hooksSrc);
for (const entry of hookEntries) {
const srcFile = path.join(hooksDist, entry);
const srcFile = path.join(hooksSrc, entry);
if (fs.statSync(srcFile).isFile()) {
const destFile = path.join(hooksDest, entry);
fs.copyFileSync(srcFile, destFile);
}
}
if (verifyInstalled(hooksDest, 'hooks')) {
console.log(` ${green}✓${reset} Installed hooks`);
console.log(` ${green}✓${reset} Installed hooks (bundled)`);
} else {
failures.push('hooks');
}
@@ -1575,17 +1538,6 @@ function handleStatusline(settings, isInteractive, callback) {
return;
}
// Auto-migrate renamed GSD statusline (hooks/statusline.js -> hooks/gsd-statusline.js)
// Only migrate if it looks like GSD's statusline (node command with hooks/statusline.js path)
const existingCmd = settings.statusLine.command || '';
const isOldGsdStatusline = existingCmd.includes('node') &&
(existingCmd.includes('hooks/statusline.js') || existingCmd.includes('hooks\\statusline.js'));
if (isOldGsdStatusline) {
console.log(` ${green}✓${reset} Migrating statusline.js → gsd-statusline.js`);
callback(true);
return;
}
if (!isInteractive) {
console.log(` ${yellow}⚠${reset} Skipping statusline (already configured)`);
console.log(` Use ${cyan}--force-statusline${reset} to replace\n`);
@@ -1593,7 +1545,7 @@ function handleStatusline(settings, isInteractive, callback) {
return;
}
const displayCmd = settings.statusLine.command || settings.statusLine.url || '(custom)';
const existingCmd = settings.statusLine.command || settings.statusLine.url || '(custom)';
const rl = readline.createInterface({
input: process.stdin,
@@ -1603,7 +1555,7 @@ function handleStatusline(settings, isInteractive, callback) {
console.log(`
${yellow}⚠${reset} Existing statusline detected\n
Your current statusline:
${dim}command: ${displayCmd}${reset}
${dim}command: ${existingCmd}${reset}
GSD includes a statusline showing:
• Model name
@@ -1644,7 +1596,6 @@ function promptRuntime(callback) {
${cyan}2${reset}) OpenCode ${dim}(~/.config/opencode)${reset} - open source, free models
${cyan}3${reset}) Gemini ${dim}(~/.gemini)${reset}
${cyan}4${reset}) All
${cyan}5${reset}) Mistral Vibe
`);
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
@@ -1652,13 +1603,11 @@ function promptRuntime(callback) {
rl.close();
const choice = answer.trim() || '1';
if (choice === '4') {
callback(['claude', 'opencode', 'gemini', 'vibe']);
callback(['claude', 'opencode', 'gemini']);
} else if (choice === '3') {
callback(['gemini']);
} else if (choice === '2') {
callback(['opencode']);
} else if (choice === '5') {
callback(['vibe']);
} else {
callback(['claude']);
}
@@ -1717,15 +1666,6 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
const results = [];
for (const runtime of runtimes) {
if (runtime === 'vibe') {
try {
const result = installVibe();
results.push(result);
} catch (e) {
console.error('Vibe installation failed', e);
}
continue;
}
const result = install(isGlobal, runtime);
results.push(result);
}
@@ -1736,30 +1676,28 @@ function installAllRuntimes(runtimes, isGlobal, isInteractive) {
// Logic: if both are present, ask once if interactive? Or ask for each?
// Simpler: Ask once and apply to both if applicable.
if (claudeResult || geminiResult) {
// Use whichever settings exist to check for existing statusline
const primaryResult = claudeResult || geminiResult;
handleStatusline(primaryResult.settings, isInteractive, (shouldInstallStatusline) => {
if (claudeResult) {
finishInstall(claudeResult.settingsPath, claudeResult.settings, claudeResult.statuslineCommand, shouldInstallStatusline, 'claude');
}
if (geminiResult) {
finishInstall(geminiResult.settingsPath, geminiResult.settings, geminiResult.statuslineCommand, shouldInstallStatusline, 'gemini');
finishInstall(geminiResult.settingsPath, geminiResult.settings, geminiResult.statuslineCommand, shouldInstallStatusline, 'gemini');
}
const opencodeResult = results.find(r => r.runtime === 'opencode');
if (opencodeResult) {
finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
}
});
} else {
// Only OpenCode or Vibe
const opencodeResult = results.find(r => r.runtime === 'opencode');
if (opencodeResult) {
finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
}
// Only OpenCode
const opencodeResult = results[0];
finishInstall(opencodeResult.settingsPath, opencodeResult.settings, opencodeResult.statuslineCommand, false, 'opencode');
}
}

View File

@@ -151,15 +151,6 @@ Task(
)
```
## 6. Close Debug Session
After fix is complete (or user chooses "Plan fix" / "Manual fix"), move debug file to resolved if the agent didn't already:
```bash
mkdir -p .planning/debug/resolved
mv .planning/debug/{slug}.md .planning/debug/resolved/ 2>/dev/null
```
</process>
<success_criteria>
@@ -168,5 +159,4 @@ mv .planning/debug/{slug}.md .planning/debug/resolved/ 2>/dev/null
- [ ] gsd-debugger spawned with context
- [ ] Checkpoints handled correctly
- [ ] Root cause confirmed before fixing
- [ ] Resolved sessions moved to .planning/debug/resolved/
</success_criteria>

View File

@@ -14,9 +14,9 @@ allowed-tools:
<objective>
Validate built features through conversational testing with persistent state.
Purpose: Confirm what Claude built actually works from user's perspective. One test at a time, plain text responses, no interrogation. When issues are found, record them in UAT.md and route user to /gsd:plan-phase --gaps in a fresh context.
Purpose: Confirm what Claude built actually works from user's perspective. One test at a time, plain text responses, no interrogation. When issues are found, automatically diagnose, plan fixes, and prepare for execution.
Output: {phase}-UAT.md tracking all test results. If issues found: structured gaps ready for /gsd:plan-phase --gaps
Output: {phase}-UAT.md tracking all test results. If issues found: diagnosed gaps, verified fix plans ready for /gsd:execute-phase
</objective>
<execution_context>
@@ -35,5 +35,5 @@ Phase: $ARGUMENTS (optional)
<process>
Execute the verify-work workflow from @~/.claude/get-shit-done/workflows/verify-work.md end-to-end.
Preserve all workflow gates (session management, test presentation, gap recording, routing).
Preserve all workflow gates (session management, test presentation, diagnosis, fix planning, routing).
</process>

View File

@@ -51,8 +51,7 @@
* progress [json|table|bar] Render progress in various formats
*
* Todos:
* todo start <filename> Move todo from pending to in-progress
* todo complete <filename> Move todo from in-progress (or pending) to done
* todo complete <filename> Move todo from pending to completed
*
* Scaffolding:
* scaffold context --phase <N> Create CONTEXT.md template
@@ -161,7 +160,6 @@ function loadConfig(cwd) {
model_profile: 'balanced',
commit_docs: true,
search_gitignored: false,
git_tag: true,
branching_strategy: 'none',
phase_branch_template: 'gsd/phase-{phase}-{slug}',
milestone_branch_template: 'gsd/{milestone}-{slug}',
@@ -195,7 +193,6 @@ function loadConfig(cwd) {
model_profile: get('model_profile') ?? defaults.model_profile,
commit_docs: get('commit_docs', { section: 'planning', field: 'commit_docs' }) ?? defaults.commit_docs,
search_gitignored: get('search_gitignored', { section: 'planning', field: 'search_gitignored' }) ?? defaults.search_gitignored,
git_tag: get('git_tag', { section: 'planning', field: 'git_tag' }) ?? defaults.git_tag,
branching_strategy: get('branching_strategy', { section: 'git', field: 'branching_strategy' }) ?? defaults.branching_strategy,
phase_branch_template: get('phase_branch_template', { section: 'git', field: 'phase_branch_template' }) ?? defaults.phase_branch_template,
milestone_branch_template: get('milestone_branch_template', { section: 'git', field: 'milestone_branch_template' }) ?? defaults.milestone_branch_template,
@@ -518,12 +515,9 @@ function cmdCurrentTimestamp(format, raw) {
function cmdListTodos(cwd, area, raw) {
const pendingDir = path.join(cwd, '.planning', 'todos', 'pending');
const inProgressDir = path.join(cwd, '.planning', 'todos', 'in-progress');
let count = 0;
const todos = [];
let inProgressCount = 0;
const inProgressTodos = [];
try {
const files = fs.readdirSync(pendingDir).filter(f => f.endsWith('.md'));
@@ -552,35 +546,7 @@ function cmdListTodos(cwd, area, raw) {
}
} catch {}
try {
const files = fs.readdirSync(inProgressDir).filter(f => f.endsWith('.md'));
for (const file of files) {
try {
const content = fs.readFileSync(path.join(inProgressDir, file), 'utf-8');
const createdMatch = content.match(/^created:\s*(.+)$/m);
const titleMatch = content.match(/^title:\s*(.+)$/m);
const areaMatch = content.match(/^area:\s*(.+)$/m);
const startedMatch = content.match(/^started:\s*(.+)$/m);
const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
if (area && todoArea !== area) continue;
inProgressCount++;
inProgressTodos.push({
file,
created: createdMatch ? createdMatch[1].trim() : 'unknown',
started: startedMatch ? startedMatch[1].trim() : 'unknown',
title: titleMatch ? titleMatch[1].trim() : 'Untitled',
area: todoArea,
path: path.join('.planning', 'todos', 'in-progress', file),
});
} catch {}
}
} catch {}
const result = { count, todos, in_progress_count: inProgressCount, in_progress_todos: inProgressTodos };
const result = { count, todos };
output(result, raw, count.toString());
}
@@ -632,7 +598,6 @@ function cmdConfigEnsureSection(cwd, raw) {
model_profile: 'balanced',
commit_docs: true,
search_gitignored: false,
git_tag: true,
branching_strategy: 'none',
phase_branch_template: 'gsd/phase-{phase}-{slug}',
milestone_branch_template: 'gsd/{milestone}-{slug}',
@@ -3418,35 +3383,6 @@ function cmdProgressRender(cwd, format, raw) {
}
}
// ─── Todo Start ──────────────────────────────────────────────────────────────
function cmdTodoStart(cwd, filename, raw) {
if (!filename) {
error('filename required for todo start');
}
const pendingDir = path.join(cwd, '.planning', 'todos', 'pending');
const inProgressDir = path.join(cwd, '.planning', 'todos', 'in-progress');
const sourcePath = path.join(pendingDir, filename);
if (!fs.existsSync(sourcePath)) {
error(`Todo not found: ${filename}`);
}
// Ensure in-progress directory exists
fs.mkdirSync(inProgressDir, { recursive: true });
// Read, add started timestamp, move
let content = fs.readFileSync(sourcePath, 'utf-8');
const today = new Date().toISOString().split('T')[0];
content = `started: ${today}\n` + content;
fs.writeFileSync(path.join(inProgressDir, filename), content, 'utf-8');
fs.unlinkSync(sourcePath);
output({ started: true, file: filename, date: today }, raw, 'started');
}
// ─── Todo Complete ────────────────────────────────────────────────────────────
function cmdTodoComplete(cwd, filename, raw) {
@@ -3454,28 +3390,23 @@ function cmdTodoComplete(cwd, filename, raw) {
error('filename required for todo complete');
}
const inProgressDir = path.join(cwd, '.planning', 'todos', 'in-progress');
const pendingDir = path.join(cwd, '.planning', 'todos', 'pending');
const doneDir = path.join(cwd, '.planning', 'todos', 'done');
const completedDir = path.join(cwd, '.planning', 'todos', 'completed');
const sourcePath = path.join(pendingDir, filename);
// Check in-progress first, then fall back to pending
let sourcePath = path.join(inProgressDir, filename);
if (!fs.existsSync(sourcePath)) {
sourcePath = path.join(pendingDir, filename);
if (!fs.existsSync(sourcePath)) {
error(`Todo not found: ${filename}`);
}
error(`Todo not found: ${filename}`);
}
// Ensure done directory exists
fs.mkdirSync(doneDir, { recursive: true });
// Ensure completed directory exists
fs.mkdirSync(completedDir, { recursive: true });
// Read, add completion timestamp, move
let content = fs.readFileSync(sourcePath, 'utf-8');
const today = new Date().toISOString().split('T')[0];
content = `completed: ${today}\n` + content;
fs.writeFileSync(path.join(doneDir, filename), content, 'utf-8');
fs.writeFileSync(path.join(completedDir, filename), content, 'utf-8');
fs.unlinkSync(sourcePath);
output({ completed: true, file: filename, date: today }, raw, 'completed');
@@ -3721,7 +3652,6 @@ function cmdInitPlanPhase(cwd, phase, includes, raw) {
research_enabled: config.research,
plan_checker_enabled: config.plan_checker,
commit_docs: config.commit_docs,
branching_strategy: config.branching_strategy,
// Phase info
phase_found: !!phaseInfo,
@@ -3731,17 +3661,6 @@ function cmdInitPlanPhase(cwd, phase, includes, raw) {
phase_slug: phaseInfo?.phase_slug || null,
padded_phase: phaseInfo?.phase_number?.padStart(2, '0') || null,
// Branch name (pre-computed) — ensures early branching before first commit
branch_name: config.branching_strategy === 'phase' && phaseInfo
? config.phase_branch_template
.replace('{phase}', phaseInfo.phase_number)
.replace('{slug}', phaseInfo.phase_slug || 'phase')
: config.branching_strategy === 'milestone'
? config.milestone_branch_template
.replace('{milestone}', getMilestoneInfo(cwd).version)
.replace('{slug}', generateSlugInternal(getMilestoneInfo(cwd).name) || 'milestone')
: null,
// Existing artifacts
has_research: phaseInfo?.has_research || false,
has_context: phaseInfo?.has_context || false,
@@ -4000,13 +3919,11 @@ function cmdInitVerifyWork(cwd, phase, raw) {
function cmdInitPhaseOp(cwd, phase, raw) {
const config = loadConfig(cwd);
const phaseInfo = findPhaseInternal(cwd, phase);
const milestone = getMilestoneInfo(cwd);
const result = {
// Config
commit_docs: config.commit_docs,
brave_search: config.brave_search,
branching_strategy: config.branching_strategy,
// Phase info
phase_found: !!phaseInfo,
@@ -4016,17 +3933,6 @@ function cmdInitPhaseOp(cwd, phase, raw) {
phase_slug: phaseInfo?.phase_slug || null,
padded_phase: phaseInfo?.phase_number?.padStart(2, '0') || null,
// Branch name (pre-computed) — ensures early branching before first commit
branch_name: config.branching_strategy === 'phase' && phaseInfo
? config.phase_branch_template
.replace('{phase}', phaseInfo.phase_number)
.replace('{slug}', phaseInfo.phase_slug || 'phase')
: config.branching_strategy === 'milestone'
? config.milestone_branch_template
.replace('{milestone}', milestone.version)
.replace('{slug}', generateSlugInternal(milestone.name) || 'milestone')
: null,
// Existing artifacts
has_research: phaseInfo?.has_research || false,
has_context: phaseInfo?.has_context || false,
@@ -4046,7 +3952,7 @@ function cmdInitTodos(cwd, area, raw) {
const config = loadConfig(cwd);
const now = new Date();
// List pending todos
// List todos (reuse existing logic)
const pendingDir = path.join(cwd, '.planning', 'todos', 'pending');
let count = 0;
const todos = [];
@@ -4075,37 +3981,6 @@ function cmdInitTodos(cwd, area, raw) {
}
} catch {}
// List in-progress todos
const inProgressDir = path.join(cwd, '.planning', 'todos', 'in-progress');
let inProgressCount = 0;
const inProgressTodos = [];
try {
const files = fs.readdirSync(inProgressDir).filter(f => f.endsWith('.md'));
for (const file of files) {
try {
const content = fs.readFileSync(path.join(inProgressDir, file), 'utf-8');
const createdMatch = content.match(/^created:\s*(.+)$/m);
const titleMatch = content.match(/^title:\s*(.+)$/m);
const areaMatch = content.match(/^area:\s*(.+)$/m);
const startedMatch = content.match(/^started:\s*(.+)$/m);
const todoArea = areaMatch ? areaMatch[1].trim() : 'general';
if (area && todoArea !== area) continue;
inProgressCount++;
inProgressTodos.push({
file,
created: createdMatch ? createdMatch[1].trim() : 'unknown',
started: startedMatch ? startedMatch[1].trim() : 'unknown',
title: titleMatch ? titleMatch[1].trim() : 'Untitled',
area: todoArea,
path: path.join('.planning', 'todos', 'in-progress', file),
});
} catch {}
}
} catch {}
const result = {
// Config
commit_docs: config.commit_docs,
@@ -4117,20 +3992,16 @@ function cmdInitTodos(cwd, area, raw) {
// Todo inventory
todo_count: count,
todos,
in_progress_count: inProgressCount,
in_progress_todos: inProgressTodos,
area_filter: area || null,
// Paths
pending_dir: '.planning/todos/pending',
in_progress_dir: '.planning/todos/in-progress',
done_dir: '.planning/todos/done',
completed_dir: '.planning/todos/completed',
// File existence
planning_exists: pathExistsInternal(cwd, '.planning'),
todos_dir_exists: pathExistsInternal(cwd, '.planning/todos'),
pending_dir_exists: pathExistsInternal(cwd, '.planning/todos/pending'),
in_progress_dir_exists: pathExistsInternal(cwd, '.planning/todos/in-progress'),
};
output(result, raw);
@@ -4623,12 +4494,10 @@ async function main() {
case 'todo': {
const subcommand = args[1];
if (subcommand === 'start') {
cmdTodoStart(cwd, args[2], raw);
} else if (subcommand === 'complete') {
if (subcommand === 'complete') {
cmdTodoComplete(cwd, args[2], raw);
} else {
error('Unknown todo subcommand. Available: start, complete');
error('Unknown todo subcommand. Available: complete');
}
break;
}

View File

@@ -1885,60 +1885,6 @@ describe('progress command', () => {
});
});
// ─────────────────────────────────────────────────────────────────────────────
// todo start command
// ─────────────────────────────────────────────────────────────────────────────
describe('todo start command', () => {
let tmpDir;
beforeEach(() => {
tmpDir = createTempProject();
});
afterEach(() => {
cleanup(tmpDir);
});
test('moves todo from pending to in-progress', () => {
const pendingDir = path.join(tmpDir, '.planning', 'todos', 'pending');
fs.mkdirSync(pendingDir, { recursive: true });
fs.writeFileSync(
path.join(pendingDir, 'add-dark-mode.md'),
`title: Add dark mode\narea: ui\ncreated: 2025-01-01\n`
);
const result = runGsdTools('todo start add-dark-mode.md', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.started, true);
// Verify moved
assert.ok(
!fs.existsSync(path.join(tmpDir, '.planning', 'todos', 'pending', 'add-dark-mode.md')),
'should be removed from pending'
);
assert.ok(
fs.existsSync(path.join(tmpDir, '.planning', 'todos', 'in-progress', 'add-dark-mode.md')),
'should be in in-progress'
);
// Verify started timestamp added
const content = fs.readFileSync(
path.join(tmpDir, '.planning', 'todos', 'in-progress', 'add-dark-mode.md'),
'utf-8'
);
assert.ok(content.startsWith('started:'), 'should have started timestamp');
});
test('fails for nonexistent todo', () => {
const result = runGsdTools('todo start nonexistent.md', tmpDir);
assert.ok(!result.success, 'should fail');
assert.ok(result.error.includes('not found'), 'error mentions not found');
});
});
// ─────────────────────────────────────────────────────────────────────────────
// todo complete command
// ─────────────────────────────────────────────────────────────────────────────
@@ -1954,12 +1900,12 @@ describe('todo complete command', () => {
cleanup(tmpDir);
});
test('completes todo from in-progress', () => {
const inProgressDir = path.join(tmpDir, '.planning', 'todos', 'in-progress');
fs.mkdirSync(inProgressDir, { recursive: true });
test('moves todo from pending to completed', () => {
const pendingDir = path.join(tmpDir, '.planning', 'todos', 'pending');
fs.mkdirSync(pendingDir, { recursive: true });
fs.writeFileSync(
path.join(inProgressDir, 'add-dark-mode.md'),
`started: 2025-01-01\ntitle: Add dark mode\narea: ui\ncreated: 2025-01-01\n`
path.join(pendingDir, 'add-dark-mode.md'),
`title: Add dark mode\narea: ui\ncreated: 2025-01-01\n`
);
const result = runGsdTools('todo complete add-dark-mode.md', tmpDir);
@@ -1970,47 +1916,22 @@ describe('todo complete command', () => {
// Verify moved
assert.ok(
!fs.existsSync(path.join(tmpDir, '.planning', 'todos', 'in-progress', 'add-dark-mode.md')),
'should be removed from in-progress'
!fs.existsSync(path.join(tmpDir, '.planning', 'todos', 'pending', 'add-dark-mode.md')),
'should be removed from pending'
);
assert.ok(
fs.existsSync(path.join(tmpDir, '.planning', 'todos', 'done', 'add-dark-mode.md')),
'should be in done'
fs.existsSync(path.join(tmpDir, '.planning', 'todos', 'completed', 'add-dark-mode.md')),
'should be in completed'
);
// Verify completion timestamp added
const content = fs.readFileSync(
path.join(tmpDir, '.planning', 'todos', 'done', 'add-dark-mode.md'),
path.join(tmpDir, '.planning', 'todos', 'completed', 'add-dark-mode.md'),
'utf-8'
);
assert.ok(content.startsWith('completed:'), 'should have completed timestamp');
});
test('completes todo from pending directly', () => {
const pendingDir = path.join(tmpDir, '.planning', 'todos', 'pending');
fs.mkdirSync(pendingDir, { recursive: true });
fs.writeFileSync(
path.join(pendingDir, 'fix-bug.md'),
`title: Fix bug\narea: api\ncreated: 2025-01-01\n`
);
const result = runGsdTools('todo complete fix-bug.md', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
const output = JSON.parse(result.output);
assert.strictEqual(output.completed, true);
// Verify moved to done (not completed)
assert.ok(
!fs.existsSync(path.join(tmpDir, '.planning', 'todos', 'pending', 'fix-bug.md')),
'should be removed from pending'
);
assert.ok(
fs.existsSync(path.join(tmpDir, '.planning', 'todos', 'done', 'fix-bug.md')),
'should be in done'
);
});
test('fails for nonexistent todo', () => {
const result = runGsdTools('todo complete nonexistent.md', tmpDir);
assert.ok(!result.success, 'should fail');

View File

@@ -6,8 +6,7 @@ Configuration options for `.planning/` directory behavior.
```json
"planning": {
"commit_docs": true,
"search_gitignored": false,
"git_tag": true
"search_gitignored": false
},
"git": {
"branching_strategy": "none",
@@ -20,7 +19,6 @@ Configuration options for `.planning/` directory behavior.
|--------|---------|-------------|
| `commit_docs` | `true` | Whether to commit planning artifacts to git |
| `search_gitignored` | `false` | Add `--no-ignore` to broad rg searches |
| `git_tag` | `true` | Create git tags on milestone completion |
| `git.branching_strategy` | `"none"` | Git branching approach: `"none"`, `"phase"`, or `"milestone"` |
| `git.phase_branch_template` | `"gsd/phase-{phase}-{slug}"` | Branch template for phase strategy |
| `git.milestone_branch_template` | `"gsd/{milestone}-{slug}"` | Branch template for milestone strategy |

View File

@@ -8,8 +8,7 @@
},
"planning": {
"commit_docs": true,
"search_gitignored": false,
"git_tag": true
"search_gitignored": false
},
"parallelization": {
"enabled": true,

View File

@@ -19,7 +19,7 @@ Extract from init JSON: `commit_docs`, `date`, `timestamp`, `todo_count`, `todos
Ensure directories exist:
```bash
mkdir -p .planning/todos/pending .planning/todos/in-progress .planning/todos/done
mkdir -p .planning/todos/pending .planning/todos/done
```
Note existing areas from the todos array for consistency in infer_area step.

View File

@@ -105,7 +105,7 @@ Use AskUserQuestion:
- header: "Action"
- question: "This todo relates to Phase [N]: [name]. What would you like to do?"
- options:
- "Work on it now" — move to in-progress, start working
- "Work on it now" — move to done, start working
- "Add to phase plan" — include when planning Phase [N]
- "Brainstorm approach" — think through before deciding
- "Put it back" — return to list
@@ -116,7 +116,7 @@ Use AskUserQuestion:
- header: "Action"
- question: "What would you like to do with this todo?"
- options:
- "Work on it now" — move to in-progress, start working
- "Work on it now" — move to done, start working
- "Create a phase" — /gsd:add-phase with this scope
- "Brainstorm approach" — think through before deciding
- "Put it back" — return to list
@@ -125,7 +125,7 @@ Use AskUserQuestion:
<step name="execute_action">
**Work on it now:**
```bash
mv ".planning/todos/pending/[filename]" ".planning/todos/in-progress/"
mv ".planning/todos/pending/[filename]" ".planning/todos/done/"
```
Update STATE.md todo count. Present problem/solution context. Begin work or ask how to proceed.
@@ -150,11 +150,11 @@ Re-run `init todos` to get updated count, then update STATE.md "### Pending Todo
</step>
<step name="git_commit">
If todo was moved to in-progress/, commit the change:
If todo was moved to done/, commit the change:
```bash
git rm --cached .planning/todos/pending/[filename] 2>/dev/null || true
node ~/.claude/get-shit-done/bin/gsd-tools.js commit "docs: start work on todo - [title]" --files .planning/todos/in-progress/[filename] .planning/STATE.md
node ~/.claude/get-shit-done/bin/gsd-tools.js commit "docs: start work on todo - [title]" --files .planning/todos/done/[filename] .planning/STATE.md
```
Tool respects `commit_docs` config and gitignore automatically.
@@ -172,5 +172,5 @@ Confirm: "Committed: docs: start work on todo - [title]"
- [ ] Appropriate actions offered
- [ ] Selected action executed
- [ ] STATE.md updated if todo count changed
- [ ] Changes committed to git (if todo moved to in-progress/)
- [ ] Changes committed to git (if todo moved to done/)
</success_criteria>

View File

@@ -531,11 +531,6 @@ fi
<step name="git_tag">
<config-check>
Read `planning.git_tag` from `.planning/config.json` (default: `true` if not set).
If `git_tag` is `false` → skip this step entirely, proceed to `git_commit_milestone`.
</config-check>
Create git tag:
```bash

View File

@@ -114,7 +114,7 @@ Phase number from argument (required).
INIT=$(node ~/.claude/get-shit-done/bin/gsd-tools.js init phase-op "${PHASE}")
```
Parse JSON for: `commit_docs`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_plans`, `has_verification`, `plan_count`, `roadmap_exists`, `planning_exists`.
Parse JSON for: `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_plans`, `has_verification`, `plan_count`, `roadmap_exists`, `planning_exists`.
**If `phase_found` is false:**
```
@@ -124,22 +124,7 @@ Use /gsd:progress to see available phases.
```
Exit workflow.
**If `phase_found` is true:** Continue to handle_branching.
</step>
<step name="handle_branching">
Check `branching_strategy` from init:
**"none":** Skip, continue on current branch.
**"phase" or "milestone":** Use pre-computed `branch_name` from init:
```bash
git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
```
This ensures all phase documentation commits (context, research, plans) go to the feature branch, not main.
Continue to check_existing.
**If `phase_found` is true:** Continue to check_existing.
</step>
<step name="check_existing">

View File

@@ -226,52 +226,94 @@ After all waves:
```
</step>
<step name="verify_phase_goal">
Verify phase achieved its GOAL, not just completed tasks.
```
Task(
prompt="Verify phase {phase_number} goal achievement.
Phase directory: {phase_dir}
Phase goal: {goal from ROADMAP.md}
Check must_haves against actual codebase. Create VERIFICATION.md.",
subagent_type="gsd-verifier",
model="{verifier_model}"
)
```
Read status:
```bash
grep "^status:" "$PHASE_DIR"/*-VERIFICATION.md | cut -d: -f2 | tr -d ' '
```
| Status | Action |
|--------|--------|
| `passed` | → update_roadmap |
| `human_needed` | Present items for human testing, get approval or feedback |
| `gaps_found` | Present gap summary, offer `/gsd:plan-phase {phase} --gaps` |
**If human_needed:**
```
## ✓ Phase {X}: {Name} — Human Verification Required
All automated checks passed. {N} items need human testing:
{From VERIFICATION.md human_verification section}
"approved" → continue | Report issues → gap closure
```
**If gaps_found:**
```
## ⚠ Phase {X}: {Name} — Gaps Found
**Score:** {N}/{M} must-haves verified
**Report:** {phase_dir}/{phase}-VERIFICATION.md
### What's Missing
{Gap summaries from VERIFICATION.md}
---
## ▶ Next Up
`/gsd:plan-phase {X} --gaps`
<sub>`/clear` first → fresh context window</sub>
Also: `cat {phase_dir}/{phase}-VERIFICATION.md` — full report
Also: `/gsd:verify-work {X}` — manual testing first
```
Gap closure cycle: `/gsd:plan-phase {X} --gaps` reads VERIFICATION.md → creates gap plans with `gap_closure: true` → user runs `/gsd:execute-phase {X} --gaps-only` → verifier re-runs.
</step>
<step name="update_roadmap">
Mark phase complete in ROADMAP.md (date, status).
```bash
node ~/.claude/get-shit-done/bin/gsd-tools.js commit "docs(phase-{X}): complete phase execution" --files .planning/ROADMAP.md .planning/STATE.md .planning/phases/{phase_dir}/*-VERIFICATION.md .planning/REQUIREMENTS.md
```
</step>
<step name="offer_next">
**Route based on execution mode:**
**If `--gaps-only` (gap closure execution):**
**If more phases:**
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► GAP FIXES EXECUTED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
## Next Up
Phase {X}: {Name} — gap closure plans executed.
**Phase {X+1}: {Name}** — {Goal}
───────────────────────────────────────────────────────────────
`/gsd:plan-phase {X+1}`
## ▶ Next Up
**Re-verify fixes** — confirm gaps are closed
`/clear` then `/gsd:verify-work {X}`
───────────────────────────────────────────────────────────────
<sub>`/clear` first for fresh context</sub>
```
**If normal execution (no flags):**
**If milestone complete:**
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► EXECUTION COMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
MILESTONE COMPLETE!
Phase {X}: {Name} — all plans executed.
All {N} phases executed.
───────────────────────────────────────────────────────────────
## ▶ Next Up
**Verify what was built** — manual acceptance testing
`/clear` then `/gsd:verify-work {X}`
───────────────────────────────────────────────────────────────
**Also available:**
- `/gsd:plan-phase {X+1}` — skip verification, plan next phase
- `/gsd:complete-milestone` — if all phases done
`/gsd:complete-milestone`
```
</step>

View File

@@ -18,25 +18,12 @@ Load all context in one call (include file contents to avoid redundant reads):
INIT=$(node ~/.claude/get-shit-done/bin/gsd-tools.js init plan-phase "$PHASE" --include state,roadmap,requirements,context,research,verification,uat)
```
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `commit_docs`, `branching_strategy`, `branch_name`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_plans`, `plan_count`, `planning_exists`, `roadmap_exists`.
Parse JSON for: `researcher_model`, `planner_model`, `checker_model`, `research_enabled`, `plan_checker_enabled`, `commit_docs`, `phase_found`, `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`, `has_research`, `has_context`, `has_plans`, `plan_count`, `planning_exists`, `roadmap_exists`.
**File contents (from --include):** `state_content`, `roadmap_content`, `requirements_content`, `context_content`, `research_content`, `verification_content`, `uat_content`. These are null if files don't exist.
**If `planning_exists` is false:** Error — run `/gsd:new-project` first.
## 1.5. Handle Branching
Check `branching_strategy` from init:
**"none":** Skip, continue on current branch.
**"phase" or "milestone":** Use pre-computed `branch_name` from init:
```bash
git checkout -b "$BRANCH_NAME" 2>/dev/null || git checkout "$BRANCH_NAME"
```
This ensures research and planning commits go to the feature branch, not main. If `/gsd:discuss-phase` already created the branch, the `git checkout` fallback switches to it.
## 2. Parse and Normalize Arguments
Extract from $ARGUMENTS: phase number (integer or decimal like `2.1`), flags (`--research`, `--skip-research`, `--gaps`, `--skip-verify`).

View File

@@ -30,7 +30,6 @@ Parse current values (default to `true` if not present):
- `workflow.verifier` — spawn verifier during execute-phase
- `model_profile` — which model each agent uses (default: `balanced`)
- `git.branching_strategy` — branching approach (default: `"none"`)
- `planning.git_tag` — create git tags on milestone completion (default: `true`)
</step>
<step name="present_settings">
@@ -84,15 +83,6 @@ AskUserQuestion([
{ label: "Per Phase", description: "Create branch for each phase (gsd/phase-{N}-{name})" },
{ label: "Per Milestone", description: "Create branch for entire milestone (gsd/{version}-{name})" }
]
},
{
question: "Create git tags on milestone completion?",
header: "Git Tagging",
multiSelect: false,
options: [
{ label: "Yes (Recommended)", description: "Tag releases with version (e.g., v1.0) on milestone completion" },
{ label: "No", description: "Skip git tagging — use if your project doesn't use tags or uses a different convention" }
]
}
])
```
@@ -110,10 +100,6 @@ Merge new settings into existing config.json:
"plan_check": true/false,
"verifier": true/false
},
"planning": {
...existing_planning,
"git_tag": true/false
},
"git": {
"branching_strategy": "none" | "phase" | "milestone"
}
@@ -138,7 +124,6 @@ Display:
| Plan Checker | {On/Off} |
| Execution Verifier | {On/Off} |
| Git Branching | {None/Per Phase/Per Milestone} |
| Git Tagging | {On/Off} |
These settings apply to future /gsd:plan-phase and /gsd:execute-phase runs.
@@ -154,7 +139,7 @@ Quick commands:
<success_criteria>
- [ ] Current config read
- [ ] User presented with 6 settings (profile + 3 workflow toggles + git branching + git tagging)
- [ ] User presented with 5 settings (profile + 3 workflow toggles + git branching)
- [ ] Config updated with model_profile, workflow, and git sections
- [ ] Changes confirmed to user
</success_criteria>

View File

@@ -311,31 +311,7 @@ Present summary:
[List from Issues section]
```
**If issues > 0:**
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► ISSUES RECORDED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
{N} issue(s) recorded in UAT.md with structured gaps.
| # | Test | Severity | Issue |
|---|------|----------|-------|
[table of issues from Gaps section]
───────────────────────────────────────────────────────────────
## ▶ Next Up
**Plan fixes** in a fresh context (saves tokens):
`/clear` then `/gsd:plan-phase {phase} --gaps`
This will read the UAT gaps and create targeted fix plans.
───────────────────────────────────────────────────────────────
```
**If issues > 0:** Proceed to `diagnose_issues`
**If issues == 0:**
```
@@ -344,6 +320,203 @@ All tests passed. Ready to continue.
- `/gsd:plan-phase {next}` — Plan next phase
- `/gsd:execute-phase {next}` — Execute next phase
```
</step>
<step name="diagnose_issues">
**Diagnose root causes before planning fixes:**
```
---
{N} issues found. Diagnosing root causes...
Spawning parallel debug agents to investigate each issue.
```
- Load diagnose-issues workflow
- Follow @~/.claude/get-shit-done/workflows/diagnose-issues.md
- Spawn parallel debug agents for each issue
- Collect root causes
- Update UAT.md with root causes
- Proceed to `plan_gap_closure`
Diagnosis runs automatically - no user prompt. Parallel agents investigate simultaneously, so overhead is minimal and fixes are more accurate.
</step>
<step name="plan_gap_closure">
**Auto-plan fixes from diagnosed gaps:**
Display:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► PLANNING FIXES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning planner for gap closure...
```
Spawn gsd-planner in --gaps mode:
```
Task(
prompt="""
<planning_context>
**Phase:** {phase_number}
**Mode:** gap_closure
**UAT with diagnoses:**
@.planning/phases/{phase_dir}/{phase}-UAT.md
**Project State:**
@.planning/STATE.md
**Roadmap:**
@.planning/ROADMAP.md
</planning_context>
<downstream_consumer>
Output consumed by /gsd:execute-phase
Plans must be executable prompts.
</downstream_consumer>
""",
subagent_type="gsd-planner",
model="{planner_model}",
description="Plan gap fixes for Phase {phase}"
)
```
On return:
- **PLANNING COMPLETE:** Proceed to `verify_gap_plans`
- **PLANNING INCONCLUSIVE:** Report and offer manual intervention
</step>
<step name="verify_gap_plans">
**Verify fix plans with checker:**
Display:
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► VERIFYING FIX PLANS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning plan checker...
```
Initialize: `iteration_count = 1`
Spawn gsd-plan-checker:
```
Task(
prompt="""
<verification_context>
**Phase:** {phase_number}
**Phase Goal:** Close diagnosed gaps from UAT
**Plans to verify:**
@.planning/phases/{phase_dir}/*-PLAN.md
</verification_context>
<expected_output>
Return one of:
- ## VERIFICATION PASSED — all checks pass
- ## ISSUES FOUND — structured issue list
</expected_output>
""",
subagent_type="gsd-plan-checker",
model="{checker_model}",
description="Verify Phase {phase} fix plans"
)
```
On return:
- **VERIFICATION PASSED:** Proceed to `present_ready`
- **ISSUES FOUND:** Proceed to `revision_loop`
</step>
<step name="revision_loop">
**Iterate planner ↔ checker until plans pass (max 3):**
**If iteration_count < 3:**
Display: `Sending back to planner for revision... (iteration {N}/3)`
Spawn gsd-planner with revision context:
```
Task(
prompt="""
<revision_context>
**Phase:** {phase_number}
**Mode:** revision
**Existing plans:**
@.planning/phases/{phase_dir}/*-PLAN.md
**Checker issues:**
{structured_issues_from_checker}
</revision_context>
<instructions>
Read existing PLAN.md files. Make targeted updates to address checker issues.
Do NOT replan from scratch unless issues are fundamental.
</instructions>
""",
subagent_type="gsd-planner",
model="{planner_model}",
description="Revise Phase {phase} plans"
)
```
After planner returns → spawn checker again (verify_gap_plans logic)
Increment iteration_count
**If iteration_count >= 3:**
Display: `Max iterations reached. {N} issues remain.`
Offer options:
1. Force proceed (execute despite issues)
2. Provide guidance (user gives direction, retry)
3. Abandon (exit, user runs /gsd:plan-phase manually)
Wait for user response.
</step>
<step name="present_ready">
**Present completion and next steps:**
```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GSD ► FIXES READY ✓
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
**Phase {X}: {Name}** — {N} gap(s) diagnosed, {M} fix plan(s) created
| Gap | Root Cause | Fix Plan |
|-----|------------|----------|
| {truth 1} | {root_cause} | {phase}-04 |
| {truth 2} | {root_cause} | {phase}-04 |
Plans verified and ready for execution.
───────────────────────────────────────────────────────────────
## ▶ Next Up
**Execute fixes** — run fix plans
`/clear` then `/gsd:execute-phase {phase} --gaps-only`
───────────────────────────────────────────────────────────────
```
</step>
</process>
@@ -389,7 +562,9 @@ Default to **major** if unclear. User can correct if needed.
- [ ] Severity inferred from description (never asked)
- [ ] Batched writes: on issue, every 5 passes, or completion
- [ ] Committed on completion
- [ ] If issues: gaps recorded in structured YAML in UAT.md
- [ ] If issues: user routed to `/gsd:plan-phase {N} --gaps` in fresh context
- [ ] If no issues: user routed to next phase
- [ ] If issues: parallel debug agents diagnose root causes
- [ ] If issues: gsd-planner creates fix plans (gap_closure mode)
- [ ] If issues: gsd-plan-checker verifies fix plans
- [ ] If issues: revision loop until plans pass (max 3 iterations)
- [ ] Ready for `/gsd:execute-phase --gaps-only` when complete
</success_criteria>

View File

@@ -1,71 +0,0 @@
Research: gsd-verifier Missing Write Tool — Root Cause Analysis
Finding: Day-One Bug, NOT a v1.17.0 → v1.18.0 Regression
The Write tool was never present in gsd-verifier.md. The diff between v1.17.0 and v1.18.0 shows zero changes to this file. Both versions have identical frontmatter:
tools: Read, Bash, Grep, Glob # Write missing since initial creation
Timeline
Date: Jan 15, 2026
Commit: f3f6707
What Happened: gsd-verifier.md created — tools: Read, Bash, Grep, Glob, model: sonnet. Write was never included.
────────────────────────────────────────
Date: Jan 15, 2026
Commit: 8e6ad96→a7986bc→f9edfcf
What Happened: Formatting, re-verification loop, color changes. Tools line unchanged.
────────────────────────────────────────
Date: Jan 30, 2026
Commit: 5660b6f
What Happened: Gemini CLI compatibility fix. Tools line unchanged.
────────────────────────────────────────
Date: Feb 7-8, 2026
Commit: d44c7dc→1b317de→6a2d1f1
What Happened: gsd-tools integration refactor. model: sonnet removed from frontmatter, but tools line still unchanged.
The bug has existed through every release since the verifier was introduced.
Contradiction in the Agent Definition
The frontmatter says tools: Read, Bash, Grep, Glob (no Write), but:
- The description says: "Creates VERIFICATION.md report"
- The output section instructs: "Create .planning/phases/{phase_dir}/{phase}-VERIFICATION.md"
Without Write, the agent falls back to Bash with heredoc (cat > file << 'EOF'), which works functionally but causes the permission corruption we saw.
Comparison with Other Agents (v1.18.0)
┌─────────────────────────┬────────────────────────────────────────────────┬──────────────────────────────┬──────────────┐
│ Agent │ Tools │ Writes Files? │ Has Write? │
├─────────────────────────┼────────────────────────────────────────────────┼──────────────────────────────┼──────────────┤
│ gsd-executor │ Read, Write, Edit, Bash, Grep, Glob │ Yes (code, summaries) │ Yes │
├─────────────────────────┼────────────────────────────────────────────────┼──────────────────────────────┼──────────────┤
│ gsd-planner │ Read, Write, Bash, Glob, Grep, WebFetch │ Yes (PLAN.md) │ Yes │
├─────────────────────────┼────────────────────────────────────────────────┼──────────────────────────────┼──────────────┤
│ gsd-roadmapper │ Read, Write, Bash, Glob, Grep │ Yes (ROADMAP.md) │ Yes │
├─────────────────────────┼────────────────────────────────────────────────┼──────────────────────────────┼──────────────┤
│ gsd-debugger │ Read, Write, Edit, Bash, Grep, Glob, WebSearch │ Yes (debug reports) │ Yes │
├─────────────────────────┼────────────────────────────────────────────────┼──────────────────────────────┼──────────────┤
│ gsd-codebase-mapper │ Read, Bash, Grep, Glob, Write │ Yes (analysis docs) │ Yes │
├─────────────────────────┼────────────────────────────────────────────────┼──────────────────────────────┼──────────────┤
│ gsd-verifier │ Read, Bash, Grep, Glob │ Yes (VERIFICATION.md) │ NO │
├─────────────────────────┼────────────────────────────────────────────────┼──────────────────────────────┼──────────────┤
│ gsd-plan-checker │ Read, Bash, Glob, Grep │ No (returns to orchestrator) │ No (correct) │
├─────────────────────────┼────────────────────────────────────────────────┼──────────────────────────────┼──────────────┤
│ gsd-integration-checker │ Read, Bash, Grep, Glob │ Borderline (returns report) │ No │
└─────────────────────────┴────────────────────────────────────────────────┴──────────────────────────────┴──────────────┘
Every agent that creates files has Write — except gsd-verifier. This is clearly an oversight.
Recommended Action
File an issue at https://github.com/gsd-build/get-shit-done with:
Title: gsd-verifier agent missing Write tool — falls back to Bash heredoc, corrupts settings.local.json
Body:
- gsd-verifier.md frontmatter has tools: Read, Bash, Grep, Glob but its instructions tell it to create VERIFICATION.md files
- Without Write, it uses Bash(cat > path << 'EOF' ... EOF) which, when user approves, saves the entire 200+ line markdown report as a permission entry in settings.local.json
- This corrupts settings.local.json and causes a "Settings Error" on every Claude Code startup
- Every other file-creating agent (executor, planner, roadmapper, debugger, codebase-mapper) includes Write
- Fix: change line 4 to tools: Read, Write, Bash, Grep, Glob
- Bug exists since initial creation (commit f3f6707, Jan 15, 2026), not a regression
Our local patch (~/.claude/agents/gsd-verifier.md line 4) adds Write but will be overwritten by /gsd:update.

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env node
// Claude Code Statusline - GSD Edition
// Shows: model | current task | git branch | directory | context usage
// Shows: model | current task | directory | context usage
const fs = require('fs');
const path = require('path');
@@ -78,54 +78,12 @@ process.stdin.on('end', () => {
} catch (e) {}
}
// Git branch (traverse up to find .git, supports worktrees)
let branch = '';
let currentPath = dir;
while (currentPath) {
const gitPath = path.join(currentPath, '.git');
if (fs.existsSync(gitPath)) {
try {
let gitDir = gitPath;
const stats = fs.statSync(gitPath);
// If .git is a file (worktree), read the gitdir path
if (stats.isFile()) {
const gitFileContent = fs.readFileSync(gitPath, 'utf8');
const gitDirMatch = gitFileContent.match(/gitdir:\s*(.+)/);
if (gitDirMatch) {
gitDir = gitDirMatch[1].trim();
// Handle relative paths in worktree .git files
if (!path.isAbsolute(gitDir)) {
gitDir = path.resolve(currentPath, gitDir);
}
}
}
// Read HEAD from the actual git directory
const headFile = path.join(gitDir, 'HEAD');
if (fs.existsSync(headFile)) {
const headContent = fs.readFileSync(headFile, 'utf8');
const match = headContent.match(/ref: refs\/heads\/(.+)/);
if (match) {
branch = ` \x1b[35m${match[1].trim()}\x1b[0m │`;
}
}
} catch (e) {
// Silently fail on git errors - don't break statusline
}
break;
}
const parentPath = path.dirname(currentPath);
if (parentPath === currentPath) break;
currentPath = parentPath;
}
// Output
const dirname = path.basename(dir);
if (task) {
process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[1m${task}\x1b[0m │${branch} \x1b[2m${dirname}\x1b[0m${ctx}`);
process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[1m${task}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
} else {
process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │${branch} \x1b[2m${dirname}\x1b[0m${ctx}`);
process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
}
} catch (e) {
// Silent fail - don't break statusline on parse errors

View File

@@ -1,9 +0,0 @@
# OpenCode Installer Package
This folder contains the OpenCode-specific installer package for GSD.
- Package location: `opencode/get-shit-done-opencode`
- Install command: `npx get-shit-done-opencode`
- Default install target: `~/.config/opencode`
Use `--local` to install into `./.opencode` for a single project.

View File

@@ -1,469 +0,0 @@
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const os = require("os");
const readline = require("readline");
// Colors
const cyan = "\x1b[36m";
const green = "\x1b[32m";
const yellow = "\x1b[33m";
const dim = "\x1b[2m";
const reset = "\x1b[0m";
// Get version from package.json
const pkg = require("../package.json");
const banner = `
${cyan} ██████╗ ███████╗██████╗
██╔════╝ ██╔════╝██╔══██╗
██║ ███╗███████╗██║ ██║
██║ ██║╚════██║██║ ██║
╚██████╔╝███████║██████╔╝
╚═════╝ ╚══════╝╚═════╝${reset}
Get Shit Done ${dim}v${pkg.version}${reset}
A meta-prompting, context engineering and spec-driven
development system for OpenCode by TÂCHES.
`;
// Parse args
const args = process.argv.slice(2);
const hasGlobal = args.includes("--global") || args.includes("-g");
const hasLocal = args.includes("--local") || args.includes("-l");
// Parse --config-dir argument
function parseConfigDirArg() {
const configDirIndex = args.findIndex((arg) => arg === "--config-dir" || arg === "-c");
if (configDirIndex !== -1) {
const nextArg = args[configDirIndex + 1];
if (!nextArg || nextArg.startsWith("-")) {
console.error(` ${yellow}--config-dir requires a path argument${reset}`);
process.exit(1);
}
return nextArg;
}
const configDirArg = args.find((arg) => arg.startsWith("--config-dir=") || arg.startsWith("-c="));
if (configDirArg) {
return configDirArg.split("=")[1];
}
return null;
}
const explicitConfigDir = parseConfigDirArg();
const hasHelp = args.includes("--help") || args.includes("-h");
console.log(banner);
// Show help if requested
if (hasHelp) {
console.log(` ${yellow}Usage:${reset} npx get-shit-done-opencode [options]
${yellow}Options:${reset}
${cyan}-g, --global${reset} Install globally (to OpenCode config directory)
${cyan}-l, --local${reset} Install locally (to ./.opencode in current directory)
${cyan}-c, --config-dir <path>${reset} Specify custom OpenCode config directory
${cyan}-h, --help${reset} Show this help message
${yellow}Examples:${reset}
${dim}# Install to default ~/.config/opencode directory${reset}
npx get-shit-done-opencode --global
${dim}# Install to custom config directory${reset}
npx get-shit-done-opencode --global --config-dir ~/.config/opencode-alt
${dim}# Install to current project only${reset}
npx get-shit-done-opencode --local
`);
process.exit(0);
}
/**
* Expand ~ to home directory
*/
function expandTilde(filePath) {
if (filePath && filePath.startsWith("~/")) {
return path.join(os.homedir(), filePath.slice(2));
}
return filePath;
}
function resolveGsdRoot() {
try {
const gsdPkg = require.resolve("get-shit-done-cc/package.json");
return path.dirname(gsdPkg);
} catch (e) {
const localRoot = path.join(__dirname, "..", "..", "..");
if (fs.existsSync(path.join(localRoot, "commands"))) return localRoot;
}
console.error(` ${yellow}✗${reset} Failed to locate get-shit-done-cc assets`);
process.exit(1);
}
function transformHook(content) {
return content
.replace(/get-shit-done-cc/g, "get-shit-done-opencode")
.replace(/['"]\\.claude['"]/g, "'.config/opencode'");
}
function colorToHex(value) {
const normalized = value.trim().toLowerCase();
const map = {
green: "#22c55e",
blue: "#3b82f6",
purple: "#8b5cf6",
cyan: "#06b6d4",
yellow: "#f59e0b",
orange: "#f97316",
};
if (normalized.startsWith("#")) return normalized;
return map[normalized] || normalized;
}
function toolsLineToBlock(line) {
const raw = line.split(":").slice(1).join(":").trim();
if (!raw) return [line];
const tools = raw
.split(",")
.map((item) => item.trim())
.filter(Boolean)
.map((item) => item.toLowerCase());
const entries = tools.map((tool) => {
const key = /^[a-z0-9_]+$/.test(tool) ? tool : `"${tool}"`;
return ` ${key}: true`;
});
return ["tools:", ...entries];
}
function transformMarkdown(content, pathPrefix) {
return content
.replace(/~\/\.claude\//g, pathPrefix)
.replace(/\bgsd:([a-z0-9-]+)/g, "gsd/$1")
.replace(/npx get-shit-done-cc/g, "npx get-shit-done-opencode");
}
function transformAgentMarkdown(content, pathPrefix) {
const updated = transformMarkdown(content, pathPrefix);
const match = updated.match(/^---\r?\n([\s\S]*?)\r?\n---/);
if (!match) return updated;
const lines = match[1].split(/\r?\n/);
const out = [];
for (const line of lines) {
if (line.startsWith("tools:")) {
out.push(...toolsLineToBlock(line));
continue;
}
if (line.startsWith("color:")) {
const value = line.split(":").slice(1).join(":").trim();
const colorValue = colorToHex(value);
out.push(`color: "${colorValue}"`);
continue;
}
out.push(line);
}
return updated.replace(match[0], `---\n${out.join("\n")}\n---`);
}
/**
* Recursively copy directory, transforming markdown files
*/
function copyWithTransform(srcDir, destDir, pathPrefix) {
if (fs.existsSync(destDir)) {
fs.rmSync(destDir, { recursive: true });
}
fs.mkdirSync(destDir, { recursive: true });
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(srcDir, entry.name);
const destPath = path.join(destDir, entry.name);
if (entry.isDirectory()) {
copyWithTransform(srcPath, destPath, pathPrefix);
} else if (entry.name.endsWith(".md")) {
let content = fs.readFileSync(srcPath, "utf8");
content = transformMarkdown(content, pathPrefix);
fs.writeFileSync(destPath, content);
} else {
fs.copyFileSync(srcPath, destPath);
}
}
}
function copyCommands(srcDir, destRoot, pathPrefix) {
const destDir = path.join(destRoot, "commands", "gsd");
if (fs.existsSync(destDir)) {
fs.rmSync(destDir, { recursive: true });
}
fs.mkdirSync(destDir, { recursive: true });
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
for (const entry of entries) {
if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
const srcPath = path.join(srcDir, entry.name);
const destPath = path.join(destDir, entry.name);
let content = fs.readFileSync(srcPath, "utf8");
content = transformMarkdown(content, pathPrefix);
fs.writeFileSync(destPath, content);
}
}
/**
* Verify a directory exists and contains files
*/
function verifyInstalled(dirPath, description) {
if (!fs.existsSync(dirPath)) {
console.error(` ${yellow}✗${reset} Failed to install ${description}: directory not created`);
return false;
}
try {
const entries = fs.readdirSync(dirPath);
if (entries.length === 0) {
console.error(` ${yellow}✗${reset} Failed to install ${description}: directory is empty`);
return false;
}
} catch (e) {
console.error(` ${yellow}✗${reset} Failed to install ${description}: ${e.message}`);
return false;
}
return true;
}
/**
* Verify a file exists
*/
function verifyFileInstalled(filePath, description) {
if (!fs.existsSync(filePath)) {
console.error(` ${yellow}✗${reset} Failed to install ${description}: file not created`);
return false;
}
return true;
}
function readConfig(configPath) {
if (!fs.existsSync(configPath)) return {};
try {
return JSON.parse(fs.readFileSync(configPath, "utf8"));
} catch (e) {
return {};
}
}
function writeConfig(configPath, config) {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
}
/**
* Install to the specified directory
*/
function install(isGlobal) {
const src = resolveGsdRoot();
const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.OPENCODE_CONFIG_DIR);
const defaultGlobalDir = configDir || path.join(os.homedir(), ".config", "opencode");
const opencodeDir = isGlobal ? defaultGlobalDir : path.join(process.cwd(), ".opencode");
const locationLabel = isGlobal
? opencodeDir.replace(os.homedir(), "~")
: opencodeDir.replace(process.cwd(), ".");
const pathPrefix = isGlobal ? (configDir ? `${opencodeDir}/` : "~/.config/opencode/") : "./.opencode/";
console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
const failures = [];
// Create commands directory and copy commands
const gsdCommandsSrc = path.join(src, "commands", "gsd");
if (fs.existsSync(gsdCommandsSrc)) {
copyCommands(gsdCommandsSrc, opencodeDir, pathPrefix);
const gsdCommandsDest = path.join(opencodeDir, "commands", "gsd");
if (verifyInstalled(gsdCommandsDest, "commands/gsd")) {
console.log(` ${green}✓${reset} Installed commands/gsd`);
} else {
failures.push("commands/gsd");
}
} else {
failures.push("commands/gsd");
}
// Copy get-shit-done assets with path replacement
const gsdSrc = path.join(src, "get-shit-done");
const gsdDest = path.join(opencodeDir, "get-shit-done");
copyWithTransform(gsdSrc, gsdDest, pathPrefix);
if (verifyInstalled(gsdDest, "get-shit-done")) {
console.log(` ${green}✓${reset} Installed get-shit-done`);
} else {
failures.push("get-shit-done");
}
// Copy agents to .opencode/agents (replace only gsd-*.md)
const agentsSrc = path.join(src, "agents");
if (fs.existsSync(agentsSrc)) {
const agentsDest = path.join(opencodeDir, "agents");
fs.mkdirSync(agentsDest, { recursive: true });
if (fs.existsSync(agentsDest)) {
for (const file of fs.readdirSync(agentsDest)) {
if (file.startsWith("gsd-") && file.endsWith(".md")) {
fs.unlinkSync(path.join(agentsDest, file));
}
}
}
const agentEntries = fs.readdirSync(agentsSrc, { withFileTypes: true });
for (const entry of agentEntries) {
if (entry.isFile() && entry.name.endsWith(".md")) {
let content = fs.readFileSync(path.join(agentsSrc, entry.name), "utf8");
content = transformAgentMarkdown(content, pathPrefix);
fs.writeFileSync(path.join(agentsDest, entry.name), content);
}
}
if (verifyInstalled(agentsDest, "agents")) {
console.log(` ${green}✓${reset} Installed agents`);
} else {
failures.push("agents");
}
}
// Copy CHANGELOG.md
const changelogSrc = path.join(src, "CHANGELOG.md");
const changelogDest = path.join(opencodeDir, "get-shit-done", "CHANGELOG.md");
if (fs.existsSync(changelogSrc)) {
fs.copyFileSync(changelogSrc, changelogDest);
if (verifyFileInstalled(changelogDest, "CHANGELOG.md")) {
console.log(` ${green}✓${reset} Installed CHANGELOG.md`);
} else {
failures.push("CHANGELOG.md");
}
}
// Write VERSION file
const versionDest = path.join(opencodeDir, "get-shit-done", "VERSION");
fs.writeFileSync(versionDest, pkg.version);
if (verifyFileInstalled(versionDest, "VERSION")) {
console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
} else {
failures.push("VERSION");
}
// Copy update hook and configure opencode.json
const hookSrc = path.join(src, "hooks", "gsd-check-update.js");
const hookDestDir = path.join(opencodeDir, "get-shit-done", "hooks");
const hookDest = path.join(hookDestDir, "gsd-check-update.js");
fs.mkdirSync(hookDestDir, { recursive: true });
if (fs.existsSync(hookSrc)) {
let content = fs.readFileSync(hookSrc, "utf8");
content = transformHook(content);
fs.writeFileSync(hookDest, content);
if (verifyFileInstalled(hookDest, "get-shit-done/hooks/gsd-check-update.js")) {
console.log(` ${green}✓${reset} Installed update hook`);
} else {
failures.push("get-shit-done/hooks/gsd-check-update.js");
}
} else {
failures.push("get-shit-done/hooks/gsd-check-update.js");
}
const configPath = path.join(opencodeDir, "opencode.json");
const config = readConfig(configPath);
if (!config.experimental) config.experimental = {};
if (!config.experimental.hook) config.experimental.hook = {};
if (!config.experimental.hook.session_completed) {
config.experimental.hook.session_completed = [];
}
const hookCommand = ["node", hookDest];
const hasHook = config.experimental.hook.session_completed.some(
(entry) =>
Array.isArray(entry.command) &&
entry.command.length === hookCommand.length &&
entry.command.every((value, idx) => value === hookCommand[idx]),
);
if (!hasHook) {
config.experimental.hook.session_completed.push({ command: hookCommand });
writeConfig(configPath, config);
console.log(` ${green}✓${reset} Configured update hook in opencode.json`);
}
if (failures.length > 0) {
console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(", ")}`);
console.error(
` Try running directly: node ~/.npm/_npx/*/node_modules/get-shit-done-opencode/bin/install.js --global\n`,
);
process.exit(1);
}
}
/**
* Prompt for install location
*/
function promptLocation() {
if (!process.stdin.isTTY) {
console.log(` ${yellow}Non-interactive terminal detected, defaulting to global install${reset}\n`);
install(true);
finishInstall();
return;
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
let answered = false;
rl.on("close", () => {
if (!answered) {
answered = true;
console.log(`\n ${yellow}Input stream closed, defaulting to global install${reset}\n`);
install(true);
finishInstall();
}
});
const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.OPENCODE_CONFIG_DIR);
const globalPath = configDir || path.join(os.homedir(), ".config", "opencode");
const globalLabel = globalPath.replace(os.homedir(), "~");
console.log(` ${yellow}Where would you like to install?${reset}
${cyan}1${reset}) Global ${dim}(${globalLabel})${reset} - available in all projects
${cyan}2${reset}) Local ${dim}(./.opencode)${reset} - this project only
`);
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
answered = true;
rl.close();
const choice = answer.trim() || "1";
const isGlobal = choice !== "2";
install(isGlobal);
finishInstall();
});
}
function finishInstall() {
console.log(`
${green}Done!${reset} Open OpenCode and run ${cyan}gsd/help${reset}.
`);
}
// Main
if (hasGlobal && hasLocal) {
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
process.exit(1);
} else if (explicitConfigDir && hasLocal) {
console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
process.exit(1);
} else if (hasGlobal) {
install(true);
finishInstall();
} else if (hasLocal) {
install(false);
finishInstall();
} else {
promptLocation();
}

View File

@@ -1,26 +0,0 @@
{
"name": "get-shit-done-opencode",
"version": "1.6.4",
"description": "Get Shit Done installer for OpenCode.",
"bin": {
"get-shit-done-opencode": "bin/install.js"
},
"files": [
"bin"
],
"keywords": [
"opencode",
"ai",
"meta-prompting",
"context-engineering",
"spec-driven-development"
],
"author": "TÂCHES",
"license": "MIT",
"dependencies": {
"get-shit-done-cc": "^1.6.4"
},
"engines": {
"node": ">=16.7.0"
}
}

View File

@@ -21,9 +21,7 @@
"context-engineering",
"spec-driven-development",
"gemini",
"gemini-cli",
"mistral-vibe",
"vibe-cli"
"gemini-cli"
],
"author": "TÂCHES",
"license": "MIT",