claude-autopm 2.5.0 → 2.7.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/bin/autopm.js +4 -0
- package/lib/cli/commands/context.js +477 -0
- package/lib/cli/commands/pm.js +827 -0
- package/lib/services/ContextService.js +595 -0
- package/lib/services/UtilityService.js +847 -0
- package/lib/services/WorkflowService.js +677 -0
- package/package.json +1 -1
|
@@ -0,0 +1,827 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI PM (Project Management) Commands
|
|
3
|
+
*
|
|
4
|
+
* Provides workflow and project management commands for ClaudeAutoPM.
|
|
5
|
+
* Implements subcommands for workflow analysis and task prioritization.
|
|
6
|
+
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* - next: Get next priority task based on dependencies and priorities
|
|
9
|
+
* - what-next: AI-powered suggestions for next steps
|
|
10
|
+
* - standup: Generate daily standup report
|
|
11
|
+
* - status: Project status overview and health
|
|
12
|
+
* - in-progress: Show all active tasks
|
|
13
|
+
* - blocked: Show all blocked tasks
|
|
14
|
+
*
|
|
15
|
+
* @module cli/commands/pm
|
|
16
|
+
* @requires ../../services/WorkflowService
|
|
17
|
+
* @requires ../../services/IssueService
|
|
18
|
+
* @requires ../../services/EpicService
|
|
19
|
+
* @requires fs-extra
|
|
20
|
+
* @requires ora
|
|
21
|
+
* @requires chalk
|
|
22
|
+
* @requires path
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const WorkflowService = require('../../services/WorkflowService');
|
|
26
|
+
const IssueService = require('../../services/IssueService');
|
|
27
|
+
const EpicService = require('../../services/EpicService');
|
|
28
|
+
const UtilityService = require('../../services/UtilityService');
|
|
29
|
+
const fs = require('fs-extra');
|
|
30
|
+
const ora = require('ora');
|
|
31
|
+
const chalk = require('chalk');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get service instances
|
|
36
|
+
* @returns {Object} Service instances
|
|
37
|
+
*/
|
|
38
|
+
async function getServices() {
|
|
39
|
+
const issueService = new IssueService();
|
|
40
|
+
const epicService = new EpicService();
|
|
41
|
+
const workflowService = new WorkflowService({
|
|
42
|
+
issueService,
|
|
43
|
+
epicService
|
|
44
|
+
});
|
|
45
|
+
return { workflowService, issueService, epicService };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* PM Next - Get next priority task
|
|
50
|
+
* @param {Object} argv - Command arguments
|
|
51
|
+
*/
|
|
52
|
+
async function pmNext(argv) {
|
|
53
|
+
const spinner = ora('Finding next priority task...').start();
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const { workflowService } = await getServices();
|
|
57
|
+
const nextTask = await workflowService.getNextTask();
|
|
58
|
+
|
|
59
|
+
if (!nextTask) {
|
|
60
|
+
spinner.info(chalk.yellow('No available tasks found'));
|
|
61
|
+
|
|
62
|
+
console.log(chalk.yellow('\n⚠️ No tasks available to start\n'));
|
|
63
|
+
console.log(chalk.bold('Possible reasons:'));
|
|
64
|
+
console.log(' • All tasks are completed or in-progress');
|
|
65
|
+
console.log(' • Remaining tasks are blocked by dependencies');
|
|
66
|
+
console.log(' • No tasks have been created yet\n');
|
|
67
|
+
|
|
68
|
+
console.log(chalk.bold('💡 Suggestions:'));
|
|
69
|
+
console.log(` ${chalk.cyan('1.')} Check blocked tasks: ${chalk.yellow('autopm pm blocked')}`);
|
|
70
|
+
console.log(` ${chalk.cyan('2.')} Check active work: ${chalk.yellow('autopm pm in-progress')}`);
|
|
71
|
+
console.log(` ${chalk.cyan('3.')} View all tasks: ${chalk.yellow('autopm issue list')}\n`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
spinner.succeed(chalk.green('Found next task'));
|
|
76
|
+
|
|
77
|
+
// Display task with reasoning
|
|
78
|
+
console.log(chalk.cyan('\n📋 Next Task:\n'));
|
|
79
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
80
|
+
|
|
81
|
+
console.log(chalk.bold(`#${nextTask.id}: ${nextTask.title}`));
|
|
82
|
+
if (nextTask.epic) {
|
|
83
|
+
console.log(chalk.gray(`Epic: ${nextTask.epic}`));
|
|
84
|
+
}
|
|
85
|
+
console.log(chalk.gray(`Priority: ${nextTask.priority || 'P2'}`));
|
|
86
|
+
if (nextTask.effort) {
|
|
87
|
+
console.log(chalk.gray(`Estimated effort: ${nextTask.effort}`));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(chalk.yellow(`\n💡 Why this task?\n${nextTask.reasoning}`));
|
|
91
|
+
|
|
92
|
+
console.log('\n' + chalk.gray('─'.repeat(60)) + '\n');
|
|
93
|
+
|
|
94
|
+
// TDD Reminder
|
|
95
|
+
console.log(chalk.red('⚠️ TDD REMINDER - Before starting work:\n'));
|
|
96
|
+
console.log(chalk.dim(' 🚨 ALWAYS follow Test-Driven Development:'));
|
|
97
|
+
console.log(chalk.dim(' 1. RED: Write failing test first'));
|
|
98
|
+
console.log(chalk.dim(' 2. GREEN: Write minimal code to pass'));
|
|
99
|
+
console.log(chalk.dim(' 3. REFACTOR: Clean up while keeping tests green\n'));
|
|
100
|
+
|
|
101
|
+
console.log(chalk.green('✅ Ready to start?'));
|
|
102
|
+
console.log(` ${chalk.yellow(`autopm issue start ${nextTask.id}`)}\n`);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
spinner.fail(chalk.red('Failed to find next task'));
|
|
105
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* PM What-Next - AI-powered next step suggestions
|
|
111
|
+
* @param {Object} argv - Command arguments
|
|
112
|
+
*/
|
|
113
|
+
async function pmWhatNext(argv) {
|
|
114
|
+
const spinner = ora('Analyzing project state...').start();
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const { workflowService } = await getServices();
|
|
118
|
+
const result = await workflowService.getWhatNext();
|
|
119
|
+
|
|
120
|
+
spinner.succeed(chalk.green('Analysis complete'));
|
|
121
|
+
|
|
122
|
+
console.log(chalk.cyan('\n🤔 What Should You Work On Next?\n'));
|
|
123
|
+
console.log(chalk.gray('='.repeat(60)) + '\n');
|
|
124
|
+
|
|
125
|
+
// Display project state
|
|
126
|
+
console.log(chalk.bold('📊 Current Project State:\n'));
|
|
127
|
+
console.log(` PRDs: ${result.projectState.prdCount}`);
|
|
128
|
+
console.log(` Epics: ${result.projectState.epicCount}`);
|
|
129
|
+
console.log(` Total Issues: ${result.projectState.issueCount}`);
|
|
130
|
+
console.log(` Open: ${result.projectState.openIssues}`);
|
|
131
|
+
console.log(` In Progress: ${result.projectState.inProgressIssues}`);
|
|
132
|
+
console.log(` Blocked: ${result.projectState.blockedIssues}\n`);
|
|
133
|
+
|
|
134
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
135
|
+
|
|
136
|
+
// Display suggestions
|
|
137
|
+
console.log(chalk.bold('💡 Suggested Next Steps:\n'));
|
|
138
|
+
|
|
139
|
+
result.suggestions.forEach((suggestion, index) => {
|
|
140
|
+
const marker = suggestion.recommended ? '⭐' : '○';
|
|
141
|
+
const priorityColor = suggestion.priority === 'high' ? chalk.red : chalk.yellow;
|
|
142
|
+
|
|
143
|
+
console.log(`${index + 1}. ${marker} ${chalk.bold(suggestion.title)}`);
|
|
144
|
+
console.log(` ${suggestion.description}`);
|
|
145
|
+
console.log(` ${priorityColor(`Priority: ${suggestion.priority.toUpperCase()}`)}`);
|
|
146
|
+
|
|
147
|
+
if (Array.isArray(suggestion.commands)) {
|
|
148
|
+
suggestion.commands.forEach(cmd => {
|
|
149
|
+
console.log(` ${chalk.yellow(cmd)}`);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log(` ${chalk.dim(`💭 ${suggestion.why}`)}\n`);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (result.suggestions.length === 0) {
|
|
157
|
+
console.log(chalk.yellow(' No specific suggestions at this time\n'));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
161
|
+
} catch (error) {
|
|
162
|
+
spinner.fail(chalk.red('Failed to analyze project'));
|
|
163
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* PM Standup - Generate daily standup report
|
|
169
|
+
* @param {Object} argv - Command arguments
|
|
170
|
+
*/
|
|
171
|
+
async function pmStandup(argv) {
|
|
172
|
+
const spinner = ora('Generating standup report...').start();
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const { workflowService } = await getServices();
|
|
176
|
+
const report = await workflowService.generateStandup();
|
|
177
|
+
|
|
178
|
+
spinner.succeed(chalk.green('Standup report generated'));
|
|
179
|
+
|
|
180
|
+
console.log(chalk.cyan(`\n📅 Daily Standup - ${report.date}\n`));
|
|
181
|
+
console.log(chalk.gray('='.repeat(60)) + '\n');
|
|
182
|
+
|
|
183
|
+
// Yesterday
|
|
184
|
+
console.log(chalk.bold('✅ Yesterday (Completed):\n'));
|
|
185
|
+
if (report.yesterday.length > 0) {
|
|
186
|
+
report.yesterday.forEach(task => {
|
|
187
|
+
console.log(` #${task.id} - ${task.title}`);
|
|
188
|
+
if (task.epic) {
|
|
189
|
+
console.log(` ${chalk.gray(`Epic: ${task.epic}`)}`);
|
|
190
|
+
}
|
|
191
|
+
console.log('');
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
console.log(chalk.gray(' No tasks completed yesterday\n'));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
198
|
+
|
|
199
|
+
// Today
|
|
200
|
+
console.log(chalk.bold('🚀 Today (In Progress):\n'));
|
|
201
|
+
if (report.today.length > 0) {
|
|
202
|
+
report.today.forEach(task => {
|
|
203
|
+
console.log(` #${task.id} - ${task.title || 'Unnamed task'}`);
|
|
204
|
+
if (task.stale) {
|
|
205
|
+
console.log(` ${chalk.red('⚠️ STALE (>3 days in progress)')}`);
|
|
206
|
+
}
|
|
207
|
+
console.log('');
|
|
208
|
+
});
|
|
209
|
+
} else {
|
|
210
|
+
console.log(chalk.gray(' No tasks currently in progress\n'));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
214
|
+
|
|
215
|
+
// Blockers
|
|
216
|
+
console.log(chalk.bold('🚫 Blockers:\n'));
|
|
217
|
+
if (report.blockers.length > 0) {
|
|
218
|
+
report.blockers.forEach(task => {
|
|
219
|
+
console.log(` #${task.id} - ${task.title || 'Unnamed task'}`);
|
|
220
|
+
console.log(` ${chalk.red(`Blocked by: ${task.reason}`)}`);
|
|
221
|
+
if (task.daysBlocked) {
|
|
222
|
+
console.log(` ${chalk.gray(`Blocked for: ${task.daysBlocked} days`)}`);
|
|
223
|
+
}
|
|
224
|
+
console.log('');
|
|
225
|
+
});
|
|
226
|
+
} else {
|
|
227
|
+
console.log(chalk.green(' No blockers! 🎉\n'));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
231
|
+
|
|
232
|
+
// Metrics
|
|
233
|
+
console.log(chalk.bold('📊 Metrics:\n'));
|
|
234
|
+
console.log(` Velocity: ${report.velocity} tasks/day (7-day avg)`);
|
|
235
|
+
console.log(` Sprint Progress: ${report.sprintProgress.completed}/${report.sprintProgress.total} (${report.sprintProgress.percentage}%)\n`);
|
|
236
|
+
|
|
237
|
+
console.log(chalk.gray('='.repeat(60)) + '\n');
|
|
238
|
+
} catch (error) {
|
|
239
|
+
spinner.fail(chalk.red('Failed to generate standup'));
|
|
240
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* PM Status - Project status overview
|
|
246
|
+
* @param {Object} argv - Command arguments
|
|
247
|
+
*/
|
|
248
|
+
async function pmStatus(argv) {
|
|
249
|
+
const spinner = ora('Analyzing project status...').start();
|
|
250
|
+
|
|
251
|
+
try {
|
|
252
|
+
const { workflowService } = await getServices();
|
|
253
|
+
const status = await workflowService.getProjectStatus();
|
|
254
|
+
|
|
255
|
+
spinner.succeed(chalk.green('Status analysis complete'));
|
|
256
|
+
|
|
257
|
+
console.log(chalk.cyan('\n📊 Project Status Overview\n'));
|
|
258
|
+
console.log(chalk.gray('='.repeat(60)) + '\n');
|
|
259
|
+
|
|
260
|
+
// Epics
|
|
261
|
+
console.log(chalk.bold('📚 Epics:\n'));
|
|
262
|
+
console.log(` Backlog: ${status.epics.backlog}`);
|
|
263
|
+
console.log(` Planning: ${status.epics.planning}`);
|
|
264
|
+
console.log(` In Progress: ${status.epics.inProgress}`);
|
|
265
|
+
console.log(` Completed: ${status.epics.completed}`);
|
|
266
|
+
console.log(` ${chalk.bold('Total:')} ${status.epics.total}\n`);
|
|
267
|
+
|
|
268
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
269
|
+
|
|
270
|
+
// Issues
|
|
271
|
+
console.log(chalk.bold('📋 Issues:\n'));
|
|
272
|
+
console.log(` Open: ${status.issues.open}`);
|
|
273
|
+
console.log(` In Progress: ${status.issues.inProgress}`);
|
|
274
|
+
console.log(` Blocked: ${chalk.red(status.issues.blocked)}`);
|
|
275
|
+
console.log(` Closed: ${chalk.green(status.issues.closed)}`);
|
|
276
|
+
console.log(` ${chalk.bold('Total:')} ${status.issues.total}\n`);
|
|
277
|
+
|
|
278
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
279
|
+
|
|
280
|
+
// Progress
|
|
281
|
+
console.log(chalk.bold('📈 Progress:\n'));
|
|
282
|
+
console.log(` Overall: ${status.progress.overall}% complete`);
|
|
283
|
+
console.log(` Velocity: ${status.progress.velocity} tasks/day\n`);
|
|
284
|
+
|
|
285
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
286
|
+
|
|
287
|
+
// Health
|
|
288
|
+
const healthColor = status.health === 'ON_TRACK' ? chalk.green : chalk.red;
|
|
289
|
+
console.log(chalk.bold('🎯 Health: ') + healthColor(status.health) + '\n');
|
|
290
|
+
|
|
291
|
+
if (status.recommendations.length > 0) {
|
|
292
|
+
console.log(chalk.bold('💡 Recommendations:\n'));
|
|
293
|
+
status.recommendations.forEach((rec, index) => {
|
|
294
|
+
console.log(` ${index + 1}. ${rec}`);
|
|
295
|
+
});
|
|
296
|
+
console.log('');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
console.log(chalk.gray('='.repeat(60)) + '\n');
|
|
300
|
+
} catch (error) {
|
|
301
|
+
spinner.fail(chalk.red('Failed to analyze status'));
|
|
302
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* PM In-Progress - Show all active tasks
|
|
308
|
+
* @param {Object} argv - Command arguments
|
|
309
|
+
*/
|
|
310
|
+
async function pmInProgress(argv) {
|
|
311
|
+
const spinner = ora('Finding active tasks...').start();
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const { workflowService } = await getServices();
|
|
315
|
+
const tasks = await workflowService.getInProgressTasks();
|
|
316
|
+
|
|
317
|
+
spinner.succeed(chalk.green('Active tasks found'));
|
|
318
|
+
|
|
319
|
+
console.log(chalk.cyan('\n🚀 In Progress Tasks\n'));
|
|
320
|
+
console.log(chalk.gray('='.repeat(60)) + '\n');
|
|
321
|
+
|
|
322
|
+
if (tasks.length === 0) {
|
|
323
|
+
console.log(chalk.yellow('No tasks currently in progress\n'));
|
|
324
|
+
console.log(chalk.bold('💡 Ready to start work?'));
|
|
325
|
+
console.log(` ${chalk.yellow('autopm pm next')}\n`);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Group by epic if available
|
|
330
|
+
const byEpic = {};
|
|
331
|
+
tasks.forEach(task => {
|
|
332
|
+
const epic = task.epic || 'No Epic';
|
|
333
|
+
if (!byEpic[epic]) {
|
|
334
|
+
byEpic[epic] = [];
|
|
335
|
+
}
|
|
336
|
+
byEpic[epic].push(task);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
Object.keys(byEpic).forEach(epicName => {
|
|
340
|
+
console.log(chalk.bold(`Epic: ${epicName}\n`));
|
|
341
|
+
|
|
342
|
+
byEpic[epicName].forEach(task => {
|
|
343
|
+
console.log(` #${task.id} - ${task.title || 'Unnamed task'}`);
|
|
344
|
+
if (task.started) {
|
|
345
|
+
console.log(` ${chalk.gray(`Started: ${new Date(task.started).toLocaleDateString()}`)}`);
|
|
346
|
+
}
|
|
347
|
+
if (task.assignee) {
|
|
348
|
+
console.log(` ${chalk.gray(`Assignee: ${task.assignee}`)}`);
|
|
349
|
+
}
|
|
350
|
+
if (task.stale) {
|
|
351
|
+
console.log(` ${chalk.red('⚠️ STALE (>3 days without update)')}`);
|
|
352
|
+
console.log(` ${chalk.yellow('💡 Consider checking in or splitting task')}`);
|
|
353
|
+
}
|
|
354
|
+
console.log('');
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Summary
|
|
359
|
+
const staleCount = tasks.filter(t => t.stale).length;
|
|
360
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
361
|
+
console.log(chalk.bold('📊 Summary:\n'));
|
|
362
|
+
console.log(` Total active: ${tasks.length} tasks`);
|
|
363
|
+
if (staleCount > 0) {
|
|
364
|
+
console.log(` ${chalk.red(`⚠️ Stale: ${staleCount} tasks (>3 days)`)}`);
|
|
365
|
+
}
|
|
366
|
+
console.log('');
|
|
367
|
+
|
|
368
|
+
console.log(chalk.gray('='.repeat(60)) + '\n');
|
|
369
|
+
} catch (error) {
|
|
370
|
+
spinner.fail(chalk.red('Failed to find active tasks'));
|
|
371
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* PM Blocked - Show all blocked tasks
|
|
377
|
+
* @param {Object} argv - Command arguments
|
|
378
|
+
*/
|
|
379
|
+
async function pmBlocked(argv) {
|
|
380
|
+
const spinner = ora('Finding blocked tasks...').start();
|
|
381
|
+
|
|
382
|
+
try {
|
|
383
|
+
const { workflowService } = await getServices();
|
|
384
|
+
const tasks = await workflowService.getBlockedTasks();
|
|
385
|
+
|
|
386
|
+
spinner.succeed(chalk.green('Blocked tasks analyzed'));
|
|
387
|
+
|
|
388
|
+
console.log(chalk.cyan('\n🚫 Blocked Tasks\n'));
|
|
389
|
+
console.log(chalk.gray('='.repeat(60)) + '\n');
|
|
390
|
+
|
|
391
|
+
if (tasks.length === 0) {
|
|
392
|
+
console.log(chalk.green('✅ No blocked tasks! All tasks are unblocked.\n'));
|
|
393
|
+
console.log(chalk.bold('💡 Ready to work?'));
|
|
394
|
+
console.log(` ${chalk.yellow('autopm pm next')}\n`);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
tasks.forEach((task, index) => {
|
|
399
|
+
console.log(`${index + 1}. ${chalk.bold(`#${task.id} - ${task.title || 'Unnamed task'}`)}`);
|
|
400
|
+
console.log(` ${chalk.red(`Blocked by: ${task.reason}`)}`);
|
|
401
|
+
if (task.daysBlocked !== undefined) {
|
|
402
|
+
const daysLabel = task.daysBlocked === 1 ? 'day' : 'days';
|
|
403
|
+
const daysColor = task.daysBlocked > 3 ? chalk.red : chalk.yellow;
|
|
404
|
+
console.log(` ${daysColor(`Blocked since: ${task.daysBlocked} ${daysLabel} ago`)}`);
|
|
405
|
+
}
|
|
406
|
+
if (task.suggestedAction) {
|
|
407
|
+
console.log(` ${chalk.yellow(`💡 Action: ${task.suggestedAction}`)}`);
|
|
408
|
+
}
|
|
409
|
+
console.log('');
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
// Summary
|
|
413
|
+
const criticalCount = tasks.filter(t => t.daysBlocked > 3).length;
|
|
414
|
+
console.log(chalk.gray('─'.repeat(60)) + '\n');
|
|
415
|
+
console.log(chalk.bold('📊 Summary:\n'));
|
|
416
|
+
console.log(` Total blocked: ${tasks.length} tasks`);
|
|
417
|
+
if (criticalCount > 0) {
|
|
418
|
+
console.log(` ${chalk.red(`🔴 Critical: ${criticalCount} tasks (>3 days)`)}`);
|
|
419
|
+
}
|
|
420
|
+
console.log('');
|
|
421
|
+
|
|
422
|
+
console.log(chalk.bold('💡 Recommendations:\n'));
|
|
423
|
+
console.log(' 1. Unblock critical tasks first (>3 days)');
|
|
424
|
+
console.log(' 2. Review and resolve dependencies');
|
|
425
|
+
console.log(' 3. Update stakeholders on delays\n');
|
|
426
|
+
|
|
427
|
+
console.log(chalk.gray('='.repeat(60)) + '\n');
|
|
428
|
+
} catch (error) {
|
|
429
|
+
spinner.fail(chalk.red('Failed to find blocked tasks'));
|
|
430
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* PM Init - Initialize PM structure
|
|
436
|
+
* @param {Object} argv - Command arguments
|
|
437
|
+
*/
|
|
438
|
+
async function pmInit(argv) {
|
|
439
|
+
const spinner = ora('Initializing project structure...').start();
|
|
440
|
+
try {
|
|
441
|
+
const utilityService = new UtilityService();
|
|
442
|
+
const result = await utilityService.initializeProject({
|
|
443
|
+
force: argv.force,
|
|
444
|
+
template: argv.template
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
spinner.succeed(chalk.green('Project initialized'));
|
|
448
|
+
console.log(chalk.cyan('\n📁 Created:\n'));
|
|
449
|
+
result.created.forEach(item => {
|
|
450
|
+
console.log(chalk.gray(` ✓ ${item}`));
|
|
451
|
+
});
|
|
452
|
+
console.log(chalk.green('\n✅ Ready to use ClaudeAutoPM!'));
|
|
453
|
+
console.log(chalk.gray('Next steps:'));
|
|
454
|
+
console.log(chalk.gray(' 1. autopm config set provider github'));
|
|
455
|
+
console.log(chalk.gray(' 2. autopm prd create my-feature\n'));
|
|
456
|
+
} catch (error) {
|
|
457
|
+
spinner.fail(chalk.red('Failed to initialize'));
|
|
458
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* PM Validate - Validate project structure
|
|
465
|
+
* @param {Object} argv - Command arguments
|
|
466
|
+
*/
|
|
467
|
+
async function pmValidate(argv) {
|
|
468
|
+
const spinner = ora('Validating project...').start();
|
|
469
|
+
try {
|
|
470
|
+
const utilityService = new UtilityService();
|
|
471
|
+
const result = await utilityService.validateProject({
|
|
472
|
+
strict: argv.strict,
|
|
473
|
+
fix: argv.fix
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
if (result.valid) {
|
|
477
|
+
spinner.succeed(chalk.green('Project is valid'));
|
|
478
|
+
} else {
|
|
479
|
+
spinner.warn(chalk.yellow('Issues found'));
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (result.errors.length > 0) {
|
|
483
|
+
console.log(chalk.red('\n❌ Errors:\n'));
|
|
484
|
+
result.errors.forEach(err => console.log(chalk.red(` • ${err}`)));
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
if (result.warnings.length > 0) {
|
|
488
|
+
console.log(chalk.yellow('\n⚠️ Warnings:\n'));
|
|
489
|
+
result.warnings.forEach(warn => console.log(chalk.yellow(` • ${warn}`)));
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (!result.valid) {
|
|
493
|
+
console.log(chalk.cyan('\n💡 Fix issues automatically:'));
|
|
494
|
+
console.log(chalk.gray(' autopm pm validate --fix\n'));
|
|
495
|
+
} else {
|
|
496
|
+
console.log(chalk.green('\n✅ Project structure is valid\n'));
|
|
497
|
+
}
|
|
498
|
+
} catch (error) {
|
|
499
|
+
spinner.fail(chalk.red('Validation failed'));
|
|
500
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
501
|
+
process.exit(1);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* PM Sync - Sync with provider
|
|
507
|
+
* @param {Object} argv - Command arguments
|
|
508
|
+
*/
|
|
509
|
+
async function pmSync(argv) {
|
|
510
|
+
const spinner = ora('Syncing with provider...').start();
|
|
511
|
+
try {
|
|
512
|
+
const utilityService = new UtilityService();
|
|
513
|
+
const result = await utilityService.syncAll({
|
|
514
|
+
type: argv.type || 'all',
|
|
515
|
+
dryRun: argv.dryRun
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
spinner.succeed(chalk.green('Sync complete'));
|
|
519
|
+
console.log(chalk.cyan('\n📊 Sync Results:\n'));
|
|
520
|
+
console.log(chalk.gray(` Epics: ${result.synced.epics || 0}`));
|
|
521
|
+
console.log(chalk.gray(` Issues: ${result.synced.issues || 0}`));
|
|
522
|
+
console.log(chalk.gray(` PRDs: ${result.synced.prds || 0}`));
|
|
523
|
+
|
|
524
|
+
if (result.errors.length > 0) {
|
|
525
|
+
console.log(chalk.red('\n❌ Errors:\n'));
|
|
526
|
+
result.errors.forEach(err => console.log(chalk.red(` • ${err}`)));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
console.log('');
|
|
530
|
+
} catch (error) {
|
|
531
|
+
spinner.fail(chalk.red('Sync failed'));
|
|
532
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* PM Clean - Clean artifacts
|
|
539
|
+
* @param {Object} argv - Command arguments
|
|
540
|
+
*/
|
|
541
|
+
async function pmClean(argv) {
|
|
542
|
+
const spinner = ora('Cleaning artifacts...').start();
|
|
543
|
+
try {
|
|
544
|
+
const utilityService = new UtilityService();
|
|
545
|
+
const result = await utilityService.cleanArtifacts({
|
|
546
|
+
archive: argv.archive,
|
|
547
|
+
dryRun: argv.dryRun
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
spinner.succeed(chalk.green('Cleanup complete'));
|
|
551
|
+
console.log(chalk.cyan('\n🧹 Cleanup Results:\n'));
|
|
552
|
+
console.log(chalk.gray(` Removed: ${result.removed.length} files`));
|
|
553
|
+
console.log(chalk.gray(` Archived: ${result.archived.length} files`));
|
|
554
|
+
|
|
555
|
+
if (argv.dryRun) {
|
|
556
|
+
console.log(chalk.yellow('\n⚠️ Dry run mode - no changes made'));
|
|
557
|
+
console.log(chalk.gray('Remove --dry-run to apply changes\n'));
|
|
558
|
+
} else {
|
|
559
|
+
console.log('');
|
|
560
|
+
}
|
|
561
|
+
} catch (error) {
|
|
562
|
+
spinner.fail(chalk.red('Cleanup failed'));
|
|
563
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
564
|
+
process.exit(1);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* PM Search - Search entities
|
|
570
|
+
* @param {Object} argv - Command arguments
|
|
571
|
+
*/
|
|
572
|
+
async function pmSearch(argv) {
|
|
573
|
+
const spinner = ora('Searching...').start();
|
|
574
|
+
try {
|
|
575
|
+
const utilityService = new UtilityService();
|
|
576
|
+
const result = await utilityService.searchEntities(argv.query, {
|
|
577
|
+
type: argv.type,
|
|
578
|
+
regex: argv.regex,
|
|
579
|
+
status: argv.status
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
spinner.succeed(chalk.green(`Found ${result.results.length} matches`));
|
|
583
|
+
|
|
584
|
+
console.log(chalk.cyan('\n🔍 Search Results:\n'));
|
|
585
|
+
|
|
586
|
+
if (result.results.length === 0) {
|
|
587
|
+
console.log(chalk.gray(' No matches found\n'));
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Group by type
|
|
592
|
+
const grouped = {};
|
|
593
|
+
result.results.forEach(item => {
|
|
594
|
+
if (!grouped[item.type]) grouped[item.type] = [];
|
|
595
|
+
grouped[item.type].push(item);
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
Object.entries(grouped).forEach(([type, items]) => {
|
|
599
|
+
console.log(chalk.bold(`\n${type.toUpperCase()}S:`));
|
|
600
|
+
items.forEach(item => {
|
|
601
|
+
console.log(chalk.gray(` • ${item.id}: ${item.title}`));
|
|
602
|
+
if (item.match) {
|
|
603
|
+
console.log(chalk.yellow(` "${item.match.substring(0, 80)}..."`));
|
|
604
|
+
}
|
|
605
|
+
});
|
|
606
|
+
});
|
|
607
|
+
console.log('');
|
|
608
|
+
} catch (error) {
|
|
609
|
+
spinner.fail(chalk.red('Search failed'));
|
|
610
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* PM Import - Import from external source
|
|
617
|
+
* @param {Object} argv - Command arguments
|
|
618
|
+
*/
|
|
619
|
+
async function pmImport(argv) {
|
|
620
|
+
const spinner = ora('Importing...').start();
|
|
621
|
+
try {
|
|
622
|
+
const utilityService = new UtilityService();
|
|
623
|
+
const result = await utilityService.importFromProvider(argv.source, {
|
|
624
|
+
provider: argv.provider,
|
|
625
|
+
mapping: argv.mapping ? JSON.parse(argv.mapping) : null
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
spinner.succeed(chalk.green('Import complete'));
|
|
629
|
+
console.log(chalk.cyan('\n📥 Import Results:\n'));
|
|
630
|
+
console.log(chalk.gray(` Imported: ${result.imported.length} items`));
|
|
631
|
+
|
|
632
|
+
if (result.errors.length > 0) {
|
|
633
|
+
console.log(chalk.red('\n❌ Errors:\n'));
|
|
634
|
+
result.errors.forEach(err => console.log(chalk.red(` • ${err}`)));
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
console.log('');
|
|
638
|
+
} catch (error) {
|
|
639
|
+
spinner.fail(chalk.red('Import failed'));
|
|
640
|
+
console.error(chalk.red(`\nError: ${error.message}\n`));
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Command builder - registers all subcommands
|
|
647
|
+
* @param {Object} yargs - Yargs instance
|
|
648
|
+
* @returns {Object} Configured yargs instance
|
|
649
|
+
*/
|
|
650
|
+
function builder(yargs) {
|
|
651
|
+
return yargs
|
|
652
|
+
.command(
|
|
653
|
+
'next',
|
|
654
|
+
'Get next priority task',
|
|
655
|
+
(yargs) => {
|
|
656
|
+
return yargs
|
|
657
|
+
.example('autopm pm next', 'Show next priority task to work on');
|
|
658
|
+
},
|
|
659
|
+
pmNext
|
|
660
|
+
)
|
|
661
|
+
.command(
|
|
662
|
+
'what-next',
|
|
663
|
+
'AI-powered next step suggestions',
|
|
664
|
+
(yargs) => {
|
|
665
|
+
return yargs
|
|
666
|
+
.example('autopm pm what-next', 'Get intelligent suggestions for next steps');
|
|
667
|
+
},
|
|
668
|
+
pmWhatNext
|
|
669
|
+
)
|
|
670
|
+
.command(
|
|
671
|
+
'standup',
|
|
672
|
+
'Generate daily standup report',
|
|
673
|
+
(yargs) => {
|
|
674
|
+
return yargs
|
|
675
|
+
.example('autopm pm standup', 'Generate daily standup summary');
|
|
676
|
+
},
|
|
677
|
+
pmStandup
|
|
678
|
+
)
|
|
679
|
+
.command(
|
|
680
|
+
'status',
|
|
681
|
+
'Project status overview',
|
|
682
|
+
(yargs) => {
|
|
683
|
+
return yargs
|
|
684
|
+
.example('autopm pm status', 'Show overall project health and metrics');
|
|
685
|
+
},
|
|
686
|
+
pmStatus
|
|
687
|
+
)
|
|
688
|
+
.command(
|
|
689
|
+
'in-progress',
|
|
690
|
+
'Show all active tasks',
|
|
691
|
+
(yargs) => {
|
|
692
|
+
return yargs
|
|
693
|
+
.example('autopm pm in-progress', 'List all tasks currently being worked on');
|
|
694
|
+
},
|
|
695
|
+
pmInProgress
|
|
696
|
+
)
|
|
697
|
+
.command(
|
|
698
|
+
'blocked',
|
|
699
|
+
'Show all blocked tasks',
|
|
700
|
+
(yargs) => {
|
|
701
|
+
return yargs
|
|
702
|
+
.example('autopm pm blocked', 'List all blocked tasks with reasons');
|
|
703
|
+
},
|
|
704
|
+
pmBlocked
|
|
705
|
+
)
|
|
706
|
+
.command(
|
|
707
|
+
'init',
|
|
708
|
+
'Initialize PM structure',
|
|
709
|
+
(yargs) => {
|
|
710
|
+
return yargs
|
|
711
|
+
.option('force', { type: 'boolean', desc: 'Overwrite existing files' })
|
|
712
|
+
.option('template', { type: 'string', desc: 'Template file to use' })
|
|
713
|
+
.example('autopm pm init', 'Initialize project PM structure')
|
|
714
|
+
.example('autopm pm init --force', 'Reinitialize with overwrite');
|
|
715
|
+
},
|
|
716
|
+
pmInit
|
|
717
|
+
)
|
|
718
|
+
.command(
|
|
719
|
+
'validate',
|
|
720
|
+
'Validate project structure',
|
|
721
|
+
(yargs) => {
|
|
722
|
+
return yargs
|
|
723
|
+
.option('strict', { type: 'boolean', desc: 'Strict validation mode' })
|
|
724
|
+
.option('fix', { type: 'boolean', desc: 'Auto-fix issues' })
|
|
725
|
+
.example('autopm pm validate', 'Validate project structure')
|
|
726
|
+
.example('autopm pm validate --fix', 'Validate and auto-fix issues');
|
|
727
|
+
},
|
|
728
|
+
pmValidate
|
|
729
|
+
)
|
|
730
|
+
.command(
|
|
731
|
+
'sync',
|
|
732
|
+
'Sync with provider',
|
|
733
|
+
(yargs) => {
|
|
734
|
+
return yargs
|
|
735
|
+
.option('type', { type: 'string', choices: ['all', 'epic', 'issue', 'prd'], default: 'all', desc: 'Entity type to sync' })
|
|
736
|
+
.option('dry-run', { type: 'boolean', desc: 'Preview changes without applying' })
|
|
737
|
+
.example('autopm pm sync', 'Sync all entities')
|
|
738
|
+
.example('autopm pm sync --type epic --dry-run', 'Preview epic sync');
|
|
739
|
+
},
|
|
740
|
+
pmSync
|
|
741
|
+
)
|
|
742
|
+
.command(
|
|
743
|
+
'clean',
|
|
744
|
+
'Clean old artifacts',
|
|
745
|
+
(yargs) => {
|
|
746
|
+
return yargs
|
|
747
|
+
.option('archive', { type: 'boolean', default: true, desc: 'Archive before delete' })
|
|
748
|
+
.option('dry-run', { type: 'boolean', desc: 'Preview cleanup' })
|
|
749
|
+
.example('autopm pm clean', 'Clean stale files (with archive)')
|
|
750
|
+
.example('autopm pm clean --dry-run', 'Preview cleanup');
|
|
751
|
+
},
|
|
752
|
+
pmClean
|
|
753
|
+
)
|
|
754
|
+
.command(
|
|
755
|
+
'search <query>',
|
|
756
|
+
'Search entities',
|
|
757
|
+
(yargs) => {
|
|
758
|
+
return yargs
|
|
759
|
+
.positional('query', { type: 'string', describe: 'Search query' })
|
|
760
|
+
.option('type', { type: 'string', choices: ['all', 'epic', 'issue', 'prd'], default: 'all', desc: 'Entity type' })
|
|
761
|
+
.option('regex', { type: 'boolean', desc: 'Use regex pattern' })
|
|
762
|
+
.option('status', { type: 'string', desc: 'Filter by status' })
|
|
763
|
+
.example('autopm pm search "auth"', 'Search for "auth"')
|
|
764
|
+
.example('autopm pm search --regex "user.*api"', 'Regex search');
|
|
765
|
+
},
|
|
766
|
+
pmSearch
|
|
767
|
+
)
|
|
768
|
+
.command(
|
|
769
|
+
'import <source>',
|
|
770
|
+
'Import from external source',
|
|
771
|
+
(yargs) => {
|
|
772
|
+
return yargs
|
|
773
|
+
.positional('source', { type: 'string', describe: 'Source file path' })
|
|
774
|
+
.option('provider', { type: 'string', choices: ['github', 'azure', 'csv', 'json'], default: 'json', desc: 'Source provider' })
|
|
775
|
+
.option('mapping', { type: 'string', desc: 'Field mapping JSON' })
|
|
776
|
+
.example('autopm pm import data.json', 'Import from JSON')
|
|
777
|
+
.example('autopm pm import data.csv --provider csv', 'Import from CSV');
|
|
778
|
+
},
|
|
779
|
+
pmImport
|
|
780
|
+
)
|
|
781
|
+
.demandCommand(1, 'You must specify a pm command')
|
|
782
|
+
.strictCommands()
|
|
783
|
+
.help();
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Command export
|
|
788
|
+
*/
|
|
789
|
+
module.exports = {
|
|
790
|
+
command: 'pm',
|
|
791
|
+
describe: 'Project management and workflow commands',
|
|
792
|
+
builder,
|
|
793
|
+
handler: (argv) => {
|
|
794
|
+
if (!argv._.includes('pm') || argv._.length === 1) {
|
|
795
|
+
console.log(chalk.yellow('\nPlease specify a pm command\n'));
|
|
796
|
+
console.log('Usage: autopm pm <command>\n');
|
|
797
|
+
console.log('Available commands:');
|
|
798
|
+
console.log(' next Get next priority task');
|
|
799
|
+
console.log(' what-next AI-powered next step suggestions');
|
|
800
|
+
console.log(' standup Generate daily standup report');
|
|
801
|
+
console.log(' status Project status overview');
|
|
802
|
+
console.log(' in-progress Show all active tasks');
|
|
803
|
+
console.log(' blocked Show all blocked tasks');
|
|
804
|
+
console.log(' init Initialize PM structure');
|
|
805
|
+
console.log(' validate Validate project structure');
|
|
806
|
+
console.log(' sync Sync with provider');
|
|
807
|
+
console.log(' clean Clean old artifacts');
|
|
808
|
+
console.log(' search Search entities');
|
|
809
|
+
console.log(' import Import from external source');
|
|
810
|
+
console.log('\nUse: autopm pm <command> --help for more info\n');
|
|
811
|
+
}
|
|
812
|
+
},
|
|
813
|
+
handlers: {
|
|
814
|
+
next: pmNext,
|
|
815
|
+
whatNext: pmWhatNext,
|
|
816
|
+
standup: pmStandup,
|
|
817
|
+
status: pmStatus,
|
|
818
|
+
inProgress: pmInProgress,
|
|
819
|
+
blocked: pmBlocked,
|
|
820
|
+
init: pmInit,
|
|
821
|
+
validate: pmValidate,
|
|
822
|
+
sync: pmSync,
|
|
823
|
+
clean: pmClean,
|
|
824
|
+
search: pmSearch,
|
|
825
|
+
import: pmImport
|
|
826
|
+
}
|
|
827
|
+
};
|