@girardmedia/bootspring 1.1.0

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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +255 -0
  3. package/agents/README.md +93 -0
  4. package/agents/api-expert/context.md +416 -0
  5. package/agents/architecture-expert/context.md +454 -0
  6. package/agents/backend-expert/context.md +483 -0
  7. package/agents/code-review-expert/context.md +365 -0
  8. package/agents/database-expert/context.md +250 -0
  9. package/agents/devops-expert/context.md +446 -0
  10. package/agents/frontend-expert/context.md +364 -0
  11. package/agents/index.js +140 -0
  12. package/agents/performance-expert/context.md +377 -0
  13. package/agents/security-expert/context.md +343 -0
  14. package/agents/testing-expert/context.md +414 -0
  15. package/agents/ui-ux-expert/context.md +448 -0
  16. package/agents/vercel-expert/context.md +426 -0
  17. package/bin/bootspring.js +310 -0
  18. package/cli/agent.js +337 -0
  19. package/cli/context.js +194 -0
  20. package/cli/dashboard.js +150 -0
  21. package/cli/generate.js +294 -0
  22. package/cli/init.js +410 -0
  23. package/cli/loop.js +421 -0
  24. package/cli/mcp.js +241 -0
  25. package/cli/memory.js +303 -0
  26. package/cli/orchestrator.js +400 -0
  27. package/cli/plugin.js +451 -0
  28. package/cli/quality.js +332 -0
  29. package/cli/skill.js +369 -0
  30. package/cli/task.js +628 -0
  31. package/cli/telemetry.js +114 -0
  32. package/cli/todo.js +614 -0
  33. package/cli/update.js +312 -0
  34. package/core/config.js +245 -0
  35. package/core/context.js +329 -0
  36. package/core/entitlements.js +209 -0
  37. package/core/index.js +43 -0
  38. package/core/policies.js +68 -0
  39. package/core/telemetry.js +247 -0
  40. package/core/utils.js +380 -0
  41. package/dashboard/server.js +818 -0
  42. package/docs/integrations/claude-code.md +42 -0
  43. package/docs/integrations/codex.md +42 -0
  44. package/docs/mcp-api-platform.md +102 -0
  45. package/generators/generate.js +598 -0
  46. package/generators/index.js +18 -0
  47. package/hooks/context-detector.js +177 -0
  48. package/hooks/index.js +35 -0
  49. package/hooks/prompt-enhancer.js +289 -0
  50. package/intelligence/git-memory.js +551 -0
  51. package/intelligence/index.js +59 -0
  52. package/intelligence/orchestrator.js +964 -0
  53. package/intelligence/prd.js +447 -0
  54. package/intelligence/recommendation-weights.json +18 -0
  55. package/intelligence/recommendations.js +234 -0
  56. package/mcp/capabilities.js +71 -0
  57. package/mcp/contracts/mcp-contract.v1.json +497 -0
  58. package/mcp/registry.js +213 -0
  59. package/mcp/response-formatter.js +462 -0
  60. package/mcp/server.js +99 -0
  61. package/mcp/tools/agent-tool.js +137 -0
  62. package/mcp/tools/capabilities-tool.js +54 -0
  63. package/mcp/tools/context-tool.js +49 -0
  64. package/mcp/tools/dashboard-tool.js +58 -0
  65. package/mcp/tools/generate-tool.js +46 -0
  66. package/mcp/tools/loop-tool.js +134 -0
  67. package/mcp/tools/memory-tool.js +180 -0
  68. package/mcp/tools/orchestrator-tool.js +232 -0
  69. package/mcp/tools/plugin-tool.js +76 -0
  70. package/mcp/tools/quality-tool.js +47 -0
  71. package/mcp/tools/skill-tool.js +233 -0
  72. package/mcp/tools/telemetry-tool.js +95 -0
  73. package/mcp/tools/todo-tool.js +133 -0
  74. package/package.json +98 -0
  75. package/plugins/index.js +141 -0
  76. package/quality/index.js +380 -0
  77. package/quality/lint-budgets.json +19 -0
  78. package/skills/index.js +787 -0
  79. package/skills/patterns/README.md +163 -0
  80. package/skills/patterns/api/route-handler.md +217 -0
  81. package/skills/patterns/api/server-action.md +249 -0
  82. package/skills/patterns/auth/clerk.md +132 -0
  83. package/skills/patterns/database/prisma.md +180 -0
  84. package/skills/patterns/payments/stripe.md +272 -0
  85. package/skills/patterns/security/validation.md +268 -0
  86. package/skills/patterns/testing/vitest.md +307 -0
  87. package/templates/bootspring.config.js +83 -0
  88. package/templates/mcp.json +9 -0
package/cli/task.js ADDED
@@ -0,0 +1,628 @@
1
+ /**
2
+ * Bootspring Task Command
3
+ * Manage development tasks with lifecycle tracking
4
+ *
5
+ * @package bootspring
6
+ * @command task
7
+ */
8
+
9
+ const path = require('path');
10
+ const fs = require('fs');
11
+ const config = require('../core/config');
12
+ const utils = require('../core/utils');
13
+
14
+ /**
15
+ * Task statuses with emoji indicators
16
+ */
17
+ const TASK_STATUS = {
18
+ draft: { emoji: '📝', label: 'Draft', color: 'dim' },
19
+ ready: { emoji: '📋', label: 'Ready', color: 'cyan' },
20
+ 'in-progress': { emoji: '🔨', label: 'In Progress', color: 'yellow' },
21
+ review: { emoji: '👀', label: 'Review', color: 'magenta' },
22
+ testing: { emoji: '🧪', label: 'Testing', color: 'blue' },
23
+ done: { emoji: '✅', label: 'Done', color: 'green' },
24
+ blocked: { emoji: '🚫', label: 'Blocked', color: 'red' }
25
+ };
26
+
27
+ /**
28
+ * Task priorities
29
+ */
30
+ const TASK_PRIORITY = {
31
+ P0: { label: 'Critical', color: 'red' },
32
+ P1: { label: 'High', color: 'yellow' },
33
+ P2: { label: 'Medium', color: 'cyan' },
34
+ P3: { label: 'Low', color: 'dim' }
35
+ };
36
+
37
+ /**
38
+ * Task lifecycle phases (11-phase workflow)
39
+ */
40
+ const LIFECYCLE_PHASES = [
41
+ { id: 'context', name: 'Context Gathering', description: 'Understand the task fully' },
42
+ { id: 'research', name: 'Research', description: 'Investigate options and approaches' },
43
+ { id: 'design', name: 'Design', description: 'Plan the implementation' },
44
+ { id: 'setup', name: 'Setup', description: 'Prepare environment and dependencies' },
45
+ { id: 'implement', name: 'Implementation', description: 'Write the code' },
46
+ { id: 'test', name: 'Testing', description: 'Write and run tests' },
47
+ { id: 'review', name: 'Review', description: 'Code review and feedback' },
48
+ { id: 'refine', name: 'Refinement', description: 'Address feedback and polish' },
49
+ { id: 'document', name: 'Documentation', description: 'Update docs and comments' },
50
+ { id: 'deploy', name: 'Deployment', description: 'Deploy to target environment' },
51
+ { id: 'verify', name: 'Verification', description: 'Verify in production' }
52
+ ];
53
+
54
+ /**
55
+ * Get tasks file path
56
+ */
57
+ function getTasksPath(projectRoot) {
58
+ return path.join(projectRoot, '.bootspring', 'tasks.json');
59
+ }
60
+
61
+ /**
62
+ * Load tasks from file
63
+ */
64
+ function loadTasks(projectRoot) {
65
+ const tasksPath = getTasksPath(projectRoot);
66
+
67
+ if (!fs.existsSync(tasksPath)) {
68
+ return { tasks: [], nextId: 1 };
69
+ }
70
+
71
+ try {
72
+ const content = fs.readFileSync(tasksPath, 'utf-8');
73
+ return JSON.parse(content);
74
+ } catch (error) {
75
+ return { tasks: [], nextId: 1 };
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Save tasks to file
81
+ */
82
+ function saveTasks(projectRoot, data) {
83
+ const tasksPath = getTasksPath(projectRoot);
84
+ const dir = path.dirname(tasksPath);
85
+
86
+ if (!fs.existsSync(dir)) {
87
+ fs.mkdirSync(dir, { recursive: true });
88
+ }
89
+
90
+ fs.writeFileSync(tasksPath, JSON.stringify(data, null, 2));
91
+ }
92
+
93
+ /**
94
+ * Create a new task
95
+ */
96
+ function createTask(options = {}) {
97
+ const cfg = config.load();
98
+ const projectRoot = cfg._projectRoot;
99
+ const data = loadTasks(projectRoot);
100
+
101
+ const task = {
102
+ id: data.nextId,
103
+ title: options.title || 'Untitled Task',
104
+ description: options.description || '',
105
+ status: 'draft',
106
+ priority: options.priority || 'P2',
107
+ phase: 'context',
108
+ tags: options.tags || [],
109
+ assignee: options.assignee || null,
110
+ created: new Date().toISOString(),
111
+ updated: new Date().toISOString(),
112
+ blockedBy: [],
113
+ blocks: [],
114
+ notes: [],
115
+ timeTracking: {
116
+ estimated: options.estimate || null,
117
+ actual: 0,
118
+ sessions: []
119
+ }
120
+ };
121
+
122
+ data.tasks.push(task);
123
+ data.nextId++;
124
+ saveTasks(projectRoot, data);
125
+
126
+ utils.print.success(`Created task #${task.id}: ${task.title}`);
127
+ console.log(`${utils.COLORS.dim}Status: ${TASK_STATUS[task.status].label}${utils.COLORS.reset}`);
128
+ console.log(`${utils.COLORS.dim}Priority: ${TASK_PRIORITY[task.priority].label}${utils.COLORS.reset}`);
129
+
130
+ return task;
131
+ }
132
+
133
+ /**
134
+ * List tasks
135
+ */
136
+ function listTasks(options = {}) {
137
+ const cfg = config.load();
138
+ const projectRoot = cfg._projectRoot;
139
+ const data = loadTasks(projectRoot);
140
+
141
+ let tasks = data.tasks;
142
+
143
+ // Filter by status
144
+ if (options.status) {
145
+ tasks = tasks.filter(t => t.status === options.status);
146
+ }
147
+
148
+ // Filter by priority
149
+ if (options.priority) {
150
+ tasks = tasks.filter(t => t.priority === options.priority);
151
+ }
152
+
153
+ // Filter by tag
154
+ if (options.tag) {
155
+ tasks = tasks.filter(t => t.tags.includes(options.tag));
156
+ }
157
+
158
+ // Exclude done unless requested
159
+ if (!options.all && !options.status) {
160
+ tasks = tasks.filter(t => t.status !== 'done');
161
+ }
162
+
163
+ console.log(`
164
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Tasks${utils.COLORS.reset}
165
+ ${utils.COLORS.dim}${tasks.length} task(s) found${utils.COLORS.reset}
166
+ `);
167
+
168
+ if (tasks.length === 0) {
169
+ utils.print.dim('No tasks found. Use "bootspring task create" to add one.');
170
+ return;
171
+ }
172
+
173
+ // Group by status
174
+ const grouped = {};
175
+ for (const task of tasks) {
176
+ if (!grouped[task.status]) {
177
+ grouped[task.status] = [];
178
+ }
179
+ grouped[task.status].push(task);
180
+ }
181
+
182
+ for (const [status, statusTasks] of Object.entries(grouped)) {
183
+ const statusInfo = TASK_STATUS[status];
184
+ console.log(`${statusInfo.emoji} ${utils.COLORS.bold}${statusInfo.label}${utils.COLORS.reset}`);
185
+
186
+ for (const task of statusTasks) {
187
+ const priorityInfo = TASK_PRIORITY[task.priority];
188
+ const priorityColor = utils.COLORS[priorityInfo.color] || '';
189
+
190
+ console.log(` ${utils.COLORS.dim}#${task.id}${utils.COLORS.reset} ${task.title}`);
191
+ console.log(` ${priorityColor}${task.priority}${utils.COLORS.reset} ${utils.COLORS.dim}| Phase: ${task.phase}${utils.COLORS.reset}`);
192
+
193
+ if (task.tags.length > 0) {
194
+ console.log(` ${utils.COLORS.dim}Tags: ${task.tags.join(', ')}${utils.COLORS.reset}`);
195
+ }
196
+ }
197
+ console.log();
198
+ }
199
+ }
200
+
201
+ /**
202
+ * Show task details
203
+ */
204
+ function showTask(taskId) {
205
+ const cfg = config.load();
206
+ const projectRoot = cfg._projectRoot;
207
+ const data = loadTasks(projectRoot);
208
+
209
+ const task = data.tasks.find(t => t.id === parseInt(taskId));
210
+
211
+ if (!task) {
212
+ utils.print.error(`Task #${taskId} not found`);
213
+ return;
214
+ }
215
+
216
+ const statusInfo = TASK_STATUS[task.status];
217
+ const priorityInfo = TASK_PRIORITY[task.priority];
218
+ const phaseInfo = LIFECYCLE_PHASES.find(p => p.id === task.phase);
219
+
220
+ console.log(`
221
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Task #${task.id}${utils.COLORS.reset}
222
+ ${utils.COLORS.bold}${task.title}${utils.COLORS.reset}
223
+
224
+ ${utils.COLORS.bold}Status${utils.COLORS.reset}
225
+ ${statusInfo.emoji} ${statusInfo.label}
226
+ Priority: ${utils.COLORS[priorityInfo.color]}${task.priority}${utils.COLORS.reset} (${priorityInfo.label})
227
+ Phase: ${task.phase} - ${phaseInfo?.name || 'Unknown'}
228
+
229
+ ${utils.COLORS.bold}Description${utils.COLORS.reset}
230
+ ${task.description || utils.COLORS.dim + 'No description' + utils.COLORS.reset}
231
+
232
+ ${utils.COLORS.bold}Details${utils.COLORS.reset}
233
+ Created: ${utils.formatRelativeTime(new Date(task.created))}
234
+ Updated: ${utils.formatRelativeTime(new Date(task.updated))}
235
+ ${task.assignee ? `Assignee: ${task.assignee}` : ''}
236
+ ${task.tags.length > 0 ? `Tags: ${task.tags.join(', ')}` : ''}
237
+ `);
238
+
239
+ // Show lifecycle progress
240
+ console.log(`${utils.COLORS.bold}Lifecycle Progress${utils.COLORS.reset}`);
241
+ const currentPhaseIndex = LIFECYCLE_PHASES.findIndex(p => p.id === task.phase);
242
+
243
+ for (let i = 0; i < LIFECYCLE_PHASES.length; i++) {
244
+ const phase = LIFECYCLE_PHASES[i];
245
+ let icon, color;
246
+
247
+ if (i < currentPhaseIndex) {
248
+ icon = '✓';
249
+ color = utils.COLORS.green;
250
+ } else if (i === currentPhaseIndex) {
251
+ icon = '●';
252
+ color = utils.COLORS.cyan;
253
+ } else {
254
+ icon = '○';
255
+ color = utils.COLORS.dim;
256
+ }
257
+
258
+ console.log(` ${color}${icon} ${phase.name}${utils.COLORS.reset}`);
259
+ }
260
+
261
+ // Show notes
262
+ if (task.notes.length > 0) {
263
+ console.log(`\n${utils.COLORS.bold}Notes${utils.COLORS.reset}`);
264
+ for (const note of task.notes.slice(-5)) {
265
+ console.log(` ${utils.COLORS.dim}${note.timestamp}${utils.COLORS.reset}`);
266
+ console.log(` ${note.content}`);
267
+ console.log();
268
+ }
269
+ }
270
+
271
+ // Show blocking info
272
+ if (task.blockedBy.length > 0) {
273
+ console.log(`${utils.COLORS.red}Blocked by: ${task.blockedBy.map(id => `#${id}`).join(', ')}${utils.COLORS.reset}`);
274
+ }
275
+ if (task.blocks.length > 0) {
276
+ console.log(`${utils.COLORS.yellow}Blocks: ${task.blocks.map(id => `#${id}`).join(', ')}${utils.COLORS.reset}`);
277
+ }
278
+ }
279
+
280
+ /**
281
+ * Update task status
282
+ */
283
+ function updateTaskStatus(taskId, newStatus) {
284
+ const cfg = config.load();
285
+ const projectRoot = cfg._projectRoot;
286
+ const data = loadTasks(projectRoot);
287
+
288
+ const task = data.tasks.find(t => t.id === parseInt(taskId));
289
+
290
+ if (!task) {
291
+ utils.print.error(`Task #${taskId} not found`);
292
+ return;
293
+ }
294
+
295
+ if (!TASK_STATUS[newStatus]) {
296
+ utils.print.error(`Invalid status: ${newStatus}`);
297
+ utils.print.dim(`Valid statuses: ${Object.keys(TASK_STATUS).join(', ')}`);
298
+ return;
299
+ }
300
+
301
+ const oldStatus = task.status;
302
+ task.status = newStatus;
303
+ task.updated = new Date().toISOString();
304
+
305
+ saveTasks(projectRoot, data);
306
+
307
+ const oldInfo = TASK_STATUS[oldStatus];
308
+ const newInfo = TASK_STATUS[newStatus];
309
+
310
+ utils.print.success(`Task #${taskId}: ${oldInfo.emoji} ${oldInfo.label} → ${newInfo.emoji} ${newInfo.label}`);
311
+ }
312
+
313
+ /**
314
+ * Advance task to next phase
315
+ */
316
+ function advancePhase(taskId) {
317
+ const cfg = config.load();
318
+ const projectRoot = cfg._projectRoot;
319
+ const data = loadTasks(projectRoot);
320
+
321
+ const task = data.tasks.find(t => t.id === parseInt(taskId));
322
+
323
+ if (!task) {
324
+ utils.print.error(`Task #${taskId} not found`);
325
+ return;
326
+ }
327
+
328
+ const currentIndex = LIFECYCLE_PHASES.findIndex(p => p.id === task.phase);
329
+
330
+ if (currentIndex === LIFECYCLE_PHASES.length - 1) {
331
+ utils.print.success(`Task #${taskId} is already at final phase`);
332
+ return;
333
+ }
334
+
335
+ const nextPhase = LIFECYCLE_PHASES[currentIndex + 1];
336
+ const oldPhase = task.phase;
337
+ task.phase = nextPhase.id;
338
+ task.updated = new Date().toISOString();
339
+
340
+ // Auto-update status based on phase
341
+ if (nextPhase.id === 'implement' && task.status === 'ready') {
342
+ task.status = 'in-progress';
343
+ } else if (nextPhase.id === 'review') {
344
+ task.status = 'review';
345
+ } else if (nextPhase.id === 'test') {
346
+ task.status = 'testing';
347
+ } else if (nextPhase.id === 'verify') {
348
+ task.status = 'done';
349
+ }
350
+
351
+ saveTasks(projectRoot, data);
352
+
353
+ utils.print.success(`Task #${taskId}: ${oldPhase} → ${nextPhase.id}`);
354
+ console.log(`${utils.COLORS.dim}${nextPhase.name}: ${nextPhase.description}${utils.COLORS.reset}`);
355
+ }
356
+
357
+ /**
358
+ * Add note to task
359
+ */
360
+ function addNote(taskId, noteContent) {
361
+ const cfg = config.load();
362
+ const projectRoot = cfg._projectRoot;
363
+ const data = loadTasks(projectRoot);
364
+
365
+ const task = data.tasks.find(t => t.id === parseInt(taskId));
366
+
367
+ if (!task) {
368
+ utils.print.error(`Task #${taskId} not found`);
369
+ return;
370
+ }
371
+
372
+ task.notes.push({
373
+ timestamp: new Date().toISOString(),
374
+ content: noteContent
375
+ });
376
+ task.updated = new Date().toISOString();
377
+
378
+ saveTasks(projectRoot, data);
379
+
380
+ utils.print.success(`Added note to task #${taskId}`);
381
+ }
382
+
383
+ /**
384
+ * Set task priority
385
+ */
386
+ function setPriority(taskId, priority) {
387
+ const cfg = config.load();
388
+ const projectRoot = cfg._projectRoot;
389
+ const data = loadTasks(projectRoot);
390
+
391
+ const task = data.tasks.find(t => t.id === parseInt(taskId));
392
+
393
+ if (!task) {
394
+ utils.print.error(`Task #${taskId} not found`);
395
+ return;
396
+ }
397
+
398
+ if (!TASK_PRIORITY[priority]) {
399
+ utils.print.error(`Invalid priority: ${priority}`);
400
+ utils.print.dim(`Valid priorities: ${Object.keys(TASK_PRIORITY).join(', ')}`);
401
+ return;
402
+ }
403
+
404
+ task.priority = priority;
405
+ task.updated = new Date().toISOString();
406
+
407
+ saveTasks(projectRoot, data);
408
+
409
+ const priorityInfo = TASK_PRIORITY[priority];
410
+ utils.print.success(`Task #${taskId} priority set to ${priority} (${priorityInfo.label})`);
411
+ }
412
+
413
+ /**
414
+ * Delete a task
415
+ */
416
+ function deleteTask(taskId) {
417
+ const cfg = config.load();
418
+ const projectRoot = cfg._projectRoot;
419
+ const data = loadTasks(projectRoot);
420
+
421
+ const taskIndex = data.tasks.findIndex(t => t.id === parseInt(taskId));
422
+
423
+ if (taskIndex === -1) {
424
+ utils.print.error(`Task #${taskId} not found`);
425
+ return;
426
+ }
427
+
428
+ const task = data.tasks[taskIndex];
429
+ data.tasks.splice(taskIndex, 1);
430
+
431
+ saveTasks(projectRoot, data);
432
+
433
+ utils.print.success(`Deleted task #${taskId}: ${task.title}`);
434
+ }
435
+
436
+ /**
437
+ * Show lifecycle phases
438
+ */
439
+ function showLifecycle() {
440
+ console.log(`
441
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Task Lifecycle Phases${utils.COLORS.reset}
442
+ ${utils.COLORS.dim}11-phase development workflow${utils.COLORS.reset}
443
+ `);
444
+
445
+ for (let i = 0; i < LIFECYCLE_PHASES.length; i++) {
446
+ const phase = LIFECYCLE_PHASES[i];
447
+ console.log(` ${utils.COLORS.cyan}${i + 1}. ${phase.name}${utils.COLORS.reset}`);
448
+ console.log(` ${utils.COLORS.dim}${phase.description}${utils.COLORS.reset}`);
449
+ }
450
+
451
+ console.log(`
452
+ ${utils.COLORS.bold}Usage${utils.COLORS.reset}
453
+ ${utils.COLORS.dim}bootspring task advance <id>${utils.COLORS.reset} Move to next phase
454
+ ${utils.COLORS.dim}bootspring task show <id>${utils.COLORS.reset} See current phase
455
+ `);
456
+ }
457
+
458
+ /**
459
+ * Show task help
460
+ */
461
+ function showHelp() {
462
+ console.log(`
463
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Task${utils.COLORS.reset}
464
+ ${utils.COLORS.dim}Manage development tasks with lifecycle tracking${utils.COLORS.reset}
465
+
466
+ ${utils.COLORS.bold}Usage:${utils.COLORS.reset}
467
+ bootspring task <command> [args] [options]
468
+
469
+ ${utils.COLORS.bold}Commands:${utils.COLORS.reset}
470
+ ${utils.COLORS.cyan}list${utils.COLORS.reset} List all tasks
471
+ ${utils.COLORS.cyan}create${utils.COLORS.reset} Create a new task
472
+ ${utils.COLORS.cyan}show${utils.COLORS.reset} <id> Show task details
473
+ ${utils.COLORS.cyan}status${utils.COLORS.reset} <id> <s> Update task status
474
+ ${utils.COLORS.cyan}advance${utils.COLORS.reset} <id> Advance to next phase
475
+ ${utils.COLORS.cyan}priority${utils.COLORS.reset} <id> <p> Set task priority
476
+ ${utils.COLORS.cyan}note${utils.COLORS.reset} <id> <text> Add note to task
477
+ ${utils.COLORS.cyan}delete${utils.COLORS.reset} <id> Delete a task
478
+ ${utils.COLORS.cyan}lifecycle${utils.COLORS.reset} Show lifecycle phases
479
+
480
+ ${utils.COLORS.bold}Options:${utils.COLORS.reset}
481
+ --title <title> Task title
482
+ --description <desc> Task description
483
+ --priority <P0-P3> Task priority
484
+ --status <status> Filter by status
485
+ --tag <tag> Filter by tag
486
+ --all Include completed tasks
487
+
488
+ ${utils.COLORS.bold}Statuses:${utils.COLORS.reset}
489
+ draft, ready, in-progress, review, testing, done, blocked
490
+
491
+ ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
492
+ bootspring task create --title "Add auth" --priority P1
493
+ bootspring task list --status in-progress
494
+ bootspring task status 1 in-progress
495
+ bootspring task advance 1
496
+ bootspring task note 1 "Completed API integration"
497
+ `);
498
+ }
499
+
500
+ /**
501
+ * Run task command
502
+ */
503
+ async function run(args) {
504
+ const parsedArgs = utils.parseArgs(args);
505
+ const subcommand = parsedArgs._[0] || 'list';
506
+ const subargs = parsedArgs._.slice(1);
507
+
508
+ switch (subcommand) {
509
+ case 'list':
510
+ case 'ls':
511
+ listTasks({
512
+ status: parsedArgs.status,
513
+ priority: parsedArgs.priority,
514
+ tag: parsedArgs.tag,
515
+ all: parsedArgs.all
516
+ });
517
+ break;
518
+
519
+ case 'create':
520
+ case 'new':
521
+ case 'add':
522
+ createTask({
523
+ title: parsedArgs.title || subargs.join(' '),
524
+ description: parsedArgs.description,
525
+ priority: parsedArgs.priority,
526
+ tags: parsedArgs.tags ? parsedArgs.tags.split(',') : [],
527
+ estimate: parsedArgs.estimate
528
+ });
529
+ break;
530
+
531
+ case 'show':
532
+ case 'view':
533
+ if (!subargs[0]) {
534
+ utils.print.error('Please specify a task ID');
535
+ utils.print.dim('Usage: bootspring task show <id>');
536
+ return;
537
+ }
538
+ showTask(subargs[0]);
539
+ break;
540
+
541
+ case 'status':
542
+ if (!subargs[0] || !subargs[1]) {
543
+ utils.print.error('Please specify task ID and new status');
544
+ utils.print.dim('Usage: bootspring task status <id> <status>');
545
+ return;
546
+ }
547
+ updateTaskStatus(subargs[0], subargs[1]);
548
+ break;
549
+
550
+ case 'advance':
551
+ case 'next':
552
+ if (!subargs[0]) {
553
+ utils.print.error('Please specify a task ID');
554
+ utils.print.dim('Usage: bootspring task advance <id>');
555
+ return;
556
+ }
557
+ advancePhase(subargs[0]);
558
+ break;
559
+
560
+ case 'priority':
561
+ case 'prio':
562
+ if (!subargs[0] || !subargs[1]) {
563
+ utils.print.error('Please specify task ID and priority');
564
+ utils.print.dim('Usage: bootspring task priority <id> <P0-P3>');
565
+ return;
566
+ }
567
+ setPriority(subargs[0], subargs[1].toUpperCase());
568
+ break;
569
+
570
+ case 'note': {
571
+ if (!subargs[0]) {
572
+ utils.print.error('Please specify a task ID');
573
+ utils.print.dim('Usage: bootspring task note <id> <text>');
574
+ return;
575
+ }
576
+ const noteContent = subargs.slice(1).join(' ') || parsedArgs.text;
577
+ if (!noteContent) {
578
+ utils.print.error('Please provide note content');
579
+ return;
580
+ }
581
+ addNote(subargs[0], noteContent);
582
+ break;
583
+ }
584
+
585
+ case 'delete':
586
+ case 'rm':
587
+ case 'remove':
588
+ if (!subargs[0]) {
589
+ utils.print.error('Please specify a task ID');
590
+ utils.print.dim('Usage: bootspring task delete <id>');
591
+ return;
592
+ }
593
+ deleteTask(subargs[0]);
594
+ break;
595
+
596
+ case 'lifecycle':
597
+ case 'phases':
598
+ showLifecycle();
599
+ break;
600
+
601
+ case 'help':
602
+ case '-h':
603
+ case '--help':
604
+ showHelp();
605
+ break;
606
+
607
+ default:
608
+ // Check if it's a task ID (number)
609
+ if (/^\d+$/.test(subcommand)) {
610
+ showTask(subcommand);
611
+ } else {
612
+ utils.print.error(`Unknown subcommand: ${subcommand}`);
613
+ showHelp();
614
+ }
615
+ }
616
+ }
617
+
618
+ module.exports = {
619
+ run,
620
+ createTask,
621
+ listTasks,
622
+ showTask,
623
+ updateTaskStatus,
624
+ advancePhase,
625
+ TASK_STATUS,
626
+ TASK_PRIORITY,
627
+ LIFECYCLE_PHASES
628
+ };