@hyperdrive.bot/bmad-workflow 1.0.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.
- package/LICENSE +21 -0
- package/README.md +1017 -0
- package/bin/dev +5 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +5 -0
- package/bin/run +5 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/config/show.d.ts +34 -0
- package/dist/commands/config/show.js +108 -0
- package/dist/commands/config/validate.d.ts +29 -0
- package/dist/commands/config/validate.js +131 -0
- package/dist/commands/decompose.d.ts +79 -0
- package/dist/commands/decompose.js +327 -0
- package/dist/commands/demo.d.ts +18 -0
- package/dist/commands/demo.js +107 -0
- package/dist/commands/epics/create.d.ts +123 -0
- package/dist/commands/epics/create.js +459 -0
- package/dist/commands/epics/list.d.ts +120 -0
- package/dist/commands/epics/list.js +280 -0
- package/dist/commands/hello/index.d.ts +12 -0
- package/dist/commands/hello/index.js +34 -0
- package/dist/commands/hello/world.d.ts +8 -0
- package/dist/commands/hello/world.js +24 -0
- package/dist/commands/prd/fix.d.ts +39 -0
- package/dist/commands/prd/fix.js +140 -0
- package/dist/commands/prd/validate.d.ts +112 -0
- package/dist/commands/prd/validate.js +302 -0
- package/dist/commands/stories/create.d.ts +95 -0
- package/dist/commands/stories/create.js +431 -0
- package/dist/commands/stories/develop.d.ts +91 -0
- package/dist/commands/stories/develop.js +460 -0
- package/dist/commands/stories/list.d.ts +84 -0
- package/dist/commands/stories/list.js +291 -0
- package/dist/commands/stories/move.d.ts +66 -0
- package/dist/commands/stories/move.js +273 -0
- package/dist/commands/stories/qa.d.ts +99 -0
- package/dist/commands/stories/qa.js +530 -0
- package/dist/commands/workflow.d.ts +97 -0
- package/dist/commands/workflow.js +390 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models/agent-options.d.ts +50 -0
- package/dist/models/agent-options.js +1 -0
- package/dist/models/agent-result.d.ts +29 -0
- package/dist/models/agent-result.js +1 -0
- package/dist/models/index.d.ts +10 -0
- package/dist/models/index.js +10 -0
- package/dist/models/phase-result.d.ts +65 -0
- package/dist/models/phase-result.js +7 -0
- package/dist/models/provider.d.ts +28 -0
- package/dist/models/provider.js +18 -0
- package/dist/models/story.d.ts +154 -0
- package/dist/models/story.js +18 -0
- package/dist/models/workflow-config.d.ts +148 -0
- package/dist/models/workflow-config.js +1 -0
- package/dist/models/workflow-result.d.ts +164 -0
- package/dist/models/workflow-result.js +7 -0
- package/dist/services/agents/agent-runner-factory.d.ts +31 -0
- package/dist/services/agents/agent-runner-factory.js +44 -0
- package/dist/services/agents/agent-runner.d.ts +46 -0
- package/dist/services/agents/agent-runner.js +29 -0
- package/dist/services/agents/claude-agent-runner.d.ts +81 -0
- package/dist/services/agents/claude-agent-runner.js +332 -0
- package/dist/services/agents/gemini-agent-runner.d.ts +82 -0
- package/dist/services/agents/gemini-agent-runner.js +350 -0
- package/dist/services/agents/index.d.ts +7 -0
- package/dist/services/agents/index.js +7 -0
- package/dist/services/file-system/file-manager.d.ts +110 -0
- package/dist/services/file-system/file-manager.js +223 -0
- package/dist/services/file-system/glob-matcher.d.ts +75 -0
- package/dist/services/file-system/glob-matcher.js +126 -0
- package/dist/services/file-system/path-resolver.d.ts +183 -0
- package/dist/services/file-system/path-resolver.js +400 -0
- package/dist/services/logging/workflow-logger.d.ts +232 -0
- package/dist/services/logging/workflow-logger.js +552 -0
- package/dist/services/orchestration/batch-processor.d.ts +113 -0
- package/dist/services/orchestration/batch-processor.js +187 -0
- package/dist/services/orchestration/dependency-graph-executor.d.ts +60 -0
- package/dist/services/orchestration/dependency-graph-executor.js +447 -0
- package/dist/services/orchestration/index.d.ts +10 -0
- package/dist/services/orchestration/index.js +8 -0
- package/dist/services/orchestration/input-detector.d.ts +125 -0
- package/dist/services/orchestration/input-detector.js +381 -0
- package/dist/services/orchestration/story-queue.d.ts +94 -0
- package/dist/services/orchestration/story-queue.js +170 -0
- package/dist/services/orchestration/story-type-detector.d.ts +80 -0
- package/dist/services/orchestration/story-type-detector.js +258 -0
- package/dist/services/orchestration/task-decomposition-service.d.ts +67 -0
- package/dist/services/orchestration/task-decomposition-service.js +607 -0
- package/dist/services/orchestration/workflow-orchestrator.d.ts +659 -0
- package/dist/services/orchestration/workflow-orchestrator.js +2201 -0
- package/dist/services/parsers/epic-parser.d.ts +117 -0
- package/dist/services/parsers/epic-parser.js +264 -0
- package/dist/services/parsers/prd-fixer.d.ts +86 -0
- package/dist/services/parsers/prd-fixer.js +194 -0
- package/dist/services/parsers/prd-parser.d.ts +123 -0
- package/dist/services/parsers/prd-parser.js +286 -0
- package/dist/services/parsers/standalone-story-parser.d.ts +114 -0
- package/dist/services/parsers/standalone-story-parser.js +255 -0
- package/dist/services/parsers/story-parser-factory.d.ts +81 -0
- package/dist/services/parsers/story-parser-factory.js +108 -0
- package/dist/services/parsers/story-parser.d.ts +122 -0
- package/dist/services/parsers/story-parser.js +262 -0
- package/dist/services/scaffolding/decompose-session-scaffolder.d.ts +74 -0
- package/dist/services/scaffolding/decompose-session-scaffolder.js +315 -0
- package/dist/services/scaffolding/file-scaffolder.d.ts +94 -0
- package/dist/services/scaffolding/file-scaffolder.js +314 -0
- package/dist/services/validation/config-validator.d.ts +88 -0
- package/dist/services/validation/config-validator.js +167 -0
- package/dist/types/task-graph.d.ts +142 -0
- package/dist/types/task-graph.js +5 -0
- package/dist/utils/colors.d.ts +49 -0
- package/dist/utils/colors.js +50 -0
- package/dist/utils/error-formatter.d.ts +64 -0
- package/dist/utils/error-formatter.js +279 -0
- package/dist/utils/errors.d.ts +170 -0
- package/dist/utils/errors.js +233 -0
- package/dist/utils/formatters.d.ts +84 -0
- package/dist/utils/formatters.js +162 -0
- package/dist/utils/logger.d.ts +63 -0
- package/dist/utils/logger.js +78 -0
- package/dist/utils/progress.d.ts +104 -0
- package/dist/utils/progress.js +161 -0
- package/dist/utils/retry.d.ts +114 -0
- package/dist/utils/retry.js +160 -0
- package/dist/utils/shared-flags.d.ts +28 -0
- package/dist/utils/shared-flags.js +43 -0
- package/package.json +119 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Story Queue
|
|
3
|
+
*
|
|
4
|
+
* Thread-safe queue mechanism for capturing completed stories during the story creation phase
|
|
5
|
+
* and making them available to the development phase. Supports concurrent consumers with
|
|
6
|
+
* async waiting when queue is empty.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```typescript
|
|
10
|
+
* import pino from 'pino'
|
|
11
|
+
* import { StoryQueue } from './story-queue.js'
|
|
12
|
+
*
|
|
13
|
+
* const logger = pino()
|
|
14
|
+
* const queue = new StoryQueue(logger)
|
|
15
|
+
*
|
|
16
|
+
* // Producer: enqueue completed stories
|
|
17
|
+
* queue.enqueue(story1)
|
|
18
|
+
* queue.enqueue(story2)
|
|
19
|
+
*
|
|
20
|
+
* // Consumer: dequeue stories for processing
|
|
21
|
+
* const story = await queue.dequeue() // Waits if queue is empty
|
|
22
|
+
* if (story === null) {
|
|
23
|
+
* // Queue is closed and empty
|
|
24
|
+
* return
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* // When all stories are created, close the queue
|
|
28
|
+
* queue.close()
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* Thread-safe queue for story pipeline coordination.
|
|
33
|
+
*
|
|
34
|
+
* Provides FIFO queueing with async waiting for consumers. Safe for concurrent
|
|
35
|
+
* access in Node.js event loop (no mutex needed due to single-threaded execution).
|
|
36
|
+
*/
|
|
37
|
+
export class StoryQueue {
|
|
38
|
+
/**
|
|
39
|
+
* Flag indicating no more stories will be added
|
|
40
|
+
*/
|
|
41
|
+
closed = false;
|
|
42
|
+
/**
|
|
43
|
+
* Structured logger instance
|
|
44
|
+
*/
|
|
45
|
+
logger;
|
|
46
|
+
/**
|
|
47
|
+
* Internal FIFO queue of stories
|
|
48
|
+
*/
|
|
49
|
+
queue = [];
|
|
50
|
+
/**
|
|
51
|
+
* Array of waiting dequeue promises
|
|
52
|
+
*/
|
|
53
|
+
waiters = [];
|
|
54
|
+
/**
|
|
55
|
+
* Creates a new StoryQueue instance
|
|
56
|
+
*
|
|
57
|
+
* @param logger - Pino logger for structured logging
|
|
58
|
+
*/
|
|
59
|
+
constructor(logger) {
|
|
60
|
+
this.logger = logger;
|
|
61
|
+
this.logger.debug('StoryQueue initialized');
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Signals that no more stories will be added to the queue.
|
|
65
|
+
* All waiting dequeue calls will be resolved with null.
|
|
66
|
+
*/
|
|
67
|
+
close() {
|
|
68
|
+
if (this.closed) {
|
|
69
|
+
this.logger.warn('Close called on already closed queue');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.closed = true;
|
|
73
|
+
this.logger.info({
|
|
74
|
+
remainingQueueSize: this.queue.length,
|
|
75
|
+
waitersToNotify: this.waiters.length,
|
|
76
|
+
}, 'Queue closed');
|
|
77
|
+
// Wake up all waiting dequeue calls with null
|
|
78
|
+
while (this.waiters.length > 0) {
|
|
79
|
+
const waiter = this.waiters.shift();
|
|
80
|
+
if (waiter) {
|
|
81
|
+
waiter(null);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Removes and returns the next story from the queue.
|
|
87
|
+
* If queue is empty and not closed, waits asynchronously for a story to be enqueued.
|
|
88
|
+
* If queue is closed and empty, returns null.
|
|
89
|
+
*
|
|
90
|
+
* @returns Promise that resolves to the next story, or null if queue is closed and empty
|
|
91
|
+
*/
|
|
92
|
+
async dequeue() {
|
|
93
|
+
// If queue has items, return immediately
|
|
94
|
+
if (this.queue.length > 0) {
|
|
95
|
+
const story = this.queue.shift() ?? null;
|
|
96
|
+
this.logger.info({
|
|
97
|
+
remainingQueueSize: this.queue.length,
|
|
98
|
+
storyNumber: story?.fullNumber,
|
|
99
|
+
storyTitle: story?.title,
|
|
100
|
+
}, 'Story dequeued');
|
|
101
|
+
return story;
|
|
102
|
+
}
|
|
103
|
+
// If closed and empty, return null
|
|
104
|
+
if (this.closed) {
|
|
105
|
+
this.logger.debug({
|
|
106
|
+
closed: this.closed,
|
|
107
|
+
isEmpty: true,
|
|
108
|
+
}, 'Dequeue returning null: queue closed and empty');
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
// Wait for item or close
|
|
112
|
+
this.logger.debug({
|
|
113
|
+
closed: this.closed,
|
|
114
|
+
isEmpty: this.isEmpty(),
|
|
115
|
+
waiters: this.waiters.length,
|
|
116
|
+
}, 'Dequeue waiting for story');
|
|
117
|
+
return new Promise((resolve) => {
|
|
118
|
+
this.waiters.push(resolve);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Adds a story to the queue
|
|
123
|
+
*
|
|
124
|
+
* @param story - The story to enqueue
|
|
125
|
+
* @throws Error if queue is already closed
|
|
126
|
+
*/
|
|
127
|
+
enqueue(story) {
|
|
128
|
+
if (this.closed) {
|
|
129
|
+
const error = new Error('Cannot enqueue to closed queue');
|
|
130
|
+
this.logger.error({
|
|
131
|
+
closed: this.closed,
|
|
132
|
+
storyNumber: story.fullNumber,
|
|
133
|
+
}, 'Enqueue failed: queue closed');
|
|
134
|
+
throw error;
|
|
135
|
+
}
|
|
136
|
+
this.queue.push(story);
|
|
137
|
+
this.logger.info({
|
|
138
|
+
queueSize: this.queue.length,
|
|
139
|
+
storyNumber: story.fullNumber,
|
|
140
|
+
storyTitle: story.title,
|
|
141
|
+
waiters: this.waiters.length,
|
|
142
|
+
}, 'Story enqueued');
|
|
143
|
+
// Wake up a waiting dequeue call if any
|
|
144
|
+
const waiter = this.waiters.shift();
|
|
145
|
+
if (waiter) {
|
|
146
|
+
const dequeuedStory = this.queue.shift() ?? null;
|
|
147
|
+
this.logger.debug({
|
|
148
|
+
remainingQueueSize: this.queue.length,
|
|
149
|
+
storyNumber: dequeuedStory?.fullNumber,
|
|
150
|
+
}, 'Woke up waiting dequeue consumer');
|
|
151
|
+
waiter(dequeuedStory);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Gets the number of pending stories in the queue
|
|
156
|
+
*
|
|
157
|
+
* @returns Number of stories waiting to be dequeued
|
|
158
|
+
*/
|
|
159
|
+
getPendingCount() {
|
|
160
|
+
return this.queue.length;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Checks if the queue is empty
|
|
164
|
+
*
|
|
165
|
+
* @returns true if queue has no items, false otherwise
|
|
166
|
+
*/
|
|
167
|
+
isEmpty() {
|
|
168
|
+
return this.queue.length === 0;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StoryTypeDetector Service
|
|
3
|
+
*
|
|
4
|
+
* Analyzes story content to determine if it's frontend, backend, or full-stack work.
|
|
5
|
+
* Used to automatically include relevant documentation references for dev agents.
|
|
6
|
+
*/
|
|
7
|
+
import type pino from 'pino';
|
|
8
|
+
/**
|
|
9
|
+
* Story type classification
|
|
10
|
+
*/
|
|
11
|
+
export type StoryType = 'backend' | 'frontend' | 'fullstack' | 'unknown';
|
|
12
|
+
/**
|
|
13
|
+
* Detection result with confidence score
|
|
14
|
+
*/
|
|
15
|
+
export interface StoryTypeDetection {
|
|
16
|
+
/**
|
|
17
|
+
* Confidence score (0-1)
|
|
18
|
+
*/
|
|
19
|
+
confidence: number;
|
|
20
|
+
/**
|
|
21
|
+
* Matched keywords that led to detection
|
|
22
|
+
*/
|
|
23
|
+
matchedKeywords: string[];
|
|
24
|
+
/**
|
|
25
|
+
* Detected story type
|
|
26
|
+
*/
|
|
27
|
+
type: StoryType;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Configuration for documentation references by story type
|
|
31
|
+
*/
|
|
32
|
+
export interface DocumentationReferences {
|
|
33
|
+
backend: string[];
|
|
34
|
+
frontend: string[];
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* StoryTypeDetector service for analyzing story content
|
|
38
|
+
*
|
|
39
|
+
* Detects whether a story is frontend, backend, or full-stack work
|
|
40
|
+
* by analyzing keywords, file paths, and acceptance criteria.
|
|
41
|
+
*/
|
|
42
|
+
export declare class StoryTypeDetector {
|
|
43
|
+
private readonly logger;
|
|
44
|
+
/**
|
|
45
|
+
* Create a new StoryTypeDetector instance
|
|
46
|
+
*
|
|
47
|
+
* @param logger - Logger instance for structured logging
|
|
48
|
+
*/
|
|
49
|
+
constructor(logger: pino.Logger);
|
|
50
|
+
/**
|
|
51
|
+
* Detect story type from content
|
|
52
|
+
*
|
|
53
|
+
* Analyzes the story content (title, description, tasks, acceptance criteria)
|
|
54
|
+
* to determine if it's frontend, backend, or full-stack work.
|
|
55
|
+
*
|
|
56
|
+
* @param storyContent - Full story markdown content
|
|
57
|
+
* @returns Detection result with type, confidence, and matched keywords
|
|
58
|
+
*/
|
|
59
|
+
detectStoryType(storyContent: string): StoryTypeDetection;
|
|
60
|
+
/**
|
|
61
|
+
* Get documentation references for a story type
|
|
62
|
+
*
|
|
63
|
+
* Returns the appropriate documentation file paths based on the detected story type.
|
|
64
|
+
* Saves tokens by only including relevant documentation.
|
|
65
|
+
*
|
|
66
|
+
* @param storyType - Detected story type
|
|
67
|
+
* @returns Array of documentation file paths to include
|
|
68
|
+
*/
|
|
69
|
+
getDocumentationReferences(storyType: StoryType): string[];
|
|
70
|
+
/**
|
|
71
|
+
* Count keyword matches in content
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
private countKeywordMatches;
|
|
75
|
+
/**
|
|
76
|
+
* Count file path pattern matches
|
|
77
|
+
* @private
|
|
78
|
+
*/
|
|
79
|
+
private countPathPatternMatches;
|
|
80
|
+
}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StoryTypeDetector Service
|
|
3
|
+
*
|
|
4
|
+
* Analyzes story content to determine if it's frontend, backend, or full-stack work.
|
|
5
|
+
* Used to automatically include relevant documentation references for dev agents.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Keywords that indicate frontend work
|
|
9
|
+
*/
|
|
10
|
+
const FRONTEND_KEYWORDS = [
|
|
11
|
+
'frontend',
|
|
12
|
+
'ui',
|
|
13
|
+
'component',
|
|
14
|
+
'react',
|
|
15
|
+
'amplify',
|
|
16
|
+
'interface',
|
|
17
|
+
'page',
|
|
18
|
+
'view',
|
|
19
|
+
'form',
|
|
20
|
+
'button',
|
|
21
|
+
'modal',
|
|
22
|
+
'navigation',
|
|
23
|
+
'routing',
|
|
24
|
+
'css',
|
|
25
|
+
'style',
|
|
26
|
+
'theme',
|
|
27
|
+
'layout',
|
|
28
|
+
'responsive',
|
|
29
|
+
'client-side',
|
|
30
|
+
'browser',
|
|
31
|
+
'web app',
|
|
32
|
+
'user interface',
|
|
33
|
+
'vite',
|
|
34
|
+
'typescript react',
|
|
35
|
+
'tsx',
|
|
36
|
+
'jsx',
|
|
37
|
+
'cognito',
|
|
38
|
+
'authentication ui',
|
|
39
|
+
'sign-in',
|
|
40
|
+
'sign-up',
|
|
41
|
+
];
|
|
42
|
+
/**
|
|
43
|
+
* Keywords that indicate backend work
|
|
44
|
+
*/
|
|
45
|
+
const BACKEND_KEYWORDS = [
|
|
46
|
+
'backend',
|
|
47
|
+
'api',
|
|
48
|
+
'endpoint',
|
|
49
|
+
'handler',
|
|
50
|
+
'service',
|
|
51
|
+
'dynamodb',
|
|
52
|
+
'database',
|
|
53
|
+
'lambda',
|
|
54
|
+
'serverless',
|
|
55
|
+
'middleware',
|
|
56
|
+
'authentication',
|
|
57
|
+
'authorization',
|
|
58
|
+
'rest api',
|
|
59
|
+
'graphql',
|
|
60
|
+
's3',
|
|
61
|
+
'eventbridge',
|
|
62
|
+
'sqs',
|
|
63
|
+
'sns',
|
|
64
|
+
'cognito user pool',
|
|
65
|
+
'aws sdk',
|
|
66
|
+
'marshall',
|
|
67
|
+
'unmarshall',
|
|
68
|
+
'query command',
|
|
69
|
+
'put item',
|
|
70
|
+
'get item',
|
|
71
|
+
'update item',
|
|
72
|
+
'delete item',
|
|
73
|
+
'gsi',
|
|
74
|
+
'partition key',
|
|
75
|
+
'sort key',
|
|
76
|
+
'http status',
|
|
77
|
+
'error handling',
|
|
78
|
+
'validation',
|
|
79
|
+
'business logic',
|
|
80
|
+
];
|
|
81
|
+
/**
|
|
82
|
+
* File path patterns that indicate frontend work
|
|
83
|
+
*/
|
|
84
|
+
const FRONTEND_PATH_PATTERNS = [
|
|
85
|
+
/packages\/web-apps\//i,
|
|
86
|
+
/packages\/entities\/clients\/.*\/repos\//i,
|
|
87
|
+
/src\/components\//i,
|
|
88
|
+
/src\/pages\//i,
|
|
89
|
+
/src\/lib\//i,
|
|
90
|
+
/\.tsx$/i,
|
|
91
|
+
/\.jsx$/i,
|
|
92
|
+
/frontend/i,
|
|
93
|
+
];
|
|
94
|
+
/**
|
|
95
|
+
* File path patterns that indicate backend work
|
|
96
|
+
*/
|
|
97
|
+
const BACKEND_PATH_PATTERNS = [
|
|
98
|
+
/packages\/serverless\/api/i,
|
|
99
|
+
/src\/handlers\//i,
|
|
100
|
+
/src\/services\//i,
|
|
101
|
+
/src\/middleware/i,
|
|
102
|
+
/serverless\.yml$/i,
|
|
103
|
+
/\.handler\.ts$/i,
|
|
104
|
+
/\.service\.ts$/i,
|
|
105
|
+
/backend/i,
|
|
106
|
+
/api/i,
|
|
107
|
+
];
|
|
108
|
+
/**
|
|
109
|
+
* StoryTypeDetector service for analyzing story content
|
|
110
|
+
*
|
|
111
|
+
* Detects whether a story is frontend, backend, or full-stack work
|
|
112
|
+
* by analyzing keywords, file paths, and acceptance criteria.
|
|
113
|
+
*/
|
|
114
|
+
export class StoryTypeDetector {
|
|
115
|
+
logger;
|
|
116
|
+
/**
|
|
117
|
+
* Create a new StoryTypeDetector instance
|
|
118
|
+
*
|
|
119
|
+
* @param logger - Logger instance for structured logging
|
|
120
|
+
*/
|
|
121
|
+
constructor(logger) {
|
|
122
|
+
this.logger = logger;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Detect story type from content
|
|
126
|
+
*
|
|
127
|
+
* Analyzes the story content (title, description, tasks, acceptance criteria)
|
|
128
|
+
* to determine if it's frontend, backend, or full-stack work.
|
|
129
|
+
*
|
|
130
|
+
* @param storyContent - Full story markdown content
|
|
131
|
+
* @returns Detection result with type, confidence, and matched keywords
|
|
132
|
+
*/
|
|
133
|
+
detectStoryType(storyContent) {
|
|
134
|
+
this.logger.debug('Detecting story type from content');
|
|
135
|
+
// Convert to lowercase for case-insensitive matching
|
|
136
|
+
const contentLower = storyContent.toLowerCase();
|
|
137
|
+
// Count keyword matches
|
|
138
|
+
const frontendMatches = this.countKeywordMatches(contentLower, FRONTEND_KEYWORDS);
|
|
139
|
+
const backendMatches = this.countKeywordMatches(contentLower, BACKEND_KEYWORDS);
|
|
140
|
+
// Check file path patterns
|
|
141
|
+
const frontendPathMatches = this.countPathPatternMatches(storyContent, FRONTEND_PATH_PATTERNS);
|
|
142
|
+
const backendPathMatches = this.countPathPatternMatches(storyContent, BACKEND_PATH_PATTERNS);
|
|
143
|
+
// Calculate weighted scores (keywords count more than paths)
|
|
144
|
+
const frontendScore = frontendMatches.count * 2 + frontendPathMatches.count;
|
|
145
|
+
const backendScore = backendMatches.count * 2 + backendPathMatches.count;
|
|
146
|
+
this.logger.debug({
|
|
147
|
+
backendScore,
|
|
148
|
+
frontendScore,
|
|
149
|
+
}, 'Story type detection scores');
|
|
150
|
+
// Determine type based on scores
|
|
151
|
+
let type;
|
|
152
|
+
let confidence;
|
|
153
|
+
let matchedKeywords;
|
|
154
|
+
if (frontendScore === 0 && backendScore === 0) {
|
|
155
|
+
// No clear indicators
|
|
156
|
+
type = 'unknown';
|
|
157
|
+
confidence = 0;
|
|
158
|
+
matchedKeywords = [];
|
|
159
|
+
}
|
|
160
|
+
else if (frontendScore > backendScore * 2) {
|
|
161
|
+
// Clearly frontend
|
|
162
|
+
type = 'frontend';
|
|
163
|
+
confidence = Math.min(frontendScore / 10, 1);
|
|
164
|
+
matchedKeywords = [...frontendMatches.keywords, ...frontendPathMatches.patterns];
|
|
165
|
+
}
|
|
166
|
+
else if (backendScore > frontendScore * 2) {
|
|
167
|
+
// Clearly backend
|
|
168
|
+
type = 'backend';
|
|
169
|
+
confidence = Math.min(backendScore / 10, 1);
|
|
170
|
+
matchedKeywords = [...backendMatches.keywords, ...backendPathMatches.patterns];
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
// Mixed signals - likely full-stack
|
|
174
|
+
type = 'fullstack';
|
|
175
|
+
confidence = Math.min((frontendScore + backendScore) / 15, 1);
|
|
176
|
+
matchedKeywords = [
|
|
177
|
+
...frontendMatches.keywords,
|
|
178
|
+
...backendMatches.keywords,
|
|
179
|
+
...frontendPathMatches.patterns,
|
|
180
|
+
...backendPathMatches.patterns,
|
|
181
|
+
];
|
|
182
|
+
}
|
|
183
|
+
this.logger.info({
|
|
184
|
+
confidence,
|
|
185
|
+
matchedCount: matchedKeywords.length,
|
|
186
|
+
type,
|
|
187
|
+
}, 'Story type detected');
|
|
188
|
+
return {
|
|
189
|
+
confidence,
|
|
190
|
+
matchedKeywords,
|
|
191
|
+
type,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get documentation references for a story type
|
|
196
|
+
*
|
|
197
|
+
* Returns the appropriate documentation file paths based on the detected story type.
|
|
198
|
+
* Saves tokens by only including relevant documentation.
|
|
199
|
+
*
|
|
200
|
+
* @param storyType - Detected story type
|
|
201
|
+
* @returns Array of documentation file paths to include
|
|
202
|
+
*/
|
|
203
|
+
getDocumentationReferences(storyType) {
|
|
204
|
+
const docs = {
|
|
205
|
+
backend: [
|
|
206
|
+
'docs/api/src/services/service-style-typescript.md',
|
|
207
|
+
'docs/api/src/handlers/handlers-guide-typescript.md',
|
|
208
|
+
'docs/api/dynamodb-tables.md',
|
|
209
|
+
'docs/api/multitenancy-guide.md',
|
|
210
|
+
],
|
|
211
|
+
frontend: ['docs/frontend-tenant-and-amplify-guide.md', 'docs/api/multitenancy-guide.md'],
|
|
212
|
+
};
|
|
213
|
+
switch (storyType) {
|
|
214
|
+
case 'backend': {
|
|
215
|
+
this.logger.debug('Returning backend documentation references');
|
|
216
|
+
return docs.backend;
|
|
217
|
+
}
|
|
218
|
+
case 'frontend': {
|
|
219
|
+
this.logger.debug('Returning frontend documentation references');
|
|
220
|
+
return docs.frontend;
|
|
221
|
+
}
|
|
222
|
+
case 'fullstack': {
|
|
223
|
+
this.logger.debug('Returning full-stack documentation references (frontend + backend)');
|
|
224
|
+
return [...docs.frontend, ...docs.backend];
|
|
225
|
+
}
|
|
226
|
+
default: {
|
|
227
|
+
this.logger.debug('No documentation references for unknown story type');
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Count keyword matches in content
|
|
234
|
+
* @private
|
|
235
|
+
*/
|
|
236
|
+
countKeywordMatches(content, keywords) {
|
|
237
|
+
const matched = [];
|
|
238
|
+
for (const keyword of keywords) {
|
|
239
|
+
if (content.includes(keyword)) {
|
|
240
|
+
matched.push(keyword);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return { count: matched.length, keywords: matched };
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Count file path pattern matches
|
|
247
|
+
* @private
|
|
248
|
+
*/
|
|
249
|
+
countPathPatternMatches(content, patterns) {
|
|
250
|
+
const matched = [];
|
|
251
|
+
for (const pattern of patterns) {
|
|
252
|
+
if (pattern.test(content)) {
|
|
253
|
+
matched.push(pattern.source);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { count: matched.length, patterns: matched };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TaskDecompositionService
|
|
3
|
+
*
|
|
4
|
+
* Decomposes large goals into executable task graphs with dependency management.
|
|
5
|
+
* Leverages Claude AI to intelligently break down complex objectives into
|
|
6
|
+
* small, actionable tasks with proper sequencing and parallelization.
|
|
7
|
+
*/
|
|
8
|
+
import type pino from 'pino';
|
|
9
|
+
import type { DecomposeOptions, TaskGraph } from '../../types/task-graph.js';
|
|
10
|
+
import type { AIProviderRunner } from '../agents/agent-runner.js';
|
|
11
|
+
import type { FileManager } from '../file-system/file-manager.js';
|
|
12
|
+
import type { GlobMatcher } from '../file-system/glob-matcher.js';
|
|
13
|
+
/**
|
|
14
|
+
* Service for decomposing goals into task graphs
|
|
15
|
+
*/
|
|
16
|
+
export declare class TaskDecompositionService {
|
|
17
|
+
private readonly agentRunner;
|
|
18
|
+
private readonly fileManager;
|
|
19
|
+
private readonly globMatcher;
|
|
20
|
+
private readonly logger;
|
|
21
|
+
constructor(agentRunner: AIProviderRunner, fileManager: FileManager, globMatcher: GlobMatcher, logger: pino.Logger);
|
|
22
|
+
/**
|
|
23
|
+
* Decompose a goal into an executable task graph
|
|
24
|
+
*
|
|
25
|
+
* @param options - Decomposition options including goal, context, and mode
|
|
26
|
+
* @param sessionDir - Directory where session outputs will be stored
|
|
27
|
+
* @returns Complete task graph with dependencies
|
|
28
|
+
*/
|
|
29
|
+
decomposeGoal(options: DecomposeOptions, sessionDir: string): Promise<TaskGraph>;
|
|
30
|
+
/**
|
|
31
|
+
* Build the decomposition prompt for Claude
|
|
32
|
+
*/
|
|
33
|
+
private buildDecompositionPrompt;
|
|
34
|
+
/**
|
|
35
|
+
* Build execution layers using topological sort (Kahn's algorithm)
|
|
36
|
+
* Each layer contains tasks that can run in parallel
|
|
37
|
+
*/
|
|
38
|
+
private buildExecutionLayers;
|
|
39
|
+
/**
|
|
40
|
+
* Detect circular dependencies using DFS
|
|
41
|
+
*/
|
|
42
|
+
private detectCircularDependencies;
|
|
43
|
+
/**
|
|
44
|
+
* Extract YAML content from output (handles markdown code fences)
|
|
45
|
+
*/
|
|
46
|
+
private extractYaml;
|
|
47
|
+
/**
|
|
48
|
+
* Gather target files using glob pattern
|
|
49
|
+
*/
|
|
50
|
+
private gatherTargetFiles;
|
|
51
|
+
/**
|
|
52
|
+
* Generate unique session ID
|
|
53
|
+
*/
|
|
54
|
+
private generateSessionId;
|
|
55
|
+
/**
|
|
56
|
+
* Parse Claude's YAML output into a structured TaskGraph
|
|
57
|
+
*/
|
|
58
|
+
private parseTaskGraph;
|
|
59
|
+
/**
|
|
60
|
+
* Validate YAML and ask Claude to fix it if invalid
|
|
61
|
+
*/
|
|
62
|
+
private validateAndFixYaml;
|
|
63
|
+
/**
|
|
64
|
+
* Validate task graph for common errors
|
|
65
|
+
*/
|
|
66
|
+
private validateTaskGraph;
|
|
67
|
+
}
|