pan-wizard 2.8.1
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/LICENSE +21 -0
- package/README.md +772 -0
- package/agents/pan-debugger.md +1246 -0
- package/agents/pan-document_code.md +965 -0
- package/agents/pan-executor.md +469 -0
- package/agents/pan-integration-checker.md +443 -0
- package/agents/pan-phase-researcher.md +572 -0
- package/agents/pan-plan-checker.md +763 -0
- package/agents/pan-planner.md +1297 -0
- package/agents/pan-project-researcher.md +647 -0
- package/agents/pan-research-synthesizer.md +239 -0
- package/agents/pan-reviewer.md +112 -0
- package/agents/pan-roadmapper.md +642 -0
- package/agents/pan-verifier.md +672 -0
- package/assets/pan-logo-2000-transparent.svg +30 -0
- package/assets/pan-logo-2000.svg +43 -0
- package/assets/terminal.svg +119 -0
- package/bin/install-lib.cjs +616 -0
- package/bin/install.js +1936 -0
- package/commands/pan/add-phase.md +44 -0
- package/commands/pan/assumptions.md +47 -0
- package/commands/pan/audit-deployment.md +378 -0
- package/commands/pan/debug.md +168 -0
- package/commands/pan/discord.md +19 -0
- package/commands/pan/discuss-phase.md +84 -0
- package/commands/pan/exec-phase.md +45 -0
- package/commands/pan/focus-auto.md +323 -0
- package/commands/pan/focus-design.md +816 -0
- package/commands/pan/focus-exec.md +316 -0
- package/commands/pan/focus-plan.md +101 -0
- package/commands/pan/focus-scan.md +272 -0
- package/commands/pan/focus-sync.md +104 -0
- package/commands/pan/health.md +23 -0
- package/commands/pan/help.md +23 -0
- package/commands/pan/insert-phase.md +33 -0
- package/commands/pan/map-codebase.md +72 -0
- package/commands/pan/milestone-audit.md +37 -0
- package/commands/pan/milestone-cleanup.md +19 -0
- package/commands/pan/milestone-done.md +137 -0
- package/commands/pan/milestone-gaps.md +35 -0
- package/commands/pan/milestone-new.md +45 -0
- package/commands/pan/new-project.md +43 -0
- package/commands/pan/patches.md +110 -0
- package/commands/pan/pause.md +39 -0
- package/commands/pan/phase-budget.md +23 -0
- package/commands/pan/phase-tests.md +42 -0
- package/commands/pan/plan-phase.md +46 -0
- package/commands/pan/profile.md +36 -0
- package/commands/pan/progress.md +25 -0
- package/commands/pan/quick.md +42 -0
- package/commands/pan/remove-phase.md +32 -0
- package/commands/pan/research-phase.md +190 -0
- package/commands/pan/resume.md +41 -0
- package/commands/pan/retro.md +33 -0
- package/commands/pan/settings.md +37 -0
- package/commands/pan/todo-add.md +48 -0
- package/commands/pan/todo-check.md +46 -0
- package/commands/pan/update.md +38 -0
- package/commands/pan/verify-phase.md +39 -0
- package/hooks/dist/pan-check-update.js +62 -0
- package/hooks/dist/pan-context-monitor.js +122 -0
- package/hooks/dist/pan-statusline.js +108 -0
- package/package.json +66 -0
- package/pan-wizard-core/bin/lib/codebase.cjs +746 -0
- package/pan-wizard-core/bin/lib/commands.cjs +1435 -0
- package/pan-wizard-core/bin/lib/config.cjs +611 -0
- package/pan-wizard-core/bin/lib/constants.cjs +696 -0
- package/pan-wizard-core/bin/lib/context-budget.cjs +150 -0
- package/pan-wizard-core/bin/lib/core.cjs +650 -0
- package/pan-wizard-core/bin/lib/focus.cjs +900 -0
- package/pan-wizard-core/bin/lib/frontmatter.cjs +442 -0
- package/pan-wizard-core/bin/lib/init.cjs +881 -0
- package/pan-wizard-core/bin/lib/milestone.cjs +276 -0
- package/pan-wizard-core/bin/lib/phase.cjs +1212 -0
- package/pan-wizard-core/bin/lib/roadmap.cjs +470 -0
- package/pan-wizard-core/bin/lib/state.cjs +1029 -0
- package/pan-wizard-core/bin/lib/template.cjs +314 -0
- package/pan-wizard-core/bin/lib/utils.cjs +171 -0
- package/pan-wizard-core/bin/lib/verify.cjs +1808 -0
- package/pan-wizard-core/bin/pan-tools.cjs +773 -0
- package/pan-wizard-core/references/checkpoints.md +776 -0
- package/pan-wizard-core/references/continuation-format.md +249 -0
- package/pan-wizard-core/references/decimal-phase-calculation.md +65 -0
- package/pan-wizard-core/references/git-integration.md +248 -0
- package/pan-wizard-core/references/git-planning-commit.md +38 -0
- package/pan-wizard-core/references/model-profile-resolution.md +34 -0
- package/pan-wizard-core/references/model-profiles.md +111 -0
- package/pan-wizard-core/references/phase-argument-parsing.md +61 -0
- package/pan-wizard-core/references/planning-config.md +196 -0
- package/pan-wizard-core/references/questioning.md +145 -0
- package/pan-wizard-core/references/tdd.md +263 -0
- package/pan-wizard-core/references/ui-brand.md +160 -0
- package/pan-wizard-core/references/verification-patterns.md +612 -0
- package/pan-wizard-core/templates/codebase/architecture.md +283 -0
- package/pan-wizard-core/templates/codebase/best-practices.md +133 -0
- package/pan-wizard-core/templates/codebase/concerns.md +325 -0
- package/pan-wizard-core/templates/codebase/conventions.md +307 -0
- package/pan-wizard-core/templates/codebase/integrations.md +305 -0
- package/pan-wizard-core/templates/codebase/relationships.md +124 -0
- package/pan-wizard-core/templates/codebase/stack.md +199 -0
- package/pan-wizard-core/templates/codebase/structure.md +298 -0
- package/pan-wizard-core/templates/codebase/testing.md +480 -0
- package/pan-wizard-core/templates/config.json +37 -0
- package/pan-wizard-core/templates/context.md +283 -0
- package/pan-wizard-core/templates/continue-here.md +78 -0
- package/pan-wizard-core/templates/debug-subagent-prompt.md +91 -0
- package/pan-wizard-core/templates/debug.md +164 -0
- package/pan-wizard-core/templates/discovery.md +146 -0
- package/pan-wizard-core/templates/milestone-archive.md +123 -0
- package/pan-wizard-core/templates/milestone.md +115 -0
- package/pan-wizard-core/templates/phase-prompt.md +593 -0
- package/pan-wizard-core/templates/planner-subagent-prompt.md +117 -0
- package/pan-wizard-core/templates/project.md +184 -0
- package/pan-wizard-core/templates/requirements.md +231 -0
- package/pan-wizard-core/templates/research-project/architecture.md +204 -0
- package/pan-wizard-core/templates/research-project/features.md +147 -0
- package/pan-wizard-core/templates/research-project/pitfalls.md +200 -0
- package/pan-wizard-core/templates/research-project/stack.md +120 -0
- package/pan-wizard-core/templates/research-project/summary.md +170 -0
- package/pan-wizard-core/templates/research.md +552 -0
- package/pan-wizard-core/templates/retrospective.md +54 -0
- package/pan-wizard-core/templates/roadmap.md +202 -0
- package/pan-wizard-core/templates/standards.md +24 -0
- package/pan-wizard-core/templates/state.md +176 -0
- package/pan-wizard-core/templates/summary-complex.md +59 -0
- package/pan-wizard-core/templates/summary-minimal.md +41 -0
- package/pan-wizard-core/templates/summary-standard.md +49 -0
- package/pan-wizard-core/templates/summary.md +249 -0
- package/pan-wizard-core/templates/uat.md +247 -0
- package/pan-wizard-core/templates/user-setup.md +311 -0
- package/pan-wizard-core/templates/validation.md +76 -0
- package/pan-wizard-core/templates/verification-report.md +322 -0
- package/pan-wizard-core/workflows/add-phase.md +111 -0
- package/pan-wizard-core/workflows/assumptions.md +178 -0
- package/pan-wizard-core/workflows/diagnose-issues.md +219 -0
- package/pan-wizard-core/workflows/discuss-phase.md +542 -0
- package/pan-wizard-core/workflows/exec-phase.md +572 -0
- package/pan-wizard-core/workflows/execute-plan.md +448 -0
- package/pan-wizard-core/workflows/health.md +156 -0
- package/pan-wizard-core/workflows/help.md +431 -0
- package/pan-wizard-core/workflows/insert-phase.md +129 -0
- package/pan-wizard-core/workflows/map-codebase.md +401 -0
- package/pan-wizard-core/workflows/milestone-audit.md +297 -0
- package/pan-wizard-core/workflows/milestone-cleanup.md +152 -0
- package/pan-wizard-core/workflows/milestone-gaps.md +274 -0
- package/pan-wizard-core/workflows/milestone-new.md +382 -0
- package/pan-wizard-core/workflows/new-project.md +1178 -0
- package/pan-wizard-core/workflows/pause.md +122 -0
- package/pan-wizard-core/workflows/phase-tests.md +388 -0
- package/pan-wizard-core/workflows/plan-phase.md +569 -0
- package/pan-wizard-core/workflows/profile.md +115 -0
- package/pan-wizard-core/workflows/progress.md +381 -0
- package/pan-wizard-core/workflows/quick.md +453 -0
- package/pan-wizard-core/workflows/remove-phase.md +154 -0
- package/pan-wizard-core/workflows/research-phase.md +73 -0
- package/pan-wizard-core/workflows/resume-project.md +306 -0
- package/pan-wizard-core/workflows/retro.md +121 -0
- package/pan-wizard-core/workflows/settings.md +213 -0
- package/pan-wizard-core/workflows/todo-add.md +157 -0
- package/pan-wizard-core/workflows/todo-check.md +176 -0
- package/pan-wizard-core/workflows/transition.md +544 -0
- package/pan-wizard-core/workflows/update.md +219 -0
- package/pan-wizard-core/workflows/verify-phase.md +301 -0
- package/scripts/build-hooks.js +43 -0
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Roadmap — Roadmap parsing and update operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { PLANNING_DIR, PHASES_DIR, ROADMAP_FILE, REQUIREMENTS_FILE, isPlanFile, isSummaryFile, isContextFile, isResearchFile, PHASE_HEADER_RE, getPlanId, getSummaryId } = require('./constants.cjs');
|
|
8
|
+
const { planningPath, phasesPath, filterPlanFiles, filterSummaryFiles, classifyPhaseStatus } = require('./utils.cjs');
|
|
9
|
+
const { escapeRegex, normalizePhaseName, output, error, findPhaseInternal } = require('./core.cjs');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extract a single phase section from roadmap.md including goal and success criteria.
|
|
13
|
+
* @param {string} cwd - Working directory path
|
|
14
|
+
* @param {string} phaseNum - Phase number to retrieve
|
|
15
|
+
* @param {boolean} raw - If true, output raw section text instead of JSON
|
|
16
|
+
* @returns {void}
|
|
17
|
+
*/
|
|
18
|
+
function cmdRoadmapGetPhase(cwd, phaseNum, raw) {
|
|
19
|
+
const roadmapPath = path.join(planningPath(cwd), ROADMAP_FILE);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const content = fs.readFileSync(roadmapPath, 'utf-8');
|
|
23
|
+
|
|
24
|
+
// Escape special regex chars in phase number, handle decimal
|
|
25
|
+
const escapedPhase = escapeRegex(phaseNum);
|
|
26
|
+
|
|
27
|
+
// Match "## Phase X:", "### Phase X:", or "#### Phase X:" with optional name
|
|
28
|
+
const phasePattern = new RegExp(
|
|
29
|
+
`#{2,4}\\s*Phase\\s+${escapedPhase}:\\s*([^\\n]+)`,
|
|
30
|
+
'i'
|
|
31
|
+
);
|
|
32
|
+
const headerMatch = content.match(phasePattern);
|
|
33
|
+
|
|
34
|
+
if (!headerMatch) {
|
|
35
|
+
// Fallback: check if phase exists in summary list but missing detail section
|
|
36
|
+
const checklistPattern = new RegExp(
|
|
37
|
+
`-\\s*\\[[ x]\\]\\s*\\*\\*Phase\\s+${escapedPhase}:\\s*([^*]+)\\*\\*`,
|
|
38
|
+
'i'
|
|
39
|
+
);
|
|
40
|
+
const checklistMatch = content.match(checklistPattern);
|
|
41
|
+
|
|
42
|
+
if (checklistMatch) {
|
|
43
|
+
// Phase exists in summary but missing detail section - malformed ROADMAP
|
|
44
|
+
output({
|
|
45
|
+
found: false,
|
|
46
|
+
phase_number: phaseNum,
|
|
47
|
+
phase_name: checklistMatch[1].trim(),
|
|
48
|
+
error: 'malformed_roadmap',
|
|
49
|
+
message: `Phase ${phaseNum} exists in summary list but missing "### Phase ${phaseNum}:" detail section. roadmap.md needs both formats.`
|
|
50
|
+
}, raw, '');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
output({ found: false, phase_number: phaseNum }, raw, '');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const phaseName = headerMatch[1].trim();
|
|
59
|
+
const headerIndex = headerMatch.index;
|
|
60
|
+
|
|
61
|
+
// Find the end of this section (next ## or ### phase header, or end of file)
|
|
62
|
+
const restOfContent = content.slice(headerIndex);
|
|
63
|
+
const nextHeaderMatch = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
64
|
+
const sectionEnd = nextHeaderMatch
|
|
65
|
+
? headerIndex + nextHeaderMatch.index
|
|
66
|
+
: content.length;
|
|
67
|
+
|
|
68
|
+
const section = content.slice(headerIndex, sectionEnd).trim();
|
|
69
|
+
|
|
70
|
+
// Extract goal if present
|
|
71
|
+
const goalMatch = section.match(/\*\*Goal:\*\*\s*([^\n]+)/i);
|
|
72
|
+
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
73
|
+
|
|
74
|
+
// Extract success criteria as structured array
|
|
75
|
+
const criteriaMatch = section.match(/\*\*Success Criteria\*\*[^\n]*:\s*\n((?:\s*\d+\.\s*[^\n]+\n?)+)/i);
|
|
76
|
+
const success_criteria = criteriaMatch
|
|
77
|
+
? criteriaMatch[1].trim().split('\n').map(line => line.replace(/^\s*\d+\.\s*/, '').trim()).filter(Boolean)
|
|
78
|
+
: [];
|
|
79
|
+
|
|
80
|
+
output(
|
|
81
|
+
{
|
|
82
|
+
found: true,
|
|
83
|
+
phase_number: phaseNum,
|
|
84
|
+
phase_name: phaseName,
|
|
85
|
+
goal,
|
|
86
|
+
success_criteria,
|
|
87
|
+
section,
|
|
88
|
+
},
|
|
89
|
+
raw,
|
|
90
|
+
section
|
|
91
|
+
);
|
|
92
|
+
} catch {
|
|
93
|
+
output({ found: false, error: 'roadmap.md not found' }, raw, '');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ─── Helper functions extracted from cmdRoadmapAnalyze ──────────────────────
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Parse all phase sections from roadmap.md content.
|
|
101
|
+
* Returns an array of phase objects with number, name, goal, depends_on,
|
|
102
|
+
* and the raw section text.
|
|
103
|
+
* @param {string} content - Full roadmap.md content
|
|
104
|
+
* @returns {Array<{number: string, name: string, goal: string|null, depends_on: string|null, sectionStart: number}>}
|
|
105
|
+
*/
|
|
106
|
+
function enumerateRoadmapPhases(content) {
|
|
107
|
+
const phases = [];
|
|
108
|
+
|
|
109
|
+
// Reset lastIndex since PHASE_HEADER_RE is a global regex
|
|
110
|
+
PHASE_HEADER_RE.lastIndex = 0;
|
|
111
|
+
let match;
|
|
112
|
+
|
|
113
|
+
while ((match = PHASE_HEADER_RE.exec(content)) !== null) {
|
|
114
|
+
const phaseNum = match[1];
|
|
115
|
+
const phaseName = match[2].replace(/\(INSERTED\)/i, '').trim();
|
|
116
|
+
|
|
117
|
+
// Extract the section text between this header and the next phase header
|
|
118
|
+
const sectionStart = match.index;
|
|
119
|
+
const restOfContent = content.slice(sectionStart);
|
|
120
|
+
const nextHeader = restOfContent.match(/\n#{2,4}\s+Phase\s+\d/i);
|
|
121
|
+
const sectionEnd = nextHeader ? sectionStart + nextHeader.index : content.length;
|
|
122
|
+
const section = content.slice(sectionStart, sectionEnd);
|
|
123
|
+
|
|
124
|
+
// Extract goal from the section
|
|
125
|
+
const goalMatch = section.match(/\*\*Goal:\*\*\s*([^\n]+)/i);
|
|
126
|
+
const goal = goalMatch ? goalMatch[1].trim() : null;
|
|
127
|
+
|
|
128
|
+
// Extract dependency info from the section
|
|
129
|
+
const dependsMatch = section.match(/\*\*Depends on:\*\*\s*([^\n]+)/i);
|
|
130
|
+
const dependsOn = dependsMatch ? dependsMatch[1].trim() : null;
|
|
131
|
+
|
|
132
|
+
phases.push({
|
|
133
|
+
number: phaseNum,
|
|
134
|
+
name: phaseName,
|
|
135
|
+
goal,
|
|
136
|
+
depends_on: dependsOn,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return phases;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Enrich a phase object with disk status by reading the phases directory.
|
|
145
|
+
* Adds plan_count, summary_count, has_context, has_research, and disk_status.
|
|
146
|
+
* @param {string} cwd - Working directory path
|
|
147
|
+
* @param {Object} phase - Phase object with at least a `number` field
|
|
148
|
+
* @returns {Object} Phase object enriched with disk status fields
|
|
149
|
+
*/
|
|
150
|
+
function enrichPhaseWithDiskStatus(cwd, phase) {
|
|
151
|
+
const phasesDirPath = phasesPath(cwd);
|
|
152
|
+
const normalized = normalizePhaseName(phase.number);
|
|
153
|
+
let diskStatus = 'no_directory';
|
|
154
|
+
let planCount = 0;
|
|
155
|
+
let summaryCount = 0;
|
|
156
|
+
let hasContext = false;
|
|
157
|
+
let hasResearch = false;
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const entries = fs.readdirSync(phasesDirPath, { withFileTypes: true });
|
|
161
|
+
const dirs = entries.filter(entry => entry.isDirectory()).map(entry => entry.name);
|
|
162
|
+
const dirMatch = dirs.find(dirName => dirName.startsWith(normalized + '-') || dirName === normalized);
|
|
163
|
+
|
|
164
|
+
if (dirMatch) {
|
|
165
|
+
const phaseFiles = fs.readdirSync(path.join(phasesDirPath, dirMatch));
|
|
166
|
+
planCount = filterPlanFiles(phaseFiles).length;
|
|
167
|
+
summaryCount = filterSummaryFiles(phaseFiles).length;
|
|
168
|
+
hasContext = phaseFiles.some(isContextFile);
|
|
169
|
+
hasResearch = phaseFiles.some(isResearchFile);
|
|
170
|
+
|
|
171
|
+
diskStatus = classifyPhaseStatus(planCount, summaryCount, { hasResearch, hasContext });
|
|
172
|
+
}
|
|
173
|
+
} catch {
|
|
174
|
+
// Phase directory may not exist yet
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
...phase,
|
|
179
|
+
plan_count: planCount,
|
|
180
|
+
summary_count: summaryCount,
|
|
181
|
+
has_context: hasContext,
|
|
182
|
+
has_research: hasResearch,
|
|
183
|
+
disk_status: diskStatus,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Extract milestone headings from roadmap.md content.
|
|
189
|
+
* Looks for ## headers containing version numbers (e.g., "## v1.0 MVP").
|
|
190
|
+
* @param {string} content - Full roadmap.md content
|
|
191
|
+
* @returns {Array<{heading: string, version: string}>}
|
|
192
|
+
*/
|
|
193
|
+
function extractMilestones(content) {
|
|
194
|
+
const milestones = [];
|
|
195
|
+
const milestonePattern = /##\s*(.*v(\d+\.\d+)[^(\n]*)/gi;
|
|
196
|
+
let milestoneMatch;
|
|
197
|
+
|
|
198
|
+
while ((milestoneMatch = milestonePattern.exec(content)) !== null) {
|
|
199
|
+
milestones.push({
|
|
200
|
+
heading: milestoneMatch[1].trim(),
|
|
201
|
+
version: 'v' + milestoneMatch[2],
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return milestones;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Compute aggregate statistics from an array of enriched phase objects.
|
|
210
|
+
* Identifies current phase (in progress), next phase (not yet started),
|
|
211
|
+
* and computes progress percentage.
|
|
212
|
+
* @param {Array} phases - Array of phase objects with disk_status, plan_count, summary_count
|
|
213
|
+
* @returns {{currentPhase: string|null, nextPhase: string|null, totalPlans: number, totalSummaries: number, completedPhases: number, progressPercent: number}}
|
|
214
|
+
*/
|
|
215
|
+
function computeRoadmapStats(phases) {
|
|
216
|
+
const totalPlans = phases.reduce((sum, phase) => sum + phase.plan_count, 0);
|
|
217
|
+
const totalSummaries = phases.reduce((sum, phase) => sum + phase.summary_count, 0);
|
|
218
|
+
const completedPhases = phases.filter(phase => phase.disk_status === 'complete').length;
|
|
219
|
+
|
|
220
|
+
// Current phase: first phase that is in-progress (planned or partial)
|
|
221
|
+
const currentPhase = phases.find(
|
|
222
|
+
phase => phase.disk_status === 'planned' || phase.disk_status === 'partial'
|
|
223
|
+
) || null;
|
|
224
|
+
|
|
225
|
+
// Next phase: first phase that hasn't been started yet
|
|
226
|
+
const nextPhase = phases.find(
|
|
227
|
+
phase => phase.disk_status === 'empty' ||
|
|
228
|
+
phase.disk_status === 'no_directory' ||
|
|
229
|
+
phase.disk_status === 'discussed' ||
|
|
230
|
+
phase.disk_status === 'researched'
|
|
231
|
+
) || null;
|
|
232
|
+
|
|
233
|
+
const progressPercent = totalPlans > 0
|
|
234
|
+
? Math.min(100, Math.round((totalSummaries / totalPlans) * 100))
|
|
235
|
+
: 0;
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
currentPhase: currentPhase ? currentPhase.number : null,
|
|
239
|
+
nextPhase: nextPhase ? nextPhase.number : null,
|
|
240
|
+
totalPlans,
|
|
241
|
+
totalSummaries,
|
|
242
|
+
completedPhases,
|
|
243
|
+
progressPercent,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Analyze entire roadmap.md: extract all phases, disk status, milestones, and progress stats.
|
|
249
|
+
* Orchestrates enumerateRoadmapPhases, enrichPhaseWithDiskStatus,
|
|
250
|
+
* extractMilestones, and computeRoadmapStats.
|
|
251
|
+
* @param {string} cwd - Working directory path
|
|
252
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
253
|
+
* @returns {void}
|
|
254
|
+
*/
|
|
255
|
+
function cmdRoadmapAnalyze(cwd, raw) {
|
|
256
|
+
const roadmapPath = path.join(planningPath(cwd), ROADMAP_FILE);
|
|
257
|
+
|
|
258
|
+
let content;
|
|
259
|
+
try {
|
|
260
|
+
content = fs.readFileSync(roadmapPath, 'utf-8');
|
|
261
|
+
} catch {
|
|
262
|
+
output({ error: 'roadmap.md not found', milestones: [], phases: [], current_phase: null }, raw);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Parse phase sections from ROADMAP content
|
|
267
|
+
const rawPhases = enumerateRoadmapPhases(content);
|
|
268
|
+
|
|
269
|
+
// Enrich each phase with disk status (plan/summary counts)
|
|
270
|
+
const phases = rawPhases.map(phase => {
|
|
271
|
+
const enriched = enrichPhaseWithDiskStatus(cwd, phase);
|
|
272
|
+
|
|
273
|
+
// Check ROADMAP checkbox status for this phase
|
|
274
|
+
const checkboxPattern = new RegExp(
|
|
275
|
+
`-\\s*\\[(x| )\\]\\s*.*Phase\\s+${escapeRegex(phase.number)}`,
|
|
276
|
+
'i'
|
|
277
|
+
);
|
|
278
|
+
const checkboxMatch = content.match(checkboxPattern);
|
|
279
|
+
const roadmapComplete = checkboxMatch ? checkboxMatch[1] === 'x' : false;
|
|
280
|
+
|
|
281
|
+
return { ...enriched, roadmap_complete: roadmapComplete };
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Extract milestone headings
|
|
285
|
+
const milestones = extractMilestones(content);
|
|
286
|
+
|
|
287
|
+
// Compute aggregate stats
|
|
288
|
+
const stats = computeRoadmapStats(phases);
|
|
289
|
+
|
|
290
|
+
// Detect phases in summary list without detail sections (malformed ROADMAP)
|
|
291
|
+
const checklistPattern = /-\s*\[[ x]\]\s*\*\*Phase\s+(\d+[A-Z]?(?:\.\d+)*)/gi;
|
|
292
|
+
const checklistPhases = new Set();
|
|
293
|
+
let checklistMatch;
|
|
294
|
+
while ((checklistMatch = checklistPattern.exec(content)) !== null) {
|
|
295
|
+
checklistPhases.add(checklistMatch[1]);
|
|
296
|
+
}
|
|
297
|
+
const detailPhases = new Set(phases.map(phase => phase.number));
|
|
298
|
+
const missingDetails = [...checklistPhases].filter(phaseNum => !detailPhases.has(phaseNum));
|
|
299
|
+
|
|
300
|
+
const result = {
|
|
301
|
+
milestones,
|
|
302
|
+
phases,
|
|
303
|
+
phase_count: phases.length,
|
|
304
|
+
completed_phases: stats.completedPhases,
|
|
305
|
+
total_plans: stats.totalPlans,
|
|
306
|
+
total_summaries: stats.totalSummaries,
|
|
307
|
+
progress_percent: stats.progressPercent,
|
|
308
|
+
current_phase: stats.currentPhase,
|
|
309
|
+
next_phase: stats.nextPhase,
|
|
310
|
+
missing_phase_details: missingDetails.length > 0 ? missingDetails : null,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
output(result, raw);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Update roadmap.md progress table and plan counts for a specific phase from disk state.
|
|
318
|
+
* @param {string} cwd - Working directory path
|
|
319
|
+
* @param {string} phaseNum - Phase number to update progress for
|
|
320
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
321
|
+
* @returns {void}
|
|
322
|
+
*/
|
|
323
|
+
function cmdRoadmapUpdatePlanProgress(cwd, phaseNum, raw) {
|
|
324
|
+
if (!phaseNum) {
|
|
325
|
+
error('phase number required for roadmap update-plan-progress');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const roadmapPath = path.join(planningPath(cwd), ROADMAP_FILE);
|
|
329
|
+
|
|
330
|
+
const phaseInfo = findPhaseInternal(cwd, phaseNum);
|
|
331
|
+
if (!phaseInfo) {
|
|
332
|
+
error(`Phase ${phaseNum} not found`);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const planCount = phaseInfo.plans.length;
|
|
336
|
+
const summaryCount = phaseInfo.summaries.length;
|
|
337
|
+
|
|
338
|
+
if (planCount === 0) {
|
|
339
|
+
output({ updated: false, reason: 'No plans found', plan_count: 0, summary_count: 0 }, raw, 'no plans');
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const isComplete = summaryCount >= planCount;
|
|
344
|
+
const status = isComplete ? 'Complete' : summaryCount > 0 ? 'In Progress' : 'Planned';
|
|
345
|
+
const today = new Date().toISOString().split('T')[0];
|
|
346
|
+
|
|
347
|
+
let roadmapContent;
|
|
348
|
+
try {
|
|
349
|
+
roadmapContent = fs.readFileSync(roadmapPath, 'utf-8');
|
|
350
|
+
} catch {
|
|
351
|
+
output({ updated: false, reason: 'roadmap.md not found', plan_count: planCount, summary_count: summaryCount }, raw, 'no roadmap');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
const phaseEscaped = escapeRegex(phaseNum);
|
|
355
|
+
|
|
356
|
+
// Progress table row: update Plans column (summaries/plans) and Status column
|
|
357
|
+
const tablePattern = new RegExp(
|
|
358
|
+
`(\\|\\s*${phaseEscaped}\\.?\\s[^|]*\\|)[^|]*(\\|)\\s*[^|]*(\\|)\\s*[^|]*(\\|)`,
|
|
359
|
+
'i'
|
|
360
|
+
);
|
|
361
|
+
const dateField = isComplete ? ` ${today} ` : ' ';
|
|
362
|
+
roadmapContent = roadmapContent.replace(
|
|
363
|
+
tablePattern,
|
|
364
|
+
`$1 ${summaryCount}/${planCount} $2 ${status.padEnd(11)}$3${dateField}$4`
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
// Update plan count in phase detail section
|
|
368
|
+
const planCountPattern = new RegExp(
|
|
369
|
+
`(#{2,4}\\s*Phase\\s+${phaseEscaped}[\\s\\S]*?\\*\\*Plans:\\*\\*\\s*)[^\\n]+`,
|
|
370
|
+
'i'
|
|
371
|
+
);
|
|
372
|
+
const planCountText = isComplete
|
|
373
|
+
? `${summaryCount}/${planCount} plans complete`
|
|
374
|
+
: `${summaryCount}/${planCount} plans executed`;
|
|
375
|
+
roadmapContent = roadmapContent.replace(planCountPattern, `$1${planCountText}`);
|
|
376
|
+
|
|
377
|
+
// If complete: check checkbox
|
|
378
|
+
if (isComplete) {
|
|
379
|
+
const checkboxPattern = new RegExp(
|
|
380
|
+
`(-\\s*\\[)[ ](\\]\\s*.*Phase\\s+${phaseEscaped}[:\\s][^\\n]*)`,
|
|
381
|
+
'i'
|
|
382
|
+
);
|
|
383
|
+
roadmapContent = roadmapContent.replace(checkboxPattern, `$1x$2 (completed ${today})`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Check individual plan checkboxes for plans that have matching summaries
|
|
387
|
+
const completedPlanIds = new Set(phaseInfo.summaries.map(s => getSummaryId(s)));
|
|
388
|
+
for (const plan of phaseInfo.plans) {
|
|
389
|
+
const planId = getPlanId(plan);
|
|
390
|
+
if (completedPlanIds.has(planId)) {
|
|
391
|
+
const planEscaped = escapeRegex(plan);
|
|
392
|
+
const planCheckbox = new RegExp(
|
|
393
|
+
`(-\\s*\\[) (\\]\\s*${planEscaped})`,
|
|
394
|
+
'i'
|
|
395
|
+
);
|
|
396
|
+
roadmapContent = roadmapContent.replace(planCheckbox, '$1x$2');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
fs.writeFileSync(roadmapPath, roadmapContent, 'utf-8');
|
|
402
|
+
} catch (err) {
|
|
403
|
+
output({ updated: false, reason: 'Failed to write roadmap.md: ' + err.message }, raw, 'write error');
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
output({
|
|
408
|
+
updated: true,
|
|
409
|
+
phase: phaseNum,
|
|
410
|
+
plan_count: planCount,
|
|
411
|
+
summary_count: summaryCount,
|
|
412
|
+
status,
|
|
413
|
+
complete: isComplete,
|
|
414
|
+
}, raw, `${summaryCount}/${planCount} ${status}`);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Check requirement checkboxes in REQUIREMENTS.md for the given requirement IDs.
|
|
419
|
+
* Called after plan/phase completion to keep requirements in sync with STATE.md.
|
|
420
|
+
* @param {string} cwd - Working directory path
|
|
421
|
+
* @param {string[]} requirementIds - Array of requirement IDs to check (e.g., ['CONN-01', 'DISC-02'])
|
|
422
|
+
* @returns {{ updated: boolean, checked: number, total: number }}
|
|
423
|
+
*/
|
|
424
|
+
function syncRequirementCheckboxes(cwd, requirementIds) {
|
|
425
|
+
if (!requirementIds || requirementIds.length === 0) {
|
|
426
|
+
return { updated: false, checked: 0, total: 0 };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const reqPath = path.join(planningPath(cwd), REQUIREMENTS_FILE);
|
|
430
|
+
let content;
|
|
431
|
+
try {
|
|
432
|
+
content = fs.readFileSync(reqPath, 'utf-8');
|
|
433
|
+
} catch {
|
|
434
|
+
return { updated: false, checked: 0, total: requirementIds.length, reason: 'REQUIREMENTS.md not found' };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
let checked = 0;
|
|
438
|
+
for (const id of requirementIds) {
|
|
439
|
+
const idEscaped = escapeRegex(id);
|
|
440
|
+
// Match unchecked checkbox followed by the requirement ID (possibly bold)
|
|
441
|
+
const pattern = new RegExp(`(- \\[) (\\]\\s*\\*{0,2}${idEscaped})`, 'g');
|
|
442
|
+
const newContent = content.replace(pattern, '$1x$2');
|
|
443
|
+
if (newContent !== content) {
|
|
444
|
+
checked++;
|
|
445
|
+
content = newContent;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (checked > 0) {
|
|
450
|
+
try {
|
|
451
|
+
fs.writeFileSync(reqPath, content, 'utf-8');
|
|
452
|
+
} catch {
|
|
453
|
+
return { updated: false, checked: 0, total: requirementIds.length, reason: 'Failed to write REQUIREMENTS.md' };
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return { updated: checked > 0, checked, total: requirementIds.length };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
module.exports = {
|
|
461
|
+
cmdRoadmapGetPhase,
|
|
462
|
+
cmdRoadmapAnalyze,
|
|
463
|
+
cmdRoadmapUpdatePlanProgress,
|
|
464
|
+
syncRequirementCheckboxes,
|
|
465
|
+
// Exported for testability
|
|
466
|
+
enumerateRoadmapPhases,
|
|
467
|
+
enrichPhaseWithDiskStatus,
|
|
468
|
+
extractMilestones,
|
|
469
|
+
computeRoadmapStats,
|
|
470
|
+
};
|