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.
Files changed (164) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +772 -0
  3. package/agents/pan-debugger.md +1246 -0
  4. package/agents/pan-document_code.md +965 -0
  5. package/agents/pan-executor.md +469 -0
  6. package/agents/pan-integration-checker.md +443 -0
  7. package/agents/pan-phase-researcher.md +572 -0
  8. package/agents/pan-plan-checker.md +763 -0
  9. package/agents/pan-planner.md +1297 -0
  10. package/agents/pan-project-researcher.md +647 -0
  11. package/agents/pan-research-synthesizer.md +239 -0
  12. package/agents/pan-reviewer.md +112 -0
  13. package/agents/pan-roadmapper.md +642 -0
  14. package/agents/pan-verifier.md +672 -0
  15. package/assets/pan-logo-2000-transparent.svg +30 -0
  16. package/assets/pan-logo-2000.svg +43 -0
  17. package/assets/terminal.svg +119 -0
  18. package/bin/install-lib.cjs +616 -0
  19. package/bin/install.js +1936 -0
  20. package/commands/pan/add-phase.md +44 -0
  21. package/commands/pan/assumptions.md +47 -0
  22. package/commands/pan/audit-deployment.md +378 -0
  23. package/commands/pan/debug.md +168 -0
  24. package/commands/pan/discord.md +19 -0
  25. package/commands/pan/discuss-phase.md +84 -0
  26. package/commands/pan/exec-phase.md +45 -0
  27. package/commands/pan/focus-auto.md +323 -0
  28. package/commands/pan/focus-design.md +816 -0
  29. package/commands/pan/focus-exec.md +316 -0
  30. package/commands/pan/focus-plan.md +101 -0
  31. package/commands/pan/focus-scan.md +272 -0
  32. package/commands/pan/focus-sync.md +104 -0
  33. package/commands/pan/health.md +23 -0
  34. package/commands/pan/help.md +23 -0
  35. package/commands/pan/insert-phase.md +33 -0
  36. package/commands/pan/map-codebase.md +72 -0
  37. package/commands/pan/milestone-audit.md +37 -0
  38. package/commands/pan/milestone-cleanup.md +19 -0
  39. package/commands/pan/milestone-done.md +137 -0
  40. package/commands/pan/milestone-gaps.md +35 -0
  41. package/commands/pan/milestone-new.md +45 -0
  42. package/commands/pan/new-project.md +43 -0
  43. package/commands/pan/patches.md +110 -0
  44. package/commands/pan/pause.md +39 -0
  45. package/commands/pan/phase-budget.md +23 -0
  46. package/commands/pan/phase-tests.md +42 -0
  47. package/commands/pan/plan-phase.md +46 -0
  48. package/commands/pan/profile.md +36 -0
  49. package/commands/pan/progress.md +25 -0
  50. package/commands/pan/quick.md +42 -0
  51. package/commands/pan/remove-phase.md +32 -0
  52. package/commands/pan/research-phase.md +190 -0
  53. package/commands/pan/resume.md +41 -0
  54. package/commands/pan/retro.md +33 -0
  55. package/commands/pan/settings.md +37 -0
  56. package/commands/pan/todo-add.md +48 -0
  57. package/commands/pan/todo-check.md +46 -0
  58. package/commands/pan/update.md +38 -0
  59. package/commands/pan/verify-phase.md +39 -0
  60. package/hooks/dist/pan-check-update.js +62 -0
  61. package/hooks/dist/pan-context-monitor.js +122 -0
  62. package/hooks/dist/pan-statusline.js +108 -0
  63. package/package.json +66 -0
  64. package/pan-wizard-core/bin/lib/codebase.cjs +746 -0
  65. package/pan-wizard-core/bin/lib/commands.cjs +1435 -0
  66. package/pan-wizard-core/bin/lib/config.cjs +611 -0
  67. package/pan-wizard-core/bin/lib/constants.cjs +696 -0
  68. package/pan-wizard-core/bin/lib/context-budget.cjs +150 -0
  69. package/pan-wizard-core/bin/lib/core.cjs +650 -0
  70. package/pan-wizard-core/bin/lib/focus.cjs +900 -0
  71. package/pan-wizard-core/bin/lib/frontmatter.cjs +442 -0
  72. package/pan-wizard-core/bin/lib/init.cjs +881 -0
  73. package/pan-wizard-core/bin/lib/milestone.cjs +276 -0
  74. package/pan-wizard-core/bin/lib/phase.cjs +1212 -0
  75. package/pan-wizard-core/bin/lib/roadmap.cjs +470 -0
  76. package/pan-wizard-core/bin/lib/state.cjs +1029 -0
  77. package/pan-wizard-core/bin/lib/template.cjs +314 -0
  78. package/pan-wizard-core/bin/lib/utils.cjs +171 -0
  79. package/pan-wizard-core/bin/lib/verify.cjs +1808 -0
  80. package/pan-wizard-core/bin/pan-tools.cjs +773 -0
  81. package/pan-wizard-core/references/checkpoints.md +776 -0
  82. package/pan-wizard-core/references/continuation-format.md +249 -0
  83. package/pan-wizard-core/references/decimal-phase-calculation.md +65 -0
  84. package/pan-wizard-core/references/git-integration.md +248 -0
  85. package/pan-wizard-core/references/git-planning-commit.md +38 -0
  86. package/pan-wizard-core/references/model-profile-resolution.md +34 -0
  87. package/pan-wizard-core/references/model-profiles.md +111 -0
  88. package/pan-wizard-core/references/phase-argument-parsing.md +61 -0
  89. package/pan-wizard-core/references/planning-config.md +196 -0
  90. package/pan-wizard-core/references/questioning.md +145 -0
  91. package/pan-wizard-core/references/tdd.md +263 -0
  92. package/pan-wizard-core/references/ui-brand.md +160 -0
  93. package/pan-wizard-core/references/verification-patterns.md +612 -0
  94. package/pan-wizard-core/templates/codebase/architecture.md +283 -0
  95. package/pan-wizard-core/templates/codebase/best-practices.md +133 -0
  96. package/pan-wizard-core/templates/codebase/concerns.md +325 -0
  97. package/pan-wizard-core/templates/codebase/conventions.md +307 -0
  98. package/pan-wizard-core/templates/codebase/integrations.md +305 -0
  99. package/pan-wizard-core/templates/codebase/relationships.md +124 -0
  100. package/pan-wizard-core/templates/codebase/stack.md +199 -0
  101. package/pan-wizard-core/templates/codebase/structure.md +298 -0
  102. package/pan-wizard-core/templates/codebase/testing.md +480 -0
  103. package/pan-wizard-core/templates/config.json +37 -0
  104. package/pan-wizard-core/templates/context.md +283 -0
  105. package/pan-wizard-core/templates/continue-here.md +78 -0
  106. package/pan-wizard-core/templates/debug-subagent-prompt.md +91 -0
  107. package/pan-wizard-core/templates/debug.md +164 -0
  108. package/pan-wizard-core/templates/discovery.md +146 -0
  109. package/pan-wizard-core/templates/milestone-archive.md +123 -0
  110. package/pan-wizard-core/templates/milestone.md +115 -0
  111. package/pan-wizard-core/templates/phase-prompt.md +593 -0
  112. package/pan-wizard-core/templates/planner-subagent-prompt.md +117 -0
  113. package/pan-wizard-core/templates/project.md +184 -0
  114. package/pan-wizard-core/templates/requirements.md +231 -0
  115. package/pan-wizard-core/templates/research-project/architecture.md +204 -0
  116. package/pan-wizard-core/templates/research-project/features.md +147 -0
  117. package/pan-wizard-core/templates/research-project/pitfalls.md +200 -0
  118. package/pan-wizard-core/templates/research-project/stack.md +120 -0
  119. package/pan-wizard-core/templates/research-project/summary.md +170 -0
  120. package/pan-wizard-core/templates/research.md +552 -0
  121. package/pan-wizard-core/templates/retrospective.md +54 -0
  122. package/pan-wizard-core/templates/roadmap.md +202 -0
  123. package/pan-wizard-core/templates/standards.md +24 -0
  124. package/pan-wizard-core/templates/state.md +176 -0
  125. package/pan-wizard-core/templates/summary-complex.md +59 -0
  126. package/pan-wizard-core/templates/summary-minimal.md +41 -0
  127. package/pan-wizard-core/templates/summary-standard.md +49 -0
  128. package/pan-wizard-core/templates/summary.md +249 -0
  129. package/pan-wizard-core/templates/uat.md +247 -0
  130. package/pan-wizard-core/templates/user-setup.md +311 -0
  131. package/pan-wizard-core/templates/validation.md +76 -0
  132. package/pan-wizard-core/templates/verification-report.md +322 -0
  133. package/pan-wizard-core/workflows/add-phase.md +111 -0
  134. package/pan-wizard-core/workflows/assumptions.md +178 -0
  135. package/pan-wizard-core/workflows/diagnose-issues.md +219 -0
  136. package/pan-wizard-core/workflows/discuss-phase.md +542 -0
  137. package/pan-wizard-core/workflows/exec-phase.md +572 -0
  138. package/pan-wizard-core/workflows/execute-plan.md +448 -0
  139. package/pan-wizard-core/workflows/health.md +156 -0
  140. package/pan-wizard-core/workflows/help.md +431 -0
  141. package/pan-wizard-core/workflows/insert-phase.md +129 -0
  142. package/pan-wizard-core/workflows/map-codebase.md +401 -0
  143. package/pan-wizard-core/workflows/milestone-audit.md +297 -0
  144. package/pan-wizard-core/workflows/milestone-cleanup.md +152 -0
  145. package/pan-wizard-core/workflows/milestone-gaps.md +274 -0
  146. package/pan-wizard-core/workflows/milestone-new.md +382 -0
  147. package/pan-wizard-core/workflows/new-project.md +1178 -0
  148. package/pan-wizard-core/workflows/pause.md +122 -0
  149. package/pan-wizard-core/workflows/phase-tests.md +388 -0
  150. package/pan-wizard-core/workflows/plan-phase.md +569 -0
  151. package/pan-wizard-core/workflows/profile.md +115 -0
  152. package/pan-wizard-core/workflows/progress.md +381 -0
  153. package/pan-wizard-core/workflows/quick.md +453 -0
  154. package/pan-wizard-core/workflows/remove-phase.md +154 -0
  155. package/pan-wizard-core/workflows/research-phase.md +73 -0
  156. package/pan-wizard-core/workflows/resume-project.md +306 -0
  157. package/pan-wizard-core/workflows/retro.md +121 -0
  158. package/pan-wizard-core/workflows/settings.md +213 -0
  159. package/pan-wizard-core/workflows/todo-add.md +157 -0
  160. package/pan-wizard-core/workflows/todo-check.md +176 -0
  161. package/pan-wizard-core/workflows/transition.md +544 -0
  162. package/pan-wizard-core/workflows/update.md +219 -0
  163. package/pan-wizard-core/workflows/verify-phase.md +301 -0
  164. 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
+ };