@girardmedia/bootspring 1.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.
Files changed (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +255 -0
  3. package/agents/README.md +93 -0
  4. package/agents/api-expert/context.md +416 -0
  5. package/agents/architecture-expert/context.md +454 -0
  6. package/agents/backend-expert/context.md +483 -0
  7. package/agents/code-review-expert/context.md +365 -0
  8. package/agents/database-expert/context.md +250 -0
  9. package/agents/devops-expert/context.md +446 -0
  10. package/agents/frontend-expert/context.md +364 -0
  11. package/agents/index.js +140 -0
  12. package/agents/performance-expert/context.md +377 -0
  13. package/agents/security-expert/context.md +343 -0
  14. package/agents/testing-expert/context.md +414 -0
  15. package/agents/ui-ux-expert/context.md +448 -0
  16. package/agents/vercel-expert/context.md +426 -0
  17. package/bin/bootspring.js +310 -0
  18. package/cli/agent.js +337 -0
  19. package/cli/context.js +194 -0
  20. package/cli/dashboard.js +150 -0
  21. package/cli/generate.js +294 -0
  22. package/cli/init.js +410 -0
  23. package/cli/loop.js +421 -0
  24. package/cli/mcp.js +241 -0
  25. package/cli/memory.js +303 -0
  26. package/cli/orchestrator.js +400 -0
  27. package/cli/plugin.js +451 -0
  28. package/cli/quality.js +332 -0
  29. package/cli/skill.js +369 -0
  30. package/cli/task.js +628 -0
  31. package/cli/telemetry.js +114 -0
  32. package/cli/todo.js +614 -0
  33. package/cli/update.js +312 -0
  34. package/core/config.js +245 -0
  35. package/core/context.js +329 -0
  36. package/core/entitlements.js +209 -0
  37. package/core/index.js +43 -0
  38. package/core/policies.js +68 -0
  39. package/core/telemetry.js +247 -0
  40. package/core/utils.js +380 -0
  41. package/dashboard/server.js +818 -0
  42. package/docs/integrations/claude-code.md +42 -0
  43. package/docs/integrations/codex.md +42 -0
  44. package/docs/mcp-api-platform.md +102 -0
  45. package/generators/generate.js +598 -0
  46. package/generators/index.js +18 -0
  47. package/hooks/context-detector.js +177 -0
  48. package/hooks/index.js +35 -0
  49. package/hooks/prompt-enhancer.js +289 -0
  50. package/intelligence/git-memory.js +551 -0
  51. package/intelligence/index.js +59 -0
  52. package/intelligence/orchestrator.js +964 -0
  53. package/intelligence/prd.js +447 -0
  54. package/intelligence/recommendation-weights.json +18 -0
  55. package/intelligence/recommendations.js +234 -0
  56. package/mcp/capabilities.js +71 -0
  57. package/mcp/contracts/mcp-contract.v1.json +497 -0
  58. package/mcp/registry.js +213 -0
  59. package/mcp/response-formatter.js +462 -0
  60. package/mcp/server.js +99 -0
  61. package/mcp/tools/agent-tool.js +137 -0
  62. package/mcp/tools/capabilities-tool.js +54 -0
  63. package/mcp/tools/context-tool.js +49 -0
  64. package/mcp/tools/dashboard-tool.js +58 -0
  65. package/mcp/tools/generate-tool.js +46 -0
  66. package/mcp/tools/loop-tool.js +134 -0
  67. package/mcp/tools/memory-tool.js +180 -0
  68. package/mcp/tools/orchestrator-tool.js +232 -0
  69. package/mcp/tools/plugin-tool.js +76 -0
  70. package/mcp/tools/quality-tool.js +47 -0
  71. package/mcp/tools/skill-tool.js +233 -0
  72. package/mcp/tools/telemetry-tool.js +95 -0
  73. package/mcp/tools/todo-tool.js +133 -0
  74. package/package.json +98 -0
  75. package/plugins/index.js +141 -0
  76. package/quality/index.js +380 -0
  77. package/quality/lint-budgets.json +19 -0
  78. package/skills/index.js +787 -0
  79. package/skills/patterns/README.md +163 -0
  80. package/skills/patterns/api/route-handler.md +217 -0
  81. package/skills/patterns/api/server-action.md +249 -0
  82. package/skills/patterns/auth/clerk.md +132 -0
  83. package/skills/patterns/database/prisma.md +180 -0
  84. package/skills/patterns/payments/stripe.md +272 -0
  85. package/skills/patterns/security/validation.md +268 -0
  86. package/skills/patterns/testing/vitest.md +307 -0
  87. package/templates/bootspring.config.js +83 -0
  88. package/templates/mcp.json +9 -0
@@ -0,0 +1,551 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Bootspring Git Memory
5
+ *
6
+ * Extracts learnings, patterns, and decisions from git history.
7
+ * Replaces Ralph's progress.txt with structured git-based memory.
8
+ *
9
+ * @package bootspring
10
+ * @module intelligence/git-memory
11
+ */
12
+
13
+ const { execSync } = require('child_process');
14
+ const path = require('path');
15
+
16
+ /**
17
+ * Memory categories extracted from commits
18
+ */
19
+ const MEMORY_CATEGORIES = {
20
+ DECISION: {
21
+ patterns: [
22
+ /decided to/i, /chose to/i, /switched to/i, /migrated to/i,
23
+ /now using/i, /replaced .+ with/i, /prefer/i
24
+ ],
25
+ icon: '🎯',
26
+ label: 'Decisions'
27
+ },
28
+ BUGFIX: {
29
+ patterns: [
30
+ /fix(ed|es)?:/i, /bug(fix)?:/i, /resolved/i, /patched/i,
31
+ /was causing/i, /issue with/i, /broke/i, /fixed by/i
32
+ ],
33
+ icon: '🐛',
34
+ label: 'Bug Fixes & Gotchas'
35
+ },
36
+ PATTERN: {
37
+ patterns: [
38
+ /pattern/i, /refactor/i, /abstracted/i, /extracted/i,
39
+ /standardized/i, /convention/i, /approach/i
40
+ ],
41
+ icon: '🔧',
42
+ label: 'Patterns & Conventions'
43
+ },
44
+ ARCHITECTURE: {
45
+ patterns: [
46
+ /architect/i, /structure/i, /reorganiz/i, /module/i,
47
+ /split/i, /merged/i, /moved .+ to/i, /renamed/i
48
+ ],
49
+ icon: '🏗️',
50
+ label: 'Architecture'
51
+ },
52
+ DEPENDENCY: {
53
+ patterns: [
54
+ /added .+ dependency/i, /upgraded/i, /downgraded/i,
55
+ /pinned/i, /removed .+ package/i, /npm/i, /yarn/i
56
+ ],
57
+ icon: '📦',
58
+ label: 'Dependencies'
59
+ },
60
+ PERFORMANCE: {
61
+ patterns: [
62
+ /performance/i, /optimiz/i, /faster/i, /slower/i,
63
+ /memory/i, /cache/i, /lazy/i, /async/i
64
+ ],
65
+ icon: '⚡',
66
+ label: 'Performance'
67
+ },
68
+ SECURITY: {
69
+ patterns: [
70
+ /security/i, /vulnerab/i, /auth/i, /permission/i,
71
+ /sanitiz/i, /validat/i, /escape/i, /inject/i
72
+ ],
73
+ icon: '🔒',
74
+ label: 'Security'
75
+ },
76
+ CONFIG: {
77
+ patterns: [
78
+ /config/i, /environment/i, /\.env/i, /settings/i,
79
+ /setup/i, /initialize/i
80
+ ],
81
+ icon: '⚙️',
82
+ label: 'Configuration'
83
+ }
84
+ };
85
+
86
+ /**
87
+ * Execute git command and return output
88
+ */
89
+ function git(command, options = {}) {
90
+ try {
91
+ const cwd = options.cwd || process.cwd();
92
+ return execSync(`git ${command}`, {
93
+ cwd,
94
+ encoding: 'utf-8',
95
+ stdio: ['pipe', 'pipe', 'pipe']
96
+ }).trim();
97
+ } catch (e) {
98
+ if (options.throwOnError) throw e;
99
+ return '';
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Check if current directory is a git repo
105
+ */
106
+ function isGitRepo(cwd = process.cwd()) {
107
+ try {
108
+ git('rev-parse --git-dir', { cwd, throwOnError: true });
109
+ return true;
110
+ } catch {
111
+ return false;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Get recent commits with full messages
117
+ */
118
+ function getCommits(options = {}) {
119
+ const {
120
+ limit = 100,
121
+ since = null,
122
+ until = null,
123
+ author = null,
124
+ path: filePath = null,
125
+ cwd = process.cwd()
126
+ } = options;
127
+
128
+ // Use %x00 (null char) as record separator and ||| as field separator
129
+ // This handles multi-line commit bodies correctly
130
+ let command = `log --pretty=format:"%H|||%ai|||%an|||%s%x00" -n ${limit}`;
131
+
132
+ if (since) command += ` --since="${since}"`;
133
+ if (until) command += ` --until="${until}"`;
134
+ if (author) command += ` --author="${author}"`;
135
+ if (filePath) command += ` -- "${filePath}"`;
136
+
137
+ const output = git(command, { cwd });
138
+ if (!output) return [];
139
+
140
+ // Split by null character to get individual commits
141
+ return output.split('\x00')
142
+ .filter(record => record.trim())
143
+ .map(record => {
144
+ const [hash, date, author, subject] = record.trim().split('|||');
145
+ return {
146
+ hash: hash?.slice(0, 8),
147
+ date,
148
+ author,
149
+ subject,
150
+ body: '', // Body extraction removed for simplicity - subject is enough for categorization
151
+ fullMessage: subject || ''
152
+ };
153
+ })
154
+ .filter(c => c.hash && c.hash.length === 8);
155
+ }
156
+
157
+ /**
158
+ * Categorize a commit based on its message
159
+ */
160
+ function categorizeCommit(commit) {
161
+ const message = commit.fullMessage.toLowerCase();
162
+ const categories = [];
163
+
164
+ for (const [category, config] of Object.entries(MEMORY_CATEGORIES)) {
165
+ for (const pattern of config.patterns) {
166
+ if (pattern.test(commit.fullMessage)) {
167
+ categories.push(category);
168
+ break;
169
+ }
170
+ }
171
+ }
172
+
173
+ // Default to general if no category matched
174
+ if (categories.length === 0) {
175
+ categories.push('GENERAL');
176
+ }
177
+
178
+ return categories;
179
+ }
180
+
181
+ /**
182
+ * Extract files changed in a commit
183
+ */
184
+ function getCommitFiles(hash, cwd = process.cwd()) {
185
+ const output = git(`show --name-only --pretty=format:"" ${hash}`, { cwd });
186
+ return output.split('\n').filter(f => f.trim());
187
+ }
188
+
189
+ /**
190
+ * Extract learnings from commit history
191
+ */
192
+ function extractLearnings(options = {}) {
193
+ const {
194
+ limit = 50,
195
+ since = '3 months ago',
196
+ cwd = process.cwd(),
197
+ includeFiles = false
198
+ } = options;
199
+
200
+ if (!isGitRepo(cwd)) {
201
+ return { error: 'Not a git repository', learnings: [] };
202
+ }
203
+
204
+ const commits = getCommits({ limit, since, cwd });
205
+ const learnings = [];
206
+
207
+ for (const commit of commits) {
208
+ const categories = categorizeCommit(commit);
209
+
210
+ // Skip mundane commits
211
+ if (isMundaneCommit(commit)) continue;
212
+
213
+ const learning = {
214
+ hash: commit.hash,
215
+ date: commit.date,
216
+ categories,
217
+ summary: commit.subject,
218
+ details: commit.body || null,
219
+ importance: calculateImportance(commit, categories)
220
+ };
221
+
222
+ if (includeFiles) {
223
+ learning.files = getCommitFiles(commit.hash, cwd);
224
+ }
225
+
226
+ learnings.push(learning);
227
+ }
228
+
229
+ // Sort by importance
230
+ learnings.sort((a, b) => b.importance - a.importance);
231
+
232
+ return { learnings, total: commits.length };
233
+ }
234
+
235
+ /**
236
+ * Check if commit is mundane (low-value for memory)
237
+ */
238
+ function isMundaneCommit(commit) {
239
+ const mundanePatterns = [
240
+ /^merge/i,
241
+ /^wip/i,
242
+ /^temp/i,
243
+ /^bump version/i,
244
+ /^update changelog/i,
245
+ /^lint/i,
246
+ /^format/i,
247
+ /^typo/i,
248
+ /^minor/i,
249
+ /^small/i
250
+ ];
251
+
252
+ return mundanePatterns.some(p => p.test(commit.subject));
253
+ }
254
+
255
+ /**
256
+ * Calculate importance score for a learning
257
+ */
258
+ function calculateImportance(commit, categories) {
259
+ let score = 0;
260
+
261
+ // Category weights
262
+ const weights = {
263
+ DECISION: 10,
264
+ ARCHITECTURE: 9,
265
+ SECURITY: 8,
266
+ BUGFIX: 7,
267
+ PATTERN: 6,
268
+ PERFORMANCE: 5,
269
+ DEPENDENCY: 4,
270
+ CONFIG: 3,
271
+ GENERAL: 1
272
+ };
273
+
274
+ for (const cat of categories) {
275
+ score += weights[cat] || 1;
276
+ }
277
+
278
+ // Boost for longer messages (more context)
279
+ if (commit.body && commit.body.length > 50) {
280
+ score += 2;
281
+ }
282
+
283
+ // Boost for conventional commit prefixes
284
+ if (/^(feat|fix|refactor|perf|security):/i.test(commit.subject)) {
285
+ score += 2;
286
+ }
287
+
288
+ return score;
289
+ }
290
+
291
+ /**
292
+ * Group learnings by category
293
+ */
294
+ function groupByCategory(learnings) {
295
+ const grouped = {};
296
+
297
+ for (const learning of learnings) {
298
+ for (const category of learning.categories) {
299
+ if (!grouped[category]) {
300
+ grouped[category] = [];
301
+ }
302
+ grouped[category].push(learning);
303
+ }
304
+ }
305
+
306
+ return grouped;
307
+ }
308
+
309
+ /**
310
+ * Generate markdown summary of learnings
311
+ */
312
+ function toMarkdown(learnings, options = {}) {
313
+ const { title = 'Project Learnings', maxPerCategory = 5 } = options;
314
+ const grouped = groupByCategory(learnings);
315
+
316
+ let md = `## ${title}\n\n`;
317
+ md += `> Extracted from ${learnings.length} significant commits\n\n`;
318
+
319
+ for (const [category, items] of Object.entries(grouped)) {
320
+ const config = MEMORY_CATEGORIES[category] || { icon: '📝', label: category };
321
+ const topItems = items.slice(0, maxPerCategory);
322
+
323
+ md += `### ${config.icon} ${config.label}\n\n`;
324
+
325
+ for (const item of topItems) {
326
+ md += `- **${item.summary}**`;
327
+ if (item.details) {
328
+ // First line of details only
329
+ const firstLine = item.details.split('\n')[0].trim();
330
+ if (firstLine && firstLine.length < 100) {
331
+ md += ` - ${firstLine}`;
332
+ }
333
+ }
334
+ md += ` _(${item.hash})_\n`;
335
+ }
336
+
337
+ md += '\n';
338
+ }
339
+
340
+ return md;
341
+ }
342
+
343
+ /**
344
+ * Generate compact summary for CLAUDE.md injection
345
+ */
346
+ function toCompactSummary(learnings, options = {}) {
347
+ const { maxItems = 15 } = options;
348
+ const topLearnings = learnings.slice(0, maxItems);
349
+
350
+ let summary = `## Recent Learnings\n\n`;
351
+ summary += `Key decisions and patterns from recent development:\n\n`;
352
+
353
+ for (const learning of topLearnings) {
354
+ const categoryIcons = learning.categories
355
+ .map(c => MEMORY_CATEGORIES[c]?.icon || '📝')
356
+ .join('');
357
+
358
+ summary += `- ${categoryIcons} ${learning.summary}\n`;
359
+ }
360
+
361
+ return summary;
362
+ }
363
+
364
+ /**
365
+ * Search learnings by keyword
366
+ */
367
+ function searchLearnings(learnings, query) {
368
+ const queryLower = query.toLowerCase();
369
+
370
+ return learnings.filter(l => {
371
+ return l.summary.toLowerCase().includes(queryLower) ||
372
+ (l.details && l.details.toLowerCase().includes(queryLower)) ||
373
+ l.categories.some(c => c.toLowerCase().includes(queryLower));
374
+ });
375
+ }
376
+
377
+ /**
378
+ * Get learnings relevant to specific files
379
+ */
380
+ function getLearningsForFiles(files, options = {}) {
381
+ const { cwd = process.cwd(), limit = 20 } = options;
382
+ const learnings = [];
383
+
384
+ for (const file of files) {
385
+ const commits = getCommits({ limit: 10, path: file, cwd });
386
+
387
+ for (const commit of commits) {
388
+ if (!isMundaneCommit(commit)) {
389
+ const categories = categorizeCommit(commit);
390
+ learnings.push({
391
+ hash: commit.hash,
392
+ date: commit.date,
393
+ categories,
394
+ summary: commit.subject,
395
+ details: commit.body,
396
+ file,
397
+ importance: calculateImportance(commit, categories)
398
+ });
399
+ }
400
+ }
401
+ }
402
+
403
+ // Dedupe by hash and sort
404
+ const seen = new Set();
405
+ return learnings
406
+ .filter(l => {
407
+ if (seen.has(l.hash)) return false;
408
+ seen.add(l.hash);
409
+ return true;
410
+ })
411
+ .sort((a, b) => b.importance - a.importance)
412
+ .slice(0, limit);
413
+ }
414
+
415
+ /**
416
+ * Get summary stats about the repo
417
+ */
418
+ function getRepoStats(cwd = process.cwd()) {
419
+ if (!isGitRepo(cwd)) {
420
+ return null;
421
+ }
422
+
423
+ const totalCommits = parseInt(git('rev-list --count HEAD', { cwd }) || '0');
424
+ const firstCommit = git('log --reverse --pretty=format:"%ai" | head -1', { cwd });
425
+ const contributors = git('shortlog -sn --no-merges', { cwd })
426
+ .split('\n')
427
+ .filter(l => l.trim())
428
+ .length;
429
+
430
+ const recentActivity = git('log --since="1 week ago" --oneline', { cwd })
431
+ .split('\n')
432
+ .filter(l => l.trim())
433
+ .length;
434
+
435
+ return {
436
+ totalCommits,
437
+ firstCommit,
438
+ contributors,
439
+ recentActivity,
440
+ weeklyPace: recentActivity
441
+ };
442
+ }
443
+
444
+ // CLI
445
+ if (require.main === module) {
446
+ const args = process.argv.slice(2);
447
+ const command = args[0] || 'summary';
448
+
449
+ switch (command) {
450
+ case 'extract': {
451
+ const result = extractLearnings({
452
+ limit: parseInt(args[1]) || 50,
453
+ includeFiles: args.includes('--files')
454
+ });
455
+
456
+ if (result.error) {
457
+ console.error(result.error);
458
+ process.exit(1);
459
+ }
460
+
461
+ console.log(JSON.stringify(result.learnings, null, 2));
462
+ break;
463
+ }
464
+
465
+ case 'summary': {
466
+ const result = extractLearnings({ limit: 50 });
467
+
468
+ if (result.error) {
469
+ console.error(result.error);
470
+ process.exit(1);
471
+ }
472
+
473
+ console.log(toMarkdown(result.learnings));
474
+ break;
475
+ }
476
+
477
+ case 'compact': {
478
+ const result = extractLearnings({ limit: 30 });
479
+
480
+ if (result.error) {
481
+ console.error(result.error);
482
+ process.exit(1);
483
+ }
484
+
485
+ console.log(toCompactSummary(result.learnings));
486
+ break;
487
+ }
488
+
489
+ case 'search': {
490
+ const query = args.slice(1).join(' ');
491
+ if (!query) {
492
+ console.error('Usage: git-memory.js search <query>');
493
+ process.exit(1);
494
+ }
495
+
496
+ const result = extractLearnings({ limit: 100 });
497
+ const matches = searchLearnings(result.learnings, query);
498
+
499
+ console.log(`Found ${matches.length} matches for "${query}":\n`);
500
+ for (const match of matches.slice(0, 10)) {
501
+ console.log(` [${match.hash}] ${match.summary}`);
502
+ }
503
+ break;
504
+ }
505
+
506
+ case 'stats': {
507
+ const stats = getRepoStats();
508
+ if (!stats) {
509
+ console.error('Not a git repository');
510
+ process.exit(1);
511
+ }
512
+ console.log(JSON.stringify(stats, null, 2));
513
+ break;
514
+ }
515
+
516
+ case 'categories': {
517
+ console.log('\nAvailable categories:\n');
518
+ for (const [key, config] of Object.entries(MEMORY_CATEGORIES)) {
519
+ console.log(` ${config.icon} ${key.padEnd(15)} ${config.label}`);
520
+ }
521
+ console.log();
522
+ break;
523
+ }
524
+
525
+ default:
526
+ console.log(`
527
+ Bootspring Git Memory
528
+
529
+ Usage:
530
+ git-memory.js extract [limit] [--files] Extract learnings as JSON
531
+ git-memory.js summary Generate markdown summary
532
+ git-memory.js compact Compact summary for CLAUDE.md
533
+ git-memory.js search <query> Search learnings
534
+ git-memory.js stats Repository statistics
535
+ git-memory.js categories List memory categories
536
+ `);
537
+ }
538
+ }
539
+
540
+ module.exports = {
541
+ isGitRepo,
542
+ getCommits,
543
+ extractLearnings,
544
+ groupByCategory,
545
+ toMarkdown,
546
+ toCompactSummary,
547
+ searchLearnings,
548
+ getLearningsForFiles,
549
+ getRepoStats,
550
+ MEMORY_CATEGORIES
551
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Bootspring Intelligence Module
3
+ *
4
+ * Provides intelligent agent orchestration, context analysis,
5
+ * workflow management, and autonomous loop execution.
6
+ *
7
+ * @package bootspring
8
+ * @module intelligence
9
+ */
10
+
11
+ const orchestrator = require('./orchestrator');
12
+ const prd = require('./prd');
13
+ const gitMemory = require('./git-memory');
14
+ const recommendations = require('./recommendations');
15
+
16
+ module.exports = {
17
+ // Orchestrator functions
18
+ analyzeContext: orchestrator.analyzeContext,
19
+ getWorkflow: orchestrator.getWorkflow,
20
+ listWorkflows: orchestrator.listWorkflows,
21
+ startWorkflow: orchestrator.startWorkflow,
22
+ advanceWorkflow: orchestrator.advanceWorkflow,
23
+ markWorkflowCheckpoint: orchestrator.markWorkflowCheckpoint,
24
+ getWorkflowSignalProgress: orchestrator.getWorkflowSignalProgress,
25
+ loadState: orchestrator.loadState,
26
+ saveState: orchestrator.saveState,
27
+ getCurrentPhase: orchestrator.getCurrentPhase,
28
+ agentExists: orchestrator.agentExists,
29
+ getAvailableAgents: orchestrator.getAvailableAgents,
30
+
31
+ // PRD functions
32
+ createPRD: prd.createPRD,
33
+ loadPRD: prd.loadPRD,
34
+ savePRD: prd.savePRD,
35
+ getNextStory: prd.getNextStory,
36
+ updateStoryStatus: prd.updateStoryStatus,
37
+ getPRDProgress: prd.getProgress,
38
+ isPRDComplete: prd.isComplete,
39
+ parseMarkdownPRD: prd.parseMarkdownPRD,
40
+ prdToMarkdown: prd.toMarkdown,
41
+
42
+ // Git Memory functions
43
+ extractLearnings: gitMemory.extractLearnings,
44
+ getLearningsForFiles: gitMemory.getLearningsForFiles,
45
+ searchLearnings: gitMemory.searchLearnings,
46
+ learningsToMarkdown: gitMemory.toMarkdown,
47
+ learningsToCompact: gitMemory.toCompactSummary,
48
+ getRepoStats: gitMemory.getRepoStats,
49
+ isGitRepo: gitMemory.isGitRepo,
50
+ MEMORY_CATEGORIES: gitMemory.MEMORY_CATEGORIES,
51
+
52
+ // Recommendation engine
53
+ createRecommendationsEngine: recommendations.createRecommendationsEngine,
54
+
55
+ // Constants
56
+ PHASE_AGENTS: orchestrator.PHASE_AGENTS,
57
+ TECHNICAL_TRIGGERS: orchestrator.TECHNICAL_TRIGGERS,
58
+ WORKFLOWS: orchestrator.WORKFLOWS
59
+ };