@codebakers/cli 3.7.2 β†’ 3.8.1

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.
@@ -0,0 +1,808 @@
1
+ "use strict";
2
+ /**
3
+ * ENGINEERING TOOLS FOR MCP
4
+ *
5
+ * MCP tools for the AI agent-based engineering workflow.
6
+ * These tools enable enterprise-grade software development with NO FRICTION.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.ENGINEERING_TOOLS = void 0;
10
+ exports.handleEngineeringTool = handleEngineeringTool;
11
+ const engineering_state_js_1 = require("../lib/engineering-state.js");
12
+ // =============================================================================
13
+ // TOOL DEFINITIONS
14
+ // =============================================================================
15
+ exports.ENGINEERING_TOOLS = [
16
+ {
17
+ name: 'engineering_start',
18
+ description: 'Start a new engineering build project. This launches the scoping wizard to define what you\'re building. Use when user says "build me a...", "create a new app", "start a new project", or wants to build something from scratch with enterprise-grade engineering process. Returns the first scoping question.',
19
+ inputSchema: {
20
+ type: 'object',
21
+ properties: {
22
+ projectName: {
23
+ type: 'string',
24
+ description: 'Name for the project (will be used for folder name)',
25
+ },
26
+ description: {
27
+ type: 'string',
28
+ description: 'Brief description of what you\'re building',
29
+ },
30
+ },
31
+ required: ['projectName'],
32
+ },
33
+ },
34
+ {
35
+ name: 'engineering_scope',
36
+ description: 'Answer a scoping wizard question. Called after engineering_start to progressively define the project scope. Returns the next question or signals scoping is complete.',
37
+ inputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ stepId: {
41
+ type: 'string',
42
+ description: 'ID of the scoping step being answered',
43
+ },
44
+ answer: {
45
+ type: 'string',
46
+ description: 'User\'s answer (for text/single), or JSON array for multiple selection',
47
+ },
48
+ },
49
+ required: ['stepId', 'answer'],
50
+ },
51
+ },
52
+ {
53
+ name: 'engineering_status',
54
+ description: 'Get the current engineering build status. Shows which phase you\'re in, overall progress, and what\'s next. Use when user asks "where am I?", "show progress", "what phase?", or "what\'s the status?".',
55
+ inputSchema: {
56
+ type: 'object',
57
+ properties: {},
58
+ },
59
+ },
60
+ {
61
+ name: 'engineering_advance',
62
+ description: 'Advance to the next engineering phase after completing the current one. Use after all work in the current phase is done and the gate is passed.',
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ artifacts: {
67
+ type: 'array',
68
+ items: { type: 'string' },
69
+ description: 'List of artifacts produced in this phase (e.g., ["prd.md", "tech-spec.md"])',
70
+ },
71
+ },
72
+ },
73
+ },
74
+ {
75
+ name: 'engineering_gate',
76
+ description: 'Pass or fail the current phase gate. Use pass_gate when the phase work is complete and ready for review. Use fail_gate if there\'s an issue that needs to be resolved.',
77
+ inputSchema: {
78
+ type: 'object',
79
+ properties: {
80
+ action: {
81
+ type: 'string',
82
+ enum: ['pass', 'fail'],
83
+ description: 'Whether to pass or fail the gate',
84
+ },
85
+ artifacts: {
86
+ type: 'array',
87
+ items: { type: 'string' },
88
+ description: 'Artifacts produced (for pass)',
89
+ },
90
+ reason: {
91
+ type: 'string',
92
+ description: 'Reason for failure (for fail)',
93
+ },
94
+ },
95
+ required: ['action'],
96
+ },
97
+ },
98
+ {
99
+ name: 'engineering_artifact',
100
+ description: 'Save or retrieve an engineering artifact (PRD, tech spec, API docs, etc.). Use this to store documents produced during each phase.',
101
+ inputSchema: {
102
+ type: 'object',
103
+ properties: {
104
+ action: {
105
+ type: 'string',
106
+ enum: ['save', 'get', 'list'],
107
+ description: 'Action to perform',
108
+ },
109
+ name: {
110
+ type: 'string',
111
+ description: 'Artifact name (e.g., "prd.md", "tech-spec.md")',
112
+ },
113
+ content: {
114
+ type: 'string',
115
+ description: 'Content to save (for save action)',
116
+ },
117
+ },
118
+ required: ['action'],
119
+ },
120
+ },
121
+ {
122
+ name: 'engineering_decision',
123
+ description: 'Record an engineering decision with reasoning. Use this whenever making a significant architectural or design decision. This creates an audit trail of why decisions were made.',
124
+ inputSchema: {
125
+ type: 'object',
126
+ properties: {
127
+ agent: {
128
+ type: 'string',
129
+ enum: ['orchestrator', 'pm', 'architect', 'engineer', 'qa', 'security', 'documentation', 'devops'],
130
+ description: 'Which agent role is making this decision',
131
+ },
132
+ decision: {
133
+ type: 'string',
134
+ description: 'What was decided',
135
+ },
136
+ reasoning: {
137
+ type: 'string',
138
+ description: 'Why this decision was made',
139
+ },
140
+ alternatives: {
141
+ type: 'array',
142
+ items: { type: 'string' },
143
+ description: 'What alternatives were considered',
144
+ },
145
+ confidence: {
146
+ type: 'number',
147
+ description: 'Confidence level 0-100',
148
+ },
149
+ impact: {
150
+ type: 'string',
151
+ enum: ['low', 'medium', 'high', 'critical'],
152
+ description: 'Impact level of this decision',
153
+ },
154
+ },
155
+ required: ['agent', 'decision', 'reasoning'],
156
+ },
157
+ },
158
+ {
159
+ name: 'engineering_graph_add',
160
+ description: 'Add a node or edge to the dependency graph. Use this after creating any file to track dependencies between components.',
161
+ inputSchema: {
162
+ type: 'object',
163
+ properties: {
164
+ nodeType: {
165
+ type: 'string',
166
+ enum: ['schema', 'api', 'component', 'service', 'page', 'util', 'config'],
167
+ description: 'Type of node to add',
168
+ },
169
+ name: {
170
+ type: 'string',
171
+ description: 'Name of the node',
172
+ },
173
+ filePath: {
174
+ type: 'string',
175
+ description: 'File path for the node',
176
+ },
177
+ dependsOn: {
178
+ type: 'array',
179
+ items: { type: 'string' },
180
+ description: 'Array of file paths this node depends on',
181
+ },
182
+ dependencyTypes: {
183
+ type: 'array',
184
+ items: { type: 'string', enum: ['import', 'api-call', 'db-query', 'event', 'config'] },
185
+ description: 'Types of dependencies (parallel with dependsOn array)',
186
+ },
187
+ },
188
+ required: ['nodeType', 'name', 'filePath'],
189
+ },
190
+ },
191
+ {
192
+ name: 'engineering_impact',
193
+ description: 'Analyze the impact of changing a file. Use BEFORE modifying any existing file to understand what else might break. Returns affected files, risk level, and recommendations.',
194
+ inputSchema: {
195
+ type: 'object',
196
+ properties: {
197
+ filePath: {
198
+ type: 'string',
199
+ description: 'Path of the file you\'re about to change',
200
+ },
201
+ },
202
+ required: ['filePath'],
203
+ },
204
+ },
205
+ {
206
+ name: 'engineering_graph_view',
207
+ description: 'View the current dependency graph. Use to understand how components are connected before making changes.',
208
+ inputSchema: {
209
+ type: 'object',
210
+ properties: {
211
+ focusFile: {
212
+ type: 'string',
213
+ description: 'Optional: Focus on dependencies for a specific file',
214
+ },
215
+ },
216
+ },
217
+ },
218
+ ];
219
+ // =============================================================================
220
+ // TOOL HANDLERS
221
+ // =============================================================================
222
+ async function handleEngineeringTool(name, args, apiUrl, authHeaders) {
223
+ const stateManager = (0, engineering_state_js_1.getStateManager)();
224
+ switch (name) {
225
+ case 'engineering_start':
226
+ return handleEngineeringStart(stateManager, args);
227
+ case 'engineering_scope':
228
+ return handleEngineeringScope(stateManager, args);
229
+ case 'engineering_status':
230
+ return handleEngineeringStatus(stateManager);
231
+ case 'engineering_advance':
232
+ return handleEngineeringAdvance(stateManager, args);
233
+ case 'engineering_gate':
234
+ return handleEngineeringGate(stateManager, args);
235
+ case 'engineering_artifact':
236
+ return handleEngineeringArtifact(stateManager, args);
237
+ case 'engineering_decision':
238
+ return handleEngineeringDecision(stateManager, args);
239
+ case 'engineering_graph_add':
240
+ return handleEngineeringGraphAdd(stateManager, args);
241
+ case 'engineering_impact':
242
+ return handleEngineeringImpact(stateManager, args);
243
+ case 'engineering_graph_view':
244
+ return handleEngineeringGraphView(stateManager, args);
245
+ default:
246
+ return {
247
+ content: [{ type: 'text', text: `Unknown engineering tool: ${name}` }],
248
+ };
249
+ }
250
+ }
251
+ // =============================================================================
252
+ // INDIVIDUAL HANDLERS
253
+ // =============================================================================
254
+ const SCOPING_STEPS = [
255
+ {
256
+ id: 'audience',
257
+ question: 'Who is this for?',
258
+ description: 'Your target users',
259
+ type: 'single',
260
+ options: [
261
+ { value: 'consumers', label: 'Consumers (B2C)' },
262
+ { value: 'businesses', label: 'Businesses (B2B)' },
263
+ { value: 'internal', label: 'Internal Team' },
264
+ { value: 'developers', label: 'Developers (API)' },
265
+ ],
266
+ },
267
+ {
268
+ id: 'isFullBusiness',
269
+ question: 'Is this a full business product?',
270
+ description: 'Needs marketing, analytics, team features, etc.',
271
+ type: 'boolean',
272
+ },
273
+ {
274
+ id: 'platforms',
275
+ question: 'Which platforms?',
276
+ description: 'Where will users access this?',
277
+ type: 'multiple',
278
+ options: [
279
+ { value: 'web', label: 'Web App' },
280
+ { value: 'mobile', label: 'Mobile App' },
281
+ { value: 'api', label: 'API Only' },
282
+ ],
283
+ },
284
+ {
285
+ id: 'hasAuth',
286
+ question: 'Do users need accounts?',
287
+ description: 'Login, signup, profiles',
288
+ type: 'boolean',
289
+ },
290
+ {
291
+ id: 'hasPayments',
292
+ question: 'Will you charge money?',
293
+ description: 'Subscriptions or one-time payments',
294
+ type: 'boolean',
295
+ },
296
+ {
297
+ id: 'hasRealtime',
298
+ question: 'Need real-time features?',
299
+ description: 'Live updates, chat, notifications',
300
+ type: 'boolean',
301
+ },
302
+ {
303
+ id: 'compliance',
304
+ question: 'Any compliance requirements?',
305
+ description: 'Skip if none apply',
306
+ type: 'multiple',
307
+ options: [
308
+ { value: 'hipaa', label: 'HIPAA (Healthcare)' },
309
+ { value: 'pci', label: 'PCI DSS (Payments)' },
310
+ { value: 'gdpr', label: 'GDPR (EU Privacy)' },
311
+ { value: 'soc2', label: 'SOC 2 (Enterprise)' },
312
+ { value: 'coppa', label: 'COPPA (Children)' },
313
+ ],
314
+ },
315
+ {
316
+ id: 'expectedUsers',
317
+ question: 'Expected scale?',
318
+ description: 'Helps with architecture decisions',
319
+ type: 'single',
320
+ options: [
321
+ { value: 'small', label: 'Small (< 1,000 users)' },
322
+ { value: 'medium', label: 'Medium (1K - 100K users)' },
323
+ { value: 'large', label: 'Large (100K - 1M users)' },
324
+ { value: 'enterprise', label: 'Enterprise (1M+ users)' },
325
+ ],
326
+ },
327
+ {
328
+ id: 'launchTimeline',
329
+ question: 'When do you want to launch?',
330
+ description: 'Affects prioritization',
331
+ type: 'single',
332
+ options: [
333
+ { value: 'asap', label: 'ASAP (MVP)' },
334
+ { value: 'weeks', label: 'Few weeks (Core features)' },
335
+ { value: 'months', label: 'Few months (Full feature set)' },
336
+ { value: 'flexible', label: 'Flexible (Quality over speed)' },
337
+ ],
338
+ },
339
+ ];
340
+ async function handleEngineeringStart(stateManager, args) {
341
+ const { projectName, description = '' } = args;
342
+ // Create project
343
+ stateManager.createProject(projectName, description);
344
+ // Return first scoping question
345
+ const firstStep = SCOPING_STEPS[0];
346
+ let response = `# πŸ—οΈ Engineering Build Started: ${projectName}\n\n`;
347
+ response += `I'll guide you through a quick scoping wizard to understand what you're building.\n`;
348
+ response += `This ensures we build with the right architecture from the start.\n\n`;
349
+ response += `---\n\n`;
350
+ response += `## ${firstStep.question}\n`;
351
+ response += `*${firstStep.description}*\n\n`;
352
+ if (firstStep.options) {
353
+ for (const opt of firstStep.options) {
354
+ response += `- **${opt.value}**: ${opt.label}\n`;
355
+ }
356
+ }
357
+ else if (firstStep.type === 'boolean') {
358
+ response += `Answer: **yes** or **no**\n`;
359
+ }
360
+ response += `\n*Use \`engineering_scope\` with stepId="${firstStep.id}" to answer.*`;
361
+ return { content: [{ type: 'text', text: response }] };
362
+ }
363
+ async function handleEngineeringScope(stateManager, args) {
364
+ const { stepId, answer } = args;
365
+ const project = stateManager.getProject();
366
+ if (!project) {
367
+ return { content: [{ type: 'text', text: '❌ No engineering project found. Run `engineering_start` first.' }] };
368
+ }
369
+ // Find current step index
370
+ const currentIndex = SCOPING_STEPS.findIndex(s => s.id === stepId);
371
+ if (currentIndex === -1) {
372
+ return { content: [{ type: 'text', text: `❌ Unknown step: ${stepId}` }] };
373
+ }
374
+ // Update scope based on answer
375
+ const step = SCOPING_STEPS[currentIndex];
376
+ let parsedAnswer = answer;
377
+ if (step.type === 'boolean') {
378
+ parsedAnswer = answer.toLowerCase() === 'yes' || answer.toLowerCase() === 'true';
379
+ }
380
+ else if (step.type === 'multiple') {
381
+ try {
382
+ parsedAnswer = JSON.parse(answer);
383
+ }
384
+ catch {
385
+ // If not JSON, split by comma
386
+ parsedAnswer = answer.split(',').map(s => s.trim());
387
+ }
388
+ }
389
+ // Update project scope
390
+ const scopeUpdate = {};
391
+ switch (stepId) {
392
+ case 'audience':
393
+ scopeUpdate.targetAudience = parsedAnswer;
394
+ break;
395
+ case 'isFullBusiness':
396
+ scopeUpdate.isFullBusiness = parsedAnswer;
397
+ if (parsedAnswer) {
398
+ scopeUpdate.needsMarketing = true;
399
+ scopeUpdate.needsAnalytics = true;
400
+ scopeUpdate.needsAdminDashboard = true;
401
+ }
402
+ break;
403
+ case 'platforms':
404
+ scopeUpdate.platforms = parsedAnswer;
405
+ break;
406
+ case 'hasAuth':
407
+ scopeUpdate.hasAuth = parsedAnswer;
408
+ break;
409
+ case 'hasPayments':
410
+ scopeUpdate.hasPayments = parsedAnswer;
411
+ break;
412
+ case 'hasRealtime':
413
+ scopeUpdate.hasRealtime = parsedAnswer;
414
+ break;
415
+ case 'compliance':
416
+ const values = parsedAnswer;
417
+ scopeUpdate.compliance = {
418
+ hipaa: values.includes('hipaa'),
419
+ pci: values.includes('pci'),
420
+ gdpr: values.includes('gdpr'),
421
+ soc2: values.includes('soc2'),
422
+ coppa: values.includes('coppa'),
423
+ };
424
+ break;
425
+ case 'expectedUsers':
426
+ scopeUpdate.expectedUsers = parsedAnswer;
427
+ break;
428
+ case 'launchTimeline':
429
+ scopeUpdate.launchTimeline = parsedAnswer;
430
+ break;
431
+ }
432
+ stateManager.updateScope(scopeUpdate);
433
+ // Check if there's a next step
434
+ const nextStep = SCOPING_STEPS[currentIndex + 1];
435
+ if (!nextStep) {
436
+ // Scoping complete!
437
+ stateManager.passGate('scoping', ['scope.json'], 'auto');
438
+ stateManager.setPhase('requirements', 'pm');
439
+ stateManager.recordDecision({
440
+ agent: 'orchestrator',
441
+ phase: 'scoping',
442
+ decision: 'Project scope defined',
443
+ reasoning: `Scope completed: ${project.scope.platforms.join(', ')} platforms, ${project.scope.targetAudience} audience`,
444
+ alternatives: [],
445
+ confidence: 100,
446
+ reversible: true,
447
+ impact: 'high',
448
+ });
449
+ const updatedProject = stateManager.getProject();
450
+ let response = `# βœ… Scoping Complete!\n\n`;
451
+ response += `## Project: ${updatedProject.name}\n\n`;
452
+ response += `### Scope Summary\n`;
453
+ response += `- **Audience:** ${updatedProject.scope.targetAudience}\n`;
454
+ response += `- **Platforms:** ${updatedProject.scope.platforms.join(', ')}\n`;
455
+ response += `- **Auth:** ${updatedProject.scope.hasAuth ? 'Yes' : 'No'}\n`;
456
+ response += `- **Payments:** ${updatedProject.scope.hasPayments ? 'Yes' : 'No'}\n`;
457
+ response += `- **Realtime:** ${updatedProject.scope.hasRealtime ? 'Yes' : 'No'}\n`;
458
+ response += `- **Full Business:** ${updatedProject.scope.isFullBusiness ? 'Yes' : 'No'}\n`;
459
+ response += `- **Scale:** ${updatedProject.scope.expectedUsers}\n`;
460
+ response += `- **Timeline:** ${updatedProject.scope.launchTimeline}\n\n`;
461
+ response += `### Detected Stack\n`;
462
+ response += `- **Framework:** ${updatedProject.stack.framework}\n`;
463
+ response += `- **Database:** ${updatedProject.stack.database}\n`;
464
+ response += `- **ORM:** ${updatedProject.stack.orm}\n`;
465
+ response += `- **Auth:** ${updatedProject.stack.auth}\n`;
466
+ response += `- **UI:** ${updatedProject.stack.ui}\n\n`;
467
+ response += `---\n\n`;
468
+ response += `## πŸ“‹ Next Phase: Requirements\n\n`;
469
+ response += `The PM agent will now create a Product Requirements Document (PRD).\n`;
470
+ response += `This defines exactly what we're building before any code is written.\n\n`;
471
+ response += `*Ready to proceed? Start writing the PRD using the patterns and save with \`engineering_artifact\`.*`;
472
+ return { content: [{ type: 'text', text: response }] };
473
+ }
474
+ // Return next question
475
+ let response = `## ${nextStep.question}\n`;
476
+ response += `*${nextStep.description}*\n\n`;
477
+ if (nextStep.options) {
478
+ for (const opt of nextStep.options) {
479
+ response += `- **${opt.value}**: ${opt.label}\n`;
480
+ }
481
+ if (nextStep.type === 'multiple') {
482
+ response += `\n*Select multiple by providing a JSON array or comma-separated values*\n`;
483
+ }
484
+ }
485
+ else if (nextStep.type === 'boolean') {
486
+ response += `Answer: **yes** or **no**\n`;
487
+ }
488
+ response += `\n*Use \`engineering_scope\` with stepId="${nextStep.id}" to answer.*`;
489
+ return { content: [{ type: 'text', text: response }] };
490
+ }
491
+ async function handleEngineeringStatus(stateManager) {
492
+ const summary = stateManager.getSummary();
493
+ if (!summary.project || !summary.state) {
494
+ return { content: [{ type: 'text', text: '❌ No engineering project found. Run `engineering_start` to begin.' }] };
495
+ }
496
+ const { project, state, graphStats, decisions, artifacts } = summary;
497
+ const PHASE_NAMES = {
498
+ scoping: 'Scoping',
499
+ requirements: 'Requirements',
500
+ architecture: 'Architecture',
501
+ design_review: 'Design Review',
502
+ implementation: 'Implementation',
503
+ code_review: 'Code Review',
504
+ testing: 'Testing',
505
+ security_review: 'Security Review',
506
+ documentation: 'Documentation',
507
+ staging: 'Staging',
508
+ launch: 'Launch',
509
+ };
510
+ let response = `# πŸ—οΈ Engineering Status: ${project.name}\n\n`;
511
+ response += `## Progress: ${state.overallProgress}%\n\n`;
512
+ response += `### Current Phase: ${PHASE_NAMES[state.currentPhase]}\n`;
513
+ response += `**Agent:** ${state.currentAgent}\n\n`;
514
+ response += `### Phase Status\n`;
515
+ response += `| Phase | Status |\n`;
516
+ response += `|-------|--------|\n`;
517
+ const phases = [
518
+ 'scoping', 'requirements', 'architecture', 'design_review',
519
+ 'implementation', 'code_review', 'testing', 'security_review',
520
+ 'documentation', 'staging', 'launch'
521
+ ];
522
+ for (const phase of phases) {
523
+ const gate = state.gates[phase];
524
+ const statusIcon = gate.status === 'passed' ? 'βœ…' :
525
+ gate.status === 'in_progress' ? 'πŸ”„' :
526
+ gate.status === 'failed' ? '❌' :
527
+ gate.status === 'skipped' ? '⏭️' : '⬜';
528
+ const current = phase === state.currentPhase ? ' ← Current' : '';
529
+ response += `| ${statusIcon} ${PHASE_NAMES[phase]} | ${gate.status}${current} |\n`;
530
+ }
531
+ response += `\n### Metrics\n`;
532
+ response += `- **Dependency Graph:** ${graphStats.nodes} nodes, ${graphStats.edges} edges\n`;
533
+ response += `- **Decisions Recorded:** ${decisions}\n`;
534
+ response += `- **Artifacts:** ${artifacts.length > 0 ? artifacts.join(', ') : 'None yet'}\n`;
535
+ if (state.blockers.length > 0) {
536
+ response += `\n### ⚠️ Blockers\n`;
537
+ for (const blocker of state.blockers) {
538
+ response += `- ${blocker}\n`;
539
+ }
540
+ }
541
+ response += `\n---\n`;
542
+ response += `*Last activity: ${new Date(state.lastActivity).toLocaleString()}*`;
543
+ return { content: [{ type: 'text', text: response }] };
544
+ }
545
+ async function handleEngineeringAdvance(stateManager, args) {
546
+ const state = stateManager.getState();
547
+ if (!state) {
548
+ return { content: [{ type: 'text', text: '❌ No engineering project found.' }] };
549
+ }
550
+ const currentGate = state.gates[state.currentPhase];
551
+ if (currentGate.status !== 'passed') {
552
+ return { content: [{ type: 'text', text: `❌ Current phase gate not passed. Use \`engineering_gate\` to pass the ${state.currentPhase} gate first.` }] };
553
+ }
554
+ // Find next phase
555
+ const phases = [
556
+ 'scoping', 'requirements', 'architecture', 'design_review',
557
+ 'implementation', 'code_review', 'testing', 'security_review',
558
+ 'documentation', 'staging', 'launch'
559
+ ];
560
+ const currentIndex = phases.indexOf(state.currentPhase);
561
+ if (currentIndex >= phases.length - 1) {
562
+ return { content: [{ type: 'text', text: 'πŸŽ‰ All phases complete! Project is ready for launch.' }] };
563
+ }
564
+ const nextPhase = phases[currentIndex + 1];
565
+ const agentMap = {
566
+ scoping: 'orchestrator',
567
+ requirements: 'pm',
568
+ architecture: 'architect',
569
+ design_review: 'orchestrator',
570
+ implementation: 'engineer',
571
+ code_review: 'engineer',
572
+ testing: 'qa',
573
+ security_review: 'security',
574
+ documentation: 'documentation',
575
+ staging: 'devops',
576
+ launch: 'devops',
577
+ };
578
+ stateManager.setPhase(nextPhase, agentMap[nextPhase]);
579
+ const PHASE_NAMES = {
580
+ scoping: 'Scoping', requirements: 'Requirements', architecture: 'Architecture',
581
+ design_review: 'Design Review', implementation: 'Implementation', code_review: 'Code Review',
582
+ testing: 'Testing', security_review: 'Security Review', documentation: 'Documentation',
583
+ staging: 'Staging', launch: 'Launch',
584
+ };
585
+ let response = `# βœ… Advanced to ${PHASE_NAMES[nextPhase]} Phase\n\n`;
586
+ response += `**Agent:** ${agentMap[nextPhase]}\n\n`;
587
+ const phaseDescriptions = {
588
+ scoping: 'Define what you\'re building',
589
+ requirements: 'PM agent creates detailed PRD',
590
+ architecture: 'Architect agent designs system structure',
591
+ design_review: 'Review architecture with stakeholders',
592
+ implementation: 'Engineer agents build the features',
593
+ code_review: 'Review code quality and patterns',
594
+ testing: 'QA agent writes and runs tests',
595
+ security_review: 'Security agent audits vulnerabilities',
596
+ documentation: 'Generate comprehensive docs',
597
+ staging: 'Deploy to staging environment',
598
+ launch: 'Final production deployment',
599
+ };
600
+ response += `**Goal:** ${phaseDescriptions[nextPhase]}\n\n`;
601
+ response += `Use \`engineering_gate\` to pass this phase when complete.`;
602
+ return { content: [{ type: 'text', text: response }] };
603
+ }
604
+ async function handleEngineeringGate(stateManager, args) {
605
+ const { action, artifacts = [], reason } = args;
606
+ const state = stateManager.getState();
607
+ if (!state) {
608
+ return { content: [{ type: 'text', text: '❌ No engineering project found.' }] };
609
+ }
610
+ if (action === 'pass') {
611
+ stateManager.passGate(state.currentPhase, artifacts, 'auto');
612
+ return {
613
+ content: [{
614
+ type: 'text',
615
+ text: `βœ… Gate passed for ${state.currentPhase} phase!\n\nArtifacts: ${artifacts.length > 0 ? artifacts.join(', ') : 'None'}\n\nUse \`engineering_advance\` to move to the next phase.`
616
+ }]
617
+ };
618
+ }
619
+ else if (action === 'fail') {
620
+ stateManager.failGate(state.currentPhase, reason || 'Gate failed');
621
+ return {
622
+ content: [{
623
+ type: 'text',
624
+ text: `❌ Gate failed for ${state.currentPhase} phase.\n\nReason: ${reason || 'Not specified'}\n\nResolve the issue and try again.`
625
+ }]
626
+ };
627
+ }
628
+ return { content: [{ type: 'text', text: '❌ Invalid action. Use "pass" or "fail".' }] };
629
+ }
630
+ async function handleEngineeringArtifact(stateManager, args) {
631
+ const { action, name, content } = args;
632
+ if (action === 'list') {
633
+ const artifacts = stateManager.listArtifacts();
634
+ if (artifacts.length === 0) {
635
+ return { content: [{ type: 'text', text: 'No artifacts saved yet.' }] };
636
+ }
637
+ return { content: [{ type: 'text', text: `## Artifacts\n\n${artifacts.map(a => `- ${a}`).join('\n')}` }] };
638
+ }
639
+ if (action === 'save') {
640
+ if (!name || !content) {
641
+ return { content: [{ type: 'text', text: '❌ Name and content required for save.' }] };
642
+ }
643
+ stateManager.saveArtifact(name, content);
644
+ return { content: [{ type: 'text', text: `βœ… Saved artifact: ${name}` }] };
645
+ }
646
+ if (action === 'get') {
647
+ if (!name) {
648
+ return { content: [{ type: 'text', text: '❌ Name required for get.' }] };
649
+ }
650
+ const artifactContent = stateManager.getArtifact(name);
651
+ if (!artifactContent) {
652
+ return { content: [{ type: 'text', text: `❌ Artifact not found: ${name}` }] };
653
+ }
654
+ return { content: [{ type: 'text', text: `## ${name}\n\n${artifactContent}` }] };
655
+ }
656
+ return { content: [{ type: 'text', text: '❌ Invalid action. Use "save", "get", or "list".' }] };
657
+ }
658
+ async function handleEngineeringDecision(stateManager, args) {
659
+ const state = stateManager.getState();
660
+ if (!state) {
661
+ return { content: [{ type: 'text', text: '❌ No engineering project found.' }] };
662
+ }
663
+ const decision = stateManager.recordDecision({
664
+ agent: args.agent,
665
+ phase: state.currentPhase,
666
+ decision: args.decision,
667
+ reasoning: args.reasoning,
668
+ alternatives: args.alternatives || [],
669
+ confidence: args.confidence || 80,
670
+ reversible: true,
671
+ impact: args.impact || 'medium',
672
+ });
673
+ let response = `# πŸ“ Decision Recorded\n\n`;
674
+ response += `**Agent:** ${args.agent}\n`;
675
+ response += `**Phase:** ${state.currentPhase}\n`;
676
+ response += `**Decision:** ${args.decision}\n\n`;
677
+ response += `**Reasoning:** ${args.reasoning}\n\n`;
678
+ if (args.alternatives && args.alternatives.length > 0) {
679
+ response += `**Alternatives Considered:**\n`;
680
+ for (const alt of args.alternatives) {
681
+ response += `- ${alt}\n`;
682
+ }
683
+ }
684
+ response += `\n**Confidence:** ${args.confidence || 80}%\n`;
685
+ response += `**Impact:** ${args.impact || 'medium'}`;
686
+ return { content: [{ type: 'text', text: response }] };
687
+ }
688
+ async function handleEngineeringGraphAdd(stateManager, args) {
689
+ const { nodeType, name, filePath, dependsOn = [], dependencyTypes = [] } = args;
690
+ // Add the node
691
+ const node = stateManager.addNode({
692
+ type: nodeType,
693
+ name,
694
+ filePath,
695
+ });
696
+ // Add edges for dependencies
697
+ const graph = stateManager.getGraph();
698
+ let edgesAdded = 0;
699
+ for (let i = 0; i < dependsOn.length; i++) {
700
+ const targetPath = dependsOn[i];
701
+ const targetNode = graph.nodes.find(n => n.filePath === targetPath);
702
+ if (targetNode) {
703
+ stateManager.addEdge({
704
+ sourceId: node.id,
705
+ targetId: targetNode.id,
706
+ type: dependencyTypes[i] || 'import',
707
+ });
708
+ edgesAdded++;
709
+ }
710
+ }
711
+ let response = `# βœ… Added to Dependency Graph\n\n`;
712
+ response += `**Node:** ${name} (${nodeType})\n`;
713
+ response += `**Path:** ${filePath}\n`;
714
+ response += `**Dependencies:** ${edgesAdded} edges added\n`;
715
+ return { content: [{ type: 'text', text: response }] };
716
+ }
717
+ async function handleEngineeringImpact(stateManager, args) {
718
+ const { filePath } = args;
719
+ const graph = stateManager.getGraph();
720
+ // Find the node
721
+ const node = graph.nodes.find(n => n.filePath === filePath);
722
+ if (!node) {
723
+ return { content: [{ type: 'text', text: `File not in dependency graph: ${filePath}\n\nThis might be a new file or one that hasn't been tracked yet.` }] };
724
+ }
725
+ // Find affected nodes
726
+ const { direct, transitive } = stateManager.findAffectedNodes(node.id);
727
+ // Determine risk level
728
+ const totalAffected = direct.length + transitive.length;
729
+ let riskLevel = 'low';
730
+ let riskIcon = '🟒';
731
+ if (totalAffected > 10) {
732
+ riskLevel = 'critical';
733
+ riskIcon = 'πŸ”΄';
734
+ }
735
+ else if (totalAffected > 5) {
736
+ riskLevel = 'high';
737
+ riskIcon = '🟠';
738
+ }
739
+ else if (totalAffected > 2) {
740
+ riskLevel = 'medium';
741
+ riskIcon = '🟑';
742
+ }
743
+ let response = `# πŸ” Impact Analysis: ${node.name}\n\n`;
744
+ response += `**File:** ${filePath}\n`;
745
+ response += `**Type:** ${node.type}\n`;
746
+ response += `**Risk Level:** ${riskIcon} ${riskLevel.toUpperCase()}\n\n`;
747
+ if (direct.length > 0) {
748
+ response += `## Directly Affected (${direct.length})\n`;
749
+ response += `These files import or directly depend on this file:\n\n`;
750
+ for (const n of direct) {
751
+ response += `- ${n.name} (${n.type}) - ${n.filePath}\n`;
752
+ }
753
+ response += `\n`;
754
+ }
755
+ if (transitive.length > 0) {
756
+ response += `## Transitively Affected (${transitive.length})\n`;
757
+ response += `These files are indirectly affected through the dependency chain:\n\n`;
758
+ for (const n of transitive.slice(0, 10)) { // Limit to 10
759
+ response += `- ${n.name} (${n.type}) - ${n.filePath}\n`;
760
+ }
761
+ if (transitive.length > 10) {
762
+ response += `... and ${transitive.length - 10} more\n`;
763
+ }
764
+ response += `\n`;
765
+ }
766
+ if (totalAffected === 0) {
767
+ response += `No other files depend on this one. Changes are safe to make.\n`;
768
+ }
769
+ else {
770
+ response += `## Recommendations\n`;
771
+ if (node.type === 'schema') {
772
+ response += `- Consider running database migration after changes\n`;
773
+ response += `- Check all API routes that use this schema\n`;
774
+ }
775
+ if (riskLevel === 'high' || riskLevel === 'critical') {
776
+ response += `- Create a snapshot before making changes\n`;
777
+ response += `- Run full test suite after changes\n`;
778
+ }
779
+ response += `- Update affected files if interface changes\n`;
780
+ }
781
+ return { content: [{ type: 'text', text: response }] };
782
+ }
783
+ async function handleEngineeringGraphView(stateManager, args) {
784
+ const graph = stateManager.getGraph();
785
+ if (graph.nodes.length === 0) {
786
+ return { content: [{ type: 'text', text: 'Dependency graph is empty. Use `engineering_graph_add` to add nodes.' }] };
787
+ }
788
+ let response = `# πŸ•ΈοΈ Dependency Graph\n\n`;
789
+ response += `**Nodes:** ${graph.nodes.length} | **Edges:** ${graph.edges.length}\n\n`;
790
+ // Group by type
791
+ const byType = {};
792
+ for (const node of graph.nodes) {
793
+ if (!byType[node.type])
794
+ byType[node.type] = [];
795
+ byType[node.type].push(node);
796
+ }
797
+ for (const [type, nodes] of Object.entries(byType)) {
798
+ response += `## ${type.charAt(0).toUpperCase() + type.slice(1)}s (${nodes.length})\n`;
799
+ for (const node of nodes) {
800
+ const incoming = graph.edges.filter(e => e.targetId === node.id).length;
801
+ const outgoing = graph.edges.filter(e => e.sourceId === node.id).length;
802
+ response += `- **${node.name}** - ↓${incoming} ↑${outgoing} deps\n`;
803
+ response += ` ${node.filePath}\n`;
804
+ }
805
+ response += `\n`;
806
+ }
807
+ return { content: [{ type: 'text', text: response }] };
808
+ }