@ahmed118glitch/get-shit-done-codex 1.18.4 → 1.19.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -1
- package/agents/gsd-phase-researcher.md +1 -1
- package/agents/gsd-roadmapper.md +39 -5
- package/agents/gsd-verifier.md +20 -4
- package/bin/install-codex.js +1 -1
- package/bin/install.js +65 -8
- package/commands/gsd/discuss-phase.md +3 -2
- package/commands/gsd/plan-phase.md +1 -1
- package/commands/gsd/reapply-patches.md +3 -3
- package/commands/gsd/verify-work.md +1 -1
- package/get-shit-done/bin/gsd-tools.js +222 -20
- package/get-shit-done/bin/gsd-tools.test.js +189 -0
- package/get-shit-done/references/model-profile-resolution.md +4 -2
- package/get-shit-done/references/model-profiles.md +3 -0
- package/get-shit-done/references/questioning.md +1 -0
- package/get-shit-done/templates/UAT.md +1 -1
- package/get-shit-done/templates/config.json +2 -1
- package/get-shit-done/templates/context.md +2 -2
- package/get-shit-done/templates/planner-subagent-prompt.md +4 -4
- package/get-shit-done/templates/research.md +2 -2
- package/get-shit-done/templates/verification-report.md +1 -1
- package/get-shit-done/workflows/diagnose-issues.md +1 -1
- package/get-shit-done/workflows/discovery-phase.md +1 -1
- package/get-shit-done/workflows/discuss-phase.md +64 -3
- package/get-shit-done/workflows/execute-phase.md +68 -20
- package/get-shit-done/workflows/execute-plan.md +4 -1
- package/get-shit-done/workflows/new-milestone.md +1 -1
- package/get-shit-done/workflows/new-project.md +3 -3
- package/get-shit-done/workflows/plan-phase.md +54 -2
- package/get-shit-done/workflows/progress.md +2 -2
- package/get-shit-done/workflows/settings.md +13 -2
- package/get-shit-done/workflows/transition.md +48 -2
- package/get-shit-done/workflows/update.md +4 -2
- package/get-shit-done/workflows/verify-phase.md +18 -2
- package/get-shit-done/workflows/verify-work.md +3 -3
- package/package.json +1 -1
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
* Roadmap Operations:
|
|
40
40
|
* roadmap get-phase <phase> Extract phase section from ROADMAP.md
|
|
41
41
|
* roadmap analyze Full roadmap parse with disk status
|
|
42
|
+
* roadmap update-plan-progress <N> Update progress table row from disk (PLAN vs SUMMARY counts)
|
|
42
43
|
*
|
|
43
44
|
* Milestone Operations:
|
|
44
45
|
* milestone complete <version> Archive milestone, create MILESTONES.md
|
|
@@ -835,14 +836,33 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
835
836
|
// Escape special regex chars in phase number, handle decimal
|
|
836
837
|
const escapedPhase = phaseNum.replace(/\./g, '\\.');
|
|
837
838
|
|
|
838
|
-
// Match "
|
|
839
|
+
// Match "## Phase X:" or "### Phase X:" with optional name
|
|
839
840
|
const phasePattern = new RegExp(
|
|
840
|
-
|
|
841
|
+
`#{2,3}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`,
|
|
841
842
|
'i'
|
|
842
843
|
);
|
|
843
844
|
const headerMatch = content.match(phasePattern);
|
|
844
845
|
|
|
845
846
|
if (!headerMatch) {
|
|
847
|
+
// Fallback: check if phase exists in summary list but missing detail section
|
|
848
|
+
const checklistPattern = new RegExp(
|
|
849
|
+
`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`,
|
|
850
|
+
'i'
|
|
851
|
+
);
|
|
852
|
+
const checklistMatch = content.match(checklistPattern);
|
|
853
|
+
|
|
854
|
+
if (checklistMatch) {
|
|
855
|
+
// Phase exists in summary but missing detail section - malformed ROADMAP
|
|
856
|
+
output({
|
|
857
|
+
found: false,
|
|
858
|
+
phase_number: phaseNum,
|
|
859
|
+
phase_name: checklistMatch[1].trim(),
|
|
860
|
+
error: 'malformed_roadmap',
|
|
861
|
+
message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. ROADMAP.md needs both formats.`
|
|
862
|
+
}, raw, '');
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
|
|
846
866
|
output({ found: false, phase_number: phaseNum }, raw, '');
|
|
847
867
|
return;
|
|
848
868
|
}
|
|
@@ -850,9 +870,9 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
850
870
|
const phaseName = headerMatch[1].trim();
|
|
851
871
|
const headerIndex = headerMatch.index;
|
|
852
872
|
|
|
853
|
-
// Find the end of this section (next ### or end of file)
|
|
873
|
+
// Find the end of this section (next ## or ### phase header, or end of file)
|
|
854
874
|
const restOfContent = content.slice(headerIndex);
|
|
855
|
-
const nextHeaderMatch = restOfContent.match(/\n
|
|
875
|
+
const nextHeaderMatch = restOfContent.match(/\n#{2,3}\s+Phase\s+\d/i);
|
|
856
876
|
const sectionEnd = nextHeaderMatch
|
|
857
877
|
? headerIndex + nextHeaderMatch.index
|
|
858
878
|
: content.length;
|
|
@@ -863,12 +883,19 @@ function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
|
863
883
|
const goalMatch = section.match(/\*\*Goal:\*\*\s*([^\n]+)/i);
|
|
864
884
|
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
865
885
|
|
|
886
|
+
// Extract success criteria as structured array
|
|
887
|
+
const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
|
|
888
|
+
const success_criteria = criteriaMatch
|
|
889
|
+
? criteriaMatch[1].trim().split('\n').map(line => line.replace(/^\s*\d+\.\s*/, '').trim()).filter(Boolean)
|
|
890
|
+
: [];
|
|
891
|
+
|
|
866
892
|
output(
|
|
867
893
|
{
|
|
868
894
|
found: true,
|
|
869
895
|
phase_number: phaseNum,
|
|
870
896
|
phase_name: phaseName,
|
|
871
897
|
goal,
|
|
898
|
+
success_criteria,
|
|
872
899
|
section,
|
|
873
900
|
},
|
|
874
901
|
raw,
|
|
@@ -1328,7 +1355,8 @@ function cmdResolveModel(cwd, agentType, raw) {
|
|
|
1328
1355
|
return;
|
|
1329
1356
|
}
|
|
1330
1357
|
|
|
1331
|
-
const
|
|
1358
|
+
const resolved = agentModels[profile] || agentModels['balanced'] || 'sonnet';
|
|
1359
|
+
const model = resolved === 'opus' ? 'inherit' : resolved;
|
|
1332
1360
|
const result = { model, profile };
|
|
1333
1361
|
output(result, raw, model);
|
|
1334
1362
|
}
|
|
@@ -2430,8 +2458,8 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
2430
2458
|
const content = fs.readFileSync(roadmapPath, 'utf-8');
|
|
2431
2459
|
const phasesDir = path.join(cwd, '.planning', 'phases');
|
|
2432
2460
|
|
|
2433
|
-
// Extract all phase headings: ### Phase N: Name
|
|
2434
|
-
const phasePattern =
|
|
2461
|
+
// Extract all phase headings: ## Phase N: Name or ### Phase N: Name
|
|
2462
|
+
const phasePattern = /#{2,3}\s*Phase\s+(\d+(?:\.\d+)?)\s*:\s*([^\n]+)/gi;
|
|
2435
2463
|
const phases = [];
|
|
2436
2464
|
let match;
|
|
2437
2465
|
|
|
@@ -2442,7 +2470,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
2442
2470
|
// Extract goal from the section
|
|
2443
2471
|
const sectionStart = match.index;
|
|
2444
2472
|
const restOfContent = content.slice(sectionStart);
|
|
2445
|
-
const nextHeader = restOfContent.match(/\n
|
|
2473
|
+
const nextHeader = restOfContent.match(/\n#{2,3}\s+Phase\s+\d/i);
|
|
2446
2474
|
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
|
|
2447
2475
|
const section = content.slice(sectionStart, sectionEnd);
|
|
2448
2476
|
|
|
@@ -2520,6 +2548,16 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
2520
2548
|
const totalSummaries = phases.reduce((sum, p) => sum + p.summary_count, 0);
|
|
2521
2549
|
const completedPhases = phases.filter(p => p.disk_status === 'complete').length;
|
|
2522
2550
|
|
|
2551
|
+
// Detect phases in summary list without detail sections (malformed ROADMAP)
|
|
2552
|
+
const checklistPattern = /-\s*\[[ x]\]\s*\*\*Phase\s+(\d+(?:\.\d+)?)/gi;
|
|
2553
|
+
const checklistPhases = new Set();
|
|
2554
|
+
let checklistMatch;
|
|
2555
|
+
while ((checklistMatch = checklistPattern.exec(content)) !== null) {
|
|
2556
|
+
checklistPhases.add(checklistMatch[1]);
|
|
2557
|
+
}
|
|
2558
|
+
const detailPhases = new Set(phases.map(p => p.number));
|
|
2559
|
+
const missingDetails = [...checklistPhases].filter(p => !detailPhases.has(p));
|
|
2560
|
+
|
|
2523
2561
|
const result = {
|
|
2524
2562
|
milestones,
|
|
2525
2563
|
phases,
|
|
@@ -2530,6 +2568,7 @@ function cmdRoadmapAnalyze(cwd, raw) {
|
|
|
2530
2568
|
progress_percent: totalPlans > 0 ? Math.round((totalSummaries / totalPlans) * 100) : 0,
|
|
2531
2569
|
current_phase: currentPhase ? currentPhase.number : null,
|
|
2532
2570
|
next_phase: nextPhase ? nextPhase.number : null,
|
|
2571
|
+
missing_phase_details: missingDetails.length > 0 ? missingDetails : null,
|
|
2533
2572
|
};
|
|
2534
2573
|
|
|
2535
2574
|
output(result, raw);
|
|
@@ -2551,7 +2590,7 @@ function cmdPhaseAdd(cwd, description, raw) {
|
|
|
2551
2590
|
const slug = generateSlugInternal(description);
|
|
2552
2591
|
|
|
2553
2592
|
// Find highest integer phase number
|
|
2554
|
-
const phasePattern =
|
|
2593
|
+
const phasePattern = /#{2,3}\s*Phase\s+(\d+)(?:\.\d+)?:/gi;
|
|
2555
2594
|
let maxPhase = 0;
|
|
2556
2595
|
let m;
|
|
2557
2596
|
while ((m = phasePattern.exec(content)) !== null) {
|
|
@@ -2609,7 +2648,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
|
|
2609
2648
|
|
|
2610
2649
|
// Verify target phase exists
|
|
2611
2650
|
const afterPhaseEscaped = afterPhase.replace(/\./g, '\\.');
|
|
2612
|
-
const targetPattern = new RegExp(
|
|
2651
|
+
const targetPattern = new RegExp(`#{2,3}\\s*Phase\\s+${afterPhaseEscaped}:`, 'i');
|
|
2613
2652
|
if (!targetPattern.test(content)) {
|
|
2614
2653
|
error(`Phase ${afterPhase} not found in ROADMAP.md`);
|
|
2615
2654
|
}
|
|
@@ -2641,7 +2680,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
|
|
2641
2680
|
const phaseEntry = `\n### Phase ${decimalPhase}: ${description} (INSERTED)\n\n**Goal:** [Urgent work - to be planned]\n**Depends on:** Phase ${afterPhase}\n**Plans:** 0 plans\n\nPlans:\n- [ ] TBD (run /gsd:plan-phase ${decimalPhase} to break down)\n`;
|
|
2642
2681
|
|
|
2643
2682
|
// Insert after the target phase section
|
|
2644
|
-
const headerPattern = new RegExp(`(
|
|
2683
|
+
const headerPattern = new RegExp(`(#{2,3}\\s*Phase\\s+${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
|
|
2645
2684
|
const headerMatch = content.match(headerPattern);
|
|
2646
2685
|
if (!headerMatch) {
|
|
2647
2686
|
error(`Could not find Phase ${afterPhase} header`);
|
|
@@ -2649,7 +2688,7 @@ function cmdPhaseInsert(cwd, afterPhase, description, raw) {
|
|
|
2649
2688
|
|
|
2650
2689
|
const headerIdx = content.indexOf(headerMatch[0]);
|
|
2651
2690
|
const afterHeader = content.slice(headerIdx + headerMatch[0].length);
|
|
2652
|
-
const nextPhaseMatch = afterHeader.match(/\n
|
|
2691
|
+
const nextPhaseMatch = afterHeader.match(/\n#{2,3}\s+Phase\s+\d/i);
|
|
2653
2692
|
|
|
2654
2693
|
let insertIdx;
|
|
2655
2694
|
if (nextPhaseMatch) {
|
|
@@ -2832,7 +2871,7 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
2832
2871
|
// Remove the target phase section
|
|
2833
2872
|
const targetEscaped = targetPhase.replace(/\./g, '\\.');
|
|
2834
2873
|
const sectionPattern = new RegExp(
|
|
2835
|
-
`\\n
|
|
2874
|
+
`\\n?#{2,3}\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n#{2,3}\\s+Phase\\s+\\d|$)`,
|
|
2836
2875
|
'i'
|
|
2837
2876
|
);
|
|
2838
2877
|
roadmapContent = roadmapContent.replace(sectionPattern, '');
|
|
@@ -2858,9 +2897,9 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
2858
2897
|
const oldPad = oldStr.padStart(2, '0');
|
|
2859
2898
|
const newPad = newStr.padStart(2, '0');
|
|
2860
2899
|
|
|
2861
|
-
// Phase headings: ### Phase 18: → ### Phase 17:
|
|
2900
|
+
// Phase headings: ## Phase 18: or ### Phase 18: → ## Phase 17: or ### Phase 17:
|
|
2862
2901
|
roadmapContent = roadmapContent.replace(
|
|
2863
|
-
new RegExp(`(
|
|
2902
|
+
new RegExp(`(#{2,3}\\s*Phase\\s+)${oldStr}(\\s*:)`, 'gi'),
|
|
2864
2903
|
`$1${newStr}$2`
|
|
2865
2904
|
);
|
|
2866
2905
|
|
|
@@ -2925,6 +2964,82 @@ function cmdPhaseRemove(cwd, targetPhase, options, raw) {
|
|
|
2925
2964
|
output(result, raw);
|
|
2926
2965
|
}
|
|
2927
2966
|
|
|
2967
|
+
// ─── Roadmap Update Plan Progress ────────────────────────────────────────────
|
|
2968
|
+
|
|
2969
|
+
function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
2970
|
+
if (!phaseNum) {
|
|
2971
|
+
error('phase number required for roadmap update-plan-progress');
|
|
2972
|
+
}
|
|
2973
|
+
|
|
2974
|
+
const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
|
|
2975
|
+
|
|
2976
|
+
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
2977
|
+
if (!phaseInfo) {
|
|
2978
|
+
error(`Phase ${phaseNum} not found`);
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
const planCount = phaseInfo.plans.length;
|
|
2982
|
+
const summaryCount = phaseInfo.summaries.length;
|
|
2983
|
+
|
|
2984
|
+
if (planCount === 0) {
|
|
2985
|
+
output({ updated: false, reason: 'No plans found', plan_count: 0, summary_count: 0 }, raw, 'no plans');
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
const isComplete = summaryCount >= planCount;
|
|
2990
|
+
const status = isComplete ? 'Complete' : summaryCount > 0 ? 'In Progress' : 'Planned';
|
|
2991
|
+
const today = new Date().toISOString().split('T')[0];
|
|
2992
|
+
|
|
2993
|
+
if (!fs.existsSync(roadmapPath)) {
|
|
2994
|
+
output({ updated: false, reason: 'ROADMAP.md not found', plan_count: planCount, summary_count: summaryCount }, raw, 'no roadmap');
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
let roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
2999
|
+
const phaseEscaped = phaseNum.replace('.', '\\.');
|
|
3000
|
+
|
|
3001
|
+
// Progress table row: update Plans column (summaries/plans) and Status column
|
|
3002
|
+
const tablePattern = new RegExp(
|
|
3003
|
+
`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|)[^|]*(\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`,
|
|
3004
|
+
'i'
|
|
3005
|
+
);
|
|
3006
|
+
const dateField = isComplete ? ` ${today} ` : ' ';
|
|
3007
|
+
roadmapContent = roadmapContent.replace(
|
|
3008
|
+
tablePattern,
|
|
3009
|
+
`$1 ${summaryCount}/${planCount} $2 ${status.padEnd(11)}$3${dateField}$4`
|
|
3010
|
+
);
|
|
3011
|
+
|
|
3012
|
+
// Update plan count in phase detail section
|
|
3013
|
+
const planCountPattern = new RegExp(
|
|
3014
|
+
`(#{2,3}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
|
|
3015
|
+
'i'
|
|
3016
|
+
);
|
|
3017
|
+
const planCountText = isComplete
|
|
3018
|
+
? `${summaryCount}/${planCount} plans complete`
|
|
3019
|
+
: `${summaryCount}/${planCount} plans executed`;
|
|
3020
|
+
roadmapContent = roadmapContent.replace(planCountPattern, `$1${planCountText}`);
|
|
3021
|
+
|
|
3022
|
+
// If complete: check checkbox
|
|
3023
|
+
if (isComplete) {
|
|
3024
|
+
const checkboxPattern = new RegExp(
|
|
3025
|
+
`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
|
|
3026
|
+
'i'
|
|
3027
|
+
);
|
|
3028
|
+
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
fs.writeFileSync(roadmapPath, roadmapContent, 'utf-8');
|
|
3032
|
+
|
|
3033
|
+
output({
|
|
3034
|
+
updated: true,
|
|
3035
|
+
phase: phaseNum,
|
|
3036
|
+
plan_count: planCount,
|
|
3037
|
+
summary_count: summaryCount,
|
|
3038
|
+
status,
|
|
3039
|
+
complete: isComplete,
|
|
3040
|
+
}, raw, `${summaryCount}/${planCount} ${status}`);
|
|
3041
|
+
}
|
|
3042
|
+
|
|
2928
3043
|
// ─── Phase Complete (Transition) ──────────────────────────────────────────────
|
|
2929
3044
|
|
|
2930
3045
|
function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
@@ -2971,7 +3086,7 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
2971
3086
|
|
|
2972
3087
|
// Update plan count in phase section
|
|
2973
3088
|
const planCountPattern = new RegExp(
|
|
2974
|
-
`(
|
|
3089
|
+
`(#{2,3}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
|
|
2975
3090
|
'i'
|
|
2976
3091
|
);
|
|
2977
3092
|
roadmapContent = roadmapContent.replace(
|
|
@@ -2980,6 +3095,35 @@ function cmdPhaseComplete(cwd, phaseNum, raw) {
|
|
|
2980
3095
|
);
|
|
2981
3096
|
|
|
2982
3097
|
fs.writeFileSync(roadmapPath, roadmapContent, 'utf-8');
|
|
3098
|
+
|
|
3099
|
+
// Update REQUIREMENTS.md traceability for this phase's requirements
|
|
3100
|
+
const reqPath = path.join(cwd, '.planning', 'REQUIREMENTS.md');
|
|
3101
|
+
if (fs.existsSync(reqPath)) {
|
|
3102
|
+
// Extract Requirements line from roadmap for this phase
|
|
3103
|
+
const reqMatch = roadmapContent.match(
|
|
3104
|
+
new RegExp(`Phase\\s+${phaseNum.replace('.', '\\.')}[\\s\\S]*?\\*\\*Requirements:\\*\\*\\s*([^\\n]+)`, 'i')
|
|
3105
|
+
);
|
|
3106
|
+
|
|
3107
|
+
if (reqMatch) {
|
|
3108
|
+
const reqIds = reqMatch[1].split(/[,\s]+/).map(r => r.trim()).filter(Boolean);
|
|
3109
|
+
let reqContent = fs.readFileSync(reqPath, 'utf-8');
|
|
3110
|
+
|
|
3111
|
+
for (const reqId of reqIds) {
|
|
3112
|
+
// Update checkbox: - [ ] **REQ-ID** → - [x] **REQ-ID**
|
|
3113
|
+
reqContent = reqContent.replace(
|
|
3114
|
+
new RegExp(`(-\\s*\\[)[ ](\\]\\s*\\*\\*${reqId}\\*\\*)`, 'gi'),
|
|
3115
|
+
'$1x$2'
|
|
3116
|
+
);
|
|
3117
|
+
// Update traceability table: | REQ-ID | Phase N | Pending | → | REQ-ID | Phase N | Complete |
|
|
3118
|
+
reqContent = reqContent.replace(
|
|
3119
|
+
new RegExp(`(\\|\\s*${reqId}\\s*\\|[^|]+\\|)\\s*Pending\\s*(\\|)`, 'gi'),
|
|
3120
|
+
'$1 Complete $2'
|
|
3121
|
+
);
|
|
3122
|
+
}
|
|
3123
|
+
|
|
3124
|
+
fs.writeFileSync(reqPath, reqContent, 'utf-8');
|
|
3125
|
+
}
|
|
3126
|
+
}
|
|
2983
3127
|
}
|
|
2984
3128
|
|
|
2985
3129
|
// Find next phase
|
|
@@ -3206,7 +3350,7 @@ function cmdValidateConsistency(cwd, raw) {
|
|
|
3206
3350
|
|
|
3207
3351
|
// Extract phases from ROADMAP
|
|
3208
3352
|
const roadmapPhases = new Set();
|
|
3209
|
-
const phasePattern =
|
|
3353
|
+
const phasePattern = /#{2,3}\s*Phase\s+(\d+(?:\.\d+)?)\s*:/gi;
|
|
3210
3354
|
let m;
|
|
3211
3355
|
while ((m = phasePattern.exec(roadmapContent)) !== null) {
|
|
3212
3356
|
roadmapPhases.add(m[1]);
|
|
@@ -3479,7 +3623,8 @@ function resolveModelInternal(cwd, agentType) {
|
|
|
3479
3623
|
const profile = config.model_profile || 'balanced';
|
|
3480
3624
|
const agentModels = MODEL_PROFILES[agentType];
|
|
3481
3625
|
if (!agentModels) return 'sonnet';
|
|
3482
|
-
|
|
3626
|
+
const resolved = agentModels[profile] || agentModels['balanced'] || 'sonnet';
|
|
3627
|
+
return resolved === 'opus' ? 'inherit' : resolved;
|
|
3483
3628
|
}
|
|
3484
3629
|
|
|
3485
3630
|
function findPhaseInternal(cwd, phase) {
|
|
@@ -3533,6 +3678,40 @@ function findPhaseInternal(cwd, phase) {
|
|
|
3533
3678
|
}
|
|
3534
3679
|
}
|
|
3535
3680
|
|
|
3681
|
+
function getRoadmapPhaseInternal(cwd, phaseNum) {
|
|
3682
|
+
if (!phaseNum) return null;
|
|
3683
|
+
const roadmapPath = path.join(cwd, '.planning', 'ROADMAP.md');
|
|
3684
|
+
if (!fs.existsSync(roadmapPath)) return null;
|
|
3685
|
+
|
|
3686
|
+
try {
|
|
3687
|
+
const content = fs.readFileSync(roadmapPath, 'utf-8');
|
|
3688
|
+
const escapedPhase = phaseNum.toString().replace(/\./g, '\\.');
|
|
3689
|
+
const phasePattern = new RegExp(`#{2,3}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`, 'i');
|
|
3690
|
+
const headerMatch = content.match(phasePattern);
|
|
3691
|
+
if (!headerMatch) return null;
|
|
3692
|
+
|
|
3693
|
+
const phaseName = headerMatch[1].trim();
|
|
3694
|
+
const headerIndex = headerMatch.index;
|
|
3695
|
+
const restOfContent = content.slice(headerIndex);
|
|
3696
|
+
const nextHeaderMatch = restOfContent.match(/\n#{2,3}\s+Phase\s+\d/i);
|
|
3697
|
+
const sectionEnd = nextHeaderMatch ? headerIndex + nextHeaderMatch.index : content.length;
|
|
3698
|
+
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
3699
|
+
|
|
3700
|
+
const goalMatch = section.match(/\*\*Goal:\*\*\s*([^\n]+)/i);
|
|
3701
|
+
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
3702
|
+
|
|
3703
|
+
return {
|
|
3704
|
+
found: true,
|
|
3705
|
+
phase_number: phaseNum.toString(),
|
|
3706
|
+
phase_name: phaseName,
|
|
3707
|
+
goal,
|
|
3708
|
+
section,
|
|
3709
|
+
};
|
|
3710
|
+
} catch {
|
|
3711
|
+
return null;
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3536
3715
|
function pathExistsInternal(cwd, targetPath) {
|
|
3537
3716
|
const fullPath = path.isAbsolute(targetPath) ? targetPath : path.join(cwd, targetPath);
|
|
3538
3717
|
try {
|
|
@@ -3918,7 +4097,28 @@ function cmdInitVerifyWork(cwd, phase, raw) {
|
|
|
3918
4097
|
|
|
3919
4098
|
function cmdInitPhaseOp(cwd, phase, raw) {
|
|
3920
4099
|
const config = loadConfig(cwd);
|
|
3921
|
-
|
|
4100
|
+
let phaseInfo = findPhaseInternal(cwd, phase);
|
|
4101
|
+
|
|
4102
|
+
// Fallback to ROADMAP.md if no directory exists (e.g., Plans: TBD)
|
|
4103
|
+
if (!phaseInfo) {
|
|
4104
|
+
const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
|
4105
|
+
if (roadmapPhase?.found) {
|
|
4106
|
+
const phaseName = roadmapPhase.phase_name;
|
|
4107
|
+
phaseInfo = {
|
|
4108
|
+
found: true,
|
|
4109
|
+
directory: null,
|
|
4110
|
+
phase_number: roadmapPhase.phase_number,
|
|
4111
|
+
phase_name: phaseName,
|
|
4112
|
+
phase_slug: phaseName ? phaseName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') : null,
|
|
4113
|
+
plans: [],
|
|
4114
|
+
summaries: [],
|
|
4115
|
+
incomplete_plans: [],
|
|
4116
|
+
has_research: false,
|
|
4117
|
+
has_context: false,
|
|
4118
|
+
has_verification: false,
|
|
4119
|
+
};
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
3922
4122
|
|
|
3923
4123
|
const result = {
|
|
3924
4124
|
// Config
|
|
@@ -4439,8 +4639,10 @@ async function main() {
|
|
|
4439
4639
|
cmdRoadmapGetPhase(cwd, args[2], raw);
|
|
4440
4640
|
} else if (subcommand === 'analyze') {
|
|
4441
4641
|
cmdRoadmapAnalyze(cwd, raw);
|
|
4642
|
+
} else if (subcommand === 'update-plan-progress') {
|
|
4643
|
+
cmdRoadmapUpdatePlanProgress(cwd, args[2], raw);
|
|
4442
4644
|
} else {
|
|
4443
|
-
error('Unknown roadmap subcommand. Available: get-phase, analyze');
|
|
4645
|
+
error('Unknown roadmap subcommand. Available: get-phase, analyze, update-plan-progress');
|
|
4444
4646
|
}
|
|
4445
4647
|
break;
|
|
4446
4648
|
}
|
|
@@ -520,6 +520,50 @@ This phase covers:
|
|
|
520
520
|
assert.strictEqual(output.found, false, 'should return not found');
|
|
521
521
|
assert.strictEqual(output.error, 'ROADMAP.md not found', 'should explain why');
|
|
522
522
|
});
|
|
523
|
+
|
|
524
|
+
test('accepts ## phase headers (two hashes)', () => {
|
|
525
|
+
fs.writeFileSync(
|
|
526
|
+
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
527
|
+
`# Roadmap v1.0
|
|
528
|
+
|
|
529
|
+
## Phase 1: Foundation
|
|
530
|
+
**Goal:** Set up project infrastructure
|
|
531
|
+
**Plans:** 2 plans
|
|
532
|
+
|
|
533
|
+
## Phase 2: API
|
|
534
|
+
**Goal:** Build REST API
|
|
535
|
+
`
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
const result = runGsdTools('roadmap get-phase 1', tmpDir);
|
|
539
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
540
|
+
|
|
541
|
+
const output = JSON.parse(result.output);
|
|
542
|
+
assert.strictEqual(output.found, true, 'phase with ## header should be found');
|
|
543
|
+
assert.strictEqual(output.phase_name, 'Foundation', 'phase name extracted');
|
|
544
|
+
assert.strictEqual(output.goal, 'Set up project infrastructure', 'goal extracted');
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
test('detects malformed ROADMAP with summary list but no detail sections', () => {
|
|
548
|
+
fs.writeFileSync(
|
|
549
|
+
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
550
|
+
`# Roadmap v1.0
|
|
551
|
+
|
|
552
|
+
## Phases
|
|
553
|
+
|
|
554
|
+
- [ ] **Phase 1: Foundation** - Set up project
|
|
555
|
+
- [ ] **Phase 2: API** - Build REST API
|
|
556
|
+
`
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
const result = runGsdTools('roadmap get-phase 1', tmpDir);
|
|
560
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
561
|
+
|
|
562
|
+
const output = JSON.parse(result.output);
|
|
563
|
+
assert.strictEqual(output.found, false, 'phase should not be found');
|
|
564
|
+
assert.strictEqual(output.error, 'malformed_roadmap', 'should identify malformed roadmap');
|
|
565
|
+
assert.ok(output.message.includes('missing'), 'should explain the issue');
|
|
566
|
+
});
|
|
523
567
|
});
|
|
524
568
|
|
|
525
569
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -1656,6 +1700,151 @@ describe('phase complete command', () => {
|
|
|
1656
1700
|
const state = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
|
|
1657
1701
|
assert.ok(state.includes('Milestone complete'), 'status should be milestone complete');
|
|
1658
1702
|
});
|
|
1703
|
+
|
|
1704
|
+
test('updates REQUIREMENTS.md traceability when phase completes', () => {
|
|
1705
|
+
fs.writeFileSync(
|
|
1706
|
+
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
1707
|
+
`# Roadmap
|
|
1708
|
+
|
|
1709
|
+
- [ ] Phase 1: Auth
|
|
1710
|
+
|
|
1711
|
+
### Phase 1: Auth
|
|
1712
|
+
**Goal:** User authentication
|
|
1713
|
+
**Requirements:** AUTH-01, AUTH-02
|
|
1714
|
+
**Plans:** 1 plans
|
|
1715
|
+
|
|
1716
|
+
### Phase 2: API
|
|
1717
|
+
**Goal:** Build API
|
|
1718
|
+
**Requirements:** API-01
|
|
1719
|
+
`
|
|
1720
|
+
);
|
|
1721
|
+
fs.writeFileSync(
|
|
1722
|
+
path.join(tmpDir, '.planning', 'REQUIREMENTS.md'),
|
|
1723
|
+
`# Requirements
|
|
1724
|
+
|
|
1725
|
+
## v1 Requirements
|
|
1726
|
+
|
|
1727
|
+
### Authentication
|
|
1728
|
+
|
|
1729
|
+
- [ ] **AUTH-01**: User can sign up with email
|
|
1730
|
+
- [ ] **AUTH-02**: User can log in
|
|
1731
|
+
- [ ] **AUTH-03**: User can reset password
|
|
1732
|
+
|
|
1733
|
+
### API
|
|
1734
|
+
|
|
1735
|
+
- [ ] **API-01**: REST endpoints
|
|
1736
|
+
|
|
1737
|
+
## Traceability
|
|
1738
|
+
|
|
1739
|
+
| Requirement | Phase | Status |
|
|
1740
|
+
|-------------|-------|--------|
|
|
1741
|
+
| AUTH-01 | Phase 1 | Pending |
|
|
1742
|
+
| AUTH-02 | Phase 1 | Pending |
|
|
1743
|
+
| AUTH-03 | Phase 2 | Pending |
|
|
1744
|
+
| API-01 | Phase 2 | Pending |
|
|
1745
|
+
`
|
|
1746
|
+
);
|
|
1747
|
+
fs.writeFileSync(
|
|
1748
|
+
path.join(tmpDir, '.planning', 'STATE.md'),
|
|
1749
|
+
`# State\n\n**Current Phase:** 01\n**Current Phase Name:** Auth\n**Status:** In progress\n**Current Plan:** 01-01\n**Last Activity:** 2025-01-01\n**Last Activity Description:** Working\n`
|
|
1750
|
+
);
|
|
1751
|
+
|
|
1752
|
+
const p1 = path.join(tmpDir, '.planning', 'phases', '01-auth');
|
|
1753
|
+
fs.mkdirSync(p1, { recursive: true });
|
|
1754
|
+
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
|
1755
|
+
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');
|
|
1756
|
+
fs.mkdirSync(path.join(tmpDir, '.planning', 'phases', '02-api'), { recursive: true });
|
|
1757
|
+
|
|
1758
|
+
const result = runGsdTools('phase complete 1', tmpDir);
|
|
1759
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
1760
|
+
|
|
1761
|
+
const req = fs.readFileSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), 'utf-8');
|
|
1762
|
+
|
|
1763
|
+
// Checkboxes updated for phase 1 requirements
|
|
1764
|
+
assert.ok(req.includes('- [x] **AUTH-01**'), 'AUTH-01 checkbox should be checked');
|
|
1765
|
+
assert.ok(req.includes('- [x] **AUTH-02**'), 'AUTH-02 checkbox should be checked');
|
|
1766
|
+
// Other requirements unchanged
|
|
1767
|
+
assert.ok(req.includes('- [ ] **AUTH-03**'), 'AUTH-03 should remain unchecked');
|
|
1768
|
+
assert.ok(req.includes('- [ ] **API-01**'), 'API-01 should remain unchecked');
|
|
1769
|
+
|
|
1770
|
+
// Traceability table updated
|
|
1771
|
+
assert.ok(req.includes('| AUTH-01 | Phase 1 | Complete |'), 'AUTH-01 status should be Complete');
|
|
1772
|
+
assert.ok(req.includes('| AUTH-02 | Phase 1 | Complete |'), 'AUTH-02 status should be Complete');
|
|
1773
|
+
assert.ok(req.includes('| AUTH-03 | Phase 2 | Pending |'), 'AUTH-03 should remain Pending');
|
|
1774
|
+
assert.ok(req.includes('| API-01 | Phase 2 | Pending |'), 'API-01 should remain Pending');
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1777
|
+
test('handles phase with no requirements mapping', () => {
|
|
1778
|
+
fs.writeFileSync(
|
|
1779
|
+
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
1780
|
+
`# Roadmap
|
|
1781
|
+
|
|
1782
|
+
- [ ] Phase 1: Setup
|
|
1783
|
+
|
|
1784
|
+
### Phase 1: Setup
|
|
1785
|
+
**Goal:** Project setup (no requirements)
|
|
1786
|
+
**Plans:** 1 plans
|
|
1787
|
+
`
|
|
1788
|
+
);
|
|
1789
|
+
fs.writeFileSync(
|
|
1790
|
+
path.join(tmpDir, '.planning', 'REQUIREMENTS.md'),
|
|
1791
|
+
`# Requirements
|
|
1792
|
+
|
|
1793
|
+
## v1 Requirements
|
|
1794
|
+
|
|
1795
|
+
- [ ] **REQ-01**: Some requirement
|
|
1796
|
+
|
|
1797
|
+
## Traceability
|
|
1798
|
+
|
|
1799
|
+
| Requirement | Phase | Status |
|
|
1800
|
+
|-------------|-------|--------|
|
|
1801
|
+
| REQ-01 | Phase 2 | Pending |
|
|
1802
|
+
`
|
|
1803
|
+
);
|
|
1804
|
+
fs.writeFileSync(
|
|
1805
|
+
path.join(tmpDir, '.planning', 'STATE.md'),
|
|
1806
|
+
`# 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`
|
|
1807
|
+
);
|
|
1808
|
+
|
|
1809
|
+
const p1 = path.join(tmpDir, '.planning', 'phases', '01-setup');
|
|
1810
|
+
fs.mkdirSync(p1, { recursive: true });
|
|
1811
|
+
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
|
1812
|
+
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');
|
|
1813
|
+
|
|
1814
|
+
const result = runGsdTools('phase complete 1', tmpDir);
|
|
1815
|
+
assert.ok(result.success, `Command failed: ${result.error}`);
|
|
1816
|
+
|
|
1817
|
+
// REQUIREMENTS.md should be unchanged
|
|
1818
|
+
const req = fs.readFileSync(path.join(tmpDir, '.planning', 'REQUIREMENTS.md'), 'utf-8');
|
|
1819
|
+
assert.ok(req.includes('- [ ] **REQ-01**'), 'REQ-01 should remain unchecked');
|
|
1820
|
+
assert.ok(req.includes('| REQ-01 | Phase 2 | Pending |'), 'REQ-01 should remain Pending');
|
|
1821
|
+
});
|
|
1822
|
+
|
|
1823
|
+
test('handles missing REQUIREMENTS.md gracefully', () => {
|
|
1824
|
+
fs.writeFileSync(
|
|
1825
|
+
path.join(tmpDir, '.planning', 'ROADMAP.md'),
|
|
1826
|
+
`# Roadmap
|
|
1827
|
+
|
|
1828
|
+
- [ ] Phase 1: Foundation
|
|
1829
|
+
**Requirements:** REQ-01
|
|
1830
|
+
|
|
1831
|
+
### Phase 1: Foundation
|
|
1832
|
+
**Goal:** Setup
|
|
1833
|
+
`
|
|
1834
|
+
);
|
|
1835
|
+
fs.writeFileSync(
|
|
1836
|
+
path.join(tmpDir, '.planning', 'STATE.md'),
|
|
1837
|
+
`# 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`
|
|
1838
|
+
);
|
|
1839
|
+
|
|
1840
|
+
const p1 = path.join(tmpDir, '.planning', 'phases', '01-foundation');
|
|
1841
|
+
fs.mkdirSync(p1, { recursive: true });
|
|
1842
|
+
fs.writeFileSync(path.join(p1, '01-01-PLAN.md'), '# Plan');
|
|
1843
|
+
fs.writeFileSync(path.join(p1, '01-01-SUMMARY.md'), '# Summary');
|
|
1844
|
+
|
|
1845
|
+
const result = runGsdTools('phase complete 1', tmpDir);
|
|
1846
|
+
assert.ok(result.success, `Command should succeed even without REQUIREMENTS.md: ${result.error}`);
|
|
1847
|
+
});
|
|
1659
1848
|
});
|
|
1660
1849
|
|
|
1661
1850
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -20,13 +20,15 @@ Look up the agent in the table for the resolved profile. Pass the model paramete
|
|
|
20
20
|
Task(
|
|
21
21
|
prompt="...",
|
|
22
22
|
subagent_type="gsd-planner",
|
|
23
|
-
model="{resolved_model}" #
|
|
23
|
+
model="{resolved_model}" # "inherit", "sonnet", or "haiku"
|
|
24
24
|
)
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
+
**Note:** Opus-tier agents resolve to `"inherit"` (not `"opus"`). This causes the agent to use the parent session's model, avoiding conflicts with organization policies that may block specific opus versions.
|
|
28
|
+
|
|
27
29
|
## Usage
|
|
28
30
|
|
|
29
31
|
1. Resolve once at orchestration start
|
|
30
32
|
2. Store the profile value
|
|
31
33
|
3. Look up each agent's model from the table when spawning
|
|
32
|
-
4. Pass model parameter to each Task call
|
|
34
|
+
4. Pass model parameter to each Task call (values: `"inherit"`, `"sonnet"`, `"haiku"`)
|
|
@@ -71,3 +71,6 @@ Verification requires goal-backward reasoning - checking if code *delivers* what
|
|
|
71
71
|
|
|
72
72
|
**Why Haiku for gsd-codebase-mapper?**
|
|
73
73
|
Read-only exploration and pattern extraction. No reasoning required, just structured output from file contents.
|
|
74
|
+
|
|
75
|
+
**Why `inherit` instead of passing `opus` directly?**
|
|
76
|
+
Claude Code's `"opus"` alias maps to a specific model version. Organizations may block older opus versions while allowing newer ones. GSD returns `"inherit"` for opus-tier agents, causing them to use whatever opus version the user has configured in their session. This avoids version conflicts and silent fallbacks to Sonnet.
|
|
@@ -79,6 +79,7 @@ Use AskUserQuestion to help users think by presenting concrete options to react
|
|
|
79
79
|
- Generic categories ("Technical", "Business", "Other")
|
|
80
80
|
- Leading options that presume an answer
|
|
81
81
|
- Too many options (2-4 is ideal)
|
|
82
|
+
- Headers longer than 12 characters (hard limit — validation will reject them)
|
|
82
83
|
|
|
83
84
|
**Example — vague answer:**
|
|
84
85
|
User says "it should be fast"
|