claude-autopm 1.31.0 → 2.1.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.
@@ -0,0 +1,345 @@
1
+ /**
2
+ * CLI PRD Commands
3
+ *
4
+ * Provides PRD (Product Requirements Document) management commands.
5
+ * Implements subcommands for parse, extract-epics, summarize, and validate operations.
6
+ *
7
+ * @module cli/commands/prd
8
+ * @requires ../../services/PRDService
9
+ * @requires fs-extra
10
+ * @requires ora
11
+ * @requires chalk
12
+ * @requires path
13
+ */
14
+
15
+ const PRDService = require('../../services/PRDService');
16
+ const fs = require('fs-extra');
17
+ const ora = require('ora');
18
+ const chalk = require('chalk');
19
+ const path = require('path');
20
+
21
+ /**
22
+ * Get PRD file path
23
+ * @param {string} name - PRD name
24
+ * @returns {string} Full path to PRD file
25
+ */
26
+ function getPrdPath(name) {
27
+ return path.join(process.cwd(), '.claude', 'prds', `${name}.md`);
28
+ }
29
+
30
+ /**
31
+ * Read PRD file
32
+ * @param {string} name - PRD name
33
+ * @returns {Promise<string>} PRD content
34
+ * @throws {Error} If file doesn't exist or can't be read
35
+ */
36
+ async function readPrdFile(name) {
37
+ const prdPath = getPrdPath(name);
38
+
39
+ // Check if file exists
40
+ const exists = await fs.pathExists(prdPath);
41
+ if (!exists) {
42
+ throw new Error(`PRD file not found: ${prdPath}`);
43
+ }
44
+
45
+ // Read file content
46
+ return await fs.readFile(prdPath, 'utf8');
47
+ }
48
+
49
+ /**
50
+ * Parse PRD with AI
51
+ * @param {Object} argv - Command arguments
52
+ */
53
+ async function prdParse(argv) {
54
+ const spinner = ora(`Parsing PRD: ${argv.name}`).start();
55
+ const prdService = new PRDService();
56
+
57
+ try {
58
+ const content = await readPrdFile(argv.name);
59
+
60
+ if (argv.stream) {
61
+ // Streaming mode
62
+ spinner.text = 'Streaming PRD analysis...';
63
+
64
+ for await (const chunk of prdService.parseStream(content)) {
65
+ process.stdout.write(chunk);
66
+ }
67
+
68
+ spinner.succeed(chalk.green('PRD parsed successfully'));
69
+ } else {
70
+ // Non-streaming mode
71
+ const result = await prdService.parse(content);
72
+
73
+ spinner.succeed(chalk.green('PRD parsed successfully'));
74
+
75
+ if (result.epics && result.epics.length > 0) {
76
+ console.log(chalk.green(`\nFound ${result.epics.length} epic(s):`));
77
+ result.epics.forEach(epic => {
78
+ console.log(` - ${epic.id}: ${epic.title}`);
79
+ });
80
+ }
81
+ }
82
+ } catch (error) {
83
+ spinner.fail(chalk.red('Failed to parse PRD'));
84
+
85
+ if (error.message.includes('not found')) {
86
+ console.error(chalk.red(`\nError: ${error.message}`));
87
+ } else if (error.message.includes('Failed to read')) {
88
+ console.error(chalk.red(`\nError: ${error.message}`));
89
+ } else {
90
+ console.error(chalk.red(`\nError: Failed to parse PRD: ${error.message}`));
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Extract epics from PRD
97
+ * @param {Object} argv - Command arguments
98
+ */
99
+ async function prdExtractEpics(argv) {
100
+ const spinner = ora(`Extracting epics from: ${argv.name}`).start();
101
+ const prdService = new PRDService();
102
+
103
+ try {
104
+ const content = await readPrdFile(argv.name);
105
+
106
+ if (argv.stream) {
107
+ // Streaming mode
108
+ spinner.text = 'Streaming epic extraction...';
109
+
110
+ for await (const chunk of prdService.extractEpicsStream(content)) {
111
+ process.stdout.write(chunk);
112
+ }
113
+
114
+ spinner.succeed(chalk.green('Epics extracted successfully'));
115
+ } else {
116
+ // Non-streaming mode
117
+ const epics = await prdService.extractEpics(content);
118
+
119
+ spinner.succeed(chalk.green(`Found ${epics.length} epics`));
120
+
121
+ console.log(chalk.green(`\nExtracted ${epics.length} epic(s):`));
122
+ epics.forEach((epic, index) => {
123
+ console.log(`\n${index + 1}. ${epic.title || epic.id}`);
124
+ if (epic.description) {
125
+ console.log(` ${epic.description}`);
126
+ }
127
+ });
128
+ }
129
+ } catch (error) {
130
+ spinner.fail(chalk.red('Failed to extract epics'));
131
+
132
+ if (error.message.includes('not found')) {
133
+ console.error(chalk.red(`\nError: ${error.message}`));
134
+ } else if (error.message.includes('Failed to read')) {
135
+ console.error(chalk.red(`\nError: ${error.message}`));
136
+ } else {
137
+ console.error(chalk.red(`\nError: Failed to extract epics: ${error.message}`));
138
+ }
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Generate PRD summary
144
+ * @param {Object} argv - Command arguments
145
+ */
146
+ async function prdSummarize(argv) {
147
+ const spinner = ora(`Generating summary for: ${argv.name}`).start();
148
+ const prdService = new PRDService();
149
+
150
+ try {
151
+ const content = await readPrdFile(argv.name);
152
+
153
+ if (argv.stream) {
154
+ // Streaming mode
155
+ spinner.text = 'Streaming summary generation...';
156
+
157
+ for await (const chunk of prdService.summarizeStream(content)) {
158
+ process.stdout.write(chunk);
159
+ }
160
+
161
+ spinner.succeed(chalk.green('Summary generated successfully'));
162
+ } else {
163
+ // Non-streaming mode
164
+ const summary = await prdService.summarize(content);
165
+
166
+ spinner.succeed(chalk.green('Summary generated successfully'));
167
+
168
+ console.log(chalk.green('\nPRD Summary:'));
169
+ console.log(summary);
170
+ }
171
+ } catch (error) {
172
+ spinner.fail(chalk.red('Failed to generate summary'));
173
+
174
+ if (error.message.includes('not found')) {
175
+ console.error(chalk.red(`\nError: ${error.message}`));
176
+ } else if (error.message.includes('Failed to read')) {
177
+ console.error(chalk.red(`\nError: ${error.message}`));
178
+ } else {
179
+ console.error(chalk.red(`\nError: Failed to generate summary: ${error.message}`));
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Validate PRD structure
186
+ * @param {Object} argv - Command arguments
187
+ */
188
+ async function prdValidate(argv) {
189
+ const spinner = ora(`Validating PRD: ${argv.name}`).start();
190
+ const prdService = new PRDService();
191
+
192
+ try {
193
+ const content = await readPrdFile(argv.name);
194
+ const result = await prdService.validate(content);
195
+
196
+ if (result.valid) {
197
+ spinner.succeed(chalk.green('PRD is valid'));
198
+ console.log(chalk.green('\nValidation passed - PRD structure is correct'));
199
+ } else {
200
+ spinner.fail(chalk.red(`PRD validation failed - ${result.issues.length} issues found`));
201
+ console.error(chalk.red(`\nValidation failed - ${result.issues.length} issue(s):`));
202
+ result.issues.forEach((issue, index) => {
203
+ console.error(chalk.red(` ${index + 1}. ${issue}`));
204
+ });
205
+ }
206
+ } catch (error) {
207
+ spinner.fail(chalk.red('Failed to validate PRD'));
208
+
209
+ if (error.message.includes('not found')) {
210
+ console.error(chalk.red(`\nError: ${error.message}`));
211
+ } else if (error.message.includes('Failed to read')) {
212
+ console.error(chalk.red(`\nError: ${error.message}`));
213
+ } else {
214
+ console.error(chalk.red(`\nError: Failed to validate PRD: ${error.message}`));
215
+ }
216
+ }
217
+ }
218
+
219
+ /**
220
+ * Main command handler
221
+ * @param {Object} argv - Command arguments
222
+ */
223
+ async function handler(argv) {
224
+ // Validate action
225
+ const validActions = ['parse', 'extract-epics', 'summarize', 'validate'];
226
+
227
+ if (!validActions.includes(argv.action)) {
228
+ console.error(chalk.red(`\nError: Unknown action: ${argv.action}`));
229
+ console.error(chalk.yellow(`Valid actions: ${validActions.join(', ')}`));
230
+ return;
231
+ }
232
+
233
+ // Route to appropriate handler
234
+ try {
235
+ switch (argv.action) {
236
+ case 'parse':
237
+ await prdParse(argv);
238
+ break;
239
+ case 'extract-epics':
240
+ await prdExtractEpics(argv);
241
+ break;
242
+ case 'summarize':
243
+ await prdSummarize(argv);
244
+ break;
245
+ case 'validate':
246
+ await prdValidate(argv);
247
+ break;
248
+ }
249
+ } catch (error) {
250
+ // Global error handler for unexpected errors
251
+ console.error(chalk.red(`\nUnexpected error: ${error.message}`));
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Command builder - registers all subcommands
257
+ * @param {Object} yargs - Yargs instance
258
+ * @returns {Object} Configured yargs instance
259
+ */
260
+ function builder(yargs) {
261
+ return yargs
262
+ .command(
263
+ 'parse <name>',
264
+ 'Parse PRD with AI analysis',
265
+ (yargs) => {
266
+ return yargs
267
+ .positional('name', {
268
+ describe: 'PRD name (without .md extension)',
269
+ type: 'string'
270
+ })
271
+ .option('stream', {
272
+ describe: 'Use streaming mode',
273
+ type: 'boolean',
274
+ default: false
275
+ })
276
+ .option('ai', {
277
+ describe: 'Use AI for parsing',
278
+ type: 'boolean',
279
+ default: true
280
+ });
281
+ }
282
+ )
283
+ .command(
284
+ 'extract-epics <name>',
285
+ 'Extract epics from PRD',
286
+ (yargs) => {
287
+ return yargs
288
+ .positional('name', {
289
+ describe: 'PRD name (without .md extension)',
290
+ type: 'string'
291
+ })
292
+ .option('stream', {
293
+ describe: 'Use streaming mode',
294
+ type: 'boolean',
295
+ default: false
296
+ });
297
+ }
298
+ )
299
+ .command(
300
+ 'summarize <name>',
301
+ 'Generate PRD summary',
302
+ (yargs) => {
303
+ return yargs
304
+ .positional('name', {
305
+ describe: 'PRD name (without .md extension)',
306
+ type: 'string'
307
+ })
308
+ .option('stream', {
309
+ describe: 'Use streaming mode',
310
+ type: 'boolean',
311
+ default: false
312
+ });
313
+ }
314
+ )
315
+ .command(
316
+ 'validate <name>',
317
+ 'Validate PRD structure',
318
+ (yargs) => {
319
+ return yargs
320
+ .positional('name', {
321
+ describe: 'PRD name (without .md extension)',
322
+ type: 'string'
323
+ });
324
+ }
325
+ )
326
+ .demandCommand(1, 'You must specify a PRD action')
327
+ .strictCommands()
328
+ .help();
329
+ }
330
+
331
+ /**
332
+ * Command export
333
+ */
334
+ module.exports = {
335
+ command: 'prd <action> [name]',
336
+ describe: 'Manage PRD (Product Requirements Documents)',
337
+ builder,
338
+ handler,
339
+ handlers: {
340
+ parse: prdParse,
341
+ extractEpics: prdExtractEpics,
342
+ summarize: prdSummarize,
343
+ validate: prdValidate
344
+ }
345
+ };
@@ -0,0 +1,206 @@
1
+ /**
2
+ * CLI Task Commands
3
+ *
4
+ * Provides task management commands for epic-based workflows.
5
+ * Implements subcommands for list and prioritize operations.
6
+ *
7
+ * @module cli/commands/task
8
+ * @requires ../../services/TaskService
9
+ * @requires fs-extra
10
+ * @requires ora
11
+ * @requires chalk
12
+ * @requires path
13
+ */
14
+
15
+ const TaskService = require('../../services/TaskService');
16
+ const PRDService = require('../../services/PRDService');
17
+ const fs = require('fs-extra');
18
+ const ora = require('ora');
19
+ const chalk = require('chalk');
20
+ const path = require('path');
21
+
22
+ /**
23
+ * Get epic file path
24
+ * @param {string} name - Epic name
25
+ * @returns {string} Full path to epic file
26
+ */
27
+ function getEpicPath(name) {
28
+ return path.join(process.cwd(), '.claude', 'epics', `${name}.md`);
29
+ }
30
+
31
+ /**
32
+ * Read epic file
33
+ * @param {string} name - Epic name
34
+ * @returns {Promise<string>} Epic content
35
+ * @throws {Error} If file doesn't exist or can't be read
36
+ */
37
+ async function readEpicFile(name) {
38
+ const epicPath = getEpicPath(name);
39
+
40
+ // Check if file exists
41
+ const exists = await fs.pathExists(epicPath);
42
+ if (!exists) {
43
+ throw new Error(`Epic file not found: ${epicPath}`);
44
+ }
45
+
46
+ // Read file content
47
+ return await fs.readFile(epicPath, 'utf8');
48
+ }
49
+
50
+ /**
51
+ * List all tasks in epic
52
+ * @param {Object} argv - Command arguments
53
+ */
54
+ async function taskList(argv) {
55
+ const spinner = ora(`Loading tasks from epic: ${argv.epic}`).start();
56
+
57
+ try {
58
+ const content = await readEpicFile(argv.epic);
59
+
60
+ // Initialize services
61
+ const prdService = new PRDService();
62
+ const taskService = new TaskService({ prdService });
63
+
64
+ // Get tasks from epic content
65
+ const tasks = taskService.getTasks(content);
66
+
67
+ spinner.succeed(chalk.green(`Found ${tasks.length} tasks`));
68
+
69
+ // Display task list
70
+ if (tasks.length > 0) {
71
+ console.log(chalk.green(`\n${tasks.length} tasks in epic '${argv.epic}':`));
72
+ tasks.forEach((task, index) => {
73
+ const statusIcon = task.status === 'completed' ? '✓' :
74
+ task.status === 'in-progress' ? '→' :
75
+ task.status === 'blocked' ? '✗' : '○';
76
+ console.log(` ${statusIcon} ${task.id || `#${index + 1}`}: ${task.title} [${task.status}]`);
77
+ });
78
+ } else {
79
+ console.log(chalk.yellow('\nNo tasks found in this epic.'));
80
+ }
81
+ } catch (error) {
82
+ spinner.fail(chalk.red('Failed to list tasks'));
83
+
84
+ if (error.message.includes('not found')) {
85
+ console.error(chalk.red(`\nError: ${error.message}`));
86
+ } else {
87
+ console.error(chalk.red(`\nError: Failed to list tasks: ${error.message}`));
88
+ }
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Prioritize tasks in epic
94
+ * @param {Object} argv - Command arguments
95
+ */
96
+ async function taskPrioritize(argv) {
97
+ const spinner = ora(`Prioritizing tasks in epic: ${argv.epic}`).start();
98
+
99
+ try {
100
+ const content = await readEpicFile(argv.epic);
101
+
102
+ // Initialize services
103
+ const prdService = new PRDService();
104
+ const taskService = new TaskService({ prdService });
105
+
106
+ // Prioritize tasks
107
+ const prioritizedTasks = await taskService.prioritize(content);
108
+
109
+ spinner.succeed(chalk.green('Tasks prioritized successfully'));
110
+
111
+ // Display prioritized tasks
112
+ if (prioritizedTasks.length > 0) {
113
+ console.log(chalk.green(`\nPrioritized ${prioritizedTasks.length} tasks:`));
114
+ prioritizedTasks.forEach((task, index) => {
115
+ const priorityLabel = task.priority || 'P2';
116
+ console.log(` ${index + 1}. [${priorityLabel}] ${task.id}: ${task.title || 'Untitled'}`);
117
+ });
118
+ }
119
+ } catch (error) {
120
+ spinner.fail(chalk.red('Failed to prioritize tasks'));
121
+
122
+ if (error.message.includes('not found')) {
123
+ console.error(chalk.red(`\nError: ${error.message}`));
124
+ } else {
125
+ console.error(chalk.red(`\nError: Failed to prioritize tasks: ${error.message}`));
126
+ }
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Main command handler
132
+ * @param {Object} argv - Command arguments
133
+ */
134
+ async function handler(argv) {
135
+ // Validate action
136
+ const validActions = ['list', 'prioritize'];
137
+
138
+ if (!validActions.includes(argv.action)) {
139
+ console.error(chalk.red(`\nError: Unknown action: ${argv.action}`));
140
+ console.error(chalk.yellow(`Valid actions: ${validActions.join(', ')}`));
141
+ return;
142
+ }
143
+
144
+ // Route to appropriate handler
145
+ try {
146
+ switch (argv.action) {
147
+ case 'list':
148
+ await taskList(argv);
149
+ break;
150
+ case 'prioritize':
151
+ await taskPrioritize(argv);
152
+ break;
153
+ }
154
+ } catch (error) {
155
+ // Global error handler for unexpected errors
156
+ console.error(chalk.red(`\nUnexpected error: ${error.message}`));
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Command builder - registers all subcommands
162
+ * @param {Object} yargs - Yargs instance
163
+ * @returns {Object} Configured yargs instance
164
+ */
165
+ function builder(yargs) {
166
+ return yargs
167
+ .command(
168
+ 'list <epic>',
169
+ 'List all tasks in epic',
170
+ (yargs) => {
171
+ return yargs
172
+ .positional('epic', {
173
+ describe: 'Epic name (without .md extension)',
174
+ type: 'string'
175
+ });
176
+ }
177
+ )
178
+ .command(
179
+ 'prioritize <epic>',
180
+ 'Prioritize tasks in epic',
181
+ (yargs) => {
182
+ return yargs
183
+ .positional('epic', {
184
+ describe: 'Epic name (without .md extension)',
185
+ type: 'string'
186
+ });
187
+ }
188
+ )
189
+ .demandCommand(1, 'You must specify a task action')
190
+ .strictCommands()
191
+ .help();
192
+ }
193
+
194
+ /**
195
+ * Command export
196
+ */
197
+ module.exports = {
198
+ command: 'task <action>',
199
+ describe: 'Manage tasks in epics',
200
+ builder,
201
+ handler,
202
+ handlers: {
203
+ list: taskList,
204
+ prioritize: taskPrioritize
205
+ }
206
+ };