@girardmedia/bootspring 2.1.3 → 2.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/bin/bootspring.js +157 -83
  2. package/claude-commands/agent.md +34 -0
  3. package/claude-commands/bs.md +31 -0
  4. package/claude-commands/build.md +25 -0
  5. package/claude-commands/skill.md +31 -0
  6. package/claude-commands/todo.md +25 -0
  7. package/dist/core/index.d.ts +5814 -0
  8. package/dist/core.js +5779 -0
  9. package/dist/index.js +93883 -0
  10. package/dist/mcp/index.d.ts +1 -0
  11. package/dist/mcp-server.js +2298 -0
  12. package/generators/api-docs.js +3 -3
  13. package/generators/decisions.js +14 -14
  14. package/generators/health.js +6 -6
  15. package/generators/sprint.js +4 -4
  16. package/generators/templates/build-planning.template.js +2 -2
  17. package/generators/visual-doc-generator.js +1 -1
  18. package/package.json +22 -68
  19. package/cli/agent.js +0 -799
  20. package/cli/auth.js +0 -896
  21. package/cli/billing.js +0 -320
  22. package/cli/build.js +0 -1442
  23. package/cli/dashboard.js +0 -123
  24. package/cli/init.js +0 -669
  25. package/cli/mcp.js +0 -240
  26. package/cli/orchestrator.js +0 -240
  27. package/cli/project.js +0 -825
  28. package/cli/quality.js +0 -281
  29. package/cli/skill.js +0 -503
  30. package/cli/switch.js +0 -453
  31. package/cli/todo.js +0 -629
  32. package/cli/update.js +0 -132
  33. package/core/api-client.d.ts +0 -69
  34. package/core/api-client.js +0 -1482
  35. package/core/auth.d.ts +0 -98
  36. package/core/auth.js +0 -737
  37. package/core/build-orchestrator.js +0 -508
  38. package/core/build-state.js +0 -612
  39. package/core/config.d.ts +0 -106
  40. package/core/config.js +0 -1328
  41. package/core/context-loader.js +0 -580
  42. package/core/context.d.ts +0 -61
  43. package/core/context.js +0 -327
  44. package/core/entitlements.d.ts +0 -70
  45. package/core/entitlements.js +0 -322
  46. package/core/index.d.ts +0 -53
  47. package/core/index.js +0 -62
  48. package/core/mcp-config.js +0 -115
  49. package/core/policies.d.ts +0 -43
  50. package/core/policies.js +0 -113
  51. package/core/policy-matrix.js +0 -303
  52. package/core/project-activity.js +0 -175
  53. package/core/redaction.d.ts +0 -5
  54. package/core/redaction.js +0 -63
  55. package/core/self-update.js +0 -259
  56. package/core/session.js +0 -353
  57. package/core/task-extractor.js +0 -1098
  58. package/core/telemetry.d.ts +0 -55
  59. package/core/telemetry.js +0 -617
  60. package/core/tier-enforcement.js +0 -928
  61. package/core/utils.d.ts +0 -90
  62. package/core/utils.js +0 -455
  63. package/core/validation.js +0 -572
  64. package/mcp/server.d.ts +0 -57
  65. package/mcp/server.js +0 -264
package/cli/todo.js DELETED
@@ -1,629 +0,0 @@
1
- /**
2
- * Bootspring Todo Command
3
- * Simple and powerful todo management
4
- *
5
- * @package bootspring
6
- * @command todo
7
- */
8
-
9
- const fs = require('fs');
10
- const path = require('path');
11
- const config = require('../core/config');
12
- const utils = require('../core/utils');
13
- const { validateTodoText, validateNumericId } = require('../core/validation');
14
-
15
- // ============================================================================
16
- // Utility Functions (for programmatic/test use)
17
- // ============================================================================
18
-
19
- /**
20
- * Parse todo file and return todos
21
- * @param {string} filePath - Path to todo file
22
- * @returns {object[]} Array of todos with { done, text } format
23
- */
24
- function parseTodoFile(filePath) {
25
- try {
26
- const content = fs.readFileSync(filePath, 'utf-8');
27
- const todos = [];
28
- const lines = content.split('\n');
29
-
30
- for (const line of lines) {
31
- const match = line.match(/^(\s*)-\s*\[([ xX])\]\s*(.+)$/);
32
- if (match) {
33
- todos.push({
34
- done: match[2].toLowerCase() === 'x',
35
- text: match[3].trim()
36
- });
37
- }
38
- }
39
-
40
- return todos;
41
- } catch {
42
- return [];
43
- }
44
- }
45
-
46
- /**
47
- * Write todos to file
48
- * @param {string} filePath - Path to todo file
49
- * @param {object[]} items - Array of todos with { done, text } format
50
- */
51
- function writeTodoFile(filePath, items) {
52
- const lines = ['# Todo', ''];
53
-
54
- for (const item of items) {
55
- const checkbox = item.done ? '[x]' : '[ ]';
56
- lines.push(`- ${checkbox} ${item.text}`);
57
- }
58
-
59
- lines.push(''); // trailing newline
60
- fs.writeFileSync(filePath, lines.join('\n'), 'utf-8');
61
- }
62
-
63
- /**
64
- * Add a todo to file (utility function)
65
- * @param {string} filePath - Path to todo file
66
- * @param {string} text - Todo text
67
- */
68
- function addTodoUtil(filePath, text) {
69
- const todos = parseTodoFile(filePath);
70
- todos.push({ done: false, text });
71
- writeTodoFile(filePath, todos);
72
- }
73
-
74
- /**
75
- * Mark a todo as done (utility function)
76
- * @param {string} filePath - Path to todo file
77
- * @param {number} index - Zero-based index
78
- */
79
- function markDone(filePath, index) {
80
- const todos = parseTodoFile(filePath);
81
- if (index >= 0 && index < todos.length) {
82
- todos[index].done = true;
83
- writeTodoFile(filePath, todos);
84
- }
85
- }
86
-
87
- /**
88
- * Mark a todo as not done (utility function)
89
- * @param {string} filePath - Path to todo file
90
- * @param {number} index - Zero-based index
91
- */
92
- function markUndone(filePath, index) {
93
- const todos = parseTodoFile(filePath);
94
- if (index >= 0 && index < todos.length) {
95
- todos[index].done = false;
96
- writeTodoFile(filePath, todos);
97
- }
98
- }
99
-
100
- /**
101
- * Remove a todo by index (utility function)
102
- * @param {string} filePath - Path to todo file
103
- * @param {number} index - Zero-based index
104
- */
105
- function removeTodoUtil(filePath, index) {
106
- const todos = parseTodoFile(filePath);
107
- if (index >= 0 && index < todos.length) {
108
- todos.splice(index, 1);
109
- writeTodoFile(filePath, todos);
110
- }
111
- }
112
-
113
- /**
114
- * Clear todos (utility function)
115
- * @param {string} filePath - Path to todo file
116
- * @param {string} type - 'completed' or 'all'
117
- */
118
- function clearTodos(filePath, type = 'completed') {
119
- const todos = parseTodoFile(filePath);
120
-
121
- if (type === 'all') {
122
- writeTodoFile(filePath, []);
123
- } else if (type === 'completed') {
124
- const remaining = todos.filter(t => !t.done);
125
- writeTodoFile(filePath, remaining);
126
- }
127
- }
128
-
129
- /**
130
- * List all todos from file (utility function)
131
- * @param {string} filePath - Path to todo file
132
- * @returns {object[]} Array of todos
133
- */
134
- function listTodosUtil(filePath) {
135
- return parseTodoFile(filePath);
136
- }
137
-
138
- // ============================================================================
139
- // Internal Parsing (for CLI use)
140
- // ============================================================================
141
-
142
- /**
143
- * Parse todo.md file content
144
- * @param {string} content - File content
145
- * @returns {object[]} Array of todos with full metadata
146
- */
147
- function parseTodos(content) {
148
- const todos = [];
149
- const lines = content.split('\n');
150
-
151
- for (let i = 0; i < lines.length; i++) {
152
- const line = lines[i];
153
-
154
- // Match todo items: - [ ] or - [x]
155
- const match = line.match(/^(\s*)-\s*\[([ xX])\]\s*(.+)$/);
156
- if (match) {
157
- todos.push({
158
- index: todos.length + 1,
159
- indent: match[1].length,
160
- completed: match[2].toLowerCase() === 'x',
161
- text: match[3].trim(),
162
- line: i
163
- });
164
- }
165
- }
166
-
167
- return todos;
168
- }
169
-
170
- /**
171
- * Format todos for display
172
- * @param {object[]} todos - Array of todos
173
- * @param {object} options - Display options
174
- */
175
- function displayTodos(todos, options = {}) {
176
- const { showCompleted = true, showIndex = true } = options;
177
-
178
- const pending = todos.filter(t => !t.completed);
179
- const completed = todos.filter(t => t.completed);
180
-
181
- if (pending.length === 0 && completed.length === 0) {
182
- utils.print.dim('No todos found. Add one with: bootspring todo add "Your task"');
183
- return;
184
- }
185
-
186
- // Display pending
187
- if (pending.length > 0) {
188
- console.log(`\n${utils.COLORS.bold}Pending (${pending.length})${utils.COLORS.reset}\n`);
189
- for (const todo of pending) {
190
- const index = showIndex ? `${utils.COLORS.dim}${String(todo.index).padStart(2)}${utils.COLORS.reset} ` : '';
191
- const indent = ' '.repeat(todo.indent);
192
- console.log(`${index}${indent}${utils.COLORS.yellow}○${utils.COLORS.reset} ${todo.text}`);
193
- }
194
- }
195
-
196
- // Display completed
197
- if (showCompleted && completed.length > 0) {
198
- console.log(`\n${utils.COLORS.bold}Completed (${completed.length})${utils.COLORS.reset}\n`);
199
- for (const todo of completed) {
200
- const index = showIndex ? `${utils.COLORS.dim}${String(todo.index).padStart(2)}${utils.COLORS.reset} ` : '';
201
- const indent = ' '.repeat(todo.indent);
202
- console.log(`${index}${indent}${utils.COLORS.green}●${utils.COLORS.reset} ${utils.COLORS.dim}${todo.text}${utils.COLORS.reset}`);
203
- }
204
- }
205
-
206
- console.log();
207
- }
208
-
209
- /**
210
- * Get todo file path
211
- * @returns {string} Todo file path
212
- */
213
- function getTodoPath() {
214
- const cfg = config.load();
215
- return path.join(cfg._projectRoot, cfg.paths?.todo || 'todo.md');
216
- }
217
-
218
- /**
219
- * Read todo file
220
- * @returns {string} File content
221
- */
222
- function readTodoFile() {
223
- const todoPath = getTodoPath();
224
- if (!utils.fileExists(todoPath)) {
225
- // Create default todo file
226
- const defaultContent = '# Todo List\n\n## Pending\n\n## Completed\n';
227
- utils.writeFile(todoPath, defaultContent);
228
- return defaultContent;
229
- }
230
- return utils.readFile(todoPath);
231
- }
232
-
233
- /**
234
- * Save todo file content (internal CLI use)
235
- * @param {string} content - File content
236
- */
237
- function saveTodoContent(content) {
238
- const todoPath = getTodoPath();
239
- utils.writeFile(todoPath, content);
240
- }
241
-
242
- /**
243
- * List all todos
244
- */
245
- function listTodos(args) {
246
- const parsedArgs = utils.parseArgs(args);
247
- const content = readTodoFile();
248
- const todos = parseTodos(content);
249
-
250
- console.log(`${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Todo${utils.COLORS.reset}`);
251
-
252
- displayTodos(todos, {
253
- showCompleted: !parsedArgs.pending,
254
- showIndex: true
255
- });
256
-
257
- // Summary
258
- const pending = todos.filter(t => !t.completed).length;
259
- const completed = todos.filter(t => t.completed).length;
260
- utils.print.dim(`${pending} pending, ${completed} completed`);
261
- }
262
-
263
- /**
264
- * Add a new todo
265
- */
266
- function addTodo(args) {
267
- const rawText = args.join(' ').trim();
268
-
269
- if (!rawText) {
270
- utils.print.error('Please provide a todo text');
271
- utils.print.dim('Usage: bootspring todo add "Your task"');
272
- return;
273
- }
274
-
275
- // Validate and sanitize todo text
276
- const validation = validateTodoText(rawText);
277
- if (!validation.valid) {
278
- utils.print.error(`Invalid todo text: ${validation.error}`);
279
- return;
280
- }
281
- const text = validation.sanitized;
282
-
283
- const content = readTodoFile();
284
- const lines = content.split('\n');
285
-
286
- // Find the "Pending" section or add after first heading
287
- let insertIndex = -1;
288
- for (let i = 0; i < lines.length; i++) {
289
- if (lines[i].match(/^##?\s*(Pending|In Progress|Todo)/i)) {
290
- insertIndex = i + 1;
291
- // Skip empty lines after heading
292
- while (insertIndex < lines.length && lines[insertIndex].trim() === '') {
293
- insertIndex++;
294
- }
295
- break;
296
- }
297
- }
298
-
299
- // If no section found, add after first heading
300
- if (insertIndex === -1) {
301
- for (let i = 0; i < lines.length; i++) {
302
- if (lines[i].startsWith('#')) {
303
- insertIndex = i + 1;
304
- break;
305
- }
306
- }
307
- }
308
-
309
- // If still no good place, add at the end
310
- if (insertIndex === -1) {
311
- insertIndex = lines.length;
312
- }
313
-
314
- // Insert the new todo
315
- const newTodo = `- [ ] ${text}`;
316
- lines.splice(insertIndex, 0, newTodo);
317
-
318
- saveTodoContent(lines.join('\n'));
319
-
320
- utils.print.success(`Added: ${text}`);
321
-
322
- // Show count
323
- const todos = parseTodos(lines.join('\n'));
324
- const pending = todos.filter(t => !t.completed).length;
325
- utils.print.dim(`${pending} pending todos`);
326
- }
327
-
328
- /**
329
- * Mark todo as done
330
- */
331
- function doneTodo(args) {
332
- const indexStr = args[0];
333
-
334
- if (!indexStr) {
335
- utils.print.error('Please provide a todo number');
336
- utils.print.dim('Usage: bootspring todo done <number>');
337
- return;
338
- }
339
-
340
- // Validate numeric ID
341
- const validation = validateNumericId(indexStr, { min: 1 });
342
- if (!validation.valid) {
343
- utils.print.error(`Invalid todo number: ${validation.error}`);
344
- return;
345
- }
346
- const index = validation.value;
347
-
348
- const content = readTodoFile();
349
- const todos = parseTodos(content);
350
-
351
- const todo = todos.find(t => t.index === index);
352
- if (!todo) {
353
- utils.print.error(`Todo #${index} not found`);
354
- return;
355
- }
356
-
357
- if (todo.completed) {
358
- utils.print.warning(`Todo #${index} is already completed`);
359
- return;
360
- }
361
-
362
- // Update the line
363
- const lines = content.split('\n');
364
- const originalLine = lines[todo.line];
365
- lines[todo.line] = originalLine.replace('[ ]', '[x]');
366
-
367
- saveTodoContent(lines.join('\n'));
368
-
369
- utils.print.success(`Completed: ${todo.text}`);
370
-
371
- // Show remaining
372
- const remaining = todos.filter(t => !t.completed && t.index !== index).length;
373
- utils.print.dim(`${remaining} pending todos remaining`);
374
- }
375
-
376
- /**
377
- * Undo a completed todo
378
- */
379
- function undoTodo(args) {
380
- const indexStr = args[0];
381
-
382
- if (!indexStr) {
383
- utils.print.error('Please provide a todo number');
384
- utils.print.dim('Usage: bootspring todo undo <number>');
385
- return;
386
- }
387
-
388
- // Validate numeric ID
389
- const validation = validateNumericId(indexStr, { min: 1 });
390
- if (!validation.valid) {
391
- utils.print.error(`Invalid todo number: ${validation.error}`);
392
- return;
393
- }
394
- const index = validation.value;
395
-
396
- const content = readTodoFile();
397
- const todos = parseTodos(content);
398
-
399
- const todo = todos.find(t => t.index === index);
400
- if (!todo) {
401
- utils.print.error(`Todo #${index} not found`);
402
- return;
403
- }
404
-
405
- if (!todo.completed) {
406
- utils.print.warning(`Todo #${index} is not completed`);
407
- return;
408
- }
409
-
410
- // Update the line
411
- const lines = content.split('\n');
412
- const originalLine = lines[todo.line];
413
- lines[todo.line] = originalLine.replace(/\[[xX]\]/, '[ ]');
414
-
415
- saveTodoContent(lines.join('\n'));
416
-
417
- utils.print.success(`Reopened: ${todo.text}`);
418
- }
419
-
420
- /**
421
- * Remove a todo
422
- */
423
- function removeTodo(args) {
424
- const indexStr = args[0];
425
-
426
- if (!indexStr) {
427
- utils.print.error('Please provide a todo number');
428
- utils.print.dim('Usage: bootspring todo remove <number>');
429
- return;
430
- }
431
-
432
- // Validate numeric ID
433
- const validation = validateNumericId(indexStr, { min: 1 });
434
- if (!validation.valid) {
435
- utils.print.error(`Invalid todo number: ${validation.error}`);
436
- return;
437
- }
438
- const index = validation.value;
439
-
440
- const content = readTodoFile();
441
- const todos = parseTodos(content);
442
-
443
- const todo = todos.find(t => t.index === index);
444
- if (!todo) {
445
- utils.print.error(`Todo #${index} not found`);
446
- return;
447
- }
448
-
449
- // Remove the line
450
- const lines = content.split('\n');
451
- lines.splice(todo.line, 1);
452
-
453
- saveTodoContent(lines.join('\n'));
454
-
455
- utils.print.success(`Removed: ${todo.text}`);
456
- }
457
-
458
- /**
459
- * Clear completed todos
460
- */
461
- function clearCompleted() {
462
- const content = readTodoFile();
463
- const todos = parseTodos(content);
464
- const completedTodos = todos.filter(t => t.completed);
465
-
466
- if (completedTodos.length === 0) {
467
- utils.print.info('No completed todos to clear');
468
- return;
469
- }
470
-
471
- // Remove completed lines (in reverse order to maintain indices)
472
- const lines = content.split('\n');
473
- const linesToRemove = completedTodos.map(t => t.line).sort((a, b) => b - a);
474
-
475
- for (const lineIndex of linesToRemove) {
476
- lines.splice(lineIndex, 1);
477
- }
478
-
479
- saveTodoContent(lines.join('\n'));
480
-
481
- utils.print.success(`Cleared ${completedTodos.length} completed todos`);
482
- }
483
-
484
- /**
485
- * Show todo help
486
- */
487
- function showHelp() {
488
- console.log(`
489
- ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Todo${utils.COLORS.reset}
490
- ${utils.COLORS.dim}Simple and powerful todo management${utils.COLORS.reset}
491
-
492
- ${utils.COLORS.bold}Usage:${utils.COLORS.reset}
493
- bootspring todo <command> [args]
494
-
495
- ${utils.COLORS.bold}Commands:${utils.COLORS.reset}
496
- ${utils.COLORS.cyan}list${utils.COLORS.reset} List all todos
497
- ${utils.COLORS.cyan}add${utils.COLORS.reset} <text> Add a new todo
498
- ${utils.COLORS.cyan}done${utils.COLORS.reset} <number> Mark todo as completed
499
- ${utils.COLORS.cyan}undo${utils.COLORS.reset} <number> Reopen a completed todo
500
- ${utils.COLORS.cyan}remove${utils.COLORS.reset} <number> Remove a todo
501
- ${utils.COLORS.cyan}clear${utils.COLORS.reset} Clear all completed todos
502
-
503
- ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
504
- bootspring todo add "Implement user auth"
505
- bootspring todo done 1
506
- bootspring todo list --pending
507
- bootspring todo clear
508
- `);
509
- }
510
-
511
- /**
512
- * Run todo command
513
- */
514
- async function run(args) {
515
- const subcommand = args[0] || 'list';
516
- const subargs = args.slice(1);
517
-
518
- switch (subcommand) {
519
- case 'list':
520
- case 'ls':
521
- listTodos(subargs);
522
- break;
523
-
524
- case 'add':
525
- case 'a':
526
- case 'new':
527
- addTodo(subargs);
528
- break;
529
-
530
- case 'done':
531
- case 'd':
532
- case 'complete':
533
- case 'check':
534
- doneTodo(subargs);
535
- break;
536
-
537
- case 'undo':
538
- case 'u':
539
- case 'reopen':
540
- undoTodo(subargs);
541
- break;
542
-
543
- case 'remove':
544
- case 'rm':
545
- case 'delete':
546
- removeTodo(subargs);
547
- break;
548
-
549
- case 'clear':
550
- clearCompleted();
551
- break;
552
-
553
- case 'help':
554
- case '-h':
555
- case '--help':
556
- showHelp();
557
- break;
558
-
559
- default:
560
- // If it looks like a todo text, treat as add
561
- if (subcommand && !subcommand.startsWith('-')) {
562
- addTodo([subcommand, ...subargs]);
563
- } else {
564
- utils.print.error(`Unknown subcommand: ${subcommand}`);
565
- showHelp();
566
- }
567
- }
568
- }
569
-
570
- // Smart wrapper that detects if called with file path or CLI args
571
- function addTodoSmart(firstArg, secondArg) {
572
- if (typeof firstArg === 'string' && typeof secondArg === 'string') {
573
- // Utility mode: addTodo(filePath, text)
574
- return addTodoUtil(firstArg, secondArg);
575
- } else if (Array.isArray(firstArg)) {
576
- // CLI mode: addTodo(args)
577
- return addTodo(firstArg);
578
- } else {
579
- // Default CLI mode with single arg treated as array
580
- return addTodo([firstArg, secondArg].filter(Boolean));
581
- }
582
- }
583
-
584
- function removeTodoSmart(firstArg, secondArg) {
585
- if (typeof firstArg === 'string' && typeof secondArg === 'number') {
586
- // Utility mode: removeTodo(filePath, index)
587
- return removeTodoUtil(firstArg, secondArg);
588
- } else if (Array.isArray(firstArg)) {
589
- // CLI mode: removeTodo(args)
590
- return removeTodo(firstArg);
591
- } else {
592
- return removeTodo([firstArg]);
593
- }
594
- }
595
-
596
- function listTodosSmart(firstArg) {
597
- if (typeof firstArg === 'string' && (firstArg.includes('/') || firstArg.includes('\\'))) {
598
- // Utility mode: listTodos(filePath)
599
- return listTodosUtil(firstArg);
600
- } else if (Array.isArray(firstArg)) {
601
- // CLI mode: listTodos(args)
602
- return listTodos(firstArg);
603
- } else {
604
- return listTodos([firstArg].filter(Boolean));
605
- }
606
- }
607
-
608
- module.exports = {
609
- // CLI command runner
610
- run,
611
-
612
- // CLI-focused internal functions
613
- parseTodos,
614
- doneTodo,
615
- undoTodo,
616
- clearCompleted,
617
-
618
- // Utility functions (for programmatic/test use)
619
- parseTodoFile,
620
- writeTodoFile,
621
- markDone,
622
- markUndone,
623
- clearTodos,
624
-
625
- // Smart wrappers that work in both modes
626
- addTodo: addTodoSmart,
627
- removeTodo: removeTodoSmart,
628
- listTodos: listTodosSmart
629
- };