diff --git a/get-shit-done/bin/lib/commands.cjs b/get-shit-done/bin/lib/commands.cjs index feeb7557f..7227ea5c8 100644 --- a/get-shit-done/bin/lib/commands.cjs +++ b/get-shit-done/bin/lib/commands.cjs @@ -4,7 +4,7 @@ const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); -const { safeReadFile, loadConfig, isGitIgnored, execGit, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, resolveModelInternal, stripShippedMilestones, extractCurrentMilestone, toPosixPath, output, error, findPhaseInternal, getRoadmapPhaseInternal } = require('./core.cjs'); +const { safeReadFile, loadConfig, isGitIgnored, execGit, normalizePhaseName, comparePhaseNum, getArchivedPhaseDirs, generateSlugInternal, getMilestoneInfo, getMilestonePhaseFilter, resolveModelInternal, stripShippedMilestones, extractCurrentMilestone, planningPaths, toPosixPath, output, error, findPhaseInternal, getRoadmapPhaseInternal } = require('./core.cjs'); const { extractFrontmatter } = require('./frontmatter.cjs'); const { MODEL_PROFILES } = require('./model-profiles.cjs'); @@ -98,7 +98,7 @@ function cmdVerifyPathExists(cwd, targetPath, raw) { } function cmdHistoryDigest(cwd, raw) { - const phasesDir = path.join(cwd, '.planning', 'phases'); + const phasesDir = planningPaths(cwd).phases; const digest = { phases: {}, decisions: [], tech_stack: new Set() }; // Collect all phase directories: archived + current @@ -382,8 +382,8 @@ async function cmdWebsearch(query, options, raw) { } function cmdProgressRender(cwd, format, raw) { - const phasesDir = path.join(cwd, '.planning', 'phases'); - const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md'); + const phasesDir = planningPaths(cwd).phases; + const roadmapPath = planningPaths(cwd).roadmap; const milestone = getMilestoneInfo(cwd); const phases = []; @@ -637,7 +637,7 @@ function cmdScaffold(cwd, type, options, raw) { } const slug = generateSlugInternal(name); const dirName = `${padded}-${slug}`; - const phasesParent = path.join(cwd, '.planning', 'phases'); + const phasesParent = planningPaths(cwd).phases; fs.mkdirSync(phasesParent, { recursive: true }); const dirPath = path.join(phasesParent, dirName); fs.mkdirSync(dirPath, { recursive: true }); @@ -659,10 +659,10 @@ function cmdScaffold(cwd, type, options, raw) { } function cmdStats(cwd, format, raw) { - const phasesDir = path.join(cwd, '.planning', 'phases'); - const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md'); - const reqPath = path.join(cwd, '.planning', 'REQUIREMENTS.md'); - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const phasesDir = planningPaths(cwd).phases; + const roadmapPath = planningPaths(cwd).roadmap; + const reqPath = planningPaths(cwd).requirements; + const statePath = planningPaths(cwd).state; const milestone = getMilestoneInfo(cwd); const isDirInMilestone = getMilestonePhaseFilter(cwd); diff --git a/get-shit-done/bin/lib/milestone.cjs b/get-shit-done/bin/lib/milestone.cjs index ba56a0727..63c5e15fb 100644 --- a/get-shit-done/bin/lib/milestone.cjs +++ b/get-shit-done/bin/lib/milestone.cjs @@ -4,7 +4,7 @@ const fs = require('fs'); const path = require('path'); -const { escapeRegex, getMilestonePhaseFilter, normalizeMd, output, error } = require('./core.cjs'); +const { escapeRegex, getMilestonePhaseFilter, normalizeMd, planningPaths, output, error } = require('./core.cjs'); const { extractFrontmatter } = require('./frontmatter.cjs'); const { writeStateMd } = require('./state.cjs'); @@ -25,7 +25,7 @@ function cmdRequirementsMarkComplete(cwd, reqIdsRaw, raw) { error('no valid requirement IDs found'); } - const reqPath = path.join(cwd, '.planning', 'REQUIREMENTS.md'); + const reqPath = planningPaths(cwd).requirements; if (!fs.existsSync(reqPath)) { output({ updated: false, reason: 'REQUIREMENTS.md not found', ids: reqIds }, raw, 'no requirements file'); return; @@ -90,12 +90,12 @@ function cmdMilestoneComplete(cwd, version, options, raw) { error('version required for milestone complete (e.g., v1.0)'); } - const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md'); - const reqPath = path.join(cwd, '.planning', 'REQUIREMENTS.md'); - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const roadmapPath = planningPaths(cwd).roadmap; + const reqPath = planningPaths(cwd).requirements; + const statePath = planningPaths(cwd).state; const milestonesPath = path.join(cwd, '.planning', 'MILESTONES.md'); const archiveDir = path.join(cwd, '.planning', 'milestones'); - const phasesDir = path.join(cwd, '.planning', 'phases'); + const phasesDir = planningPaths(cwd).phases; const today = new Date().toISOString().split('T')[0]; const milestoneName = options.name || version; diff --git a/get-shit-done/bin/lib/roadmap.cjs b/get-shit-done/bin/lib/roadmap.cjs index 0e6d900e7..a2fe661a6 100644 --- a/get-shit-done/bin/lib/roadmap.cjs +++ b/get-shit-done/bin/lib/roadmap.cjs @@ -4,10 +4,10 @@ const fs = require('fs'); const path = require('path'); -const { escapeRegex, normalizePhaseName, output, error, findPhaseInternal, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone } = require('./core.cjs'); +const { escapeRegex, normalizePhaseName, planningPaths, output, error, findPhaseInternal, stripShippedMilestones, extractCurrentMilestone, replaceInCurrentMilestone } = require('./core.cjs'); function cmdRoadmapGetPhase(cwd, phaseNum, raw) { - const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md'); + const roadmapPath = planningPaths(cwd).roadmap; if (!fs.existsSync(roadmapPath)) { output({ found: false, error: 'ROADMAP.md not found' }, raw, ''); @@ -91,7 +91,7 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) { } function cmdRoadmapAnalyze(cwd, raw) { - const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md'); + const roadmapPath = planningPaths(cwd).roadmap; if (!fs.existsSync(roadmapPath)) { output({ error: 'ROADMAP.md not found', milestones: [], phases: [], current_phase: null }, raw); @@ -100,7 +100,7 @@ function cmdRoadmapAnalyze(cwd, raw) { const rawContent = fs.readFileSync(roadmapPath, 'utf-8'); const content = extractCurrentMilestone(rawContent, cwd); - const phasesDir = path.join(cwd, '.planning', 'phases'); + const phasesDir = planningPaths(cwd).phases; // Extract all phase headings: ## Phase N: Name or ### Phase N: Name const phasePattern = /#{2,4}\s*Phase\s+(\d+[A-Z]?(?:\.\d+)*)\s*:\s*([^\n]+)/gi; @@ -230,7 +230,7 @@ function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) { error('phase number required for roadmap update-plan-progress'); } - const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md'); + const roadmapPath = planningPaths(cwd).roadmap; const phaseInfo = findPhaseInternal(cwd, phaseNum); if (!phaseInfo) { diff --git a/get-shit-done/bin/lib/state.cjs b/get-shit-done/bin/lib/state.cjs index 08aa84862..3d75dfa96 100644 --- a/get-shit-done/bin/lib/state.cjs +++ b/get-shit-done/bin/lib/state.cjs @@ -26,15 +26,15 @@ function stateExtractField(content, fieldName) { function cmdStateLoad(cwd, raw) { const config = loadConfig(cwd); - const planningDir = path.join(cwd, '.planning'); + const planDir = planningPaths(cwd).planning; let stateRaw = ''; try { - stateRaw = fs.readFileSync(path.join(planningDir, 'STATE.md'), 'utf-8'); + stateRaw = fs.readFileSync(path.join(planDir, 'STATE.md'), 'utf-8'); } catch { /* intentionally empty */ } - const configExists = fs.existsSync(path.join(planningDir, 'config.json')); - const roadmapExists = fs.existsSync(path.join(planningDir, 'ROADMAP.md')); + const configExists = fs.existsSync(path.join(planDir, 'config.json')); + const roadmapExists = fs.existsSync(path.join(planDir, 'ROADMAP.md')); const stateExists = stateRaw.length > 0; const result = { @@ -70,7 +70,7 @@ function cmdStateLoad(cwd, raw) { } function cmdStateGet(cwd, section, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; try { const content = fs.readFileSync(statePath, 'utf-8'); @@ -124,7 +124,7 @@ function readTextArgOrFile(cwd, value, filePath, label) { } function cmdStatePatch(cwd, patches, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; try { let content = fs.readFileSync(statePath, 'utf-8'); const results = { updated: [], failed: [] }; @@ -161,7 +161,7 @@ function cmdStateUpdate(cwd, field, value) { error('field and value required for state update'); } - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; try { let content = fs.readFileSync(statePath, 'utf-8'); const fieldEscaped = escapeRegex(field); @@ -202,7 +202,7 @@ function stateReplaceField(content, fieldName, newValue) { } function cmdStateAdvancePlan(cwd, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; } let content = fs.readFileSync(statePath, 'utf-8'); @@ -231,7 +231,7 @@ function cmdStateAdvancePlan(cwd, raw) { } function cmdStateRecordMetric(cwd, options, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; } let content = fs.readFileSync(statePath, 'utf-8'); @@ -265,13 +265,13 @@ function cmdStateRecordMetric(cwd, options, raw) { } function cmdStateUpdateProgress(cwd, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; } let content = fs.readFileSync(statePath, 'utf-8'); // Count summaries across current milestone phases only - const phasesDir = path.join(cwd, '.planning', 'phases'); + const phasesDir = planningPaths(cwd).phases; let totalPlans = 0; let totalSummaries = 0; @@ -310,7 +310,7 @@ function cmdStateUpdateProgress(cwd, raw) { } function cmdStateAddDecision(cwd, options, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; } const { phase, summary, summary_file, rationale, rationale_file } = options; @@ -348,7 +348,7 @@ function cmdStateAddDecision(cwd, options, raw) { } function cmdStateAddBlocker(cwd, text, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; } const blockerOptions = typeof text === 'object' && text !== null ? text : { text }; let blockerText = null; @@ -381,7 +381,7 @@ function cmdStateAddBlocker(cwd, text, raw) { } function cmdStateResolveBlocker(cwd, text, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; } if (!text) { output({ error: 'text required' }, raw); return; } @@ -413,7 +413,7 @@ function cmdStateResolveBlocker(cwd, text, raw) { } function cmdStateRecordSession(cwd, options, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; } let content = fs.readFileSync(statePath, 'utf-8'); @@ -448,7 +448,7 @@ function cmdStateRecordSession(cwd, options, raw) { } function cmdStateSnapshot(cwd, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); @@ -580,7 +580,7 @@ function buildStateFrontmatter(bodyContent, cwd) { if (cwd) { try { - const phasesDir = path.join(cwd, '.planning', 'phases'); + const phasesDir = planningPaths(cwd).phases; if (fs.existsSync(phasesDir)) { const isDirInMilestone = getMilestonePhaseFilter(cwd); const phaseDirs = fs.readdirSync(phasesDir, { withFileTypes: true }) @@ -722,7 +722,7 @@ function writeStateMd(statePath, content, cwd) { } function cmdStateJson(cwd, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw, 'STATE.md not found'); return; @@ -748,7 +748,7 @@ function cmdStateJson(cwd, raw) { * Fixes: #1102 (plan counts), #1103 (status/last_activity), #1104 (body text). */ function cmdStateBeginPhase(cwd, phaseNumber, phaseName, planCount, raw) { - const statePath = path.join(cwd, '.planning', 'STATE.md'); + const statePath = planningPaths(cwd).state; if (!fs.existsSync(statePath)) { output({ error: 'STATE.md not found' }, raw); return; diff --git a/tests/cursor-conversion.test.cjs b/tests/cursor-conversion.test.cjs index 2c7c79d52..a6fe43844 100644 --- a/tests/cursor-conversion.test.cjs +++ b/tests/cursor-conversion.test.cjs @@ -32,7 +32,7 @@ Test body const nameMatch = result.match(/^name:\s*(.+)$/m); assert.ok(nameMatch, 'frontmatter contains name field'); - assert.equal(nameMatch[1], 'gsd-quick', 'skill name is plain scalar'); + assert.strictEqual(nameMatch[1], 'gsd-quick', 'skill name is plain scalar'); assert.ok(!result.includes('name: "gsd-quick"'), 'quoted skill name is not emitted'); }); @@ -75,7 +75,7 @@ Planner body const nameMatch = result.match(/^name:\s*(.+)$/m); assert.ok(nameMatch, 'frontmatter contains name field'); - assert.equal(nameMatch[1], 'gsd-planner', 'agent name is plain scalar'); + assert.strictEqual(nameMatch[1], 'gsd-planner', 'agent name is plain scalar'); assert.ok(!result.includes('name: "gsd-planner"'), 'quoted agent name is not emitted'); }); });