feat(phases): archive completed milestone phase directories (closes #489)
Add optional phase archival to milestone completion and a standalone
/gsd:cleanup command for retroactive use. Phase dirs move to
.planning/milestones/v{X.Y}-phases/, reducing phases/ clutter after
multiple milestones.
Core changes:
- getArchivedPhaseDirs() and searchPhaseInDir() helpers in gsd-tools
- findPhaseInternal() searches archives when phase not found in current
- cmdPhasesList() accepts --include-archived flag
- cmdHistoryDigest() scans both current and archived phases
- cmdMilestoneComplete() accepts --archive-phases flag
- Workflow globs replaced with find-phase/phases-list CLI calls
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
18
commands/gsd/cleanup.md
Normal file
18
commands/gsd/cleanup.md
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: gsd:cleanup
|
||||
description: Archive accumulated phase directories from completed milestones
|
||||
---
|
||||
<objective>
|
||||
Archive phase directories from completed milestones into `.planning/milestones/v{X.Y}-phases/`.
|
||||
|
||||
Use when `.planning/phases/` has accumulated directories from past milestones.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/cleanup.md
|
||||
</execution_context>
|
||||
|
||||
<process>
|
||||
Follow the cleanup workflow at @~/.claude/get-shit-done/workflows/cleanup.md.
|
||||
Identify completed milestones, show a dry-run summary, and archive on confirmation.
|
||||
</process>
|
||||
@@ -44,6 +44,7 @@
|
||||
* Milestone Operations:
|
||||
* milestone complete <version> Archive milestone, create MILESTONES.md
|
||||
* [--name <name>]
|
||||
* [--archive-phases] Move phase dirs to milestones/vX.Y-phases/
|
||||
*
|
||||
* Validation:
|
||||
* validate consistency Check phase numbering, disk/roadmap sync
|
||||
@@ -694,20 +695,36 @@ function cmdHistoryDigest(cwd, raw) {
|
||||
const phasesDir = path.join(cwd, '.planning', 'phases');
|
||||
const digest = { phases: {}, decisions: [], tech_stack: new Set() };
|
||||
|
||||
if (!fs.existsSync(phasesDir)) {
|
||||
// Collect all phase directories: archived + current
|
||||
const allPhaseDirs = [];
|
||||
|
||||
// Add archived phases first (oldest milestones first)
|
||||
const archived = getArchivedPhaseDirs(cwd);
|
||||
for (const a of archived) {
|
||||
allPhaseDirs.push({ name: a.name, fullPath: a.fullPath, milestone: a.milestone });
|
||||
}
|
||||
|
||||
// Add current phases
|
||||
if (fs.existsSync(phasesDir)) {
|
||||
try {
|
||||
const currentDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
||||
.filter(e => e.isDirectory())
|
||||
.map(e => e.name)
|
||||
.sort();
|
||||
for (const dir of currentDirs) {
|
||||
allPhaseDirs.push({ name: dir, fullPath: path.join(phasesDir, dir), milestone: null });
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
if (allPhaseDirs.length === 0) {
|
||||
digest.tech_stack = [];
|
||||
output(digest, raw);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const phaseDirs = fs.readdirSync(phasesDir, { withFileTypes: true })
|
||||
.filter(e => e.isDirectory())
|
||||
.map(e => e.name)
|
||||
.sort();
|
||||
|
||||
for (const dir of phaseDirs) {
|
||||
const dirPath = path.join(phasesDir, dir);
|
||||
for (const { name: dir, fullPath: dirPath } of allPhaseDirs) {
|
||||
const summaries = fs.readdirSync(dirPath).filter(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
|
||||
|
||||
for (const summary of summaries) {
|
||||
@@ -777,7 +794,7 @@ function cmdHistoryDigest(cwd, raw) {
|
||||
|
||||
function cmdPhasesList(cwd, options, raw) {
|
||||
const phasesDir = path.join(cwd, '.planning', 'phases');
|
||||
const { type, phase } = options;
|
||||
const { type, phase, includeArchived } = options;
|
||||
|
||||
// If no phases directory, return empty
|
||||
if (!fs.existsSync(phasesDir)) {
|
||||
@@ -794,6 +811,14 @@ function cmdPhasesList(cwd, options, raw) {
|
||||
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
||||
let dirs = entries.filter(e => e.isDirectory()).map(e => e.name);
|
||||
|
||||
// Include archived phases if requested
|
||||
if (includeArchived) {
|
||||
const archived = getArchivedPhaseDirs(cwd);
|
||||
for (const a of archived) {
|
||||
dirs.push(`${a.name} [${a.milestone}]`);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort numerically (handles decimals: 01, 02, 02.1, 02.2, 03)
|
||||
dirs.sort((a, b) => {
|
||||
const aNum = parseFloat(a.match(/^(\d+(?:\.\d+)?)/)?.[1] || '0');
|
||||
@@ -3338,6 +3363,22 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
||||
fs.writeFileSync(statePath, stateContent, 'utf-8');
|
||||
}
|
||||
|
||||
// Archive phase directories if requested
|
||||
let phasesArchived = false;
|
||||
if (options.archivePhases) {
|
||||
try {
|
||||
const phaseArchiveDir = path.join(archiveDir, `${version}-phases`);
|
||||
fs.mkdirSync(phaseArchiveDir, { recursive: true });
|
||||
|
||||
const phaseEntries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
||||
const phaseDirNames = phaseEntries.filter(e => e.isDirectory()).map(e => e.name);
|
||||
for (const dir of phaseDirNames) {
|
||||
fs.renameSync(path.join(phasesDir, dir), path.join(phaseArchiveDir, dir));
|
||||
}
|
||||
phasesArchived = phaseDirNames.length > 0;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const result = {
|
||||
version,
|
||||
name: milestoneName,
|
||||
@@ -3350,6 +3391,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
||||
roadmap: fs.existsSync(path.join(archiveDir, `${version}-ROADMAP.md`)),
|
||||
requirements: fs.existsSync(path.join(archiveDir, `${version}-REQUIREMENTS.md`)),
|
||||
audit: fs.existsSync(path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`)),
|
||||
phases: phasesArchived,
|
||||
},
|
||||
milestones_updated: true,
|
||||
state_updated: fs.existsSync(statePath),
|
||||
@@ -3662,14 +3704,44 @@ function resolveModelInternal(cwd, agentType) {
|
||||
return resolved === 'opus' ? 'inherit' : resolved;
|
||||
}
|
||||
|
||||
function findPhaseInternal(cwd, phase) {
|
||||
if (!phase) return null;
|
||||
function getArchivedPhaseDirs(cwd) {
|
||||
const milestonesDir = path.join(cwd, '.planning', 'milestones');
|
||||
const results = [];
|
||||
|
||||
const phasesDir = path.join(cwd, '.planning', 'phases');
|
||||
const normalized = normalizePhaseName(phase);
|
||||
if (!fs.existsSync(milestonesDir)) return results;
|
||||
|
||||
try {
|
||||
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
|
||||
const milestoneEntries = fs.readdirSync(milestonesDir, { withFileTypes: true });
|
||||
// Find v*-phases directories, sort newest first
|
||||
const phaseDirs = milestoneEntries
|
||||
.filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
|
||||
.map(e => e.name)
|
||||
.sort()
|
||||
.reverse();
|
||||
|
||||
for (const archiveName of phaseDirs) {
|
||||
const version = archiveName.match(/^(v[\d.]+)-phases$/)[1];
|
||||
const archivePath = path.join(milestonesDir, archiveName);
|
||||
const entries = fs.readdirSync(archivePath, { withFileTypes: true });
|
||||
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
|
||||
|
||||
for (const dir of dirs) {
|
||||
results.push({
|
||||
name: dir,
|
||||
milestone: version,
|
||||
basePath: path.join('.planning', 'milestones', archiveName),
|
||||
fullPath: path.join(archivePath, dir),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function searchPhaseInDir(baseDir, relBase, normalized) {
|
||||
try {
|
||||
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
||||
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort();
|
||||
const match = dirs.find(d => d.startsWith(normalized));
|
||||
if (!match) return null;
|
||||
@@ -3677,7 +3749,7 @@ function findPhaseInternal(cwd, phase) {
|
||||
const dirMatch = match.match(/^(\d+(?:\.\d+)?)-?(.*)/);
|
||||
const phaseNumber = dirMatch ? dirMatch[1] : normalized;
|
||||
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
||||
const phaseDir = path.join(phasesDir, match);
|
||||
const phaseDir = path.join(baseDir, match);
|
||||
const phaseFiles = fs.readdirSync(phaseDir);
|
||||
|
||||
const plans = phaseFiles.filter(f => f.endsWith('-PLAN.md') || f === 'PLAN.md').sort();
|
||||
@@ -3686,7 +3758,6 @@ function findPhaseInternal(cwd, phase) {
|
||||
const hasContext = phaseFiles.some(f => f.endsWith('-CONTEXT.md') || f === 'CONTEXT.md');
|
||||
const hasVerification = phaseFiles.some(f => f.endsWith('-VERIFICATION.md') || f === 'VERIFICATION.md');
|
||||
|
||||
// Determine incomplete plans (plans without matching summaries)
|
||||
const completedPlanIds = new Set(
|
||||
summaries.map(s => s.replace('-SUMMARY.md', '').replace('SUMMARY.md', ''))
|
||||
);
|
||||
@@ -3697,7 +3768,7 @@ function findPhaseInternal(cwd, phase) {
|
||||
|
||||
return {
|
||||
found: true,
|
||||
directory: path.join('.planning', 'phases', match),
|
||||
directory: path.join(relBase, match),
|
||||
phase_number: phaseNumber,
|
||||
phase_name: phaseName,
|
||||
phase_slug: phaseName ? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') : null,
|
||||
@@ -3713,6 +3784,43 @@ function findPhaseInternal(cwd, phase) {
|
||||
}
|
||||
}
|
||||
|
||||
function findPhaseInternal(cwd, phase) {
|
||||
if (!phase) return null;
|
||||
|
||||
const phasesDir = path.join(cwd, '.planning', 'phases');
|
||||
const normalized = normalizePhaseName(phase);
|
||||
|
||||
// Search current phases first
|
||||
const current = searchPhaseInDir(phasesDir, path.join('.planning', 'phases'), normalized);
|
||||
if (current) return current;
|
||||
|
||||
// Search archived milestone phases (newest first)
|
||||
const milestonesDir = path.join(cwd, '.planning', 'milestones');
|
||||
if (!fs.existsSync(milestonesDir)) return null;
|
||||
|
||||
try {
|
||||
const milestoneEntries = fs.readdirSync(milestonesDir, { withFileTypes: true });
|
||||
const archiveDirs = milestoneEntries
|
||||
.filter(e => e.isDirectory() && /^v[\d.]+-phases$/.test(e.name))
|
||||
.map(e => e.name)
|
||||
.sort()
|
||||
.reverse();
|
||||
|
||||
for (const archiveName of archiveDirs) {
|
||||
const version = archiveName.match(/^(v[\d.]+)-phases$/)[1];
|
||||
const archivePath = path.join(milestonesDir, archiveName);
|
||||
const relBase = path.join('.planning', 'milestones', archiveName);
|
||||
const result = searchPhaseInDir(archivePath, relBase, normalized);
|
||||
if (result) {
|
||||
result.archived = version;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getRoadmapPhaseInternal(cwd, phaseNum) {
|
||||
if (!phaseNum) return null;
|
||||
const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
|
||||
@@ -4660,6 +4768,7 @@ async function main() {
|
||||
const options = {
|
||||
type: typeIndex !== -1 ? args[typeIndex + 1] : null,
|
||||
phase: phaseIndex !== -1 ? args[phaseIndex + 1] : null,
|
||||
includeArchived: args.includes('--include-archived'),
|
||||
};
|
||||
cmdPhasesList(cwd, options, raw);
|
||||
} else {
|
||||
@@ -4705,8 +4814,18 @@ async function main() {
|
||||
const subcommand = args[1];
|
||||
if (subcommand === 'complete') {
|
||||
const nameIndex = args.indexOf('--name');
|
||||
const milestoneName = nameIndex !== -1 ? args.slice(nameIndex + 1).join(' ') : null;
|
||||
cmdMilestoneComplete(cwd, args[2], { name: milestoneName }, raw);
|
||||
const archivePhases = args.includes('--archive-phases');
|
||||
// Collect --name value (everything after --name until next flag or end)
|
||||
let milestoneName = null;
|
||||
if (nameIndex !== -1) {
|
||||
const nameArgs = [];
|
||||
for (let i = nameIndex + 1; i < args.length; i++) {
|
||||
if (args[i].startsWith('--')) break;
|
||||
nameArgs.push(args[i]);
|
||||
}
|
||||
milestoneName = nameArgs.join(' ') || null;
|
||||
}
|
||||
cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases }, raw);
|
||||
} else {
|
||||
error('Unknown milestone subcommand. Available: complete');
|
||||
}
|
||||
|
||||
@@ -38,9 +38,10 @@ node ~/.claude/get-shit-done/bin/gsd-tools.cjs phases list
|
||||
For each phase directory, read the VERIFICATION.md:
|
||||
|
||||
```bash
|
||||
cat .planning/phases/01-*/*-VERIFICATION.md
|
||||
cat .planning/phases/02-*/*-VERIFICATION.md
|
||||
# etc.
|
||||
# For each phase, use find-phase to resolve the directory (handles archived phases)
|
||||
PHASE_INFO=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs find-phase 01 --raw)
|
||||
# Extract directory from JSON, then read VERIFICATION.md from that directory
|
||||
# Repeat for each phase number from ROADMAP.md
|
||||
```
|
||||
|
||||
From each VERIFICATION.md, extract:
|
||||
|
||||
152
get-shit-done/workflows/cleanup.md
Normal file
152
get-shit-done/workflows/cleanup.md
Normal file
@@ -0,0 +1,152 @@
|
||||
<purpose>
|
||||
|
||||
Archive accumulated phase directories from completed milestones into `.planning/milestones/v{X.Y}-phases/`. Identifies which phases belong to each completed milestone, shows a dry-run summary, and moves directories on confirmation.
|
||||
|
||||
</purpose>
|
||||
|
||||
<required_reading>
|
||||
|
||||
1. `.planning/MILESTONES.md`
|
||||
2. `.planning/milestones/` directory listing
|
||||
3. `.planning/phases/` directory listing
|
||||
|
||||
</required_reading>
|
||||
|
||||
<process>
|
||||
|
||||
<step name="identify_completed_milestones">
|
||||
|
||||
Read `.planning/MILESTONES.md` to identify completed milestones and their versions.
|
||||
|
||||
```bash
|
||||
cat .planning/MILESTONES.md
|
||||
```
|
||||
|
||||
Extract each milestone version (e.g., v1.0, v1.1, v2.0).
|
||||
|
||||
Check which milestone archive dirs already exist:
|
||||
|
||||
```bash
|
||||
ls -d .planning/milestones/v*-phases 2>/dev/null
|
||||
```
|
||||
|
||||
Filter to milestones that do NOT already have a `-phases` archive directory.
|
||||
|
||||
If all milestones already have phase archives:
|
||||
|
||||
```
|
||||
All completed milestones already have phase directories archived. Nothing to clean up.
|
||||
```
|
||||
|
||||
Stop here.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="determine_phase_membership">
|
||||
|
||||
For each completed milestone without a `-phases` archive, read the archived ROADMAP snapshot to determine which phases belong to it:
|
||||
|
||||
```bash
|
||||
cat .planning/milestones/v{X.Y}-ROADMAP.md
|
||||
```
|
||||
|
||||
Extract phase numbers and names from the archived roadmap (e.g., Phase 1: Foundation, Phase 2: Auth).
|
||||
|
||||
Check which of those phase directories still exist in `.planning/phases/`:
|
||||
|
||||
```bash
|
||||
ls -d .planning/phases/*/ 2>/dev/null
|
||||
```
|
||||
|
||||
Match phase directories to milestone membership. Only include directories that still exist in `.planning/phases/`.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="show_dry_run">
|
||||
|
||||
Present a dry-run summary for each milestone:
|
||||
|
||||
```
|
||||
## Cleanup Summary
|
||||
|
||||
### v{X.Y} — {Milestone Name}
|
||||
These phase directories will be archived:
|
||||
- 01-foundation/
|
||||
- 02-auth/
|
||||
- 03-core-features/
|
||||
|
||||
Destination: .planning/milestones/v{X.Y}-phases/
|
||||
|
||||
### v{X.Z} — {Milestone Name}
|
||||
These phase directories will be archived:
|
||||
- 04-security/
|
||||
- 05-hardening/
|
||||
|
||||
Destination: .planning/milestones/v{X.Z}-phases/
|
||||
```
|
||||
|
||||
If no phase directories remain to archive (all already moved or deleted):
|
||||
|
||||
```
|
||||
No phase directories found to archive. Phases may have been removed or archived previously.
|
||||
```
|
||||
|
||||
Stop here.
|
||||
|
||||
AskUserQuestion: "Proceed with archiving?" with options: "Yes — archive listed phases" | "Cancel"
|
||||
|
||||
If "Cancel": Stop.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="archive_phases">
|
||||
|
||||
For each milestone, move phase directories:
|
||||
|
||||
```bash
|
||||
mkdir -p .planning/milestones/v{X.Y}-phases
|
||||
```
|
||||
|
||||
For each phase directory belonging to this milestone:
|
||||
|
||||
```bash
|
||||
mv .planning/phases/{dir} .planning/milestones/v{X.Y}-phases/
|
||||
```
|
||||
|
||||
Repeat for all milestones in the cleanup set.
|
||||
|
||||
</step>
|
||||
|
||||
<step name="commit">
|
||||
|
||||
Commit the changes:
|
||||
|
||||
```bash
|
||||
node ~/.claude/get-shit-done/bin/gsd-tools.cjs commit "chore: archive phase directories from completed milestones" --files .planning/milestones/ .planning/phases/
|
||||
```
|
||||
|
||||
</step>
|
||||
|
||||
<step name="report">
|
||||
|
||||
```
|
||||
Archived:
|
||||
{For each milestone}
|
||||
- v{X.Y}: {N} phase directories → .planning/milestones/v{X.Y}-phases/
|
||||
|
||||
.planning/phases/ cleaned up.
|
||||
```
|
||||
|
||||
</step>
|
||||
|
||||
</process>
|
||||
|
||||
<success_criteria>
|
||||
|
||||
- [ ] All completed milestones without existing phase archives identified
|
||||
- [ ] Phase membership determined from archived ROADMAP snapshots
|
||||
- [ ] Dry-run summary shown and user confirmed
|
||||
- [ ] Phase directories moved to `.planning/milestones/v{X.Y}-phases/`
|
||||
- [ ] Changes committed
|
||||
|
||||
</success_criteria>
|
||||
@@ -359,7 +359,19 @@ Extract from result: `version`, `date`, `phases`, `plans`, `tasks`, `accomplishm
|
||||
|
||||
Verify: `✅ Milestone archived to .planning/milestones/`
|
||||
|
||||
**Note:** Phase directories (`.planning/phases/`) are NOT deleted — they accumulate across milestones as raw execution history. Phase numbering continues (v1.0 phases 1-4, v1.1 phases 5-8, etc.).
|
||||
**Phase archival (optional):** After archival completes, ask the user:
|
||||
|
||||
AskUserQuestion(header="Archive Phases", question="Archive phase directories to milestones/?", options: "Yes — move to milestones/v[X.Y]-phases/" | "Skip — keep phases in place")
|
||||
|
||||
If "Yes": move phase directories to the milestone archive:
|
||||
```bash
|
||||
mkdir -p .planning/milestones/v[X.Y]-phases
|
||||
# For each phase directory in .planning/phases/:
|
||||
mv .planning/phases/{phase-dir} .planning/milestones/v[X.Y]-phases/
|
||||
```
|
||||
Verify: `✅ Phase directories archived to .planning/milestones/v[X.Y]-phases/`
|
||||
|
||||
If "Skip": Phase directories remain in `.planning/phases/` as raw execution history. Use `/gsd:cleanup` later to archive retroactively.
|
||||
|
||||
After archival, the AI still handles:
|
||||
- Reorganizing ROADMAP.md with milestone grouping (requires judgment)
|
||||
|
||||
@@ -241,7 +241,8 @@ fi
|
||||
|
||||
**2. Find parent UAT file:**
|
||||
```bash
|
||||
find .planning/phases -path "*${PARENT_PHASE}*/*-UAT.md" -type f 2>/dev/null
|
||||
PARENT_INFO=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs find-phase "${PARENT_PHASE}" --raw)
|
||||
# Extract directory from PARENT_INFO JSON, then find UAT file in that directory
|
||||
```
|
||||
|
||||
**If no parent UAT found:** Skip this step (gap-closure may have been triggered by VERIFICATION.md instead).
|
||||
|
||||
@@ -15,15 +15,7 @@ Read config.json for planning behavior settings.
|
||||
Load execution context (uses `init execute-phase` for full context, including file contents):
|
||||
|
||||
```bash
|
||||
INIT_RAW=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init execute-phase "${PHASE}" --include state,config)
|
||||
# Large payloads are written to a tmpfile — output starts with @file:/path
|
||||
if [[ "$INIT_RAW" == @file:* ]]; then
|
||||
INIT_FILE="${INIT_RAW#@file:}"
|
||||
INIT=$(cat "$INIT_FILE")
|
||||
rm -f "$INIT_FILE"
|
||||
else
|
||||
INIT="$INIT_RAW"
|
||||
fi
|
||||
INIT=$(node ~/.claude/get-shit-done/bin/gsd-tools.cjs init execute-phase "${PHASE}" --include state,config)
|
||||
```
|
||||
|
||||
Extract from init JSON: `executor_model`, `commit_docs`, `phase_dir`, `phase_number`, `plans`, `summaries`, `incomplete_plans`.
|
||||
@@ -136,7 +128,8 @@ This IS the execution instructions. Follow exactly. If plan references CONTEXT.m
|
||||
|
||||
<step name="previous_phase_check">
|
||||
```bash
|
||||
ls .planning/phases/*/SUMMARY.md 2>/dev/null | sort -r | head -2 | tail -1
|
||||
node ~/.claude/get-shit-done/bin/gsd-tools.cjs phases list --type summaries --raw
|
||||
# Extract the second-to-last summary from the JSON result
|
||||
```
|
||||
If previous SUMMARY has unresolved "Issues Encountered" or "Next Phase Readiness" blockers: AskUserQuestion(header="Previous Issues", options: "Proceed anyway" | "Address first" | "Review previous").
|
||||
</step>
|
||||
|
||||
@@ -312,6 +312,16 @@ Usage: `/gsd:set-profile budget`
|
||||
|
||||
### Utility Commands
|
||||
|
||||
**`/gsd:cleanup`**
|
||||
Archive accumulated phase directories from completed milestones.
|
||||
|
||||
- Identifies phases from completed milestones still in `.planning/phases/`
|
||||
- Shows dry-run summary before moving anything
|
||||
- Moves phase dirs to `.planning/milestones/v{X.Y}-phases/`
|
||||
- Use after multiple milestones to reduce `.planning/phases/` clutter
|
||||
|
||||
Usage: `/gsd:cleanup`
|
||||
|
||||
**`/gsd:help`**
|
||||
Show this command reference.
|
||||
|
||||
@@ -347,6 +357,12 @@ Usage: `/gsd:join-discord`
|
||||
│ └── done/ # Completed todos
|
||||
├── debug/ # Active debug sessions
|
||||
│ └── resolved/ # Archived resolved issues
|
||||
├── milestones/
|
||||
│ ├── v1.0-ROADMAP.md # Archived roadmap snapshot
|
||||
│ ├── v1.0-REQUIREMENTS.md # Archived requirements
|
||||
│ └── v1.0-phases/ # Archived phase dirs (via /gsd:cleanup or --archive-phases)
|
||||
│ ├── 01-foundation/
|
||||
│ └── 02-core-features/
|
||||
├── codebase/ # Codebase map (brownfield projects)
|
||||
│ ├── STACK.md # Languages, frameworks, dependencies
|
||||
│ ├── ARCHITECTURE.md # Patterns, layers, data flow
|
||||
|
||||
Reference in New Issue
Block a user