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: 2 additions & 1 deletion commands/gsd/plan-phase.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: gsd:plan-phase
description: Create detailed phase plan (PLAN.md) with verification loop
argument-hint: "[phase] [--auto] [--research] [--skip-research] [--gaps] [--skip-verify] [--prd <file>]"
argument-hint: "[phase] [--auto] [--research] [--skip-research] [--gaps] [--skip-verify] [--prd <file>] [--reviews]"
agent: gsd-planner
allowed-tools:
- Read
Expand Down Expand Up @@ -35,6 +35,7 @@ Phase number: $ARGUMENTS (optional — auto-detects next unplanned phase if omit
- `--gaps` — Gap closure mode (reads VERIFICATION.md, skips research)
- `--skip-verify` — Skip verification loop
- `--prd <file>` — Use a PRD/acceptance criteria file instead of discuss-phase. Parses requirements into CONTEXT.md automatically. Skips discuss-phase entirely.
- `--reviews` — Incorporate cross-AI review feedback from REVIEWS.md into planning (run /gsd:review first)

Normalize phase input in step 2 before any directory lookups.
</context>
Expand Down
36 changes: 36 additions & 0 deletions commands/gsd/review.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
name: gsd:review
description: Request cross-AI peer review of phase plans from external AI CLIs
argument-hint: "--phase N [--gemini] [--claude] [--codex] [--all]"
agent: gsd-planner
allowed-tools:
- Read
- Write
- Bash
- Glob
- Grep
---
<objective>
Invoke external AI CLIs (Gemini, Claude, Codex) to independently review phase plans. Produces a REVIEWS.md document with structured feedback from each reviewer that can be fed back into planning.

**Flow:** Init → Check CLIs → Build Prompt → Invoke CLIs → Write REVIEWS.md → Commit → Present Results
</objective>

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

<context>
Phase number: extracted from $ARGUMENTS (required)

**Flags:**
- `--gemini` — Include Gemini CLI review
- `--claude` — Include Claude CLI review
- `--codex` — Include Codex CLI review
- `--all` — Include all available CLIs
</context>

<process>
Execute the review workflow from @~/.claude/get-shit-done/workflows/review.md end-to-end.
</process>
31 changes: 30 additions & 1 deletion get-shit-done/bin/gsd-tools.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,32 @@ async function main() {
break;
}

case 'review': {
const subcommand = args[1];
if (subcommand === 'check-cli') {
commands.cmdReviewCheckCli(cwd, raw);
} else if (subcommand === 'build-prompt') {
const phaseIdx = args.indexOf('--phase');
commands.cmdReviewBuildPrompt(cwd, phaseIdx !== -1 ? args[phaseIdx + 1] : null, raw);
} else if (subcommand === 'write-reviews') {
const phaseIdx = args.indexOf('--phase');
const reviews = {};
const geminiIdx = args.indexOf('--gemini-file');
const claudeIdx = args.indexOf('--claude-file');
const codexIdx = args.indexOf('--codex-file');
if (geminiIdx !== -1) reviews.gemini = args[geminiIdx + 1];
if (claudeIdx !== -1) reviews.claude = args[claudeIdx + 1];
if (codexIdx !== -1) reviews.codex = args[codexIdx + 1];
commands.cmdReviewWriteReviews(cwd, {
phase: phaseIdx !== -1 ? args[phaseIdx + 1] : null,
reviews,
}, raw);
} else {
error('Unknown review subcommand. Available: check-cli, build-prompt, write-reviews');
}
break;
}

case 'todo': {
const subcommand = args[1];
if (subcommand === 'complete') {
Expand Down Expand Up @@ -549,8 +575,11 @@ async function main() {
case 'progress':
init.cmdInitProgress(cwd, raw);
break;
case 'review':
init.cmdInitReview(cwd, args[2], 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, review`);
}
break;
}
Expand Down
182 changes: 182 additions & 0 deletions get-shit-done/bin/lib/commands.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,185 @@ function cmdScaffold(cwd, type, options, raw) {
output({ created: true, path: relPath }, raw, relPath);
}

// ─── Review commands ──────────────────────────────────────────────────────────

function cmdReviewCheckCli(cwd, raw) {
const { execFileSync } = require('child_process');
const result = {};
for (const cli of ['gemini', 'claude', 'codex']) {
try {
execFileSync('which', [cli], { stdio: 'pipe' });
result[cli] = true;
} catch {
result[cli] = false;
}
}
output(result, raw);
}

function cmdReviewBuildPrompt(cwd, phase, raw) {
if (!phase) {
error('phase required for review build-prompt');
}

const phaseInfo = findPhaseInternal(cwd, phase);
if (!phaseInfo) {
error(`Phase ${phase} not found`);
}

const padded = normalizePhaseName(phase);
const phaseDirFull = path.join(cwd, phaseInfo.directory);

// Gather PROJECT.md (first 50 lines)
const projectContent = safeReadFile(path.join(cwd, '.planning', 'PROJECT.md'));
const projectSnippet = projectContent ? projectContent.split('\n').slice(0, 50).join('\n') : '';

// Gather ROADMAP phase section
const { getRoadmapPhaseInternal } = require('./core.cjs');
const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
const roadmapSection = roadmapPhase?.section || '';

// Gather REQUIREMENTS.md
const requirementsContent = safeReadFile(path.join(cwd, '.planning', 'REQUIREMENTS.md')) || '';

// Gather all PLANs
const planFiles = phaseInfo.plans || [];
const planContents = planFiles.map(f => {
const content = safeReadFile(path.join(phaseDirFull, f));
return content ? `## ${f}\n\n${content}` : '';
}).filter(Boolean);

// Optional: CONTEXT.md
let contextContent = '';
try {
const files = fs.readdirSync(phaseDirFull);
const contextFile = files.find(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
if (contextFile) {
contextContent = safeReadFile(path.join(phaseDirFull, contextFile)) || '';
}
} catch {}

// Optional: RESEARCH.md
let researchContent = '';
try {
const files = fs.readdirSync(phaseDirFull);
const researchFile = files.find(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
if (researchFile) {
researchContent = safeReadFile(path.join(phaseDirFull, researchFile)) || '';
}
} catch {}

// Build prompt
const prompt = `# Cross-AI Review Request
## Project Overview (first 50 lines of PROJECT.md)
${projectSnippet}
## Phase ${phaseInfo.phase_number}: ${phaseInfo.phase_name || 'Unnamed'}
### Roadmap Section
${roadmapSection}
### Requirements
${requirementsContent}
${contextContent ? `### Context\n\n${contextContent}\n` : ''}
${researchContent ? `### Research\n\n${researchContent}\n` : ''}
### Plans (${planContents.length} total)
${planContents.join('\n\n---\n\n')}
## Review Instructions
Please review the above phase plans and provide:
1. **Summary** — Brief overview of what the plans cover
2. **Strengths** — What's well-designed or thorough
3. **Concerns** — Potential issues, gaps, or risks
4. **Suggestions** — Concrete improvements or alternatives
5. **Risk Assessment** — Overall risk level (low/medium/high) with justification
Focus on:
- Completeness: Do the plans cover all requirements?
- Feasibility: Are the tasks achievable as described?
- Dependencies: Are inter-plan dependencies correctly identified?
- Testing: Is the verification strategy adequate?
- Architecture: Are there design concerns or anti-patterns?
`;

// Write to temp file
const os = require('os');
const promptPath = path.join(os.tmpdir(), `gsd-review-prompt-${padded}-${Date.now()}.md`);
fs.writeFileSync(promptPath, prompt, 'utf-8');

output({
prompt_path: promptPath,
plan_count: planContents.length,
phase_number: phaseInfo.phase_number,
phase_name: phaseInfo.phase_name,
}, raw);
}

function cmdReviewWriteReviews(cwd, options, raw) {
const { phase, reviews } = options;
if (!phase) {
error('phase required for review write-reviews');
}

const phaseInfo = findPhaseInternal(cwd, phase);
if (!phaseInfo) {
error(`Phase ${phase} not found`);
}

const padded = normalizePhaseName(phase);
const phaseDirFull = path.join(cwd, phaseInfo.directory);
const today = new Date().toISOString().split('T')[0];

// reviews is an object like { gemini: '/tmp/gemini-review.md', claude: '/tmp/claude-review.md' }
const reviewerNames = Object.keys(reviews || {});
if (reviewerNames.length === 0) {
error('No review files provided');
}

// Build REVIEWS.md content
let content = `---
phase: "${padded}"
name: "${phaseInfo.phase_name || 'Unnamed'}"
created: ${today}
reviewers: [${reviewerNames.join(', ')}]
status: complete
---
# Phase ${phaseInfo.phase_number}: ${phaseInfo.phase_name || 'Unnamed'} -- Cross-AI Reviews
`;

for (const reviewer of reviewerNames) {
const reviewFile = reviews[reviewer];
const reviewContent = safeReadFile(reviewFile);
if (!reviewContent) {
content += `## ${reviewer.charAt(0).toUpperCase() + reviewer.slice(1)} Review\n\n_Review not available (file not found: ${reviewFile})_\n\n`;
continue;
}
content += `## ${reviewer.charAt(0).toUpperCase() + reviewer.slice(1)} Review\n\n${reviewContent.trim()}\n\n`;
}

// Write REVIEWS.md
const reviewsPath = path.join(phaseDirFull, `${padded}-REVIEWS.md`);
fs.writeFileSync(reviewsPath, content, 'utf-8');

const relPath = toPosixPath(path.relative(cwd, reviewsPath));
output({
created: true,
path: relPath,
reviewers: reviewerNames,
phase_number: phaseInfo.phase_number,
}, raw);
}

module.exports = {
cmdGenerateSlug,
cmdCurrentTimestamp,
Expand All @@ -545,4 +724,7 @@ module.exports = {
cmdProgressRender,
cmdTodoComplete,
cmdScaffold,
cmdReviewCheckCli,
cmdReviewBuildPrompt,
cmdReviewWriteReviews,
};
2 changes: 2 additions & 0 deletions get-shit-done/bin/lib/core.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
const hasResearch = phaseFiles.some(f => f.endsWith('-RESEARCH.md') || f === 'RESEARCH.md');
const hasContext = phaseFiles.some(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
const hasVerification = phaseFiles.some(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
const hasReviews = phaseFiles.some(f => f.endsWith('-REVIEWS.md') || f === 'REVIEWS.md');

const completedPlanIds = new Set(
summaries.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', ''))
Expand All @@ -250,6 +251,7 @@ function searchPhaseInDir(baseDir, relBase, normalized) {
has_research: hasResearch,
has_context: hasContext,
has_verification: hasVerification,
has_reviews: hasReviews,
};
} catch {
return null;
Expand Down
Loading