claude-autopm 1.24.2 โ 1.26.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/README.md
CHANGED
|
@@ -328,6 +328,117 @@ claude --dangerously-skip-permissions .
|
|
|
328
328
|
|
|
329
329
|
---
|
|
330
330
|
|
|
331
|
+
## ๐ง Advanced Tools
|
|
332
|
+
|
|
333
|
+
### Epic Sync (JavaScript)
|
|
334
|
+
|
|
335
|
+
Complete epic synchronization workflow in one command:
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
# Full epic sync (create epic + tasks + update references)
|
|
339
|
+
node .claude/lib/commands/pm/epicSync.js sync fullstack/01-infrastructure
|
|
340
|
+
|
|
341
|
+
# Individual operations
|
|
342
|
+
node .claude/lib/commands/pm/epicSync.js create-epic fullstack/01-infrastructure
|
|
343
|
+
node .claude/lib/commands/pm/epicSync.js create-tasks fullstack/01-infrastructure 2
|
|
344
|
+
node .claude/lib/commands/pm/epicSync.js update-epic fullstack/01-infrastructure 2
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Features:**
|
|
348
|
+
- Creates GitHub epic issue with labels and stats
|
|
349
|
+
- Creates task issues for all tasks in epic
|
|
350
|
+
- Updates epic file with GitHub URLs
|
|
351
|
+
- Renames task files to match issue numbers
|
|
352
|
+
- Updates all cross-references automatically
|
|
353
|
+
|
|
354
|
+
### Issue Sync (JavaScript)
|
|
355
|
+
|
|
356
|
+
Synchronize local development progress with GitHub issues:
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
# Full sync workflow
|
|
360
|
+
node .claude/lib/commands/pm/issueSync.js sync 123 .claude/epics/auth/updates/123
|
|
361
|
+
|
|
362
|
+
# Mark task as complete
|
|
363
|
+
node .claude/lib/commands/pm/issueSync.js sync 456 ./updates --complete
|
|
364
|
+
|
|
365
|
+
# Dry run (preview without posting)
|
|
366
|
+
node .claude/lib/commands/pm/issueSync.js sync 789 ./updates --dry-run
|
|
367
|
+
|
|
368
|
+
# Individual operations
|
|
369
|
+
node .claude/lib/commands/pm/issueSync.js gather 123 ./updates
|
|
370
|
+
node .claude/lib/commands/pm/issueSync.js format 123 ./updates
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
**Features:**
|
|
374
|
+
- Gathers updates from multiple sources (progress, notes, commits)
|
|
375
|
+
- Formats professional GitHub comments
|
|
376
|
+
- Posts updates to issues
|
|
377
|
+
- Updates frontmatter with sync timestamps
|
|
378
|
+
- Preflight validation (auth, issue exists, etc.)
|
|
379
|
+
- Supports completion workflow
|
|
380
|
+
|
|
381
|
+
**What gets synced:**
|
|
382
|
+
- Progress updates and completion %
|
|
383
|
+
- Technical notes and decisions
|
|
384
|
+
- Recent commits (auto-detected or manual)
|
|
385
|
+
- Acceptance criteria updates
|
|
386
|
+
- Next steps and blockers
|
|
387
|
+
|
|
388
|
+
### Epic Status (JavaScript)
|
|
389
|
+
|
|
390
|
+
Track epic progress with detailed status reporting:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
# Show epic status
|
|
394
|
+
node .claude/lib/commands/pm/epicStatus.js fullstack/01-infrastructure
|
|
395
|
+
|
|
396
|
+
# List available epics
|
|
397
|
+
node .claude/lib/commands/pm/epicStatus.js
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
**Features:**
|
|
401
|
+
- Counts tasks by status (completed/in-progress/pending)
|
|
402
|
+
- Calculates progress percentage
|
|
403
|
+
- Visual progress bar
|
|
404
|
+
- Sub-epic breakdown
|
|
405
|
+
- Comprehensive status reporting
|
|
406
|
+
|
|
407
|
+
**Example output:**
|
|
408
|
+
```
|
|
409
|
+
Epic: fullstack/01-infrastructure
|
|
410
|
+
==================================
|
|
411
|
+
|
|
412
|
+
Total tasks: 12
|
|
413
|
+
Completed: 8 (67%)
|
|
414
|
+
In Progress: 2
|
|
415
|
+
Pending: 2
|
|
416
|
+
|
|
417
|
+
Progress: [=================================-------------] 67%
|
|
418
|
+
|
|
419
|
+
Sub-Epic Breakdown:
|
|
420
|
+
-------------------
|
|
421
|
+
backend 6 tasks (4 completed)
|
|
422
|
+
frontend 4 tasks (3 completed)
|
|
423
|
+
infrastructure 2 tasks (1 completed)
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Why JavaScript Tools?
|
|
427
|
+
|
|
428
|
+
**Replaced 10 Bash scripts** (~2600 lines) with **3 JavaScript tools** (~1500 lines):
|
|
429
|
+
|
|
430
|
+
**Benefits:**
|
|
431
|
+
- โ
Zero parsing errors (no heredoc/awk/sed complexity)
|
|
432
|
+
- ๐งช Fully testable (all functions exported)
|
|
433
|
+
- ๐ More readable and maintainable
|
|
434
|
+
- ๐ 50% less code
|
|
435
|
+
- ๐พ Better error handling
|
|
436
|
+
- ๐ Easier debugging
|
|
437
|
+
|
|
438
|
+
**Backward compatible:** Old Bash scripts still work, but new JS tools are recommended.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
331
442
|
## ๐ค Contributing
|
|
332
443
|
|
|
333
444
|
We welcome contributions! See [CONTRIBUTING.md](docs/development/contributing.md) for:
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Epic Status - Complete epic progress tracking
|
|
4
|
+
*
|
|
5
|
+
* Replaces epic-status.sh with clean, testable JavaScript
|
|
6
|
+
* - Counts tasks by status (completed/in-progress/pending)
|
|
7
|
+
* - Calculates progress percentage
|
|
8
|
+
* - Shows progress bar visualization
|
|
9
|
+
* - Provides sub-epic breakdown
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parse frontmatter from markdown file
|
|
17
|
+
*/
|
|
18
|
+
function parseFrontmatter(filePath) {
|
|
19
|
+
try {
|
|
20
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
21
|
+
const lines = content.split('\n');
|
|
22
|
+
|
|
23
|
+
let inFrontmatter = false;
|
|
24
|
+
let frontmatterCount = 0;
|
|
25
|
+
const frontmatter = {};
|
|
26
|
+
|
|
27
|
+
for (const line of lines) {
|
|
28
|
+
if (line === '---') {
|
|
29
|
+
frontmatterCount++;
|
|
30
|
+
if (frontmatterCount === 1) {
|
|
31
|
+
inFrontmatter = true;
|
|
32
|
+
continue;
|
|
33
|
+
} else if (frontmatterCount === 2) {
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (inFrontmatter) {
|
|
39
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
40
|
+
if (match) {
|
|
41
|
+
const [, key, value] = match;
|
|
42
|
+
frontmatter[key] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return frontmatter;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Find all task files in directory
|
|
55
|
+
*/
|
|
56
|
+
function findTaskFiles(dir, maxDepth = 2, currentDepth = 0) {
|
|
57
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (currentDepth >= maxDepth) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const files = [];
|
|
66
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
67
|
+
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
const fullPath = path.join(dir, entry.name);
|
|
70
|
+
|
|
71
|
+
if (entry.isFile() && /^\d+\.md$/.test(entry.name)) {
|
|
72
|
+
files.push(fullPath);
|
|
73
|
+
} else if (entry.isDirectory() && currentDepth < maxDepth - 1) {
|
|
74
|
+
files.push(...findTaskFiles(fullPath, maxDepth, currentDepth + 1));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return files;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Count tasks by status
|
|
83
|
+
*/
|
|
84
|
+
function countTasksByStatus(taskFiles) {
|
|
85
|
+
const counts = {
|
|
86
|
+
completed: 0,
|
|
87
|
+
in_progress: 0,
|
|
88
|
+
pending: 0,
|
|
89
|
+
total: taskFiles.length
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
for (const taskFile of taskFiles) {
|
|
93
|
+
const frontmatter = parseFrontmatter(taskFile);
|
|
94
|
+
const status = frontmatter.status || '';
|
|
95
|
+
|
|
96
|
+
if (status === 'completed') {
|
|
97
|
+
counts.completed++;
|
|
98
|
+
} else if (status === 'in-progress' || status === 'in_progress') {
|
|
99
|
+
counts.in_progress++;
|
|
100
|
+
} else {
|
|
101
|
+
counts.pending++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return counts;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Generate progress bar
|
|
110
|
+
*/
|
|
111
|
+
function generateProgressBar(percentage, length = 50) {
|
|
112
|
+
const filled = Math.round((percentage * length) / 100);
|
|
113
|
+
const empty = length - filled;
|
|
114
|
+
|
|
115
|
+
const bar = '='.repeat(filled) + '-'.repeat(empty);
|
|
116
|
+
return `[${bar}] ${percentage}%`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get sub-epic breakdown
|
|
121
|
+
*/
|
|
122
|
+
function getSubEpicBreakdown(epicDir) {
|
|
123
|
+
const breakdown = [];
|
|
124
|
+
|
|
125
|
+
if (!fs.existsSync(epicDir)) {
|
|
126
|
+
return breakdown;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const entries = fs.readdirSync(epicDir, { withFileTypes: true });
|
|
130
|
+
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
if (entry.isDirectory()) {
|
|
133
|
+
const subDir = path.join(epicDir, entry.name);
|
|
134
|
+
const taskFiles = findTaskFiles(subDir, 1);
|
|
135
|
+
|
|
136
|
+
if (taskFiles.length > 0) {
|
|
137
|
+
const counts = countTasksByStatus(taskFiles);
|
|
138
|
+
breakdown.push({
|
|
139
|
+
name: entry.name,
|
|
140
|
+
total: counts.total,
|
|
141
|
+
completed: counts.completed
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return breakdown;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Format epic status report
|
|
152
|
+
*/
|
|
153
|
+
function formatEpicStatus(epicName, epicDir) {
|
|
154
|
+
// Find all tasks
|
|
155
|
+
const taskFiles = findTaskFiles(epicDir);
|
|
156
|
+
const counts = countTasksByStatus(taskFiles);
|
|
157
|
+
|
|
158
|
+
// Calculate progress
|
|
159
|
+
const progress = counts.total > 0
|
|
160
|
+
? Math.round((counts.completed * 100) / counts.total)
|
|
161
|
+
: 0;
|
|
162
|
+
|
|
163
|
+
// Build report
|
|
164
|
+
const lines = [];
|
|
165
|
+
lines.push(`Epic: ${epicName}`);
|
|
166
|
+
lines.push('='.repeat(20 + epicName.length));
|
|
167
|
+
lines.push('');
|
|
168
|
+
lines.push(`Total tasks: ${counts.total}`);
|
|
169
|
+
lines.push(`Completed: ${counts.completed} (${progress}%)`);
|
|
170
|
+
lines.push(`In Progress: ${counts.in_progress}`);
|
|
171
|
+
lines.push(`Pending: ${counts.pending}`);
|
|
172
|
+
lines.push('');
|
|
173
|
+
|
|
174
|
+
// Progress bar
|
|
175
|
+
if (counts.total > 0) {
|
|
176
|
+
lines.push(`Progress: ${generateProgressBar(progress)}`);
|
|
177
|
+
lines.push('');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Sub-epic breakdown
|
|
181
|
+
const breakdown = getSubEpicBreakdown(epicDir);
|
|
182
|
+
if (breakdown.length > 0) {
|
|
183
|
+
lines.push('Sub-Epic Breakdown:');
|
|
184
|
+
lines.push('-'.repeat(19));
|
|
185
|
+
|
|
186
|
+
for (const sub of breakdown) {
|
|
187
|
+
const name = sub.name.padEnd(30);
|
|
188
|
+
lines.push(` ${name} ${sub.total.toString().padStart(3)} tasks (${sub.completed} completed)`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return lines.join('\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* List available epics
|
|
197
|
+
*/
|
|
198
|
+
function listAvailableEpics(epicsDir) {
|
|
199
|
+
if (!fs.existsSync(epicsDir)) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const entries = fs.readdirSync(epicsDir, { withFileTypes: true });
|
|
204
|
+
return entries
|
|
205
|
+
.filter(entry => entry.isDirectory())
|
|
206
|
+
.map(entry => entry.name);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Main function
|
|
211
|
+
*/
|
|
212
|
+
function main() {
|
|
213
|
+
const args = process.argv.slice(2);
|
|
214
|
+
const epicName = args[0];
|
|
215
|
+
|
|
216
|
+
const epicsDir = path.join(process.cwd(), '.claude/epics');
|
|
217
|
+
|
|
218
|
+
if (!epicName) {
|
|
219
|
+
console.log('Usage: epicStatus.js <epic-name>');
|
|
220
|
+
console.log('');
|
|
221
|
+
console.log('Available epics:');
|
|
222
|
+
|
|
223
|
+
const epics = listAvailableEpics(epicsDir);
|
|
224
|
+
if (epics.length > 0) {
|
|
225
|
+
epics.forEach(epic => console.log(` ${epic}`));
|
|
226
|
+
} else {
|
|
227
|
+
console.log(' No epics found');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const epicDir = path.join(epicsDir, epicName);
|
|
234
|
+
|
|
235
|
+
if (!fs.existsSync(epicDir)) {
|
|
236
|
+
console.error(`Error: Epic '${epicName}' not found`);
|
|
237
|
+
console.log('');
|
|
238
|
+
console.log('Available epics:');
|
|
239
|
+
|
|
240
|
+
const epics = listAvailableEpics(epicsDir);
|
|
241
|
+
epics.forEach(epic => console.log(` ${epic}`));
|
|
242
|
+
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Generate and display status
|
|
247
|
+
const status = formatEpicStatus(epicName, epicDir);
|
|
248
|
+
console.log(status);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (require.main === module) {
|
|
252
|
+
main();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
module.exports = {
|
|
256
|
+
parseFrontmatter,
|
|
257
|
+
findTaskFiles,
|
|
258
|
+
countTasksByStatus,
|
|
259
|
+
generateProgressBar,
|
|
260
|
+
getSubEpicBreakdown,
|
|
261
|
+
formatEpicStatus,
|
|
262
|
+
listAvailableEpics
|
|
263
|
+
};
|