@girardmedia/bootspring 2.0.21 → 2.0.23

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 (159) hide show
  1. package/bin/bootspring.js +5 -0
  2. package/cli/org.js +474 -0
  3. package/cli/preseed/index.js +16 -0
  4. package/cli/preseed/interactive.js +143 -0
  5. package/cli/preseed/templates.js +227 -0
  6. package/cli/preseed.js +9 -301
  7. package/cli/seed/builders/ai-context-builder.js +85 -0
  8. package/cli/seed/builders/index.js +13 -0
  9. package/cli/seed/builders/seed-builder.js +272 -0
  10. package/cli/seed/extractors/content-extractors.js +383 -0
  11. package/cli/seed/extractors/index.js +47 -0
  12. package/cli/seed/extractors/metadata-extractors.js +167 -0
  13. package/cli/seed/extractors/section-extractor.js +54 -0
  14. package/cli/seed/extractors/stack-extractors.js +228 -0
  15. package/cli/seed/index.js +18 -0
  16. package/cli/seed/utils/folder-structure.js +84 -0
  17. package/cli/seed/utils/index.js +11 -0
  18. package/cli/seed.js +23 -1074
  19. package/core/api-client.js +77 -0
  20. package/core/entitlements.js +36 -0
  21. package/core/organizations.js +223 -0
  22. package/core/policies.js +51 -6
  23. package/core/policy-matrix.js +303 -0
  24. package/core/project-context.js +1 -0
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.js +3220 -0
  27. package/dist/cli/index.js.map +1 -0
  28. package/dist/context-McpJQa_2.d.ts +5710 -0
  29. package/dist/core/index.d.ts +635 -0
  30. package/dist/core/index.js +2593 -0
  31. package/dist/core/index.js.map +1 -0
  32. package/dist/index-QqbeEiDm.d.ts +857 -0
  33. package/dist/index-UiYCgwiH.d.ts +174 -0
  34. package/dist/index.d.ts +453 -0
  35. package/dist/index.js +44228 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/mcp/index.d.ts +1 -0
  38. package/dist/mcp/index.js +41173 -0
  39. package/dist/mcp/index.js.map +1 -0
  40. package/generators/index.ts +82 -0
  41. package/intelligence/orchestrator/config/failure-signatures.js +48 -0
  42. package/intelligence/orchestrator/config/index.js +23 -0
  43. package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
  44. package/intelligence/orchestrator/config/phases.js +111 -0
  45. package/intelligence/orchestrator/config/remediation.js +150 -0
  46. package/intelligence/orchestrator/config/workflows.js +168 -0
  47. package/intelligence/orchestrator/core/index.js +16 -0
  48. package/intelligence/orchestrator/core/state-manager.js +88 -0
  49. package/intelligence/orchestrator/core/telemetry.js +24 -0
  50. package/intelligence/orchestrator/index.js +17 -0
  51. package/intelligence/orchestrator.js +17 -512
  52. package/mcp/contracts/mcp-contract.v1.json +1 -1
  53. package/package.json +16 -3
  54. package/src/cli/agent.ts +703 -0
  55. package/src/cli/analyze.ts +640 -0
  56. package/src/cli/audit.ts +707 -0
  57. package/src/cli/auth.ts +930 -0
  58. package/src/cli/billing.ts +364 -0
  59. package/src/cli/build.ts +1089 -0
  60. package/src/cli/business.ts +508 -0
  61. package/src/cli/checkpoint-utils.ts +236 -0
  62. package/src/cli/checkpoint.ts +757 -0
  63. package/src/cli/cloud-sync.ts +534 -0
  64. package/src/cli/content.ts +273 -0
  65. package/src/cli/context.ts +667 -0
  66. package/src/cli/dashboard.ts +133 -0
  67. package/src/cli/deploy.ts +704 -0
  68. package/src/cli/doctor.ts +480 -0
  69. package/src/cli/fundraise.ts +494 -0
  70. package/src/cli/generate.ts +346 -0
  71. package/src/cli/github-cmd.ts +566 -0
  72. package/src/cli/health.ts +599 -0
  73. package/src/cli/index.ts +113 -0
  74. package/src/cli/init.ts +838 -0
  75. package/src/cli/legal.ts +495 -0
  76. package/src/cli/log.ts +316 -0
  77. package/src/cli/loop.ts +1660 -0
  78. package/src/cli/manager.ts +878 -0
  79. package/src/cli/mcp.ts +275 -0
  80. package/src/cli/memory.ts +346 -0
  81. package/src/cli/metrics.ts +590 -0
  82. package/src/cli/monitor.ts +960 -0
  83. package/src/cli/mvp.ts +662 -0
  84. package/src/cli/onboard.ts +663 -0
  85. package/src/cli/orchestrator.ts +622 -0
  86. package/src/cli/plugin.ts +483 -0
  87. package/src/cli/prd.ts +671 -0
  88. package/src/cli/preseed-start.ts +1633 -0
  89. package/src/cli/preseed.ts +2434 -0
  90. package/src/cli/project.ts +526 -0
  91. package/src/cli/quality.ts +885 -0
  92. package/src/cli/security.ts +1079 -0
  93. package/src/cli/seed.ts +1224 -0
  94. package/src/cli/skill.ts +537 -0
  95. package/src/cli/suggest.ts +1225 -0
  96. package/src/cli/switch.ts +518 -0
  97. package/src/cli/task.ts +780 -0
  98. package/src/cli/telemetry.ts +172 -0
  99. package/src/cli/todo.ts +627 -0
  100. package/src/cli/types.ts +15 -0
  101. package/src/cli/update.ts +334 -0
  102. package/src/cli/visualize.ts +609 -0
  103. package/src/cli/watch.ts +895 -0
  104. package/src/cli/workspace.ts +709 -0
  105. package/src/core/action-recorder.ts +673 -0
  106. package/src/core/analyze-workflow.ts +1453 -0
  107. package/src/core/api-client.ts +1120 -0
  108. package/src/core/audit-workflow.ts +1681 -0
  109. package/src/core/auth.ts +471 -0
  110. package/src/core/build-orchestrator.ts +509 -0
  111. package/src/core/build-state.ts +621 -0
  112. package/src/core/checkpoint-engine.ts +482 -0
  113. package/src/core/config.ts +1285 -0
  114. package/src/core/context-loader.ts +694 -0
  115. package/src/core/context.ts +410 -0
  116. package/src/core/deploy-workflow.ts +1085 -0
  117. package/src/core/entitlements.ts +322 -0
  118. package/src/core/github-sync.ts +720 -0
  119. package/src/core/index.ts +981 -0
  120. package/src/core/ingest.ts +1186 -0
  121. package/src/core/metrics-engine.ts +886 -0
  122. package/src/core/mvp.ts +847 -0
  123. package/src/core/onboard-workflow.ts +1293 -0
  124. package/src/core/policies.ts +81 -0
  125. package/src/core/preseed-workflow.ts +1163 -0
  126. package/src/core/preseed.ts +1826 -0
  127. package/src/core/project-context.ts +380 -0
  128. package/src/core/project-state.ts +699 -0
  129. package/src/core/r2-sync.ts +691 -0
  130. package/src/core/scaffold.ts +1715 -0
  131. package/src/core/session.ts +286 -0
  132. package/src/core/task-extractor.ts +799 -0
  133. package/src/core/telemetry.ts +371 -0
  134. package/src/core/tier-enforcement.ts +737 -0
  135. package/src/core/utils.ts +437 -0
  136. package/src/index.ts +29 -0
  137. package/src/intelligence/agent-collab.ts +2376 -0
  138. package/src/intelligence/auto-suggest.ts +713 -0
  139. package/src/intelligence/content-gen.ts +1351 -0
  140. package/src/intelligence/cross-project.ts +1692 -0
  141. package/src/intelligence/git-memory.ts +529 -0
  142. package/src/intelligence/index.ts +318 -0
  143. package/src/intelligence/orchestrator.ts +534 -0
  144. package/src/intelligence/prd.ts +466 -0
  145. package/src/intelligence/recommendations.ts +982 -0
  146. package/src/intelligence/workflow-composer.ts +1472 -0
  147. package/src/mcp/capabilities.ts +233 -0
  148. package/src/mcp/index.ts +37 -0
  149. package/src/mcp/registry.ts +1268 -0
  150. package/src/mcp/response-formatter.ts +797 -0
  151. package/src/mcp/server.ts +240 -0
  152. package/src/types/agent.ts +69 -0
  153. package/src/types/config.ts +86 -0
  154. package/src/types/context.ts +77 -0
  155. package/src/types/index.ts +53 -0
  156. package/src/types/mcp.ts +91 -0
  157. package/src/types/skills.ts +47 -0
  158. package/src/types/workflow.ts +155 -0
  159. package/generators/index.js +0 -18
@@ -0,0 +1,780 @@
1
+ /**
2
+ * Bootspring Task Command
3
+ * Manage development tasks with lifecycle tracking
4
+ *
5
+ * @package bootspring
6
+ * @module cli/task
7
+ */
8
+
9
+ import * as path from 'path';
10
+ import * as fs from 'fs';
11
+
12
+ // Import JS modules with type interfaces
13
+ interface Config {
14
+ _projectRoot: string;
15
+ }
16
+
17
+ interface ConfigModule {
18
+ load(): Config;
19
+ }
20
+
21
+ interface ParsedArgs {
22
+ _: string[];
23
+ title?: string | undefined;
24
+ description?: string | undefined;
25
+ priority?: string | undefined;
26
+ status?: string | undefined;
27
+ tag?: string | undefined;
28
+ tags?: string | undefined;
29
+ estimate?: string | undefined;
30
+ all?: boolean | undefined;
31
+ text?: string | undefined;
32
+ }
33
+
34
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
35
+ const config = require('../../core/config') as ConfigModule;
36
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
37
+ const utils = require('../../core/utils') as typeof import('../core/utils');
38
+
39
+ /**
40
+ * Task status info
41
+ */
42
+ interface StatusInfo {
43
+ emoji: string;
44
+ label: string;
45
+ color: string;
46
+ }
47
+
48
+ /**
49
+ * Priority info
50
+ */
51
+ interface PriorityInfo {
52
+ label: string;
53
+ color: string;
54
+ }
55
+
56
+ /**
57
+ * Lifecycle phase
58
+ */
59
+ interface LifecyclePhase {
60
+ id: string;
61
+ name: string;
62
+ description: string;
63
+ }
64
+
65
+ /**
66
+ * Task note
67
+ */
68
+ interface TaskNote {
69
+ timestamp: string;
70
+ content: string;
71
+ }
72
+
73
+ /**
74
+ * Time tracking session
75
+ */
76
+ interface TimeSession {
77
+ start: string;
78
+ end?: string | undefined;
79
+ duration?: number | undefined;
80
+ }
81
+
82
+ /**
83
+ * Task time tracking
84
+ */
85
+ interface TimeTracking {
86
+ estimated: string | null;
87
+ actual: number;
88
+ sessions: TimeSession[];
89
+ }
90
+
91
+ /**
92
+ * Task definition
93
+ */
94
+ interface Task {
95
+ id: number;
96
+ title: string;
97
+ description: string;
98
+ status: string;
99
+ priority: string;
100
+ phase: string;
101
+ tags: string[];
102
+ assignee: string | null;
103
+ created: string;
104
+ updated: string;
105
+ blockedBy: number[];
106
+ blocks: number[];
107
+ notes: TaskNote[];
108
+ timeTracking: TimeTracking;
109
+ }
110
+
111
+ /**
112
+ * Tasks data file
113
+ */
114
+ interface TasksData {
115
+ tasks: Task[];
116
+ nextId: number;
117
+ }
118
+
119
+ /**
120
+ * Create task options
121
+ */
122
+ interface CreateTaskOptions {
123
+ title?: string | undefined;
124
+ description?: string | undefined;
125
+ priority?: string | undefined;
126
+ tags?: string[] | undefined;
127
+ estimate?: string | undefined;
128
+ assignee?: string | undefined;
129
+ }
130
+
131
+ /**
132
+ * List task options
133
+ */
134
+ interface ListTasksOptions {
135
+ status?: string | undefined;
136
+ priority?: string | undefined;
137
+ tag?: string | undefined;
138
+ all?: boolean | undefined;
139
+ }
140
+
141
+ /**
142
+ * Task statuses with emoji indicators
143
+ */
144
+ export const TASK_STATUS: Record<string, StatusInfo> = {
145
+ draft: { emoji: '📝', label: 'Draft', color: 'dim' },
146
+ ready: { emoji: '📋', label: 'Ready', color: 'cyan' },
147
+ 'in-progress': { emoji: '🔨', label: 'In Progress', color: 'yellow' },
148
+ review: { emoji: '👀', label: 'Review', color: 'magenta' },
149
+ testing: { emoji: '🧪', label: 'Testing', color: 'blue' },
150
+ done: { emoji: '✅', label: 'Done', color: 'green' },
151
+ blocked: { emoji: '🚫', label: 'Blocked', color: 'red' }
152
+ };
153
+
154
+ /**
155
+ * Task priorities
156
+ */
157
+ export const TASK_PRIORITY: Record<string, PriorityInfo> = {
158
+ P0: { label: 'Critical', color: 'red' },
159
+ P1: { label: 'High', color: 'yellow' },
160
+ P2: { label: 'Medium', color: 'cyan' },
161
+ P3: { label: 'Low', color: 'dim' }
162
+ };
163
+
164
+ /**
165
+ * Task lifecycle phases (11-phase workflow)
166
+ */
167
+ export const LIFECYCLE_PHASES: LifecyclePhase[] = [
168
+ { id: 'context', name: 'Context Gathering', description: 'Understand the task fully' },
169
+ { id: 'research', name: 'Research', description: 'Investigate options and approaches' },
170
+ { id: 'design', name: 'Design', description: 'Plan the implementation' },
171
+ { id: 'setup', name: 'Setup', description: 'Prepare environment and dependencies' },
172
+ { id: 'implement', name: 'Implementation', description: 'Write the code' },
173
+ { id: 'test', name: 'Testing', description: 'Write and run tests' },
174
+ { id: 'review', name: 'Review', description: 'Code review and feedback' },
175
+ { id: 'refine', name: 'Refinement', description: 'Address feedback and polish' },
176
+ { id: 'document', name: 'Documentation', description: 'Update docs and comments' },
177
+ { id: 'deploy', name: 'Deployment', description: 'Deploy to target environment' },
178
+ { id: 'verify', name: 'Verification', description: 'Verify in production' }
179
+ ];
180
+
181
+ /**
182
+ * Get tasks file path
183
+ */
184
+ function getTasksPath(projectRoot: string): string {
185
+ return path.join(projectRoot, '.bootspring', 'tasks.json');
186
+ }
187
+
188
+ /**
189
+ * Load tasks from file
190
+ */
191
+ function loadTasks(projectRoot: string): TasksData {
192
+ const tasksPath = getTasksPath(projectRoot);
193
+
194
+ if (!fs.existsSync(tasksPath)) {
195
+ return { tasks: [], nextId: 1 };
196
+ }
197
+
198
+ try {
199
+ const content = fs.readFileSync(tasksPath, 'utf-8');
200
+ return JSON.parse(content) as TasksData;
201
+ } catch {
202
+ return { tasks: [], nextId: 1 };
203
+ }
204
+ }
205
+
206
+ /**
207
+ * Save tasks to file
208
+ */
209
+ function saveTasks(projectRoot: string, data: TasksData): void {
210
+ const tasksPath = getTasksPath(projectRoot);
211
+ const dir = path.dirname(tasksPath);
212
+
213
+ if (!fs.existsSync(dir)) {
214
+ fs.mkdirSync(dir, { recursive: true });
215
+ }
216
+
217
+ fs.writeFileSync(tasksPath, JSON.stringify(data, null, 2));
218
+ }
219
+
220
+ /**
221
+ * Create a new task
222
+ */
223
+ export function createTask(options: CreateTaskOptions = {}): Task {
224
+ const cfg = config.load();
225
+ const projectRoot = cfg._projectRoot;
226
+ const data = loadTasks(projectRoot);
227
+
228
+ const task: Task = {
229
+ id: data.nextId,
230
+ title: options.title || 'Untitled Task',
231
+ description: options.description || '',
232
+ status: 'draft',
233
+ priority: options.priority || 'P2',
234
+ phase: 'context',
235
+ tags: options.tags || [],
236
+ assignee: options.assignee || null,
237
+ created: new Date().toISOString(),
238
+ updated: new Date().toISOString(),
239
+ blockedBy: [],
240
+ blocks: [],
241
+ notes: [],
242
+ timeTracking: {
243
+ estimated: options.estimate || null,
244
+ actual: 0,
245
+ sessions: []
246
+ }
247
+ };
248
+
249
+ data.tasks.push(task);
250
+ data.nextId++;
251
+ saveTasks(projectRoot, data);
252
+
253
+ const statusInfo = TASK_STATUS[task.status];
254
+ const priorityInfo = TASK_PRIORITY[task.priority];
255
+
256
+ utils.print.success(`Created task #${task.id}: ${task.title}`);
257
+ if (statusInfo) {
258
+ console.log(`${utils.COLORS.dim}Status: ${statusInfo.label}${utils.COLORS.reset}`);
259
+ }
260
+ if (priorityInfo) {
261
+ console.log(`${utils.COLORS.dim}Priority: ${priorityInfo.label}${utils.COLORS.reset}`);
262
+ }
263
+
264
+ return task;
265
+ }
266
+
267
+ /**
268
+ * List tasks
269
+ */
270
+ export function listTasks(options: ListTasksOptions = {}): void {
271
+ const cfg = config.load();
272
+ const projectRoot = cfg._projectRoot;
273
+ const data = loadTasks(projectRoot);
274
+
275
+ let tasks = data.tasks;
276
+
277
+ // Filter by status
278
+ if (options.status) {
279
+ tasks = tasks.filter(t => t.status === options.status);
280
+ }
281
+
282
+ // Filter by priority
283
+ if (options.priority) {
284
+ tasks = tasks.filter(t => t.priority === options.priority);
285
+ }
286
+
287
+ // Filter by tag
288
+ if (options.tag) {
289
+ tasks = tasks.filter(t => t.tags.includes(options.tag!));
290
+ }
291
+
292
+ // Exclude done unless requested
293
+ if (!options.all && !options.status) {
294
+ tasks = tasks.filter(t => t.status !== 'done');
295
+ }
296
+
297
+ console.log(`
298
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Tasks${utils.COLORS.reset}
299
+ ${utils.COLORS.dim}${tasks.length} task(s) found${utils.COLORS.reset}
300
+ `);
301
+
302
+ if (tasks.length === 0) {
303
+ utils.print.dim('No tasks found. Use "bootspring task create" to add one.');
304
+ return;
305
+ }
306
+
307
+ // Group by status
308
+ const grouped: Record<string, Task[]> = {};
309
+ for (const task of tasks) {
310
+ if (!grouped[task.status]) {
311
+ grouped[task.status] = [];
312
+ }
313
+ grouped[task.status]!.push(task);
314
+ }
315
+
316
+ for (const [status, statusTasks] of Object.entries(grouped)) {
317
+ const statusInfo = TASK_STATUS[status];
318
+ if (!statusInfo) continue;
319
+ console.log(`${statusInfo.emoji} ${utils.COLORS.bold}${statusInfo.label}${utils.COLORS.reset}`);
320
+
321
+ for (const task of statusTasks) {
322
+ const priorityInfo = TASK_PRIORITY[task.priority];
323
+ const priorityColor = priorityInfo ? (utils.COLORS as Record<string, string>)[priorityInfo.color] || '' : '';
324
+
325
+ console.log(` ${utils.COLORS.dim}#${task.id}${utils.COLORS.reset} ${task.title}`);
326
+ console.log(` ${priorityColor}${task.priority}${utils.COLORS.reset} ${utils.COLORS.dim}| Phase: ${task.phase}${utils.COLORS.reset}`);
327
+
328
+ if (task.tags.length > 0) {
329
+ console.log(` ${utils.COLORS.dim}Tags: ${task.tags.join(', ')}${utils.COLORS.reset}`);
330
+ }
331
+ }
332
+ console.log();
333
+ }
334
+ }
335
+
336
+ /**
337
+ * Show task details
338
+ */
339
+ export function showTask(taskId: string): void {
340
+ const cfg = config.load();
341
+ const projectRoot = cfg._projectRoot;
342
+ const data = loadTasks(projectRoot);
343
+
344
+ const task = data.tasks.find(t => t.id === parseInt(taskId));
345
+
346
+ if (!task) {
347
+ utils.print.error(`Task #${taskId} not found`);
348
+ return;
349
+ }
350
+
351
+ const statusInfo = TASK_STATUS[task.status];
352
+ const priorityInfo = TASK_PRIORITY[task.priority];
353
+ const phaseInfo = LIFECYCLE_PHASES.find(p => p.id === task.phase);
354
+
355
+ console.log(`
356
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Task #${task.id}${utils.COLORS.reset}
357
+ ${utils.COLORS.bold}${task.title}${utils.COLORS.reset}
358
+
359
+ ${utils.COLORS.bold}Status${utils.COLORS.reset}
360
+ ${statusInfo?.emoji || '?'} ${statusInfo?.label || task.status}
361
+ Priority: ${priorityInfo ? (utils.COLORS as Record<string, string>)[priorityInfo.color] || '' : ''}${task.priority}${utils.COLORS.reset} (${priorityInfo?.label || 'Unknown'})
362
+ Phase: ${task.phase} - ${phaseInfo?.name || 'Unknown'}
363
+
364
+ ${utils.COLORS.bold}Description${utils.COLORS.reset}
365
+ ${task.description || utils.COLORS.dim + 'No description' + utils.COLORS.reset}
366
+
367
+ ${utils.COLORS.bold}Details${utils.COLORS.reset}
368
+ Created: ${utils.formatRelativeTime(new Date(task.created))}
369
+ Updated: ${utils.formatRelativeTime(new Date(task.updated))}
370
+ ${task.assignee ? `Assignee: ${task.assignee}` : ''}
371
+ ${task.tags.length > 0 ? `Tags: ${task.tags.join(', ')}` : ''}
372
+ `);
373
+
374
+ // Show lifecycle progress
375
+ console.log(`${utils.COLORS.bold}Lifecycle Progress${utils.COLORS.reset}`);
376
+ const currentPhaseIndex = LIFECYCLE_PHASES.findIndex(p => p.id === task.phase);
377
+
378
+ for (let i = 0; i < LIFECYCLE_PHASES.length; i++) {
379
+ const phase = LIFECYCLE_PHASES[i];
380
+ if (!phase) continue;
381
+ let icon: string, color: string;
382
+
383
+ if (i < currentPhaseIndex) {
384
+ icon = '✓';
385
+ color = utils.COLORS.green;
386
+ } else if (i === currentPhaseIndex) {
387
+ icon = '●';
388
+ color = utils.COLORS.cyan;
389
+ } else {
390
+ icon = '○';
391
+ color = utils.COLORS.dim;
392
+ }
393
+
394
+ console.log(` ${color}${icon} ${phase.name}${utils.COLORS.reset}`);
395
+ }
396
+
397
+ // Show notes
398
+ if (task.notes.length > 0) {
399
+ console.log(`\n${utils.COLORS.bold}Notes${utils.COLORS.reset}`);
400
+ for (const note of task.notes.slice(-5)) {
401
+ console.log(` ${utils.COLORS.dim}${note.timestamp}${utils.COLORS.reset}`);
402
+ console.log(` ${note.content}`);
403
+ console.log();
404
+ }
405
+ }
406
+
407
+ // Show blocking info
408
+ if (task.blockedBy.length > 0) {
409
+ console.log(`${utils.COLORS.red}Blocked by: ${task.blockedBy.map(id => `#${id}`).join(', ')}${utils.COLORS.reset}`);
410
+ }
411
+ if (task.blocks.length > 0) {
412
+ console.log(`${utils.COLORS.yellow}Blocks: ${task.blocks.map(id => `#${id}`).join(', ')}${utils.COLORS.reset}`);
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Update task status
418
+ */
419
+ export function updateTaskStatus(taskId: string, newStatus: string): void {
420
+ const cfg = config.load();
421
+ const projectRoot = cfg._projectRoot;
422
+ const data = loadTasks(projectRoot);
423
+
424
+ const task = data.tasks.find(t => t.id === parseInt(taskId));
425
+
426
+ if (!task) {
427
+ utils.print.error(`Task #${taskId} not found`);
428
+ return;
429
+ }
430
+
431
+ if (!TASK_STATUS[newStatus]) {
432
+ utils.print.error(`Invalid status: ${newStatus}`);
433
+ utils.print.dim(`Valid statuses: ${Object.keys(TASK_STATUS).join(', ')}`);
434
+ return;
435
+ }
436
+
437
+ const oldStatus = task.status;
438
+ task.status = newStatus;
439
+ task.updated = new Date().toISOString();
440
+
441
+ saveTasks(projectRoot, data);
442
+
443
+ const oldInfo = TASK_STATUS[oldStatus];
444
+ const newInfo = TASK_STATUS[newStatus];
445
+
446
+ if (oldInfo && newInfo) {
447
+ utils.print.success(`Task #${taskId}: ${oldInfo.emoji} ${oldInfo.label} → ${newInfo.emoji} ${newInfo.label}`);
448
+ }
449
+ }
450
+
451
+ /**
452
+ * Advance task to next phase
453
+ */
454
+ export function advancePhase(taskId: string): void {
455
+ const cfg = config.load();
456
+ const projectRoot = cfg._projectRoot;
457
+ const data = loadTasks(projectRoot);
458
+
459
+ const task = data.tasks.find(t => t.id === parseInt(taskId));
460
+
461
+ if (!task) {
462
+ utils.print.error(`Task #${taskId} not found`);
463
+ return;
464
+ }
465
+
466
+ const currentIndex = LIFECYCLE_PHASES.findIndex(p => p.id === task.phase);
467
+
468
+ if (currentIndex === LIFECYCLE_PHASES.length - 1) {
469
+ utils.print.success(`Task #${taskId} is already at final phase`);
470
+ return;
471
+ }
472
+
473
+ const nextPhase = LIFECYCLE_PHASES[currentIndex + 1];
474
+ if (!nextPhase) {
475
+ utils.print.error(`Cannot advance phase`);
476
+ return;
477
+ }
478
+
479
+ const oldPhase = task.phase;
480
+ task.phase = nextPhase.id;
481
+ task.updated = new Date().toISOString();
482
+
483
+ // Auto-update status based on phase
484
+ if (nextPhase.id === 'implement' && task.status === 'ready') {
485
+ task.status = 'in-progress';
486
+ } else if (nextPhase.id === 'review') {
487
+ task.status = 'review';
488
+ } else if (nextPhase.id === 'test') {
489
+ task.status = 'testing';
490
+ } else if (nextPhase.id === 'verify') {
491
+ task.status = 'done';
492
+ }
493
+
494
+ saveTasks(projectRoot, data);
495
+
496
+ utils.print.success(`Task #${taskId}: ${oldPhase} → ${nextPhase.id}`);
497
+ console.log(`${utils.COLORS.dim}${nextPhase.name}: ${nextPhase.description}${utils.COLORS.reset}`);
498
+ }
499
+
500
+ /**
501
+ * Add note to task
502
+ */
503
+ function addNote(taskId: string, noteContent: string): void {
504
+ const cfg = config.load();
505
+ const projectRoot = cfg._projectRoot;
506
+ const data = loadTasks(projectRoot);
507
+
508
+ const task = data.tasks.find(t => t.id === parseInt(taskId));
509
+
510
+ if (!task) {
511
+ utils.print.error(`Task #${taskId} not found`);
512
+ return;
513
+ }
514
+
515
+ task.notes.push({
516
+ timestamp: new Date().toISOString(),
517
+ content: noteContent
518
+ });
519
+ task.updated = new Date().toISOString();
520
+
521
+ saveTasks(projectRoot, data);
522
+
523
+ utils.print.success(`Added note to task #${taskId}`);
524
+ }
525
+
526
+ /**
527
+ * Set task priority
528
+ */
529
+ function setPriority(taskId: string, priority: string): void {
530
+ const cfg = config.load();
531
+ const projectRoot = cfg._projectRoot;
532
+ const data = loadTasks(projectRoot);
533
+
534
+ const task = data.tasks.find(t => t.id === parseInt(taskId));
535
+
536
+ if (!task) {
537
+ utils.print.error(`Task #${taskId} not found`);
538
+ return;
539
+ }
540
+
541
+ if (!TASK_PRIORITY[priority]) {
542
+ utils.print.error(`Invalid priority: ${priority}`);
543
+ utils.print.dim(`Valid priorities: ${Object.keys(TASK_PRIORITY).join(', ')}`);
544
+ return;
545
+ }
546
+
547
+ task.priority = priority;
548
+ task.updated = new Date().toISOString();
549
+
550
+ saveTasks(projectRoot, data);
551
+
552
+ const priorityInfo = TASK_PRIORITY[priority];
553
+ if (priorityInfo) {
554
+ utils.print.success(`Task #${taskId} priority set to ${priority} (${priorityInfo.label})`);
555
+ }
556
+ }
557
+
558
+ /**
559
+ * Delete a task
560
+ */
561
+ function deleteTask(taskId: string): void {
562
+ const cfg = config.load();
563
+ const projectRoot = cfg._projectRoot;
564
+ const data = loadTasks(projectRoot);
565
+
566
+ const taskIndex = data.tasks.findIndex(t => t.id === parseInt(taskId));
567
+
568
+ if (taskIndex === -1) {
569
+ utils.print.error(`Task #${taskId} not found`);
570
+ return;
571
+ }
572
+
573
+ const task = data.tasks[taskIndex];
574
+ if (!task) {
575
+ utils.print.error(`Task #${taskId} not found`);
576
+ return;
577
+ }
578
+
579
+ data.tasks.splice(taskIndex, 1);
580
+
581
+ saveTasks(projectRoot, data);
582
+
583
+ utils.print.success(`Deleted task #${taskId}: ${task.title}`);
584
+ }
585
+
586
+ /**
587
+ * Show lifecycle phases
588
+ */
589
+ function showLifecycle(): void {
590
+ console.log(`
591
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Task Lifecycle Phases${utils.COLORS.reset}
592
+ ${utils.COLORS.dim}11-phase development workflow${utils.COLORS.reset}
593
+ `);
594
+
595
+ for (let i = 0; i < LIFECYCLE_PHASES.length; i++) {
596
+ const phase = LIFECYCLE_PHASES[i];
597
+ if (!phase) continue;
598
+ console.log(` ${utils.COLORS.cyan}${i + 1}. ${phase.name}${utils.COLORS.reset}`);
599
+ console.log(` ${utils.COLORS.dim}${phase.description}${utils.COLORS.reset}`);
600
+ }
601
+
602
+ console.log(`
603
+ ${utils.COLORS.bold}Usage${utils.COLORS.reset}
604
+ ${utils.COLORS.dim}bootspring task advance <id>${utils.COLORS.reset} Move to next phase
605
+ ${utils.COLORS.dim}bootspring task show <id>${utils.COLORS.reset} See current phase
606
+ `);
607
+ }
608
+
609
+ /**
610
+ * Show task help
611
+ */
612
+ function showHelp(): void {
613
+ console.log(`
614
+ ${utils.COLORS.cyan}${utils.COLORS.bold}⚡ Bootspring Task${utils.COLORS.reset}
615
+ ${utils.COLORS.dim}Manage development tasks with lifecycle tracking${utils.COLORS.reset}
616
+
617
+ ${utils.COLORS.bold}Usage:${utils.COLORS.reset}
618
+ bootspring task <command> [args] [options]
619
+
620
+ ${utils.COLORS.bold}Commands:${utils.COLORS.reset}
621
+ ${utils.COLORS.cyan}list${utils.COLORS.reset} List all tasks
622
+ ${utils.COLORS.cyan}create${utils.COLORS.reset} Create a new task
623
+ ${utils.COLORS.cyan}show${utils.COLORS.reset} <id> Show task details
624
+ ${utils.COLORS.cyan}status${utils.COLORS.reset} <id> <s> Update task status
625
+ ${utils.COLORS.cyan}advance${utils.COLORS.reset} <id> Advance to next phase
626
+ ${utils.COLORS.cyan}priority${utils.COLORS.reset} <id> <p> Set task priority
627
+ ${utils.COLORS.cyan}note${utils.COLORS.reset} <id> <text> Add note to task
628
+ ${utils.COLORS.cyan}delete${utils.COLORS.reset} <id> Delete a task
629
+ ${utils.COLORS.cyan}lifecycle${utils.COLORS.reset} Show lifecycle phases
630
+
631
+ ${utils.COLORS.bold}Options:${utils.COLORS.reset}
632
+ --title <title> Task title
633
+ --description <desc> Task description
634
+ --priority <P0-P3> Task priority
635
+ --status <status> Filter by status
636
+ --tag <tag> Filter by tag
637
+ --all Include completed tasks
638
+
639
+ ${utils.COLORS.bold}Statuses:${utils.COLORS.reset}
640
+ draft, ready, in-progress, review, testing, done, blocked
641
+
642
+ ${utils.COLORS.bold}Examples:${utils.COLORS.reset}
643
+ bootspring task create --title "Add auth" --priority P1
644
+ bootspring task list --status in-progress
645
+ bootspring task status 1 in-progress
646
+ bootspring task advance 1
647
+ bootspring task note 1 "Completed API integration"
648
+ `);
649
+ }
650
+
651
+ /**
652
+ * Run task command
653
+ */
654
+ export async function run(args: string[]): Promise<void> {
655
+ const parsedArgs = utils.parseArgs(args) as ParsedArgs;
656
+ const subcommand = parsedArgs._[0] || 'list';
657
+ const subargs = parsedArgs._.slice(1);
658
+
659
+ switch (subcommand) {
660
+ case 'list':
661
+ case 'ls':
662
+ listTasks({
663
+ status: parsedArgs.status,
664
+ priority: parsedArgs.priority,
665
+ tag: parsedArgs.tag,
666
+ all: parsedArgs.all
667
+ });
668
+ break;
669
+
670
+ case 'create':
671
+ case 'new':
672
+ case 'add':
673
+ createTask({
674
+ title: parsedArgs.title || subargs.join(' '),
675
+ description: parsedArgs.description,
676
+ priority: parsedArgs.priority,
677
+ tags: parsedArgs.tags ? parsedArgs.tags.split(',') : [],
678
+ estimate: parsedArgs.estimate
679
+ });
680
+ break;
681
+
682
+ case 'show':
683
+ case 'view': {
684
+ const showId = subargs[0];
685
+ if (!showId) {
686
+ utils.print.error('Please specify a task ID');
687
+ utils.print.dim('Usage: bootspring task show <id>');
688
+ return;
689
+ }
690
+ showTask(showId);
691
+ break;
692
+ }
693
+
694
+ case 'status': {
695
+ const statusId = subargs[0];
696
+ const statusValue = subargs[1];
697
+ if (!statusId || !statusValue) {
698
+ utils.print.error('Please specify task ID and new status');
699
+ utils.print.dim('Usage: bootspring task status <id> <status>');
700
+ return;
701
+ }
702
+ updateTaskStatus(statusId, statusValue);
703
+ break;
704
+ }
705
+
706
+ case 'advance':
707
+ case 'next': {
708
+ const advanceId = subargs[0];
709
+ if (!advanceId) {
710
+ utils.print.error('Please specify a task ID');
711
+ utils.print.dim('Usage: bootspring task advance <id>');
712
+ return;
713
+ }
714
+ advancePhase(advanceId);
715
+ break;
716
+ }
717
+
718
+ case 'priority':
719
+ case 'prio': {
720
+ const prioId = subargs[0];
721
+ const prioValue = subargs[1];
722
+ if (!prioId || !prioValue) {
723
+ utils.print.error('Please specify task ID and priority');
724
+ utils.print.dim('Usage: bootspring task priority <id> <P0-P3>');
725
+ return;
726
+ }
727
+ setPriority(prioId, prioValue.toUpperCase());
728
+ break;
729
+ }
730
+
731
+ case 'note': {
732
+ const noteId = subargs[0];
733
+ if (!noteId) {
734
+ utils.print.error('Please specify a task ID');
735
+ utils.print.dim('Usage: bootspring task note <id> <text>');
736
+ return;
737
+ }
738
+ const noteContent = subargs.slice(1).join(' ') || parsedArgs.text;
739
+ if (!noteContent) {
740
+ utils.print.error('Please provide note content');
741
+ return;
742
+ }
743
+ addNote(noteId, noteContent);
744
+ break;
745
+ }
746
+
747
+ case 'delete':
748
+ case 'rm':
749
+ case 'remove': {
750
+ const deleteId = subargs[0];
751
+ if (!deleteId) {
752
+ utils.print.error('Please specify a task ID');
753
+ utils.print.dim('Usage: bootspring task delete <id>');
754
+ return;
755
+ }
756
+ deleteTask(deleteId);
757
+ break;
758
+ }
759
+
760
+ case 'lifecycle':
761
+ case 'phases':
762
+ showLifecycle();
763
+ break;
764
+
765
+ case 'help':
766
+ case '-h':
767
+ case '--help':
768
+ showHelp();
769
+ break;
770
+
771
+ default:
772
+ // Check if it's a task ID (number)
773
+ if (/^\d+$/.test(subcommand)) {
774
+ showTask(subcommand);
775
+ } else {
776
+ utils.print.error(`Unknown subcommand: ${subcommand}`);
777
+ showHelp();
778
+ }
779
+ }
780
+ }