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,881 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Init — Compound init commands for workflow bootstrapping
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const { loadConfig, resolveModelInternal, findPhaseInternal, getRoadmapPhaseInternal, pathExistsInternal, generateSlugInternal, getMilestoneInfo, normalizePhaseName, toPosix, output, error, scanPendingTodos, isGitRepo, execGit } = require('./core.cjs');
|
|
9
|
+
const { PLANNING_DIR, PHASES_DIR, CODEBASE_DIR, QUICK_DIR, MILESTONES_DIR, STATE_FILE, ROADMAP_FILE, CONFIG_FILE, PROJECT_FILE, REQUIREMENTS_FILE, isPlanFile, isSummaryFile, isResearchFile, isContextFile, isVerificationFile, PLAN_SUFFIX, SUMMARY_SUFFIX, CONTEXT_SUFFIX, RESEARCH_SUFFIX, VERIFICATION_SUFFIX, UAT_SUFFIX, MAX_SLUG_LENGTH } = require('./constants.cjs');
|
|
10
|
+
const { planningPath, phasesPath, filterPlanFiles, filterSummaryFiles, classifyPhaseStatus, hasBraveSearchKey } = require('./utils.cjs');
|
|
11
|
+
const { classifyPlanTier } = require('./phase.cjs');
|
|
12
|
+
const { extractFrontmatter } = require('./frontmatter.cjs');
|
|
13
|
+
const { detectLanguages } = require('./codebase.cjs');
|
|
14
|
+
|
|
15
|
+
// ---- Git helpers ----
|
|
16
|
+
|
|
17
|
+
function ensureGitRepo(cwd) {
|
|
18
|
+
if (isGitRepo(cwd)) return true;
|
|
19
|
+
const result = execGit(cwd, ['init']);
|
|
20
|
+
return result.exitCode === 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---- Shared path builders for JSON output values (forward-slash, not filesystem) ----
|
|
24
|
+
|
|
25
|
+
/** Build a forward-slash relative path under .planning for JSON output */
|
|
26
|
+
function planningRelPath(...segments) {
|
|
27
|
+
return [PLANNING_DIR, ...segments].join('/');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Extract requirement IDs from a ROADMAP phase section.
|
|
32
|
+
* Looks for a **Requirements**: line, strips brackets, splits by comma,
|
|
33
|
+
* and returns null when absent or set to 'TBD'.
|
|
34
|
+
*/
|
|
35
|
+
function extractReqIds(roadmapPhase) {
|
|
36
|
+
const reqMatch = roadmapPhase?.section?.match(/^\*\*Requirements\*\*:[^\S\n]*([^\n]*)$/m);
|
|
37
|
+
const reqExtracted = reqMatch
|
|
38
|
+
? reqMatch[1].replace(/[\[\]]/g, '').split(',').map(segment => segment.trim()).filter(Boolean).join(', ')
|
|
39
|
+
: null;
|
|
40
|
+
return (reqExtracted && reqExtracted !== 'TBD') ? reqExtracted : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Discover optional artifact files (CONTEXT, RESEARCH, VERIFICATION, UAT)
|
|
45
|
+
* within a phase directory and attach their paths to a result object.
|
|
46
|
+
*
|
|
47
|
+
* Each suffix is checked because phases may contain any combination of these
|
|
48
|
+
* optional artifacts. Files may be named either with a prefix (e.g. "01-context.md")
|
|
49
|
+
* or as bare names (e.g. "context.md").
|
|
50
|
+
*/
|
|
51
|
+
function attachPhaseArtifactPaths(result, cwd, phaseDirectory) {
|
|
52
|
+
const phaseDirFull = path.join(cwd, phaseDirectory);
|
|
53
|
+
try {
|
|
54
|
+
const files = fs.readdirSync(phaseDirFull);
|
|
55
|
+
const contextFile = files.find(isContextFile);
|
|
56
|
+
if (contextFile) {
|
|
57
|
+
result.context_path = toPosix(path.join(phaseDirectory, contextFile));
|
|
58
|
+
}
|
|
59
|
+
const researchFile = files.find(isResearchFile);
|
|
60
|
+
if (researchFile) {
|
|
61
|
+
result.research_path = toPosix(path.join(phaseDirectory, researchFile));
|
|
62
|
+
}
|
|
63
|
+
const verificationFile = files.find(isVerificationFile);
|
|
64
|
+
if (verificationFile) {
|
|
65
|
+
result.verification_path = toPosix(path.join(phaseDirectory, verificationFile));
|
|
66
|
+
}
|
|
67
|
+
const uatFile = files.find(filename => filename.endsWith(UAT_SUFFIX) || filename === 'uat.md');
|
|
68
|
+
if (uatFile) {
|
|
69
|
+
result.uat_path = toPosix(path.join(phaseDirectory, uatFile));
|
|
70
|
+
}
|
|
71
|
+
} catch {
|
|
72
|
+
// Phase directory may not exist yet or may be unreadable
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Bootstrap context for phase execution: models, config, phase info, and plan inventory.
|
|
78
|
+
* @param {string} cwd - Working directory path
|
|
79
|
+
* @param {string} phase - Phase number to initialize execution for
|
|
80
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
81
|
+
* @returns {void}
|
|
82
|
+
*/
|
|
83
|
+
/**
|
|
84
|
+
* Map effort string to budget points.
|
|
85
|
+
* @param {string|null} effort
|
|
86
|
+
* @returns {number}
|
|
87
|
+
*/
|
|
88
|
+
function effortToPoints(effort) {
|
|
89
|
+
const map = { XS: 1, S: 2, M: 4, L: 10, XL: 20 };
|
|
90
|
+
return map[String(effort).toUpperCase()] || 4; // default to M (4)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function cmdInitExecutePhase(cwd, phase, raw, opts) {
|
|
94
|
+
if (!phase) {
|
|
95
|
+
error('phase required for init execute-phase');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const dryRun = opts && opts.dry_run === true;
|
|
99
|
+
const budgetArg = opts && opts.budget;
|
|
100
|
+
const config = loadConfig(cwd);
|
|
101
|
+
|
|
102
|
+
// Validate and resolve budget
|
|
103
|
+
let totalBudget = (config.budget && config.budget.default_points) || 50;
|
|
104
|
+
if (budgetArg !== undefined && budgetArg !== null) {
|
|
105
|
+
const parsed = parseInt(budgetArg, 10);
|
|
106
|
+
if (isNaN(parsed)) {
|
|
107
|
+
error('Budget must be a number');
|
|
108
|
+
}
|
|
109
|
+
if (parsed < 1) {
|
|
110
|
+
error('Budget must be >= 1');
|
|
111
|
+
}
|
|
112
|
+
totalBudget = Math.min(parsed, 200);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const phaseInfo = findPhaseInternal(cwd, phase);
|
|
116
|
+
const milestone = getMilestoneInfo(cwd);
|
|
117
|
+
|
|
118
|
+
// Extract requirement IDs from the ROADMAP **Requirements** field for this phase
|
|
119
|
+
const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
|
120
|
+
const phaseReqIds = extractReqIds(roadmapPhase);
|
|
121
|
+
|
|
122
|
+
// Classify tiers for each plan and calculate budget
|
|
123
|
+
const plans = phaseInfo?.plans || [];
|
|
124
|
+
const plansTierInfo = { micro: 0, standard: 0, full: 0 };
|
|
125
|
+
let estimatedPoints = 0;
|
|
126
|
+
|
|
127
|
+
for (const planFile of plans) {
|
|
128
|
+
// Read plan frontmatter to classify tier
|
|
129
|
+
const planPath = path.join(cwd, phaseInfo?.directory || '', planFile);
|
|
130
|
+
let fm = {};
|
|
131
|
+
try {
|
|
132
|
+
const content = fs.readFileSync(planPath, 'utf-8');
|
|
133
|
+
fm = extractFrontmatter(content) || {};
|
|
134
|
+
} catch { /* plan file unreadable — use defaults */ }
|
|
135
|
+
|
|
136
|
+
const tier = classifyPlanTier(fm, config);
|
|
137
|
+
plansTierInfo[tier] = (plansTierInfo[tier] || 0) + 1;
|
|
138
|
+
estimatedPoints += effortToPoints(fm.effort);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const result = {
|
|
142
|
+
// Models
|
|
143
|
+
executor_model: resolveModelInternal(cwd, 'pan-executor'),
|
|
144
|
+
verifier_model: resolveModelInternal(cwd, 'pan-verifier'),
|
|
145
|
+
reviewer_model: resolveModelInternal(cwd, 'pan-reviewer'),
|
|
146
|
+
|
|
147
|
+
// Config flags
|
|
148
|
+
commit_docs: config.commit_docs,
|
|
149
|
+
parallelization: config.parallelization,
|
|
150
|
+
branching_strategy: config.branching_strategy,
|
|
151
|
+
phase_branch_template: config.phase_branch_template,
|
|
152
|
+
milestone_branch_template: config.milestone_branch_template,
|
|
153
|
+
verifier_enabled: config.verifier,
|
|
154
|
+
|
|
155
|
+
// Phase info
|
|
156
|
+
phase_found: !!phaseInfo,
|
|
157
|
+
phase_dir: phaseInfo?.directory || null,
|
|
158
|
+
phase_number: phaseInfo?.phase_number || null,
|
|
159
|
+
phase_name: phaseInfo?.phase_name || null,
|
|
160
|
+
phase_slug: phaseInfo?.phase_slug || null,
|
|
161
|
+
phase_req_ids: phaseReqIds,
|
|
162
|
+
|
|
163
|
+
// Plan inventory
|
|
164
|
+
plans,
|
|
165
|
+
summaries: phaseInfo?.summaries || [],
|
|
166
|
+
incomplete_plans: phaseInfo?.incomplete_plans || [],
|
|
167
|
+
plan_count: plans.length,
|
|
168
|
+
incomplete_count: phaseInfo?.incomplete_plans?.length || 0,
|
|
169
|
+
|
|
170
|
+
// Branch name (pre-computed from template).
|
|
171
|
+
branch_name: config.branching_strategy === 'phase' && phaseInfo
|
|
172
|
+
? config.phase_branch_template
|
|
173
|
+
.replace('{phase}', phaseInfo.phase_number)
|
|
174
|
+
.replace('{slug}', phaseInfo.phase_slug || 'phase')
|
|
175
|
+
: config.branching_strategy === 'milestone'
|
|
176
|
+
? config.milestone_branch_template
|
|
177
|
+
.replace('{milestone}', milestone.version)
|
|
178
|
+
.replace('{slug}', generateSlugInternal(milestone.name) || 'milestone')
|
|
179
|
+
: null,
|
|
180
|
+
|
|
181
|
+
// Milestone info
|
|
182
|
+
milestone_version: milestone.version,
|
|
183
|
+
milestone_name: milestone.name,
|
|
184
|
+
milestone_slug: generateSlugInternal(milestone.name),
|
|
185
|
+
|
|
186
|
+
// File existence
|
|
187
|
+
state_exists: pathExistsInternal(cwd, planningRelPath(STATE_FILE)),
|
|
188
|
+
roadmap_exists: pathExistsInternal(cwd, planningRelPath(ROADMAP_FILE)),
|
|
189
|
+
config_exists: pathExistsInternal(cwd, planningRelPath(CONFIG_FILE)),
|
|
190
|
+
// File paths
|
|
191
|
+
state_path: planningRelPath(STATE_FILE),
|
|
192
|
+
roadmap_path: planningRelPath(ROADMAP_FILE),
|
|
193
|
+
config_path: planningRelPath(CONFIG_FILE),
|
|
194
|
+
|
|
195
|
+
// Execution enhancements
|
|
196
|
+
plans_by_tier: plansTierInfo,
|
|
197
|
+
total_budget_points: totalBudget,
|
|
198
|
+
estimated_points: estimatedPoints,
|
|
199
|
+
budget_exceeded: estimatedPoints > totalBudget,
|
|
200
|
+
execution_mode: (config.execution && config.execution.default_mode) || 'wave_order',
|
|
201
|
+
dry_run: dryRun,
|
|
202
|
+
rollback_tag: null,
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
output(result, raw);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Bootstrap context for phase planning: models, workflow flags, and existing artifacts.
|
|
210
|
+
* @param {string} cwd - Working directory path
|
|
211
|
+
* @param {string} phase - Phase number to initialize planning for
|
|
212
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
213
|
+
* @returns {void}
|
|
214
|
+
*/
|
|
215
|
+
function cmdInitPlanPhase(cwd, phase, raw) {
|
|
216
|
+
if (!phase) {
|
|
217
|
+
error('phase required for init plan-phase');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const config = loadConfig(cwd);
|
|
221
|
+
const phaseInfo = findPhaseInternal(cwd, phase);
|
|
222
|
+
|
|
223
|
+
// Extract requirement IDs from the ROADMAP **Requirements** field for this phase
|
|
224
|
+
const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
|
225
|
+
const phaseReqIds = extractReqIds(roadmapPhase);
|
|
226
|
+
|
|
227
|
+
const result = {
|
|
228
|
+
// Models
|
|
229
|
+
researcher_model: resolveModelInternal(cwd, 'pan-phase-researcher'),
|
|
230
|
+
planner_model: resolveModelInternal(cwd, 'pan-planner'),
|
|
231
|
+
checker_model: resolveModelInternal(cwd, 'pan-plan-checker'),
|
|
232
|
+
|
|
233
|
+
// Workflow flags
|
|
234
|
+
research_enabled: config.research,
|
|
235
|
+
plan_checker_enabled: config.plan_checker,
|
|
236
|
+
nyquist_validation_enabled: config.nyquist_validation,
|
|
237
|
+
commit_docs: config.commit_docs,
|
|
238
|
+
|
|
239
|
+
// Phase info
|
|
240
|
+
phase_found: !!phaseInfo,
|
|
241
|
+
phase_dir: phaseInfo?.directory || null,
|
|
242
|
+
phase_number: phaseInfo?.phase_number || null,
|
|
243
|
+
phase_name: phaseInfo?.phase_name || null,
|
|
244
|
+
phase_slug: phaseInfo?.phase_slug || null,
|
|
245
|
+
padded_phase: phaseInfo?.phase_number?.padStart(2, '0') || null,
|
|
246
|
+
phase_req_ids: phaseReqIds,
|
|
247
|
+
|
|
248
|
+
// Existing artifacts
|
|
249
|
+
has_research: phaseInfo?.has_research || false,
|
|
250
|
+
has_context: phaseInfo?.has_context || false,
|
|
251
|
+
has_plans: (phaseInfo?.plans?.length || 0) > 0,
|
|
252
|
+
plan_count: phaseInfo?.plans?.length || 0,
|
|
253
|
+
|
|
254
|
+
// Environment
|
|
255
|
+
planning_exists: pathExistsInternal(cwd, PLANNING_DIR),
|
|
256
|
+
roadmap_exists: pathExistsInternal(cwd, planningRelPath(ROADMAP_FILE)),
|
|
257
|
+
|
|
258
|
+
// File paths
|
|
259
|
+
state_path: planningRelPath(STATE_FILE),
|
|
260
|
+
roadmap_path: planningRelPath(ROADMAP_FILE),
|
|
261
|
+
requirements_path: planningRelPath(REQUIREMENTS_FILE),
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
if (phaseInfo?.directory) {
|
|
265
|
+
// Discover optional artifact files (CONTEXT, RESEARCH, VERIFICATION, UAT)
|
|
266
|
+
attachPhaseArtifactPaths(result, cwd, phaseInfo.directory);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
output(result, raw);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Bootstrap context for new project initialization: models, brownfield detection, git state.
|
|
274
|
+
* @param {string} cwd - Working directory path
|
|
275
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
276
|
+
* @returns {void}
|
|
277
|
+
*/
|
|
278
|
+
function cmdInitNewProject(cwd, raw) {
|
|
279
|
+
const config = loadConfig(cwd);
|
|
280
|
+
|
|
281
|
+
// Detect Brave Search API key availability
|
|
282
|
+
const hasBraveSearch = hasBraveSearchKey();
|
|
283
|
+
|
|
284
|
+
// Detect existing code (cross-platform, no shell dependency)
|
|
285
|
+
let hasCode = false;
|
|
286
|
+
let hasPackageFile = false;
|
|
287
|
+
const CODE_EXTS = new Set(['.ts', '.js', '.py', '.go', '.rs', '.swift', '.java']);
|
|
288
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', '.planning', '.claude', '.opencode', '.gemini', '.codex', '.copilot']);
|
|
289
|
+
try {
|
|
290
|
+
const scanDir = (dir, depth) => {
|
|
291
|
+
if (depth > 3 || hasCode) return;
|
|
292
|
+
let entries;
|
|
293
|
+
try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
294
|
+
for (const entry of entries) {
|
|
295
|
+
if (hasCode) return;
|
|
296
|
+
if (entry.isDirectory()) {
|
|
297
|
+
if (!SKIP_DIRS.has(entry.name)) scanDir(path.join(dir, entry.name), depth + 1);
|
|
298
|
+
} else if (CODE_EXTS.has(path.extname(entry.name))) {
|
|
299
|
+
hasCode = true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
scanDir(cwd, 0);
|
|
304
|
+
} catch {
|
|
305
|
+
// Filesystem error — treat as greenfield
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
hasPackageFile = pathExistsInternal(cwd, 'package.json') ||
|
|
309
|
+
pathExistsInternal(cwd, 'requirements.txt') ||
|
|
310
|
+
pathExistsInternal(cwd, 'Cargo.toml') ||
|
|
311
|
+
pathExistsInternal(cwd, 'go.mod') ||
|
|
312
|
+
pathExistsInternal(cwd, 'Package.swift');
|
|
313
|
+
|
|
314
|
+
const result = {
|
|
315
|
+
// Models
|
|
316
|
+
researcher_model: resolveModelInternal(cwd, 'pan-project-researcher'),
|
|
317
|
+
synthesizer_model: resolveModelInternal(cwd, 'pan-research-synthesizer'),
|
|
318
|
+
roadmapper_model: resolveModelInternal(cwd, 'pan-roadmapper'),
|
|
319
|
+
|
|
320
|
+
// Config
|
|
321
|
+
commit_docs: config.commit_docs,
|
|
322
|
+
|
|
323
|
+
// Existing state
|
|
324
|
+
project_exists: pathExistsInternal(cwd, planningRelPath(PROJECT_FILE)),
|
|
325
|
+
has_codebase_map: pathExistsInternal(cwd, planningRelPath(CODEBASE_DIR)),
|
|
326
|
+
planning_exists: pathExistsInternal(cwd, PLANNING_DIR),
|
|
327
|
+
|
|
328
|
+
// Brownfield detection
|
|
329
|
+
has_existing_code: hasCode,
|
|
330
|
+
has_package_file: hasPackageFile,
|
|
331
|
+
is_brownfield: hasCode || hasPackageFile,
|
|
332
|
+
needs_codebase_map: (hasCode || hasPackageFile) && !pathExistsInternal(cwd, planningRelPath(CODEBASE_DIR)),
|
|
333
|
+
|
|
334
|
+
// Git state — auto-init if missing
|
|
335
|
+
has_git: ensureGitRepo(cwd),
|
|
336
|
+
|
|
337
|
+
// Enhanced search
|
|
338
|
+
brave_search_available: hasBraveSearch,
|
|
339
|
+
|
|
340
|
+
// File paths
|
|
341
|
+
project_path: planningRelPath(PROJECT_FILE),
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
output(result, raw);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Bootstrap context for new milestone creation: models, current milestone info, file existence.
|
|
349
|
+
* @param {string} cwd - Working directory path
|
|
350
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
351
|
+
* @returns {void}
|
|
352
|
+
*/
|
|
353
|
+
function cmdInitNewMilestone(cwd, raw) {
|
|
354
|
+
const config = loadConfig(cwd);
|
|
355
|
+
const milestone = getMilestoneInfo(cwd);
|
|
356
|
+
|
|
357
|
+
const result = {
|
|
358
|
+
// Models
|
|
359
|
+
researcher_model: resolveModelInternal(cwd, 'pan-project-researcher'),
|
|
360
|
+
synthesizer_model: resolveModelInternal(cwd, 'pan-research-synthesizer'),
|
|
361
|
+
roadmapper_model: resolveModelInternal(cwd, 'pan-roadmapper'),
|
|
362
|
+
|
|
363
|
+
// Config
|
|
364
|
+
commit_docs: config.commit_docs,
|
|
365
|
+
research_enabled: config.research,
|
|
366
|
+
|
|
367
|
+
// Current milestone
|
|
368
|
+
current_milestone: milestone.version,
|
|
369
|
+
current_milestone_name: milestone.name,
|
|
370
|
+
|
|
371
|
+
// File existence
|
|
372
|
+
project_exists: pathExistsInternal(cwd, planningRelPath(PROJECT_FILE)),
|
|
373
|
+
roadmap_exists: pathExistsInternal(cwd, planningRelPath(ROADMAP_FILE)),
|
|
374
|
+
state_exists: pathExistsInternal(cwd, planningRelPath(STATE_FILE)),
|
|
375
|
+
|
|
376
|
+
// File paths
|
|
377
|
+
project_path: planningRelPath(PROJECT_FILE),
|
|
378
|
+
roadmap_path: planningRelPath(ROADMAP_FILE),
|
|
379
|
+
state_path: planningRelPath(STATE_FILE),
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
output(result, raw);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Bootstrap context for a quick task: models, task numbering, and directory paths.
|
|
387
|
+
* @param {string} cwd - Working directory path
|
|
388
|
+
* @param {string} description - Optional task description for slug generation
|
|
389
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
390
|
+
* @returns {void}
|
|
391
|
+
*/
|
|
392
|
+
function cmdInitQuick(cwd, description, raw) {
|
|
393
|
+
const config = loadConfig(cwd);
|
|
394
|
+
const now = new Date();
|
|
395
|
+
const slug = description ? generateSlugInternal(description)?.substring(0, MAX_SLUG_LENGTH) : null;
|
|
396
|
+
|
|
397
|
+
// Find next quick task number by scanning existing numbered directories
|
|
398
|
+
const quickDirPath = path.join(planningPath(cwd), QUICK_DIR);
|
|
399
|
+
let nextNum = 1;
|
|
400
|
+
try {
|
|
401
|
+
const existingNums = fs.readdirSync(quickDirPath)
|
|
402
|
+
.filter(entry => /^\d+-/.test(entry))
|
|
403
|
+
.map(entry => parseInt(entry.split('-')[0], 10))
|
|
404
|
+
.filter(num => !isNaN(num));
|
|
405
|
+
if (existingNums.length > 0) {
|
|
406
|
+
nextNum = Math.max(...existingNums) + 1;
|
|
407
|
+
}
|
|
408
|
+
} catch {
|
|
409
|
+
// Quick directory does not exist yet -- start at 1
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const quickRelDir = planningRelPath(QUICK_DIR);
|
|
413
|
+
|
|
414
|
+
const result = {
|
|
415
|
+
// Models
|
|
416
|
+
planner_model: resolveModelInternal(cwd, 'pan-planner'),
|
|
417
|
+
executor_model: resolveModelInternal(cwd, 'pan-executor'),
|
|
418
|
+
checker_model: resolveModelInternal(cwd, 'pan-plan-checker'),
|
|
419
|
+
verifier_model: resolveModelInternal(cwd, 'pan-verifier'),
|
|
420
|
+
|
|
421
|
+
// Config
|
|
422
|
+
commit_docs: config.commit_docs,
|
|
423
|
+
|
|
424
|
+
// Quick task info
|
|
425
|
+
next_num: nextNum,
|
|
426
|
+
slug: slug,
|
|
427
|
+
description: description || null,
|
|
428
|
+
|
|
429
|
+
// Timestamps
|
|
430
|
+
date: now.toISOString().split('T')[0],
|
|
431
|
+
timestamp: now.toISOString(),
|
|
432
|
+
|
|
433
|
+
// Paths
|
|
434
|
+
quick_dir: quickRelDir,
|
|
435
|
+
task_dir: slug ? `${quickRelDir}/${nextNum}-${slug}` : null,
|
|
436
|
+
|
|
437
|
+
// File existence
|
|
438
|
+
roadmap_exists: pathExistsInternal(cwd, planningRelPath(ROADMAP_FILE)),
|
|
439
|
+
planning_exists: pathExistsInternal(cwd, PLANNING_DIR),
|
|
440
|
+
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
output(result, raw);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Bootstrap context for resuming work: file existence, interrupted agent detection.
|
|
448
|
+
* @param {string} cwd - Working directory path
|
|
449
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
450
|
+
* @returns {void}
|
|
451
|
+
*/
|
|
452
|
+
function cmdInitResume(cwd, raw) {
|
|
453
|
+
const config = loadConfig(cwd);
|
|
454
|
+
|
|
455
|
+
// Check for interrupted agent
|
|
456
|
+
let interruptedAgentId = null;
|
|
457
|
+
try {
|
|
458
|
+
interruptedAgentId = fs.readFileSync(path.join(planningPath(cwd), 'current-agent-id.txt'), 'utf-8').trim();
|
|
459
|
+
} catch {
|
|
460
|
+
// No interrupted agent file found -- normal state
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const result = {
|
|
464
|
+
// File existence
|
|
465
|
+
state_exists: pathExistsInternal(cwd, planningRelPath(STATE_FILE)),
|
|
466
|
+
roadmap_exists: pathExistsInternal(cwd, planningRelPath(ROADMAP_FILE)),
|
|
467
|
+
project_exists: pathExistsInternal(cwd, planningRelPath(PROJECT_FILE)),
|
|
468
|
+
planning_exists: pathExistsInternal(cwd, PLANNING_DIR),
|
|
469
|
+
|
|
470
|
+
// File paths
|
|
471
|
+
state_path: planningRelPath(STATE_FILE),
|
|
472
|
+
roadmap_path: planningRelPath(ROADMAP_FILE),
|
|
473
|
+
project_path: planningRelPath(PROJECT_FILE),
|
|
474
|
+
|
|
475
|
+
// Agent state
|
|
476
|
+
has_interrupted_agent: !!interruptedAgentId,
|
|
477
|
+
interrupted_agent_id: interruptedAgentId,
|
|
478
|
+
|
|
479
|
+
// Config
|
|
480
|
+
commit_docs: config.commit_docs,
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
output(result, raw);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Bootstrap context for work verification: models, phase info, existing verification artifacts.
|
|
488
|
+
* @param {string} cwd - Working directory path
|
|
489
|
+
* @param {string} phase - Phase number to verify
|
|
490
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
491
|
+
* @returns {void}
|
|
492
|
+
*/
|
|
493
|
+
function cmdInitVerifyWork(cwd, phase, raw) {
|
|
494
|
+
if (!phase) {
|
|
495
|
+
error('phase required for init verify-work');
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const config = loadConfig(cwd);
|
|
499
|
+
const phaseInfo = findPhaseInternal(cwd, phase);
|
|
500
|
+
|
|
501
|
+
const result = {
|
|
502
|
+
// Models
|
|
503
|
+
planner_model: resolveModelInternal(cwd, 'pan-planner'),
|
|
504
|
+
checker_model: resolveModelInternal(cwd, 'pan-plan-checker'),
|
|
505
|
+
|
|
506
|
+
// Config
|
|
507
|
+
commit_docs: config.commit_docs,
|
|
508
|
+
|
|
509
|
+
// Phase info
|
|
510
|
+
phase_found: !!phaseInfo,
|
|
511
|
+
phase_dir: phaseInfo?.directory || null,
|
|
512
|
+
phase_number: phaseInfo?.phase_number || null,
|
|
513
|
+
phase_name: phaseInfo?.phase_name || null,
|
|
514
|
+
|
|
515
|
+
// Existing artifacts
|
|
516
|
+
has_verification: phaseInfo?.has_verification || false,
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
output(result, raw);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Bootstrap context for phase operations: config, artifacts, and context/research file paths.
|
|
524
|
+
* @param {string} cwd - Working directory path
|
|
525
|
+
* @param {string} phase - Phase number to operate on
|
|
526
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
527
|
+
* @returns {void}
|
|
528
|
+
*/
|
|
529
|
+
function cmdInitPhaseOp(cwd, phase, raw) {
|
|
530
|
+
const config = loadConfig(cwd);
|
|
531
|
+
let phaseInfo = findPhaseInternal(cwd, phase);
|
|
532
|
+
|
|
533
|
+
// Fallback to roadmap.md if no directory exists (e.g., Plans: TBD)
|
|
534
|
+
if (!phaseInfo) {
|
|
535
|
+
const roadmapPhase = getRoadmapPhaseInternal(cwd, phase);
|
|
536
|
+
if (roadmapPhase?.found) {
|
|
537
|
+
const phaseName = roadmapPhase.phase_name;
|
|
538
|
+
phaseInfo = {
|
|
539
|
+
found: true,
|
|
540
|
+
directory: null,
|
|
541
|
+
phase_number: roadmapPhase.phase_number,
|
|
542
|
+
phase_name: phaseName,
|
|
543
|
+
phase_slug: phaseName ? generateSlugInternal(phaseName) : null,
|
|
544
|
+
plans: [],
|
|
545
|
+
summaries: [],
|
|
546
|
+
incomplete_plans: [],
|
|
547
|
+
has_research: false,
|
|
548
|
+
has_context: false,
|
|
549
|
+
has_verification: false,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const result = {
|
|
555
|
+
// Config
|
|
556
|
+
commit_docs: config.commit_docs,
|
|
557
|
+
brave_search: config.brave_search,
|
|
558
|
+
|
|
559
|
+
// Phase info
|
|
560
|
+
phase_found: !!phaseInfo,
|
|
561
|
+
phase_dir: phaseInfo?.directory || null,
|
|
562
|
+
phase_number: phaseInfo?.phase_number || null,
|
|
563
|
+
phase_name: phaseInfo?.phase_name || null,
|
|
564
|
+
phase_slug: phaseInfo?.phase_slug || null,
|
|
565
|
+
padded_phase: phaseInfo?.phase_number?.padStart(2, '0') || null,
|
|
566
|
+
|
|
567
|
+
// Existing artifacts
|
|
568
|
+
has_research: phaseInfo?.has_research || false,
|
|
569
|
+
has_context: phaseInfo?.has_context || false,
|
|
570
|
+
has_plans: (phaseInfo?.plans?.length || 0) > 0,
|
|
571
|
+
has_verification: phaseInfo?.has_verification || false,
|
|
572
|
+
plan_count: phaseInfo?.plans?.length || 0,
|
|
573
|
+
|
|
574
|
+
// File existence
|
|
575
|
+
roadmap_exists: pathExistsInternal(cwd, planningRelPath(ROADMAP_FILE)),
|
|
576
|
+
planning_exists: pathExistsInternal(cwd, PLANNING_DIR),
|
|
577
|
+
|
|
578
|
+
// File paths
|
|
579
|
+
state_path: planningRelPath(STATE_FILE),
|
|
580
|
+
roadmap_path: planningRelPath(ROADMAP_FILE),
|
|
581
|
+
requirements_path: planningRelPath(REQUIREMENTS_FILE),
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
if (phaseInfo?.directory) {
|
|
585
|
+
// Discover optional artifact files (CONTEXT, RESEARCH, VERIFICATION, UAT)
|
|
586
|
+
attachPhaseArtifactPaths(result, cwd, phaseInfo.directory);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
output(result, raw);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Bootstrap context for todo management: config, pending todo inventory, and paths.
|
|
594
|
+
* @param {string} cwd - Working directory path
|
|
595
|
+
* @param {string} area - Optional area filter for todos
|
|
596
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
597
|
+
* @returns {void}
|
|
598
|
+
*/
|
|
599
|
+
function cmdInitTodos(cwd, area, raw) {
|
|
600
|
+
const config = loadConfig(cwd);
|
|
601
|
+
const now = new Date();
|
|
602
|
+
|
|
603
|
+
const { count, todos } = scanPendingTodos(cwd, area);
|
|
604
|
+
|
|
605
|
+
const result = {
|
|
606
|
+
commit_docs: config.commit_docs,
|
|
607
|
+
date: now.toISOString().split('T')[0],
|
|
608
|
+
timestamp: now.toISOString(),
|
|
609
|
+
todo_count: count,
|
|
610
|
+
todos,
|
|
611
|
+
area_filter: area || null,
|
|
612
|
+
pending_dir: planningRelPath('todos/pending'),
|
|
613
|
+
completed_dir: planningRelPath('todos/completed'),
|
|
614
|
+
planning_exists: pathExistsInternal(cwd, PLANNING_DIR),
|
|
615
|
+
todos_dir_exists: pathExistsInternal(cwd, planningRelPath('todos')),
|
|
616
|
+
pending_dir_exists: pathExistsInternal(cwd, planningRelPath('todos/pending')),
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
output(result, raw);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Bootstrap context for milestone operations: phase counts, archive info, file existence.
|
|
624
|
+
* @param {string} cwd - Working directory path
|
|
625
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
626
|
+
* @returns {void}
|
|
627
|
+
*/
|
|
628
|
+
function cmdInitMilestoneOp(cwd, raw) {
|
|
629
|
+
const config = loadConfig(cwd);
|
|
630
|
+
const milestone = getMilestoneInfo(cwd);
|
|
631
|
+
|
|
632
|
+
// Count phases
|
|
633
|
+
let phaseCount = 0;
|
|
634
|
+
let completedPhases = 0;
|
|
635
|
+
const phasesDirPath = phasesPath(cwd);
|
|
636
|
+
try {
|
|
637
|
+
const entries = fs.readdirSync(phasesDirPath, { withFileTypes: true });
|
|
638
|
+
const dirNames = entries.filter(entry => entry.isDirectory()).map(entry => entry.name);
|
|
639
|
+
phaseCount = dirNames.length;
|
|
640
|
+
|
|
641
|
+
// Count phases with summaries (completed)
|
|
642
|
+
for (const dirName of dirNames) {
|
|
643
|
+
try {
|
|
644
|
+
const phaseFiles = fs.readdirSync(path.join(phasesDirPath, dirName));
|
|
645
|
+
const hasSummary = phaseFiles.some(isSummaryFile);
|
|
646
|
+
if (hasSummary) completedPhases++;
|
|
647
|
+
} catch {
|
|
648
|
+
// Phase directory unreadable -- skip
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
} catch {
|
|
652
|
+
// Phases directory does not exist yet
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Check archive
|
|
656
|
+
const archiveDirPath = path.join(planningPath(cwd), MILESTONES_DIR);
|
|
657
|
+
let archivedMilestones = [];
|
|
658
|
+
try {
|
|
659
|
+
archivedMilestones = fs.readdirSync(archiveDirPath, { withFileTypes: true })
|
|
660
|
+
.filter(entry => entry.isDirectory())
|
|
661
|
+
.map(entry => entry.name);
|
|
662
|
+
} catch {
|
|
663
|
+
// Archive directory does not exist yet
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const result = {
|
|
667
|
+
// Config
|
|
668
|
+
commit_docs: config.commit_docs,
|
|
669
|
+
|
|
670
|
+
// Current milestone
|
|
671
|
+
milestone_version: milestone.version,
|
|
672
|
+
milestone_name: milestone.name,
|
|
673
|
+
milestone_slug: generateSlugInternal(milestone.name),
|
|
674
|
+
|
|
675
|
+
// Phase counts
|
|
676
|
+
phase_count: phaseCount,
|
|
677
|
+
completed_phases: completedPhases,
|
|
678
|
+
all_phases_complete: phaseCount > 0 && phaseCount === completedPhases,
|
|
679
|
+
|
|
680
|
+
// Archive
|
|
681
|
+
archived_milestones: archivedMilestones,
|
|
682
|
+
archive_count: archivedMilestones.length,
|
|
683
|
+
|
|
684
|
+
// File existence
|
|
685
|
+
project_exists: pathExistsInternal(cwd, planningRelPath(PROJECT_FILE)),
|
|
686
|
+
roadmap_exists: pathExistsInternal(cwd, planningRelPath(ROADMAP_FILE)),
|
|
687
|
+
state_exists: pathExistsInternal(cwd, planningRelPath(STATE_FILE)),
|
|
688
|
+
archive_exists: pathExistsInternal(cwd, planningRelPath(MILESTONES_DIR)),
|
|
689
|
+
phases_dir_exists: pathExistsInternal(cwd, planningRelPath(PHASES_DIR)),
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
output(result, raw);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Bootstrap context for codebase mapping: mapper model, existing maps, config flags.
|
|
697
|
+
* @param {string} cwd - Working directory path
|
|
698
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
699
|
+
* @returns {void}
|
|
700
|
+
*/
|
|
701
|
+
function cmdInitMapCodebase(cwd, raw) {
|
|
702
|
+
const config = loadConfig(cwd);
|
|
703
|
+
|
|
704
|
+
// Check for existing codebase maps
|
|
705
|
+
const codebaseDirPath = path.join(planningPath(cwd), CODEBASE_DIR);
|
|
706
|
+
let existingMaps = [];
|
|
707
|
+
try {
|
|
708
|
+
existingMaps = fs.readdirSync(codebaseDirPath).filter(filename => filename.endsWith('.md'));
|
|
709
|
+
} catch {
|
|
710
|
+
// Codebase directory does not exist yet
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const result = {
|
|
714
|
+
// Models
|
|
715
|
+
mapper_model: resolveModelInternal(cwd, 'pan-document_code'),
|
|
716
|
+
|
|
717
|
+
// Config
|
|
718
|
+
commit_docs: config.commit_docs,
|
|
719
|
+
search_gitignored: config.search_gitignored,
|
|
720
|
+
parallelization: config.parallelization,
|
|
721
|
+
|
|
722
|
+
// Paths
|
|
723
|
+
codebase_dir: planningRelPath(CODEBASE_DIR),
|
|
724
|
+
|
|
725
|
+
// Existing maps
|
|
726
|
+
existing_maps: existingMaps,
|
|
727
|
+
has_maps: existingMaps.length > 0,
|
|
728
|
+
|
|
729
|
+
// File existence
|
|
730
|
+
planning_exists: pathExistsInternal(cwd, PLANNING_DIR),
|
|
731
|
+
codebase_dir_exists: pathExistsInternal(cwd, planningRelPath(CODEBASE_DIR)),
|
|
732
|
+
|
|
733
|
+
// Language detection
|
|
734
|
+
...(() => {
|
|
735
|
+
const langInfo = detectLanguages(cwd);
|
|
736
|
+
return {
|
|
737
|
+
supported_languages: [langInfo.primary, ...langInfo.secondary].filter(Boolean),
|
|
738
|
+
file_count: langInfo.file_count,
|
|
739
|
+
};
|
|
740
|
+
})(),
|
|
741
|
+
|
|
742
|
+
// Focus areas
|
|
743
|
+
focus_areas: ['tech', 'arch', 'quality', 'concerns', 'relationships', 'practices'],
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
output(result, raw);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Bootstrap context for progress display: phase analysis, current/next phase, paused state.
|
|
751
|
+
* @param {string} cwd - Working directory path
|
|
752
|
+
* @param {boolean} raw - If true, output raw value instead of JSON
|
|
753
|
+
* @returns {void}
|
|
754
|
+
*/
|
|
755
|
+
/**
|
|
756
|
+
* Scan all phase directories and return phase info, current phase, and next phase.
|
|
757
|
+
* @param {string} cwd - Working directory path
|
|
758
|
+
* @returns {{ phases: Object[], currentPhase: Object|null, nextPhase: Object|null }}
|
|
759
|
+
*/
|
|
760
|
+
function scanAllPhases(cwd) {
|
|
761
|
+
const phasesDirPath = phasesPath(cwd);
|
|
762
|
+
const phases = [];
|
|
763
|
+
let currentPhase = null;
|
|
764
|
+
let nextPhase = null;
|
|
765
|
+
|
|
766
|
+
try {
|
|
767
|
+
const entries = fs.readdirSync(phasesDirPath, { withFileTypes: true });
|
|
768
|
+
const dirNames = entries.filter(entry => entry.isDirectory()).map(entry => entry.name).sort();
|
|
769
|
+
|
|
770
|
+
for (const dirName of dirNames) {
|
|
771
|
+
const dirMatch = dirName.match(/^(\d+(?:\.\d+)*)-?(.*)/);
|
|
772
|
+
const phaseNumber = dirMatch ? dirMatch[1] : dirName;
|
|
773
|
+
const phaseName = dirMatch && dirMatch[2] ? dirMatch[2] : null;
|
|
774
|
+
|
|
775
|
+
const phaseFullPath = path.join(phasesDirPath, dirName);
|
|
776
|
+
let phaseFiles;
|
|
777
|
+
try { phaseFiles = fs.readdirSync(phaseFullPath); } catch { continue; }
|
|
778
|
+
|
|
779
|
+
const plans = phaseFiles.filter(isPlanFile);
|
|
780
|
+
const summaries = phaseFiles.filter(isSummaryFile);
|
|
781
|
+
const hasResearch = phaseFiles.some(isResearchFile);
|
|
782
|
+
|
|
783
|
+
const rawStatus = classifyPhaseStatus(plans.length, summaries.length, { hasResearch });
|
|
784
|
+
const status = rawStatus === 'partial' || rawStatus === 'planned' ? 'in_progress' :
|
|
785
|
+
rawStatus === 'discussed' || rawStatus === 'empty' ? 'pending' :
|
|
786
|
+
rawStatus;
|
|
787
|
+
|
|
788
|
+
const phaseInfo = {
|
|
789
|
+
number: phaseNumber,
|
|
790
|
+
name: phaseName,
|
|
791
|
+
directory: toPosix(path.join(PLANNING_DIR, PHASES_DIR, dirName)),
|
|
792
|
+
status,
|
|
793
|
+
plan_count: plans.length,
|
|
794
|
+
summary_count: summaries.length,
|
|
795
|
+
has_research: hasResearch,
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
phases.push(phaseInfo);
|
|
799
|
+
|
|
800
|
+
if (!currentPhase && (status === 'in_progress' || status === 'researched')) {
|
|
801
|
+
currentPhase = phaseInfo;
|
|
802
|
+
}
|
|
803
|
+
if (!nextPhase && status === 'pending') {
|
|
804
|
+
nextPhase = phaseInfo;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
} catch {
|
|
808
|
+
// Phases directory does not exist yet
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
return { phases, currentPhase, nextPhase };
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function cmdInitProgress(cwd, raw) {
|
|
815
|
+
const config = loadConfig(cwd);
|
|
816
|
+
const milestone = getMilestoneInfo(cwd);
|
|
817
|
+
|
|
818
|
+
const { phases, currentPhase, nextPhase } = scanAllPhases(cwd);
|
|
819
|
+
|
|
820
|
+
// Check for paused work
|
|
821
|
+
let pausedAt = null;
|
|
822
|
+
try {
|
|
823
|
+
const stateContent = fs.readFileSync(path.join(planningPath(cwd), STATE_FILE), 'utf-8');
|
|
824
|
+
const pauseMatch = stateContent.match(/\*\*Paused At:\*\*\s*(.+)/);
|
|
825
|
+
if (pauseMatch) pausedAt = pauseMatch[1].trim();
|
|
826
|
+
} catch {
|
|
827
|
+
// state.md does not exist yet -- no paused state
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const result = {
|
|
831
|
+
// Models
|
|
832
|
+
executor_model: resolveModelInternal(cwd, 'pan-executor'),
|
|
833
|
+
planner_model: resolveModelInternal(cwd, 'pan-planner'),
|
|
834
|
+
|
|
835
|
+
// Config
|
|
836
|
+
commit_docs: config.commit_docs,
|
|
837
|
+
|
|
838
|
+
// Milestone
|
|
839
|
+
milestone_version: milestone.version,
|
|
840
|
+
milestone_name: milestone.name,
|
|
841
|
+
|
|
842
|
+
// Phase overview
|
|
843
|
+
phases,
|
|
844
|
+
phase_count: phases.length,
|
|
845
|
+
completed_count: phases.filter(phaseEntry => phaseEntry.status === 'complete').length,
|
|
846
|
+
in_progress_count: phases.filter(phaseEntry => phaseEntry.status === 'in_progress').length,
|
|
847
|
+
|
|
848
|
+
// Current state
|
|
849
|
+
current_phase: currentPhase,
|
|
850
|
+
next_phase: nextPhase,
|
|
851
|
+
paused_at: pausedAt,
|
|
852
|
+
has_work_in_progress: !!currentPhase,
|
|
853
|
+
|
|
854
|
+
// File existence
|
|
855
|
+
project_exists: pathExistsInternal(cwd, planningRelPath(PROJECT_FILE)),
|
|
856
|
+
roadmap_exists: pathExistsInternal(cwd, planningRelPath(ROADMAP_FILE)),
|
|
857
|
+
state_exists: pathExistsInternal(cwd, planningRelPath(STATE_FILE)),
|
|
858
|
+
// File paths
|
|
859
|
+
state_path: planningRelPath(STATE_FILE),
|
|
860
|
+
roadmap_path: planningRelPath(ROADMAP_FILE),
|
|
861
|
+
project_path: planningRelPath(PROJECT_FILE),
|
|
862
|
+
config_path: planningRelPath(CONFIG_FILE),
|
|
863
|
+
};
|
|
864
|
+
|
|
865
|
+
output(result, raw);
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
module.exports = {
|
|
869
|
+
cmdInitExecutePhase,
|
|
870
|
+
cmdInitPlanPhase,
|
|
871
|
+
cmdInitNewProject,
|
|
872
|
+
cmdInitNewMilestone,
|
|
873
|
+
cmdInitQuick,
|
|
874
|
+
cmdInitResume,
|
|
875
|
+
cmdInitVerifyWork,
|
|
876
|
+
cmdInitPhaseOp,
|
|
877
|
+
cmdInitTodos,
|
|
878
|
+
cmdInitMilestoneOp,
|
|
879
|
+
cmdInitMapCodebase,
|
|
880
|
+
cmdInitProgress,
|
|
881
|
+
};
|