@girardmedia/bootspring 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +9 -403
  2. package/bin/bootspring.js +1 -96
  3. package/dist/cli/index.js +65134 -0
  4. package/dist/cli-launcher.js +92 -0
  5. package/dist/core/index.d.ts +2110 -5582
  6. package/dist/core/index.js +2 -0
  7. package/dist/core.js +21123 -5413
  8. package/dist/mcp/index.d.ts +357 -1
  9. package/dist/mcp/index.js +2 -0
  10. package/dist/mcp-server.js +51948 -1976
  11. package/package.json +27 -63
  12. package/scripts/postinstall.cjs +144 -0
  13. package/LICENSE +0 -29
  14. package/dist/cli/index.cjs +0 -20776
  15. package/generators/api-docs.js +0 -827
  16. package/generators/decisions.js +0 -655
  17. package/generators/generate.js +0 -595
  18. package/generators/health.js +0 -942
  19. package/generators/index.ts +0 -82
  20. package/generators/presets/full.js +0 -28
  21. package/generators/presets/index.js +0 -12
  22. package/generators/presets/minimal.js +0 -29
  23. package/generators/presets/standard.js +0 -28
  24. package/generators/questionnaire.js +0 -414
  25. package/generators/sections/advanced.js +0 -136
  26. package/generators/sections/ai.js +0 -106
  27. package/generators/sections/auth.js +0 -89
  28. package/generators/sections/backend.js +0 -146
  29. package/generators/sections/business.js +0 -118
  30. package/generators/sections/content.js +0 -300
  31. package/generators/sections/deployment.js +0 -139
  32. package/generators/sections/features.js +0 -122
  33. package/generators/sections/frontend.js +0 -118
  34. package/generators/sections/identity.js +0 -76
  35. package/generators/sections/index.js +0 -40
  36. package/generators/sections/instructions.js +0 -146
  37. package/generators/sections/payments.js +0 -104
  38. package/generators/sections/plugins.js +0 -142
  39. package/generators/sections/pre-build.js +0 -130
  40. package/generators/sections/security.js +0 -127
  41. package/generators/sections/technical.js +0 -171
  42. package/generators/sections/testing.js +0 -125
  43. package/generators/sections/workflow.js +0 -104
  44. package/generators/sprint.js +0 -675
  45. package/generators/templates/agents.template.js +0 -199
  46. package/generators/templates/assistant-context.template.js +0 -83
  47. package/generators/templates/build-planning.template.js +0 -708
  48. package/generators/templates/claude.template.js +0 -379
  49. package/generators/templates/content.template.js +0 -819
  50. package/generators/templates/index.js +0 -16
  51. package/generators/templates/planning.template.js +0 -515
  52. package/generators/templates/seed.template.js +0 -109
  53. package/generators/visual-doc-generator.js +0 -910
  54. package/scripts/postinstall.js +0 -197
  55. /package/{claude-commands → assets/claude-commands}/agent.md +0 -0
  56. /package/{claude-commands → assets/claude-commands}/bs.md +0 -0
  57. /package/{claude-commands → assets/claude-commands}/build.md +0 -0
  58. /package/{claude-commands → assets/claude-commands}/skill.md +0 -0
  59. /package/{claude-commands → assets/claude-commands}/todo.md +0 -0
@@ -1,675 +0,0 @@
1
- /**
2
- * Bootspring SPRINT.md Generator
3
- *
4
- * Generates sprint documentation with goals, tasks, blockers,
5
- * dependencies, progress tracking, and burndown metrics.
6
- *
7
- * @package bootspring
8
- * @module generators/sprint
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
-
14
- /**
15
- * Sprint status values
16
- */
17
- const SPRINT_STATUS = {
18
- PLANNING: 'planning',
19
- ACTIVE: 'active',
20
- REVIEW: 'review',
21
- COMPLETED: 'completed'
22
- };
23
-
24
- /**
25
- * Task status values
26
- */
27
- const TASK_STATUS = {
28
- TODO: 'todo',
29
- IN_PROGRESS: 'in_progress',
30
- BLOCKED: 'blocked',
31
- IN_REVIEW: 'in_review',
32
- DONE: 'done'
33
- };
34
-
35
- /**
36
- * Priority levels
37
- */
38
- const PRIORITY = {
39
- CRITICAL: { value: 1, label: 'Critical', emoji: '🔴' },
40
- HIGH: { value: 2, label: 'High', emoji: '🟠' },
41
- MEDIUM: { value: 3, label: 'Medium', emoji: '🟡' },
42
- LOW: { value: 4, label: 'Low', emoji: '🟢' }
43
- };
44
-
45
- /**
46
- * Default sprint configuration
47
- */
48
- const DEFAULT_CONFIG = {
49
- sprintDuration: 14, // days
50
- workingHoursPerDay: 8,
51
- velocityBuffer: 0.8, // 80% of capacity
52
- includeWeekends: false
53
- };
54
-
55
- /**
56
- * Sprint Document Generator
57
- */
58
- class SprintGenerator {
59
- constructor(options = {}) {
60
- this.projectRoot = options.projectRoot || process.cwd();
61
- this.config = { ...DEFAULT_CONFIG, ...options };
62
- this.sprintDataPath = path.join(this.projectRoot, '.bootspring', 'sprint.json');
63
- }
64
-
65
- /**
66
- * Load current sprint data
67
- */
68
- loadSprintData() {
69
- try {
70
- if (fs.existsSync(this.sprintDataPath)) {
71
- return JSON.parse(fs.readFileSync(this.sprintDataPath, 'utf-8'));
72
- }
73
- } catch (_err) {
74
- // Return default
75
- }
76
-
77
- return this.createDefaultSprint();
78
- }
79
-
80
- /**
81
- * Save sprint data
82
- */
83
- saveSprintData(data) {
84
- const dir = path.dirname(this.sprintDataPath);
85
- if (!fs.existsSync(dir)) {
86
- fs.mkdirSync(dir, { recursive: true });
87
- }
88
- fs.writeFileSync(this.sprintDataPath, JSON.stringify(data, null, 2));
89
- }
90
-
91
- /**
92
- * Create default sprint structure
93
- */
94
- createDefaultSprint() {
95
- const now = new Date();
96
- const endDate = new Date(now);
97
- endDate.setDate(endDate.getDate() + this.config.sprintDuration);
98
-
99
- return {
100
- id: `sprint-${now.getTime()}`,
101
- name: `Sprint ${this.getSprintNumber()}`,
102
- status: SPRINT_STATUS.PLANNING,
103
- startDate: now.toISOString().split('T')[0],
104
- endDate: endDate.toISOString().split('T')[0],
105
- goals: [],
106
- tasks: [],
107
- blockers: [],
108
- metrics: {
109
- totalPoints: 0,
110
- completedPoints: 0,
111
- velocity: 0
112
- },
113
- dailyProgress: [],
114
- team: [],
115
- notes: ''
116
- };
117
- }
118
-
119
- /**
120
- * Get sprint number based on history
121
- */
122
- getSprintNumber() {
123
- const historyPath = path.join(this.projectRoot, '.bootspring', 'sprint-history.json');
124
- try {
125
- if (fs.existsSync(historyPath)) {
126
- const history = JSON.parse(fs.readFileSync(historyPath, 'utf-8'));
127
- return (history.sprints?.length || 0) + 1;
128
- }
129
- } catch (_err) {
130
- // Return default
131
- }
132
- return 1;
133
- }
134
-
135
- /**
136
- * Add a goal to the sprint
137
- */
138
- addGoal(sprint, goal) {
139
- sprint.goals.push({
140
- id: `goal-${Date.now()}`,
141
- description: goal.description,
142
- success_criteria: goal.success_criteria || [],
143
- status: 'pending',
144
- created: new Date().toISOString()
145
- });
146
- return sprint;
147
- }
148
-
149
- /**
150
- * Add a task to the sprint
151
- */
152
- addTask(sprint, task) {
153
- sprint.tasks.push({
154
- id: `task-${Date.now()}`,
155
- title: task.title,
156
- description: task.description || '',
157
- assignee: task.assignee || null,
158
- estimate: task.estimate || 0, // story points or hours
159
- status: TASK_STATUS.TODO,
160
- priority: task.priority || 'medium',
161
- dependencies: task.dependencies || [],
162
- blockedBy: task.blockedBy || [],
163
- labels: task.labels || [],
164
- created: new Date().toISOString(),
165
- started: null,
166
- completed: null
167
- });
168
-
169
- // Update metrics
170
- sprint.metrics.totalPoints += task.estimate || 0;
171
-
172
- return sprint;
173
- }
174
-
175
- /**
176
- * Add a blocker
177
- */
178
- addBlocker(sprint, blocker) {
179
- sprint.blockers.push({
180
- id: `blocker-${Date.now()}`,
181
- description: blocker.description,
182
- affectedTasks: blocker.affectedTasks || [],
183
- severity: blocker.severity || 'medium',
184
- status: 'open',
185
- owner: blocker.owner || null,
186
- created: new Date().toISOString(),
187
- resolved: null
188
- });
189
- return sprint;
190
- }
191
-
192
- /**
193
- * Update task status
194
- */
195
- updateTaskStatus(sprint, taskId, newStatus) {
196
- const task = sprint.tasks.find(t => t.id === taskId);
197
- if (task) {
198
- const oldStatus = task.status;
199
- task.status = newStatus;
200
-
201
- if (newStatus === TASK_STATUS.IN_PROGRESS && !task.started) {
202
- task.started = new Date().toISOString();
203
- }
204
-
205
- if (newStatus === TASK_STATUS.DONE && !task.completed) {
206
- task.completed = new Date().toISOString();
207
- sprint.metrics.completedPoints += task.estimate || 0;
208
- }
209
-
210
- // If reverting from done, adjust points
211
- if (oldStatus === TASK_STATUS.DONE && newStatus !== TASK_STATUS.DONE) {
212
- sprint.metrics.completedPoints -= task.estimate || 0;
213
- }
214
- }
215
- return sprint;
216
- }
217
-
218
- /**
219
- * Record daily progress
220
- */
221
- recordDailyProgress(sprint) {
222
- const today = new Date().toISOString().split('T')[0];
223
-
224
- // Check if already recorded today
225
- const existingIndex = sprint.dailyProgress.findIndex(p => p.date === today);
226
-
227
- const progress = {
228
- date: today,
229
- completedPoints: sprint.metrics.completedPoints,
230
- remainingPoints: sprint.metrics.totalPoints - sprint.metrics.completedPoints,
231
- tasksDone: sprint.tasks.filter(t => t.status === TASK_STATUS.DONE).length,
232
- tasksInProgress: sprint.tasks.filter(t => t.status === TASK_STATUS.IN_PROGRESS).length,
233
- blockerCount: sprint.blockers.filter(b => b.status === 'open').length
234
- };
235
-
236
- if (existingIndex >= 0) {
237
- sprint.dailyProgress[existingIndex] = progress;
238
- } else {
239
- sprint.dailyProgress.push(progress);
240
- }
241
-
242
- return sprint;
243
- }
244
-
245
- /**
246
- * Calculate burndown data
247
- */
248
- calculateBurndown(sprint) {
249
- const startDate = new Date(sprint.startDate);
250
- const endDate = new Date(sprint.endDate);
251
- const totalDays = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));
252
- const idealPointsPerDay = sprint.metrics.totalPoints / totalDays;
253
-
254
- const burndown = [];
255
- const currentDate = new Date(startDate);
256
- let idealRemaining = sprint.metrics.totalPoints;
257
-
258
- while (currentDate <= endDate) {
259
- const dateStr = currentDate.toISOString().split('T')[0];
260
- const actualProgress = sprint.dailyProgress.find(p => p.date === dateStr);
261
-
262
- burndown.push({
263
- date: dateStr,
264
- ideal: Math.max(0, Math.round(idealRemaining * 10) / 10),
265
- actual: actualProgress ? actualProgress.remainingPoints : null
266
- });
267
-
268
- idealRemaining -= idealPointsPerDay;
269
- currentDate.setDate(currentDate.getDate() + 1);
270
- }
271
-
272
- return burndown;
273
- }
274
-
275
- /**
276
- * Calculate sprint metrics
277
- */
278
- calculateMetrics(sprint) {
279
- const tasks = sprint.tasks;
280
- const now = new Date();
281
- const startDate = new Date(sprint.startDate);
282
- const endDate = new Date(sprint.endDate);
283
-
284
- const totalDays = Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24));
285
- const elapsedDays = Math.ceil((now - startDate) / (1000 * 60 * 60 * 24));
286
- const remainingDays = Math.max(0, totalDays - elapsedDays);
287
-
288
- const completedTasks = tasks.filter(t => t.status === TASK_STATUS.DONE);
289
- const inProgressTasks = tasks.filter(t => t.status === TASK_STATUS.IN_PROGRESS);
290
- const blockedTasks = tasks.filter(t => t.status === TASK_STATUS.BLOCKED);
291
- const todoTasks = tasks.filter(t => t.status === TASK_STATUS.TODO);
292
-
293
- const completedPoints = completedTasks.reduce((sum, t) => sum + (t.estimate || 0), 0);
294
- const totalPoints = tasks.reduce((sum, t) => sum + (t.estimate || 0), 0);
295
- const remainingPoints = totalPoints - completedPoints;
296
-
297
- const percentComplete = totalPoints > 0 ? Math.round((completedPoints / totalPoints) * 100) : 0;
298
- const velocity = elapsedDays > 0 ? Math.round((completedPoints / elapsedDays) * 10) / 10 : 0;
299
- const projectedCompletion = velocity > 0 ? Math.ceil(remainingPoints / velocity) : null;
300
-
301
- return {
302
- totalTasks: tasks.length,
303
- completedTasks: completedTasks.length,
304
- inProgressTasks: inProgressTasks.length,
305
- blockedTasks: blockedTasks.length,
306
- todoTasks: todoTasks.length,
307
- totalPoints,
308
- completedPoints,
309
- remainingPoints,
310
- percentComplete,
311
- velocity,
312
- elapsedDays,
313
- remainingDays,
314
- projectedCompletion,
315
- onTrack: projectedCompletion !== null && projectedCompletion <= remainingDays,
316
- openBlockers: sprint.blockers.filter(b => b.status === 'open').length
317
- };
318
- }
319
-
320
- /**
321
- * Generate SPRINT.md content
322
- */
323
- generate(sprint = null) {
324
- if (!sprint) {
325
- sprint = this.loadSprintData();
326
- }
327
-
328
- const metrics = this.calculateMetrics(sprint);
329
- const burndown = this.calculateBurndown(sprint);
330
- const now = new Date().toISOString().split('T')[0];
331
-
332
- const sections = [];
333
-
334
- // Header
335
- sections.push(`# ${sprint.name}
336
-
337
- **Status:** ${this.formatStatus(sprint.status)}
338
- **Period:** ${sprint.startDate} → ${sprint.endDate}
339
- **Last Updated:** ${now}
340
-
341
- ---`);
342
-
343
- // Progress Overview
344
- const progressBar = this.generateProgressBar(metrics.percentComplete);
345
- sections.push(`## Progress Overview
346
-
347
- ${progressBar} **${metrics.percentComplete}%** Complete
348
-
349
- | Metric | Value |
350
- |--------|-------|
351
- | Total Tasks | ${metrics.totalTasks} |
352
- | Completed | ${metrics.completedTasks} |
353
- | In Progress | ${metrics.inProgressTasks} |
354
- | Blocked | ${metrics.blockedTasks} |
355
- | Story Points | ${metrics.completedPoints}/${metrics.totalPoints} |
356
- | Velocity | ${metrics.velocity} pts/day |
357
- | Days Remaining | ${metrics.remainingDays} |
358
- | Status | ${metrics.onTrack ? '✅ On Track' : '⚠️ At Risk'} |
359
-
360
- ---`);
361
-
362
- // Sprint Goals
363
- if (sprint.goals.length > 0) {
364
- sections.push(`## Sprint Goals
365
-
366
- ${sprint.goals.map((goal, i) => {
367
- const statusIcon = goal.status === 'completed' ? '✅' : goal.status === 'in_progress' ? '🔄' : '⬜';
368
- return `${i + 1}. ${statusIcon} **${goal.description}**
369
- ${goal.success_criteria.length > 0 ? goal.success_criteria.map(c => ` - ${c}`).join('\n') : ''}`;
370
- }).join('\n\n')}
371
-
372
- ---`);
373
- }
374
-
375
- // Tasks by Status
376
- sections.push(`## Tasks
377
-
378
- ### 🔄 In Progress (${metrics.inProgressTasks})
379
-
380
- ${this.formatTaskList(sprint.tasks.filter(t => t.status === TASK_STATUS.IN_PROGRESS))}
381
-
382
- ### 🚫 Blocked (${metrics.blockedTasks})
383
-
384
- ${this.formatTaskList(sprint.tasks.filter(t => t.status === TASK_STATUS.BLOCKED))}
385
-
386
- ### 📋 To Do (${metrics.todoTasks})
387
-
388
- ${this.formatTaskList(sprint.tasks.filter(t => t.status === TASK_STATUS.TODO))}
389
-
390
- ### ✅ Done (${metrics.completedTasks})
391
-
392
- ${this.formatTaskList(sprint.tasks.filter(t => t.status === TASK_STATUS.DONE), true)}
393
-
394
- ---`);
395
-
396
- // Blockers
397
- if (sprint.blockers.length > 0) {
398
- const openBlockers = sprint.blockers.filter(b => b.status === 'open');
399
- const resolvedBlockers = sprint.blockers.filter(b => b.status === 'resolved');
400
-
401
- sections.push(`## Blockers
402
-
403
- ### 🚨 Open (${openBlockers.length})
404
-
405
- ${openBlockers.length > 0 ? openBlockers.map(b => `- **${b.description}**
406
- - Severity: ${b.severity}
407
- - Owner: ${b.owner || 'Unassigned'}
408
- - Affects: ${b.affectedTasks.length} task(s)`).join('\n\n') : '_No open blockers_'}
409
-
410
- ### ✅ Resolved (${resolvedBlockers.length})
411
-
412
- ${resolvedBlockers.length > 0 ? resolvedBlockers.map(b => `- ~~${b.description}~~ (resolved ${b.resolved || 'unknown'})`).join('\n') : '_No resolved blockers_'}
413
-
414
- ---`);
415
- }
416
-
417
- // Burndown Chart (ASCII)
418
- sections.push(`## Burndown
419
-
420
- \`\`\`
421
- ${this.generateAsciiBurndown(burndown, metrics.totalPoints)}
422
- \`\`\`
423
-
424
- ---`);
425
-
426
- // Daily Log
427
- if (sprint.dailyProgress.length > 0) {
428
- const recentProgress = sprint.dailyProgress.slice(-7);
429
- sections.push(`## Daily Progress (Last 7 Days)
430
-
431
- | Date | Points Done | Remaining | Tasks Done | Blockers |
432
- |------|-------------|-----------|------------|----------|
433
- ${recentProgress.map(p => `| ${p.date} | ${p.completedPoints} | ${p.remainingPoints} | ${p.tasksDone} | ${p.blockerCount} |`).join('\n')}
434
-
435
- ---`);
436
- }
437
-
438
- // Team
439
- if (sprint.team.length > 0) {
440
- sections.push(`## Team
441
-
442
- ${sprint.team.map(member => `- **${member.name}** - ${member.role || 'Team Member'}
443
- - Tasks: ${sprint.tasks.filter(t => t.assignee === member.name).length}
444
- - Points: ${sprint.tasks.filter(t => t.assignee === member.name).reduce((s, t) => s + (t.estimate || 0), 0)}`).join('\n\n')}
445
-
446
- ---`);
447
- }
448
-
449
- // Notes
450
- if (sprint.notes) {
451
- sections.push(`## Notes
452
-
453
- ${sprint.notes}
454
-
455
- ---`);
456
- }
457
-
458
- // Footer
459
- sections.push(`---
460
-
461
- *Generated by [Bootspring](https://bootspring.com) Sprint Generator*
462
- *Sprint ID: ${sprint.id}*
463
- `);
464
-
465
- return sections.join('\n\n');
466
- }
467
-
468
- /**
469
- * Format status with emoji
470
- */
471
- formatStatus(status) {
472
- const statusMap = {
473
- [SPRINT_STATUS.PLANNING]: '📝 Planning',
474
- [SPRINT_STATUS.ACTIVE]: '🚀 Active',
475
- [SPRINT_STATUS.REVIEW]: '🔍 Review',
476
- [SPRINT_STATUS.COMPLETED]: '✅ Completed'
477
- };
478
- return statusMap[status] || status;
479
- }
480
-
481
- /**
482
- * Generate progress bar
483
- */
484
- generateProgressBar(percent, width = 20) {
485
- const filled = Math.round((percent / 100) * width);
486
- const empty = width - filled;
487
- return `[${'█'.repeat(filled)}${'░'.repeat(empty)}]`;
488
- }
489
-
490
- /**
491
- * Format task list
492
- */
493
- formatTaskList(tasks, collapsed = false) {
494
- if (tasks.length === 0) {
495
- return '_No tasks_';
496
- }
497
-
498
- return tasks.map(task => {
499
- const priority = PRIORITY[task.priority?.toUpperCase()] || PRIORITY.MEDIUM;
500
- const estimate = task.estimate ? `(${task.estimate} pts)` : '';
501
- const assignee = task.assignee ? `@${task.assignee}` : '';
502
- const labels = task.labels.length > 0 ? task.labels.map(l => `\`${l}\``).join(' ') : '';
503
-
504
- if (collapsed) {
505
- return `- [x] ${task.title} ${estimate}`;
506
- }
507
-
508
- return `- [ ] ${priority.emoji} **${task.title}** ${estimate}
509
- ${task.description ? task.description + '\n ' : ''}${assignee} ${labels}`.trim();
510
- }).join('\n\n');
511
- }
512
-
513
- /**
514
- * Generate ASCII burndown chart
515
- */
516
- generateAsciiBurndown(burndown, maxPoints) {
517
- const height = 10;
518
- const width = Math.min(burndown.length, 30);
519
- const pointsPerRow = maxPoints / height;
520
-
521
- const chart = [];
522
-
523
- // Y-axis labels
524
- for (let row = 0; row <= height; row++) {
525
- const points = Math.round(maxPoints - (row * pointsPerRow));
526
- const label = points.toString().padStart(4);
527
- let line = `${label} |`;
528
-
529
- // Plot points
530
- for (let col = 0; col < width && col < burndown.length; col++) {
531
- const data = burndown[col];
532
- const idealRow = Math.round((maxPoints - data.ideal) / pointsPerRow);
533
- const actualRow = data.actual !== null ? Math.round((maxPoints - data.actual) / pointsPerRow) : null;
534
-
535
- if (row === idealRow && actualRow === row) {
536
- line += '◆'; // Both ideal and actual
537
- } else if (row === idealRow) {
538
- line += '─'; // Ideal line
539
- } else if (row === actualRow) {
540
- line += '●'; // Actual point
541
- } else {
542
- line += ' ';
543
- }
544
- }
545
-
546
- chart.push(line);
547
- }
548
-
549
- // X-axis
550
- chart.push(' +' + '─'.repeat(width));
551
- chart.push(' ' + burndown.slice(0, width).map((_, i) => (i % 5 === 0 ? '|' : ' ')).join(''));
552
-
553
- // Legend
554
- chart.push('');
555
- chart.push('Legend: ─ Ideal ● Actual ◆ Both');
556
-
557
- return chart.join('\n');
558
- }
559
-
560
- /**
561
- * Import tasks from todo.md
562
- */
563
- importFromTodo(sprint, todoContent) {
564
- const lines = todoContent.split('\n');
565
- const tasks = [];
566
-
567
- for (const line of lines) {
568
- const match = line.match(/^[-*]\s*\[([ x])\]\s*(.+)$/);
569
- if (match) {
570
- const done = match[1] === 'x';
571
- const title = match[2].trim();
572
-
573
- tasks.push({
574
- title,
575
- estimate: this.estimateFromTitle(title),
576
- status: done ? TASK_STATUS.DONE : TASK_STATUS.TODO,
577
- priority: this.inferPriority(title)
578
- });
579
- }
580
- }
581
-
582
- for (const task of tasks) {
583
- this.addTask(sprint, task);
584
- }
585
-
586
- return sprint;
587
- }
588
-
589
- /**
590
- * Estimate points from task title (heuristic)
591
- */
592
- estimateFromTitle(title) {
593
- const lower = title.toLowerCase();
594
-
595
- if (lower.includes('fix') || lower.includes('typo') || lower.includes('update')) {
596
- return 1;
597
- }
598
- if (lower.includes('add') || lower.includes('implement') || lower.includes('create')) {
599
- return 3;
600
- }
601
- if (lower.includes('refactor') || lower.includes('redesign') || lower.includes('migrate')) {
602
- return 5;
603
- }
604
- if (lower.includes('integrate') || lower.includes('authentication') || lower.includes('payment')) {
605
- return 8;
606
- }
607
-
608
- return 2; // Default
609
- }
610
-
611
- /**
612
- * Infer priority from title
613
- */
614
- inferPriority(title) {
615
- const lower = title.toLowerCase();
616
-
617
- if (lower.includes('critical') || lower.includes('urgent') || lower.includes('security')) {
618
- return 'critical';
619
- }
620
- if (lower.includes('important') || lower.includes('high')) {
621
- return 'high';
622
- }
623
- if (lower.includes('low') || lower.includes('nice to have')) {
624
- return 'low';
625
- }
626
-
627
- return 'medium';
628
- }
629
- }
630
-
631
- /**
632
- * Generate SPRINT.md from project state
633
- */
634
- function generate(options = {}) {
635
- const generator = new SprintGenerator(options);
636
- const sprint = generator.loadSprintData();
637
- return generator.generate(sprint);
638
- }
639
-
640
- /**
641
- * Create a new sprint
642
- */
643
- function createSprint(options = {}) {
644
- const generator = new SprintGenerator(options);
645
- const sprint = generator.createDefaultSprint();
646
- generator.saveSprintData(sprint);
647
- return sprint;
648
- }
649
-
650
- /**
651
- * Load current sprint
652
- */
653
- function loadSprint(options = {}) {
654
- const generator = new SprintGenerator(options);
655
- return generator.loadSprintData();
656
- }
657
-
658
- /**
659
- * Save sprint data
660
- */
661
- function saveSprint(sprint, options = {}) {
662
- const generator = new SprintGenerator(options);
663
- generator.saveSprintData(sprint);
664
- }
665
-
666
- module.exports = {
667
- SprintGenerator,
668
- generate,
669
- createSprint,
670
- loadSprint,
671
- saveSprint,
672
- SPRINT_STATUS,
673
- TASK_STATUS,
674
- PRIORITY
675
- };