claude-autopm 1.24.0 → 1.24.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.
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Epic Sync Tasks - Create GitHub issues for all tasks in an epic
|
|
4
|
+
*
|
|
5
|
+
* Modern Node.js replacement for bash scripts that had parsing issues.
|
|
6
|
+
* Uses simple, testable code instead of complex shell heredocs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { execSync } = require('child_process');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Parse task file frontmatter and content
|
|
15
|
+
*/
|
|
16
|
+
function parseTaskFile(filePath) {
|
|
17
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
18
|
+
const lines = content.split('\n');
|
|
19
|
+
|
|
20
|
+
let inFrontmatter = false;
|
|
21
|
+
let frontmatterLines = [];
|
|
22
|
+
let bodyLines = [];
|
|
23
|
+
let frontmatterCount = 0;
|
|
24
|
+
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
if (line === '---') {
|
|
27
|
+
frontmatterCount++;
|
|
28
|
+
if (frontmatterCount === 1) {
|
|
29
|
+
inFrontmatter = true;
|
|
30
|
+
continue;
|
|
31
|
+
} else if (frontmatterCount === 2) {
|
|
32
|
+
inFrontmatter = false;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (inFrontmatter) {
|
|
38
|
+
frontmatterLines.push(line);
|
|
39
|
+
} else if (frontmatterCount === 2) {
|
|
40
|
+
bodyLines.push(line);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Parse frontmatter
|
|
45
|
+
const frontmatter = {};
|
|
46
|
+
for (const line of frontmatterLines) {
|
|
47
|
+
const match = line.match(/^(\w+):\s*(.+)$/);
|
|
48
|
+
if (match) {
|
|
49
|
+
const [, key, value] = match;
|
|
50
|
+
frontmatter[key] = value;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
frontmatter,
|
|
56
|
+
body: bodyLines.join('\n').trim(),
|
|
57
|
+
title: frontmatter.name || 'Untitled Task'
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create GitHub issue for a task
|
|
63
|
+
*/
|
|
64
|
+
function createTaskIssue(taskData, epicIssueNumber, epicPath, taskNumber) {
|
|
65
|
+
const { title, body } = taskData;
|
|
66
|
+
|
|
67
|
+
// Create issue body with epic reference
|
|
68
|
+
const issueBody = `
|
|
69
|
+
Part of Epic #${epicIssueNumber}
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
${body}
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
**Epic Path:** \`${epicPath}\`
|
|
78
|
+
**Task Number:** ${taskNumber}
|
|
79
|
+
`.trim();
|
|
80
|
+
|
|
81
|
+
// Escape quotes for shell
|
|
82
|
+
const escapedTitle = title.replace(/"/g, '\\"');
|
|
83
|
+
const escapedBody = issueBody.replace(/"/g, '\\"').replace(/`/g, '\\`');
|
|
84
|
+
|
|
85
|
+
// Create GitHub issue
|
|
86
|
+
try {
|
|
87
|
+
const result = execSync(
|
|
88
|
+
`gh issue create --title "${escapedTitle}" --body "${escapedBody}" --label "task"`,
|
|
89
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const issueMatch = result.match(/https:\/\/github\.com\/[^\/]+\/[^\/]+\/issues\/(\d+)/);
|
|
93
|
+
if (issueMatch) {
|
|
94
|
+
return parseInt(issueMatch[1]);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return null;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.error(`❌ Failed to create issue for task ${taskNumber}:`, error.message);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Update task file with GitHub issue number
|
|
106
|
+
*/
|
|
107
|
+
function updateTaskFrontmatter(filePath, issueNumber) {
|
|
108
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
109
|
+
const updatedContent = content.replace(
|
|
110
|
+
/^github:.*$/m,
|
|
111
|
+
`github: "#${issueNumber}"`
|
|
112
|
+
);
|
|
113
|
+
fs.writeFileSync(filePath, updatedContent, 'utf8');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Main function
|
|
118
|
+
*/
|
|
119
|
+
function main() {
|
|
120
|
+
const args = process.argv.slice(2);
|
|
121
|
+
|
|
122
|
+
if (args.length < 2) {
|
|
123
|
+
console.error('Usage: epicSyncTasks.js <epic-path> <epic-issue-number>');
|
|
124
|
+
console.error('Example: epicSyncTasks.js fullstack/01-infrastructure 2');
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const [epicPath, epicIssueNumber] = args;
|
|
129
|
+
const epicDir = path.join(process.cwd(), '.claude/epics', epicPath);
|
|
130
|
+
|
|
131
|
+
if (!fs.existsSync(epicDir)) {
|
|
132
|
+
console.error(`❌ Epic directory not found: ${epicDir}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Find all task files (numbered .md files)
|
|
137
|
+
const taskFiles = fs.readdirSync(epicDir)
|
|
138
|
+
.filter(f => /^\d+\.md$/.test(f))
|
|
139
|
+
.sort();
|
|
140
|
+
|
|
141
|
+
if (taskFiles.length === 0) {
|
|
142
|
+
console.error(`❌ No task files found in ${epicDir}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log(`📋 Creating ${taskFiles.length} task issues for epic #${epicIssueNumber}`);
|
|
147
|
+
console.log(`📂 Epic path: ${epicPath}\n`);
|
|
148
|
+
|
|
149
|
+
let successCount = 0;
|
|
150
|
+
let failCount = 0;
|
|
151
|
+
|
|
152
|
+
for (const taskFile of taskFiles) {
|
|
153
|
+
const taskNumber = taskFile.replace('.md', '');
|
|
154
|
+
const taskPath = path.join(epicDir, taskFile);
|
|
155
|
+
|
|
156
|
+
console.log(`[${successCount + failCount + 1}/${taskFiles.length}] Creating issue for task ${taskNumber}...`);
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const taskData = parseTaskFile(taskPath);
|
|
160
|
+
const issueNumber = createTaskIssue(taskData, epicIssueNumber, epicPath, taskNumber);
|
|
161
|
+
|
|
162
|
+
if (issueNumber) {
|
|
163
|
+
updateTaskFrontmatter(taskPath, issueNumber);
|
|
164
|
+
console.log(` ✅ Created issue #${issueNumber}: ${taskData.title}`);
|
|
165
|
+
successCount++;
|
|
166
|
+
} else {
|
|
167
|
+
console.log(` ⚠️ Issue created but number not found`);
|
|
168
|
+
failCount++;
|
|
169
|
+
}
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error(` ❌ Error: ${error.message}`);
|
|
172
|
+
failCount++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log(`\n📊 Summary:`);
|
|
177
|
+
console.log(` ✅ Success: ${successCount}`);
|
|
178
|
+
console.log(` ❌ Failed: ${failCount}`);
|
|
179
|
+
console.log(` 📝 Total: ${taskFiles.length}`);
|
|
180
|
+
|
|
181
|
+
if (failCount > 0) {
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (require.main === module) {
|
|
187
|
+
main();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = { parseTaskFile, createTaskIssue, updateTaskFrontmatter };
|