@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,624 @@
1
+ /**
2
+ * Bootspring Stage Planner
3
+ *
4
+ * Stage-aware planning for different project phases:
5
+ * - Discovery: New projects, pivots, major features
6
+ * - Definition: Ready to build, defining scope
7
+ * - Execution: During build, sprint planning
8
+ * - Iteration: Post-MVP, feedback incorporation
9
+ * - Scale: Growth phase, technical debt
10
+ *
11
+ * @package bootspring
12
+ * @module core/planning/stage-planner
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ /**
19
+ * Planning stages with their characteristics
20
+ */
21
+ const PLANNING_STAGES = {
22
+ discovery: {
23
+ name: 'Discovery',
24
+ description: 'Understanding the problem space and opportunity',
25
+ triggers: ['new project', 'pivot', 'major feature', 'exploration'],
26
+
27
+ documents: ['vision', 'audience', 'market', 'competitors'],
28
+
29
+ questions: [
30
+ 'What problem does this solve?',
31
+ 'Who experiences this problem?',
32
+ 'Why hasn\'t this been solved before?',
33
+ 'What makes now the right time?',
34
+ 'What is our unique advantage?',
35
+ 'How big is the opportunity?'
36
+ ],
37
+
38
+ activities: [
39
+ 'User research and interviews',
40
+ 'Market analysis',
41
+ 'Competitive landscape review',
42
+ 'Technical feasibility assessment',
43
+ 'Risk identification'
44
+ ],
45
+
46
+ outputs: {
47
+ vision: 'VISION.md - Core problem, solution, and success metrics',
48
+ audience: 'AUDIENCE.md - User personas and needs',
49
+ market: 'MARKET.md - Market size and opportunity',
50
+ competitors: 'COMPETITORS.md - Competitive analysis'
51
+ },
52
+
53
+ analysisDepth: 'shallow',
54
+ aiPromptStyle: 'exploratory',
55
+
56
+ completionCriteria: [
57
+ 'Problem clearly defined',
58
+ 'Target audience identified',
59
+ 'Market opportunity validated',
60
+ 'Technical feasibility confirmed'
61
+ ]
62
+ },
63
+
64
+ definition: {
65
+ name: 'Definition',
66
+ description: 'Defining what to build and how',
67
+ triggers: ['vision approved', 'ready to build', 'scope definition'],
68
+
69
+ documents: ['prd', 'technical-spec', 'business-model', 'roadmap'],
70
+
71
+ questions: [
72
+ 'What is the minimum viable feature set?',
73
+ 'What technology stack fits best?',
74
+ 'How will this make money?',
75
+ 'What are the key milestones?',
76
+ 'What are the critical risks?',
77
+ 'What resources are needed?'
78
+ ],
79
+
80
+ activities: [
81
+ 'Feature prioritization',
82
+ 'Architecture design',
83
+ 'Tech stack selection',
84
+ 'Business model development',
85
+ 'Roadmap creation',
86
+ 'Resource planning'
87
+ ],
88
+
89
+ outputs: {
90
+ prd: 'PRD.md - Product requirements and user stories',
91
+ technicalSpec: 'TECHNICAL_SPEC.md - Architecture and tech decisions',
92
+ businessModel: 'BUSINESS_MODEL.md - Revenue and growth strategy',
93
+ roadmap: 'ROADMAP.md - Milestones and timeline'
94
+ },
95
+
96
+ analysisDepth: 'deep',
97
+ aiPromptStyle: 'decisive',
98
+
99
+ completionCriteria: [
100
+ 'MVP scope defined',
101
+ 'Architecture documented',
102
+ 'Tech stack chosen',
103
+ 'Roadmap created'
104
+ ]
105
+ },
106
+
107
+ execution: {
108
+ name: 'Execution',
109
+ description: 'Building the product',
110
+ triggers: ['development started', 'sprint planning', 'active build'],
111
+
112
+ documents: ['sprint', 'tasks', 'blockers', 'decisions'],
113
+
114
+ questions: [
115
+ 'What should be built this sprint?',
116
+ 'What are the blockers?',
117
+ 'What is the critical path?',
118
+ 'Are we on track for milestones?',
119
+ 'What decisions need to be made?',
120
+ 'What has changed since last sprint?'
121
+ ],
122
+
123
+ activities: [
124
+ 'Sprint planning',
125
+ 'Daily standups',
126
+ 'Code reviews',
127
+ 'Technical decisions',
128
+ 'Blocker resolution',
129
+ 'Progress tracking'
130
+ ],
131
+
132
+ outputs: {
133
+ sprint: 'SPRINT.md - Current sprint goals and tasks',
134
+ tasks: 'TASKS.md - Detailed task breakdown',
135
+ blockers: 'BLOCKERS.md - Current blockers and resolutions',
136
+ decisions: 'DECISIONS.md - Architectural decision records'
137
+ },
138
+
139
+ analysisDepth: 'focused',
140
+ aiPromptStyle: 'tactical',
141
+
142
+ completionCriteria: [
143
+ 'Sprint goals defined',
144
+ 'Tasks estimated',
145
+ 'Dependencies mapped',
146
+ 'Blockers identified'
147
+ ]
148
+ },
149
+
150
+ iteration: {
151
+ name: 'Iteration',
152
+ description: 'Post-MVP refinement based on feedback',
153
+ triggers: ['mvp launched', 'feedback received', 'metrics review'],
154
+
155
+ documents: ['feedback', 'iteration', 'metrics', 'learnings'],
156
+
157
+ questions: [
158
+ 'What feedback are we getting?',
159
+ 'What metrics are we seeing?',
160
+ 'What needs to change?',
161
+ 'What\'s working well?',
162
+ 'What should we double down on?',
163
+ 'What should we cut?'
164
+ ],
165
+
166
+ activities: [
167
+ 'User feedback analysis',
168
+ 'Metrics review',
169
+ 'Feature prioritization update',
170
+ 'Quick wins identification',
171
+ 'Technical debt assessment',
172
+ 'Growth experiments'
173
+ ],
174
+
175
+ outputs: {
176
+ feedback: 'FEEDBACK.md - User feedback summary',
177
+ iteration: 'ITERATION.md - Planned changes',
178
+ metrics: 'METRICS.md - Key performance indicators',
179
+ learnings: 'LEARNINGS.md - What we\'ve learned'
180
+ },
181
+
182
+ analysisDepth: 'moderate',
183
+ aiPromptStyle: 'analytical',
184
+
185
+ completionCriteria: [
186
+ 'Feedback analyzed',
187
+ 'Metrics baseline established',
188
+ 'Iteration priorities set',
189
+ 'Success criteria updated'
190
+ ]
191
+ },
192
+
193
+ scale: {
194
+ name: 'Scale',
195
+ description: 'Growth phase optimization',
196
+ triggers: ['growth phase', 'scaling issues', 'team growth'],
197
+
198
+ documents: ['scale', 'tech-debt', 'team', 'infrastructure'],
199
+
200
+ questions: [
201
+ 'What\'s breaking under load?',
202
+ 'What technical debt exists?',
203
+ 'What team changes are needed?',
204
+ 'What infrastructure updates are required?',
205
+ 'What processes need improvement?',
206
+ 'What are the next growth bottlenecks?'
207
+ ],
208
+
209
+ activities: [
210
+ 'Performance optimization',
211
+ 'Technical debt cleanup',
212
+ 'Team scaling',
213
+ 'Infrastructure planning',
214
+ 'Process improvement',
215
+ 'Documentation update'
216
+ ],
217
+
218
+ outputs: {
219
+ scale: 'SCALE.md - Scaling strategy',
220
+ techDebt: 'TECH_DEBT.md - Technical debt backlog',
221
+ team: 'TEAM.md - Team structure and needs',
222
+ infrastructure: 'INFRASTRUCTURE.md - Infrastructure plan'
223
+ },
224
+
225
+ analysisDepth: 'comprehensive',
226
+ aiPromptStyle: 'strategic',
227
+
228
+ completionCriteria: [
229
+ 'Scaling bottlenecks identified',
230
+ 'Technical debt cataloged',
231
+ 'Team plan created',
232
+ 'Infrastructure roadmap defined'
233
+ ]
234
+ }
235
+ };
236
+
237
+ /**
238
+ * Stage Planner class
239
+ */
240
+ class StagePlanner {
241
+ constructor(projectRoot) {
242
+ this.projectRoot = projectRoot;
243
+ this.stateFile = path.join(projectRoot, '.bootspring', 'planning-state.json');
244
+ }
245
+
246
+ /**
247
+ * Detect current planning stage
248
+ */
249
+ async detectStage() {
250
+ const signals = await this.gatherStageSignals();
251
+
252
+ // Score each stage based on signals
253
+ const scores = {};
254
+ for (const [stageName, stage] of Object.entries(PLANNING_STAGES)) {
255
+ scores[stageName] = this.scoreStage(stageName, stage, signals);
256
+ }
257
+
258
+ // Find highest scoring stage
259
+ let bestStage = 'discovery';
260
+ let bestScore = 0;
261
+ for (const [stage, score] of Object.entries(scores)) {
262
+ if (score > bestScore) {
263
+ bestScore = score;
264
+ bestStage = stage;
265
+ }
266
+ }
267
+
268
+ return {
269
+ detectedStage: bestStage,
270
+ confidence: Math.min(bestScore / 10, 1),
271
+ signals,
272
+ scores,
273
+ recommendation: this.getStageRecommendation(bestStage, signals)
274
+ };
275
+ }
276
+
277
+ /**
278
+ * Gather signals about project state
279
+ */
280
+ async gatherStageSignals() {
281
+ const signals = {
282
+ hasVision: false,
283
+ hasPrd: false,
284
+ hasCode: false,
285
+ hasTests: false,
286
+ hasDeployment: false,
287
+ hasUsers: false,
288
+ hasMetrics: false,
289
+ codebaseSize: 'none',
290
+ gitHistory: 'none',
291
+ recentActivity: 'low'
292
+ };
293
+
294
+ try {
295
+ // Check for preseed documents
296
+ const preseedDir = path.join(this.projectRoot, '.bootspring', 'preseed');
297
+ if (fs.existsSync(preseedDir)) {
298
+ signals.hasVision = fs.existsSync(path.join(preseedDir, 'VISION.md'));
299
+ signals.hasPrd = fs.existsSync(path.join(preseedDir, 'PRD.md'));
300
+ }
301
+
302
+ // Check for code
303
+ const srcPaths = ['src', 'app', 'pages', 'lib'];
304
+ for (const srcPath of srcPaths) {
305
+ if (fs.existsSync(path.join(this.projectRoot, srcPath))) {
306
+ signals.hasCode = true;
307
+ break;
308
+ }
309
+ }
310
+
311
+ // Check for tests
312
+ const testPaths = ['__tests__', 'tests', 'test', 'spec'];
313
+ for (const testPath of testPaths) {
314
+ if (fs.existsSync(path.join(this.projectRoot, testPath))) {
315
+ signals.hasTests = true;
316
+ break;
317
+ }
318
+ }
319
+
320
+ // Check for deployment config
321
+ const deployPaths = ['vercel.json', 'fly.toml', 'railway.json', 'Dockerfile', '.github/workflows'];
322
+ for (const deployPath of deployPaths) {
323
+ if (fs.existsSync(path.join(this.projectRoot, deployPath))) {
324
+ signals.hasDeployment = true;
325
+ break;
326
+ }
327
+ }
328
+
329
+ // Estimate codebase size
330
+ const packageJson = path.join(this.projectRoot, 'package.json');
331
+ if (fs.existsSync(packageJson)) {
332
+ const pkg = JSON.parse(fs.readFileSync(packageJson, 'utf8'));
333
+ const depCount = Object.keys(pkg.dependencies || {}).length;
334
+ if (depCount > 20) signals.codebaseSize = 'large';
335
+ else if (depCount > 10) signals.codebaseSize = 'medium';
336
+ else if (depCount > 0) signals.codebaseSize = 'small';
337
+ }
338
+
339
+ // Check git history
340
+ const gitDir = path.join(this.projectRoot, '.git');
341
+ if (fs.existsSync(gitDir)) {
342
+ signals.gitHistory = 'exists';
343
+ }
344
+
345
+ } catch (_err) {
346
+ // Signal gathering failed, use defaults
347
+ }
348
+
349
+ return signals;
350
+ }
351
+
352
+ /**
353
+ * Score a stage based on signals
354
+ */
355
+ scoreStage(stageName, stage, signals) {
356
+ let score = 0;
357
+
358
+ switch (stageName) {
359
+ case 'discovery':
360
+ if (!signals.hasVision) score += 3;
361
+ if (!signals.hasPrd) score += 2;
362
+ if (!signals.hasCode || signals.codebaseSize === 'small') score += 2;
363
+ break;
364
+
365
+ case 'definition':
366
+ if (signals.hasVision && !signals.hasPrd) score += 4;
367
+ if (!signals.hasCode) score += 2;
368
+ if (signals.codebaseSize === 'small') score += 1;
369
+ break;
370
+
371
+ case 'execution':
372
+ if (signals.hasPrd && signals.hasCode) score += 4;
373
+ if (signals.codebaseSize === 'medium') score += 2;
374
+ if (signals.recentActivity === 'high') score += 2;
375
+ break;
376
+
377
+ case 'iteration':
378
+ if (signals.hasDeployment) score += 3;
379
+ if (signals.hasUsers) score += 3;
380
+ if (signals.codebaseSize === 'medium' || signals.codebaseSize === 'large') score += 2;
381
+ break;
382
+
383
+ case 'scale':
384
+ if (signals.hasUsers && signals.hasMetrics) score += 4;
385
+ if (signals.codebaseSize === 'large') score += 3;
386
+ if (signals.hasDeployment) score += 2;
387
+ break;
388
+ }
389
+
390
+ return score;
391
+ }
392
+
393
+ /**
394
+ * Get recommendation for a stage
395
+ */
396
+ getStageRecommendation(stageName, signals) {
397
+ const stage = PLANNING_STAGES[stageName];
398
+
399
+ const missingDocs = [];
400
+ const preseedDir = path.join(this.projectRoot, '.bootspring', 'preseed');
401
+
402
+ for (const doc of stage.documents) {
403
+ const docPath = path.join(preseedDir, `${doc.toUpperCase()}.md`);
404
+ if (!fs.existsSync(docPath)) {
405
+ missingDocs.push(doc);
406
+ }
407
+ }
408
+
409
+ return {
410
+ stage: stageName,
411
+ name: stage.name,
412
+ description: stage.description,
413
+ keyQuestions: stage.questions.slice(0, 3),
414
+ suggestedActivities: stage.activities.slice(0, 3),
415
+ missingDocuments: missingDocs,
416
+ nextSteps: this.getNextSteps(stageName, signals, missingDocs)
417
+ };
418
+ }
419
+
420
+ /**
421
+ * Get next steps for a stage
422
+ */
423
+ getNextSteps(stageName, signals, missingDocs) {
424
+ const steps = [];
425
+
426
+ if (missingDocs.length > 0) {
427
+ steps.push(`Generate missing documents: ${missingDocs.join(', ')}`);
428
+ steps.push('Run: bootspring preseed from-codebase --deep');
429
+ }
430
+
431
+ switch (stageName) {
432
+ case 'discovery':
433
+ steps.push('Answer the discovery questions');
434
+ steps.push('Validate problem with potential users');
435
+ break;
436
+
437
+ case 'definition':
438
+ steps.push('Define MVP feature set');
439
+ steps.push('Choose technology stack');
440
+ steps.push('Create initial roadmap');
441
+ break;
442
+
443
+ case 'execution':
444
+ steps.push('Set up sprint board');
445
+ steps.push('Break down features into tasks');
446
+ steps.push('Identify blockers early');
447
+ break;
448
+
449
+ case 'iteration':
450
+ steps.push('Set up user feedback collection');
451
+ steps.push('Define key metrics');
452
+ steps.push('Plan A/B tests');
453
+ break;
454
+
455
+ case 'scale':
456
+ steps.push('Audit performance bottlenecks');
457
+ steps.push('Catalog technical debt');
458
+ steps.push('Plan infrastructure upgrades');
459
+ break;
460
+ }
461
+
462
+ return steps;
463
+ }
464
+
465
+ /**
466
+ * Get planning context for a stage
467
+ */
468
+ async getStageContext(stageName) {
469
+ const stage = PLANNING_STAGES[stageName];
470
+
471
+ if (!stage) {
472
+ throw new Error(`Unknown stage: ${stageName}`);
473
+ }
474
+
475
+ return {
476
+ stage: stageName,
477
+ ...stage,
478
+ currentState: await this.gatherStageSignals(),
479
+ existingDocuments: await this.findExistingDocuments(stage.documents)
480
+ };
481
+ }
482
+
483
+ /**
484
+ * Find existing documents for a stage
485
+ */
486
+ async findExistingDocuments(documentTypes) {
487
+ const existing = {};
488
+ const preseedDir = path.join(this.projectRoot, '.bootspring', 'preseed');
489
+
490
+ for (const docType of documentTypes) {
491
+ const docPath = path.join(preseedDir, `${docType.toUpperCase()}.md`);
492
+ if (fs.existsSync(docPath)) {
493
+ existing[docType] = {
494
+ path: docPath,
495
+ exists: true,
496
+ lastModified: fs.statSync(docPath).mtime
497
+ };
498
+ } else {
499
+ existing[docType] = {
500
+ path: docPath,
501
+ exists: false
502
+ };
503
+ }
504
+ }
505
+
506
+ return existing;
507
+ }
508
+
509
+ /**
510
+ * Generate stage-appropriate AI prompt
511
+ */
512
+ generateAIPrompt(stageName, task) {
513
+ const stage = PLANNING_STAGES[stageName];
514
+
515
+ if (!stage) {
516
+ return this.generateGenericPrompt(task);
517
+ }
518
+
519
+ const promptStyles = {
520
+ exploratory: `You are helping with early-stage exploration. Be curious, ask questions, and help discover insights. Focus on understanding the problem space before jumping to solutions.`,
521
+
522
+ decisive: `You are helping make key decisions. Be structured, evaluate trade-offs clearly, and provide concrete recommendations. Help choose between options with clear rationale.`,
523
+
524
+ tactical: `You are helping with day-to-day execution. Be practical, focus on immediate next steps, and help remove blockers. Keep suggestions actionable and time-bound.`,
525
+
526
+ analytical: `You are helping analyze feedback and data. Be objective, look for patterns, and help prioritize based on evidence. Separate signal from noise.`,
527
+
528
+ strategic: `You are helping with long-term planning. Think about scalability, sustainability, and team growth. Balance quick wins with foundational investments.`
529
+ };
530
+
531
+ const stylePrompt = promptStyles[stage.aiPromptStyle] || promptStyles.tactical;
532
+
533
+ return `${stylePrompt}
534
+
535
+ Current project stage: ${stage.name}
536
+ Stage description: ${stage.description}
537
+
538
+ Key questions for this stage:
539
+ ${stage.questions.map(q => `- ${q}`).join('\n')}
540
+
541
+ Task: ${task}
542
+
543
+ Consider the stage-appropriate depth (${stage.analysisDepth}) and focus on outputs relevant to this stage.`;
544
+ }
545
+
546
+ /**
547
+ * Generate generic prompt
548
+ */
549
+ generateGenericPrompt(task) {
550
+ return `Help with the following task: ${task}`;
551
+ }
552
+
553
+ /**
554
+ * Save planning state
555
+ */
556
+ async saveState(state) {
557
+ const dir = path.dirname(this.stateFile);
558
+ if (!fs.existsSync(dir)) {
559
+ fs.mkdirSync(dir, { recursive: true });
560
+ }
561
+
562
+ fs.writeFileSync(this.stateFile, JSON.stringify(state, null, 2));
563
+ }
564
+
565
+ /**
566
+ * Load planning state
567
+ */
568
+ loadState() {
569
+ if (fs.existsSync(this.stateFile)) {
570
+ return JSON.parse(fs.readFileSync(this.stateFile, 'utf8'));
571
+ }
572
+
573
+ return {
574
+ currentStage: null,
575
+ stageHistory: [],
576
+ lastUpdated: null
577
+ };
578
+ }
579
+
580
+ /**
581
+ * Transition to a new stage
582
+ */
583
+ async transitionTo(newStage, reason) {
584
+ const state = this.loadState();
585
+
586
+ if (state.currentStage) {
587
+ state.stageHistory.push({
588
+ stage: state.currentStage,
589
+ exitedAt: new Date().toISOString(),
590
+ reason
591
+ });
592
+ }
593
+
594
+ state.currentStage = newStage;
595
+ state.lastUpdated = new Date().toISOString();
596
+
597
+ await this.saveState(state);
598
+
599
+ return {
600
+ previousStage: state.stageHistory[state.stageHistory.length - 1]?.stage,
601
+ currentStage: newStage,
602
+ stageInfo: PLANNING_STAGES[newStage],
603
+ nextSteps: this.getNextSteps(newStage, await this.gatherStageSignals(), [])
604
+ };
605
+ }
606
+
607
+ /**
608
+ * Get all stages info
609
+ */
610
+ getAllStages() {
611
+ return Object.entries(PLANNING_STAGES).map(([key, stage]) => ({
612
+ id: key,
613
+ name: stage.name,
614
+ description: stage.description,
615
+ documents: stage.documents,
616
+ triggers: stage.triggers
617
+ }));
618
+ }
619
+ }
620
+
621
+ module.exports = {
622
+ StagePlanner,
623
+ PLANNING_STAGES
624
+ };