claude-autopm 1.30.1 → 2.1.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/.claude/mcp/test-server.md +10 -0
- package/autopm/.claude/scripts/github/dependency-tracker.js +554 -0
- package/autopm/.claude/scripts/github/dependency-validator.js +545 -0
- package/autopm/.claude/scripts/github/dependency-visualizer.js +477 -0
- package/autopm/.claude/scripts/pm/lib/epic-discovery.js +119 -0
- package/autopm/.claude/scripts/pm/next.js +56 -58
- package/bin/autopm-poc.js +348 -0
- package/bin/autopm.js +6 -0
- package/lib/ai-providers/AbstractAIProvider.js +524 -0
- package/lib/ai-providers/ClaudeProvider.js +423 -0
- package/lib/ai-providers/TemplateProvider.js +432 -0
- package/lib/cli/commands/agent.js +206 -0
- package/lib/cli/commands/config.js +488 -0
- package/lib/cli/commands/prd.js +345 -0
- package/lib/cli/commands/task.js +206 -0
- package/lib/config/ConfigManager.js +531 -0
- package/lib/errors/AIProviderError.js +164 -0
- package/lib/services/AgentService.js +557 -0
- package/lib/services/EpicService.js +609 -0
- package/lib/services/PRDService.js +1003 -0
- package/lib/services/TaskService.js +760 -0
- package/lib/services/interfaces.js +753 -0
- package/lib/utils/CircuitBreaker.js +165 -0
- package/lib/utils/Encryption.js +201 -0
- package/lib/utils/RateLimiter.js +241 -0
- package/lib/utils/ServiceFactory.js +165 -0
- package/package.json +9 -5
- package/scripts/config/get.js +108 -0
- package/scripts/config/init.js +100 -0
- package/scripts/config/list-providers.js +93 -0
- package/scripts/config/set-api-key.js +107 -0
- package/scripts/config/set-provider.js +201 -0
- package/scripts/config/set.js +139 -0
- package/scripts/config/show.js +181 -0
- package/autopm/.claude/.env +0 -158
- package/autopm/.claude/settings.local.json +0 -9
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EpicService - Epic Management Service
|
|
3
|
+
*
|
|
4
|
+
* Pure service layer for epic operations without any I/O operations.
|
|
5
|
+
* Follows 3-layer architecture: Service (logic) -> No direct I/O
|
|
6
|
+
*
|
|
7
|
+
* Provides 12 pure business logic methods:
|
|
8
|
+
*
|
|
9
|
+
* 1. Status & Categorization (5 methods):
|
|
10
|
+
* - categorizeStatus: Categorize epic status
|
|
11
|
+
* - isTaskClosed: Check if task is completed
|
|
12
|
+
* - calculateProgress: Calculate completion percentage
|
|
13
|
+
* - generateProgressBar: Generate visual progress bar
|
|
14
|
+
* - hasValidDependencies: Validate dependency format
|
|
15
|
+
*
|
|
16
|
+
* 2. GitHub Integration (2 methods):
|
|
17
|
+
* - extractGitHubIssue: Extract issue number from URL
|
|
18
|
+
* - formatGitHubUrl: Build GitHub URL
|
|
19
|
+
*
|
|
20
|
+
* 3. Content Analysis (3 methods):
|
|
21
|
+
* - analyzePRD: Analyze PRD using PRDService
|
|
22
|
+
* - determineDependencies: Determine feature dependencies
|
|
23
|
+
* - generateEpicMetadata: Generate epic frontmatter
|
|
24
|
+
*
|
|
25
|
+
* 4. Content Generation (2 methods):
|
|
26
|
+
* - generateEpicContent: Build complete epic markdown
|
|
27
|
+
* - buildTaskSection: Format tasks as markdown list
|
|
28
|
+
*
|
|
29
|
+
* Documentation Queries:
|
|
30
|
+
* - mcp://context7/agile/epic-management - Epic management best practices
|
|
31
|
+
* - mcp://context7/agile/task-breakdown - Task breakdown patterns
|
|
32
|
+
* - mcp://context7/project-management/dependencies - Dependency management
|
|
33
|
+
* - mcp://context7/markdown/frontmatter - YAML frontmatter patterns
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const PRDService = require('./PRDService');
|
|
37
|
+
|
|
38
|
+
class EpicService {
|
|
39
|
+
/**
|
|
40
|
+
* Create a new EpicService instance
|
|
41
|
+
*
|
|
42
|
+
* @param {Object} options - Configuration options
|
|
43
|
+
* @param {PRDService} options.prdService - PRDService instance for parsing
|
|
44
|
+
* @param {ConfigManager} [options.configManager] - Optional ConfigManager instance
|
|
45
|
+
* @param {Object} [options.provider] - Optional AI provider instance for streaming
|
|
46
|
+
*/
|
|
47
|
+
constructor(options = {}) {
|
|
48
|
+
if (!options.prdService) {
|
|
49
|
+
throw new Error('PRDService instance is required');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!(options.prdService instanceof PRDService)) {
|
|
53
|
+
throw new Error('prdService must be an instance of PRDService');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
this.prdService = options.prdService;
|
|
57
|
+
|
|
58
|
+
// Store ConfigManager if provided (for future use)
|
|
59
|
+
this.configManager = options.configManager || undefined;
|
|
60
|
+
|
|
61
|
+
// Store provider if provided (for streaming operations)
|
|
62
|
+
this.provider = options.provider || undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ==========================================
|
|
66
|
+
// 1. STATUS & CATEGORIZATION (5 METHODS)
|
|
67
|
+
// ==========================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Categorize epic status into standard buckets
|
|
71
|
+
*
|
|
72
|
+
* Maps various status strings to standardized categories:
|
|
73
|
+
* - backlog: Not started, awaiting prioritization
|
|
74
|
+
* - planning: Planning/draft phase
|
|
75
|
+
* - in_progress: Active development
|
|
76
|
+
* - done: Completed/closed
|
|
77
|
+
*
|
|
78
|
+
* @param {string} status - Raw status string
|
|
79
|
+
* @returns {string} Categorized status (backlog|planning|in_progress|done)
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* categorizeStatus('in-progress') // Returns 'in_progress'
|
|
83
|
+
* categorizeStatus('completed') // Returns 'done'
|
|
84
|
+
* categorizeStatus('unknown') // Returns 'planning' (default)
|
|
85
|
+
*/
|
|
86
|
+
categorizeStatus(status) {
|
|
87
|
+
const lowerStatus = (status || '').toLowerCase();
|
|
88
|
+
|
|
89
|
+
// Backlog statuses
|
|
90
|
+
if (lowerStatus === 'backlog') {
|
|
91
|
+
return 'backlog';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Planning statuses
|
|
95
|
+
if (['planning', 'draft', ''].includes(lowerStatus)) {
|
|
96
|
+
return 'planning';
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// In-progress statuses
|
|
100
|
+
if (['in-progress', 'in_progress', 'active', 'started'].includes(lowerStatus)) {
|
|
101
|
+
return 'in_progress';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Completed statuses
|
|
105
|
+
if (['completed', 'complete', 'done', 'closed', 'finished'].includes(lowerStatus)) {
|
|
106
|
+
return 'done';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Default to planning for unknown statuses
|
|
110
|
+
return 'planning';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if task is in closed/completed state
|
|
115
|
+
*
|
|
116
|
+
* @param {Object} task - Task object with status field
|
|
117
|
+
* @returns {boolean} True if task is closed/completed
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* isTaskClosed({ status: 'closed' }) // Returns true
|
|
121
|
+
* isTaskClosed({ status: 'open' }) // Returns false
|
|
122
|
+
*/
|
|
123
|
+
isTaskClosed(task) {
|
|
124
|
+
const status = (task?.status || '').toLowerCase();
|
|
125
|
+
return ['closed', 'completed'].includes(status);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Calculate progress percentage from task array
|
|
130
|
+
*
|
|
131
|
+
* Calculates completion percentage based on closed vs total tasks.
|
|
132
|
+
* Returns 0 for empty or null arrays.
|
|
133
|
+
*
|
|
134
|
+
* @param {Array<Object>} tasks - Array of task objects with status
|
|
135
|
+
* @returns {number} Progress percentage (0-100), rounded to nearest integer
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* calculateProgress([
|
|
139
|
+
* { status: 'closed' },
|
|
140
|
+
* { status: 'open' }
|
|
141
|
+
* ]) // Returns 50
|
|
142
|
+
*/
|
|
143
|
+
calculateProgress(tasks) {
|
|
144
|
+
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
145
|
+
return 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const closedCount = tasks.filter(task => this.isTaskClosed(task)).length;
|
|
149
|
+
const percent = Math.round((closedCount * 100) / tasks.length);
|
|
150
|
+
|
|
151
|
+
return percent;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Generate visual progress bar
|
|
156
|
+
*
|
|
157
|
+
* Creates ASCII progress bar with filled/empty characters.
|
|
158
|
+
*
|
|
159
|
+
* @param {number} percent - Progress percentage (0-100)
|
|
160
|
+
* @param {number} totalChars - Total bar length in characters (default: 20)
|
|
161
|
+
* @returns {Object} Progress bar data:
|
|
162
|
+
* - bar: String representation of progress bar
|
|
163
|
+
* - percent: Input percentage
|
|
164
|
+
* - filled: Number of filled characters
|
|
165
|
+
* - empty: Number of empty characters
|
|
166
|
+
*
|
|
167
|
+
* @example
|
|
168
|
+
* generateProgressBar(50, 20)
|
|
169
|
+
* // Returns: {
|
|
170
|
+
* // bar: '[██████████░░░░░░░░░░]',
|
|
171
|
+
* // percent: 50,
|
|
172
|
+
* // filled: 10,
|
|
173
|
+
* // empty: 10
|
|
174
|
+
* // }
|
|
175
|
+
*/
|
|
176
|
+
generateProgressBar(percent, totalChars = 20) {
|
|
177
|
+
const filled = Math.round((percent * totalChars) / 100);
|
|
178
|
+
const empty = totalChars - filled;
|
|
179
|
+
|
|
180
|
+
let bar = '[';
|
|
181
|
+
bar += '█'.repeat(filled);
|
|
182
|
+
bar += '░'.repeat(empty);
|
|
183
|
+
bar += ']';
|
|
184
|
+
|
|
185
|
+
return {
|
|
186
|
+
bar,
|
|
187
|
+
percent,
|
|
188
|
+
filled,
|
|
189
|
+
empty
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Validate if dependency string has valid content
|
|
195
|
+
*
|
|
196
|
+
* Checks if dependency string contains actual dependency data
|
|
197
|
+
* after cleaning up formatting (brackets, whitespace, etc).
|
|
198
|
+
*
|
|
199
|
+
* @param {string} dependencies - Dependency string to validate
|
|
200
|
+
* @returns {boolean} True if dependencies are present and valid
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* hasValidDependencies('epic-123') // Returns true
|
|
204
|
+
* hasValidDependencies('[epic-1, epic-2]') // Returns true
|
|
205
|
+
* hasValidDependencies('[]') // Returns false
|
|
206
|
+
* hasValidDependencies('depends_on:') // Returns false
|
|
207
|
+
*/
|
|
208
|
+
hasValidDependencies(dependencies) {
|
|
209
|
+
if (!dependencies || typeof dependencies !== 'string') {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Handle malformed dependency strings
|
|
214
|
+
if (dependencies === 'depends_on:') {
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Clean up the dependency string
|
|
219
|
+
let cleanDeps = dependencies.trim();
|
|
220
|
+
|
|
221
|
+
// Remove array brackets if present
|
|
222
|
+
cleanDeps = cleanDeps.replace(/^\[|\]$/g, '');
|
|
223
|
+
|
|
224
|
+
// Check if there's actual content after cleaning
|
|
225
|
+
cleanDeps = cleanDeps.trim();
|
|
226
|
+
|
|
227
|
+
return cleanDeps.length > 0;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ==========================================
|
|
231
|
+
// 2. GITHUB INTEGRATION (2 METHODS)
|
|
232
|
+
// ==========================================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Extract GitHub issue number from URL
|
|
236
|
+
*
|
|
237
|
+
* Extracts numeric issue/PR number from GitHub URL.
|
|
238
|
+
* Supports both issues and pull requests.
|
|
239
|
+
*
|
|
240
|
+
* @param {string} githubUrl - GitHub issue or PR URL
|
|
241
|
+
* @returns {string|null} Issue number or null if not found
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* extractGitHubIssue('https://github.com/user/repo/issues/123')
|
|
245
|
+
* // Returns '123'
|
|
246
|
+
*
|
|
247
|
+
* extractGitHubIssue('https://github.com/user/repo/pull/456')
|
|
248
|
+
* // Returns '456'
|
|
249
|
+
*/
|
|
250
|
+
extractGitHubIssue(githubUrl) {
|
|
251
|
+
if (!githubUrl) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Match issue/PR number at end of URL (before optional trailing slash or query params)
|
|
256
|
+
const match = githubUrl.match(/\/(\d+)(?:\/|\?|$)/);
|
|
257
|
+
return match ? match[1] : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Format GitHub issue URL from components
|
|
262
|
+
*
|
|
263
|
+
* Builds standard GitHub issue URL from owner, repo, and issue number.
|
|
264
|
+
*
|
|
265
|
+
* @param {string} repoOwner - GitHub repository owner
|
|
266
|
+
* @param {string} repoName - GitHub repository name
|
|
267
|
+
* @param {number|string} issueNumber - Issue number
|
|
268
|
+
* @returns {string} Formatted GitHub URL
|
|
269
|
+
* @throws {Error} If required parameters are missing
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* formatGitHubUrl('user', 'repo', 123)
|
|
273
|
+
* // Returns 'https://github.com/user/repo/issues/123'
|
|
274
|
+
*/
|
|
275
|
+
formatGitHubUrl(repoOwner, repoName, issueNumber) {
|
|
276
|
+
if (!repoOwner || repoOwner.trim() === '') {
|
|
277
|
+
throw new Error('Repository owner is required');
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (!repoName || repoName.trim() === '') {
|
|
281
|
+
throw new Error('Repository name is required');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (!issueNumber) {
|
|
285
|
+
throw new Error('Issue number is required');
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return `https://github.com/${repoOwner}/${repoName}/issues/${issueNumber}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ==========================================
|
|
292
|
+
// 3. CONTENT ANALYSIS (3 METHODS)
|
|
293
|
+
// ==========================================
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Analyze PRD content using PRDService
|
|
297
|
+
*
|
|
298
|
+
* Parses PRD markdown to extract frontmatter and sections.
|
|
299
|
+
* Uses injected PRDService for parsing logic.
|
|
300
|
+
*
|
|
301
|
+
* @param {string} prdContent - PRD markdown content
|
|
302
|
+
* @returns {Object} Analysis result:
|
|
303
|
+
* - frontmatter: Parsed YAML frontmatter
|
|
304
|
+
* - sections: Extracted PRD sections (vision, features, etc)
|
|
305
|
+
*
|
|
306
|
+
* @example
|
|
307
|
+
* analyzePRD(prdMarkdown)
|
|
308
|
+
* // Returns:
|
|
309
|
+
* // {
|
|
310
|
+
* // frontmatter: { title: 'Feature', priority: 'P1' },
|
|
311
|
+
* // sections: { vision: '...', features: [...] }
|
|
312
|
+
* // }
|
|
313
|
+
*/
|
|
314
|
+
analyzePRD(prdContent) {
|
|
315
|
+
const frontmatter = this.prdService.parseFrontmatter(prdContent);
|
|
316
|
+
const sections = this.prdService.extractPrdContent(prdContent);
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
frontmatter,
|
|
320
|
+
sections
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Determine dependencies between features
|
|
326
|
+
*
|
|
327
|
+
* Analyzes feature types to determine natural dependencies:
|
|
328
|
+
* - Frontend depends on Backend
|
|
329
|
+
* - Backend depends on Data
|
|
330
|
+
* - Integration depends on all others
|
|
331
|
+
*
|
|
332
|
+
* @param {Array<Object>} features - Array of feature objects with type
|
|
333
|
+
* @returns {Object} Dependency map: { featureName: [dependency1, dependency2] }
|
|
334
|
+
*
|
|
335
|
+
* @example
|
|
336
|
+
* determineDependencies([
|
|
337
|
+
* { name: 'UI', type: 'frontend' },
|
|
338
|
+
* { name: 'API', type: 'backend' }
|
|
339
|
+
* ])
|
|
340
|
+
* // Returns: { 'UI': ['API'] }
|
|
341
|
+
*/
|
|
342
|
+
determineDependencies(features) {
|
|
343
|
+
if (!Array.isArray(features) || features.length === 0) {
|
|
344
|
+
return {};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const dependencies = {};
|
|
348
|
+
|
|
349
|
+
// Find features by type
|
|
350
|
+
const backendFeatures = features.filter(f => f.type === 'backend');
|
|
351
|
+
const dataFeatures = features.filter(f => f.type === 'data');
|
|
352
|
+
const frontendFeatures = features.filter(f => f.type === 'frontend');
|
|
353
|
+
|
|
354
|
+
// Frontend depends on Backend
|
|
355
|
+
frontendFeatures.forEach(frontend => {
|
|
356
|
+
if (backendFeatures.length > 0) {
|
|
357
|
+
dependencies[frontend.name] = backendFeatures.map(b => b.name);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Backend depends on Data
|
|
362
|
+
backendFeatures.forEach(backend => {
|
|
363
|
+
if (dataFeatures.length > 0) {
|
|
364
|
+
dependencies[backend.name] = dataFeatures.map(d => d.name);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
return dependencies;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Generate epic metadata (frontmatter)
|
|
373
|
+
*
|
|
374
|
+
* Creates standardized epic frontmatter with required and optional fields.
|
|
375
|
+
*
|
|
376
|
+
* @param {string} name - Epic name
|
|
377
|
+
* @param {string} prdId - PRD identifier
|
|
378
|
+
* @param {Object} options - Optional metadata overrides
|
|
379
|
+
* @param {string} options.status - Epic status (default: 'backlog')
|
|
380
|
+
* @param {string} options.priority - Priority level (default: 'P2')
|
|
381
|
+
* @returns {Object} Epic metadata object
|
|
382
|
+
* @throws {Error} If required parameters are missing
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* generateEpicMetadata('user-auth', 'prd-123', { priority: 'P1' })
|
|
386
|
+
* // Returns:
|
|
387
|
+
* // {
|
|
388
|
+
* // name: 'user-auth',
|
|
389
|
+
* // status: 'backlog',
|
|
390
|
+
* // prd_id: 'prd-123',
|
|
391
|
+
* // priority: 'P1',
|
|
392
|
+
* // created: '2025-01-01T00:00:00.000Z',
|
|
393
|
+
* // progress: '0%',
|
|
394
|
+
* // prd: '.claude/prds/user-auth.md',
|
|
395
|
+
* // github: '[Will be updated when synced to GitHub]'
|
|
396
|
+
* // }
|
|
397
|
+
*/
|
|
398
|
+
generateEpicMetadata(name, prdId, options = {}) {
|
|
399
|
+
if (!name || name.trim() === '') {
|
|
400
|
+
throw new Error('Epic name is required');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (!prdId || prdId.trim() === '') {
|
|
404
|
+
throw new Error('PRD ID is required');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const now = new Date().toISOString();
|
|
408
|
+
|
|
409
|
+
return {
|
|
410
|
+
name,
|
|
411
|
+
status: options.status || 'backlog',
|
|
412
|
+
prd_id: prdId,
|
|
413
|
+
priority: options.priority || 'P2',
|
|
414
|
+
created: now,
|
|
415
|
+
progress: '0%',
|
|
416
|
+
prd: `.claude/prds/${name}.md`,
|
|
417
|
+
github: '[Will be updated when synced to GitHub]'
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ==========================================
|
|
422
|
+
// 4. CONTENT GENERATION (2 METHODS)
|
|
423
|
+
// ==========================================
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Generate complete epic markdown content
|
|
427
|
+
*
|
|
428
|
+
* Builds full epic document with frontmatter, sections, and tasks.
|
|
429
|
+
* Follows standard epic template format.
|
|
430
|
+
*
|
|
431
|
+
* @param {Object} metadata - Epic metadata (frontmatter)
|
|
432
|
+
* @param {Object} sections - PRD sections (vision, problem, features, etc)
|
|
433
|
+
* @param {Array<Object>} tasks - Array of task objects
|
|
434
|
+
* @returns {string} Complete epic markdown content
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* generateEpicContent(metadata, sections, tasks)
|
|
438
|
+
* // Returns multiline markdown string with:
|
|
439
|
+
* // - YAML frontmatter
|
|
440
|
+
* // - Epic title and overview
|
|
441
|
+
* // - Vision and other sections
|
|
442
|
+
* // - Task breakdown
|
|
443
|
+
*/
|
|
444
|
+
generateEpicContent(metadata, sections, tasks) {
|
|
445
|
+
// Build frontmatter
|
|
446
|
+
const frontmatter = `---
|
|
447
|
+
name: ${metadata.name}
|
|
448
|
+
status: ${metadata.status}
|
|
449
|
+
created: ${metadata.created}
|
|
450
|
+
progress: ${metadata.progress}
|
|
451
|
+
prd: ${metadata.prd}
|
|
452
|
+
github: ${metadata.github}
|
|
453
|
+
priority: ${metadata.priority}
|
|
454
|
+
---`;
|
|
455
|
+
|
|
456
|
+
// Build overview section
|
|
457
|
+
let content = frontmatter + '\n\n';
|
|
458
|
+
content += `# Epic: ${metadata.name}\n\n`;
|
|
459
|
+
content += '## Overview\n';
|
|
460
|
+
|
|
461
|
+
if (sections.vision) {
|
|
462
|
+
content += sections.vision + '\n\n';
|
|
463
|
+
content += `### Vision\n${sections.vision}\n\n`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (sections.problem) {
|
|
467
|
+
content += `### Problem\n${sections.problem}\n\n`;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Build task breakdown
|
|
471
|
+
content += '## Task Breakdown\n\n';
|
|
472
|
+
const taskSection = this.buildTaskSection(tasks);
|
|
473
|
+
content += taskSection;
|
|
474
|
+
|
|
475
|
+
return content;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Build task section markdown
|
|
480
|
+
*
|
|
481
|
+
* Formats task array as markdown list with details.
|
|
482
|
+
* Each task includes: ID, title, type, effort, status.
|
|
483
|
+
*
|
|
484
|
+
* @param {Array<Object>} tasks - Array of task objects
|
|
485
|
+
* @returns {string} Markdown formatted task list
|
|
486
|
+
*
|
|
487
|
+
* @example
|
|
488
|
+
* buildTaskSection([
|
|
489
|
+
* { id: 'TASK-1', title: 'Setup', type: 'setup', effort: '2h', status: 'open' }
|
|
490
|
+
* ])
|
|
491
|
+
* // Returns:
|
|
492
|
+
* // ### TASK-1: Setup
|
|
493
|
+
* // - **Type**: setup
|
|
494
|
+
* // - **Effort**: 2h
|
|
495
|
+
* // - **Status**: open
|
|
496
|
+
*/
|
|
497
|
+
buildTaskSection(tasks) {
|
|
498
|
+
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
499
|
+
return '';
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return tasks.map(task => {
|
|
503
|
+
const status = task.status || 'Not Started';
|
|
504
|
+
return `### ${task.id}: ${task.title}
|
|
505
|
+
- **Type**: ${task.type}
|
|
506
|
+
- **Effort**: ${task.effort}
|
|
507
|
+
- **Status**: ${status}`;
|
|
508
|
+
}).join('\n\n');
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// ==========================================
|
|
512
|
+
// 5. AI STREAMING METHODS
|
|
513
|
+
// ==========================================
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Decompose epic into tasks with streaming output
|
|
517
|
+
*
|
|
518
|
+
* Streams AI-powered decomposition of epic content into discrete tasks.
|
|
519
|
+
* The AI analyzes the epic and generates task breakdown with estimates,
|
|
520
|
+
* dependencies, and assignments.
|
|
521
|
+
*
|
|
522
|
+
* @param {string} epicContent - Epic markdown content
|
|
523
|
+
* @param {Object} [options] - Streaming options (passed to provider)
|
|
524
|
+
* @returns {AsyncGenerator<string>} Stream of task decomposition text chunks
|
|
525
|
+
* @throws {Error} If provider is not available or lacks stream() support
|
|
526
|
+
*
|
|
527
|
+
* @example
|
|
528
|
+
* for await (const chunk of service.decomposeStream(epicContent)) {
|
|
529
|
+
* process.stdout.write(chunk); // Display task generation progress
|
|
530
|
+
* }
|
|
531
|
+
*/
|
|
532
|
+
async *decomposeStream(epicContent, options = {}) {
|
|
533
|
+
if (!this.provider || !this.provider.stream) {
|
|
534
|
+
throw new Error('Streaming requires an AI provider with stream() support');
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const prompt = `Decompose this epic into specific, actionable tasks.
|
|
538
|
+
|
|
539
|
+
For each task, provide:
|
|
540
|
+
1. Task ID and title
|
|
541
|
+
2. Task type (frontend/backend/data/testing/documentation)
|
|
542
|
+
3. Detailed description
|
|
543
|
+
4. Effort estimate (in hours or days)
|
|
544
|
+
5. Dependencies on other tasks
|
|
545
|
+
6. Acceptance criteria (bullet points)
|
|
546
|
+
|
|
547
|
+
Generate 5-15 tasks that fully cover the epic scope. Tasks should be:
|
|
548
|
+
- Small enough to complete in 1-3 days
|
|
549
|
+
- Independent where possible
|
|
550
|
+
- Clearly defined with acceptance criteria
|
|
551
|
+
- Properly sequenced with dependencies
|
|
552
|
+
|
|
553
|
+
Epic Content:
|
|
554
|
+
${epicContent}`;
|
|
555
|
+
|
|
556
|
+
for await (const chunk of this.provider.stream(prompt, options)) {
|
|
557
|
+
yield chunk;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Analyze PRD with streaming output
|
|
563
|
+
*
|
|
564
|
+
* Streams AI-powered analysis of PRD content to identify epics, themes,
|
|
565
|
+
* and high-level task breakdown. This is typically used before epic creation
|
|
566
|
+
* to understand the PRD structure and complexity.
|
|
567
|
+
*
|
|
568
|
+
* @param {string} prdContent - PRD markdown content
|
|
569
|
+
* @param {Object} [options] - Streaming options (passed to provider)
|
|
570
|
+
* @returns {AsyncGenerator<string>} Stream of PRD analysis text chunks
|
|
571
|
+
* @throws {Error} If provider is not available or lacks stream() support
|
|
572
|
+
*
|
|
573
|
+
* @example
|
|
574
|
+
* for await (const chunk of service.analyzeStream(prdContent)) {
|
|
575
|
+
* process.stdout.write(chunk); // Display PRD analysis progress
|
|
576
|
+
* }
|
|
577
|
+
*/
|
|
578
|
+
async *analyzeStream(prdContent, options = {}) {
|
|
579
|
+
if (!this.provider || !this.provider.stream) {
|
|
580
|
+
throw new Error('Streaming requires an AI provider with stream() support');
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
const prompt = `Analyze this Product Requirements Document and provide a comprehensive epic-level breakdown.
|
|
584
|
+
|
|
585
|
+
Identify:
|
|
586
|
+
1. Major themes or feature areas (2-5 epics)
|
|
587
|
+
2. For each potential epic:
|
|
588
|
+
- Epic name and scope
|
|
589
|
+
- Key features/capabilities
|
|
590
|
+
- Estimated complexity (Small/Medium/Large)
|
|
591
|
+
- Dependencies on other epics
|
|
592
|
+
- Rough task count estimate
|
|
593
|
+
|
|
594
|
+
Also provide:
|
|
595
|
+
- Overall project complexity assessment
|
|
596
|
+
- Recommended epic breakdown approach
|
|
597
|
+
- Key technical risks or challenges
|
|
598
|
+
- Suggested development sequence
|
|
599
|
+
|
|
600
|
+
PRD Content:
|
|
601
|
+
${prdContent}`;
|
|
602
|
+
|
|
603
|
+
for await (const chunk of this.provider.stream(prompt, options)) {
|
|
604
|
+
yield chunk;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
module.exports = EpicService;
|