claude-autopm 2.1.1 ā 2.2.0
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/lib/cli/commands/prd.js +389 -1
- package/package.json +1 -1
package/lib/cli/commands/prd.js
CHANGED
|
@@ -17,6 +17,7 @@ const fs = require('fs-extra');
|
|
|
17
17
|
const ora = require('ora');
|
|
18
18
|
const chalk = require('chalk');
|
|
19
19
|
const path = require('path');
|
|
20
|
+
const { spawn } = require('child_process');
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Get PRD file path
|
|
@@ -46,6 +47,310 @@ async function readPrdFile(name) {
|
|
|
46
47
|
return await fs.readFile(prdPath, 'utf8');
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* List all PRDs
|
|
52
|
+
*/
|
|
53
|
+
async function prdList(argv) {
|
|
54
|
+
const spinner = ora('Loading PRDs...').start();
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const prdsDir = path.join(process.cwd(), '.claude', 'prds');
|
|
58
|
+
|
|
59
|
+
// Check if directory exists
|
|
60
|
+
const dirExists = await fs.pathExists(prdsDir);
|
|
61
|
+
if (!dirExists) {
|
|
62
|
+
spinner.info(chalk.yellow('No PRDs directory found'));
|
|
63
|
+
console.log(chalk.yellow('\nCreate your first PRD with: autopm prd new <name>'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Read all PRD files
|
|
68
|
+
const files = await fs.readdir(prdsDir);
|
|
69
|
+
const prdFiles = files.filter(f => f.endsWith('.md'));
|
|
70
|
+
|
|
71
|
+
if (prdFiles.length === 0) {
|
|
72
|
+
spinner.info(chalk.yellow('No PRDs found'));
|
|
73
|
+
console.log(chalk.yellow('\nCreate your first PRD with: autopm prd new <name>'));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
spinner.succeed(chalk.green(`Found ${prdFiles.length} PRD(s)`));
|
|
78
|
+
|
|
79
|
+
// Read and parse each PRD
|
|
80
|
+
const prds = [];
|
|
81
|
+
for (const file of prdFiles) {
|
|
82
|
+
const filePath = path.join(prdsDir, file);
|
|
83
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
84
|
+
|
|
85
|
+
// Extract frontmatter
|
|
86
|
+
const titleMatch = content.match(/^title:\s*(.+)$/m);
|
|
87
|
+
const statusMatch = content.match(/^status:\s*(\w+)$/m);
|
|
88
|
+
const priorityMatch = content.match(/^priority:\s*(P\d|Critical|High|Medium|Low)$/m);
|
|
89
|
+
const createdMatch = content.match(/^created:\s*(.+)$/m);
|
|
90
|
+
|
|
91
|
+
prds.push({
|
|
92
|
+
name: file.replace('.md', ''),
|
|
93
|
+
title: titleMatch ? titleMatch[1] : file.replace('.md', ''),
|
|
94
|
+
status: statusMatch ? statusMatch[1] : 'unknown',
|
|
95
|
+
priority: priorityMatch ? priorityMatch[1] : 'P2',
|
|
96
|
+
created: createdMatch ? createdMatch[1] : 'unknown'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Sort by priority (P0 > P1 > P2 > P3)
|
|
101
|
+
prds.sort((a, b) => {
|
|
102
|
+
const priorities = { 'P0': 0, 'Critical': 0, 'P1': 1, 'High': 1, 'P2': 2, 'Medium': 2, 'P3': 3, 'Low': 3 };
|
|
103
|
+
return (priorities[a.priority] || 2) - (priorities[b.priority] || 2);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Display PRDs
|
|
107
|
+
console.log(chalk.green('\nš PRDs:\n'));
|
|
108
|
+
|
|
109
|
+
prds.forEach((prd, index) => {
|
|
110
|
+
const priorityColor = prd.priority.startsWith('P0') || prd.priority === 'Critical' ? chalk.red :
|
|
111
|
+
prd.priority.startsWith('P1') || prd.priority === 'High' ? chalk.yellow :
|
|
112
|
+
chalk.blue;
|
|
113
|
+
|
|
114
|
+
const statusColor = prd.status === 'completed' ? chalk.green :
|
|
115
|
+
prd.status === 'in-progress' ? chalk.yellow :
|
|
116
|
+
prd.status === 'draft' ? chalk.gray :
|
|
117
|
+
chalk.white;
|
|
118
|
+
|
|
119
|
+
console.log(`${index + 1}. ${chalk.bold(prd.name)}`);
|
|
120
|
+
console.log(` ${priorityColor(prd.priority.padEnd(10))} ${statusColor(prd.status.padEnd(12))} ${chalk.gray(prd.created)}`);
|
|
121
|
+
if (prd.title !== prd.name) {
|
|
122
|
+
console.log(` ${chalk.dim(prd.title)}`);
|
|
123
|
+
}
|
|
124
|
+
console.log('');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
console.log(chalk.dim(`\nTotal: ${prds.length} PRD(s)`));
|
|
128
|
+
console.log(chalk.dim('Use: autopm prd show <name> to view details\n'));
|
|
129
|
+
|
|
130
|
+
} catch (error) {
|
|
131
|
+
spinner.fail(chalk.red('Failed to list PRDs'));
|
|
132
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Show PRD content
|
|
138
|
+
* @param {Object} argv - Command arguments
|
|
139
|
+
*/
|
|
140
|
+
async function prdShow(argv) {
|
|
141
|
+
const spinner = ora(`Loading PRD: ${argv.name}`).start();
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
const content = await readPrdFile(argv.name);
|
|
145
|
+
spinner.succeed(chalk.green('PRD loaded'));
|
|
146
|
+
|
|
147
|
+
console.log('\n' + chalk.gray('ā'.repeat(80)) + '\n');
|
|
148
|
+
console.log(content);
|
|
149
|
+
console.log('\n' + chalk.gray('ā'.repeat(80)) + '\n');
|
|
150
|
+
|
|
151
|
+
const prdPath = getPrdPath(argv.name);
|
|
152
|
+
console.log(chalk.dim(`File: ${prdPath}\n`));
|
|
153
|
+
|
|
154
|
+
} catch (error) {
|
|
155
|
+
spinner.fail(chalk.red('Failed to show PRD'));
|
|
156
|
+
|
|
157
|
+
if (error.message.includes('not found')) {
|
|
158
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
159
|
+
console.error(chalk.yellow('Use: autopm prd list to see available PRDs'));
|
|
160
|
+
} else {
|
|
161
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Edit PRD in editor
|
|
168
|
+
* @param {Object} argv - Command arguments
|
|
169
|
+
*/
|
|
170
|
+
async function prdEdit(argv) {
|
|
171
|
+
const spinner = ora(`Opening PRD: ${argv.name}`).start();
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const prdPath = getPrdPath(argv.name);
|
|
175
|
+
|
|
176
|
+
// Check if file exists
|
|
177
|
+
const exists = await fs.pathExists(prdPath);
|
|
178
|
+
if (!exists) {
|
|
179
|
+
spinner.fail(chalk.red('PRD not found'));
|
|
180
|
+
console.error(chalk.red(`\nError: PRD file not found: ${prdPath}`));
|
|
181
|
+
console.error(chalk.yellow('Use: autopm prd list to see available PRDs'));
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
spinner.succeed(chalk.green('Opening editor...'));
|
|
186
|
+
|
|
187
|
+
// Determine editor
|
|
188
|
+
const editor = process.env.EDITOR || process.env.VISUAL || 'nano';
|
|
189
|
+
|
|
190
|
+
// Spawn editor
|
|
191
|
+
const { spawn } = require('child_process');
|
|
192
|
+
const child = spawn(editor, [prdPath], {
|
|
193
|
+
stdio: 'inherit',
|
|
194
|
+
cwd: process.cwd()
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Wait for editor to close
|
|
198
|
+
await new Promise((resolve, reject) => {
|
|
199
|
+
child.on('close', (code) => {
|
|
200
|
+
if (code === 0) {
|
|
201
|
+
console.log(chalk.green('\nā PRD saved'));
|
|
202
|
+
resolve();
|
|
203
|
+
} else {
|
|
204
|
+
reject(new Error(`Editor exited with code ${code}`));
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
child.on('error', reject);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
} catch (error) {
|
|
211
|
+
spinner.fail(chalk.red('Failed to edit PRD'));
|
|
212
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Show PRD status
|
|
218
|
+
* @param {Object} argv - Command arguments
|
|
219
|
+
*/
|
|
220
|
+
async function prdStatus(argv) {
|
|
221
|
+
const spinner = ora(`Analyzing PRD: ${argv.name}`).start();
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const content = await readPrdFile(argv.name);
|
|
225
|
+
|
|
226
|
+
// Extract metadata
|
|
227
|
+
const titleMatch = content.match(/^title:\s*(.+)$/m);
|
|
228
|
+
const statusMatch = content.match(/^status:\s*(\w+)$/m);
|
|
229
|
+
const priorityMatch = content.match(/^priority:\s*(P\d|Critical|High|Medium|Low)$/m);
|
|
230
|
+
const createdMatch = content.match(/^created:\s*(.+)$/m);
|
|
231
|
+
const authorMatch = content.match(/^author:\s*(.+)$/m);
|
|
232
|
+
const timelineMatch = content.match(/^timeline:\s*(.+)$/m);
|
|
233
|
+
|
|
234
|
+
// Count sections
|
|
235
|
+
const sections = {
|
|
236
|
+
'Problem Statement': content.includes('## Problem Statement'),
|
|
237
|
+
'User Stories': content.includes('## User Stories'),
|
|
238
|
+
'Technical Requirements': content.includes('## Technical Requirements'),
|
|
239
|
+
'Success Metrics': content.includes('## Success Metrics'),
|
|
240
|
+
'Implementation Plan': content.includes('## Implementation Plan'),
|
|
241
|
+
'Risks': content.includes('## Risks')
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const completedSections = Object.values(sections).filter(Boolean).length;
|
|
245
|
+
const totalSections = Object.keys(sections).length;
|
|
246
|
+
const completeness = Math.round((completedSections / totalSections) * 100);
|
|
247
|
+
|
|
248
|
+
spinner.succeed(chalk.green('Status analyzed'));
|
|
249
|
+
|
|
250
|
+
// Display status
|
|
251
|
+
console.log('\n' + chalk.bold('š PRD Status Report') + '\n');
|
|
252
|
+
console.log(chalk.gray('ā'.repeat(50)) + '\n');
|
|
253
|
+
|
|
254
|
+
console.log(chalk.bold('Metadata:'));
|
|
255
|
+
console.log(` Title: ${titleMatch ? titleMatch[1] : 'N/A'}`);
|
|
256
|
+
console.log(` Status: ${statusMatch ? chalk.yellow(statusMatch[1]) : 'N/A'}`);
|
|
257
|
+
console.log(` Priority: ${priorityMatch ? chalk.red(priorityMatch[1]) : 'N/A'}`);
|
|
258
|
+
console.log(` Created: ${createdMatch ? createdMatch[1] : 'N/A'}`);
|
|
259
|
+
console.log(` Author: ${authorMatch ? authorMatch[1] : 'N/A'}`);
|
|
260
|
+
console.log(` Timeline: ${timelineMatch ? timelineMatch[1] : 'N/A'}`);
|
|
261
|
+
|
|
262
|
+
console.log('\n' + chalk.bold('Completeness:') + ` ${completeness}%`);
|
|
263
|
+
|
|
264
|
+
const progressBar = 'ā'.repeat(Math.floor(completeness / 5)) +
|
|
265
|
+
'ā'.repeat(20 - Math.floor(completeness / 5));
|
|
266
|
+
console.log(` [${completeness >= 80 ? chalk.green(progressBar) :
|
|
267
|
+
completeness >= 50 ? chalk.yellow(progressBar) :
|
|
268
|
+
chalk.red(progressBar)}]`);
|
|
269
|
+
|
|
270
|
+
console.log('\n' + chalk.bold('Sections:'));
|
|
271
|
+
Object.entries(sections).forEach(([name, exists]) => {
|
|
272
|
+
const icon = exists ? chalk.green('ā') : chalk.red('ā');
|
|
273
|
+
console.log(` ${icon} ${name}`);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Statistics
|
|
277
|
+
const lines = content.split('\n').length;
|
|
278
|
+
const words = content.split(/\s+/).length;
|
|
279
|
+
const chars = content.length;
|
|
280
|
+
|
|
281
|
+
console.log('\n' + chalk.bold('Statistics:'));
|
|
282
|
+
console.log(` Lines: ${lines}`);
|
|
283
|
+
console.log(` Words: ${words}`);
|
|
284
|
+
console.log(` Chars: ${chars}`);
|
|
285
|
+
|
|
286
|
+
console.log('\n' + chalk.gray('ā'.repeat(50)) + '\n');
|
|
287
|
+
|
|
288
|
+
const prdPath = getPrdPath(argv.name);
|
|
289
|
+
console.log(chalk.dim(`File: ${prdPath}\n`));
|
|
290
|
+
|
|
291
|
+
} catch (error) {
|
|
292
|
+
spinner.fail(chalk.red('Failed to analyze status'));
|
|
293
|
+
|
|
294
|
+
if (error.message.includes('not found')) {
|
|
295
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
296
|
+
} else {
|
|
297
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Create new PRD
|
|
304
|
+
* @param {Object} argv - Command arguments
|
|
305
|
+
*/
|
|
306
|
+
async function prdNew(argv) {
|
|
307
|
+
const spinner = ora(`Creating PRD: ${argv.name}`).start();
|
|
308
|
+
|
|
309
|
+
try {
|
|
310
|
+
// Build script path
|
|
311
|
+
const scriptPath = path.join(process.cwd(), '.claude', 'scripts', 'pm', 'prd-new.js');
|
|
312
|
+
|
|
313
|
+
// Check if script exists
|
|
314
|
+
const scriptExists = await fs.pathExists(scriptPath);
|
|
315
|
+
if (!scriptExists) {
|
|
316
|
+
spinner.fail(chalk.red('PRD creation script not found'));
|
|
317
|
+
console.error(chalk.red('\nError: .claude/scripts/pm/prd-new.js not found'));
|
|
318
|
+
console.error(chalk.yellow('Run: autopm install'));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Build arguments
|
|
323
|
+
const args = [scriptPath, argv.name];
|
|
324
|
+
if (argv.template) {
|
|
325
|
+
args.push('--template', argv.template);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
spinner.stop();
|
|
329
|
+
|
|
330
|
+
// Spawn interactive process
|
|
331
|
+
const child = spawn('node', args, {
|
|
332
|
+
stdio: 'inherit',
|
|
333
|
+
cwd: process.cwd()
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Wait for completion
|
|
337
|
+
await new Promise((resolve, reject) => {
|
|
338
|
+
child.on('close', (code) => {
|
|
339
|
+
if (code === 0) {
|
|
340
|
+
resolve();
|
|
341
|
+
} else {
|
|
342
|
+
reject(new Error(`PRD creation failed with code ${code}`));
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
child.on('error', reject);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
} catch (error) {
|
|
349
|
+
spinner.fail(chalk.red('Failed to create PRD'));
|
|
350
|
+
console.error(chalk.red(`\nError: ${error.message}`));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
49
354
|
/**
|
|
50
355
|
* Parse PRD with AI
|
|
51
356
|
* @param {Object} argv - Command arguments
|
|
@@ -222,7 +527,7 @@ async function prdValidate(argv) {
|
|
|
222
527
|
*/
|
|
223
528
|
async function handler(argv) {
|
|
224
529
|
// Validate action
|
|
225
|
-
const validActions = ['parse', 'extract-epics', 'summarize', 'validate'];
|
|
530
|
+
const validActions = ['list', 'new', 'show', 'edit', 'status', 'parse', 'extract-epics', 'summarize', 'validate'];
|
|
226
531
|
|
|
227
532
|
if (!validActions.includes(argv.action)) {
|
|
228
533
|
console.error(chalk.red(`\nError: Unknown action: ${argv.action}`));
|
|
@@ -233,6 +538,21 @@ async function handler(argv) {
|
|
|
233
538
|
// Route to appropriate handler
|
|
234
539
|
try {
|
|
235
540
|
switch (argv.action) {
|
|
541
|
+
case 'list':
|
|
542
|
+
await prdList(argv);
|
|
543
|
+
break;
|
|
544
|
+
case 'new':
|
|
545
|
+
await prdNew(argv);
|
|
546
|
+
break;
|
|
547
|
+
case 'show':
|
|
548
|
+
await prdShow(argv);
|
|
549
|
+
break;
|
|
550
|
+
case 'edit':
|
|
551
|
+
await prdEdit(argv);
|
|
552
|
+
break;
|
|
553
|
+
case 'status':
|
|
554
|
+
await prdStatus(argv);
|
|
555
|
+
break;
|
|
236
556
|
case 'parse':
|
|
237
557
|
await prdParse(argv);
|
|
238
558
|
break;
|
|
@@ -259,6 +579,69 @@ async function handler(argv) {
|
|
|
259
579
|
*/
|
|
260
580
|
function builder(yargs) {
|
|
261
581
|
return yargs
|
|
582
|
+
.command(
|
|
583
|
+
'list',
|
|
584
|
+
'List all PRDs',
|
|
585
|
+
(yargs) => {
|
|
586
|
+
return yargs
|
|
587
|
+
.example('autopm prd list', 'Show all PRDs');
|
|
588
|
+
}
|
|
589
|
+
)
|
|
590
|
+
.command(
|
|
591
|
+
'new <name>',
|
|
592
|
+
'Create new PRD interactively',
|
|
593
|
+
(yargs) => {
|
|
594
|
+
return yargs
|
|
595
|
+
.positional('name', {
|
|
596
|
+
describe: 'PRD name (use-kebab-case)',
|
|
597
|
+
type: 'string'
|
|
598
|
+
})
|
|
599
|
+
.option('template', {
|
|
600
|
+
describe: 'Template to use (api-feature, ui-feature, bug-fix, data-migration, documentation)',
|
|
601
|
+
type: 'string',
|
|
602
|
+
alias: 't'
|
|
603
|
+
})
|
|
604
|
+
.example('autopm prd new my-feature', 'Create PRD with wizard')
|
|
605
|
+
.example('autopm prd new payment-api --template api-feature', 'Create PRD from template');
|
|
606
|
+
}
|
|
607
|
+
)
|
|
608
|
+
.command(
|
|
609
|
+
'show <name>',
|
|
610
|
+
'Display PRD content',
|
|
611
|
+
(yargs) => {
|
|
612
|
+
return yargs
|
|
613
|
+
.positional('name', {
|
|
614
|
+
describe: 'PRD name (without .md extension)',
|
|
615
|
+
type: 'string'
|
|
616
|
+
})
|
|
617
|
+
.example('autopm prd show my-feature', 'Display PRD content');
|
|
618
|
+
}
|
|
619
|
+
)
|
|
620
|
+
.command(
|
|
621
|
+
'edit <name>',
|
|
622
|
+
'Edit PRD in your editor',
|
|
623
|
+
(yargs) => {
|
|
624
|
+
return yargs
|
|
625
|
+
.positional('name', {
|
|
626
|
+
describe: 'PRD name (without .md extension)',
|
|
627
|
+
type: 'string'
|
|
628
|
+
})
|
|
629
|
+
.example('autopm prd edit my-feature', 'Open PRD in editor')
|
|
630
|
+
.example('EDITOR=code autopm prd edit my-feature', 'Open PRD in VS Code');
|
|
631
|
+
}
|
|
632
|
+
)
|
|
633
|
+
.command(
|
|
634
|
+
'status <name>',
|
|
635
|
+
'Show PRD status and completeness',
|
|
636
|
+
(yargs) => {
|
|
637
|
+
return yargs
|
|
638
|
+
.positional('name', {
|
|
639
|
+
describe: 'PRD name (without .md extension)',
|
|
640
|
+
type: 'string'
|
|
641
|
+
})
|
|
642
|
+
.example('autopm prd status my-feature', 'Show PRD status report');
|
|
643
|
+
}
|
|
644
|
+
)
|
|
262
645
|
.command(
|
|
263
646
|
'parse <name>',
|
|
264
647
|
'Parse PRD with AI analysis',
|
|
@@ -337,6 +720,11 @@ module.exports = {
|
|
|
337
720
|
builder,
|
|
338
721
|
handler,
|
|
339
722
|
handlers: {
|
|
723
|
+
list: prdList,
|
|
724
|
+
new: prdNew,
|
|
725
|
+
show: prdShow,
|
|
726
|
+
edit: prdEdit,
|
|
727
|
+
status: prdStatus,
|
|
340
728
|
parse: prdParse,
|
|
341
729
|
extractEpics: prdExtractEpics,
|
|
342
730
|
summarize: prdSummarize,
|