@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.
Files changed (36) hide show
  1. package/CHANGELOG.md +33 -1
  2. package/agents/gsd-phase-researcher.md +1 -1
  3. package/agents/gsd-roadmapper.md +39 -5
  4. package/agents/gsd-verifier.md +20 -4
  5. package/bin/install-codex.js +1 -1
  6. package/bin/install.js +65 -8
  7. package/commands/gsd/discuss-phase.md +3 -2
  8. package/commands/gsd/plan-phase.md +1 -1
  9. package/commands/gsd/reapply-patches.md +3 -3
  10. package/commands/gsd/verify-work.md +1 -1
  11. package/get-shit-done/bin/gsd-tools.js +222 -20
  12. package/get-shit-done/bin/gsd-tools.test.js +189 -0
  13. package/get-shit-done/references/model-profile-resolution.md +4 -2
  14. package/get-shit-done/references/model-profiles.md +3 -0
  15. package/get-shit-done/references/questioning.md +1 -0
  16. package/get-shit-done/templates/UAT.md +1 -1
  17. package/get-shit-done/templates/config.json +2 -1
  18. package/get-shit-done/templates/context.md +2 -2
  19. package/get-shit-done/templates/planner-subagent-prompt.md +4 -4
  20. package/get-shit-done/templates/research.md +2 -2
  21. package/get-shit-done/templates/verification-report.md +1 -1
  22. package/get-shit-done/workflows/diagnose-issues.md +1 -1
  23. package/get-shit-done/workflows/discovery-phase.md +1 -1
  24. package/get-shit-done/workflows/discuss-phase.md +64 -3
  25. package/get-shit-done/workflows/execute-phase.md +68 -20
  26. package/get-shit-done/workflows/execute-plan.md +4 -1
  27. package/get-shit-done/workflows/new-milestone.md +1 -1
  28. package/get-shit-done/workflows/new-project.md +3 -3
  29. package/get-shit-done/workflows/plan-phase.md +54 -2
  30. package/get-shit-done/workflows/progress.md +2 -2
  31. package/get-shit-done/workflows/settings.md +13 -2
  32. package/get-shit-done/workflows/transition.md +48 -2
  33. package/get-shit-done/workflows/update.md +4 -2
  34. package/get-shit-done/workflows/verify-phase.md +18 -2
  35. package/get-shit-done/workflows/verify-work.md +3 -3
  36. 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 "### Phase X:" or "### Phase X.Y:" with optional name
839
+ // Match "## Phase X:" or "### Phase X:" with optional name
839
840
  const phasePattern = new RegExp(
840
- `###\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`,
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###\s+Phase\s+\d/i);
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 model = agentModels[profile] || agentModels['balanced'] || 'sonnet';
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 = /###\s*Phase\s+(\d+(?:\.\d+)?)\s*:\s*([^\n]+)/gi;
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###\s+Phase\s+\d/i);
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 = /###\s*Phase\s+(\d+)(?:\.\d+)?:/gi;
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(`###\\s*Phase\\s+${afterPhaseEscaped}:`, 'i');
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(`(###\\s*Phase\\s+${afterPhaseEscaped}:[^\\n]*\\n)`, 'i');
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###\s+Phase\s+\d/i);
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?###\\s*Phase\\s+${targetEscaped}\\s*:[\\s\\S]*?(?=\\n###\\s+Phase\\s+\\d|$)`,
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(`(###\\s*Phase\\s+)${oldStr}(\\s*:)`, 'gi'),
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
- `(###\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
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 = /###\s*Phase\s+(\d+(?:\.\d+)?)\s*:/gi;
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
- return agentModels[profile] || agentModels['balanced'] || 'sonnet';
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
- const phaseInfo = findPhaseInternal(cwd, phase);
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}" # e.g., "opus" for quality profile
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"
@@ -1,6 +1,6 @@
1
1
  # UAT Template
2
2
 
3
- Template for `.planning/phases/XX-name/{phase}-UAT.md` — persistent UAT session tracking.
3
+ Template for `.planning/phases/XX-name/{phase_num}-UAT.md` — persistent UAT session tracking.
4
4
 
5
5
  ---
6
6
 
@@ -4,7 +4,8 @@
4
4
  "workflow": {
5
5
  "research": true,
6
6
  "plan_check": true,
7
- "verifier": true
7
+ "verifier": true,
8
+ "auto_advance": false
8
9
  },
9
10
  "planning": {
10
11
  "commit_docs": true,