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 table parsing in `phase complete` and `roadmap update-plan-progress` now handles 5-column tables (with Milestone column) without eating columns

## [1.22.4] - 2026-03-03

### Added
Expand Down
25 changes: 17 additions & 8 deletions get-shit-done/bin/lib/phase.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -729,16 +729,25 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
);
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);

// Progress table: update Status to Complete, add date
// Progress table: update Status to Complete, add date (handles 4 or 5 column tables)
const phaseEscaped = escapeRegex(phaseNum);
const tablePattern = new RegExp(
`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|[^|]*\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`,
'i'
);
roadmapContent = roadmapContent.replace(
tablePattern,
`$1 Complete $2 ${today} $3`
const tableRowPattern = new RegExp(
`^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`,
'im'
);
roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
const cells = fullRow.split('|').slice(1, -1);
if (cells.length === 5) {
// 5-col: Phase | Milestone | Plans | Status | Completed
cells[3] = ' Complete ';
cells[4] = ` ${today} `;
} else if (cells.length === 4) {
// 4-col: Phase | Plans | Status | Completed
cells[2] = ' Complete ';
cells[3] = ` ${today} `;
}
return '|' + cells.join('|') + '|';
});

// Update plan count in phase section
const planCountPattern = new RegExp(
Expand Down
27 changes: 19 additions & 8 deletions get-shit-done/bin/lib/roadmap.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -249,16 +249,27 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
let roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
const phaseEscaped = escapeRegex(phaseNum);

// Progress table row: update Plans column (summaries/plans) and Status column
const tablePattern = new RegExp(
`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|)[^|]*(\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`,
'i'
// Progress table row: update Plans/Status/Date columns (handles 4 or 5 column tables)
const tableRowPattern = new RegExp(
`^(\\|\\s*${phaseEscaped}\\.?\\s[^|]*(?:\\|[^\\n]*))$`,
'im'
);
const dateField = isComplete ? ` ${today} ` : ' ';
roadmapContent = roadmapContent.replace(
tablePattern,
`$1 ${summaryCount}/${planCount} $2 ${status.padEnd(11)}$3${dateField}$4`
);
roadmapContent = roadmapContent.replace(tableRowPattern, (fullRow) => {
const cells = fullRow.split('|').slice(1, -1); // drop leading/trailing empty from split
if (cells.length === 5) {
// 5-col: Phase | Milestone | Plans | Status | Completed
cells[2] = ` ${summaryCount}/${planCount} `;
cells[3] = ` ${status.padEnd(11)}`;
cells[4] = dateField;
} else if (cells.length === 4) {
// 4-col: Phase | Plans | Status | Completed
cells[1] = ` ${summaryCount}/${planCount} `;
cells[2] = ` ${status.padEnd(11)}`;
cells[3] = dateField;
}
return '|' + cells.join('|') + '|';
});

// Update plan count in phase detail section
const planCountPattern = new RegExp(
Expand Down
40 changes: 40 additions & 0 deletions tests/phase.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,46 @@ describe('phase complete command', () => {
const req = fs.readFileSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), 'utf-8');
assert.ok(req.includes('- [ ] **AMT-01**'), 'AMT-01 should remain unchanged');
});

test('preserves Milestone column in 5-column progress table', () => {
fs.writeFileSync(
path.join(tmpDir, '.planning', 'ROADMAP.md'),
`# Roadmap

- [ ] Phase 1: Foundation

### Phase 1: Foundation
**Goal:** Setup
**Plans:** 1 plans

## Progress

| Phase | Milestone | Plans Complete | Status | Completed |
|-------|-----------|----------------|--------|-----------|
| 1. Foundation | v1.0 | 0/1 | Planned | |
`
);
fs.writeFileSync(
path.join(tmpDir, '.planning', 'STATE.md'),
`# State\n\n**Current Phase:** 01\n**Status:** In progress\n**Current Plan:** 01-01\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working\n`
);

const p1 = path.join(tmpDir, '.planning', 'phases', '01-foundation');
fs.mkdirSync(p1, { recursive: true });
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');

const result = runGsdTools('phase complete 1', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);

const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
const rowMatch = roadmap.match(/^\|[^\n]*1\. Foundation[^\n]*$/m);
assert.ok(rowMatch, 'table row should exist');
const cells = rowMatch[0].split('|').slice(1, -1).map(c => c.trim());
assert.strictEqual(cells.length, 5, 'should have 5 columns');
assert.strictEqual(cells[1], 'v1.0', 'Milestone column should be preserved');
assert.ok(cells[3].includes('Complete'), 'Status column should be Complete');
});
});

// ─────────────────────────────────────────────────────────────────────────────
Expand Down
32 changes: 32 additions & 0 deletions tests/roadmap.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,38 @@ describe('roadmap update-plan-progress command', () => {
assert.strictEqual(output.updated, false, 'should not update');
assert.ok(output.reason.includes('ROADMAP.md not found'), 'reason should mention missing ROADMAP.md');
});

test('preserves Milestone column in 5-column progress table', () => {
const roadmapContent = `# Roadmap

### Phase 50: Build
**Goal:** Build stuff
**Plans:** 1 plans

## Progress

| Phase | Milestone | Plans Complete | Status | Completed |
|-------|-----------|----------------|--------|-----------|
| 50. Build | v2.0 | 0/1 | Planned | |
`;
fs.writeFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), roadmapContent);

const p50 = path.join(tmpDir, '.planning', 'phases', '50-build');
fs.mkdirSync(p50, { recursive: true });
fs.writeFileSync(path.join(p50, '50-01-PLAN.md'), '# Plan');
fs.writeFileSync(path.join(p50, '50-01-SUMMARY.md'), '# Summary');

const result = runGsdTools('roadmap update-plan-progress 50', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);

const roadmap = fs.readFileSync(path.join(tmpDir, '.planning', 'ROADMAP.md'), 'utf-8');
const rowMatch = roadmap.match(/^\|[^\n]*50\. Build[^\n]*$/m);
assert.ok(rowMatch, 'table row should exist');
const cells = rowMatch[0].split('|').slice(1, -1).map(c => c.trim());
assert.strictEqual(cells.length, 5, 'should have 5 columns');
assert.strictEqual(cells[1], 'v2.0', 'Milestone column should be preserved');
assert.ok(cells[3].includes('Complete'), 'Status column should show Complete');
});
});

// ─────────────────────────────────────────────────────────────────────────────
Expand Down