feat(manager): add interactive command center with dependency-aware recommendations
Add /gsd:manager — a single-terminal dashboard for managing milestones. Shows all phases with visual status indicators (D/P/E columns), computes recommended next actions, and dispatches discuss inline while plan/execute run as background agents. Key behaviors: - Recommendation engine prioritizes execute > plan > discuss - Filters parallel execute/plan when phases share dependency chains - Independent phases (no direct or transitive dep relationship) CAN run in parallel — dependent phases are serialized - Dashboard shows compact Deps column for at-a-glance dependency view - Sliding window limits discuss to one phase at a time - Activity detection via file mtime (5-min window) for is_active flag New files: - commands/gsd/manager.md — skill definition - get-shit-done/workflows/manager.md — full workflow spec - get-shit-done/bin/lib/init.cjs — cmdInitManager() with phase parsing, dependency graph traversal, and recommendation filtering - get-shit-done/bin/gsd-tools.cjs — route 'init manager' to new command - tests/init-manager.test.cjs — 16 tests covering status detection, deps, sliding window, recommendations, and edge cases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -614,8 +614,11 @@ async function main() {
|
||||
case 'progress':
|
||||
init.cmdInitProgress(cwd, raw);
|
||||
break;
|
||||
case 'manager':
|
||||
init.cmdInitManager(cwd, raw);
|
||||
break;
|
||||
default:
|
||||
error(`Unknown init workflow: ${workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, resume, verify-work, phase-op, todos, milestone-op, map-codebase, progress`);
|
||||
error(`Unknown init workflow: ${workflow}\nAvailable: execute-phase, plan-phase, new-project, new-milestone, quick, resume, verify-work, phase-op, todos, milestone-op, map-codebase, progress, manager`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -669,6 +669,252 @@ function cmdInitMapCodebase(cwd, raw) {
|
||||
output(result, raw);
|
||||
}
|
||||
|
||||
function cmdInitManager(cwd, raw) {
|
||||
const config = loadConfig(cwd);
|
||||
const milestone = getMilestoneInfo(cwd);
|
||||
|
||||
// Validate prerequisites
|
||||
if (!pathExistsInternal(cwd, '.planning/ROADMAP.md')) {
|
||||
error('No ROADMAP.md found. Run /gsd:new-milestone first.');
|
||||
}
|
||||
if (!pathExistsInternal(cwd, '.planning/STATE.md')) {
|
||||
error('No STATE.md found. Run /gsd:new-milestone first.');
|
||||
}
|
||||
|
||||
// Use roadmap analysis for rich phase data (depends_on, disk_status, has_context, etc.)
|
||||
const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
|
||||
const rawContent = fs.readFileSync(roadmapPath, 'utf-8');
|
||||
const content = extractCurrentMilestone(rawContent, cwd);
|
||||
const phasesDir = path.join(cwd, '.planning', 'phases');
|
||||
|
||||
const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi;
|
||||
const phases = [];
|
||||
let match;
|
||||
|
||||
while ((match = phasePattern.exec(content)) !== null) {
|
||||
const phaseNum = match[1];
|
||||
const phaseName = match[2].replace(/\(INSERTED\)/i, '').trim();
|
||||
|
||||
const sectionStart = match.index;
|
||||
const restOfContent = content.slice(sectionStart);
|
||||
const nextHeader = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
|
||||
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
|
||||
const section = content.slice(sectionStart, sectionEnd);
|
||||
|
||||
const goalMatch = section.match(/\*\*Goal(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
||||
const goal = goalMatch ? goalMatch[1].trim() : null;
|
||||
|
||||
const dependsMatch = section.match(/\*\*Depends on(?::\*\*|\*\*:)\s*([^\n]+)/i);
|
||||
const depends_on = dependsMatch ? dependsMatch[1].trim() : null;
|
||||
|
||||
const normalized = normalizePhaseName(phaseNum);
|
||||
let diskStatus = 'no_directory';
|
||||
let planCount = 0;
|
||||
let summaryCount = 0;
|
||||
let hasContext = false;
|
||||
let hasResearch = false;
|
||||
let lastActivity = null;
|
||||
let isActive = false;
|
||||
|
||||
try {
|
||||
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
||||
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
||||
const dirMatch = dirs.find(d => d.startsWith(normalized + '-') || d === normalized);
|
||||
|
||||
if (dirMatch) {
|
||||
const fullDir = path.join(phasesDir, dirMatch);
|
||||
const phaseFiles = fs.readdirSync(fullDir);
|
||||
planCount = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').length;
|
||||
summaryCount = phaseFiles.filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md').length;
|
||||
hasContext = phaseFiles.some(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
|
||||
hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
|
||||
|
||||
if (summaryCount >= planCount && planCount > 0) diskStatus = 'complete';
|
||||
else if (summaryCount > 0) diskStatus = 'partial';
|
||||
else if (planCount > 0) diskStatus = 'planned';
|
||||
else if (hasResearch) diskStatus = 'researched';
|
||||
else if (hasContext) diskStatus = 'discussed';
|
||||
else diskStatus = 'empty';
|
||||
|
||||
// Activity detection: check most recent file mtime
|
||||
const now = Date.now();
|
||||
let newestMtime = 0;
|
||||
for (const f of phaseFiles) {
|
||||
try {
|
||||
const stat = fs.statSync(path.join(fullDir, f));
|
||||
if (stat.mtimeMs > newestMtime) newestMtime = stat.mtimeMs;
|
||||
} catch { /* intentionally empty */ }
|
||||
}
|
||||
if (newestMtime > 0) {
|
||||
lastActivity = new Date(newestMtime).toISOString();
|
||||
isActive = (now - newestMtime) < 300000; // 5 minutes
|
||||
}
|
||||
}
|
||||
} catch { /* intentionally empty */ }
|
||||
|
||||
// Check ROADMAP checkbox status
|
||||
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${phaseNum.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[:\\s]`, 'i');
|
||||
const checkboxMatch = content.match(checkboxPattern);
|
||||
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === 'x' : false;
|
||||
if (roadmapComplete && diskStatus !== 'complete') {
|
||||
diskStatus = 'complete';
|
||||
}
|
||||
|
||||
phases.push({
|
||||
number: phaseNum,
|
||||
name: phaseName,
|
||||
goal,
|
||||
depends_on,
|
||||
disk_status: diskStatus,
|
||||
has_context: hasContext,
|
||||
has_research: hasResearch,
|
||||
plan_count: planCount,
|
||||
summary_count: summaryCount,
|
||||
roadmap_complete: roadmapComplete,
|
||||
last_activity: lastActivity,
|
||||
is_active: isActive,
|
||||
});
|
||||
}
|
||||
|
||||
// Compute display names: truncate to keep table aligned
|
||||
const MAX_NAME_WIDTH = 20;
|
||||
for (const phase of phases) {
|
||||
if (phase.name.length > MAX_NAME_WIDTH) {
|
||||
phase.display_name = phase.name.slice(0, MAX_NAME_WIDTH - 1) + '…';
|
||||
} else {
|
||||
phase.display_name = phase.name;
|
||||
}
|
||||
}
|
||||
|
||||
// Dependency satisfaction: check if all depends_on phases are complete
|
||||
const completedNums = new Set(phases.filter(p => p.disk_status === 'complete').map(p => p.number));
|
||||
for (const phase of phases) {
|
||||
if (!phase.depends_on || /^none$/i.test(phase.depends_on.trim())) {
|
||||
phase.deps_satisfied = true;
|
||||
} else {
|
||||
// Parse "Phase 1, Phase 3" or "1, 3" formats
|
||||
const depNums = phase.depends_on.match(/\d+(?:\.\d+)*/g) || [];
|
||||
phase.deps_satisfied = depNums.every(n => completedNums.has(n));
|
||||
phase.dep_phases = depNums;
|
||||
}
|
||||
}
|
||||
|
||||
// Compact dependency display for dashboard
|
||||
for (const phase of phases) {
|
||||
phase.deps_display = (phase.dep_phases && phase.dep_phases.length > 0)
|
||||
? phase.dep_phases.join(',')
|
||||
: '—';
|
||||
}
|
||||
|
||||
// Sliding window: discuss is sequential — only the first undiscussed phase is available
|
||||
let foundNextToDiscuss = false;
|
||||
for (const phase of phases) {
|
||||
if (!foundNextToDiscuss && (phase.disk_status === 'empty' || phase.disk_status === 'no_directory')) {
|
||||
phase.is_next_to_discuss = true;
|
||||
foundNextToDiscuss = true;
|
||||
} else {
|
||||
phase.is_next_to_discuss = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for WAITING.json signal
|
||||
let waitingSignal = null;
|
||||
try {
|
||||
const waitingPath = path.join(cwd, '.planning', 'WAITING.json');
|
||||
if (fs.existsSync(waitingPath)) {
|
||||
waitingSignal = JSON.parse(fs.readFileSync(waitingPath, 'utf-8'));
|
||||
}
|
||||
} catch { /* intentionally empty */ }
|
||||
|
||||
// Compute recommended actions (execute > plan > discuss)
|
||||
const recommendedActions = [];
|
||||
for (const phase of phases) {
|
||||
if (phase.disk_status === 'complete') continue;
|
||||
|
||||
if (phase.disk_status === 'planned' && phase.deps_satisfied) {
|
||||
recommendedActions.push({
|
||||
phase: phase.number,
|
||||
phase_name: phase.name,
|
||||
action: 'execute',
|
||||
reason: `${phase.plan_count} plans ready, dependencies met`,
|
||||
command: `/gsd:execute-phase ${phase.number}`,
|
||||
});
|
||||
} else if (phase.disk_status === 'discussed' || phase.disk_status === 'researched') {
|
||||
recommendedActions.push({
|
||||
phase: phase.number,
|
||||
phase_name: phase.name,
|
||||
action: 'plan',
|
||||
reason: 'Context gathered, ready for planning',
|
||||
command: `/gsd:plan-phase ${phase.number}`,
|
||||
});
|
||||
} else if ((phase.disk_status === 'empty' || phase.disk_status === 'no_directory') && phase.is_next_to_discuss) {
|
||||
recommendedActions.push({
|
||||
phase: phase.number,
|
||||
phase_name: phase.name,
|
||||
action: 'discuss',
|
||||
reason: 'Unblocked, ready to gather context',
|
||||
command: `/gsd:discuss-phase ${phase.number}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Filter recommendations: no parallel execute/plan unless phases are independent
|
||||
// Two phases are "independent" if neither depends on the other (directly or transitively)
|
||||
const phaseMap = new Map(phases.map(p => [p.number, p]));
|
||||
|
||||
function reaches(from, to, visited = new Set()) {
|
||||
if (visited.has(from)) return false;
|
||||
visited.add(from);
|
||||
const p = phaseMap.get(from);
|
||||
if (!p || !p.dep_phases || p.dep_phases.length === 0) return false;
|
||||
if (p.dep_phases.includes(to)) return true;
|
||||
return p.dep_phases.some(dep => reaches(dep, to, visited));
|
||||
}
|
||||
|
||||
function hasDepRelationship(numA, numB) {
|
||||
return reaches(numA, numB) || reaches(numB, numA);
|
||||
}
|
||||
|
||||
// Detect phases with active work (file modified in last 5 min)
|
||||
const activeExecuting = phases.filter(p =>
|
||||
p.disk_status === 'partial' ||
|
||||
(p.disk_status === 'planned' && p.is_active)
|
||||
);
|
||||
const activePlanning = phases.filter(p =>
|
||||
p.is_active && (p.disk_status === 'discussed' || p.disk_status === 'researched')
|
||||
);
|
||||
|
||||
const filteredActions = recommendedActions.filter(action => {
|
||||
if (action.action === 'execute' && activeExecuting.length > 0) {
|
||||
// Only allow if independent of ALL actively-executing phases
|
||||
return activeExecuting.every(active => !hasDepRelationship(action.phase, active.number));
|
||||
}
|
||||
if (action.action === 'plan' && activePlanning.length > 0) {
|
||||
// Only allow if independent of ALL actively-planning phases
|
||||
return activePlanning.every(active => !hasDepRelationship(action.phase, active.number));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const completedCount = phases.filter(p => p.disk_status === 'complete').length;
|
||||
const result = {
|
||||
milestone_version: milestone.version,
|
||||
milestone_name: milestone.name,
|
||||
phases,
|
||||
phase_count: phases.length,
|
||||
completed_count: completedCount,
|
||||
in_progress_count: phases.filter(p => ['partial', 'planned', 'discussed', 'researched'].includes(p.disk_status)).length,
|
||||
recommended_actions: filteredActions,
|
||||
waiting_signal: waitingSignal,
|
||||
all_complete: completedCount === phases.length && phases.length > 0,
|
||||
project_exists: pathExistsInternal(cwd, '.planning/PROJECT.md'),
|
||||
roadmap_exists: true,
|
||||
state_exists: true,
|
||||
};
|
||||
|
||||
output(result, raw);
|
||||
}
|
||||
|
||||
function cmdInitProgress(cwd, raw) {
|
||||
const config = loadConfig(cwd);
|
||||
const milestone = getMilestoneInfo(cwd);
|
||||
@@ -829,4 +1075,5 @@ module.exports = {
|
||||
cmdInitMilestoneOp,
|
||||
cmdInitMapCodebase,
|
||||
cmdInitProgress,
|
||||
cmdInitManager,
|
||||
};
|
||||
|
||||
360
get-shit-done/workflows/manager.md
Normal file
360
get-shit-done/workflows/manager.md
Normal file
@@ -0,0 +1,360 @@
|
||||
<purpose>
|
||||
|
||||
Interactive command center for managing a milestone from a single terminal. Shows a dashboard of all phases with visual status, dispatches discuss inline and plan/execute as background agents, and loops back to the dashboard after each action. Enables parallel phase work from one terminal.
|
||||
|
||||
</purpose>
|
||||
|
||||
<required_reading>
|
||||
|
||||
Read all files referenced by the invoking prompt's execution_context before starting.
|
||||
|
||||
</required_reading>
|
||||
|
||||
<process>
|
||||
|
||||
<step name="initialize" priority="first">
|
||||
|
||||
## 1. Initialize
|
||||
|
||||
Bootstrap via manager init:
|
||||
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init manager)
|
||||
```
|
||||
|
||||
Parse JSON for: `milestone_version`, `milestone_name`, `phase_count`, `completed_count`, `in_progress_count`, `phases`, `recommended_actions`, `all_complete`, `waiting_signal`.
|
||||
|
||||
**If error:** Display the error message and exit.
|
||||
|
||||
Display startup banner:
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► MANAGER
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
{milestone_version} — {milestone_name}
|
||||
{phase_count} phases · {completed_count} complete
|
||||
|
||||
✓ Discuss → inline ◆ Plan/Execute → background
|
||||
Dashboard auto-refreshes when background work is active.
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
Proceed to dashboard step.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="dashboard">
|
||||
|
||||
## 2. Dashboard (Refresh Point)
|
||||
|
||||
**Every time this step is reached**, re-read state from disk to pick up changes from background agents:
|
||||
|
||||
```bash
|
||||
INIT=$(node "$HOME/.claude/get-shit-done/bin/gsd-tools.cjs" init manager)
|
||||
```
|
||||
|
||||
Parse the full JSON. Build the dashboard display.
|
||||
|
||||
Build dashboard from JSON. Symbols: `✓` done, `◆` active, `○` pending, `·` queued. Progress bar: 20-char `█░`.
|
||||
|
||||
**Status mapping** (disk_status → D P E Status):
|
||||
|
||||
- `complete` → `✓ ✓ ✓` `✓ Complete`
|
||||
- `partial` → `✓ ✓ ◆` `◆ Executing...`
|
||||
- `planned` → `✓ ✓ ○` `○ Ready to execute`
|
||||
- `discussed` → `✓ ○ ·` `○ Ready to plan`
|
||||
- `researched` → `◆ · ·` `○ Ready to plan`
|
||||
- `empty`/`no_directory` + `is_next_to_discuss` → `○ · ·` `○ Ready to discuss`
|
||||
- `empty`/`no_directory` otherwise → `· · ·` `· Up next`
|
||||
- If `is_active`, replace status icon with `◆` and append `(active)`
|
||||
|
||||
If any `is_active` phases, show: `◆ Background: {action} Phase {N}, ...` above grid.
|
||||
|
||||
Use `display_name` (not `name`) for the Phase column — it's pre-truncated to 20 chars with `…` if clipped. Pad all phase names to the same width for alignment.
|
||||
|
||||
Use `deps_display` from init JSON for the Deps column — shows which phases this phase depends on (e.g. `1,3`) or `—` for none.
|
||||
|
||||
Example output:
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► DASHBOARD
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
████████████░░░░░░░░ 60% (3/5 phases)
|
||||
◆ Background: Planning Phase 4
|
||||
| # | Phase | Deps | D | P | E | Status |
|
||||
|---|----------------------|------|---|---|---|---------------------|
|
||||
| 1 | Foundation | — | ✓ | ✓ | ✓ | ✓ Complete |
|
||||
| 2 | API Layer | 1 | ✓ | ✓ | ◆ | ◆ Executing (active)|
|
||||
| 3 | Auth System | 1 | ✓ | ✓ | ○ | ○ Ready to execute |
|
||||
| 4 | Dashboard UI & Set… | 1,2 | ✓ | ◆ | · | ◆ Planning (active) |
|
||||
| 5 | Notifications | — | ○ | · | · | ○ Ready to discuss |
|
||||
| 6 | Polish & Final Mail… | 1-5 | · | · | · | · Up next |
|
||||
```
|
||||
|
||||
**Recommendations section:**
|
||||
|
||||
If `all_complete` is true:
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════════╗
|
||||
║ MILESTONE COMPLETE ║
|
||||
╚══════════════════════════════════════════════════════════════╝
|
||||
|
||||
All {phase_count} phases done. Ready for final steps:
|
||||
→ /gsd:verify-work — run acceptance testing
|
||||
→ /gsd:complete-milestone — archive and wrap up
|
||||
```
|
||||
|
||||
Ask user via AskUserQuestion:
|
||||
- **question:** "All phases complete. What next?"
|
||||
- **options:** "Verify work" / "Complete milestone" / "Exit manager"
|
||||
|
||||
Handle responses:
|
||||
- "Verify work": `Skill(skill="gsd:verify-work")` then loop to dashboard.
|
||||
- "Complete milestone": `Skill(skill="gsd:complete-milestone")` then exit.
|
||||
- "Exit manager": Go to exit step.
|
||||
|
||||
**If NOT all_complete**, build compound options from `recommended_actions`:
|
||||
|
||||
**Compound option logic:** Group background actions (plan/execute) together, and pair them with the single inline action (discuss) when one exists. The goal is to present the fewest options possible — one option can dispatch multiple background agents plus one inline action.
|
||||
|
||||
**Building options:**
|
||||
|
||||
1. Collect all background actions (execute and plan recommendations) — there can be multiple of each.
|
||||
2. Collect the inline action (discuss recommendation, if any — there will be at most one since discuss is sequential).
|
||||
3. Build compound options:
|
||||
|
||||
**If there are ANY recommended actions (background, inline, or both):**
|
||||
Create ONE primary "Continue" option that dispatches ALL of them together:
|
||||
- Label: `"Continue"` — always this exact word
|
||||
- Below the label, list every action that will happen. Enumerate ALL recommended actions — do not cap or truncate:
|
||||
```
|
||||
Continue:
|
||||
→ Execute Phase 32 (background)
|
||||
→ Plan Phase 34 (background)
|
||||
→ Discuss Phase 35 (inline)
|
||||
```
|
||||
- This dispatches all background agents first, then runs the inline discuss (if any).
|
||||
- If there is no inline discuss, the dashboard refreshes after spawning background agents.
|
||||
|
||||
**Important:** The Continue option must include EVERY action from `recommended_actions` — not just 2. If there are 3 actions, list 3. If there are 5, list 5.
|
||||
|
||||
4. Always add:
|
||||
- `"Refresh dashboard"`
|
||||
- `"Exit manager"`
|
||||
|
||||
Display recommendations compactly:
|
||||
|
||||
```
|
||||
───────────────────────────────────────────────────────────────
|
||||
▶ Next Steps
|
||||
───────────────────────────────────────────────────────────────
|
||||
|
||||
Continue:
|
||||
→ Execute Phase 32 (background)
|
||||
→ Plan Phase 34 (background)
|
||||
→ Discuss Phase 35 (inline)
|
||||
```
|
||||
|
||||
**Auto-refresh:** If background agents are running (`is_active` is true for any phase), set a 60-second auto-refresh cycle. After presenting the action menu, if no user input is received within 60 seconds, automatically refresh the dashboard. This interval is configurable via `manager_refresh_interval` in GSD config (default: 60 seconds, set to 0 to disable).
|
||||
|
||||
Present via AskUserQuestion:
|
||||
- **question:** "What would you like to do?"
|
||||
- **options:** (compound options as built above + refresh + exit, AskUserQuestion auto-adds "Other")
|
||||
|
||||
**On "Other" (free text):** Parse intent — if it mentions a phase number and action, dispatch accordingly. If unclear, display available actions and loop to action_menu.
|
||||
|
||||
Proceed to handle_action step with the selected action.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="handle_action">
|
||||
|
||||
## 4. Handle Action
|
||||
|
||||
### Refresh Dashboard
|
||||
|
||||
Loop back to dashboard step.
|
||||
|
||||
### Exit Manager
|
||||
|
||||
Go to exit step.
|
||||
|
||||
### Compound Action (background + inline)
|
||||
|
||||
When the user selects a compound option:
|
||||
|
||||
1. **Spawn all background agents first** (plan/execute) — dispatch them in parallel using the Plan Phase N / Execute Phase N handlers below.
|
||||
2. **Then run the inline discuss:**
|
||||
|
||||
```
|
||||
Skill(skill="gsd:discuss-phase", args="{PHASE_NUM}")
|
||||
```
|
||||
|
||||
After discuss completes, loop back to dashboard step (background agents continue running).
|
||||
|
||||
### Discuss Phase N
|
||||
|
||||
Discussion is interactive — needs user input. Run inline:
|
||||
|
||||
```
|
||||
Skill(skill="gsd:discuss-phase", args="{PHASE_NUM}")
|
||||
```
|
||||
|
||||
After discuss completes, loop back to dashboard step.
|
||||
|
||||
### Plan Phase N
|
||||
|
||||
Planning runs autonomously. Spawn a background agent:
|
||||
|
||||
```
|
||||
Task(
|
||||
description="Plan phase {N}: {phase_name}",
|
||||
run_in_background=true,
|
||||
prompt="You are running the GSD plan-phase workflow for phase {N} of the project.
|
||||
|
||||
Working directory: {cwd}
|
||||
Phase: {N} — {phase_name}
|
||||
Goal: {goal}
|
||||
|
||||
Steps:
|
||||
1. Read the plan-phase workflow: cat ~/.claude/get-shit-done/workflows/plan-phase.md
|
||||
2. Run: node \"$HOME/.claude/get-shit-done/bin/gsd-tools.cjs\" init plan-phase {N}
|
||||
3. Follow the workflow steps to produce PLAN.md files for this phase.
|
||||
4. If research is enabled in config, run the research step first.
|
||||
5. Spawn a gsd-planner subagent via Task() to create the plans.
|
||||
6. If plan-checker is enabled, spawn a gsd-plan-checker subagent to verify.
|
||||
7. Commit plan files when complete.
|
||||
|
||||
Important: You are running in the background. Do NOT use AskUserQuestion — make autonomous decisions based on project context. If you hit a blocker, write it to STATE.md as a blocker and stop. Do NOT silently work around permission or file access errors — let them fail so the manager can surface them with resolution hints."
|
||||
)
|
||||
```
|
||||
|
||||
Display:
|
||||
|
||||
```
|
||||
◆ Spawning planner for Phase {N}: {phase_name}...
|
||||
```
|
||||
|
||||
Loop back to dashboard step.
|
||||
|
||||
### Execute Phase N
|
||||
|
||||
Execution runs autonomously. Spawn a background agent:
|
||||
|
||||
```
|
||||
Task(
|
||||
description="Execute phase {N}: {phase_name}",
|
||||
run_in_background=true,
|
||||
prompt="You are running the GSD execute-phase workflow for phase {N} of the project.
|
||||
|
||||
Working directory: {cwd}
|
||||
Phase: {N} — {phase_name}
|
||||
Goal: {goal}
|
||||
|
||||
Steps:
|
||||
1. Read the execute-phase workflow: cat ~/.claude/get-shit-done/workflows/execute-phase.md
|
||||
2. Run: node \"$HOME/.claude/get-shit-done/bin/gsd-tools.cjs\" init execute-phase {N}
|
||||
3. Follow the workflow steps: discover plans, analyze dependencies, group into waves.
|
||||
4. For each wave, spawn gsd-executor subagents via Task() to execute plans in parallel.
|
||||
5. After all waves complete, spawn a gsd-verifier subagent if verifier is enabled.
|
||||
6. Update ROADMAP.md and STATE.md with progress.
|
||||
7. Commit all changes.
|
||||
|
||||
Important: You are running in the background. Do NOT use AskUserQuestion — make autonomous decisions. Use --no-verify on git commits. If you hit a permission error, file lock, or any access issue, do NOT work around it — let it fail and write the error to STATE.md as a blocker so the manager can surface it with resolution guidance."
|
||||
)
|
||||
```
|
||||
|
||||
Display:
|
||||
|
||||
```
|
||||
◆ Spawning executor for Phase {N}: {phase_name}...
|
||||
```
|
||||
|
||||
Loop back to dashboard step.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="background_completion">
|
||||
|
||||
## 5. Background Agent Completion
|
||||
|
||||
When notified that a background agent completed:
|
||||
|
||||
1. Read the result message from the agent.
|
||||
2. Display a brief notification:
|
||||
|
||||
```
|
||||
✓ {description}
|
||||
{brief summary from agent result}
|
||||
```
|
||||
|
||||
3. Loop back to dashboard step.
|
||||
|
||||
**If the agent reported an error or blocker:**
|
||||
|
||||
Classify the error:
|
||||
|
||||
**Permission / tool access error** (e.g. tool not allowed, permission denied, sandbox restriction):
|
||||
- Parse the error to identify which tool or command was blocked.
|
||||
- Display the error clearly, then offer to fix it:
|
||||
- **question:** "Phase {N} failed — permission denied for `{tool_or_command}`. Want me to add it to settings.local.json so it's allowed?"
|
||||
- **options:** "Add permission and retry" / "Run this phase inline instead" / "Skip and continue"
|
||||
- "Add permission and retry": Use `Skill(skill="update-config")` to add the permission to `settings.local.json`, then re-spawn the background agent. Loop to dashboard.
|
||||
- "Run this phase inline instead": Dispatch the same action (plan/execute) inline via `Skill()` instead of a background Task. Loop to dashboard after.
|
||||
- "Skip and continue": Loop to dashboard (phase stays in current state).
|
||||
|
||||
**Other errors** (git lock, file conflict, logic error, etc.):
|
||||
- Display the error, then offer options via AskUserQuestion:
|
||||
- **question:** "Background agent for Phase {N} encountered an issue: {error}. What next?"
|
||||
- **options:** "Retry" / "Run inline instead" / "Skip and continue" / "View details"
|
||||
- "Retry": Re-spawn the same background agent. Loop to dashboard.
|
||||
- "Run inline instead": Dispatch the action inline via `Skill()`. Loop to dashboard after.
|
||||
- "Skip and continue": Loop to dashboard (phase stays in current state).
|
||||
- "View details": Read STATE.md blockers section, display, then re-present options.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="exit">
|
||||
|
||||
## 6. Exit
|
||||
|
||||
Display final status with progress bar:
|
||||
|
||||
```
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GSD ► SESSION END
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
{milestone_version} — {milestone_name}
|
||||
{PROGRESS_BAR} {progress_pct}% ({completed_count}/{phase_count} phases)
|
||||
|
||||
Resume anytime: /gsd:manager
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
**Note:** Any background agents still running will continue to completion. Their results will be visible on next `/gsd:manager` or `/gsd:progress` invocation.
|
||||
|
||||
</step>
|
||||
|
||||
</process>
|
||||
|
||||
<success_criteria>
|
||||
- [ ] Dashboard displays all phases with correct status indicators (D/P/E/V columns)
|
||||
- [ ] Progress bar shows accurate completion percentage
|
||||
- [ ] Dependency resolution: blocked phases show which deps are missing
|
||||
- [ ] Recommendations prioritize: execute > plan > discuss
|
||||
- [ ] Discuss phases run inline via Skill() — interactive questions work
|
||||
- [ ] Plan phases spawn background Task agents — return to dashboard immediately
|
||||
- [ ] Execute phases spawn background Task agents — return to dashboard immediately
|
||||
- [ ] Dashboard refreshes pick up changes from background agents via disk state
|
||||
- [ ] Background agent completion triggers notification and dashboard refresh
|
||||
- [ ] Background agent errors present retry/skip options
|
||||
- [ ] All-complete state offers verify-work and complete-milestone
|
||||
- [ ] Exit shows final status with resume instructions
|
||||
- [ ] "Other" free-text input parsed for phase number and action
|
||||
- [ ] Manager loop continues until user exits or milestone completes
|
||||
</success_criteria>
|
||||
Reference in New Issue
Block a user