claude-autopm 1.26.0 → 1.28.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.
Files changed (49) hide show
  1. package/README.md +40 -0
  2. package/autopm/.claude/agents/frameworks/e2e-test-engineer.md +1 -18
  3. package/autopm/.claude/agents/frameworks/nats-messaging-expert.md +1 -18
  4. package/autopm/.claude/agents/frameworks/react-frontend-engineer.md +1 -18
  5. package/autopm/.claude/agents/frameworks/react-ui-expert.md +1 -18
  6. package/autopm/.claude/agents/frameworks/tailwindcss-expert.md +1 -18
  7. package/autopm/.claude/agents/frameworks/ux-design-expert.md +1 -18
  8. package/autopm/.claude/agents/languages/bash-scripting-expert.md +1 -18
  9. package/autopm/.claude/agents/languages/javascript-frontend-engineer.md +1 -18
  10. package/autopm/.claude/agents/languages/nodejs-backend-engineer.md +1 -18
  11. package/autopm/.claude/agents/languages/python-backend-engineer.md +1 -18
  12. package/autopm/.claude/agents/languages/python-backend-expert.md +1 -18
  13. package/autopm/.claude/commands/pm/epic-decompose.md +19 -5
  14. package/autopm/.claude/commands/pm/prd-new.md +14 -1
  15. package/autopm/.claude/includes/task-creation-excellence.md +18 -0
  16. package/autopm/.claude/lib/ai-task-generator.js +84 -0
  17. package/autopm/.claude/lib/cli-parser.js +148 -0
  18. package/autopm/.claude/lib/dependency-analyzer.js +157 -0
  19. package/autopm/.claude/lib/frontmatter.js +224 -0
  20. package/autopm/.claude/lib/task-utils.js +64 -0
  21. package/autopm/.claude/scripts/pm/prd-new.js +292 -2
  22. package/autopm/.claude/scripts/pm/template-list.js +119 -0
  23. package/autopm/.claude/scripts/pm/template-new.js +344 -0
  24. package/autopm/.claude/scripts/pm-epic-decompose-local.js +158 -0
  25. package/autopm/.claude/scripts/pm-epic-list-local.js +103 -0
  26. package/autopm/.claude/scripts/pm-epic-show-local.js +70 -0
  27. package/autopm/.claude/scripts/pm-epic-update-local.js +56 -0
  28. package/autopm/.claude/scripts/pm-prd-list-local.js +111 -0
  29. package/autopm/.claude/scripts/pm-prd-new-local.js +196 -0
  30. package/autopm/.claude/scripts/pm-prd-parse-local.js +360 -0
  31. package/autopm/.claude/scripts/pm-prd-show-local.js +101 -0
  32. package/autopm/.claude/scripts/pm-prd-update-local.js +153 -0
  33. package/autopm/.claude/scripts/pm-sync-download-local.js +424 -0
  34. package/autopm/.claude/scripts/pm-sync-upload-local.js +473 -0
  35. package/autopm/.claude/scripts/pm-task-list-local.js +86 -0
  36. package/autopm/.claude/scripts/pm-task-show-local.js +92 -0
  37. package/autopm/.claude/scripts/pm-task-update-local.js +109 -0
  38. package/autopm/.claude/scripts/setup-local-mode.js +127 -0
  39. package/autopm/.claude/templates/prds/README.md +334 -0
  40. package/autopm/.claude/templates/prds/api-feature.md +306 -0
  41. package/autopm/.claude/templates/prds/bug-fix.md +413 -0
  42. package/autopm/.claude/templates/prds/data-migration.md +483 -0
  43. package/autopm/.claude/templates/prds/documentation.md +439 -0
  44. package/autopm/.claude/templates/prds/ui-feature.md +365 -0
  45. package/lib/template-engine.js +347 -0
  46. package/package.json +5 -3
  47. package/scripts/create-task-issues.sh +26 -0
  48. package/scripts/fix-invalid-command-refs.sh +4 -3
  49. package/scripts/fix-invalid-refs-simple.sh +8 -3
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Template New - Create custom template
4
+ *
5
+ * Usage:
6
+ * autopm template:new prd my-custom-template
7
+ * autopm template:new epic my-sprint-template
8
+ * autopm template:new task my-task-template
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { execSync } = require('child_process');
14
+
15
+ // Dynamically resolve template engine path
16
+ let TemplateEngine;
17
+ try {
18
+ TemplateEngine = require(path.join(__dirname, '..', '..', '..', '..', 'lib', 'template-engine'));
19
+ } catch (err) {
20
+ try {
21
+ TemplateEngine = require(path.join(process.cwd(), 'lib', 'template-engine'));
22
+ } catch (err2) {
23
+ TemplateEngine = require('../../../lib/template-engine');
24
+ }
25
+ }
26
+
27
+ class TemplateCreator {
28
+ constructor() {
29
+ this.templateEngine = new TemplateEngine();
30
+ }
31
+
32
+ /**
33
+ * Get base template for type
34
+ */
35
+ getBaseTemplate(type) {
36
+ const templates = {
37
+ prd: `---
38
+ id: {{id}}
39
+ title: {{title}}
40
+ type: prd
41
+ status: draft
42
+ priority: {{priority}}
43
+ created: {{timestamp}}
44
+ author: {{author}}
45
+ timeline: {{timeline}}
46
+ ---
47
+
48
+ # PRD: {{title}}
49
+
50
+ ## Executive Summary
51
+
52
+ {{executive_summary}}
53
+
54
+ ## Problem Statement
55
+
56
+ ### Background
57
+ {{problem_background}}
58
+
59
+ ### Current State
60
+ {{current_state}}
61
+
62
+ ### Desired State
63
+ {{desired_state}}
64
+
65
+ ## Target Users
66
+
67
+ {{target_users}}
68
+
69
+ ## Key Features
70
+
71
+ ### Must Have (P0)
72
+ {{#if must_have_features}}
73
+ {{#each must_have_features}}
74
+ - [ ] {{this}}
75
+ {{/each}}
76
+ {{/if}}
77
+
78
+ ### Should Have (P1)
79
+ {{#if should_have_features}}
80
+ {{#each should_have_features}}
81
+ - [ ] {{this}}
82
+ {{/each}}
83
+ {{/if}}
84
+
85
+ ## Success Metrics
86
+
87
+ {{success_metrics}}
88
+
89
+ ## Technical Requirements
90
+
91
+ {{technical_requirements}}
92
+
93
+ ## Implementation Plan
94
+
95
+ ### Phase 1: Design (Week 1)
96
+ - [ ] Requirements finalized
97
+ - [ ] Technical design review
98
+ - [ ] Development environment setup
99
+
100
+ ### Phase 2: Development (Week 2-3)
101
+ - [ ] Implement core features
102
+ - [ ] Write tests (TDD)
103
+ - [ ] Code review
104
+
105
+ ### Phase 3: Testing (Week 4)
106
+ - [ ] Integration testing
107
+ - [ ] User acceptance testing
108
+ - [ ] Performance testing
109
+
110
+ ### Phase 4: Release (Week 5)
111
+ - [ ] Documentation
112
+ - [ ] Deployment
113
+ - [ ] Monitoring setup
114
+
115
+ ## Risks and Mitigation
116
+
117
+ {{risks_and_mitigation}}
118
+
119
+ ## Open Questions
120
+
121
+ - [ ] {{open_question_1}}
122
+ - [ ] {{open_question_2}}
123
+
124
+ ## Appendix
125
+
126
+ ### Changelog
127
+ - {{timestamp}}: Initial PRD created by {{author}}
128
+
129
+ ---
130
+
131
+ *Custom PRD Template*
132
+ `,
133
+ epic: `---
134
+ id: {{id}}
135
+ title: {{title}}
136
+ type: epic
137
+ status: planning
138
+ priority: {{priority}}
139
+ created: {{timestamp}}
140
+ author: {{author}}
141
+ start_date: {{start_date}}
142
+ end_date: {{end_date}}
143
+ ---
144
+
145
+ # Epic: {{title}}
146
+
147
+ ## Overview
148
+
149
+ {{overview}}
150
+
151
+ ## Goals
152
+
153
+ {{#each goals}}
154
+ - {{this}}
155
+ {{/each}}
156
+
157
+ ## User Stories
158
+
159
+ {{#each user_stories}}
160
+ - As a {{role}}, I want to {{action}}, so that {{benefit}}
161
+ {{/each}}
162
+
163
+ ## Tasks
164
+
165
+ {{#each tasks}}
166
+ - [ ] {{this}}
167
+ {{/each}}
168
+
169
+ ## Success Criteria
170
+
171
+ {{success_criteria}}
172
+
173
+ ## Dependencies
174
+
175
+ {{dependencies}}
176
+
177
+ ---
178
+
179
+ *Custom Epic Template*
180
+ `,
181
+ task: `---
182
+ id: {{id}}
183
+ title: {{title}}
184
+ type: task
185
+ status: todo
186
+ priority: {{priority}}
187
+ created: {{timestamp}}
188
+ author: {{author}}
189
+ assigned_to: {{assigned_to}}
190
+ estimated_hours: {{estimated_hours}}
191
+ ---
192
+
193
+ # Task: {{title}}
194
+
195
+ ## Description
196
+
197
+ {{description}}
198
+
199
+ ## Acceptance Criteria
200
+
201
+ {{#each acceptance_criteria}}
202
+ - [ ] {{this}}
203
+ {{/each}}
204
+
205
+ ## Technical Details
206
+
207
+ {{technical_details}}
208
+
209
+ ## Testing
210
+
211
+ {{testing_notes}}
212
+
213
+ ## Notes
214
+
215
+ {{notes}}
216
+
217
+ ---
218
+
219
+ *Custom Task Template*
220
+ `
221
+ };
222
+
223
+ return templates[type] || templates.prd;
224
+ }
225
+
226
+ /**
227
+ * Create new template
228
+ */
229
+ create(type, name) {
230
+ console.log(`\n📝 Creating Custom Template`);
231
+ console.log(`${'═'.repeat(50)}\n`);
232
+
233
+ // Validate type
234
+ const validTypes = ['prd', 'epic', 'task'];
235
+ if (!validTypes.includes(type)) {
236
+ console.error(`❌ Invalid type: ${type}`);
237
+ console.log(`Valid types: ${validTypes.join(', ')}`);
238
+ return false;
239
+ }
240
+
241
+ // Ensure template directory exists
242
+ const typeDir = type === 'prd' ? 'prds' : type === 'epic' ? 'epics' : 'tasks';
243
+ this.templateEngine.ensureTemplateDir(typeDir);
244
+
245
+ const templatePath = path.join('.claude', 'templates', typeDir, `${name}.md`);
246
+
247
+ // Check if template already exists
248
+ if (fs.existsSync(templatePath)) {
249
+ console.error(`❌ Template already exists: ${templatePath}`);
250
+ console.log(`💡 Edit file directly or choose a different name`);
251
+ return false;
252
+ }
253
+
254
+ // Get base template
255
+ const baseTemplate = this.getBaseTemplate(type);
256
+
257
+ // Write template file
258
+ fs.writeFileSync(templatePath, baseTemplate);
259
+
260
+ console.log(`✅ Template created: ${templatePath}`);
261
+ console.log(`\n📋 Template Structure:`);
262
+ console.log(` - Frontmatter: Define metadata variables`);
263
+ console.log(` - Variables: Use {{variable_name}} for substitution`);
264
+ console.log(` - Conditionals: {{#if var}}...{{/if}}`);
265
+ console.log(` - Loops: {{#each items}}...{{/each}}`);
266
+
267
+ console.log(`\n🛠️ Next Steps:`);
268
+ console.log(` 1. Edit template: nano ${templatePath}`);
269
+ console.log(` 2. Add custom variables and sections`);
270
+ console.log(` 3. Test template: autopm ${type}:new --template ${name} "Test"`);
271
+
272
+ // Try to open in editor
273
+ this.openInEditor(templatePath);
274
+
275
+ return true;
276
+ }
277
+
278
+ /**
279
+ * Open template in editor
280
+ */
281
+ openInEditor(templatePath) {
282
+ const editors = ['code', 'nano', 'vim', 'vi'];
283
+
284
+ for (const editor of editors) {
285
+ try {
286
+ // Check if editor exists
287
+ execSync(`which ${editor}`, { stdio: 'ignore' });
288
+
289
+ console.log(`\n📝 Opening in ${editor}...`);
290
+ console.log(` Edit the template, save, and exit`);
291
+
292
+ // Open editor (blocking)
293
+ execSync(`${editor} ${templatePath}`, { stdio: 'inherit' });
294
+
295
+ // Validate template after editing
296
+ const content = fs.readFileSync(templatePath, 'utf8');
297
+ const validation = this.templateEngine.validate(content);
298
+
299
+ if (!validation.valid) {
300
+ console.log(`\n⚠️ Template validation warnings:`);
301
+ validation.errors.forEach(err => console.log(` - ${err}`));
302
+ console.log(`\n💡 These are suggestions. Template will still work.`);
303
+ } else {
304
+ console.log(`\n✅ Template is valid!`);
305
+ }
306
+
307
+ return true;
308
+ } catch (err) {
309
+ // Editor not found, try next
310
+ continue;
311
+ }
312
+ }
313
+
314
+ // No editor found
315
+ console.log(`\n💡 Edit manually: ${templatePath}`);
316
+ return false;
317
+ }
318
+
319
+ run(args) {
320
+ if (args.length < 2) {
321
+ console.error('❌ Usage: autopm template:new <type> <name>');
322
+ console.log('\nExamples:');
323
+ console.log(' autopm template:new prd my-custom-prd');
324
+ console.log(' autopm template:new epic my-sprint');
325
+ console.log(' autopm template:new task my-development-task');
326
+ console.log('\nTypes: prd, epic, task');
327
+ process.exit(1);
328
+ }
329
+
330
+ const type = args[0];
331
+ const name = args[1];
332
+
333
+ const success = this.create(type, name);
334
+ process.exit(success ? 0 : 1);
335
+ }
336
+ }
337
+
338
+ // Main execution
339
+ if (require.main === module) {
340
+ const creator = new TemplateCreator();
341
+ creator.run(process.argv.slice(2));
342
+ }
343
+
344
+ module.exports = TemplateCreator;
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Epic Decomposition - Local Mode
3
+ *
4
+ * AI-powered decomposition of epics into right-sized tasks (4-8h each).
5
+ * Generates task files with frontmatter, dependencies, and acceptance criteria.
6
+ *
7
+ * Usage:
8
+ * const { decomposeLocalEpic } = require('./pm-epic-decompose-local');
9
+ *
10
+ * const result = await decomposeLocalEpic('epic-001', {
11
+ * aiProvider: new OpenAIProvider(),
12
+ * maxTasks: 10
13
+ * });
14
+ */
15
+
16
+ const fs = require('fs').promises;
17
+ const path = require('path');
18
+ const { showLocalEpic } = require('./pm-epic-show-local');
19
+ const { updateLocalEpic } = require('./pm-epic-update-local');
20
+ const { stringifyFrontmatter } = require('../lib/frontmatter');
21
+ const { TaskGenerator } = require('../lib/ai-task-generator');
22
+ const { analyzeDependencies } = require('../lib/dependency-analyzer');
23
+ const { generateTaskId, generateTaskNumber, generateTaskFilename } = require('../lib/task-utils');
24
+
25
+ /**
26
+ * Decompose epic into tasks using AI
27
+ *
28
+ * @param {string} epicId - Epic ID to decompose
29
+ * @param {Object} options - Decomposition options
30
+ * @param {Object} [options.aiProvider] - AI provider instance (for testing)
31
+ * @param {number} [options.maxTasks=15] - Maximum tasks to generate
32
+ * @param {boolean} [options.validateDependencies=false] - Validate dependency graph
33
+ * @returns {Promise<Object>} Decomposition result
34
+ */
35
+ async function decomposeLocalEpic(epicId, options = {}) {
36
+ const {
37
+ aiProvider = null,
38
+ maxTasks = 15,
39
+ validateDependencies = false
40
+ } = options;
41
+
42
+ // 1. Load epic
43
+ const epic = await showLocalEpic(epicId);
44
+ const epicDir = path.dirname(epic.path);
45
+
46
+ // 2. Generate tasks using AI
47
+ const generator = new TaskGenerator(aiProvider);
48
+ const tasks = await generator.generate(epic.body, { maxTasks });
49
+
50
+ // Handle empty response
51
+ if (tasks.length === 0) {
52
+ return {
53
+ epicId,
54
+ tasksCreated: 0,
55
+ warning: 'No tasks generated by AI provider'
56
+ };
57
+ }
58
+
59
+ // 3. Validate dependencies if requested
60
+ if (validateDependencies) {
61
+ const analysis = analyzeDependencies(tasks);
62
+ if (analysis.hasCircularDependencies) {
63
+ throw new Error(
64
+ `Circular dependency detected: ${analysis.cycles.map(c => c.join(' -> ')).join(', ')}`
65
+ );
66
+ }
67
+ }
68
+
69
+ // 4. Create task files
70
+ const taskIds = [];
71
+ for (let i = 0; i < tasks.length; i++) {
72
+ const task = tasks[i];
73
+ const taskId = generateTaskId(epicId, i + 1);
74
+ const taskFilename = generateTaskFilename(i + 1);
75
+ const taskPath = path.join(epicDir, taskFilename);
76
+
77
+ // Build task frontmatter
78
+ const taskFrontmatter = {
79
+ id: taskId,
80
+ epic_id: epicId,
81
+ title: task.title,
82
+ status: 'pending',
83
+ priority: task.priority || 'medium',
84
+ estimated_hours: task.estimated_hours || 4,
85
+ dependencies: task.dependencies || [],
86
+ created: new Date().toISOString().split('T')[0]
87
+ };
88
+
89
+ // Build task body
90
+ const taskBody = buildTaskBody(task);
91
+
92
+ // Write task file
93
+ const taskContent = stringifyFrontmatter(taskFrontmatter, taskBody);
94
+ await fs.writeFile(taskPath, taskContent, 'utf8');
95
+
96
+ taskIds.push(taskId);
97
+ }
98
+
99
+ // 5. Update epic with task count
100
+ await updateLocalEpic(epicId, {
101
+ tasks_total: tasks.length,
102
+ tasks_completed: 0,
103
+ task_ids: taskIds
104
+ });
105
+
106
+ return {
107
+ epicId,
108
+ tasksCreated: tasks.length,
109
+ taskIds
110
+ };
111
+ }
112
+
113
+ /**
114
+ * Build task body content from AI-generated task
115
+ *
116
+ * @param {Object} task - Task object from AI
117
+ * @returns {string} Markdown body content
118
+ */
119
+ function buildTaskBody(task) {
120
+ let body = `# ${task.title}\n\n`;
121
+
122
+ // Description
123
+ if (task.description) {
124
+ body += `## Description\n\n${task.description}\n\n`;
125
+ }
126
+
127
+ // Acceptance Criteria
128
+ body += `## Acceptance Criteria\n\n`;
129
+ if (task.acceptance_criteria && task.acceptance_criteria.length > 0) {
130
+ task.acceptance_criteria.forEach(criterion => {
131
+ body += `- [ ] ${criterion}\n`;
132
+ });
133
+ } else {
134
+ body += `- [ ] Implementation complete\n`;
135
+ body += `- [ ] Tests passing\n`;
136
+ body += `- [ ] Code reviewed\n`;
137
+ }
138
+
139
+ body += `\n`;
140
+
141
+ // Technical Notes (if provided)
142
+ if (task.technical_notes) {
143
+ body += `## Technical Notes\n\n${task.technical_notes}\n\n`;
144
+ }
145
+
146
+ // Dependencies
147
+ if (task.dependencies && task.dependencies.length > 0) {
148
+ body += `## Dependencies\n\n`;
149
+ task.dependencies.forEach(dep => {
150
+ body += `- ${dep}\n`;
151
+ });
152
+ body += `\n`;
153
+ }
154
+
155
+ return body.trim();
156
+ }
157
+
158
+ module.exports = { decomposeLocalEpic };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * List Local Epics
3
+ *
4
+ * Lists all epics in the local `.claude/epics/` directory
5
+ * with optional filtering by status or PRD ID.
6
+ *
7
+ * Usage:
8
+ * const { listLocalEpics } = require('./pm-epic-list-local');
9
+ *
10
+ * // List all epics
11
+ * const epics = await listLocalEpics();
12
+ *
13
+ * // Filter by status
14
+ * const inProgress = await listLocalEpics({ status: 'in_progress' });
15
+ *
16
+ * // Filter by PRD
17
+ * const prdEpics = await listLocalEpics({ prd_id: 'prd-001' });
18
+ */
19
+
20
+ const fs = require('fs').promises;
21
+ const path = require('path');
22
+ const { parseFrontmatter } = require('../lib/frontmatter');
23
+
24
+ /**
25
+ * List all local epics with optional filtering
26
+ *
27
+ * @param {Object} options - Filter options
28
+ * @param {string} [options.status] - Filter by epic status (planning, in_progress, completed, etc.)
29
+ * @param {string} [options.prd_id] - Filter by PRD ID
30
+ * @returns {Promise<Array>} Array of epic objects with frontmatter
31
+ */
32
+ async function listLocalEpics(options = {}) {
33
+ const basePath = process.cwd();
34
+ const epicsDir = path.join(basePath, '.claude', 'epics');
35
+
36
+ // Check if epics directory exists
37
+ try {
38
+ await fs.access(epicsDir);
39
+ } catch (err) {
40
+ if (err.code === 'ENOENT') {
41
+ return []; // No epics directory = no epics
42
+ }
43
+ throw err;
44
+ }
45
+
46
+ // Read all epic directories
47
+ const dirs = await fs.readdir(epicsDir);
48
+ const epics = [];
49
+
50
+ // Process each epic directory
51
+ for (const dir of dirs) {
52
+ // Skip hidden directories and files
53
+ if (dir.startsWith('.')) continue;
54
+
55
+ const epicDir = path.join(epicsDir, dir);
56
+ const epicPath = path.join(epicDir, 'epic.md');
57
+
58
+ try {
59
+ // Check if it's a directory with epic.md
60
+ const stat = await fs.stat(epicDir);
61
+ if (!stat.isDirectory()) continue;
62
+
63
+ // Read and parse epic.md
64
+ const content = await fs.readFile(epicPath, 'utf8');
65
+ const { frontmatter } = parseFrontmatter(content);
66
+
67
+ // Only include valid epics with required fields
68
+ if (frontmatter && frontmatter.id) {
69
+ epics.push({
70
+ ...frontmatter,
71
+ directory: dir
72
+ });
73
+ }
74
+ } catch (err) {
75
+ // Skip invalid epic directories (missing epic.md, parse errors, etc.)
76
+ if (err.code !== 'ENOENT') {
77
+ console.warn(`Warning: Could not process epic in ${dir}:`, err.message);
78
+ }
79
+ }
80
+ }
81
+
82
+ // Apply filters
83
+ let filtered = epics;
84
+
85
+ if (options.status) {
86
+ filtered = filtered.filter(epic => epic.status === options.status);
87
+ }
88
+
89
+ if (options.prd_id) {
90
+ filtered = filtered.filter(epic => epic.prd_id === options.prd_id);
91
+ }
92
+
93
+ // Sort by creation date (newest first)
94
+ filtered.sort((a, b) => {
95
+ const dateA = new Date(a.created || 0);
96
+ const dateB = new Date(b.created || 0);
97
+ return dateB - dateA; // Descending order (newest first)
98
+ });
99
+
100
+ return filtered;
101
+ }
102
+
103
+ module.exports = { listLocalEpics };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Show Local Epic
3
+ *
4
+ * Displays details of a specific epic including frontmatter,
5
+ * body content, and directory information.
6
+ *
7
+ * Usage:
8
+ * const { showLocalEpic } = require('./pm-epic-show-local');
9
+ *
10
+ * const epic = await showLocalEpic('epic-001');
11
+ * console.log(epic.frontmatter.title);
12
+ * console.log(epic.body);
13
+ */
14
+
15
+ const fs = require('fs').promises;
16
+ const path = require('path');
17
+ const { parseFrontmatter } = require('../lib/frontmatter');
18
+
19
+ /**
20
+ * Get epic details by ID
21
+ *
22
+ * @param {string} epicId - Epic ID to retrieve
23
+ * @returns {Promise<Object>} Epic object with frontmatter, body, and directory
24
+ * @throws {Error} If epic not found
25
+ */
26
+ async function showLocalEpic(epicId) {
27
+ const basePath = process.cwd();
28
+ const epicsDir = path.join(basePath, '.claude', 'epics');
29
+
30
+ // Check if epics directory exists
31
+ try {
32
+ await fs.access(epicsDir);
33
+ } catch (err) {
34
+ if (err.code === 'ENOENT') {
35
+ throw new Error(`Epic not found: ${epicId} (epics directory does not exist)`);
36
+ }
37
+ throw err;
38
+ }
39
+
40
+ // Find epic directory by ID
41
+ const dirs = await fs.readdir(epicsDir);
42
+ const epicDir = dirs.find(dir => dir.startsWith(`${epicId}-`));
43
+
44
+ if (!epicDir) {
45
+ throw new Error(`Epic not found: ${epicId}`);
46
+ }
47
+
48
+ const epicDirPath = path.join(epicsDir, epicDir);
49
+ const epicPath = path.join(epicDirPath, 'epic.md');
50
+
51
+ // Read and parse epic file
52
+ try {
53
+ const content = await fs.readFile(epicPath, 'utf8');
54
+ const { frontmatter, body } = parseFrontmatter(content);
55
+
56
+ return {
57
+ frontmatter,
58
+ body,
59
+ directory: epicDir,
60
+ path: epicPath
61
+ };
62
+ } catch (err) {
63
+ if (err.code === 'ENOENT') {
64
+ throw new Error(`Epic not found: ${epicId} (epic.md missing in ${epicDir})`);
65
+ }
66
+ throw err;
67
+ }
68
+ }
69
+
70
+ module.exports = { showLocalEpic };