@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.
- package/bin/bootspring.js +5 -0
- package/cli/org.js +474 -0
- package/cli/preseed/index.js +16 -0
- package/cli/preseed/interactive.js +143 -0
- package/cli/preseed/templates.js +227 -0
- package/cli/preseed.js +9 -301
- package/cli/seed/builders/ai-context-builder.js +85 -0
- package/cli/seed/builders/index.js +13 -0
- package/cli/seed/builders/seed-builder.js +272 -0
- package/cli/seed/extractors/content-extractors.js +383 -0
- package/cli/seed/extractors/index.js +47 -0
- package/cli/seed/extractors/metadata-extractors.js +167 -0
- package/cli/seed/extractors/section-extractor.js +54 -0
- package/cli/seed/extractors/stack-extractors.js +228 -0
- package/cli/seed/index.js +18 -0
- package/cli/seed/utils/folder-structure.js +84 -0
- package/cli/seed/utils/index.js +11 -0
- package/cli/seed.js +23 -1074
- package/core/api-client.js +77 -0
- package/core/entitlements.js +36 -0
- package/core/organizations.js +223 -0
- package/core/policies.js +51 -6
- package/core/policy-matrix.js +303 -0
- package/core/project-context.js +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +3220 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/context-McpJQa_2.d.ts +5710 -0
- package/dist/core/index.d.ts +635 -0
- package/dist/core/index.js +2593 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index-QqbeEiDm.d.ts +857 -0
- package/dist/index-UiYCgwiH.d.ts +174 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +44228 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +41173 -0
- package/dist/mcp/index.js.map +1 -0
- package/generators/index.ts +82 -0
- package/intelligence/orchestrator/config/failure-signatures.js +48 -0
- package/intelligence/orchestrator/config/index.js +23 -0
- package/intelligence/orchestrator/config/pack-lifecycle.js +262 -0
- package/intelligence/orchestrator/config/phases.js +111 -0
- package/intelligence/orchestrator/config/remediation.js +150 -0
- package/intelligence/orchestrator/config/workflows.js +168 -0
- package/intelligence/orchestrator/core/index.js +16 -0
- package/intelligence/orchestrator/core/state-manager.js +88 -0
- package/intelligence/orchestrator/core/telemetry.js +24 -0
- package/intelligence/orchestrator/index.js +17 -0
- package/intelligence/orchestrator.js +17 -512
- package/mcp/contracts/mcp-contract.v1.json +1 -1
- package/package.json +16 -3
- package/src/cli/agent.ts +703 -0
- package/src/cli/analyze.ts +640 -0
- package/src/cli/audit.ts +707 -0
- package/src/cli/auth.ts +930 -0
- package/src/cli/billing.ts +364 -0
- package/src/cli/build.ts +1089 -0
- package/src/cli/business.ts +508 -0
- package/src/cli/checkpoint-utils.ts +236 -0
- package/src/cli/checkpoint.ts +757 -0
- package/src/cli/cloud-sync.ts +534 -0
- package/src/cli/content.ts +273 -0
- package/src/cli/context.ts +667 -0
- package/src/cli/dashboard.ts +133 -0
- package/src/cli/deploy.ts +704 -0
- package/src/cli/doctor.ts +480 -0
- package/src/cli/fundraise.ts +494 -0
- package/src/cli/generate.ts +346 -0
- package/src/cli/github-cmd.ts +566 -0
- package/src/cli/health.ts +599 -0
- package/src/cli/index.ts +113 -0
- package/src/cli/init.ts +838 -0
- package/src/cli/legal.ts +495 -0
- package/src/cli/log.ts +316 -0
- package/src/cli/loop.ts +1660 -0
- package/src/cli/manager.ts +878 -0
- package/src/cli/mcp.ts +275 -0
- package/src/cli/memory.ts +346 -0
- package/src/cli/metrics.ts +590 -0
- package/src/cli/monitor.ts +960 -0
- package/src/cli/mvp.ts +662 -0
- package/src/cli/onboard.ts +663 -0
- package/src/cli/orchestrator.ts +622 -0
- package/src/cli/plugin.ts +483 -0
- package/src/cli/prd.ts +671 -0
- package/src/cli/preseed-start.ts +1633 -0
- package/src/cli/preseed.ts +2434 -0
- package/src/cli/project.ts +526 -0
- package/src/cli/quality.ts +885 -0
- package/src/cli/security.ts +1079 -0
- package/src/cli/seed.ts +1224 -0
- package/src/cli/skill.ts +537 -0
- package/src/cli/suggest.ts +1225 -0
- package/src/cli/switch.ts +518 -0
- package/src/cli/task.ts +780 -0
- package/src/cli/telemetry.ts +172 -0
- package/src/cli/todo.ts +627 -0
- package/src/cli/types.ts +15 -0
- package/src/cli/update.ts +334 -0
- package/src/cli/visualize.ts +609 -0
- package/src/cli/watch.ts +895 -0
- package/src/cli/workspace.ts +709 -0
- package/src/core/action-recorder.ts +673 -0
- package/src/core/analyze-workflow.ts +1453 -0
- package/src/core/api-client.ts +1120 -0
- package/src/core/audit-workflow.ts +1681 -0
- package/src/core/auth.ts +471 -0
- package/src/core/build-orchestrator.ts +509 -0
- package/src/core/build-state.ts +621 -0
- package/src/core/checkpoint-engine.ts +482 -0
- package/src/core/config.ts +1285 -0
- package/src/core/context-loader.ts +694 -0
- package/src/core/context.ts +410 -0
- package/src/core/deploy-workflow.ts +1085 -0
- package/src/core/entitlements.ts +322 -0
- package/src/core/github-sync.ts +720 -0
- package/src/core/index.ts +981 -0
- package/src/core/ingest.ts +1186 -0
- package/src/core/metrics-engine.ts +886 -0
- package/src/core/mvp.ts +847 -0
- package/src/core/onboard-workflow.ts +1293 -0
- package/src/core/policies.ts +81 -0
- package/src/core/preseed-workflow.ts +1163 -0
- package/src/core/preseed.ts +1826 -0
- package/src/core/project-context.ts +380 -0
- package/src/core/project-state.ts +699 -0
- package/src/core/r2-sync.ts +691 -0
- package/src/core/scaffold.ts +1715 -0
- package/src/core/session.ts +286 -0
- package/src/core/task-extractor.ts +799 -0
- package/src/core/telemetry.ts +371 -0
- package/src/core/tier-enforcement.ts +737 -0
- package/src/core/utils.ts +437 -0
- package/src/index.ts +29 -0
- package/src/intelligence/agent-collab.ts +2376 -0
- package/src/intelligence/auto-suggest.ts +713 -0
- package/src/intelligence/content-gen.ts +1351 -0
- package/src/intelligence/cross-project.ts +1692 -0
- package/src/intelligence/git-memory.ts +529 -0
- package/src/intelligence/index.ts +318 -0
- package/src/intelligence/orchestrator.ts +534 -0
- package/src/intelligence/prd.ts +466 -0
- package/src/intelligence/recommendations.ts +982 -0
- package/src/intelligence/workflow-composer.ts +1472 -0
- package/src/mcp/capabilities.ts +233 -0
- package/src/mcp/index.ts +37 -0
- package/src/mcp/registry.ts +1268 -0
- package/src/mcp/response-formatter.ts +797 -0
- package/src/mcp/server.ts +240 -0
- package/src/types/agent.ts +69 -0
- package/src/types/config.ts +86 -0
- package/src/types/context.ts +77 -0
- package/src/types/index.ts +53 -0
- package/src/types/mcp.ts +91 -0
- package/src/types/skills.ts +47 -0
- package/src/types/workflow.ts +155 -0
- package/generators/index.js +0 -18
package/src/cli/task.ts
ADDED
|
@@ -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
|
+
}
|