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,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template — Template selection and fill operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const {
|
|
8
|
+
PLANNING_DIR, PHASES_DIR, PLAN_SUFFIX, SUMMARY_SUFFIX, VERIFICATION_SUFFIX,
|
|
9
|
+
SIMPLE_TASK_THRESHOLD, SIMPLE_FILE_THRESHOLD, COMPLEX_TASK_THRESHOLD, COMPLEX_FILE_THRESHOLD,
|
|
10
|
+
} = require('./constants.cjs');
|
|
11
|
+
const { planningPath, phasesPath } = require('./utils.cjs');
|
|
12
|
+
const { normalizePhaseName, findPhaseInternal, generateSlugInternal, toPosix, output, error } = require('./core.cjs');
|
|
13
|
+
const { reconstructFrontmatter } = require('./frontmatter.cjs');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Select the best summary template (minimal, standard, or complex) based on plan analysis.
|
|
17
|
+
* @param {string} cwd - Working directory path
|
|
18
|
+
* @param {string} planPath - Relative path to the plan.md file to analyze
|
|
19
|
+
* @param {boolean} raw - If true, output raw template path instead of JSON
|
|
20
|
+
* @returns {void}
|
|
21
|
+
*/
|
|
22
|
+
function cmdTemplateSelect(cwd, planPath, raw) {
|
|
23
|
+
if (!planPath) {
|
|
24
|
+
error('plan-path required');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const fullPath = path.join(cwd, planPath);
|
|
29
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
30
|
+
|
|
31
|
+
// Count task headings (### Task N)
|
|
32
|
+
const taskMatch = content.match(/###\s*Task\s*\d+/g) || [];
|
|
33
|
+
const taskCount = taskMatch.length;
|
|
34
|
+
|
|
35
|
+
// Check for decision-related keywords
|
|
36
|
+
const decisionMatch = content.match(/decision/gi) || [];
|
|
37
|
+
const hasDecisions = decisionMatch.length > 0;
|
|
38
|
+
|
|
39
|
+
// Count unique file path mentions (backticked paths containing a slash)
|
|
40
|
+
const fileMentions = new Set();
|
|
41
|
+
const filePattern = /`([^`]+\.[a-zA-Z]+)`/g;
|
|
42
|
+
let fileMatch;
|
|
43
|
+
while ((fileMatch = filePattern.exec(content)) !== null) {
|
|
44
|
+
if (fileMatch[1].includes('/') && !fileMatch[1].startsWith('http')) {
|
|
45
|
+
fileMentions.add(fileMatch[1]);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const fileCount = fileMentions.size;
|
|
49
|
+
|
|
50
|
+
// Select template based on complexity heuristics
|
|
51
|
+
let template = 'templates/summary-standard.md';
|
|
52
|
+
let type = 'standard';
|
|
53
|
+
|
|
54
|
+
if (taskCount <= SIMPLE_TASK_THRESHOLD && fileCount <= SIMPLE_FILE_THRESHOLD && !hasDecisions) {
|
|
55
|
+
// Simple plan: few tasks, few files, no decisions
|
|
56
|
+
template = 'templates/summary-minimal.md';
|
|
57
|
+
type = 'minimal';
|
|
58
|
+
} else if (hasDecisions || fileCount > COMPLEX_FILE_THRESHOLD || taskCount > COMPLEX_TASK_THRESHOLD) {
|
|
59
|
+
// Complex plan: has decisions, many files, or many tasks
|
|
60
|
+
template = 'templates/summary-complex.md';
|
|
61
|
+
type = 'complex';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const result = { template, type, taskCount, fileCount, hasDecisions };
|
|
65
|
+
output(result, raw, template);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
// Fallback to standard template on any read/parse error
|
|
68
|
+
output({ template: 'templates/summary-standard.md', type: 'standard', error: err.message }, raw, 'templates/summary-standard.md');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ─── Template generators extracted from cmdTemplateFill ─────────────────────
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Generate the frontmatter and body for a plan template.
|
|
76
|
+
* @param {string} phaseId - Full phase identifier (e.g., "01-setup-auth")
|
|
77
|
+
* @param {string} planId - Zero-padded plan number (e.g., "01")
|
|
78
|
+
* @param {string} phaseName - Human-readable phase name
|
|
79
|
+
* @param {string} phaseSlug - Phase slug for directory naming
|
|
80
|
+
* @param {string} phaseNum - Raw phase number from options
|
|
81
|
+
* @param {Object} options - Additional options (type, wave, fields)
|
|
82
|
+
* @returns {{frontmatter: Object, body: string, fileName: string}}
|
|
83
|
+
*/
|
|
84
|
+
function generatePlanTemplate(phaseId, planId, phaseName, phaseSlug, phaseNum, options) {
|
|
85
|
+
const planType = options.type || 'execute';
|
|
86
|
+
const wave = parseInt(options.wave, 10) || 1;
|
|
87
|
+
const fields = options.fields || {};
|
|
88
|
+
const padded = phaseId.split('-')[0]; // e.g., "01" from "01-setup-auth"
|
|
89
|
+
|
|
90
|
+
const frontmatter = {
|
|
91
|
+
phase: phaseId,
|
|
92
|
+
plan: planId,
|
|
93
|
+
type: planType,
|
|
94
|
+
wave,
|
|
95
|
+
depends_on: [],
|
|
96
|
+
files_modified: [],
|
|
97
|
+
autonomous: true,
|
|
98
|
+
user_setup: [],
|
|
99
|
+
must_haves: { truths: [], artifacts: [], key_links: [] },
|
|
100
|
+
tier: null,
|
|
101
|
+
priority: null,
|
|
102
|
+
effort: null,
|
|
103
|
+
...fields,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const body = [
|
|
107
|
+
`# Phase ${phaseNum} Plan ${planId}: [Title]`,
|
|
108
|
+
'',
|
|
109
|
+
'## Objective',
|
|
110
|
+
'- **What:** [What this plan builds]',
|
|
111
|
+
'- **Why:** [Why it matters for the phase goal]',
|
|
112
|
+
'- **Output:** [Concrete deliverable]',
|
|
113
|
+
'',
|
|
114
|
+
'## Context',
|
|
115
|
+
'@.planning/project.md',
|
|
116
|
+
'@.planning/roadmap.md',
|
|
117
|
+
'@.planning/state.md',
|
|
118
|
+
'',
|
|
119
|
+
'## Tasks',
|
|
120
|
+
'',
|
|
121
|
+
'<task type="code">',
|
|
122
|
+
' <name>[Task name]</name>',
|
|
123
|
+
' <files>[file paths]</files>',
|
|
124
|
+
' <action>[What to do]</action>',
|
|
125
|
+
' <verify>[How to verify]</verify>',
|
|
126
|
+
' <done>[Definition of done]</done>',
|
|
127
|
+
'</task>',
|
|
128
|
+
'',
|
|
129
|
+
'## Verification',
|
|
130
|
+
'[How to verify this plan achieved its objective]',
|
|
131
|
+
'',
|
|
132
|
+
'## Success Criteria',
|
|
133
|
+
'- [ ] [Criterion 1]',
|
|
134
|
+
'- [ ] [Criterion 2]',
|
|
135
|
+
].join('\n');
|
|
136
|
+
|
|
137
|
+
const fileName = `${padded}-${planId}-plan.md`;
|
|
138
|
+
return { frontmatter, body, fileName };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generate the frontmatter and body for a summary template.
|
|
143
|
+
* @param {string} phaseId - Full phase identifier (e.g., "01-setup-auth")
|
|
144
|
+
* @param {string} planId - Zero-padded plan number (e.g., "01")
|
|
145
|
+
* @param {string} phaseName - Human-readable phase name
|
|
146
|
+
* @param {string} phaseNum - Raw phase number from options
|
|
147
|
+
* @param {Object} fields - Additional frontmatter fields to merge
|
|
148
|
+
* @returns {{frontmatter: Object, body: string, fileName: string}}
|
|
149
|
+
*/
|
|
150
|
+
function generateSummaryTemplate(phaseId, planId, phaseName, phaseNum, fields) {
|
|
151
|
+
const today = new Date().toISOString().split('T')[0];
|
|
152
|
+
const padded = phaseId.split('-')[0];
|
|
153
|
+
|
|
154
|
+
const frontmatter = {
|
|
155
|
+
phase: phaseId,
|
|
156
|
+
plan: planId,
|
|
157
|
+
subsystem: '[primary category]',
|
|
158
|
+
tags: [],
|
|
159
|
+
provides: [],
|
|
160
|
+
affects: [],
|
|
161
|
+
'tech-stack': { added: [], patterns: [] },
|
|
162
|
+
'key-files': { created: [], modified: [] },
|
|
163
|
+
'key-decisions': [],
|
|
164
|
+
'patterns-established': [],
|
|
165
|
+
duration: '[X]min',
|
|
166
|
+
completed: today,
|
|
167
|
+
...fields,
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const body = [
|
|
171
|
+
`# Phase ${phaseNum}: ${phaseName} Summary`,
|
|
172
|
+
'',
|
|
173
|
+
'**[Substantive one-liner describing outcome]**',
|
|
174
|
+
'',
|
|
175
|
+
'## Performance',
|
|
176
|
+
'- **Duration:** [time]',
|
|
177
|
+
'- **Tasks:** [count completed]',
|
|
178
|
+
'- **Files modified:** [count]',
|
|
179
|
+
'',
|
|
180
|
+
'## Accomplishments',
|
|
181
|
+
'- [Key outcome 1]',
|
|
182
|
+
'- [Key outcome 2]',
|
|
183
|
+
'',
|
|
184
|
+
'## Task Commits',
|
|
185
|
+
'1. **Task 1: [task name]** - `hash`',
|
|
186
|
+
'',
|
|
187
|
+
'## Files Created/Modified',
|
|
188
|
+
'- `path/to/file.ts` - What it does',
|
|
189
|
+
'',
|
|
190
|
+
'## Decisions & Deviations',
|
|
191
|
+
'[Key decisions or "None - followed plan as specified"]',
|
|
192
|
+
'',
|
|
193
|
+
'## Next Phase Readiness',
|
|
194
|
+
'[What\'s ready for next phase]',
|
|
195
|
+
].join('\n');
|
|
196
|
+
|
|
197
|
+
const fileName = `${padded}-${planId}-summary.md`;
|
|
198
|
+
return { frontmatter, body, fileName };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Generate the frontmatter and body for a verification template.
|
|
203
|
+
* @param {string} phaseId - Full phase identifier (e.g., "01-setup-auth")
|
|
204
|
+
* @param {string} phaseName - Human-readable phase name
|
|
205
|
+
* @param {string} phaseNum - Raw phase number from options
|
|
206
|
+
* @param {Object} fields - Additional frontmatter fields to merge
|
|
207
|
+
* @returns {{frontmatter: Object, body: string, fileName: string}}
|
|
208
|
+
*/
|
|
209
|
+
function generateVerificationTemplate(phaseId, phaseName, phaseNum, fields) {
|
|
210
|
+
const padded = phaseId.split('-')[0];
|
|
211
|
+
|
|
212
|
+
const frontmatter = {
|
|
213
|
+
phase: phaseId,
|
|
214
|
+
verified: new Date().toISOString(),
|
|
215
|
+
status: 'pending',
|
|
216
|
+
score: '0/0 must-haves verified',
|
|
217
|
+
...fields,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const body = [
|
|
221
|
+
`# Phase ${phaseNum}: ${phaseName} — Verification`,
|
|
222
|
+
'',
|
|
223
|
+
'## Observable Truths',
|
|
224
|
+
'| # | Truth | Status | Evidence |',
|
|
225
|
+
'|---|-------|--------|----------|',
|
|
226
|
+
'| 1 | [Truth] | pending | |',
|
|
227
|
+
'',
|
|
228
|
+
'## Required Artifacts',
|
|
229
|
+
'| Artifact | Expected | Status | Details |',
|
|
230
|
+
'|----------|----------|--------|---------|',
|
|
231
|
+
'| [path] | [what] | pending | |',
|
|
232
|
+
'',
|
|
233
|
+
'## Key Link Verification',
|
|
234
|
+
'| From | To | Via | Status | Details |',
|
|
235
|
+
'|------|----|----|--------|---------|',
|
|
236
|
+
'| [source] | [target] | [connection] | pending | |',
|
|
237
|
+
'',
|
|
238
|
+
'## Requirements Coverage',
|
|
239
|
+
'| Requirement | Status | Blocking Issue |',
|
|
240
|
+
'|-------------|--------|----------------|',
|
|
241
|
+
'| [req] | pending | |',
|
|
242
|
+
'',
|
|
243
|
+
'## Result',
|
|
244
|
+
'[Pending verification]',
|
|
245
|
+
].join('\n');
|
|
246
|
+
|
|
247
|
+
const fileName = `${padded}-verification.md`;
|
|
248
|
+
return { frontmatter, body, fileName };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Generate a pre-filled template file (summary, plan, or verification) in a phase directory.
|
|
253
|
+
* Dispatches to the appropriate generator function based on templateType.
|
|
254
|
+
* @param {string} cwd - Working directory path
|
|
255
|
+
* @param {string} templateType - Template type: "summary", "plan", or "verification"
|
|
256
|
+
* @param {Object} options - Options (phase, plan, name, type, wave, fields)
|
|
257
|
+
* @param {boolean} raw - If true, output raw path instead of JSON
|
|
258
|
+
* @returns {void}
|
|
259
|
+
*/
|
|
260
|
+
function cmdTemplateFill(cwd, templateType, options, raw) {
|
|
261
|
+
if (!templateType) { error('template type required: summary, plan, or verification'); }
|
|
262
|
+
if (!options.phase) { error('--phase required'); }
|
|
263
|
+
|
|
264
|
+
const phaseInfo = findPhaseInternal(cwd, options.phase);
|
|
265
|
+
if (!phaseInfo || !phaseInfo.found) { output({ error: 'Phase not found', phase: options.phase }, raw); return; }
|
|
266
|
+
|
|
267
|
+
const padded = normalizePhaseName(options.phase);
|
|
268
|
+
const phaseName = options.name || phaseInfo.phase_name || 'Unnamed';
|
|
269
|
+
const phaseSlug = phaseInfo.phase_slug || generateSlugInternal(phaseName);
|
|
270
|
+
const phaseId = `${padded}-${phaseSlug}`;
|
|
271
|
+
const planNum = (options.plan || '01').padStart(2, '0');
|
|
272
|
+
const fields = options.fields || {};
|
|
273
|
+
|
|
274
|
+
let generated;
|
|
275
|
+
|
|
276
|
+
switch (templateType) {
|
|
277
|
+
case 'summary':
|
|
278
|
+
generated = generateSummaryTemplate(phaseId, planNum, phaseName, options.phase, fields);
|
|
279
|
+
break;
|
|
280
|
+
case 'plan':
|
|
281
|
+
generated = generatePlanTemplate(phaseId, planNum, phaseName, phaseSlug, options.phase, options);
|
|
282
|
+
break;
|
|
283
|
+
case 'verification':
|
|
284
|
+
generated = generateVerificationTemplate(phaseId, phaseName, options.phase, fields);
|
|
285
|
+
break;
|
|
286
|
+
default:
|
|
287
|
+
error(`Unknown template type: ${templateType}. Available: summary, plan, verification`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const fullContent = `---\n${reconstructFrontmatter(generated.frontmatter)}\n---\n\n${generated.body}\n`;
|
|
292
|
+
const outPath = path.join(cwd, phaseInfo.directory, generated.fileName);
|
|
293
|
+
|
|
294
|
+
try {
|
|
295
|
+
fs.writeFileSync(outPath, fullContent, { encoding: 'utf-8', flag: 'wx' });
|
|
296
|
+
} catch (e) {
|
|
297
|
+
if (e.code === 'EEXIST') {
|
|
298
|
+
output({ error: 'File already exists', path: path.relative(cwd, outPath) }, raw);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
error(`Failed to write template: ${e.message}`);
|
|
302
|
+
}
|
|
303
|
+
const relPath = toPosix(path.relative(cwd, outPath));
|
|
304
|
+
output({ created: true, path: relPath, template: templateType }, raw, relPath);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
cmdTemplateSelect,
|
|
309
|
+
cmdTemplateFill,
|
|
310
|
+
// Exported for testability
|
|
311
|
+
generatePlanTemplate,
|
|
312
|
+
generateSummaryTemplate,
|
|
313
|
+
generateVerificationTemplate,
|
|
314
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utils — Shared utility functions used across multiple modules
|
|
3
|
+
*
|
|
4
|
+
* Functions here were previously duplicated in core.cjs, commands.cjs,
|
|
5
|
+
* state.cjs, and phase.cjs. Now centralized for single-source-of-truth.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const {
|
|
12
|
+
PLANNING_DIR,
|
|
13
|
+
PHASES_DIR,
|
|
14
|
+
MILESTONES_DIR,
|
|
15
|
+
isPlanFile,
|
|
16
|
+
isSummaryFile,
|
|
17
|
+
PHASE_DIR_RE,
|
|
18
|
+
} = require('./constants.cjs');
|
|
19
|
+
|
|
20
|
+
// ─── File utilities ──────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Read and parse a JSON file, returning null on any failure.
|
|
24
|
+
* @param {string} filePath - Absolute path to the JSON file
|
|
25
|
+
* @returns {Object|null} Parsed JSON object, or null if unreadable/unparseable
|
|
26
|
+
*/
|
|
27
|
+
function readJsonFile(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Remove surrounding quotes (single or double) from a string.
|
|
37
|
+
* @param {string} str - Input string possibly wrapped in quotes
|
|
38
|
+
* @returns {string} String with leading/trailing quotes removed
|
|
39
|
+
*/
|
|
40
|
+
function removeQuotes(str) {
|
|
41
|
+
return str.replace(/^["']|["']$/g, '');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ─── Phase directory utilities ───────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Build the absolute path to the .planning directory.
|
|
48
|
+
* @param {string} cwd - Project root directory
|
|
49
|
+
* @returns {string} Absolute path to .planning/
|
|
50
|
+
*/
|
|
51
|
+
function planningPath(cwd) {
|
|
52
|
+
return path.join(cwd, PLANNING_DIR);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build the absolute path to the phases directory.
|
|
57
|
+
* @param {string} cwd - Project root directory
|
|
58
|
+
* @returns {string} Absolute path to .planning/phases/
|
|
59
|
+
*/
|
|
60
|
+
function phasesPath(cwd) {
|
|
61
|
+
return path.join(cwd, PLANNING_DIR, PHASES_DIR);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build the absolute path to the milestones directory.
|
|
66
|
+
* @param {string} cwd - Project root directory
|
|
67
|
+
* @returns {string} Absolute path to .planning/milestones/
|
|
68
|
+
*/
|
|
69
|
+
function milestonesPath(cwd) {
|
|
70
|
+
return path.join(cwd, PLANNING_DIR, MILESTONES_DIR);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Read phase directories from the phases folder, sorted by phase number.
|
|
75
|
+
* @param {string} cwd - Project root directory
|
|
76
|
+
* @returns {string[]} Sorted array of directory names, or empty array on failure
|
|
77
|
+
*/
|
|
78
|
+
function listPhaseDirs(cwd) {
|
|
79
|
+
const { comparePhaseNum } = require('./core.cjs');
|
|
80
|
+
try {
|
|
81
|
+
const entries = fs.readdirSync(phasesPath(cwd), { withFileTypes: true });
|
|
82
|
+
return entries
|
|
83
|
+
.filter(e => e.isDirectory())
|
|
84
|
+
.map(e => e.name)
|
|
85
|
+
.sort((a, b) => comparePhaseNum(a, b));
|
|
86
|
+
} catch {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Filter an array of filenames to only plan files, sorted.
|
|
93
|
+
* @param {string[]} files - Array of filenames
|
|
94
|
+
* @returns {string[]} Sorted plan filenames
|
|
95
|
+
*/
|
|
96
|
+
function filterPlanFiles(files) {
|
|
97
|
+
return files.filter(isPlanFile).sort();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Filter an array of filenames to only summary files, sorted.
|
|
102
|
+
* @param {string[]} files - Array of filenames
|
|
103
|
+
* @returns {string[]} Sorted summary filenames
|
|
104
|
+
*/
|
|
105
|
+
function filterSummaryFiles(files) {
|
|
106
|
+
return files.filter(isSummaryFile).sort();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Extract the phase number and name from a phase directory name.
|
|
111
|
+
* e.g. "01-setup-auth" → { number: "01", name: "setup-auth" }
|
|
112
|
+
* @param {string} dirName - Phase directory name
|
|
113
|
+
* @returns {{ number: string, name: string|null }} Parsed phase info
|
|
114
|
+
*/
|
|
115
|
+
function parsePhaseDir(dirName) {
|
|
116
|
+
const match = dirName.match(PHASE_DIR_RE);
|
|
117
|
+
if (!match) return { number: dirName, name: null };
|
|
118
|
+
return {
|
|
119
|
+
number: match[1],
|
|
120
|
+
name: match[2] || null,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Classify phase status from file counts. Returns a granular status string.
|
|
126
|
+
* @param {number} planCount - Number of plan files
|
|
127
|
+
* @param {number} summaryCount - Number of summary files
|
|
128
|
+
* @param {{hasContext?: boolean, hasResearch?: boolean}} [flags] - Extra file flags
|
|
129
|
+
* @returns {string} One of: 'complete', 'partial', 'planned', 'researched', 'discussed', 'empty'
|
|
130
|
+
*/
|
|
131
|
+
function classifyPhaseStatus(planCount, summaryCount, flags = {}) {
|
|
132
|
+
if (summaryCount >= planCount && planCount > 0) return 'complete';
|
|
133
|
+
if (summaryCount > 0) return 'partial';
|
|
134
|
+
if (planCount > 0) return 'planned';
|
|
135
|
+
if (flags.hasResearch) return 'researched';
|
|
136
|
+
if (flags.hasContext) return 'discussed';
|
|
137
|
+
return 'empty';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check if a file is accessible (exists and is readable).
|
|
142
|
+
* @param {string} filePath - Absolute path to check
|
|
143
|
+
* @returns {boolean}
|
|
144
|
+
*/
|
|
145
|
+
function fileAccessible(filePath) {
|
|
146
|
+
try { fs.accessSync(filePath, fs.constants.R_OK); return true; } catch { return false; }
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Detect whether Brave Search API key is available (env var or key file).
|
|
151
|
+
* @returns {boolean}
|
|
152
|
+
*/
|
|
153
|
+
function hasBraveSearchKey() {
|
|
154
|
+
if (process.env.BRAVE_API_KEY) return true;
|
|
155
|
+
return fileAccessible(path.join(os.homedir(), '.pan-wizard', 'brave_api_key'));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
readJsonFile,
|
|
160
|
+
removeQuotes,
|
|
161
|
+
planningPath,
|
|
162
|
+
phasesPath,
|
|
163
|
+
milestonesPath,
|
|
164
|
+
listPhaseDirs,
|
|
165
|
+
filterPlanFiles,
|
|
166
|
+
filterSummaryFiles,
|
|
167
|
+
parsePhaseDir,
|
|
168
|
+
classifyPhaseStatus,
|
|
169
|
+
fileAccessible,
|
|
170
|
+
hasBraveSearchKey,
|
|
171
|
+
};
|