Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [Unreleased]

### Fixed
- Progress and phase-complete incorrectly route to milestone-complete when ROADMAP defines phases that have no disk directories yet (#689, #754, #757, #709)

## [1.21.1] - 2026-02-27

### Added
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ You're never locked in. The system adapts.
| `/gsd:discuss-phase [N] [--auto]` | Capture implementation decisions before planning |
| `/gsd:plan-phase [N] [--auto]` | Research + plan + verify for a phase |
| `/gsd:execute-phase <N>` | Execute all plans in parallel waves, verify when complete |
| `/gsd:autopilot [N] [N-N]` | Full pipeline (discuss → plan → execute) for remaining phases |
| `/gsd:verify-work [N]` | Manual user acceptance testing ¹ |
| `/gsd:audit-milestone` | Verify milestone achieved its definition of done |
| `/gsd:complete-milestone` | Archive milestone, tag release |
Expand Down
41 changes: 41 additions & 0 deletions commands/gsd/autopilot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
name: gsd:autopilot
description: Run full pipeline (discuss, plan, execute) for remaining phases automatically
argument-hint: "[phase] [start-end]"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
- Task
- AskUserQuestion
---
<objective>
Run the full GSD pipeline for remaining phases in the current milestone — automatically.

For each incomplete phase: generate context via synthetic multi-agent discuss, then chain through plan → execute → verify → transition. One command, full autopilot.

**Usage:**
- `/gsd:autopilot` — Run from current phase through end of milestone
- `/gsd:autopilot 5` — Run starting from phase 5
- `/gsd:autopilot 3-7` — Run phases 3 through 7
</objective>

<execution_context>
@~/.claude/get-shit-done/workflows/autopilot.md
@~/.claude/get-shit-done/workflows/auto-discuss.md
@~/.claude/get-shit-done/references/ui-brand.md
</execution_context>

<context>
Arguments: $ARGUMENTS (optional phase number or range)

Context files are resolved in-workflow using `gsd-tools init progress` and `roadmap analyze`.
</context>

<process>
Execute the autopilot workflow from @~/.claude/get-shit-done/workflows/autopilot.md end-to-end.
Preserve all workflow gates (phase loop, synthetic discuss, auto-advance chain, stop conditions).
</process>
20 changes: 20 additions & 0 deletions get-shit-done/bin/lib/config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,16 @@ function cmdConfigEnsureSection(cwd, raw) {
},
parallelization: true,
brave_search: hasBraveSearch,
autopilot: {
discuss_agents: 5,
discuss_model: 'sonnet',
},
};
const defaults = {
...hardcoded,
...userDefaults,
workflow: { ...hardcoded.workflow, ...(userDefaults.workflow || {}) },
autopilot: { ...hardcoded.autopilot, ...(userDefaults.autopilot || {}) },
};

try {
Expand Down Expand Up @@ -97,6 +102,21 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
error('Failed to read config.json: ' + err.message);
}

// Validate autopilot.discuss_agents must be odd
if (keyPath === 'autopilot.discuss_agents') {
if (typeof parsedValue !== 'number' || parsedValue % 2 === 0 || parsedValue < 3 || parsedValue > 9) {
error('discuss_agents must be odd (3/5/7/9) for consensus.');
}
}

// Validate model values
const validModels = ['opus', 'sonnet', 'haiku'];
if (keyPath === 'autopilot.discuss_model') {
if (!validModels.includes(parsedValue)) {
error(`${keyPath} must be one of: ${validModels.join(', ')}`);
}
}

// Set nested value using dot notation (e.g., "workflow.research")
const keys = keyPath.split('.');
let current = config;
Expand Down
17 changes: 17 additions & 0 deletions get-shit-done/bin/lib/core.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,22 @@ function getRoadmapPhaseInternal(cwd, phaseNum) {
}
}

function getRoadmapPhaseNumbersInternal(cwd) {
const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
try {
const content = fs.readFileSync(roadmapPath, 'utf-8');
const pattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:/gi;
const numbers = [];
let m;
while ((m = pattern.exec(content)) !== null) {
numbers.push(m[1]);
}
return numbers.sort((a, b) => comparePhaseNum(a, b));
} catch {
return [];
}
}

function resolveModelInternal(cwd, agentType) {
const config = loadConfig(cwd);

Expand Down Expand Up @@ -424,6 +440,7 @@ module.exports = {
findPhaseInternal,
getArchivedPhaseDirs,
getRoadmapPhaseInternal,
getRoadmapPhaseNumbersInternal,
resolveModelInternal,
pathExistsInternal,
generateSlugInternal,
Expand Down
8 changes: 6 additions & 2 deletions get-shit-done/bin/lib/init.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, normalizePhaseName, toPosixPath, output, error } = require('./core.cjs');
const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, getRoadmapPhaseNumbersInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, normalizePhaseName, toPosixPath, output, error } = require('./core.cjs');

function cmdInitExecutePhase(cwd, phase, raw) {
if (!phase) {
Expand Down Expand Up @@ -35,7 +35,6 @@ function cmdInitExecutePhase(cwd, phase, raw) {
phase_branch_template: config.phase_branch_template,
milestone_branch_template: config.milestone_branch_template,
verifier_enabled: config.verifier,

// Phase info
phase_found: !!phaseInfo,
phase_dir: phaseInfo?.directory || null,
Expand Down Expand Up @@ -648,6 +647,8 @@ function cmdInitProgress(cwd, raw) {
}
} catch {}

const roadmapPhaseCount = getRoadmapPhaseNumbersInternal(cwd).length;

// Check for paused work
let pausedAt = null;
try {
Expand All @@ -663,6 +664,8 @@ function cmdInitProgress(cwd, raw) {

// Config
commit_docs: config.commit_docs,
auto_advance: (config.workflow && config.workflow.auto_advance) || false,
discuss_agents: (config.autopilot && config.autopilot.discuss_agents) || 5,

// Milestone
milestone_version: milestone.version,
Expand All @@ -673,6 +676,7 @@ function cmdInitProgress(cwd, raw) {
phase_count: phases.length,
completed_count: phases.filter(p => p.status === 'complete').length,
in_progress_count: phases.filter(p => p.status === 'in_progress').length,
roadmap_phase_count: roadmapPhaseCount,

// Current state
current_phase: currentPhase,
Expand Down
20 changes: 19 additions & 1 deletion get-shit-done/bin/lib/phase.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

const fs = require('fs');
const path = require('path');
const { escapeRegex, normalizePhaseName, comparePhaseNum, findPhaseInternal, getArchivedPhaseDirs, generateSlugInternal, output, error } = require('./core.cjs');
const { escapeRegex, normalizePhaseName, comparePhaseNum, findPhaseInternal, getArchivedPhaseDirs, getRoadmapPhaseNumbersInternal, generateSlugInternal, output, error } = require('./core.cjs');
const { extractFrontmatter } = require('./frontmatter.cjs');
const { writeStateMd } = require('./state.cjs');

Expand Down Expand Up @@ -806,6 +806,24 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
}
} catch {}

if (isLastPhase) {
const roadmapNumbers = getRoadmapPhaseNumbersInternal(cwd);
for (const rmNum of roadmapNumbers) {
if (comparePhaseNum(rmNum, phaseNum) > 0) {
nextPhaseNum = rmNum;
try {
const rmPath = path.join(cwd, '.planning', 'ROADMAP.md');
const rmContent = fs.readFileSync(rmPath, 'utf-8');
const escaped = escapeRegex(rmNum);
const nameMatch = rmContent.match(new RegExp(`#{2,4}\\s*Phase\\s+${escaped}:\\s*([^\\n]+)`, 'i'));
if (nameMatch) nextPhaseName = nameMatch[1].replace(/\(INSERTED\)/i, '').trim();
} catch {}
isLastPhase = false;
break;
}
}
}

// Update STATE.md
if (fs.existsSync(statePath)) {
let stateContent = fs.readFileSync(statePath, 'utf-8');
Expand Down
4 changes: 4 additions & 0 deletions get-shit-done/templates/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
"issues_review": true,
"confirm_transition": true
},
"autopilot": {
"discuss_agents": 5,
"discuss_model": "sonnet"
},
"safety": {
"always_confirm_destructive": true,
"always_confirm_external_services": true
Expand Down
Loading