Merge pull request #1124 from trek-e/refactor/deduplicate-and-cleanup

This commit is contained in:
Tom Boucher
2026-03-18 18:06:25 -04:00
committed by GitHub
9 changed files with 98 additions and 72 deletions

View File

@@ -71,9 +71,9 @@ function cmdListTodos(cwd, area, raw) {
area: todoArea,
path: toPosixPath(path.join('.planning', 'todos', 'pending', file)),
});
} catch {}
} catch { /* intentionally empty */ }
}
} catch {}
} catch { /* intentionally empty */ }
const result = { count, todos };
output(result, raw, count.toString());
@@ -120,7 +120,7 @@ function cmdHistoryDigest(cwd, raw) {
for (const dir of currentDirs) {
allPhaseDirs.push({ name: dir, fullPath: path.join(phasesDir, dir), milestone: null });
}
} catch {}
} catch { /* intentionally empty */ }
}
if (allPhaseDirs.length === 0) {
@@ -412,7 +412,7 @@ function cmdProgressRender(cwd, format, raw) {
phases.push({ number: phaseNum, name: phaseName, plans, summaries, status });
}
} catch {}
} catch { /* intentionally empty */ }
const percent = totalPlans > 0 ? Math.min(100, Math.round((totalSummaries / totalPlans) * 100)) : 0;
@@ -559,7 +559,7 @@ function cmdStats(cwd, format, raw) {
status: 'Not Started',
});
}
} catch {}
} catch { /* intentionally empty */ }
try {
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
@@ -595,7 +595,7 @@ function cmdStats(cwd, format, raw) {
status,
});
}
} catch {}
} catch { /* intentionally empty */ }
const phases = [...phasesByNumber.values()].sort((a, b) => comparePhaseNum(a.number, b.number));
const completedPhases = phases.filter(p => p.status === 'Complete').length;
@@ -613,7 +613,7 @@ function cmdStats(cwd, format, raw) {
requirementsComplete = checked ? checked.length : 0;
requirementsTotal = requirementsComplete + (unchecked ? unchecked.length : 0);
}
} catch {}
} catch { /* intentionally empty */ }
// Last activity from STATE.md
let lastActivity = null;
@@ -626,7 +626,7 @@ function cmdStats(cwd, format, raw) {
|| stateContent.match(/^Last activity:\s*(.+)$/im);
if (activityMatch) lastActivity = activityMatch[1].trim();
}
} catch {}
} catch { /* intentionally empty */ }
// Git stats
let gitCommits = 0;

View File

@@ -76,7 +76,7 @@ function ensureConfigFile(cwd) {
delete userDefaults.depth;
try {
fs.writeFileSync(globalDefaultsPath, JSON.stringify(userDefaults, null, 2), 'utf-8');
} catch {}
} catch { /* intentionally empty */ }
}
}
} catch (err) {

View File

@@ -76,7 +76,7 @@ function loadConfig(cwd) {
const depthToGranularity = { quick: 'coarse', standard: 'standard', comprehensive: 'fine' };
parsed.granularity = depthToGranularity[parsed.depth] || parsed.depth;
delete parsed.depth;
try { fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf-8'); } catch {}
try { fs.writeFileSync(configPath, JSON.stringify(parsed, null, 2), 'utf-8'); } catch { /* intentionally empty */ }
}
const get = (key, nested) => {
@@ -250,6 +250,27 @@ function execGit(cwd, args) {
};
}
// ─── Common path helpers ──────────────────────────────────────────────────────
/** Get the .planning directory path */
function planningDir(cwd) {
return path.join(cwd, '.planning');
}
/** Get common .planning file paths */
function planningPaths(cwd) {
const base = path.join(cwd, '.planning');
return {
planning: base,
state: path.join(base, 'STATE.md'),
roadmap: path.join(base, 'ROADMAP.md'),
project: path.join(base, 'PROJECT.md'),
config: path.join(base, 'config.json'),
phases: path.join(base, 'phases'),
requirements: path.join(base, 'REQUIREMENTS.md'),
};
}
// ─── Phase utilities ──────────────────────────────────────────────────────────
function escapeRegex(value) {
@@ -370,7 +391,7 @@ function findPhaseInternal(cwd, phase) {
return result;
}
}
} catch {}
} catch { /* intentionally empty */ }
return null;
}
@@ -405,7 +426,7 @@ function getArchivedPhaseDirs(cwd) {
});
}
}
} catch {}
} catch { /* intentionally empty */ }
return results;
}
@@ -663,7 +684,7 @@ function getMilestonePhaseFilter(cwd) {
while ((m = phasePattern.exec(roadmap)) !== null) {
milestonePhaseNums.add(m[1]);
}
} catch {}
} catch { /* intentionally empty */ }
if (milestonePhaseNums.size === 0) {
const passAll = () => true;
@@ -709,4 +730,6 @@ module.exports = {
replaceInCurrentMilestone,
toPosixPath,
MODEL_ALIAS_MAP,
planningDir,
planningPaths,
};

View File

@@ -153,7 +153,7 @@ function cmdInitPlanPhase(cwd, phase, raw) {
if (uatFile) {
result.uat_path = toPosixPath(path.join(phaseInfo.directory, uatFile));
}
} catch {}
} catch { /* intentionally empty */ }
}
output(result, raw);
@@ -167,17 +167,26 @@ function cmdInitNewProject(cwd, raw) {
const braveKeyFile = path.join(homedir, '.gsd', 'brave_api_key');
const hasBraveSearch = !!(process.env.BRAVE_API_KEY || fs.existsSync(braveKeyFile));
// Detect existing code
// Detect existing code (cross-platform — no Unix `find` dependency)
let hasCode = false;
let hasPackageFile = false;
try {
const files = execSync('find . -maxdepth 3 \\( -name "*.ts" -o -name "*.js" -o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.swift" -o -name "*.java" \\) 2>/dev/null | grep -v node_modules | grep -v .git | head -5', {
cwd,
encoding: 'utf-8',
stdio: ['pipe', 'pipe', 'pipe'],
});
hasCode = files.trim().length > 0;
} catch {}
const codeExtensions = new Set(['.ts', '.js', '.py', '.go', '.rs', '.swift', '.java']);
const skipDirs = new Set(['node_modules', '.git', '.planning', '.claude', '__pycache__', 'target', 'dist', 'build']);
function findCodeFiles(dir, depth) {
if (depth > 3) return false;
let entries;
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return false; }
for (const entry of entries) {
if (entry.isFile() && codeExtensions.has(path.extname(entry.name))) return true;
if (entry.isDirectory() && !skipDirs.has(entry.name)) {
if (findCodeFiles(path.join(dir, entry.name), depth + 1)) return true;
}
}
return false;
}
hasCode = findCodeFiles(cwd, 0);
} catch { /* intentionally empty — best-effort detection */ }
hasPackageFile = pathExistsInternal(cwd, 'package.json') ||
pathExistsInternal(cwd, 'requirements.txt') ||
@@ -307,7 +316,7 @@ function cmdInitResume(cwd, raw) {
let interruptedAgentId = null;
try {
interruptedAgentId = fs.readFileSync(path.join(cwd, '.planning', 'current-agent-id.txt'), 'utf-8').trim();
} catch {}
} catch { /* intentionally empty */ }
const result = {
// File existence
@@ -459,7 +468,7 @@ function cmdInitPhaseOp(cwd, phase, raw) {
if (uatFile) {
result.uat_path = toPosixPath(path.join(phaseInfo.directory, uatFile));
}
} catch {}
} catch { /* intentionally empty */ }
}
output(result, raw);
@@ -494,9 +503,9 @@ function cmdInitTodos(cwd, area, raw) {
area: todoArea,
path: '.planning/todos/pending/' + file,
});
} catch {}
} catch { /* intentionally empty */ }
}
} catch {}
} catch { /* intentionally empty */ }
const result = {
// Config
@@ -543,9 +552,9 @@ function cmdInitMilestoneOp(cwd, raw) {
const phaseFiles = fs.readdirSync(path.join(phasesDir, dir));
const hasSummary = phaseFiles.some(f => f.endsWith('-SUMMARY.md') || f === 'SUMMARY.md');
if (hasSummary) completedPhases++;
} catch {}
} catch { /* intentionally empty */ }
}
} catch {}
} catch { /* intentionally empty */ }
// Check archive
const archiveDir = path.join(cwd, '.planning', 'archive');
@@ -554,7 +563,7 @@ function cmdInitMilestoneOp(cwd, raw) {
archivedMilestones = fs.readdirSync(archiveDir, { withFileTypes: true })
.filter(e => e.isDirectory())
.map(e => e.name);
} catch {}
} catch { /* intentionally empty */ }
const result = {
// Config
@@ -593,7 +602,7 @@ function cmdInitMapCodebase(cwd, raw) {
let existingMaps = [];
try {
existingMaps = fs.readdirSync(codebaseDir).filter(f => f.endsWith('.md'));
} catch {}
} catch { /* intentionally empty */ }
const result = {
// Models
@@ -642,7 +651,7 @@ function cmdInitProgress(cwd, raw) {
roadmapPhaseNums.add(hm[1]);
roadmapPhaseNames.set(hm[1], hm[2].replace(/\(INSERTED\)/i, '').trim());
}
} catch {}
} catch { /* intentionally empty */ }
const isDirInMilestone = getMilestonePhaseFilter(cwd);
const seenPhaseNums = new Set();
@@ -695,7 +704,7 @@ function cmdInitProgress(cwd, raw) {
nextPhase = phaseInfo;
}
}
} catch {}
} catch { /* intentionally empty */ }
// Add phases defined in ROADMAP but not yet scaffolded to disk
for (const [num, name] of roadmapPhaseNames) {
@@ -726,7 +735,7 @@ function cmdInitProgress(cwd, raw) {
const state = fs.readFileSync(path.join(cwd, '.planning', 'STATE.md'), 'utf-8');
const pauseMatch = state.match(/\*\*Paused At:\*\*\s*(.+)/);
if (pauseMatch) pausedAt = pauseMatch[1].trim();
} catch {}
} catch { /* intentionally empty */ }
const result = {
// Models

View File

@@ -137,10 +137,10 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
// Count tasks
const taskMatches = content.match(/##\s*Task\s*\d+/gi) || [];
totalTasks += taskMatches.length;
} catch {}
} catch { /* intentionally empty */ }
}
}
} catch {}
} catch { /* intentionally empty */ }
// Archive ROADMAP.md
if (fs.existsSync(roadmapPath)) {
@@ -220,7 +220,7 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
archivedCount++;
}
phasesArchived = archivedCount > 0;
} catch {}
} catch { /* intentionally empty */ }
}
const result = {

View File

@@ -401,7 +401,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
const dm = dir.match(decimalPattern);
if (dm) existingDecimals.push(parseInt(dm[1], 10));
}
} catch {}
} catch { /* intentionally empty */ }
const nextDecimal = existingDecimals.length === 0 ? 1 : Math.max(...existingDecimals) + 1;
const decimalPhase = `${normalizedBase}.${nextDecimal}`;
@@ -470,7 +470,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
const entries = fs.readdirSync(phasesDir, { withFileTypes: true });
const dirs = entries.filter(e => e.isDirectory()).map(e => e.name).sort((a, b) => comparePhaseNum(a, b));
targetDir = dirs.find(d => d.startsWith(normalized + '-') || d === normalized);
} catch {}
} catch { /* intentionally empty */ }
// Check for executed work (SUMMARY.md files)
if (targetDir && !force) {
@@ -538,7 +538,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
}
}
}
} catch {}
} catch { /* intentionally empty */ }
} else {
// Integer removal: renumber all subsequent integer phases
@@ -598,7 +598,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
}
}
}
} catch {}
} catch { /* intentionally empty */ }
}
// Update ROADMAP.md
@@ -818,7 +818,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
}
}
}
} catch {}
} catch { /* intentionally empty */ }
// Fallback: if filesystem found no next phase, check ROADMAP.md
// for phases that are defined but not yet planned (no directory on disk)
@@ -835,7 +835,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
break;
}
}
} catch {}
} catch { /* intentionally empty */ }
}
// Update STATE.md

View File

@@ -151,7 +151,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
else if (hasContext) diskStatus = 'discussed';
else diskStatus = 'empty';
}
} catch {}
} catch { /* intentionally empty */ }
// Check ROADMAP checkbox status
const checkboxPattern = new RegExp(`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${escapeRegex(phaseNum)}[:\\s]`, 'i');

View File

@@ -4,9 +4,14 @@
const fs = require('fs');
const path = require('path');
const { escapeRegex, loadConfig, getMilestoneInfo, getMilestonePhaseFilter, normalizeMd, output, error } = require('./core.cjs');
const { escapeRegex, loadConfig, getMilestoneInfo, getMilestonePhaseFilter, normalizeMd, planningPaths, output, error } = require('./core.cjs');
const { extractFrontmatter, reconstructFrontmatter } = require('./frontmatter.cjs');
/** Shorthand — every state command needs this path */
function getStatePath(cwd) {
return planningPaths(cwd).state;
}
// Shared helper: extract a field value from STATE.md content.
// Supports both **Field:** bold and plain Field: format.
function stateExtractField(content, fieldName) {
@@ -26,7 +31,7 @@ function cmdStateLoad(cwd, raw) {
let stateRaw = '';
try {
stateRaw = fs.readFileSync(path.join(planningDir, 'STATE.md'), 'utf-8');
} catch {}
} catch { /* intentionally empty */ }
const configExists = fs.existsSync(path.join(planningDir, 'config.json'));
const roadmapExists = fs.existsSync(path.join(planningDir, 'ROADMAP.md'));
@@ -75,7 +80,7 @@ function cmdStateGet(cwd, section, raw) {
}
// Try to find markdown section or field
const fieldEscaped = section.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const fieldEscaped = escapeRegex(section);
// Check for **field:** value (bold format)
const boldPattern = new RegExp(`\\*\\*${fieldEscaped}:\\*\\*\\s*(.*)`, 'i');
@@ -125,7 +130,7 @@ function cmdStatePatch(cwd, patches, raw) {
const results = { updated: [], failed: [] };
for (const [field, value] of Object.entries(patches)) {
const fieldEscaped = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const fieldEscaped = escapeRegex(field);
// Try **Field:** bold format first, then plain Field: format
const boldPattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, 'i');
const plainPattern = new RegExp(`(^${fieldEscaped}:\\s*)(.*)`, 'im');
@@ -159,7 +164,7 @@ function cmdStateUpdate(cwd, field, value) {
const statePath = path.join(cwd, '.planning', 'STATE.md');
try {
let content = fs.readFileSync(statePath, 'utf-8');
const fieldEscaped = field.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const fieldEscaped = escapeRegex(field);
// Try **Field:** bold format first, then plain Field: format
const boldPattern = new RegExp(`(\\*\\*${fieldEscaped}:\\*\\*\\s*)(.*)`, 'i');
const plainPattern = new RegExp(`(^${fieldEscaped}:\\s*)(.*)`, 'im');
@@ -180,21 +185,10 @@ function cmdStateUpdate(cwd, field, value) {
}
// ─── State Progression Engine ────────────────────────────────────────────────
function stateExtractField(content, fieldName) {
const escaped = fieldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Try **Field:** bold format first
const boldPattern = new RegExp(`\\*\\*${escaped}:\\*\\*\\s*(.+)`, 'i');
const boldMatch = content.match(boldPattern);
if (boldMatch) return boldMatch[1].trim();
// Fall back to plain Field: format
const plainPattern = new RegExp(`^${escaped}:\\s*(.+)`, 'im');
const plainMatch = content.match(plainPattern);
return plainMatch ? plainMatch[1].trim() : null;
}
// stateExtractField is defined above (shared helper) — do not duplicate.
function stateReplaceField(content, fieldName, newValue) {
const escaped = fieldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const escaped = escapeRegex(fieldName);
// Try **Field:** bold format first, then plain Field: format
const boldPattern = new RegExp(`(\\*\\*${escaped}:\\*\\*\\s*)(.*)`, 'i');
if (boldPattern.test(content)) {
@@ -576,7 +570,7 @@ function buildStateFrontmatter(bodyContent, cwd) {
const info = getMilestoneInfo(cwd);
milestone = info.version;
milestoneName = info.name;
} catch {}
} catch { /* intentionally empty */ }
}
let totalPhases = totalPhasesRaw ? parseInt(totalPhasesRaw, 10) : null;
@@ -611,7 +605,7 @@ function buildStateFrontmatter(bodyContent, cwd) {
totalPlans = diskTotalPlans;
completedPlans = diskTotalSummaries;
}
} catch {}
} catch { /* intentionally empty */ }
}
let progressPercent = null;

View File

@@ -428,7 +428,7 @@ function cmdValidateConsistency(cwd, raw) {
const dm = dir.match(/^(\d+[A-Z]?(?:\.\d+)*)/i);
if (dm) diskPhases.add(dm[1]);
}
} catch {}
} catch { /* intentionally empty */ }
// Check: phases in ROADMAP but not on disk
for (const p of roadmapPhases) {
@@ -490,7 +490,7 @@ function cmdValidateConsistency(cwd, raw) {
}
}
}
} catch {}
} catch { /* intentionally empty */ }
// Check: frontmatter in plans has required fields
try {
@@ -510,7 +510,7 @@ function cmdValidateConsistency(cwd, raw) {
}
}
}
} catch {}
} catch { /* intentionally empty */ }
const passed = errors.length === 0;
output({ passed, errors, warnings, warning_count: warnings.length }, raw, passed ? 'passed' : 'failed');
@@ -599,7 +599,7 @@ function cmdValidateHealth(cwd, options, raw) {
if (m) diskPhases.add(m[1]);
}
}
} catch {}
} catch { /* intentionally empty */ }
// Check for invalid references
for (const ref of phaseRefs) {
const normalizedRef = String(parseInt(ref, 10)).padStart(2, '0');
@@ -641,7 +641,7 @@ function cmdValidateHealth(cwd, options, raw) {
addIssue('warning', 'W008', 'config.json: workflow.nyquist_validation absent (defaults to enabled but agents may skip)', 'Run /gsd:health --repair to add key', true);
if (!repairs.includes('addNyquistKey')) repairs.push('addNyquistKey');
}
} catch {}
} catch { /* intentionally empty */ }
}
// ─── Check 6: Phase directory naming (NN-name format) ─────────────────────
@@ -652,7 +652,7 @@ function cmdValidateHealth(cwd, options, raw) {
addIssue('warning', 'W005', `Phase directory "${e.name}" doesn't follow NN-name format`, 'Rename to match pattern (e.g., 01-setup)');
}
}
} catch {}
} catch { /* intentionally empty */ }
// ─── Check 7: Orphaned plans (PLAN without SUMMARY) ───────────────────────
try {
@@ -671,7 +671,7 @@ function cmdValidateHealth(cwd, options, raw) {
}
}
}
} catch {}
} catch { /* intentionally empty */ }
// ─── Check 7b: Nyquist VALIDATION.md consistency ────────────────────────
try {
@@ -689,7 +689,7 @@ function cmdValidateHealth(cwd, options, raw) {
}
}
}
} catch {}
} catch { /* intentionally empty */ }
// ─── Check 8: Run existing consistency checks ─────────────────────────────
// Inline subset of cmdValidateConsistency
@@ -712,7 +712,7 @@ function cmdValidateHealth(cwd, options, raw) {
if (dm) diskPhases.add(dm[1]);
}
}
} catch {}
} catch { /* intentionally empty */ }
// Phases in ROADMAP but not on disk
for (const p of roadmapPhases) {