@girardmedia/bootspring 2.0.36 → 2.0.37
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/cli/plan.js +602 -2
- package/core/planning/adaptive-engine.js +958 -0
- package/core/planning/feature-decomposer.js +772 -0
- package/core/planning/index.js +49 -0
- package/core/planning/simulator.js +1328 -0
- package/core/planning/stage-planner.js +624 -0
- package/intelligence/agent-router.js +795 -0
- package/intelligence/index.js +10 -0
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,772 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bootspring Feature Decomposer
|
|
3
|
+
*
|
|
4
|
+
* Intelligently breaks down features into layered implementation tasks
|
|
5
|
+
* with effort estimates, dependencies, critical path analysis, and risks.
|
|
6
|
+
*
|
|
7
|
+
* @package bootspring
|
|
8
|
+
* @module core/planning/feature-decomposer
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Feature patterns for intelligent decomposition
|
|
16
|
+
*/
|
|
17
|
+
const FEATURE_PATTERNS = {
|
|
18
|
+
auth: {
|
|
19
|
+
keywords: ['auth', 'login', 'register', 'signup', 'signin', 'password', 'session', 'jwt', 'oauth'],
|
|
20
|
+
database: [
|
|
21
|
+
{ task: 'Create User model', effort: 'small', deps: [] },
|
|
22
|
+
{ task: 'Create Session/Account model', effort: 'small', deps: ['User model'] },
|
|
23
|
+
{ task: 'Add authentication indexes', effort: 'small', deps: ['User model'] }
|
|
24
|
+
],
|
|
25
|
+
backend: [
|
|
26
|
+
{ task: 'Configure auth provider', effort: 'medium', deps: [] },
|
|
27
|
+
{ task: 'Create auth service layer', effort: 'medium', deps: ['User model'] },
|
|
28
|
+
{ task: 'Create auth middleware', effort: 'small', deps: ['Auth service'] },
|
|
29
|
+
{ task: 'Create login endpoint', effort: 'medium', deps: ['Auth service'] },
|
|
30
|
+
{ task: 'Create register endpoint', effort: 'medium', deps: ['Auth service'] },
|
|
31
|
+
{ task: 'Create logout endpoint', effort: 'small', deps: ['Auth service'] },
|
|
32
|
+
{ task: 'Implement session management', effort: 'medium', deps: ['Session model'] }
|
|
33
|
+
],
|
|
34
|
+
frontend: [
|
|
35
|
+
{ task: 'Create login form component', effort: 'medium', deps: [] },
|
|
36
|
+
{ task: 'Create register form component', effort: 'medium', deps: [] },
|
|
37
|
+
{ task: 'Create auth context/provider', effort: 'medium', deps: ['Auth endpoints'] },
|
|
38
|
+
{ task: 'Create protected route wrapper', effort: 'small', deps: ['Auth context'] },
|
|
39
|
+
{ task: 'Add auth state persistence', effort: 'small', deps: ['Auth context'] }
|
|
40
|
+
],
|
|
41
|
+
testing: [
|
|
42
|
+
{ task: 'Unit tests for auth service', effort: 'medium', deps: ['Auth service'] },
|
|
43
|
+
{ task: 'Integration tests for auth endpoints', effort: 'medium', deps: ['Auth endpoints'] },
|
|
44
|
+
{ task: 'E2E tests for login flow', effort: 'large', deps: ['Login form', 'Login endpoint'] }
|
|
45
|
+
],
|
|
46
|
+
risks: [
|
|
47
|
+
{ risk: 'Session security vulnerabilities', severity: 'high', mitigation: 'Use httpOnly cookies, implement token rotation' },
|
|
48
|
+
{ risk: 'Password storage', severity: 'high', mitigation: 'Use bcrypt with proper cost factor (12+)' },
|
|
49
|
+
{ risk: 'Brute force attacks', severity: 'medium', mitigation: 'Implement rate limiting and account lockout' }
|
|
50
|
+
],
|
|
51
|
+
patterns: ['auth/nextauth', 'auth/jwt', 'security/password-hashing', 'security/rate-limiting']
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
payment: {
|
|
55
|
+
keywords: ['payment', 'billing', 'stripe', 'subscription', 'checkout', 'invoice', 'pricing'],
|
|
56
|
+
database: [
|
|
57
|
+
{ task: 'Create Subscription model', effort: 'small', deps: ['User model'] },
|
|
58
|
+
{ task: 'Create Payment/Transaction model', effort: 'small', deps: ['User model'] },
|
|
59
|
+
{ task: 'Create Invoice model', effort: 'small', deps: ['User model'] },
|
|
60
|
+
{ task: 'Create Plan/Pricing model', effort: 'small', deps: [] }
|
|
61
|
+
],
|
|
62
|
+
backend: [
|
|
63
|
+
{ task: 'Configure Stripe SDK', effort: 'small', deps: [] },
|
|
64
|
+
{ task: 'Create payment service layer', effort: 'large', deps: ['Subscription model'] },
|
|
65
|
+
{ task: 'Create checkout session endpoint', effort: 'medium', deps: ['Payment service'] },
|
|
66
|
+
{ task: 'Create webhook handler', effort: 'large', deps: ['Payment service'] },
|
|
67
|
+
{ task: 'Create subscription management endpoints', effort: 'medium', deps: ['Payment service'] },
|
|
68
|
+
{ task: 'Create billing portal endpoint', effort: 'small', deps: ['Payment service'] }
|
|
69
|
+
],
|
|
70
|
+
frontend: [
|
|
71
|
+
{ task: 'Create pricing page', effort: 'medium', deps: [] },
|
|
72
|
+
{ task: 'Create checkout component', effort: 'medium', deps: ['Checkout endpoint'] },
|
|
73
|
+
{ task: 'Create subscription management UI', effort: 'medium', deps: ['Subscription endpoints'] },
|
|
74
|
+
{ task: 'Create billing history component', effort: 'medium', deps: ['Invoice model'] }
|
|
75
|
+
],
|
|
76
|
+
testing: [
|
|
77
|
+
{ task: 'Unit tests for payment service', effort: 'medium', deps: ['Payment service'] },
|
|
78
|
+
{ task: 'Webhook handler tests', effort: 'medium', deps: ['Webhook handler'] },
|
|
79
|
+
{ task: 'E2E checkout flow tests', effort: 'large', deps: ['Checkout component'] }
|
|
80
|
+
],
|
|
81
|
+
risks: [
|
|
82
|
+
{ risk: 'PCI compliance', severity: 'high', mitigation: 'Use Stripe Elements, never handle raw card data' },
|
|
83
|
+
{ risk: 'Webhook reliability', severity: 'medium', mitigation: 'Implement idempotency, verify signatures' },
|
|
84
|
+
{ risk: 'Subscription state sync', severity: 'medium', mitigation: 'Rely on webhooks as source of truth' }
|
|
85
|
+
],
|
|
86
|
+
patterns: ['payment/stripe', 'payment/webhooks', 'billing/subscriptions']
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
api: {
|
|
90
|
+
keywords: ['api', 'endpoint', 'rest', 'graphql', 'crud', 'resource'],
|
|
91
|
+
database: [
|
|
92
|
+
{ task: 'Create resource model', effort: 'small', deps: [] },
|
|
93
|
+
{ task: 'Add model relationships', effort: 'small', deps: ['Resource model'] },
|
|
94
|
+
{ task: 'Add indexes for queries', effort: 'small', deps: ['Resource model'] }
|
|
95
|
+
],
|
|
96
|
+
backend: [
|
|
97
|
+
{ task: 'Create validation schemas', effort: 'small', deps: [] },
|
|
98
|
+
{ task: 'Create service layer', effort: 'medium', deps: ['Resource model'] },
|
|
99
|
+
{ task: 'Create GET endpoint', effort: 'small', deps: ['Service layer'] },
|
|
100
|
+
{ task: 'Create POST endpoint', effort: 'small', deps: ['Service layer', 'Validation'] },
|
|
101
|
+
{ task: 'Create PUT endpoint', effort: 'small', deps: ['Service layer', 'Validation'] },
|
|
102
|
+
{ task: 'Create DELETE endpoint', effort: 'small', deps: ['Service layer'] },
|
|
103
|
+
{ task: 'Add pagination support', effort: 'small', deps: ['GET endpoint'] },
|
|
104
|
+
{ task: 'Add filtering support', effort: 'small', deps: ['GET endpoint'] }
|
|
105
|
+
],
|
|
106
|
+
frontend: [
|
|
107
|
+
{ task: 'Create API client functions', effort: 'small', deps: ['API endpoints'] },
|
|
108
|
+
{ task: 'Create data display component', effort: 'medium', deps: ['API client'] },
|
|
109
|
+
{ task: 'Create form component', effort: 'medium', deps: ['API client'] }
|
|
110
|
+
],
|
|
111
|
+
testing: [
|
|
112
|
+
{ task: 'Unit tests for service layer', effort: 'medium', deps: ['Service layer'] },
|
|
113
|
+
{ task: 'Integration tests for endpoints', effort: 'medium', deps: ['API endpoints'] },
|
|
114
|
+
{ task: 'Validation tests', effort: 'small', deps: ['Validation schemas'] }
|
|
115
|
+
],
|
|
116
|
+
risks: [
|
|
117
|
+
{ risk: 'Input validation', severity: 'high', mitigation: 'Use Zod/Yup for all inputs' },
|
|
118
|
+
{ risk: 'Authorization bypass', severity: 'high', mitigation: 'Check ownership on all mutations' },
|
|
119
|
+
{ risk: 'N+1 queries', severity: 'medium', mitigation: 'Use eager loading, add query limits' }
|
|
120
|
+
],
|
|
121
|
+
patterns: ['api/rest-crud', 'api/validation', 'api/pagination']
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
dashboard: {
|
|
125
|
+
keywords: ['dashboard', 'admin', 'analytics', 'metrics', 'charts', 'overview'],
|
|
126
|
+
database: [
|
|
127
|
+
{ task: 'Create analytics/metrics models', effort: 'small', deps: [] },
|
|
128
|
+
{ task: 'Add aggregation queries', effort: 'medium', deps: ['Metrics models'] }
|
|
129
|
+
],
|
|
130
|
+
backend: [
|
|
131
|
+
{ task: 'Create analytics service', effort: 'medium', deps: ['Metrics models'] },
|
|
132
|
+
{ task: 'Create dashboard data endpoint', effort: 'medium', deps: ['Analytics service'] },
|
|
133
|
+
{ task: 'Add data aggregation logic', effort: 'medium', deps: ['Analytics service'] },
|
|
134
|
+
{ task: 'Add caching for metrics', effort: 'small', deps: ['Analytics service'] }
|
|
135
|
+
],
|
|
136
|
+
frontend: [
|
|
137
|
+
{ task: 'Create dashboard layout', effort: 'medium', deps: [] },
|
|
138
|
+
{ task: 'Create metric cards/widgets', effort: 'medium', deps: ['Dashboard layout'] },
|
|
139
|
+
{ task: 'Create charts/visualizations', effort: 'large', deps: ['Dashboard data endpoint'] },
|
|
140
|
+
{ task: 'Add date range filtering', effort: 'small', deps: ['Dashboard layout'] },
|
|
141
|
+
{ task: 'Add real-time updates', effort: 'medium', deps: ['Dashboard data endpoint'] }
|
|
142
|
+
],
|
|
143
|
+
testing: [
|
|
144
|
+
{ task: 'Unit tests for analytics service', effort: 'medium', deps: ['Analytics service'] },
|
|
145
|
+
{ task: 'Component tests for widgets', effort: 'medium', deps: ['Widgets'] },
|
|
146
|
+
{ task: 'E2E tests for dashboard', effort: 'large', deps: ['Dashboard'] }
|
|
147
|
+
],
|
|
148
|
+
risks: [
|
|
149
|
+
{ risk: 'Performance with large datasets', severity: 'medium', mitigation: 'Pre-aggregate data, add caching' },
|
|
150
|
+
{ risk: 'Real-time update complexity', severity: 'low', mitigation: 'Start with polling, upgrade to WebSocket later' }
|
|
151
|
+
],
|
|
152
|
+
patterns: ['dashboard/metrics', 'frontend/charts', 'caching/redis']
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
upload: {
|
|
156
|
+
keywords: ['upload', 'file', 'image', 'media', 'storage', 's3', 'cloudinary'],
|
|
157
|
+
database: [
|
|
158
|
+
{ task: 'Create File/Media model', effort: 'small', deps: [] },
|
|
159
|
+
{ task: 'Add file metadata fields', effort: 'small', deps: ['File model'] }
|
|
160
|
+
],
|
|
161
|
+
backend: [
|
|
162
|
+
{ task: 'Configure storage provider', effort: 'small', deps: [] },
|
|
163
|
+
{ task: 'Create upload service', effort: 'medium', deps: ['File model', 'Storage provider'] },
|
|
164
|
+
{ task: 'Create upload endpoint', effort: 'medium', deps: ['Upload service'] },
|
|
165
|
+
{ task: 'Create delete endpoint', effort: 'small', deps: ['Upload service'] },
|
|
166
|
+
{ task: 'Add file validation', effort: 'small', deps: ['Upload endpoint'] },
|
|
167
|
+
{ task: 'Add image processing', effort: 'medium', deps: ['Upload service'] }
|
|
168
|
+
],
|
|
169
|
+
frontend: [
|
|
170
|
+
{ task: 'Create file upload component', effort: 'medium', deps: [] },
|
|
171
|
+
{ task: 'Add drag-and-drop support', effort: 'small', deps: ['Upload component'] },
|
|
172
|
+
{ task: 'Add progress indicator', effort: 'small', deps: ['Upload component'] },
|
|
173
|
+
{ task: 'Create file preview component', effort: 'small', deps: ['Upload component'] }
|
|
174
|
+
],
|
|
175
|
+
testing: [
|
|
176
|
+
{ task: 'Unit tests for upload service', effort: 'medium', deps: ['Upload service'] },
|
|
177
|
+
{ task: 'Integration tests with mock storage', effort: 'medium', deps: ['Upload endpoint'] },
|
|
178
|
+
{ task: 'File validation tests', effort: 'small', deps: ['File validation'] }
|
|
179
|
+
],
|
|
180
|
+
risks: [
|
|
181
|
+
{ risk: 'Malicious file uploads', severity: 'high', mitigation: 'Validate file types, scan for malware' },
|
|
182
|
+
{ risk: 'Storage costs', severity: 'medium', mitigation: 'Set size limits, implement cleanup' },
|
|
183
|
+
{ risk: 'Large file handling', severity: 'medium', mitigation: 'Use chunked uploads, streaming' }
|
|
184
|
+
],
|
|
185
|
+
patterns: ['upload/s3', 'upload/cloudinary', 'security/file-validation']
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
notification: {
|
|
189
|
+
keywords: ['notification', 'email', 'push', 'alert', 'messaging', 'sms'],
|
|
190
|
+
database: [
|
|
191
|
+
{ task: 'Create Notification model', effort: 'small', deps: ['User model'] },
|
|
192
|
+
{ task: 'Create NotificationPreference model', effort: 'small', deps: ['User model'] }
|
|
193
|
+
],
|
|
194
|
+
backend: [
|
|
195
|
+
{ task: 'Configure email provider', effort: 'small', deps: [] },
|
|
196
|
+
{ task: 'Create notification service', effort: 'medium', deps: ['Notification model'] },
|
|
197
|
+
{ task: 'Create email templates', effort: 'medium', deps: ['Email provider'] },
|
|
198
|
+
{ task: 'Create push notification service', effort: 'medium', deps: ['Notification service'] },
|
|
199
|
+
{ task: 'Create notification preferences endpoint', effort: 'small', deps: ['Preference model'] },
|
|
200
|
+
{ task: 'Add notification queue', effort: 'medium', deps: ['Notification service'] }
|
|
201
|
+
],
|
|
202
|
+
frontend: [
|
|
203
|
+
{ task: 'Create notification bell component', effort: 'small', deps: [] },
|
|
204
|
+
{ task: 'Create notification dropdown/panel', effort: 'medium', deps: ['Notification endpoint'] },
|
|
205
|
+
{ task: 'Create preferences settings UI', effort: 'medium', deps: ['Preferences endpoint'] },
|
|
206
|
+
{ task: 'Add real-time notification updates', effort: 'medium', deps: ['Notification panel'] }
|
|
207
|
+
],
|
|
208
|
+
testing: [
|
|
209
|
+
{ task: 'Unit tests for notification service', effort: 'medium', deps: ['Notification service'] },
|
|
210
|
+
{ task: 'Email template tests', effort: 'small', deps: ['Email templates'] },
|
|
211
|
+
{ task: 'Integration tests', effort: 'medium', deps: ['Notification endpoints'] }
|
|
212
|
+
],
|
|
213
|
+
risks: [
|
|
214
|
+
{ risk: 'Email deliverability', severity: 'medium', mitigation: 'Use reputable provider, set up SPF/DKIM' },
|
|
215
|
+
{ risk: 'Notification spam', severity: 'medium', mitigation: 'Respect preferences, add rate limiting' }
|
|
216
|
+
],
|
|
217
|
+
patterns: ['notification/email', 'notification/push', 'messaging/queues']
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
search: {
|
|
221
|
+
keywords: ['search', 'filter', 'query', 'elasticsearch', 'algolia', 'fulltext'],
|
|
222
|
+
database: [
|
|
223
|
+
{ task: 'Add search indexes', effort: 'small', deps: [] },
|
|
224
|
+
{ task: 'Configure full-text search', effort: 'medium', deps: [] }
|
|
225
|
+
],
|
|
226
|
+
backend: [
|
|
227
|
+
{ task: 'Create search service', effort: 'medium', deps: ['Search indexes'] },
|
|
228
|
+
{ task: 'Create search endpoint', effort: 'medium', deps: ['Search service'] },
|
|
229
|
+
{ task: 'Add filtering capabilities', effort: 'medium', deps: ['Search endpoint'] },
|
|
230
|
+
{ task: 'Add faceted search', effort: 'medium', deps: ['Search service'] },
|
|
231
|
+
{ task: 'Implement search suggestions', effort: 'medium', deps: ['Search service'] }
|
|
232
|
+
],
|
|
233
|
+
frontend: [
|
|
234
|
+
{ task: 'Create search input component', effort: 'small', deps: [] },
|
|
235
|
+
{ task: 'Create search results component', effort: 'medium', deps: ['Search endpoint'] },
|
|
236
|
+
{ task: 'Add autocomplete/suggestions', effort: 'medium', deps: ['Suggestions endpoint'] },
|
|
237
|
+
{ task: 'Create filter sidebar', effort: 'medium', deps: ['Filtering'] }
|
|
238
|
+
],
|
|
239
|
+
testing: [
|
|
240
|
+
{ task: 'Unit tests for search service', effort: 'medium', deps: ['Search service'] },
|
|
241
|
+
{ task: 'Search accuracy tests', effort: 'medium', deps: ['Search endpoint'] },
|
|
242
|
+
{ task: 'Performance tests', effort: 'medium', deps: ['Search endpoint'] }
|
|
243
|
+
],
|
|
244
|
+
risks: [
|
|
245
|
+
{ risk: 'Search performance', severity: 'medium', mitigation: 'Use dedicated search engine for scale' },
|
|
246
|
+
{ risk: 'Index synchronization', severity: 'medium', mitigation: 'Use event-driven indexing' }
|
|
247
|
+
],
|
|
248
|
+
patterns: ['search/fulltext', 'search/elasticsearch', 'search/algolia']
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Effort mappings to time estimates
|
|
254
|
+
*/
|
|
255
|
+
const EFFORT_ESTIMATES = {
|
|
256
|
+
small: { time: '30min-1h', points: 1 },
|
|
257
|
+
medium: { time: '1-3h', points: 3 },
|
|
258
|
+
large: { time: '3-6h', points: 5 },
|
|
259
|
+
xlarge: { time: '6-12h', points: 8 }
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Feature Decomposer class
|
|
264
|
+
*/
|
|
265
|
+
class FeatureDecomposer {
|
|
266
|
+
constructor(projectRoot) {
|
|
267
|
+
this.projectRoot = projectRoot;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Decompose a feature into implementation tasks
|
|
272
|
+
*/
|
|
273
|
+
async decompose(feature, options = {}) {
|
|
274
|
+
const { depth = 'standard', includePatterns = true } = options;
|
|
275
|
+
|
|
276
|
+
// Classify the feature
|
|
277
|
+
const classification = this.classifyFeature(feature);
|
|
278
|
+
|
|
279
|
+
// Get base decomposition
|
|
280
|
+
let decomposition;
|
|
281
|
+
if (classification.pattern) {
|
|
282
|
+
decomposition = this.decomposeFromPattern(feature, classification);
|
|
283
|
+
} else {
|
|
284
|
+
decomposition = this.decomposeGeneric(feature);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Analyze project context
|
|
288
|
+
const projectContext = await this.analyzeProjectContext();
|
|
289
|
+
|
|
290
|
+
// Adjust for project context
|
|
291
|
+
decomposition = this.adjustForContext(decomposition, projectContext);
|
|
292
|
+
|
|
293
|
+
// Calculate metrics
|
|
294
|
+
const metrics = this.calculateMetrics(decomposition);
|
|
295
|
+
|
|
296
|
+
// Generate critical path
|
|
297
|
+
const criticalPath = this.calculateCriticalPath(decomposition);
|
|
298
|
+
|
|
299
|
+
// Find recommended patterns
|
|
300
|
+
const patterns = includePatterns
|
|
301
|
+
? await this.findRecommendedPatterns(feature, classification)
|
|
302
|
+
: [];
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
feature,
|
|
306
|
+
classification: classification.type,
|
|
307
|
+
confidence: classification.confidence,
|
|
308
|
+
complexity: metrics.complexity,
|
|
309
|
+
estimatedEffort: metrics.totalEffort,
|
|
310
|
+
estimatedPoints: metrics.totalPoints,
|
|
311
|
+
|
|
312
|
+
layers: decomposition.layers,
|
|
313
|
+
risks: decomposition.risks,
|
|
314
|
+
criticalPath,
|
|
315
|
+
|
|
316
|
+
metrics: {
|
|
317
|
+
totalTasks: metrics.totalTasks,
|
|
318
|
+
byLayer: metrics.byLayer,
|
|
319
|
+
byEffort: metrics.byEffort
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
recommendedPatterns: patterns,
|
|
323
|
+
suggestedOrder: this.generateSuggestedOrder(decomposition),
|
|
324
|
+
|
|
325
|
+
// Deep analysis additions
|
|
326
|
+
...(depth === 'deep' && {
|
|
327
|
+
dependencies: this.mapAllDependencies(decomposition),
|
|
328
|
+
parallelizableTasks: this.findParallelizable(decomposition),
|
|
329
|
+
blockers: this.identifyBlockers(decomposition, projectContext)
|
|
330
|
+
})
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Classify the feature type
|
|
336
|
+
*/
|
|
337
|
+
classifyFeature(feature) {
|
|
338
|
+
const featureLower = feature.toLowerCase();
|
|
339
|
+
let bestMatch = null;
|
|
340
|
+
let bestScore = 0;
|
|
341
|
+
|
|
342
|
+
for (const [patternName, pattern] of Object.entries(FEATURE_PATTERNS)) {
|
|
343
|
+
let score = 0;
|
|
344
|
+
for (const keyword of pattern.keywords) {
|
|
345
|
+
if (featureLower.includes(keyword)) {
|
|
346
|
+
score += keyword.length; // Longer matches = higher score
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (score > bestScore) {
|
|
351
|
+
bestScore = score;
|
|
352
|
+
bestMatch = patternName;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (bestMatch && bestScore > 0) {
|
|
357
|
+
return {
|
|
358
|
+
type: bestMatch,
|
|
359
|
+
pattern: FEATURE_PATTERNS[bestMatch],
|
|
360
|
+
confidence: Math.min(bestScore / 10, 1)
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
type: 'generic',
|
|
366
|
+
pattern: null,
|
|
367
|
+
confidence: 0
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Decompose from a known pattern
|
|
373
|
+
*/
|
|
374
|
+
decomposeFromPattern(feature, classification) {
|
|
375
|
+
const pattern = classification.pattern;
|
|
376
|
+
|
|
377
|
+
return {
|
|
378
|
+
layers: {
|
|
379
|
+
database: [...pattern.database],
|
|
380
|
+
backend: [...pattern.backend],
|
|
381
|
+
frontend: [...pattern.frontend],
|
|
382
|
+
testing: [...pattern.testing],
|
|
383
|
+
documentation: [
|
|
384
|
+
{ task: 'Update API documentation', effort: 'small', deps: ['API endpoints'] },
|
|
385
|
+
{ task: 'Update user guide', effort: 'small', deps: ['Frontend'] }
|
|
386
|
+
]
|
|
387
|
+
},
|
|
388
|
+
risks: [...pattern.risks]
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Generic decomposition for unknown features
|
|
394
|
+
*/
|
|
395
|
+
decomposeGeneric(feature) {
|
|
396
|
+
// Extract likely entity name
|
|
397
|
+
const entityName = this.extractEntityName(feature);
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
layers: {
|
|
401
|
+
database: [
|
|
402
|
+
{ task: `Create ${entityName} model`, effort: 'small', deps: [] },
|
|
403
|
+
{ task: 'Add model relationships', effort: 'small', deps: [`${entityName} model`] },
|
|
404
|
+
{ task: 'Add database indexes', effort: 'small', deps: [`${entityName} model`] }
|
|
405
|
+
],
|
|
406
|
+
backend: [
|
|
407
|
+
{ task: 'Create validation schema', effort: 'small', deps: [] },
|
|
408
|
+
{ task: `Create ${entityName} service`, effort: 'medium', deps: [`${entityName} model`] },
|
|
409
|
+
{ task: 'Create API endpoints', effort: 'medium', deps: [`${entityName} service`] },
|
|
410
|
+
{ task: 'Add business logic', effort: 'medium', deps: [`${entityName} service`] }
|
|
411
|
+
],
|
|
412
|
+
frontend: [
|
|
413
|
+
{ task: `Create ${entityName} list component`, effort: 'medium', deps: [] },
|
|
414
|
+
{ task: `Create ${entityName} form component`, effort: 'medium', deps: [] },
|
|
415
|
+
{ task: `Create ${entityName} detail component`, effort: 'medium', deps: [] },
|
|
416
|
+
{ task: 'Connect to API', effort: 'small', deps: ['API endpoints'] }
|
|
417
|
+
],
|
|
418
|
+
testing: [
|
|
419
|
+
{ task: 'Unit tests for service', effort: 'medium', deps: [`${entityName} service`] },
|
|
420
|
+
{ task: 'API integration tests', effort: 'medium', deps: ['API endpoints'] },
|
|
421
|
+
{ task: 'Component tests', effort: 'medium', deps: ['Components'] }
|
|
422
|
+
],
|
|
423
|
+
documentation: [
|
|
424
|
+
{ task: 'Document API endpoints', effort: 'small', deps: ['API endpoints'] }
|
|
425
|
+
]
|
|
426
|
+
},
|
|
427
|
+
risks: [
|
|
428
|
+
{ risk: 'Scope creep', severity: 'medium', mitigation: 'Define clear acceptance criteria' },
|
|
429
|
+
{ risk: 'Integration complexity', severity: 'low', mitigation: 'Build incrementally with tests' }
|
|
430
|
+
]
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Extract entity name from feature description
|
|
436
|
+
*/
|
|
437
|
+
extractEntityName(feature) {
|
|
438
|
+
const words = feature.split(/\s+/);
|
|
439
|
+
|
|
440
|
+
// Look for capitalized words or nouns
|
|
441
|
+
for (const word of words) {
|
|
442
|
+
if (/^[A-Z][a-z]+$/.test(word)) {
|
|
443
|
+
return word;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Fall back to first significant word
|
|
448
|
+
const stopWords = ['add', 'create', 'implement', 'build', 'make', 'the', 'a', 'an', 'for', 'to', 'with'];
|
|
449
|
+
for (const word of words) {
|
|
450
|
+
if (!stopWords.includes(word.toLowerCase()) && word.length > 2) {
|
|
451
|
+
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return 'Entity';
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Analyze project context
|
|
460
|
+
*/
|
|
461
|
+
async analyzeProjectContext() {
|
|
462
|
+
const context = {
|
|
463
|
+
hasDatabase: false,
|
|
464
|
+
databaseType: null,
|
|
465
|
+
hasAuth: false,
|
|
466
|
+
hasTests: false,
|
|
467
|
+
framework: null
|
|
468
|
+
};
|
|
469
|
+
|
|
470
|
+
try {
|
|
471
|
+
// Check for Prisma
|
|
472
|
+
if (fs.existsSync(path.join(this.projectRoot, 'prisma', 'schema.prisma'))) {
|
|
473
|
+
context.hasDatabase = true;
|
|
474
|
+
context.databaseType = 'prisma';
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Check for auth
|
|
478
|
+
const authPaths = ['app/api/auth', 'pages/api/auth', 'src/lib/auth'];
|
|
479
|
+
for (const authPath of authPaths) {
|
|
480
|
+
if (fs.existsSync(path.join(this.projectRoot, authPath))) {
|
|
481
|
+
context.hasAuth = true;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Check for tests
|
|
487
|
+
const testPaths = ['__tests__', 'tests', 'test', 'spec'];
|
|
488
|
+
for (const testPath of testPaths) {
|
|
489
|
+
if (fs.existsSync(path.join(this.projectRoot, testPath))) {
|
|
490
|
+
context.hasTests = true;
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Check for framework
|
|
496
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
497
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
498
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
499
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
500
|
+
|
|
501
|
+
if (deps.next) context.framework = 'nextjs';
|
|
502
|
+
else if (deps.react) context.framework = 'react';
|
|
503
|
+
else if (deps.vue) context.framework = 'vue';
|
|
504
|
+
else if (deps.express) context.framework = 'express';
|
|
505
|
+
}
|
|
506
|
+
} catch (_err) {
|
|
507
|
+
// Context analysis failed, use defaults
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return context;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Adjust decomposition based on project context
|
|
515
|
+
*/
|
|
516
|
+
adjustForContext(decomposition, context) {
|
|
517
|
+
const adjusted = JSON.parse(JSON.stringify(decomposition));
|
|
518
|
+
|
|
519
|
+
// If no database, skip database layer or add setup task
|
|
520
|
+
if (!context.hasDatabase && adjusted.layers.database.length > 0) {
|
|
521
|
+
adjusted.layers.database.unshift({
|
|
522
|
+
task: 'Set up database (Prisma)',
|
|
523
|
+
effort: 'medium',
|
|
524
|
+
deps: []
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// If no auth but feature needs it, add auth dependency
|
|
529
|
+
if (!context.hasAuth) {
|
|
530
|
+
const needsAuth = adjusted.layers.backend.some(t =>
|
|
531
|
+
t.task.toLowerCase().includes('auth') ||
|
|
532
|
+
t.deps?.some(d => d.toLowerCase().includes('user'))
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
if (needsAuth) {
|
|
536
|
+
adjusted.risks.push({
|
|
537
|
+
risk: 'Authentication not set up',
|
|
538
|
+
severity: 'high',
|
|
539
|
+
mitigation: 'Implement authentication first or use bootspring preseed auth'
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// If no tests, add test setup task
|
|
545
|
+
if (!context.hasTests && adjusted.layers.testing.length > 0) {
|
|
546
|
+
adjusted.layers.testing.unshift({
|
|
547
|
+
task: 'Set up testing framework',
|
|
548
|
+
effort: 'medium',
|
|
549
|
+
deps: []
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return adjusted;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Calculate metrics for decomposition
|
|
558
|
+
*/
|
|
559
|
+
calculateMetrics(decomposition) {
|
|
560
|
+
let totalTasks = 0;
|
|
561
|
+
let totalPoints = 0;
|
|
562
|
+
const byLayer = {};
|
|
563
|
+
const byEffort = { small: 0, medium: 0, large: 0, xlarge: 0 };
|
|
564
|
+
|
|
565
|
+
for (const [layer, tasks] of Object.entries(decomposition.layers)) {
|
|
566
|
+
byLayer[layer] = tasks.length;
|
|
567
|
+
totalTasks += tasks.length;
|
|
568
|
+
|
|
569
|
+
for (const task of tasks) {
|
|
570
|
+
const effort = task.effort || 'medium';
|
|
571
|
+
byEffort[effort] = (byEffort[effort] || 0) + 1;
|
|
572
|
+
totalPoints += EFFORT_ESTIMATES[effort]?.points || 3;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Determine complexity
|
|
577
|
+
let complexity = 'low';
|
|
578
|
+
if (totalTasks > 15 || totalPoints > 40) complexity = 'high';
|
|
579
|
+
else if (totalTasks > 8 || totalPoints > 20) complexity = 'medium';
|
|
580
|
+
|
|
581
|
+
// Calculate total effort
|
|
582
|
+
const totalHours = totalPoints * 1.5; // Rough estimate
|
|
583
|
+
let totalEffort;
|
|
584
|
+
if (totalHours <= 8) totalEffort = '1 day';
|
|
585
|
+
else if (totalHours <= 24) totalEffort = '2-3 days';
|
|
586
|
+
else if (totalHours <= 40) totalEffort = '1 week';
|
|
587
|
+
else totalEffort = '2+ weeks';
|
|
588
|
+
|
|
589
|
+
return {
|
|
590
|
+
totalTasks,
|
|
591
|
+
totalPoints,
|
|
592
|
+
totalEffort,
|
|
593
|
+
complexity,
|
|
594
|
+
byLayer,
|
|
595
|
+
byEffort
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Calculate critical path
|
|
601
|
+
*/
|
|
602
|
+
calculateCriticalPath(decomposition) {
|
|
603
|
+
const allTasks = [];
|
|
604
|
+
const tasksByName = {};
|
|
605
|
+
|
|
606
|
+
// Flatten all tasks
|
|
607
|
+
for (const [layer, tasks] of Object.entries(decomposition.layers)) {
|
|
608
|
+
for (const task of tasks) {
|
|
609
|
+
const taskEntry = { ...task, layer };
|
|
610
|
+
allTasks.push(taskEntry);
|
|
611
|
+
tasksByName[task.task] = taskEntry;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Find tasks with most dependencies leading to them
|
|
616
|
+
const criticalPath = [];
|
|
617
|
+
|
|
618
|
+
// Start with database layer
|
|
619
|
+
const dbTasks = decomposition.layers.database || [];
|
|
620
|
+
if (dbTasks.length > 0) {
|
|
621
|
+
criticalPath.push(dbTasks[0].task);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Add backend service
|
|
625
|
+
const backendTasks = decomposition.layers.backend || [];
|
|
626
|
+
const serviceTasks = backendTasks.filter(t =>
|
|
627
|
+
t.task.toLowerCase().includes('service')
|
|
628
|
+
);
|
|
629
|
+
if (serviceTasks.length > 0) {
|
|
630
|
+
criticalPath.push(serviceTasks[0].task);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Add API endpoints
|
|
634
|
+
const endpointTasks = backendTasks.filter(t =>
|
|
635
|
+
t.task.toLowerCase().includes('endpoint')
|
|
636
|
+
);
|
|
637
|
+
if (endpointTasks.length > 0) {
|
|
638
|
+
criticalPath.push('API endpoints');
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Add frontend integration
|
|
642
|
+
const frontendTasks = decomposition.layers.frontend || [];
|
|
643
|
+
if (frontendTasks.length > 0) {
|
|
644
|
+
criticalPath.push('Frontend integration');
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return criticalPath;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Find recommended patterns from skills library
|
|
652
|
+
*/
|
|
653
|
+
async findRecommendedPatterns(feature, classification) {
|
|
654
|
+
const patterns = [];
|
|
655
|
+
|
|
656
|
+
if (classification.pattern?.patterns) {
|
|
657
|
+
patterns.push(...classification.pattern.patterns);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// Try to load patterns from skills library
|
|
661
|
+
try {
|
|
662
|
+
const skillsPath = path.join(this.projectRoot, 'node_modules', '@girardmedia', 'bootspring', 'skills', 'patterns');
|
|
663
|
+
if (fs.existsSync(skillsPath)) {
|
|
664
|
+
const catalogPath = path.join(skillsPath, 'catalog.json');
|
|
665
|
+
if (fs.existsSync(catalogPath)) {
|
|
666
|
+
const catalog = JSON.parse(fs.readFileSync(catalogPath, 'utf8'));
|
|
667
|
+
// Match patterns based on feature keywords
|
|
668
|
+
// This is a simplified version
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
} catch (_err) {
|
|
672
|
+
// Skills lookup failed, use defaults
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
return [...new Set(patterns)];
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Generate suggested implementation order
|
|
680
|
+
*/
|
|
681
|
+
generateSuggestedOrder(decomposition) {
|
|
682
|
+
return [
|
|
683
|
+
'1. Database models and migrations',
|
|
684
|
+
'2. Backend service layer',
|
|
685
|
+
'3. API endpoints with validation',
|
|
686
|
+
'4. Frontend components',
|
|
687
|
+
'5. Connect frontend to API',
|
|
688
|
+
'6. Testing',
|
|
689
|
+
'7. Documentation'
|
|
690
|
+
];
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Map all dependencies (for deep analysis)
|
|
695
|
+
*/
|
|
696
|
+
mapAllDependencies(decomposition) {
|
|
697
|
+
const deps = {};
|
|
698
|
+
|
|
699
|
+
for (const [layer, tasks] of Object.entries(decomposition.layers)) {
|
|
700
|
+
for (const task of tasks) {
|
|
701
|
+
deps[task.task] = {
|
|
702
|
+
layer,
|
|
703
|
+
effort: task.effort,
|
|
704
|
+
dependsOn: task.deps || [],
|
|
705
|
+
blockedBy: []
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Calculate blockedBy (reverse dependencies)
|
|
711
|
+
for (const [taskName, taskDeps] of Object.entries(deps)) {
|
|
712
|
+
for (const dep of taskDeps.dependsOn) {
|
|
713
|
+
if (deps[dep]) {
|
|
714
|
+
deps[dep].blockedBy.push(taskName);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return deps;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Find parallelizable tasks
|
|
724
|
+
*/
|
|
725
|
+
findParallelizable(decomposition) {
|
|
726
|
+
const parallelGroups = [];
|
|
727
|
+
|
|
728
|
+
// Tasks with no dependencies can run in parallel
|
|
729
|
+
for (const [layer, tasks] of Object.entries(decomposition.layers)) {
|
|
730
|
+
const noDeps = tasks.filter(t => !t.deps || t.deps.length === 0);
|
|
731
|
+
if (noDeps.length > 1) {
|
|
732
|
+
parallelGroups.push({
|
|
733
|
+
layer,
|
|
734
|
+
tasks: noDeps.map(t => t.task)
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
return parallelGroups;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Identify blockers
|
|
744
|
+
*/
|
|
745
|
+
identifyBlockers(decomposition, context) {
|
|
746
|
+
const blockers = [];
|
|
747
|
+
|
|
748
|
+
if (!context.hasDatabase) {
|
|
749
|
+
blockers.push({
|
|
750
|
+
type: 'infrastructure',
|
|
751
|
+
blocker: 'Database not configured',
|
|
752
|
+
resolution: 'Run: npx prisma init'
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
if (!context.hasAuth && decomposition.risks.some(r => r.risk.toLowerCase().includes('auth'))) {
|
|
757
|
+
blockers.push({
|
|
758
|
+
type: 'feature',
|
|
759
|
+
blocker: 'Authentication required',
|
|
760
|
+
resolution: 'Implement auth or use bootspring preseed auth'
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return blockers;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
module.exports = {
|
|
769
|
+
FeatureDecomposer,
|
|
770
|
+
FEATURE_PATTERNS,
|
|
771
|
+
EFFORT_ESTIMATES
|
|
772
|
+
};
|