claude-autopm 1.15.4 → 1.16.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/autopm/scripts/epic-status.sh +104 -0
- package/autopm/scripts/package.json.template +9 -6
- package/bin/autopm.js +2 -0
- package/bin/commands/epic.js +161 -0
- package/install/install.js +62 -1
- package/package.json +1 -1
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Epic Status Checker
|
|
4
|
+
# Part of ClaudeAutoPM Framework
|
|
5
|
+
# Usage: ./scripts/epic-status.sh [epic-name]
|
|
6
|
+
|
|
7
|
+
EPIC_NAME=${1:-}
|
|
8
|
+
|
|
9
|
+
if [ -z "$EPIC_NAME" ]; then
|
|
10
|
+
echo "Usage: $0 <epic-name>"
|
|
11
|
+
echo ""
|
|
12
|
+
echo "Available epics:"
|
|
13
|
+
if [ -d ".claude/epics" ]; then
|
|
14
|
+
ls -1 .claude/epics/ 2>/dev/null || echo "No epics found"
|
|
15
|
+
else
|
|
16
|
+
echo "No .claude/epics directory found"
|
|
17
|
+
fi
|
|
18
|
+
exit 1
|
|
19
|
+
fi
|
|
20
|
+
|
|
21
|
+
EPIC_DIR=".claude/epics/$EPIC_NAME"
|
|
22
|
+
|
|
23
|
+
if [ ! -d "$EPIC_DIR" ]; then
|
|
24
|
+
echo "Error: Epic '$EPIC_NAME' not found"
|
|
25
|
+
echo ""
|
|
26
|
+
echo "Available epics:"
|
|
27
|
+
ls -1 .claude/epics/ 2>/dev/null
|
|
28
|
+
exit 1
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
echo "Epic: $EPIC_NAME"
|
|
32
|
+
echo "===================="
|
|
33
|
+
echo ""
|
|
34
|
+
|
|
35
|
+
# Count total tasks
|
|
36
|
+
total_tasks=$(find "$EPIC_DIR" -maxdepth 2 -name "[0-9][0-9][0-9].md" 2>/dev/null | wc -l | tr -d ' ')
|
|
37
|
+
|
|
38
|
+
# Count completed tasks (looking for status: completed in frontmatter)
|
|
39
|
+
completed=0
|
|
40
|
+
in_progress=0
|
|
41
|
+
pending=0
|
|
42
|
+
|
|
43
|
+
for task_file in $(find "$EPIC_DIR" -maxdepth 2 -name "[0-9][0-9][0-9].md" 2>/dev/null); do
|
|
44
|
+
if grep -q "^status: completed" "$task_file" 2>/dev/null; then
|
|
45
|
+
completed=$((completed + 1))
|
|
46
|
+
elif grep -q "^status: in-progress\|^status: in_progress" "$task_file" 2>/dev/null; then
|
|
47
|
+
in_progress=$((in_progress + 1))
|
|
48
|
+
else
|
|
49
|
+
pending=$((pending + 1))
|
|
50
|
+
fi
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
# Calculate progress percentage
|
|
54
|
+
if [ "$total_tasks" -gt 0 ]; then
|
|
55
|
+
progress=$((completed * 100 / total_tasks))
|
|
56
|
+
else
|
|
57
|
+
progress=0
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
echo "Total tasks: $total_tasks"
|
|
61
|
+
echo "Completed: $completed ($progress%)"
|
|
62
|
+
echo "In Progress: $in_progress"
|
|
63
|
+
echo "Pending: $pending"
|
|
64
|
+
echo ""
|
|
65
|
+
|
|
66
|
+
# Progress bar
|
|
67
|
+
if [ "$total_tasks" -gt 0 ]; then
|
|
68
|
+
bar_length=50
|
|
69
|
+
filled=$((progress * bar_length / 100))
|
|
70
|
+
empty=$((bar_length - filled))
|
|
71
|
+
|
|
72
|
+
printf "Progress: ["
|
|
73
|
+
printf "%${filled}s" | tr ' ' '='
|
|
74
|
+
printf "%${empty}s" | tr ' ' '-'
|
|
75
|
+
printf "] %d%%\n" "$progress"
|
|
76
|
+
echo ""
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# Show breakdown by sub-epic if they exist
|
|
80
|
+
sub_dirs=$(find "$EPIC_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
|
|
81
|
+
|
|
82
|
+
if [ "$sub_dirs" -gt 0 ]; then
|
|
83
|
+
echo "Sub-Epic Breakdown:"
|
|
84
|
+
echo "-------------------"
|
|
85
|
+
|
|
86
|
+
for sub_dir in "$EPIC_DIR"/*/; do
|
|
87
|
+
if [ -d "$sub_dir" ]; then
|
|
88
|
+
sub_name=$(basename "$sub_dir")
|
|
89
|
+
sub_count=$(find "$sub_dir" -maxdepth 1 -name "[0-9][0-9][0-9].md" 2>/dev/null | wc -l | tr -d ' ')
|
|
90
|
+
|
|
91
|
+
if [ "$sub_count" -gt 0 ]; then
|
|
92
|
+
# Count completed in sub-epic
|
|
93
|
+
sub_completed=0
|
|
94
|
+
for task_file in $(find "$sub_dir" -maxdepth 1 -name "[0-9][0-9][0-9].md" 2>/dev/null); do
|
|
95
|
+
if grep -q "^status: completed" "$task_file" 2>/dev/null; then
|
|
96
|
+
sub_completed=$((sub_completed + 1))
|
|
97
|
+
fi
|
|
98
|
+
done
|
|
99
|
+
|
|
100
|
+
printf " %-30s %3d tasks (%d completed)\n" "$sub_name" "$sub_count" "$sub_completed"
|
|
101
|
+
fi
|
|
102
|
+
fi
|
|
103
|
+
done
|
|
104
|
+
fi
|
|
@@ -1,30 +1,33 @@
|
|
|
1
1
|
{
|
|
2
|
+
"dependencies": {
|
|
3
|
+
"js-yaml": "^4.1.0"
|
|
4
|
+
},
|
|
2
5
|
"scripts": {
|
|
3
6
|
"// Safety Scripts": "Safeguards against bad commits",
|
|
4
7
|
"precommit": "npm test && npm run build && npm run lint",
|
|
5
8
|
"verify": "npm test && npm run build && npm run lint && npm run typecheck",
|
|
6
9
|
"safe-commit": "./scripts/safe-commit.sh",
|
|
7
10
|
"prepush": "npm run verify && npm run test:e2e",
|
|
8
|
-
|
|
11
|
+
|
|
9
12
|
"// Quick Checks": "Quick checks",
|
|
10
13
|
"check": "npm run lint && npm run typecheck",
|
|
11
14
|
"check:all": "npm run verify",
|
|
12
|
-
|
|
15
|
+
|
|
13
16
|
"// Git Helpers": "Git helper commands",
|
|
14
17
|
"commit": "npm run precommit && git add -A && git commit",
|
|
15
18
|
"push:safe": "npm run prepush && git push",
|
|
16
|
-
|
|
19
|
+
|
|
17
20
|
"// CI/CD Simulation": "Local CI/CD simulation",
|
|
18
21
|
"ci:local": "npm ci && npm run verify && npm run test:e2e",
|
|
19
22
|
"ci:validate": "npm run ci:local",
|
|
20
|
-
|
|
23
|
+
|
|
21
24
|
"// Emergency": "Emergency",
|
|
22
25
|
"fix": "npm run lint -- --fix && npm run format",
|
|
23
26
|
"format": "prettier --write .",
|
|
24
27
|
"clean:hooks": "rm -f .git/hooks/pre-* && npm run setup:hooks",
|
|
25
28
|
"setup:hooks": "husky install || cp scripts/hooks/* .git/hooks/"
|
|
26
29
|
},
|
|
27
|
-
|
|
30
|
+
|
|
28
31
|
"husky": {
|
|
29
32
|
"hooks": {
|
|
30
33
|
"pre-commit": "npm run precommit",
|
|
@@ -32,7 +35,7 @@
|
|
|
32
35
|
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
|
|
33
36
|
}
|
|
34
37
|
},
|
|
35
|
-
|
|
38
|
+
|
|
36
39
|
"// Usage examples": [
|
|
37
40
|
"npm run safe-commit 'feat: add new feature'",
|
|
38
41
|
"npm run verify",
|
package/bin/autopm.js
CHANGED
|
@@ -180,6 +180,8 @@ function main() {
|
|
|
180
180
|
.command(require('./commands/config'))
|
|
181
181
|
// MCP management command
|
|
182
182
|
.command(require('./commands/mcp'))
|
|
183
|
+
// Epic management command
|
|
184
|
+
.command(require('./commands/epic'))
|
|
183
185
|
// Validation command
|
|
184
186
|
.command('validate', 'Validate ClaudeAutoPM configuration and setup',
|
|
185
187
|
(yargs) => {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Epic Command for autopm CLI
|
|
3
|
+
* Manages epic status, breakdown, and analysis
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
|
|
10
|
+
module.exports = {
|
|
11
|
+
command: 'epic <action> [name]',
|
|
12
|
+
describe: 'Manage epics and view epic status',
|
|
13
|
+
|
|
14
|
+
builder: (yargs) => {
|
|
15
|
+
return yargs
|
|
16
|
+
.positional('action', {
|
|
17
|
+
describe: 'Epic action to perform',
|
|
18
|
+
type: 'string',
|
|
19
|
+
choices: ['status', 'list', 'breakdown']
|
|
20
|
+
})
|
|
21
|
+
.positional('name', {
|
|
22
|
+
describe: 'Epic name (for status action)',
|
|
23
|
+
type: 'string'
|
|
24
|
+
})
|
|
25
|
+
.option('detailed', {
|
|
26
|
+
alias: 'd',
|
|
27
|
+
describe: 'Show detailed breakdown',
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
default: false
|
|
30
|
+
})
|
|
31
|
+
.example('autopm epic list', 'List all available epics')
|
|
32
|
+
.example('autopm epic status fullstack', 'Show status of fullstack epic')
|
|
33
|
+
.example('autopm epic breakdown fullstack', 'Show detailed task breakdown');
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
handler: async (argv) => {
|
|
37
|
+
const action = argv.action;
|
|
38
|
+
const name = argv.name;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
switch (action) {
|
|
42
|
+
case 'list':
|
|
43
|
+
listEpics();
|
|
44
|
+
break;
|
|
45
|
+
|
|
46
|
+
case 'status':
|
|
47
|
+
if (!name) {
|
|
48
|
+
console.error('Error: Epic name required for status action');
|
|
49
|
+
console.log('Usage: autopm epic status <epic-name>');
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
showEpicStatus(name);
|
|
53
|
+
break;
|
|
54
|
+
|
|
55
|
+
case 'breakdown':
|
|
56
|
+
if (!name) {
|
|
57
|
+
console.error('Error: Epic name required for breakdown action');
|
|
58
|
+
console.log('Usage: autopm epic breakdown <epic-name>');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
showEpicBreakdown(name);
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
default:
|
|
65
|
+
console.error(`Unknown action: ${action}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Error:', error.message);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function listEpics() {
|
|
76
|
+
const epicsDir = path.join(process.cwd(), '.claude', 'epics');
|
|
77
|
+
|
|
78
|
+
if (!fs.existsSync(epicsDir)) {
|
|
79
|
+
console.log('No epics found. Create epics with /pm:epic-split or /pm:epic-decompose');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const epics = fs.readdirSync(epicsDir).filter(f => {
|
|
84
|
+
return fs.statSync(path.join(epicsDir, f)).isDirectory();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (epics.length === 0) {
|
|
88
|
+
console.log('No epics found. Create epics with /pm:epic-split or /pm:epic-decompose');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log('Available Epics:');
|
|
93
|
+
console.log('================\n');
|
|
94
|
+
|
|
95
|
+
epics.forEach(epic => {
|
|
96
|
+
console.log(` • ${epic}`);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
console.log('');
|
|
100
|
+
console.log('Use: autopm epic status <epic-name> to see details');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function showEpicStatus(epicName) {
|
|
104
|
+
const scriptPath = path.join(process.cwd(), 'scripts', 'epic-status.sh');
|
|
105
|
+
|
|
106
|
+
if (!fs.existsSync(scriptPath)) {
|
|
107
|
+
console.error('Error: epic-status.sh script not found');
|
|
108
|
+
console.error('Run: autopm install to get the latest scripts');
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
execSync(`bash "${scriptPath}" "${epicName}"`, {
|
|
114
|
+
stdio: 'inherit',
|
|
115
|
+
cwd: process.cwd()
|
|
116
|
+
});
|
|
117
|
+
} catch (error) {
|
|
118
|
+
process.exit(error.status || 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function showEpicBreakdown(epicName) {
|
|
123
|
+
const epicDir = path.join(process.cwd(), '.claude', 'epics', epicName);
|
|
124
|
+
|
|
125
|
+
if (!fs.existsSync(epicDir)) {
|
|
126
|
+
console.error(`Error: Epic '${epicName}' not found`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
console.log(`Epic Breakdown: ${epicName}`);
|
|
131
|
+
console.log('='.repeat(50));
|
|
132
|
+
console.log('');
|
|
133
|
+
|
|
134
|
+
// Find all task files
|
|
135
|
+
const findTasks = (dir, prefix = '') => {
|
|
136
|
+
const items = fs.readdirSync(dir);
|
|
137
|
+
|
|
138
|
+
items.forEach(item => {
|
|
139
|
+
const itemPath = path.join(dir, item);
|
|
140
|
+
const stat = fs.statSync(itemPath);
|
|
141
|
+
|
|
142
|
+
if (stat.isDirectory()) {
|
|
143
|
+
console.log(`\n${prefix}📁 ${item}`);
|
|
144
|
+
findTasks(itemPath, prefix + ' ');
|
|
145
|
+
} else if (item.match(/^\d{3}\.md$/)) {
|
|
146
|
+
const content = fs.readFileSync(itemPath, 'utf-8');
|
|
147
|
+
const titleMatch = content.match(/^#\s+(.+)$/m);
|
|
148
|
+
const statusMatch = content.match(/^status:\s+(.+)$/m);
|
|
149
|
+
|
|
150
|
+
const title = titleMatch ? titleMatch[1] : item;
|
|
151
|
+
const status = statusMatch ? statusMatch[1] : 'pending';
|
|
152
|
+
|
|
153
|
+
const icon = status === 'completed' ? '✅' : status === 'in-progress' ? '🔄' : '⚪';
|
|
154
|
+
|
|
155
|
+
console.log(`${prefix} ${icon} ${item}: ${title}`);
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
findTasks(epicDir);
|
|
161
|
+
}
|
package/install/install.js
CHANGED
|
@@ -323,7 +323,8 @@ ${this.colors.BOLD}Examples:${this.colors.NC}
|
|
|
323
323
|
'safe-commit.sh',
|
|
324
324
|
'safe-commit.js',
|
|
325
325
|
'setup-hooks.sh',
|
|
326
|
-
'setup-hooks.js'
|
|
326
|
+
'setup-hooks.js',
|
|
327
|
+
'epic-status.sh'
|
|
327
328
|
];
|
|
328
329
|
|
|
329
330
|
for (const script of scripts) {
|
|
@@ -339,6 +340,63 @@ ${this.colors.BOLD}Examples:${this.colors.NC}
|
|
|
339
340
|
this.printSuccess(`Installed ${script}`);
|
|
340
341
|
}
|
|
341
342
|
}
|
|
343
|
+
|
|
344
|
+
// Install package.json if it doesn't exist
|
|
345
|
+
const packageJsonPath = path.join(this.targetDir, 'package.json');
|
|
346
|
+
const packageJsonTemplatePath = path.join(this.autopmDir, 'scripts', 'package.json.template');
|
|
347
|
+
|
|
348
|
+
if (!fs.existsSync(packageJsonPath) && fs.existsSync(packageJsonTemplatePath)) {
|
|
349
|
+
this.printStep('Creating package.json from template...');
|
|
350
|
+
const templateContent = fs.readFileSync(packageJsonTemplatePath, 'utf-8');
|
|
351
|
+
|
|
352
|
+
// Try to get project name from directory
|
|
353
|
+
const projectName = path.basename(this.targetDir);
|
|
354
|
+
|
|
355
|
+
// Parse template and add name field
|
|
356
|
+
const packageJson = JSON.parse(templateContent);
|
|
357
|
+
packageJson.name = projectName;
|
|
358
|
+
packageJson.version = packageJson.version || '1.0.0';
|
|
359
|
+
packageJson.description = packageJson.description || '';
|
|
360
|
+
packageJson.main = packageJson.main || 'index.js';
|
|
361
|
+
packageJson.license = packageJson.license || 'ISC';
|
|
362
|
+
|
|
363
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf-8');
|
|
364
|
+
this.printSuccess('Created package.json');
|
|
365
|
+
} else if (fs.existsSync(packageJsonPath)) {
|
|
366
|
+
this.printInfo('package.json already exists, skipping');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
installDependencies() {
|
|
371
|
+
const packageJsonPath = path.join(this.targetDir, 'package.json');
|
|
372
|
+
|
|
373
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
374
|
+
this.printInfo('No package.json found, skipping dependency installation');
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
this.printStep('Installing npm dependencies...');
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
// Check if package.json has dependencies
|
|
382
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
383
|
+
if (!packageJson.dependencies || Object.keys(packageJson.dependencies).length === 0) {
|
|
384
|
+
this.printInfo('No dependencies to install');
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Run npm install
|
|
389
|
+
execSync('npm install', {
|
|
390
|
+
cwd: this.targetDir,
|
|
391
|
+
encoding: 'utf-8',
|
|
392
|
+
stdio: 'inherit'
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
this.printSuccess('Dependencies installed successfully');
|
|
396
|
+
} catch (error) {
|
|
397
|
+
this.printWarning(`Failed to install dependencies: ${error.message}`);
|
|
398
|
+
this.printInfo('You can manually run: npm install');
|
|
399
|
+
}
|
|
342
400
|
}
|
|
343
401
|
|
|
344
402
|
checkToolAvailability() {
|
|
@@ -903,6 +961,9 @@ See: https://github.com/rafeekpro/ClaudeAutoPM
|
|
|
903
961
|
await this.setupGitHooks();
|
|
904
962
|
}
|
|
905
963
|
|
|
964
|
+
// Install npm dependencies
|
|
965
|
+
this.installDependencies();
|
|
966
|
+
|
|
906
967
|
// Final success message
|
|
907
968
|
console.log('');
|
|
908
969
|
this.printMsg('GREEN', '╔══════════════════════════════════════════╗');
|