@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.
@@ -0,0 +1,1328 @@
1
+ /**
2
+ * Planning Simulator
3
+ *
4
+ * Provides "what if" scenario planning for projects.
5
+ * Simulates feature additions, refactoring impacts, timeline changes,
6
+ * and resource allocation decisions.
7
+ *
8
+ * @package bootspring
9
+ * @module core/planning/simulator
10
+ */
11
+
12
+ const fs = require('fs').promises;
13
+ const path = require('path');
14
+
15
+ /**
16
+ * Scenario types and their configurations
17
+ */
18
+ const SCENARIO_TYPES = {
19
+ feature: {
20
+ name: 'Feature Addition',
21
+ description: 'Simulate adding a new feature to the project',
22
+ requiredInputs: ['featureName', 'complexity'],
23
+ optionalInputs: ['dependencies', 'timeline', 'resources'],
24
+ outputs: ['timeline', 'risks', 'dependencies', 'documentation', 'testing']
25
+ },
26
+ refactor: {
27
+ name: 'Refactoring',
28
+ description: 'Simulate refactoring existing code',
29
+ requiredInputs: ['scope', 'target'],
30
+ optionalInputs: ['approach', 'constraints'],
31
+ outputs: ['risk', 'testing', 'documentation', 'timeline']
32
+ },
33
+ timeline: {
34
+ name: 'Timeline Change',
35
+ description: 'Simulate compressing or extending project timeline',
36
+ requiredInputs: ['change', 'amount'],
37
+ optionalInputs: ['priorities', 'constraints'],
38
+ outputs: ['scope', 'quality', 'resources', 'risks']
39
+ },
40
+ resource: {
41
+ name: 'Resource Change',
42
+ description: 'Simulate adding or removing team resources',
43
+ requiredInputs: ['action', 'count'],
44
+ optionalInputs: ['skills', 'timing'],
45
+ outputs: ['velocity', 'coordination', 'timeline', 'quality']
46
+ },
47
+ dependency: {
48
+ name: 'Dependency Change',
49
+ description: 'Simulate adding, updating, or removing dependencies',
50
+ requiredInputs: ['action', 'package'],
51
+ optionalInputs: ['version', 'alternatives'],
52
+ outputs: ['compatibility', 'security', 'bundleSize', 'maintenance']
53
+ },
54
+ architecture: {
55
+ name: 'Architecture Change',
56
+ description: 'Simulate architectural modifications',
57
+ requiredInputs: ['change', 'components'],
58
+ optionalInputs: ['constraints', 'phases'],
59
+ outputs: ['complexity', 'risk', 'timeline', 'benefits']
60
+ },
61
+ migration: {
62
+ name: 'Technology Migration',
63
+ description: 'Simulate migrating to new technology',
64
+ requiredInputs: ['from', 'to'],
65
+ optionalInputs: ['scope', 'parallel'],
66
+ outputs: ['effort', 'risk', 'timeline', 'training']
67
+ }
68
+ };
69
+
70
+ /**
71
+ * Impact levels
72
+ */
73
+ const IMPACT_LEVELS = {
74
+ minimal: { score: 1, label: 'Minimal', color: 'green' },
75
+ low: { score: 2, label: 'Low', color: 'blue' },
76
+ medium: { score: 3, label: 'Medium', color: 'yellow' },
77
+ high: { score: 4, label: 'High', color: 'orange' },
78
+ critical: { score: 5, label: 'Critical', color: 'red' }
79
+ };
80
+
81
+ /**
82
+ * Complexity multipliers
83
+ */
84
+ const COMPLEXITY_MULTIPLIERS = {
85
+ trivial: 0.5,
86
+ simple: 1.0,
87
+ moderate: 1.5,
88
+ complex: 2.5,
89
+ very_complex: 4.0
90
+ };
91
+
92
+ /**
93
+ * PlanningSimulator class
94
+ */
95
+ class PlanningSimulator {
96
+ /**
97
+ * @param {Object} options - Configuration options
98
+ */
99
+ constructor(options = {}) {
100
+ this.projectRoot = options.projectRoot || process.cwd();
101
+ this.projectContext = null;
102
+ }
103
+
104
+ /**
105
+ * Initialize with project context
106
+ * @param {Object} context - Project context
107
+ */
108
+ async initialize(context = null) {
109
+ if (context) {
110
+ this.projectContext = context;
111
+ } else {
112
+ this.projectContext = await this.loadProjectContext();
113
+ }
114
+ return this;
115
+ }
116
+
117
+ /**
118
+ * Load project context from codebase
119
+ */
120
+ async loadProjectContext() {
121
+ const context = {
122
+ framework: null,
123
+ language: 'javascript',
124
+ hasTypescript: false,
125
+ hasTests: false,
126
+ hasCI: false,
127
+ dependencies: [],
128
+ devDependencies: [],
129
+ structure: 'unknown'
130
+ };
131
+
132
+ try {
133
+ const pkgPath = path.join(this.projectRoot, 'package.json');
134
+ const pkgContent = await fs.readFile(pkgPath, 'utf-8');
135
+ const pkg = JSON.parse(pkgContent);
136
+
137
+ context.dependencies = Object.keys(pkg.dependencies || {});
138
+ context.devDependencies = Object.keys(pkg.devDependencies || {});
139
+
140
+ // Detect framework
141
+ if (context.dependencies.includes('next')) context.framework = 'nextjs';
142
+ else if (context.dependencies.includes('react')) context.framework = 'react';
143
+ else if (context.dependencies.includes('vue')) context.framework = 'vue';
144
+ else if (context.dependencies.includes('express')) context.framework = 'express';
145
+
146
+ // Detect TypeScript
147
+ context.hasTypescript = context.devDependencies.includes('typescript');
148
+
149
+ // Detect tests
150
+ context.hasTests = context.devDependencies.some(d =>
151
+ ['jest', 'vitest', 'mocha', 'cypress'].includes(d)
152
+ );
153
+
154
+ // Detect CI
155
+ try {
156
+ await fs.access(path.join(this.projectRoot, '.github/workflows'));
157
+ context.hasCI = true;
158
+ } catch {
159
+ context.hasCI = false;
160
+ }
161
+ } catch {
162
+ // No package.json
163
+ }
164
+
165
+ return context;
166
+ }
167
+
168
+ /**
169
+ * Run a simulation scenario
170
+ * @param {Object} scenario - Scenario configuration
171
+ */
172
+ async simulate(scenario) {
173
+ const scenarioType = SCENARIO_TYPES[scenario.type];
174
+ if (!scenarioType) {
175
+ throw new Error(`Unknown scenario type: ${scenario.type}. Valid types: ${Object.keys(SCENARIO_TYPES).join(', ')}`);
176
+ }
177
+
178
+ // Validate required inputs
179
+ for (const input of scenarioType.requiredInputs) {
180
+ if (!scenario[input]) {
181
+ throw new Error(`Missing required input: ${input}`);
182
+ }
183
+ }
184
+
185
+ // Run the appropriate simulation
186
+ const simulationMethod = `simulate${scenario.type.charAt(0).toUpperCase() + scenario.type.slice(1)}`;
187
+ const results = await this[simulationMethod](scenario);
188
+
189
+ // Add metadata
190
+ return {
191
+ timestamp: new Date().toISOString(),
192
+ scenario: {
193
+ type: scenario.type,
194
+ name: scenarioType.name,
195
+ inputs: scenario
196
+ },
197
+ results,
198
+ summary: this.generateSummary(results),
199
+ recommendations: this.generateRecommendations(scenario, results)
200
+ };
201
+ }
202
+
203
+ /**
204
+ * Simulate feature addition
205
+ * @param {Object} scenario - Feature scenario
206
+ */
207
+ async simulateFeature(scenario) {
208
+ const complexity = scenario.complexity || 'moderate';
209
+ const multiplier = COMPLEXITY_MULTIPLIERS[complexity] || 1.5;
210
+
211
+ const results = {
212
+ timeline: this.estimateFeatureTimeline(scenario, multiplier),
213
+ risks: this.identifyFeatureRisks(scenario, complexity),
214
+ dependencies: this.analyzeFeatureDependencies(scenario),
215
+ documentation: this.assessDocumentationNeeds(scenario),
216
+ testing: this.assessTestingNeeds(scenario, complexity),
217
+ impact: this.assessFeatureImpact(scenario, complexity)
218
+ };
219
+
220
+ return results;
221
+ }
222
+
223
+ /**
224
+ * Estimate feature timeline
225
+ * @param {Object} scenario - Feature scenario
226
+ * @param {number} multiplier - Complexity multiplier
227
+ */
228
+ estimateFeatureTimeline(scenario, multiplier) {
229
+ const baseDays = {
230
+ trivial: 0.5,
231
+ simple: 2,
232
+ moderate: 5,
233
+ complex: 10,
234
+ very_complex: 20
235
+ };
236
+
237
+ const base = baseDays[scenario.complexity] || 5;
238
+ const adjusted = base * multiplier;
239
+
240
+ // Factor in dependencies
241
+ const depFactor = (scenario.dependencies?.length || 0) * 0.5;
242
+
243
+ const totalDays = adjusted + depFactor;
244
+
245
+ return {
246
+ estimatedDays: Math.round(totalDays),
247
+ range: {
248
+ optimistic: Math.round(totalDays * 0.7),
249
+ likely: Math.round(totalDays),
250
+ pessimistic: Math.round(totalDays * 1.5)
251
+ },
252
+ phases: [
253
+ { name: 'Design', days: Math.round(totalDays * 0.15) },
254
+ { name: 'Implementation', days: Math.round(totalDays * 0.5) },
255
+ { name: 'Testing', days: Math.round(totalDays * 0.25) },
256
+ { name: 'Documentation', days: Math.round(totalDays * 0.1) }
257
+ ],
258
+ confidence: scenario.complexity === 'trivial' ? 'high' : 'medium'
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Identify feature risks
264
+ * @param {Object} scenario - Feature scenario
265
+ * @param {string} complexity - Complexity level
266
+ */
267
+ identifyFeatureRisks(scenario, complexity) {
268
+ const risks = [];
269
+
270
+ // Complexity-based risks
271
+ if (complexity === 'complex' || complexity === 'very_complex') {
272
+ risks.push({
273
+ type: 'complexity',
274
+ severity: 'high',
275
+ description: 'High complexity increases implementation risk',
276
+ mitigation: 'Break into smaller increments, add review checkpoints'
277
+ });
278
+ }
279
+
280
+ // Dependency risks
281
+ if (scenario.dependencies?.length > 3) {
282
+ risks.push({
283
+ type: 'dependencies',
284
+ severity: 'medium',
285
+ description: 'Multiple new dependencies increase integration risk',
286
+ mitigation: 'Evaluate alternatives, lock versions, add integration tests'
287
+ });
288
+ }
289
+
290
+ // Timeline risks
291
+ if (scenario.timeline === 'tight') {
292
+ risks.push({
293
+ type: 'timeline',
294
+ severity: 'high',
295
+ description: 'Tight timeline may impact quality',
296
+ mitigation: 'Identify scope reduction options, prioritize core functionality'
297
+ });
298
+ }
299
+
300
+ // Integration risks
301
+ risks.push({
302
+ type: 'integration',
303
+ severity: complexity === 'trivial' ? 'low' : 'medium',
304
+ description: 'New feature may affect existing functionality',
305
+ mitigation: 'Comprehensive regression testing, feature flags'
306
+ });
307
+
308
+ return risks;
309
+ }
310
+
311
+ /**
312
+ * Analyze feature dependencies
313
+ * @param {Object} scenario - Feature scenario
314
+ */
315
+ analyzeFeatureDependencies(scenario) {
316
+ const deps = scenario.dependencies || [];
317
+
318
+ return {
319
+ explicit: deps,
320
+ count: deps.length,
321
+ impact: deps.length > 3 ? 'high' : deps.length > 1 ? 'medium' : 'low',
322
+ suggestions: deps.length > 0 ? [
323
+ 'Lock dependency versions for stability',
324
+ 'Add integration tests for dependency interactions',
325
+ 'Document dependency requirements'
326
+ ] : ['No external dependencies - good isolation']
327
+ };
328
+ }
329
+
330
+ /**
331
+ * Assess documentation needs
332
+ * @param {Object} scenario - Feature scenario
333
+ */
334
+ assessDocumentationNeeds(scenario) {
335
+ const complexity = scenario.complexity || 'moderate';
336
+
337
+ const docs = [];
338
+
339
+ // API docs if backend
340
+ if (scenario.includesApi !== false) {
341
+ docs.push({
342
+ type: 'API',
343
+ priority: 'high',
344
+ description: 'API endpoint documentation'
345
+ });
346
+ }
347
+
348
+ // User docs
349
+ if (complexity !== 'trivial') {
350
+ docs.push({
351
+ type: 'User Guide',
352
+ priority: 'medium',
353
+ description: 'End-user documentation'
354
+ });
355
+ }
356
+
357
+ // Technical docs for complex features
358
+ if (complexity === 'complex' || complexity === 'very_complex') {
359
+ docs.push({
360
+ type: 'Technical',
361
+ priority: 'high',
362
+ description: 'Architecture and design documentation'
363
+ });
364
+ }
365
+
366
+ // PRD update
367
+ docs.push({
368
+ type: 'PRD',
369
+ priority: 'high',
370
+ description: 'Update PRD with new feature'
371
+ });
372
+
373
+ return {
374
+ required: docs,
375
+ estimatedEffort: `${docs.length * 2} hours`,
376
+ automatable: ['API documentation can be auto-generated from code']
377
+ };
378
+ }
379
+
380
+ /**
381
+ * Assess testing needs
382
+ * @param {Object} scenario - Feature scenario
383
+ * @param {string} complexity - Complexity level
384
+ */
385
+ assessTestingNeeds(scenario, complexity) {
386
+ const tests = [];
387
+
388
+ // Unit tests always needed
389
+ tests.push({
390
+ type: 'Unit',
391
+ coverage: complexity === 'trivial' ? '70%' : '80%',
392
+ priority: 'high'
393
+ });
394
+
395
+ // Integration tests for non-trivial
396
+ if (complexity !== 'trivial') {
397
+ tests.push({
398
+ type: 'Integration',
399
+ coverage: '60%',
400
+ priority: 'high'
401
+ });
402
+ }
403
+
404
+ // E2E for complex features
405
+ if (complexity === 'complex' || complexity === 'very_complex') {
406
+ tests.push({
407
+ type: 'E2E',
408
+ coverage: 'Critical paths',
409
+ priority: 'high'
410
+ });
411
+ }
412
+
413
+ // Performance tests if needed
414
+ if (scenario.performanceSensitive) {
415
+ tests.push({
416
+ type: 'Performance',
417
+ coverage: 'Key operations',
418
+ priority: 'medium'
419
+ });
420
+ }
421
+
422
+ return {
423
+ required: tests,
424
+ estimatedTestCode: this.estimateTestCode(complexity),
425
+ regressionRisk: complexity === 'trivial' ? 'low' : 'medium'
426
+ };
427
+ }
428
+
429
+ /**
430
+ * Estimate test code effort
431
+ * @param {string} complexity - Complexity level
432
+ */
433
+ estimateTestCode(complexity) {
434
+ const ratios = {
435
+ trivial: '0.3x implementation',
436
+ simple: '0.5x implementation',
437
+ moderate: '0.7x implementation',
438
+ complex: '1x implementation',
439
+ very_complex: '1.2x implementation'
440
+ };
441
+ return ratios[complexity] || '0.7x implementation';
442
+ }
443
+
444
+ /**
445
+ * Assess feature impact
446
+ * @param {Object} scenario - Feature scenario
447
+ * @param {string} complexity - Complexity level
448
+ */
449
+ assessFeatureImpact(scenario, complexity) {
450
+ const impactScore = COMPLEXITY_MULTIPLIERS[complexity] || 1.5;
451
+
452
+ return {
453
+ codebase: impactScore > 2 ? 'high' : impactScore > 1 ? 'medium' : 'low',
454
+ users: scenario.userFacing !== false ? 'visible' : 'internal',
455
+ performance: scenario.performanceSensitive ? 'may impact' : 'minimal',
456
+ security: scenario.securitySensitive ? 'requires review' : 'standard',
457
+ maintenance: impactScore > 2.5 ? 'increased' : 'normal'
458
+ };
459
+ }
460
+
461
+ /**
462
+ * Simulate refactoring
463
+ * @param {Object} scenario - Refactor scenario
464
+ */
465
+ async simulateRefactor(scenario) {
466
+ return {
467
+ risk: this.assessRefactoringRisk(scenario),
468
+ testing: this.assessRefactoringTests(scenario),
469
+ timeline: this.estimateRefactoringTimeline(scenario),
470
+ documentation: this.assessRefactoringDocs(scenario),
471
+ rollback: this.planRefactoringRollback(scenario)
472
+ };
473
+ }
474
+
475
+ /**
476
+ * Assess refactoring risk
477
+ * @param {Object} scenario - Refactor scenario
478
+ */
479
+ assessRefactoringRisk(scenario) {
480
+ const scope = scenario.scope || 'module';
481
+ const risks = [];
482
+
483
+ if (scope === 'system' || scope === 'architecture') {
484
+ risks.push({
485
+ type: 'scope',
486
+ severity: 'high',
487
+ description: 'Wide-reaching changes increase regression risk',
488
+ mitigation: 'Incremental approach with feature flags'
489
+ });
490
+ }
491
+
492
+ risks.push({
493
+ type: 'regression',
494
+ severity: scope === 'file' ? 'low' : 'medium',
495
+ description: 'Existing functionality may break',
496
+ mitigation: 'Comprehensive test coverage before starting'
497
+ });
498
+
499
+ if (!this.projectContext?.hasTests) {
500
+ risks.push({
501
+ type: 'testing',
502
+ severity: 'high',
503
+ description: 'Lack of existing tests increases risk',
504
+ mitigation: 'Write characterization tests first'
505
+ });
506
+ }
507
+
508
+ return {
509
+ overall: scope === 'system' ? 'high' : scope === 'module' ? 'medium' : 'low',
510
+ factors: risks
511
+ };
512
+ }
513
+
514
+ /**
515
+ * Assess refactoring test requirements
516
+ * @param {Object} scenario - Refactor scenario
517
+ */
518
+ assessRefactoringTests(scenario) {
519
+ return {
520
+ beforeRefactor: [
521
+ 'Characterization tests for current behavior',
522
+ 'Integration tests for affected modules',
523
+ 'Performance baseline if relevant'
524
+ ],
525
+ afterRefactor: [
526
+ 'Same tests must pass',
527
+ 'Add tests for new structure',
528
+ 'Regression suite execution'
529
+ ],
530
+ coverage: 'Ensure 80%+ coverage of refactored code'
531
+ };
532
+ }
533
+
534
+ /**
535
+ * Estimate refactoring timeline
536
+ * @param {Object} scenario - Refactor scenario
537
+ */
538
+ estimateRefactoringTimeline(scenario) {
539
+ const scopeMultipliers = {
540
+ file: 1,
541
+ module: 3,
542
+ feature: 5,
543
+ system: 10,
544
+ architecture: 20
545
+ };
546
+
547
+ const baseDays = scopeMultipliers[scenario.scope] || 3;
548
+
549
+ return {
550
+ estimatedDays: baseDays,
551
+ phases: [
552
+ { name: 'Analysis', days: Math.round(baseDays * 0.2) },
553
+ { name: 'Test Setup', days: Math.round(baseDays * 0.2) },
554
+ { name: 'Refactor', days: Math.round(baseDays * 0.4) },
555
+ { name: 'Validation', days: Math.round(baseDays * 0.2) }
556
+ ]
557
+ };
558
+ }
559
+
560
+ /**
561
+ * Assess refactoring documentation
562
+ * @param {Object} scenario - Refactor scenario
563
+ */
564
+ assessRefactoringDocs(scenario) {
565
+ return {
566
+ required: [
567
+ 'ADR for refactoring decision',
568
+ 'Updated architecture docs if structure changes',
569
+ 'Migration guide if API changes'
570
+ ],
571
+ updateNeeded: ['Technical spec', 'Code comments']
572
+ };
573
+ }
574
+
575
+ /**
576
+ * Plan refactoring rollback
577
+ * @param {Object} scenario - Refactor scenario
578
+ */
579
+ planRefactoringRollback(scenario) {
580
+ return {
581
+ strategy: 'git revert',
582
+ preparation: [
583
+ 'Commit frequently with clear messages',
584
+ 'Use feature branch',
585
+ 'Tag stable points'
586
+ ],
587
+ timeToRollback: scenario.scope === 'system' ? '2-4 hours' : '15-30 minutes'
588
+ };
589
+ }
590
+
591
+ /**
592
+ * Simulate timeline change
593
+ * @param {Object} scenario - Timeline scenario
594
+ */
595
+ async simulateTimeline(scenario) {
596
+ const change = scenario.change; // 'compress' or 'extend'
597
+ const amount = scenario.amount || 20; // percentage
598
+
599
+ return {
600
+ scope: this.assessTimelineScopeImpact(change, amount),
601
+ quality: this.assessTimelineQualityImpact(change, amount),
602
+ resources: this.assessTimelineResourceImpact(change, amount),
603
+ risks: this.identifyTimelineRisks(change, amount)
604
+ };
605
+ }
606
+
607
+ /**
608
+ * Assess timeline scope impact
609
+ * @param {string} change - Change type
610
+ * @param {number} amount - Percentage change
611
+ */
612
+ assessTimelineScopeImpact(change, amount) {
613
+ if (change === 'compress') {
614
+ return {
615
+ impact: amount > 30 ? 'high' : 'medium',
616
+ recommendations: [
617
+ 'Prioritize MVP features',
618
+ 'Defer nice-to-have features',
619
+ 'Consider phased release'
620
+ ],
621
+ scopeReduction: `${Math.round(amount * 0.7)}%`
622
+ };
623
+ } else {
624
+ return {
625
+ impact: 'positive',
626
+ recommendations: [
627
+ 'Can include more features',
628
+ 'More time for polish',
629
+ 'Better testing coverage'
630
+ ],
631
+ additionalScope: `${Math.round(amount * 0.5)}%`
632
+ };
633
+ }
634
+ }
635
+
636
+ /**
637
+ * Assess timeline quality impact
638
+ * @param {string} change - Change type
639
+ * @param {number} amount - Percentage change
640
+ */
641
+ assessTimelineQualityImpact(change, amount) {
642
+ if (change === 'compress') {
643
+ return {
644
+ risk: amount > 30 ? 'high' : 'medium',
645
+ areas: [
646
+ { name: 'Test coverage', impact: 'may decrease' },
647
+ { name: 'Code reviews', impact: 'less thorough' },
648
+ { name: 'Documentation', impact: 'likely reduced' }
649
+ ],
650
+ mitigations: [
651
+ 'Automated testing critical',
652
+ 'Focus on critical path quality',
653
+ 'Accept technical debt consciously'
654
+ ]
655
+ };
656
+ } else {
657
+ return {
658
+ risk: 'low',
659
+ areas: [
660
+ { name: 'Test coverage', impact: 'can increase' },
661
+ { name: 'Code reviews', impact: 'more thorough' },
662
+ { name: 'Documentation', impact: 'can improve' }
663
+ ]
664
+ };
665
+ }
666
+ }
667
+
668
+ /**
669
+ * Assess timeline resource impact
670
+ * @param {string} change - Change type
671
+ * @param {number} amount - Percentage change
672
+ */
673
+ assessTimelineResourceImpact(change, amount) {
674
+ if (change === 'compress') {
675
+ return {
676
+ options: [
677
+ 'Add team members (ramp-up overhead)',
678
+ 'Increase working hours (burnout risk)',
679
+ 'Outsource components (coordination overhead)'
680
+ ],
681
+ recommendation: amount > 20 ?
682
+ 'Consider scope reduction over resource increase' :
683
+ 'Current team may absorb with focused priorities'
684
+ };
685
+ } else {
686
+ return {
687
+ options: [
688
+ 'Maintain current team',
689
+ 'Add quality-focused resources',
690
+ 'Invest in tooling/automation'
691
+ ],
692
+ recommendation: 'Use time for quality improvements, not just features'
693
+ };
694
+ }
695
+ }
696
+
697
+ /**
698
+ * Identify timeline risks
699
+ * @param {string} change - Change type
700
+ * @param {number} amount - Percentage change
701
+ */
702
+ identifyTimelineRisks(change, amount) {
703
+ const risks = [];
704
+
705
+ if (change === 'compress') {
706
+ risks.push({
707
+ type: 'burnout',
708
+ severity: amount > 30 ? 'high' : 'medium',
709
+ description: 'Team fatigue from accelerated pace',
710
+ mitigation: 'Clear priorities, sustainable pace commitment'
711
+ });
712
+
713
+ risks.push({
714
+ type: 'technical_debt',
715
+ severity: 'high',
716
+ description: 'Shortcuts accumulate',
717
+ mitigation: 'Track debt explicitly, plan remediation'
718
+ });
719
+ } else {
720
+ risks.push({
721
+ type: 'scope_creep',
722
+ severity: 'medium',
723
+ description: 'Extra time invites additional features',
724
+ mitigation: 'Lock scope, use time for quality'
725
+ });
726
+ }
727
+
728
+ return risks;
729
+ }
730
+
731
+ /**
732
+ * Simulate resource change
733
+ * @param {Object} scenario - Resource scenario
734
+ */
735
+ async simulateResource(scenario) {
736
+ const action = scenario.action; // 'add' or 'remove'
737
+ const count = scenario.count || 1;
738
+
739
+ return {
740
+ velocity: this.assessResourceVelocity(action, count),
741
+ coordination: this.assessResourceCoordination(action, count),
742
+ timeline: this.assessResourceTimeline(action, count),
743
+ quality: this.assessResourceQuality(action, count),
744
+ costs: this.assessResourceCosts(action, count)
745
+ };
746
+ }
747
+
748
+ /**
749
+ * Assess resource velocity impact
750
+ * @param {string} action - Add or remove
751
+ * @param {number} count - Number of resources
752
+ */
753
+ assessResourceVelocity(action, count) {
754
+ if (action === 'add') {
755
+ return {
756
+ immediate: 'Decreased (onboarding)',
757
+ shortTerm: `+${count * 15}% after ramp-up`,
758
+ longTerm: `+${count * 25}% at full productivity`,
759
+ rampUpTime: `${count * 2}-${count * 4} weeks`
760
+ };
761
+ } else {
762
+ return {
763
+ immediate: `-${count * 30}%`,
764
+ shortTerm: 'Stabilizes as work redistributes',
765
+ longTerm: 'New baseline established',
766
+ knowledgeLoss: 'Critical knowledge may be lost'
767
+ };
768
+ }
769
+ }
770
+
771
+ /**
772
+ * Assess resource coordination impact
773
+ * @param {string} action - Add or remove
774
+ * @param {number} count - Number of resources
775
+ */
776
+ assessResourceCoordination(action, count) {
777
+ if (action === 'add') {
778
+ return {
779
+ overhead: count > 2 ? 'high' : 'medium',
780
+ meetings: 'More coordination meetings needed',
781
+ communication: 'Communication channels expand',
782
+ recommendations: [
783
+ 'Clear ownership boundaries',
784
+ 'Regular sync meetings',
785
+ 'Documentation emphasis'
786
+ ]
787
+ };
788
+ } else {
789
+ return {
790
+ overhead: 'Decreased',
791
+ consolidation: 'Responsibilities must be reassigned',
792
+ recommendations: [
793
+ 'Knowledge transfer sessions',
794
+ 'Document tribal knowledge',
795
+ 'Update ownership docs'
796
+ ]
797
+ };
798
+ }
799
+ }
800
+
801
+ /**
802
+ * Assess resource timeline impact
803
+ * @param {string} action - Add or remove
804
+ * @param {number} count - Number of resources
805
+ */
806
+ assessResourceTimeline(action, count) {
807
+ if (action === 'add') {
808
+ return {
809
+ effect: 'Potential acceleration after ramp-up',
810
+ caution: 'Brooks Law: adding people to late projects makes them later',
811
+ netEffect: count > 2 ? 'Diminishing returns likely' : 'Moderate acceleration possible'
812
+ };
813
+ } else {
814
+ return {
815
+ effect: 'Timeline extension likely needed',
816
+ amount: `${count * 15}-${count * 25}% longer`,
817
+ alternatives: ['Reduce scope', 'Accept delays', 'Backfill']
818
+ };
819
+ }
820
+ }
821
+
822
+ /**
823
+ * Assess resource quality impact
824
+ * @param {string} action - Add or remove
825
+ * @param {number} count - Number of resources
826
+ */
827
+ assessResourceQuality(action, count) {
828
+ if (action === 'add') {
829
+ return {
830
+ shortTerm: 'May decrease (new contributors learning)',
831
+ longTerm: 'Can increase (more reviewers, diverse perspectives)',
832
+ risks: ['Inconsistent coding styles', 'Knowledge silos']
833
+ };
834
+ } else {
835
+ return {
836
+ shortTerm: 'May decrease (remaining team stretched)',
837
+ risks: ['Fewer reviewers', 'Burnout risk', 'Knowledge gaps']
838
+ };
839
+ }
840
+ }
841
+
842
+ /**
843
+ * Assess resource costs
844
+ * @param {string} action - Add or remove
845
+ * @param {number} count - Number of resources
846
+ */
847
+ assessResourceCosts(action, count) {
848
+ // Assuming $150k/year average fully loaded cost
849
+ const avgCost = 150000;
850
+ const monthlyImpact = Math.round((avgCost / 12) * count);
851
+
852
+ if (action === 'add') {
853
+ return {
854
+ monthly: `+$${monthlyImpact.toLocaleString()}`,
855
+ oneTime: `$${(count * 5000).toLocaleString()} (equipment, onboarding)`,
856
+ notes: 'Productivity lag during ramp-up'
857
+ };
858
+ } else {
859
+ return {
860
+ monthly: `-$${monthlyImpact.toLocaleString()}`,
861
+ oneTime: 'Potential severance costs',
862
+ notes: 'Hidden costs: knowledge loss, morale impact'
863
+ };
864
+ }
865
+ }
866
+
867
+ /**
868
+ * Simulate dependency change
869
+ * @param {Object} scenario - Dependency scenario
870
+ */
871
+ async simulateDependency(scenario) {
872
+ return {
873
+ compatibility: this.assessDependencyCompatibility(scenario),
874
+ security: this.assessDependencySecurity(scenario),
875
+ bundleSize: this.assessDependencyBundleSize(scenario),
876
+ maintenance: this.assessDependencyMaintenance(scenario)
877
+ };
878
+ }
879
+
880
+ /**
881
+ * Assess dependency compatibility
882
+ * @param {Object} scenario - Dependency scenario
883
+ */
884
+ assessDependencyCompatibility(scenario) {
885
+ return {
886
+ risk: scenario.action === 'add' ? 'medium' : 'low',
887
+ checks: [
888
+ 'Verify version compatibility with existing deps',
889
+ 'Check peer dependency requirements',
890
+ 'Test in staging environment'
891
+ ]
892
+ };
893
+ }
894
+
895
+ /**
896
+ * Assess dependency security
897
+ * @param {Object} scenario - Dependency scenario
898
+ */
899
+ assessDependencySecurity(scenario) {
900
+ return {
901
+ action: 'Run npm audit after change',
902
+ considerations: [
903
+ 'Check for known vulnerabilities',
904
+ 'Review package maintainer reputation',
905
+ 'Consider supply chain risks'
906
+ ]
907
+ };
908
+ }
909
+
910
+ /**
911
+ * Assess dependency bundle size
912
+ * @param {Object} scenario - Dependency scenario
913
+ */
914
+ assessDependencyBundleSize(scenario) {
915
+ if (scenario.action === 'add') {
916
+ return {
917
+ impact: 'Increased',
918
+ recommendations: [
919
+ 'Check bundle size with bundlephobia.com',
920
+ 'Consider tree-shaking support',
921
+ 'Evaluate alternatives'
922
+ ]
923
+ };
924
+ }
925
+ return {
926
+ impact: 'Decreased',
927
+ recommendations: ['Verify bundle reduction']
928
+ };
929
+ }
930
+
931
+ /**
932
+ * Assess dependency maintenance
933
+ * @param {Object} scenario - Dependency scenario
934
+ */
935
+ assessDependencyMaintenance(scenario) {
936
+ return {
937
+ ongoing: [
938
+ 'Monitor for security updates',
939
+ 'Track breaking changes in major versions',
940
+ 'Consider dependabot or similar'
941
+ ]
942
+ };
943
+ }
944
+
945
+ /**
946
+ * Simulate architecture change
947
+ * @param {Object} scenario - Architecture scenario
948
+ */
949
+ async simulateArchitecture(scenario) {
950
+ return {
951
+ complexity: this.assessArchitectureComplexity(scenario),
952
+ risk: this.assessArchitectureRisk(scenario),
953
+ timeline: this.estimateArchitectureTimeline(scenario),
954
+ benefits: this.identifyArchitectureBenefits(scenario)
955
+ };
956
+ }
957
+
958
+ /**
959
+ * Assess architecture complexity
960
+ * @param {Object} scenario - Architecture scenario
961
+ */
962
+ assessArchitectureComplexity(scenario) {
963
+ const components = scenario.components?.length || 1;
964
+ return {
965
+ level: components > 5 ? 'very_high' : components > 2 ? 'high' : 'medium',
966
+ factors: [
967
+ 'Cross-cutting concerns',
968
+ 'Data migration needs',
969
+ 'API compatibility',
970
+ 'Testing strategy changes'
971
+ ]
972
+ };
973
+ }
974
+
975
+ /**
976
+ * Assess architecture risk
977
+ * @param {Object} scenario - Architecture scenario
978
+ */
979
+ assessArchitectureRisk(scenario) {
980
+ return {
981
+ overall: 'high',
982
+ factors: [
983
+ {
984
+ type: 'scope',
985
+ severity: 'high',
986
+ description: 'Architecture changes affect many components'
987
+ },
988
+ {
989
+ type: 'knowledge',
990
+ severity: 'medium',
991
+ description: 'Team needs to learn new patterns'
992
+ },
993
+ {
994
+ type: 'rollback',
995
+ severity: 'high',
996
+ description: 'Difficult to revert once started'
997
+ }
998
+ ],
999
+ mitigation: [
1000
+ 'Incremental migration',
1001
+ 'Parallel running period',
1002
+ 'Feature flags for gradual rollout'
1003
+ ]
1004
+ };
1005
+ }
1006
+
1007
+ /**
1008
+ * Estimate architecture timeline
1009
+ * @param {Object} scenario - Architecture scenario
1010
+ */
1011
+ estimateArchitectureTimeline(scenario) {
1012
+ const components = scenario.components?.length || 1;
1013
+ const weeks = components * 2;
1014
+
1015
+ return {
1016
+ estimatedWeeks: weeks,
1017
+ phases: [
1018
+ { name: 'Design', weeks: Math.ceil(weeks * 0.2) },
1019
+ { name: 'Foundation', weeks: Math.ceil(weeks * 0.3) },
1020
+ { name: 'Migration', weeks: Math.ceil(weeks * 0.35) },
1021
+ { name: 'Validation', weeks: Math.ceil(weeks * 0.15) }
1022
+ ],
1023
+ parallelWork: 'Limited - changes are often sequential'
1024
+ };
1025
+ }
1026
+
1027
+ /**
1028
+ * Identify architecture benefits
1029
+ * @param {Object} scenario - Architecture scenario
1030
+ */
1031
+ identifyArchitectureBenefits(scenario) {
1032
+ return {
1033
+ shortTerm: [
1034
+ 'Cleaner code structure',
1035
+ 'Better separation of concerns'
1036
+ ],
1037
+ longTerm: [
1038
+ 'Easier to maintain',
1039
+ 'Better scalability',
1040
+ 'Improved testability',
1041
+ 'Faster feature development'
1042
+ ],
1043
+ breakEven: 'Typically 3-6 months post-migration'
1044
+ };
1045
+ }
1046
+
1047
+ /**
1048
+ * Simulate technology migration
1049
+ * @param {Object} scenario - Migration scenario
1050
+ */
1051
+ async simulateMigration(scenario) {
1052
+ return {
1053
+ effort: this.estimateMigrationEffort(scenario),
1054
+ risk: this.assessMigrationRisk(scenario),
1055
+ timeline: this.estimateMigrationTimeline(scenario),
1056
+ training: this.assessMigrationTraining(scenario)
1057
+ };
1058
+ }
1059
+
1060
+ /**
1061
+ * Estimate migration effort
1062
+ * @param {Object} scenario - Migration scenario
1063
+ */
1064
+ estimateMigrationEffort(scenario) {
1065
+ return {
1066
+ level: 'high',
1067
+ components: [
1068
+ 'Code migration',
1069
+ 'Configuration updates',
1070
+ 'Testing updates',
1071
+ 'Documentation updates',
1072
+ 'CI/CD pipeline changes'
1073
+ ],
1074
+ hiddenEffort: [
1075
+ 'Learning curve',
1076
+ 'Debugging new issues',
1077
+ 'Performance tuning'
1078
+ ]
1079
+ };
1080
+ }
1081
+
1082
+ /**
1083
+ * Assess migration risk
1084
+ * @param {Object} scenario - Migration scenario
1085
+ */
1086
+ assessMigrationRisk(scenario) {
1087
+ return {
1088
+ overall: 'high',
1089
+ factors: [
1090
+ 'Breaking changes likely',
1091
+ 'Knowledge gaps in new technology',
1092
+ 'Integration unknowns',
1093
+ 'Performance differences'
1094
+ ],
1095
+ mitigation: [
1096
+ 'Proof of concept first',
1097
+ 'Incremental migration',
1098
+ 'Comprehensive testing',
1099
+ 'Rollback plan'
1100
+ ]
1101
+ };
1102
+ }
1103
+
1104
+ /**
1105
+ * Estimate migration timeline
1106
+ * @param {Object} scenario - Migration scenario
1107
+ */
1108
+ estimateMigrationTimeline(scenario) {
1109
+ const scope = scenario.scope || 'full';
1110
+ const baseWeeks = scope === 'full' ? 8 : 4;
1111
+
1112
+ return {
1113
+ estimatedWeeks: baseWeeks,
1114
+ phases: [
1115
+ { name: 'Planning & POC', weeks: 2 },
1116
+ { name: 'Core Migration', weeks: Math.round(baseWeeks * 0.5) },
1117
+ { name: 'Testing & Fixes', weeks: Math.round(baseWeeks * 0.25) },
1118
+ { name: 'Deployment', weeks: 1 }
1119
+ ],
1120
+ parallel: scenario.parallel ? 'Run both systems during transition' : 'Big bang cutover'
1121
+ };
1122
+ }
1123
+
1124
+ /**
1125
+ * Assess migration training needs
1126
+ * @param {Object} scenario - Migration scenario
1127
+ */
1128
+ assessMigrationTraining(scenario) {
1129
+ return {
1130
+ needed: true,
1131
+ areas: [
1132
+ `${scenario.to} fundamentals`,
1133
+ 'New patterns and best practices',
1134
+ 'Tooling changes',
1135
+ 'Debugging techniques'
1136
+ ],
1137
+ estimatedHours: '20-40 hours per developer',
1138
+ resources: [
1139
+ 'Official documentation',
1140
+ 'Online courses',
1141
+ 'Pair programming sessions',
1142
+ 'Code reviews'
1143
+ ]
1144
+ };
1145
+ }
1146
+
1147
+ /**
1148
+ * Generate simulation summary
1149
+ * @param {Object} results - Simulation results
1150
+ */
1151
+ generateSummary(results) {
1152
+ const summaryPoints = [];
1153
+
1154
+ // Extract key insights from results
1155
+ for (const [key, value] of Object.entries(results)) {
1156
+ if (value.overall) {
1157
+ summaryPoints.push(`${key}: ${value.overall} impact`);
1158
+ } else if (value.estimatedDays) {
1159
+ summaryPoints.push(`Timeline: ${value.estimatedDays} days estimated`);
1160
+ } else if (value.level) {
1161
+ summaryPoints.push(`${key}: ${value.level}`);
1162
+ }
1163
+ }
1164
+
1165
+ return {
1166
+ keyPoints: summaryPoints,
1167
+ overallAssessment: this.determineOverallAssessment(results)
1168
+ };
1169
+ }
1170
+
1171
+ /**
1172
+ * Determine overall assessment
1173
+ * @param {Object} results - Simulation results
1174
+ */
1175
+ determineOverallAssessment(results) {
1176
+ // Count high-risk/high-impact items
1177
+ let highCount = 0;
1178
+ const checkValue = (val) => {
1179
+ if (typeof val === 'object' && val !== null) {
1180
+ if (val.severity === 'high' || val.level === 'high' || val.impact === 'high') {
1181
+ highCount++;
1182
+ }
1183
+ Object.values(val).forEach(checkValue);
1184
+ }
1185
+ };
1186
+
1187
+ Object.values(results).forEach(checkValue);
1188
+
1189
+ if (highCount >= 3) {
1190
+ return 'High risk - careful planning required';
1191
+ } else if (highCount >= 1) {
1192
+ return 'Moderate risk - manageable with proper planning';
1193
+ } else {
1194
+ return 'Low risk - straightforward to execute';
1195
+ }
1196
+ }
1197
+
1198
+ /**
1199
+ * Generate recommendations from simulation
1200
+ * @param {Object} scenario - Input scenario
1201
+ * @param {Object} results - Simulation results
1202
+ */
1203
+ generateRecommendations(scenario, results) {
1204
+ const recommendations = [];
1205
+
1206
+ // Add risk-based recommendations
1207
+ if (results.risks) {
1208
+ const highRisks = results.risks.filter(r => r.severity === 'high');
1209
+ if (highRisks.length > 0) {
1210
+ recommendations.push({
1211
+ priority: 'high',
1212
+ action: 'Address high-severity risks before proceeding',
1213
+ details: highRisks.map(r => r.mitigation)
1214
+ });
1215
+ }
1216
+ }
1217
+
1218
+ // Add testing recommendations
1219
+ if (results.testing) {
1220
+ recommendations.push({
1221
+ priority: 'medium',
1222
+ action: 'Establish testing strategy',
1223
+ details: results.testing.required?.map(t => t.type) || []
1224
+ });
1225
+ }
1226
+
1227
+ // Add documentation recommendations
1228
+ if (results.documentation) {
1229
+ recommendations.push({
1230
+ priority: 'medium',
1231
+ action: 'Update documentation',
1232
+ details: results.documentation.required?.map(d => d.type) || []
1233
+ });
1234
+ }
1235
+
1236
+ // Add scenario-specific recommendations
1237
+ if (scenario.type === 'feature') {
1238
+ recommendations.push({
1239
+ priority: 'low',
1240
+ action: 'Consider feature decomposition',
1241
+ command: `bootspring plan decompose "${scenario.featureName}"`
1242
+ });
1243
+ }
1244
+
1245
+ return recommendations.sort((a, b) => {
1246
+ const order = { high: 0, medium: 1, low: 2 };
1247
+ return order[a.priority] - order[b.priority];
1248
+ });
1249
+ }
1250
+
1251
+ /**
1252
+ * Compare multiple scenarios
1253
+ * @param {Array} scenarios - Array of scenarios to compare
1254
+ */
1255
+ async compare(scenarios) {
1256
+ const results = [];
1257
+
1258
+ for (const scenario of scenarios) {
1259
+ const result = await this.simulate(scenario);
1260
+ results.push({
1261
+ scenario: scenario,
1262
+ result: result
1263
+ });
1264
+ }
1265
+
1266
+ return {
1267
+ comparisons: results,
1268
+ recommendation: this.determinePreferredScenario(results),
1269
+ tradeoffs: this.identifyTradeoffs(results)
1270
+ };
1271
+ }
1272
+
1273
+ /**
1274
+ * Determine preferred scenario
1275
+ * @param {Array} results - Simulation results
1276
+ */
1277
+ determinePreferredScenario(results) {
1278
+ // Simple scoring based on overall assessment
1279
+ const scores = results.map((r, i) => {
1280
+ let score = 100;
1281
+ const assessment = r.result.summary?.overallAssessment || '';
1282
+
1283
+ if (assessment.includes('High risk')) score -= 40;
1284
+ else if (assessment.includes('Moderate risk')) score -= 20;
1285
+
1286
+ return { index: i, scenario: r.scenario, score };
1287
+ });
1288
+
1289
+ scores.sort((a, b) => b.score - a.score);
1290
+
1291
+ return {
1292
+ preferred: scores[0]?.scenario,
1293
+ ranking: scores.map(s => ({
1294
+ scenario: s.scenario.type,
1295
+ score: s.score
1296
+ }))
1297
+ };
1298
+ }
1299
+
1300
+ /**
1301
+ * Identify tradeoffs between scenarios
1302
+ * @param {Array} results - Simulation results
1303
+ */
1304
+ identifyTradeoffs(results) {
1305
+ if (results.length < 2) return [];
1306
+
1307
+ const tradeoffs = [];
1308
+
1309
+ // Compare timeline vs risk for each pair
1310
+ for (let i = 0; i < results.length; i++) {
1311
+ for (let j = i + 1; j < results.length; j++) {
1312
+ tradeoffs.push({
1313
+ scenarios: [results[i].scenario.type, results[j].scenario.type],
1314
+ factors: ['timeline', 'risk', 'effort']
1315
+ });
1316
+ }
1317
+ }
1318
+
1319
+ return tradeoffs;
1320
+ }
1321
+ }
1322
+
1323
+ module.exports = {
1324
+ PlanningSimulator,
1325
+ SCENARIO_TYPES,
1326
+ IMPACT_LEVELS,
1327
+ COMPLEXITY_MULTIPLIERS
1328
+ };