claude-autopm 3.0.0 → 3.0.2
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/README.md +7 -2
- package/bin/autopm.js +17 -0
- package/bin/commands/config.js +17 -0
- package/package.json +1 -1
- package/packages/plugin-cloud/package.json +1 -1
- package/packages/plugin-core/package.json +1 -1
- package/packages/plugin-data/package.json +1 -1
- package/packages/plugin-databases/package.json +1 -1
- package/packages/plugin-devops/package.json +1 -1
- package/packages/plugin-ml/package.json +1 -1
- package/packages/plugin-pm/commands/pm:epic-oneshot.md +93 -48
- package/packages/plugin-pm/plugin.json +425 -47
- package/packages/plugin-pm/scripts/pm/epic-oneshot.cjs +345 -0
- package/packages/plugin-testing/package.json +1 -1
- package/scripts/add-wrapper-scripts.js +66 -0
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Epic Oneshot - One-command workflow: Parse PRD → Decompose → Sync
|
|
4
|
+
*
|
|
5
|
+
* Usage: node epic-oneshot.js <feature-name> [options]
|
|
6
|
+
*
|
|
7
|
+
* This script combines three operations:
|
|
8
|
+
* 1. Parse PRD into epic structure
|
|
9
|
+
* 2. Decompose epic into implementation tasks
|
|
10
|
+
* 3. Sync to GitHub/Azure DevOps
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { execSync } = require('child_process');
|
|
16
|
+
|
|
17
|
+
class EpicOneshot {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.scriptsDir = __dirname;
|
|
20
|
+
this.claudeDir = path.join(process.cwd(), '.claude');
|
|
21
|
+
this.prdsDir = path.join(this.claudeDir, 'prds');
|
|
22
|
+
this.epicsDir = path.join(this.claudeDir, 'epics');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
log(message, emoji = '📋') {
|
|
26
|
+
console.log(`${emoji} ${message}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
error(message) {
|
|
30
|
+
console.error(`❌ ${message}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
success(message) {
|
|
34
|
+
console.log(`✅ ${message}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async step1ParsePRD(featureName) {
|
|
38
|
+
this.log('Step 1/3: Parsing PRD into epic...', '🔄');
|
|
39
|
+
console.log(`${'═'.repeat(50)}\n`);
|
|
40
|
+
|
|
41
|
+
const prdParseScript = path.join(this.scriptsDir, 'prd-parse.js');
|
|
42
|
+
|
|
43
|
+
if (!fs.existsSync(prdParseScript)) {
|
|
44
|
+
this.error('PRD parse script not found');
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
execSync(`node "${prdParseScript}" ${featureName}`, {
|
|
50
|
+
stdio: 'inherit',
|
|
51
|
+
cwd: process.cwd()
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Verify epic was created
|
|
55
|
+
const epicFile = path.join(this.epicsDir, featureName, 'epic.md');
|
|
56
|
+
if (fs.existsSync(epicFile)) {
|
|
57
|
+
this.success('Epic created successfully');
|
|
58
|
+
return true;
|
|
59
|
+
} else {
|
|
60
|
+
this.error('Epic file not created');
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
this.error(`Failed to parse PRD: ${error.message}`);
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async step2DecomposeTasks(featureName) {
|
|
70
|
+
this.log('Step 2/3: Decomposing epic into tasks...', '🔨');
|
|
71
|
+
console.log(`${'═'.repeat(50)}\n`);
|
|
72
|
+
|
|
73
|
+
const epicFile = path.join(this.epicsDir, featureName, 'epic.md');
|
|
74
|
+
|
|
75
|
+
if (!fs.existsSync(epicFile)) {
|
|
76
|
+
this.error('Epic file not found');
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
// Read epic file
|
|
82
|
+
const epicContent = fs.readFileSync(epicFile, 'utf8');
|
|
83
|
+
|
|
84
|
+
// Check if tasks already exist
|
|
85
|
+
if (epicContent.includes('## Tasks') || epicContent.includes('## Implementation Tasks')) {
|
|
86
|
+
this.log('Tasks already exist in epic', 'ℹ️');
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Generate tasks section
|
|
91
|
+
const tasksSection = this.generateTasksFromEpic(epicContent, featureName);
|
|
92
|
+
|
|
93
|
+
// Append tasks to epic
|
|
94
|
+
const updatedContent = epicContent + '\n\n' + tasksSection;
|
|
95
|
+
fs.writeFileSync(epicFile, updatedContent, 'utf8');
|
|
96
|
+
|
|
97
|
+
this.success('Tasks decomposed successfully');
|
|
98
|
+
return true;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.error(`Failed to decompose tasks: ${error.message}`);
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
generateTasksFromEpic(epicContent, featureName) {
|
|
106
|
+
// Extract features and requirements from epic
|
|
107
|
+
const features = this.extractListItems(epicContent, 'Features');
|
|
108
|
+
const requirements = this.extractListItems(epicContent, 'Requirements');
|
|
109
|
+
|
|
110
|
+
let tasks = [];
|
|
111
|
+
let taskId = 1;
|
|
112
|
+
|
|
113
|
+
// Setup task
|
|
114
|
+
tasks.push({
|
|
115
|
+
id: taskId++,
|
|
116
|
+
title: 'Project setup and configuration',
|
|
117
|
+
description: 'Initialize project structure, dependencies, and development environment',
|
|
118
|
+
effort: '2h',
|
|
119
|
+
type: 'setup'
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// Feature implementation tasks
|
|
123
|
+
features.forEach(feature => {
|
|
124
|
+
tasks.push({
|
|
125
|
+
id: taskId++,
|
|
126
|
+
title: `Implement: ${feature}`,
|
|
127
|
+
description: `Implementation for ${feature}`,
|
|
128
|
+
effort: '1d',
|
|
129
|
+
type: 'feature'
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Requirements implementation tasks
|
|
134
|
+
requirements.forEach(req => {
|
|
135
|
+
tasks.push({
|
|
136
|
+
id: taskId++,
|
|
137
|
+
title: `Requirement: ${req}`,
|
|
138
|
+
description: `Implementation for ${req}`,
|
|
139
|
+
effort: '1d',
|
|
140
|
+
type: 'requirement'
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Integration tasks
|
|
145
|
+
tasks.push({
|
|
146
|
+
id: taskId++,
|
|
147
|
+
title: 'Integration and API connections',
|
|
148
|
+
description: 'Connect components and integrate APIs',
|
|
149
|
+
effort: '1d',
|
|
150
|
+
type: 'integration'
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Testing task
|
|
154
|
+
tasks.push({
|
|
155
|
+
id: taskId++,
|
|
156
|
+
title: 'Testing and quality assurance',
|
|
157
|
+
description: 'Write tests, perform QA, and fix bugs',
|
|
158
|
+
effort: '1d',
|
|
159
|
+
type: 'testing'
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Deployment task
|
|
163
|
+
tasks.push({
|
|
164
|
+
id: taskId++,
|
|
165
|
+
title: 'Deployment and documentation',
|
|
166
|
+
description: 'Deploy to production and update documentation',
|
|
167
|
+
effort: '4h',
|
|
168
|
+
type: 'deployment'
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Format tasks as markdown
|
|
172
|
+
let tasksMarkdown = '## Implementation Tasks\n\n';
|
|
173
|
+
tasksMarkdown += '### Task Breakdown\n\n';
|
|
174
|
+
|
|
175
|
+
tasks.forEach(task => {
|
|
176
|
+
tasksMarkdown += `#### TASK-${String(task.id).padStart(3, '0')}: ${task.title}\n\n`;
|
|
177
|
+
tasksMarkdown += `**Type:** ${task.type}\n`;
|
|
178
|
+
tasksMarkdown += `**Effort:** ${task.effort}\n`;
|
|
179
|
+
tasksMarkdown += `**Description:** ${task.description}\n\n`;
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
return tasksMarkdown;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
extractListItems(content, sectionName) {
|
|
186
|
+
const items = [];
|
|
187
|
+
const regex = new RegExp(`## ${sectionName}[\\s\\S]*?(?=##|$)`, 'i');
|
|
188
|
+
const match = content.match(regex);
|
|
189
|
+
|
|
190
|
+
if (match) {
|
|
191
|
+
const section = match[0];
|
|
192
|
+
const lines = section.split('\n');
|
|
193
|
+
|
|
194
|
+
lines.forEach(line => {
|
|
195
|
+
const trimmed = line.trim();
|
|
196
|
+
if (trimmed.match(/^[-*•]\s/) || trimmed.match(/^\d+\.\s/)) {
|
|
197
|
+
// Remove bullet points or numbers
|
|
198
|
+
let item = trimmed.replace(/^[-*•]\s+/, '').trim();
|
|
199
|
+
item = item.replace(/^\d+\.\s+/, '').trim();
|
|
200
|
+
if (item) items.push(item);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return items;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async step3SyncToProvider(featureName) {
|
|
209
|
+
this.log('Step 3/3: Syncing to GitHub/Azure DevOps...', '🔄');
|
|
210
|
+
console.log(`${'═'.repeat(50)}\n`);
|
|
211
|
+
|
|
212
|
+
// Check if sync script exists
|
|
213
|
+
const syncScript = path.join(this.scriptsDir, 'sync.js');
|
|
214
|
+
const epicSyncScript = path.join(this.scriptsDir, 'epic-sync.sh');
|
|
215
|
+
|
|
216
|
+
if (fs.existsSync(epicSyncScript)) {
|
|
217
|
+
try {
|
|
218
|
+
execSync(`bash "${epicSyncScript}" ${featureName}`, {
|
|
219
|
+
stdio: 'inherit',
|
|
220
|
+
cwd: process.cwd()
|
|
221
|
+
});
|
|
222
|
+
this.success('Epic synced to provider');
|
|
223
|
+
return true;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
this.error(`Failed to sync: ${error.message}`);
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
} else if (fs.existsSync(syncScript)) {
|
|
229
|
+
try {
|
|
230
|
+
execSync(`node "${syncScript}" epic ${featureName}`, {
|
|
231
|
+
stdio: 'inherit',
|
|
232
|
+
cwd: process.cwd()
|
|
233
|
+
});
|
|
234
|
+
this.success('Epic synced to provider');
|
|
235
|
+
return true;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
this.error(`Failed to sync: ${error.message}`);
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
} else {
|
|
241
|
+
this.log('Sync script not found - skipping provider sync', '⚠️');
|
|
242
|
+
this.log('Epic is ready locally. Use /pm:epic-sync to sync manually', 'ℹ️');
|
|
243
|
+
return true; // Don't fail if sync not available
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async run(featureName, options = {}) {
|
|
248
|
+
console.log('\n╔════════════════════════════════════════════════╗');
|
|
249
|
+
console.log('║ Epic Oneshot Workflow ║');
|
|
250
|
+
console.log('╚════════════════════════════════════════════════╝\n');
|
|
251
|
+
|
|
252
|
+
this.log(`Feature: ${featureName}`, '🎯');
|
|
253
|
+
console.log('\n');
|
|
254
|
+
|
|
255
|
+
// Validate PRD exists
|
|
256
|
+
const prdFile = path.join(this.prdsDir, `${featureName}.md`);
|
|
257
|
+
if (!fs.existsSync(prdFile)) {
|
|
258
|
+
this.error(`PRD not found: ${featureName}.md`);
|
|
259
|
+
this.log('Create it first with: /pm:prd-new ' + featureName, '💡');
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Step 1: Parse PRD
|
|
264
|
+
const parseSuccess = await this.step1ParsePRD(featureName);
|
|
265
|
+
if (!parseSuccess) {
|
|
266
|
+
this.error('Failed at step 1 (Parse PRD)');
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
console.log('\n');
|
|
270
|
+
|
|
271
|
+
// Step 2: Decompose tasks
|
|
272
|
+
const decomposeSuccess = await this.step2DecomposeTasks(featureName);
|
|
273
|
+
if (!decomposeSuccess) {
|
|
274
|
+
this.error('Failed at step 2 (Decompose tasks)');
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
console.log('\n');
|
|
278
|
+
|
|
279
|
+
// Step 3: Sync to provider (optional)
|
|
280
|
+
if (!options.noSync) {
|
|
281
|
+
await this.step3SyncToProvider(featureName);
|
|
282
|
+
} else {
|
|
283
|
+
this.log('Skipping sync (--no-sync flag)', 'ℹ️');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
console.log('\n╔════════════════════════════════════════════════╗');
|
|
287
|
+
console.log('║ Epic Oneshot Complete! ✨ ║');
|
|
288
|
+
console.log('╚════════════════════════════════════════════════╝\n');
|
|
289
|
+
|
|
290
|
+
this.log('Next steps:', '📋');
|
|
291
|
+
console.log(` • View epic: /pm:epic-show ${featureName}`);
|
|
292
|
+
console.log(` • Get next task: /pm:next`);
|
|
293
|
+
console.log(` • View status: /pm:status`);
|
|
294
|
+
console.log('');
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// CLI execution
|
|
299
|
+
if (require.main === module) {
|
|
300
|
+
const args = process.argv.slice(2);
|
|
301
|
+
|
|
302
|
+
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
303
|
+
console.log(`
|
|
304
|
+
╔════════════════════════════════════════════════╗
|
|
305
|
+
║ Epic Oneshot - Quick Start ║
|
|
306
|
+
╚════════════════════════════════════════════════╝
|
|
307
|
+
|
|
308
|
+
Usage: node epic-oneshot.js <feature-name> [options]
|
|
309
|
+
|
|
310
|
+
Options:
|
|
311
|
+
--no-sync Skip syncing to GitHub/Azure DevOps
|
|
312
|
+
--help Show this help message
|
|
313
|
+
|
|
314
|
+
Example:
|
|
315
|
+
node epic-oneshot.js user-authentication
|
|
316
|
+
node epic-oneshot.js my-feature --no-sync
|
|
317
|
+
|
|
318
|
+
What it does:
|
|
319
|
+
1. 🔄 Parses PRD into epic structure
|
|
320
|
+
2. 🔨 Decomposes epic into implementation tasks
|
|
321
|
+
3. 🔄 Syncs to GitHub/Azure DevOps
|
|
322
|
+
|
|
323
|
+
Prerequisites:
|
|
324
|
+
• PRD must exist in .claude/prds/<feature-name>.md
|
|
325
|
+
• Create with: /pm:prd-new <feature-name>
|
|
326
|
+
`);
|
|
327
|
+
process.exit(0);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const featureName = args[0];
|
|
331
|
+
const options = {
|
|
332
|
+
noSync: args.includes('--no-sync')
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
const oneshot = new EpicOneshot();
|
|
336
|
+
oneshot.run(featureName, options).catch(error => {
|
|
337
|
+
console.error('❌ Fatal error:', error.message);
|
|
338
|
+
if (process.env.DEBUG) {
|
|
339
|
+
console.error(error.stack);
|
|
340
|
+
}
|
|
341
|
+
process.exit(1);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
module.exports = EpicOneshot;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Add missing wrapper scripts to plugin.json
|
|
4
|
+
*
|
|
5
|
+
* This script finds all .sh wrapper scripts and ensures they have
|
|
6
|
+
* corresponding entries in plugin.json as standalone scripts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const pluginJsonPath = path.join(__dirname, '..', 'packages', 'plugin-pm', 'plugin.json');
|
|
13
|
+
const scriptsDir = path.join(__dirname, '..', 'packages', 'plugin-pm', 'scripts', 'pm');
|
|
14
|
+
|
|
15
|
+
// Read plugin.json
|
|
16
|
+
const pluginJson = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
|
|
17
|
+
|
|
18
|
+
// Get all .sh files
|
|
19
|
+
const shellScripts = fs.readdirSync(scriptsDir)
|
|
20
|
+
.filter(file => file.endsWith('.sh'))
|
|
21
|
+
.map(file => path.basename(file, '.sh'));
|
|
22
|
+
|
|
23
|
+
console.log(`Found ${shellScripts.length} shell scripts:`, shellScripts);
|
|
24
|
+
|
|
25
|
+
// Find which ones are missing as standalone entries
|
|
26
|
+
const existingScripts = new Set(
|
|
27
|
+
pluginJson.scripts
|
|
28
|
+
.filter(s => s.file && s.file.includes('.sh'))
|
|
29
|
+
.map(s => path.basename(s.file, '.sh'))
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
console.log(`\nExisting standalone entries:`, Array.from(existingScripts));
|
|
33
|
+
|
|
34
|
+
const missingScripts = shellScripts.filter(name => !existingScripts.has(name));
|
|
35
|
+
|
|
36
|
+
console.log(`\nMissing standalone entries:`, missingScripts);
|
|
37
|
+
|
|
38
|
+
if (missingScripts.length === 0) {
|
|
39
|
+
console.log('\n✅ All wrapper scripts already have standalone entries!');
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Add missing scripts
|
|
44
|
+
const newEntries = missingScripts.map(name => ({
|
|
45
|
+
name: name,
|
|
46
|
+
file: `scripts/pm/${name}.sh`,
|
|
47
|
+
description: `${name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ')} wrapper script`,
|
|
48
|
+
type: 'workflow',
|
|
49
|
+
executable: true,
|
|
50
|
+
category: 'pm-workflow',
|
|
51
|
+
tags: ['pm', name.includes('epic') ? 'epic' : 'workflow', 'automation']
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
console.log(`\n📝 Adding ${newEntries.length} new entries...`);
|
|
55
|
+
|
|
56
|
+
// Insert new entries after existing scripts
|
|
57
|
+
pluginJson.scripts.push(...newEntries);
|
|
58
|
+
|
|
59
|
+
// Write back
|
|
60
|
+
fs.writeFileSync(pluginJsonPath, JSON.stringify(pluginJson, null, 2) + '\n', 'utf8');
|
|
61
|
+
|
|
62
|
+
console.log(`\n✅ Updated plugin.json with ${newEntries.length} new wrapper script entries`);
|
|
63
|
+
console.log(`\nAdded scripts:`);
|
|
64
|
+
newEntries.forEach(entry => {
|
|
65
|
+
console.log(` - ${entry.name} (${entry.file})`);
|
|
66
|
+
});
|