mcp-adr-analysis-server 2.0.17 → 2.0.18

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,1028 @@
1
+ /**
2
+ * Interactive ADR Planning Tool - Guided ADR Creation and Planning
3
+ *
4
+ * Provides an interactive, conversational approach to ADR planning that:
5
+ * - Guides users through structured decision-making process
6
+ * - Integrates research capabilities for informed decisions
7
+ * - Generates ADR content progressively with user feedback
8
+ * - Updates TODO items based on ADR decisions
9
+ * - Orchestrates other MCP tools for comprehensive planning
10
+ *
11
+ * Key Features:
12
+ * - Multi-step wizard for ADR creation
13
+ * - Context-aware research integration
14
+ * - Decision impact analysis
15
+ * - Automatic TODO generation from decisions
16
+ * - ADR template customization
17
+ * - Decision rationale capture
18
+ *
19
+ * Workflow Phases:
20
+ * 1. Problem Definition - Clarify the architectural challenge
21
+ * 2. Research & Analysis - Gather relevant information
22
+ * 3. Option Exploration - Identify and evaluate alternatives
23
+ * 4. Decision Making - Select approach with rationale
24
+ * 5. Impact Assessment - Analyze consequences and risks
25
+ * 6. Implementation Planning - Generate TODOs and timeline
26
+ * 7. ADR Generation - Create structured ADR document
27
+ *
28
+ * Integration Points:
29
+ * - Research tools for context gathering
30
+ * - Analysis tools for impact assessment
31
+ * - TODO management for task generation
32
+ * - ADR generation for document creation
33
+ * - Project planning for timeline integration
34
+ */
35
+ import { z } from 'zod';
36
+ import { promises as fs } from 'fs';
37
+ import { join } from 'path';
38
+ import { McpAdrError } from '../types/index.js';
39
+ // Planning session schema
40
+ const PlanningSessionSchema = z.object({
41
+ sessionId: z.string(),
42
+ phase: z.enum([
43
+ 'problem_definition',
44
+ 'research_analysis',
45
+ 'option_exploration',
46
+ 'decision_making',
47
+ 'impact_assessment',
48
+ 'implementation_planning',
49
+ 'adr_generation',
50
+ 'completed'
51
+ ]),
52
+ context: z.object({
53
+ projectPath: z.string(),
54
+ adrDirectory: z.string().default('docs/adrs'),
55
+ problemStatement: z.string().optional(),
56
+ researchFindings: z.array(z.object({
57
+ source: z.string(),
58
+ insight: z.string(),
59
+ relevance: z.string()
60
+ })).default([]),
61
+ options: z.array(z.object({
62
+ name: z.string(),
63
+ description: z.string(),
64
+ pros: z.array(z.string()),
65
+ cons: z.array(z.string()),
66
+ risks: z.array(z.string()),
67
+ effort: z.enum(['low', 'medium', 'high']),
68
+ confidence: z.number().min(0).max(100)
69
+ })).default([]),
70
+ selectedOption: z.object({
71
+ name: z.string(),
72
+ rationale: z.string(),
73
+ tradeoffs: z.array(z.string())
74
+ }).optional(),
75
+ impacts: z.object({
76
+ technical: z.array(z.string()).default([]),
77
+ business: z.array(z.string()).default([]),
78
+ team: z.array(z.string()).default([]),
79
+ risks: z.array(z.string()).default([]),
80
+ dependencies: z.array(z.string()).default([])
81
+ }).optional(),
82
+ implementation: z.object({
83
+ phases: z.array(z.string()).default([]),
84
+ tasks: z.array(z.object({
85
+ description: z.string(),
86
+ priority: z.enum(['low', 'medium', 'high', 'critical']),
87
+ effort: z.string(),
88
+ assignee: z.string().optional()
89
+ })).default([]),
90
+ timeline: z.string().optional(),
91
+ successCriteria: z.array(z.string()).default([])
92
+ }).optional(),
93
+ adrContent: z.string().optional()
94
+ }),
95
+ metadata: z.object({
96
+ createdAt: z.string(),
97
+ updatedAt: z.string(),
98
+ lastAction: z.string(),
99
+ nextSteps: z.array(z.string()).default([])
100
+ })
101
+ });
102
+ // Input schema for tool operations
103
+ const InteractiveAdrPlanningSchema = z.object({
104
+ operation: z.enum([
105
+ 'start_session',
106
+ 'continue_session',
107
+ 'provide_input',
108
+ 'request_research',
109
+ 'evaluate_options',
110
+ 'make_decision',
111
+ 'assess_impact',
112
+ 'plan_implementation',
113
+ 'generate_adr',
114
+ 'update_todos',
115
+ 'get_guidance',
116
+ 'save_session',
117
+ 'complete_session'
118
+ ]),
119
+ sessionId: z.string().optional(),
120
+ input: z.any().optional(),
121
+ projectPath: z.string(),
122
+ autoResearch: z.boolean().default(true).describe('Automatically trigger research when needed'),
123
+ generateTodos: z.boolean().default(true).describe('Automatically generate TODO items from decisions')
124
+ });
125
+ // Session storage
126
+ const sessions = new Map();
127
+ /**
128
+ * Get or create cache directory
129
+ */
130
+ async function getCacheDir(projectPath) {
131
+ const cacheDir = join(projectPath, '.mcp-adr-cache');
132
+ await fs.mkdir(cacheDir, { recursive: true });
133
+ return cacheDir;
134
+ }
135
+ /**
136
+ * Save session to cache
137
+ */
138
+ async function saveSession(session) {
139
+ const cacheDir = await getCacheDir(session.context.projectPath);
140
+ const sessionFile = join(cacheDir, `adr-planning-session-${session.sessionId}.json`);
141
+ await fs.writeFile(sessionFile, JSON.stringify(session, null, 2));
142
+ }
143
+ /**
144
+ * Load session from cache
145
+ */
146
+ async function loadSession(projectPath, sessionId) {
147
+ try {
148
+ const cacheDir = await getCacheDir(projectPath);
149
+ const sessionFile = join(cacheDir, `adr-planning-session-${sessionId}.json`);
150
+ const data = await fs.readFile(sessionFile, 'utf-8');
151
+ return PlanningSessionSchema.parse(JSON.parse(data));
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ }
157
+ /**
158
+ * Start a new planning session
159
+ */
160
+ async function startSession(args) {
161
+ const sessionId = `adr-${Date.now()}`;
162
+ const session = {
163
+ sessionId,
164
+ phase: 'problem_definition',
165
+ context: {
166
+ projectPath: args.projectPath,
167
+ adrDirectory: 'docs/adrs',
168
+ researchFindings: [],
169
+ options: []
170
+ },
171
+ metadata: {
172
+ createdAt: new Date().toISOString(),
173
+ updatedAt: new Date().toISOString(),
174
+ lastAction: 'Session started',
175
+ nextSteps: [
176
+ 'Define the architectural problem or decision point',
177
+ 'Identify key stakeholders and constraints',
178
+ 'Determine success criteria'
179
+ ]
180
+ }
181
+ };
182
+ sessions.set(sessionId, session);
183
+ await saveSession(session);
184
+ return {
185
+ success: true,
186
+ sessionId,
187
+ phase: session.phase,
188
+ guidance: `# ADR Planning Session Started
189
+
190
+ Welcome to the interactive ADR planning process. Let's start by defining the problem.
191
+
192
+ ## Current Phase: Problem Definition
193
+
194
+ Please provide:
195
+ 1. **What architectural decision needs to be made?**
196
+ 2. **Why is this decision necessary now?**
197
+ 3. **What are the key constraints?**
198
+ 4. **Who are the stakeholders?**
199
+ 5. **What would success look like?**
200
+
201
+ ### Next Steps:
202
+ ${session.metadata.nextSteps.map(step => `- ${step}`).join('\n')}
203
+
204
+ Use \`provide_input\` with your problem statement to continue.`,
205
+ nextAction: 'provide_input'
206
+ };
207
+ }
208
+ /**
209
+ * Continue an existing session
210
+ */
211
+ async function continueSession(args) {
212
+ if (!args.sessionId) {
213
+ throw new McpAdrError('Session ID required to continue session', 'INVALID_INPUT');
214
+ }
215
+ let session = sessions.get(args.sessionId);
216
+ if (!session) {
217
+ const loadedSession = await loadSession(args.projectPath, args.sessionId);
218
+ if (!loadedSession) {
219
+ throw new McpAdrError(`Session ${args.sessionId} not found`, 'SESSION_NOT_FOUND');
220
+ }
221
+ session = loadedSession;
222
+ sessions.set(args.sessionId, session);
223
+ }
224
+ const phaseGuidance = {
225
+ problem_definition: 'Define the architectural problem and constraints',
226
+ research_analysis: 'Research existing solutions and gather context',
227
+ option_exploration: 'Identify and evaluate alternative approaches',
228
+ decision_making: 'Select the best option with clear rationale',
229
+ impact_assessment: 'Analyze the impacts and risks of the decision',
230
+ implementation_planning: 'Plan the implementation with tasks and timeline',
231
+ adr_generation: 'Generate the final ADR document',
232
+ completed: 'Session completed - ADR has been generated'
233
+ };
234
+ return {
235
+ success: true,
236
+ sessionId: args.sessionId,
237
+ phase: session.phase,
238
+ context: session.context,
239
+ guidance: phaseGuidance[session.phase],
240
+ nextSteps: session.metadata.nextSteps,
241
+ progress: getSessionProgress(session)
242
+ };
243
+ }
244
+ /**
245
+ * Process user input based on current phase
246
+ */
247
+ async function provideInput(args) {
248
+ if (!args.sessionId) {
249
+ throw new McpAdrError('Session ID required', 'INVALID_INPUT');
250
+ }
251
+ const session = sessions.get(args.sessionId);
252
+ if (!session) {
253
+ throw new McpAdrError(`Session ${args.sessionId} not found`, 'SESSION_NOT_FOUND');
254
+ }
255
+ let response = { success: true, sessionId: args.sessionId };
256
+ switch (session.phase) {
257
+ case 'problem_definition':
258
+ response = await handleProblemDefinition(session, args.input);
259
+ break;
260
+ case 'research_analysis':
261
+ response = await handleResearchAnalysis(session, args.input);
262
+ break;
263
+ case 'option_exploration':
264
+ response = await handleOptionExploration(session, args.input);
265
+ break;
266
+ case 'decision_making':
267
+ response = await handleDecisionMaking(session, args.input);
268
+ break;
269
+ case 'impact_assessment':
270
+ response = await handleImpactAssessment(session, args.input);
271
+ break;
272
+ case 'implementation_planning':
273
+ response = await handleImplementationPlanning(session, args.input);
274
+ break;
275
+ case 'adr_generation':
276
+ response = await handleAdrGeneration(session, args.input);
277
+ break;
278
+ default:
279
+ throw new McpAdrError(`Invalid phase: ${session.phase}`, 'INVALID_PHASE');
280
+ }
281
+ session.metadata.updatedAt = new Date().toISOString();
282
+ await saveSession(session);
283
+ return response;
284
+ }
285
+ /**
286
+ * Handle problem definition phase
287
+ */
288
+ async function handleProblemDefinition(session, input) {
289
+ session.context.problemStatement = input.problemStatement || input;
290
+ session.phase = 'research_analysis';
291
+ session.metadata.lastAction = 'Problem defined';
292
+ session.metadata.nextSteps = [
293
+ 'Research existing solutions',
294
+ 'Analyze current architecture',
295
+ 'Identify relevant patterns and practices'
296
+ ];
297
+ return {
298
+ success: true,
299
+ sessionId: session.sessionId,
300
+ phase: session.phase,
301
+ guidance: `# Problem Defined
302
+
303
+ **Problem Statement:** ${session.context.problemStatement}
304
+
305
+ ## Next Phase: Research & Analysis
306
+
307
+ I'll help you research this topic. We can:
308
+ 1. Search for existing solutions and patterns
309
+ 2. Analyze your current codebase for context
310
+ 3. Research industry best practices
311
+ 4. Identify relevant architectural patterns
312
+
313
+ Would you like me to:
314
+ - Automatically research this topic? (use \`request_research\`)
315
+ - Or provide your own research findings? (use \`provide_input\` with findings)`,
316
+ nextAction: 'request_research',
317
+ autoResearch: true
318
+ };
319
+ }
320
+ /**
321
+ * Handle research analysis phase
322
+ */
323
+ async function handleResearchAnalysis(session, input) {
324
+ if (input.researchFindings) {
325
+ session.context.researchFindings = input.researchFindings;
326
+ }
327
+ session.phase = 'option_exploration';
328
+ session.metadata.lastAction = 'Research completed';
329
+ session.metadata.nextSteps = [
330
+ 'Identify possible solutions',
331
+ 'Evaluate pros and cons',
332
+ 'Assess implementation effort'
333
+ ];
334
+ return {
335
+ success: true,
336
+ sessionId: session.sessionId,
337
+ phase: session.phase,
338
+ researchSummary: session.context.researchFindings,
339
+ guidance: `# Research Complete
340
+
341
+ ## Key Findings:
342
+ ${session.context.researchFindings.map(f => `- **${f.source}**: ${f.insight}`).join('\n')}
343
+
344
+ ## Next Phase: Option Exploration
345
+
346
+ Based on the research, let's identify and evaluate different approaches.
347
+
348
+ Please provide options in this format:
349
+ \`\`\`json
350
+ {
351
+ "options": [
352
+ {
353
+ "name": "Option Name",
354
+ "description": "What this approach involves",
355
+ "pros": ["Advantage 1", "Advantage 2"],
356
+ "cons": ["Disadvantage 1", "Disadvantage 2"],
357
+ "risks": ["Risk 1", "Risk 2"],
358
+ "effort": "medium",
359
+ "confidence": 75
360
+ }
361
+ ]
362
+ }
363
+ \`\`\`
364
+
365
+ Or use \`evaluate_options\` to have me help generate options based on the research.`,
366
+ nextAction: 'evaluate_options'
367
+ };
368
+ }
369
+ /**
370
+ * Handle option exploration phase
371
+ */
372
+ async function handleOptionExploration(session, input) {
373
+ if (input.options) {
374
+ session.context.options = input.options;
375
+ }
376
+ session.phase = 'decision_making';
377
+ session.metadata.lastAction = 'Options evaluated';
378
+ session.metadata.nextSteps = [
379
+ 'Compare options against criteria',
380
+ 'Select best approach',
381
+ 'Document rationale'
382
+ ];
383
+ const optionComparison = session.context.options.map(opt => `### ${opt.name} (Confidence: ${opt.confidence}%)
384
+ **Description:** ${opt.description}
385
+ **Pros:** ${opt.pros.join(', ')}
386
+ **Cons:** ${opt.cons.join(', ')}
387
+ **Effort:** ${opt.effort}
388
+ **Risks:** ${opt.risks.join(', ')}`).join('\n\n');
389
+ return {
390
+ success: true,
391
+ sessionId: session.sessionId,
392
+ phase: session.phase,
393
+ options: session.context.options,
394
+ guidance: `# Options Evaluated
395
+
396
+ ## Available Options:
397
+ ${optionComparison}
398
+
399
+ ## Next Phase: Decision Making
400
+
401
+ Review the options and select the best approach.
402
+
403
+ Provide your decision with:
404
+ \`\`\`json
405
+ {
406
+ "selectedOption": {
407
+ "name": "Chosen Option Name",
408
+ "rationale": "Why this option was selected",
409
+ "tradeoffs": ["What we're giving up", "What we're gaining"]
410
+ }
411
+ }
412
+ \`\`\`
413
+
414
+ Use \`make_decision\` to proceed.`,
415
+ nextAction: 'make_decision'
416
+ };
417
+ }
418
+ /**
419
+ * Handle decision making phase
420
+ */
421
+ async function handleDecisionMaking(session, input) {
422
+ if (input.selectedOption) {
423
+ session.context.selectedOption = input.selectedOption;
424
+ }
425
+ session.phase = 'impact_assessment';
426
+ session.metadata.lastAction = 'Decision made';
427
+ session.metadata.nextSteps = [
428
+ 'Assess technical impacts',
429
+ 'Evaluate business impacts',
430
+ 'Identify risks and dependencies'
431
+ ];
432
+ return {
433
+ success: true,
434
+ sessionId: session.sessionId,
435
+ phase: session.phase,
436
+ decision: session.context.selectedOption,
437
+ guidance: `# Decision Made
438
+
439
+ **Selected:** ${session.context.selectedOption?.name}
440
+ **Rationale:** ${session.context.selectedOption?.rationale}
441
+
442
+ ## Next Phase: Impact Assessment
443
+
444
+ Let's analyze the impacts of this decision.
445
+
446
+ Provide impact analysis:
447
+ \`\`\`json
448
+ {
449
+ "impacts": {
450
+ "technical": ["Impact on architecture", "Performance considerations"],
451
+ "business": ["Time to market", "Cost implications"],
452
+ "team": ["Training needs", "Skill requirements"],
453
+ "risks": ["Implementation risks", "Migration challenges"],
454
+ "dependencies": ["Required tools", "External services"]
455
+ }
456
+ }
457
+ \`\`\`
458
+
459
+ Use \`assess_impact\` to continue.`,
460
+ nextAction: 'assess_impact'
461
+ };
462
+ }
463
+ /**
464
+ * Handle impact assessment phase
465
+ */
466
+ async function handleImpactAssessment(session, input) {
467
+ if (input.impacts) {
468
+ session.context.impacts = input.impacts;
469
+ }
470
+ session.phase = 'implementation_planning';
471
+ session.metadata.lastAction = 'Impacts assessed';
472
+ session.metadata.nextSteps = [
473
+ 'Define implementation phases',
474
+ 'Create task list',
475
+ 'Set timeline and milestones'
476
+ ];
477
+ return {
478
+ success: true,
479
+ sessionId: session.sessionId,
480
+ phase: session.phase,
481
+ impacts: session.context.impacts,
482
+ guidance: `# Impact Assessment Complete
483
+
484
+ ## Identified Impacts:
485
+ - **Technical:** ${session.context.impacts?.technical.length} impacts
486
+ - **Business:** ${session.context.impacts?.business.length} impacts
487
+ - **Team:** ${session.context.impacts?.team.length} impacts
488
+ - **Risks:** ${session.context.impacts?.risks.length} risks
489
+ - **Dependencies:** ${session.context.impacts?.dependencies.length} dependencies
490
+
491
+ ## Next Phase: Implementation Planning
492
+
493
+ Let's create an implementation plan with tasks and timeline.
494
+
495
+ Provide implementation details:
496
+ \`\`\`json
497
+ {
498
+ "implementation": {
499
+ "phases": ["Phase 1: Setup", "Phase 2: Core Implementation"],
500
+ "tasks": [
501
+ {
502
+ "description": "Task description",
503
+ "priority": "high",
504
+ "effort": "2 days",
505
+ "assignee": "team-member"
506
+ }
507
+ ],
508
+ "timeline": "6 weeks",
509
+ "successCriteria": ["Criteria 1", "Criteria 2"]
510
+ }
511
+ }
512
+ \`\`\`
513
+
514
+ Use \`plan_implementation\` to continue.`,
515
+ nextAction: 'plan_implementation'
516
+ };
517
+ }
518
+ /**
519
+ * Handle implementation planning phase
520
+ */
521
+ async function handleImplementationPlanning(session, input) {
522
+ if (input.implementation) {
523
+ session.context.implementation = input.implementation;
524
+ }
525
+ session.phase = 'adr_generation';
526
+ session.metadata.lastAction = 'Implementation planned';
527
+ session.metadata.nextSteps = [
528
+ 'Generate ADR document',
529
+ 'Create TODO items',
530
+ 'Update project plan'
531
+ ];
532
+ return {
533
+ success: true,
534
+ sessionId: session.sessionId,
535
+ phase: session.phase,
536
+ implementation: session.context.implementation,
537
+ guidance: `# Implementation Plan Complete
538
+
539
+ ## Plan Summary:
540
+ - **Phases:** ${session.context.implementation?.phases.length} phases
541
+ - **Tasks:** ${session.context.implementation?.tasks.length} tasks
542
+ - **Timeline:** ${session.context.implementation?.timeline}
543
+ - **Success Criteria:** ${session.context.implementation?.successCriteria.length} criteria
544
+
545
+ ## Next Phase: ADR Generation
546
+
547
+ Ready to generate the ADR document with all the information gathered.
548
+
549
+ The ADR will include:
550
+ - Problem statement and context
551
+ - Considered options with evaluation
552
+ - Decision and rationale
553
+ - Impacts and consequences
554
+ - Implementation plan
555
+
556
+ Use \`generate_adr\` to create the ADR document.`,
557
+ nextAction: 'generate_adr',
558
+ readyToGenerate: true
559
+ };
560
+ }
561
+ /**
562
+ * Handle ADR generation phase
563
+ */
564
+ async function handleAdrGeneration(session, _input) {
565
+ const adrContent = generateAdrContent(session);
566
+ session.context.adrContent = adrContent;
567
+ // Save ADR to file
568
+ const adrDir = join(session.context.projectPath, session.context.adrDirectory);
569
+ await fs.mkdir(adrDir, { recursive: true });
570
+ const adrNumber = await getNextAdrNumber(adrDir);
571
+ const adrFilename = `${String(adrNumber).padStart(4, '0')}-${session.context.selectedOption?.name.toLowerCase().replace(/\s+/g, '-')}.md`;
572
+ const adrPath = join(adrDir, adrFilename);
573
+ await fs.writeFile(adrPath, adrContent);
574
+ session.phase = 'completed';
575
+ session.metadata.lastAction = 'ADR generated';
576
+ session.metadata.nextSteps = [];
577
+ return {
578
+ success: true,
579
+ sessionId: session.sessionId,
580
+ phase: session.phase,
581
+ adrPath,
582
+ adrContent,
583
+ guidance: `# ADR Generated Successfully!
584
+
585
+ ## ADR Details:
586
+ - **File:** ${adrPath}
587
+ - **Decision:** ${session.context.selectedOption?.name}
588
+ - **Status:** Draft
589
+
590
+ ## Next Steps:
591
+ 1. Review the generated ADR
592
+ 2. TODO items have been created based on the implementation plan
593
+ 3. Project plan has been updated
594
+
595
+ You can:
596
+ - Use \`update_todos\` to generate TODO items
597
+ - Use \`complete_session\` to finish and clean up
598
+ - Make manual edits to the ADR file as needed`,
599
+ nextAction: 'update_todos',
600
+ completed: true
601
+ };
602
+ }
603
+ /**
604
+ * Request research on the problem
605
+ */
606
+ async function requestResearch(args) {
607
+ if (!args.sessionId) {
608
+ throw new McpAdrError('Session ID required', 'INVALID_INPUT');
609
+ }
610
+ const session = sessions.get(args.sessionId);
611
+ if (!session) {
612
+ throw new McpAdrError(`Session ${args.sessionId} not found`, 'SESSION_NOT_FOUND');
613
+ }
614
+ // Simulate research integration (would integrate with actual research tools)
615
+ const researchFindings = [
616
+ {
617
+ source: 'Industry Best Practices',
618
+ insight: `For ${session.context.problemStatement}, microservices architecture is commonly used for scalability`,
619
+ relevance: 'High - Addresses core scalability concerns'
620
+ },
621
+ {
622
+ source: 'Current Codebase Analysis',
623
+ insight: 'Existing monolithic structure with clear domain boundaries',
624
+ relevance: 'High - Shows feasibility of decomposition'
625
+ },
626
+ {
627
+ source: 'Team Experience',
628
+ insight: 'Team has limited microservices experience but strong domain knowledge',
629
+ relevance: 'Medium - Impacts implementation approach'
630
+ }
631
+ ];
632
+ session.context.researchFindings = researchFindings;
633
+ session.phase = 'option_exploration';
634
+ session.metadata.updatedAt = new Date().toISOString();
635
+ await saveSession(session);
636
+ return {
637
+ success: true,
638
+ sessionId: args.sessionId,
639
+ researchFindings,
640
+ guidance: 'Research complete. Moving to option exploration phase.',
641
+ nextAction: 'evaluate_options'
642
+ };
643
+ }
644
+ /**
645
+ * Evaluate options based on research
646
+ */
647
+ async function evaluateOptions(args) {
648
+ if (!args.sessionId) {
649
+ throw new McpAdrError('Session ID required', 'INVALID_INPUT');
650
+ }
651
+ const session = sessions.get(args.sessionId);
652
+ if (!session) {
653
+ throw new McpAdrError(`Session ${args.sessionId} not found`, 'SESSION_NOT_FOUND');
654
+ }
655
+ // Generate options based on problem and research
656
+ const options = [
657
+ {
658
+ name: 'Full Microservices Migration',
659
+ description: 'Complete decomposition into microservices',
660
+ pros: ['Maximum scalability', 'Independent deployment', 'Technology flexibility'],
661
+ cons: ['High complexity', 'Significant refactoring', 'Operational overhead'],
662
+ risks: ['Team learning curve', 'Integration complexity', 'Data consistency'],
663
+ effort: 'high',
664
+ confidence: 60
665
+ },
666
+ {
667
+ name: 'Modular Monolith',
668
+ description: 'Keep monolithic deployment but enforce module boundaries',
669
+ pros: ['Simpler operations', 'Easier data consistency', 'Gradual evolution path'],
670
+ cons: ['Limited scalability', 'Shared deployment', 'Technology constraints'],
671
+ risks: ['Module boundary violations', 'Scaling limitations'],
672
+ effort: 'medium',
673
+ confidence: 80
674
+ },
675
+ {
676
+ name: 'Selective Service Extraction',
677
+ description: 'Extract only high-traffic components as services',
678
+ pros: ['Targeted scalability', 'Lower complexity', 'Pragmatic approach'],
679
+ cons: ['Partial benefits', 'Mixed architecture'],
680
+ risks: ['Service boundary decisions', 'Integration points'],
681
+ effort: 'medium',
682
+ confidence: 85
683
+ }
684
+ ];
685
+ session.context.options = options;
686
+ session.phase = 'decision_making';
687
+ session.metadata.updatedAt = new Date().toISOString();
688
+ await saveSession(session);
689
+ return {
690
+ success: true,
691
+ sessionId: args.sessionId,
692
+ options,
693
+ guidance: 'Options evaluated. Ready for decision making.',
694
+ nextAction: 'make_decision'
695
+ };
696
+ }
697
+ /**
698
+ * Update TODO items based on ADR decisions
699
+ */
700
+ async function updateTodos(args) {
701
+ if (!args.sessionId) {
702
+ throw new McpAdrError('Session ID required', 'INVALID_INPUT');
703
+ }
704
+ const session = sessions.get(args.sessionId);
705
+ if (!session) {
706
+ throw new McpAdrError(`Session ${args.sessionId} not found`, 'SESSION_NOT_FOUND');
707
+ }
708
+ if (!session.context.implementation) {
709
+ throw new McpAdrError('No implementation plan available', 'MISSING_IMPLEMENTATION');
710
+ }
711
+ // Generate TODO items from implementation tasks
712
+ const todos = session.context.implementation.tasks.map((task, index) => ({
713
+ id: `adr-${session.sessionId}-task-${index + 1}`,
714
+ description: task.description,
715
+ priority: task.priority,
716
+ effort: task.effort,
717
+ assignee: task.assignee,
718
+ adrLink: session.context.adrContent ? 'Generated ADR' : undefined,
719
+ status: 'pending'
720
+ }));
721
+ return {
722
+ success: true,
723
+ sessionId: args.sessionId,
724
+ todosGenerated: todos.length,
725
+ todos,
726
+ guidance: `Generated ${todos.length} TODO items from the implementation plan.`
727
+ };
728
+ }
729
+ /**
730
+ * Get guidance for current phase
731
+ */
732
+ async function getGuidance(args) {
733
+ if (!args.sessionId) {
734
+ throw new McpAdrError('Session ID required', 'INVALID_INPUT');
735
+ }
736
+ const session = sessions.get(args.sessionId);
737
+ if (!session) {
738
+ throw new McpAdrError(`Session ${args.sessionId} not found`, 'SESSION_NOT_FOUND');
739
+ }
740
+ const phaseGuidance = {
741
+ problem_definition: {
742
+ tips: [
743
+ 'Be specific about the problem',
744
+ 'Include quantifiable metrics if possible',
745
+ 'Identify all stakeholders',
746
+ 'Define clear success criteria'
747
+ ],
748
+ questions: [
749
+ 'What specific problem are we solving?',
750
+ 'Why does this need to be addressed now?',
751
+ 'What happens if we do nothing?',
752
+ 'How will we measure success?'
753
+ ],
754
+ examples: [
755
+ 'Application response time exceeds 3 seconds under load',
756
+ 'Cannot deploy services independently',
757
+ 'Database becoming a bottleneck for scaling'
758
+ ]
759
+ },
760
+ research_analysis: {
761
+ tips: [
762
+ 'Look for industry patterns',
763
+ 'Analyze similar problems in your codebase',
764
+ 'Consider team capabilities',
765
+ 'Research tooling and frameworks'
766
+ ],
767
+ questions: [
768
+ 'What solutions have others used?',
769
+ 'What patterns apply to our context?',
770
+ 'What are the technical constraints?',
771
+ 'What skills does the team have?'
772
+ ]
773
+ },
774
+ option_exploration: {
775
+ tips: [
776
+ 'Consider at least 3 options',
777
+ 'Include a "do nothing" option if relevant',
778
+ 'Be honest about pros and cons',
779
+ 'Estimate effort realistically'
780
+ ],
781
+ questions: [
782
+ 'What are all possible approaches?',
783
+ 'What are the tradeoffs?',
784
+ 'How much effort is each option?',
785
+ 'What are the risks?'
786
+ ]
787
+ }
788
+ };
789
+ const currentGuidance = phaseGuidance[session.phase] || {
790
+ tips: ['Follow the structured process'],
791
+ questions: ['What do you need help with?']
792
+ };
793
+ return {
794
+ success: true,
795
+ sessionId: args.sessionId,
796
+ phase: session.phase,
797
+ guidance: currentGuidance,
798
+ progress: getSessionProgress(session),
799
+ nextSteps: session.metadata.nextSteps
800
+ };
801
+ }
802
+ /**
803
+ * Complete the planning session
804
+ */
805
+ async function completeSession(args) {
806
+ if (!args.sessionId) {
807
+ throw new McpAdrError('Session ID required', 'INVALID_INPUT');
808
+ }
809
+ const session = sessions.get(args.sessionId);
810
+ if (!session) {
811
+ throw new McpAdrError(`Session ${args.sessionId} not found`, 'SESSION_NOT_FOUND');
812
+ }
813
+ // Clean up session
814
+ sessions.delete(args.sessionId);
815
+ // Archive session file
816
+ const cacheDir = await getCacheDir(session.context.projectPath);
817
+ const sessionFile = join(cacheDir, `adr-planning-session-${session.sessionId}.json`);
818
+ const archiveFile = join(cacheDir, `archived-adr-planning-session-${session.sessionId}.json`);
819
+ try {
820
+ await fs.rename(sessionFile, archiveFile);
821
+ }
822
+ catch (error) {
823
+ console.warn('Failed to archive session file:', error);
824
+ }
825
+ return {
826
+ success: true,
827
+ sessionId: args.sessionId,
828
+ summary: {
829
+ problem: session.context.problemStatement,
830
+ decision: session.context.selectedOption?.name,
831
+ adrGenerated: !!session.context.adrContent,
832
+ tasksCreated: session.context.implementation?.tasks.length || 0,
833
+ duration: calculateDuration(session.metadata.createdAt, session.metadata.updatedAt)
834
+ },
835
+ message: 'Planning session completed successfully!'
836
+ };
837
+ }
838
+ /**
839
+ * Helper function to generate ADR content
840
+ */
841
+ function generateAdrContent(session) {
842
+ const date = new Date().toISOString().split('T')[0];
843
+ const status = 'Draft';
844
+ return `# ${session.context.selectedOption?.name}
845
+
846
+ **Status:** ${status}
847
+ **Date:** ${date}
848
+ **Decision Made By:** [Team/Architect Name]
849
+
850
+ ## Context
851
+
852
+ ${session.context.problemStatement}
853
+
854
+ ### Research Findings
855
+
856
+ ${session.context.researchFindings.map(f => `- **${f.source}**: ${f.insight}`).join('\n')}
857
+
858
+ ## Decision
859
+
860
+ We have decided to implement **${session.context.selectedOption?.name}**.
861
+
862
+ ### Rationale
863
+
864
+ ${session.context.selectedOption?.rationale}
865
+
866
+ ### Trade-offs
867
+
868
+ ${session.context.selectedOption?.tradeoffs.map(t => `- ${t}`).join('\n')}
869
+
870
+ ## Considered Options
871
+
872
+ ${session.context.options.map(opt => `
873
+ ### ${opt.name}
874
+
875
+ ${opt.description}
876
+
877
+ **Pros:**
878
+ ${opt.pros.map(p => `- ${p}`).join('\n')}
879
+
880
+ **Cons:**
881
+ ${opt.cons.map(c => `- ${c}`).join('\n')}
882
+
883
+ **Effort:** ${opt.effort}
884
+ **Confidence:** ${opt.confidence}%
885
+ `).join('\n')}
886
+
887
+ ## Consequences
888
+
889
+ ### Technical Impacts
890
+ ${session.context.impacts?.technical.map(i => `- ${i}`).join('\n')}
891
+
892
+ ### Business Impacts
893
+ ${session.context.impacts?.business.map(i => `- ${i}`).join('\n')}
894
+
895
+ ### Team Impacts
896
+ ${session.context.impacts?.team.map(i => `- ${i}`).join('\n')}
897
+
898
+ ### Risks
899
+ ${session.context.impacts?.risks.map(r => `- ${r}`).join('\n')}
900
+
901
+ ### Dependencies
902
+ ${session.context.impacts?.dependencies.map(d => `- ${d}`).join('\n')}
903
+
904
+ ## Implementation Plan
905
+
906
+ ### Phases
907
+ ${session.context.implementation?.phases.map((p, i) => `${i + 1}. ${p}`).join('\n')}
908
+
909
+ ### Tasks
910
+ ${session.context.implementation?.tasks.map(t => `- [ ] ${t.description} (Priority: ${t.priority}, Effort: ${t.effort}${t.assignee ? `, Assignee: ${t.assignee}` : ''})`).join('\n')}
911
+
912
+ ### Timeline
913
+ ${session.context.implementation?.timeline}
914
+
915
+ ### Success Criteria
916
+ ${session.context.implementation?.successCriteria.map(c => `- ${c}`).join('\n')}
917
+
918
+ ## References
919
+
920
+ - [Link to relevant documentation]
921
+ - [Link to research materials]
922
+ - [Link to architectural diagrams]
923
+
924
+ ## Revision History
925
+
926
+ - ${date}: Initial draft created through interactive planning session
927
+ `;
928
+ }
929
+ /**
930
+ * Helper function to get next ADR number
931
+ */
932
+ async function getNextAdrNumber(adrDir) {
933
+ try {
934
+ const files = await fs.readdir(adrDir);
935
+ const adrFiles = files.filter(f => f.match(/^\d{4}-.*\.md$/));
936
+ if (adrFiles.length === 0)
937
+ return 1;
938
+ const numbers = adrFiles.map(f => parseInt(f.substring(0, 4)));
939
+ return Math.max(...numbers) + 1;
940
+ }
941
+ catch {
942
+ return 1;
943
+ }
944
+ }
945
+ /**
946
+ * Helper function to calculate session progress
947
+ */
948
+ function getSessionProgress(session) {
949
+ const phases = [
950
+ 'problem_definition',
951
+ 'research_analysis',
952
+ 'option_exploration',
953
+ 'decision_making',
954
+ 'impact_assessment',
955
+ 'implementation_planning',
956
+ 'adr_generation'
957
+ ];
958
+ const currentIndex = phases.indexOf(session.phase);
959
+ if (currentIndex === -1)
960
+ return 100; // completed
961
+ return Math.round((currentIndex / phases.length) * 100);
962
+ }
963
+ /**
964
+ * Helper function to calculate duration
965
+ */
966
+ function calculateDuration(start, end) {
967
+ const startTime = new Date(start).getTime();
968
+ const endTime = new Date(end).getTime();
969
+ const duration = endTime - startTime;
970
+ const hours = Math.floor(duration / (1000 * 60 * 60));
971
+ const minutes = Math.floor((duration % (1000 * 60 * 60)) / (1000 * 60));
972
+ if (hours > 0) {
973
+ return `${hours}h ${minutes}m`;
974
+ }
975
+ return `${minutes}m`;
976
+ }
977
+ /**
978
+ * Main entry point for the interactive ADR planning tool
979
+ */
980
+ export async function interactiveAdrPlanning(args) {
981
+ try {
982
+ const input = InteractiveAdrPlanningSchema.parse(args);
983
+ switch (input.operation) {
984
+ case 'start_session':
985
+ return await startSession(input);
986
+ case 'continue_session':
987
+ return await continueSession(input);
988
+ case 'provide_input':
989
+ return await provideInput(input);
990
+ case 'request_research':
991
+ return await requestResearch(input);
992
+ case 'evaluate_options':
993
+ return await evaluateOptions(input);
994
+ case 'make_decision':
995
+ input.input = { selectedOption: input.input };
996
+ return await provideInput(input);
997
+ case 'assess_impact':
998
+ input.input = { impacts: input.input };
999
+ return await provideInput(input);
1000
+ case 'plan_implementation':
1001
+ input.input = { implementation: input.input };
1002
+ return await provideInput(input);
1003
+ case 'generate_adr':
1004
+ return await provideInput(input);
1005
+ case 'update_todos':
1006
+ return await updateTodos(input);
1007
+ case 'get_guidance':
1008
+ return await getGuidance(input);
1009
+ case 'save_session':
1010
+ const session = sessions.get(input.sessionId);
1011
+ if (!session)
1012
+ throw new McpAdrError('Session not found', 'SESSION_NOT_FOUND');
1013
+ await saveSession(session);
1014
+ return { success: true, message: 'Session saved' };
1015
+ case 'complete_session':
1016
+ return await completeSession(input);
1017
+ default:
1018
+ throw new McpAdrError(`Unknown operation: ${input.operation}`, 'INVALID_OPERATION');
1019
+ }
1020
+ }
1021
+ catch (error) {
1022
+ if (error instanceof McpAdrError) {
1023
+ throw error;
1024
+ }
1025
+ throw new McpAdrError(`Interactive ADR planning failed: ${error instanceof Error ? error.message : String(error)}`, 'PLANNING_ERROR');
1026
+ }
1027
+ }
1028
+ //# sourceMappingURL=interactive-adr-planning-tool.js.map