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:
124
CHANGELOG.md
124
CHANGELOG.md
@@ -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
|
||||
|
||||
|
||||
13
README.md
13
README.md
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
@@ -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.
|
||||
@@ -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|=>)"
|
||||
```
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
130
bin/install.js
130
bin/install.js
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
},
|
||||
"planning": {
|
||||
"commit_docs": true,
|
||||
"search_gitignored": false,
|
||||
"git_tag": true
|
||||
"search_gitignored": false
|
||||
},
|
||||
"parallelization": {
|
||||
"enabled": true,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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`).
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,7 @@
|
||||
"context-engineering",
|
||||
"spec-driven-development",
|
||||
"gemini",
|
||||
"gemini-cli",
|
||||
"mistral-vibe",
|
||||
"vibe-cli"
|
||||
"gemini-cli"
|
||||
],
|
||||
"author": "TÂCHES",
|
||||
"license": "MIT",
|
||||
|
||||
Reference in New Issue
Block a user