diff --git a/get-shit-done/bin/lib/commands.cjs b/get-shit-done/bin/lib/commands.cjs index d7109df19..4dc913183 100644 --- a/get-shit-done/bin/lib/commands.cjs +++ b/get-shit-done/bin/lib/commands.cjs @@ -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; diff --git a/get-shit-done/bin/lib/config.cjs b/get-shit-done/bin/lib/config.cjs index 1e0e65491..ef1466f61 100644 --- a/get-shit-done/bin/lib/config.cjs +++ b/get-shit-done/bin/lib/config.cjs @@ -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) { diff --git a/get-shit-done/bin/lib/core.cjs b/get-shit-done/bin/lib/core.cjs index 5038dc247..847ea1a4f 100644 --- a/get-shit-done/bin/lib/core.cjs +++ b/get-shit-done/bin/lib/core.cjs @@ -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, }; diff --git a/get-shit-done/bin/lib/init.cjs b/get-shit-done/bin/lib/init.cjs index 9c9d35655..25fab1581 100644 --- a/get-shit-done/bin/lib/init.cjs +++ b/get-shit-done/bin/lib/init.cjs @@ -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 diff --git a/get-shit-done/bin/lib/milestone.cjs b/get-shit-done/bin/lib/milestone.cjs index 6fd032798..ba56a0727 100644 --- a/get-shit-done/bin/lib/milestone.cjs +++ b/get-shit-done/bin/lib/milestone.cjs @@ -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 = { diff --git a/get-shit-done/bin/lib/phase.cjs b/get-shit-done/bin/lib/phase.cjs index 5cb5eac4d..dc2c52f99 100644 --- a/get-shit-done/bin/lib/phase.cjs +++ b/get-shit-done/bin/lib/phase.cjs @@ -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 diff --git a/get-shit-done/bin/lib/roadmap.cjs b/get-shit-done/bin/lib/roadmap.cjs index a199a6ab8..0e6d900e7 100644 --- a/get-shit-done/bin/lib/roadmap.cjs +++ b/get-shit-done/bin/lib/roadmap.cjs @@ -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'); diff --git a/get-shit-done/bin/lib/state.cjs b/get-shit-done/bin/lib/state.cjs index 78797694a..9296aaa5b 100644 --- a/get-shit-done/bin/lib/state.cjs +++ b/get-shit-done/bin/lib/state.cjs @@ -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; diff --git a/get-shit-done/bin/lib/verify.cjs b/get-shit-done/bin/lib/verify.cjs index 61fbc16c6..5b3a12c13 100644 --- a/get-shit-done/bin/lib/verify.cjs +++ b/get-shit-done/bin/lib/verify.cjs @@ -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) {