mulby-cli 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/PLUGIN_DEVELOP_PROMPT.md +1164 -0
  2. package/README.md +852 -0
  3. package/assets/default-icon.png +0 -0
  4. package/dist/commands/ai-session.js +44 -0
  5. package/dist/commands/build.js +111 -0
  6. package/dist/commands/config-ai.js +291 -0
  7. package/dist/commands/config.js +53 -0
  8. package/dist/commands/create/ai-create.js +183 -0
  9. package/dist/commands/create/assets.js +53 -0
  10. package/dist/commands/create/basic.js +72 -0
  11. package/dist/commands/create/index.js +73 -0
  12. package/dist/commands/create/react.js +136 -0
  13. package/dist/commands/create/templates/basic.js +383 -0
  14. package/dist/commands/create/templates/react/backend.js +72 -0
  15. package/dist/commands/create/templates/react/config.js +166 -0
  16. package/dist/commands/create/templates/react/docs.js +78 -0
  17. package/dist/commands/create/templates/react/hooks.js +469 -0
  18. package/dist/commands/create/templates/react/index.js +41 -0
  19. package/dist/commands/create/templates/react/types.js +1228 -0
  20. package/dist/commands/create/templates/react/ui.js +528 -0
  21. package/dist/commands/create/templates/react.js +1888 -0
  22. package/dist/commands/dev.js +141 -0
  23. package/dist/commands/pack.js +160 -0
  24. package/dist/commands/resume.js +97 -0
  25. package/dist/commands/test-ui.js +50 -0
  26. package/dist/index.js +71 -0
  27. package/dist/services/ai/PLUGIN_API.md +1102 -0
  28. package/dist/services/ai/PLUGIN_DEVELOP_PROMPT.md +1164 -0
  29. package/dist/services/ai/context-manager.js +639 -0
  30. package/dist/services/ai/index.js +88 -0
  31. package/dist/services/ai/knowledge.js +52 -0
  32. package/dist/services/ai/prompts.js +114 -0
  33. package/dist/services/ai/providers/base.js +38 -0
  34. package/dist/services/ai/providers/claude.js +284 -0
  35. package/dist/services/ai/providers/deepseek.js +28 -0
  36. package/dist/services/ai/providers/gemini.js +191 -0
  37. package/dist/services/ai/providers/glm.js +31 -0
  38. package/dist/services/ai/providers/minimax.js +27 -0
  39. package/dist/services/ai/providers/openai.js +177 -0
  40. package/dist/services/ai/tools.js +204 -0
  41. package/dist/services/ai-generator.js +968 -0
  42. package/dist/services/config-manager.js +117 -0
  43. package/dist/services/dependency-manager.js +236 -0
  44. package/dist/services/file-writer.js +66 -0
  45. package/dist/services/plan-adapter.js +244 -0
  46. package/dist/services/plan-command-handler.js +172 -0
  47. package/dist/services/plan-manager.js +502 -0
  48. package/dist/services/session-manager.js +113 -0
  49. package/dist/services/task-analyzer.js +136 -0
  50. package/dist/services/tui/index.js +57 -0
  51. package/dist/services/tui/store.js +123 -0
  52. package/dist/types/ai.js +172 -0
  53. package/dist/types/plan.js +2 -0
  54. package/dist/ui/Terminal.js +56 -0
  55. package/dist/ui/components/InputArea.js +176 -0
  56. package/dist/ui/components/LogArea.js +19 -0
  57. package/dist/ui/components/PlanPanel.js +69 -0
  58. package/dist/ui/components/SelectArea.js +13 -0
  59. package/package.json +45 -0
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PlanCommandHandler = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const tui_1 = require("./tui");
9
+ /**
10
+ * Simplified Plan Command Handler
11
+ *
12
+ * Usage:
13
+ * - /plan Show current plan status
14
+ * - /plan <需求> Force plan mode with requirement
15
+ *
16
+ * Everything else is automatic - AI manages the todo list
17
+ */
18
+ class PlanCommandHandler {
19
+ constructor(planManager, getCurrentPlan, setCurrentPlan, savePlan) {
20
+ this.planManager = planManager;
21
+ this.getCurrentPlan = getCurrentPlan;
22
+ this.setCurrentPlan = setCurrentPlan;
23
+ this.savePlan = savePlan;
24
+ }
25
+ /**
26
+ * Handle /plan command
27
+ * Returns: true if handled, false if should pass to AI
28
+ */
29
+ async handlePlanCommand(args) {
30
+ const requirement = args.join(' ').trim();
31
+ if (requirement) {
32
+ // /plan <requirement> - Force plan mode, pass to AI
33
+ tui_1.tui.log(chalk_1.default.cyan('📋 进入计划模式...'));
34
+ return { handled: false, requirement };
35
+ }
36
+ // /plan with no args - show current plan
37
+ const plan = this.getCurrentPlan();
38
+ if (!plan) {
39
+ tui_1.tui.log(chalk_1.default.gray('当前没有活动的计划。'));
40
+ tui_1.tui.log(chalk_1.default.gray('提示: 输入 /plan <你的需求> 来创建计划'));
41
+ return { handled: true };
42
+ }
43
+ this.displayPlan(plan);
44
+ return { handled: true };
45
+ }
46
+ /**
47
+ * Display plan with progress bar
48
+ */
49
+ displayPlan(plan) {
50
+ const summary = this.planManager.getProgressSummary(plan);
51
+ tui_1.tui.log(chalk_1.default.cyan(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`));
52
+ tui_1.tui.log(chalk_1.default.cyan(`📋 ${plan.goal}`));
53
+ tui_1.tui.log(chalk_1.default.cyan(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`));
54
+ // Progress bar
55
+ const progressBar = this.renderProgressBar(summary.percentage, 30);
56
+ tui_1.tui.log(`${progressBar} ${summary.completed}/${summary.total} (${Math.round(summary.percentage)}%)`);
57
+ tui_1.tui.log('');
58
+ // Task list
59
+ for (const task of plan.tasks) {
60
+ const icon = this.getStatusIcon(task.status);
61
+ const color = task.status === 'in_progress' ? chalk_1.default.yellow :
62
+ task.status === 'completed' ? chalk_1.default.green :
63
+ task.status === 'failed' ? chalk_1.default.red : chalk_1.default.gray;
64
+ tui_1.tui.log(color(` ${icon} ${task.title}`));
65
+ }
66
+ tui_1.tui.log(chalk_1.default.cyan(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`));
67
+ }
68
+ /**
69
+ * Display compact progress line (for status updates during execution)
70
+ */
71
+ displayProgress(plan) {
72
+ const summary = this.planManager.getProgressSummary(plan);
73
+ const currentTask = plan.tasks.find(t => t.status === 'in_progress');
74
+ const progressBar = this.renderProgressBar(summary.percentage, 15);
75
+ let status = `${progressBar} ${summary.completed}/${summary.total}`;
76
+ if (currentTask) {
77
+ status += chalk_1.default.gray(` | ${currentTask.title.slice(0, 30)}`);
78
+ }
79
+ tui_1.tui.log(chalk_1.default.cyan(`📋 ${status}`));
80
+ }
81
+ // --- Methods for AI to call automatically ---
82
+ /**
83
+ * Start a task (mark as in_progress)
84
+ */
85
+ async startTask(taskId) {
86
+ const plan = this.getCurrentPlan();
87
+ if (!plan)
88
+ return;
89
+ const task = plan.tasks.find(t => t.id === taskId);
90
+ if (task && task.status === 'pending') {
91
+ task.status = 'in_progress';
92
+ task.startedAt = new Date();
93
+ plan.status = 'in_progress';
94
+ await this.savePlan(plan);
95
+ this.displayProgress(plan);
96
+ }
97
+ }
98
+ /**
99
+ * Complete a task
100
+ */
101
+ async completeTask(taskId) {
102
+ const plan = this.getCurrentPlan();
103
+ if (!plan)
104
+ return;
105
+ const task = plan.tasks.find(t => t.id === taskId);
106
+ if (task) {
107
+ task.status = 'completed';
108
+ task.completedAt = new Date();
109
+ await this.savePlan(plan);
110
+ // Check if all tasks completed
111
+ const allDone = plan.tasks.every(t => t.status === 'completed');
112
+ if (allDone) {
113
+ plan.status = 'completed';
114
+ await this.savePlan(plan);
115
+ tui_1.tui.log(chalk_1.default.green('\n🎉 所有任务已完成!'));
116
+ this.displayPlan(plan);
117
+ }
118
+ else {
119
+ this.displayProgress(plan);
120
+ }
121
+ }
122
+ }
123
+ /**
124
+ * Mark task as failed
125
+ */
126
+ async failTask(taskId, error) {
127
+ const plan = this.getCurrentPlan();
128
+ if (!plan)
129
+ return;
130
+ const task = plan.tasks.find(t => t.id === taskId);
131
+ if (task) {
132
+ task.status = 'failed';
133
+ task.error = error;
134
+ await this.savePlan(plan);
135
+ tui_1.tui.log(chalk_1.default.red(`❌ 任务失败: ${task.title}`));
136
+ }
137
+ }
138
+ /**
139
+ * Get next pending task
140
+ */
141
+ getNextTask() {
142
+ const plan = this.getCurrentPlan();
143
+ if (!plan)
144
+ return null;
145
+ return plan.tasks.find(t => t.status === 'pending') || null;
146
+ }
147
+ /**
148
+ * Get current in-progress task
149
+ */
150
+ getCurrentTask() {
151
+ const plan = this.getCurrentPlan();
152
+ if (!plan)
153
+ return null;
154
+ return plan.tasks.find(t => t.status === 'in_progress') || null;
155
+ }
156
+ // --- Helper methods ---
157
+ getStatusIcon(status) {
158
+ switch (status) {
159
+ case 'completed': return '✅';
160
+ case 'in_progress': return '🔄';
161
+ case 'failed': return '❌';
162
+ case 'skipped': return '⏭️';
163
+ default: return '⏸️';
164
+ }
165
+ }
166
+ renderProgressBar(percentage, width) {
167
+ const filled = Math.round((percentage / 100) * width);
168
+ const empty = width - filled;
169
+ return chalk_1.default.green('█'.repeat(filled)) + chalk_1.default.gray('░'.repeat(empty));
170
+ }
171
+ }
172
+ exports.PlanCommandHandler = PlanCommandHandler;
@@ -0,0 +1,502 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PlanManager = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const uuid_1 = require("uuid");
10
+ /**
11
+ * Plan Manager - handles task plan creation, persistence, and management
12
+ */
13
+ class PlanManager {
14
+ constructor(baseDir = '.mulby') {
15
+ this.plansDir = path_1.default.join(baseDir, 'plans');
16
+ this.sessionsDir = path_1.default.join(baseDir, 'sessions');
17
+ this.ensureDirectories();
18
+ }
19
+ /**
20
+ * Ensure required directories exist
21
+ */
22
+ ensureDirectories() {
23
+ fs_extra_1.default.ensureDirSync(this.plansDir);
24
+ fs_extra_1.default.ensureDirSync(path_1.default.join(this.plansDir, 'templates'));
25
+ fs_extra_1.default.ensureDirSync(this.sessionsDir);
26
+ }
27
+ /**
28
+ * Create a new task plan
29
+ */
30
+ createPlan(goal, tasks) {
31
+ const plan = {
32
+ id: `plan-${Date.now()}-${(0, uuid_1.v4)().slice(0, 8)}`,
33
+ goal,
34
+ tasks: tasks.map((t, index) => ({
35
+ ...t,
36
+ id: `task-${index + 1}`,
37
+ createdAt: new Date()
38
+ })),
39
+ totalEstimatedTokens: tasks.reduce((sum, t) => sum + (t.estimatedTokens || 0), 0),
40
+ createdAt: new Date(),
41
+ updatedAt: new Date(),
42
+ status: 'draft',
43
+ currentTaskIndex: 0
44
+ };
45
+ return plan;
46
+ }
47
+ /**
48
+ * Save plan to file
49
+ */
50
+ async savePlan(plan, sessionId) {
51
+ const serializable = this.toSerializable(plan);
52
+ // Save to plans directory
53
+ const planPath = path_1.default.join(this.plansDir, `${plan.id}.json`);
54
+ await fs_extra_1.default.writeJson(planPath, serializable, { spaces: 2 });
55
+ // Also save to session directory if sessionId provided
56
+ if (sessionId) {
57
+ const sessionDir = path_1.default.join(this.sessionsDir, sessionId);
58
+ await fs_extra_1.default.ensureDir(sessionDir);
59
+ const sessionPlanPath = path_1.default.join(sessionDir, 'plan.json');
60
+ await fs_extra_1.default.writeJson(sessionPlanPath, serializable, { spaces: 2 });
61
+ }
62
+ }
63
+ /**
64
+ * Load plan from file
65
+ */
66
+ async loadPlan(planId) {
67
+ const planPath = path_1.default.join(this.plansDir, `${planId}.json`);
68
+ if (!await fs_extra_1.default.pathExists(planPath)) {
69
+ return null;
70
+ }
71
+ const serializable = await fs_extra_1.default.readJson(planPath);
72
+ return this.fromSerializable(serializable);
73
+ }
74
+ /**
75
+ * Load plan from session
76
+ */
77
+ async loadSessionPlan(sessionId) {
78
+ const sessionPlanPath = path_1.default.join(this.sessionsDir, sessionId, 'plan.json');
79
+ if (!await fs_extra_1.default.pathExists(sessionPlanPath)) {
80
+ return null;
81
+ }
82
+ const serializable = await fs_extra_1.default.readJson(sessionPlanPath);
83
+ return this.fromSerializable(serializable);
84
+ }
85
+ /**
86
+ * Update task status
87
+ */
88
+ updateTaskStatus(plan, taskId, status, error) {
89
+ const task = plan.tasks.find(t => t.id === taskId);
90
+ if (!task) {
91
+ throw new Error(`Task ${taskId} not found`);
92
+ }
93
+ task.status = status;
94
+ task.error = error;
95
+ if (status === 'in_progress' && !task.startedAt) {
96
+ task.startedAt = new Date();
97
+ }
98
+ if (status === 'completed' || status === 'failed' || status === 'skipped') {
99
+ task.completedAt = new Date();
100
+ }
101
+ plan.updatedAt = new Date();
102
+ // Update plan status
103
+ this.updatePlanStatus(plan);
104
+ return plan;
105
+ }
106
+ /**
107
+ * Update plan status based on task statuses
108
+ */
109
+ updatePlanStatus(plan) {
110
+ const allCompleted = plan.tasks.every(t => t.status === 'completed' || t.status === 'skipped');
111
+ const anyFailed = plan.tasks.some(t => t.status === 'failed');
112
+ const anyInProgress = plan.tasks.some(t => t.status === 'in_progress');
113
+ if (allCompleted) {
114
+ plan.status = 'completed';
115
+ }
116
+ else if (anyFailed) {
117
+ plan.status = 'failed';
118
+ }
119
+ else if (anyInProgress) {
120
+ plan.status = 'in_progress';
121
+ }
122
+ }
123
+ /**
124
+ * Get next pending task
125
+ */
126
+ getNextTask(plan) {
127
+ const completedTaskIds = new Set(plan.tasks
128
+ .filter(t => t.status === 'completed' || t.status === 'skipped')
129
+ .map(t => t.id));
130
+ // Find first pending task whose dependencies are satisfied
131
+ for (const task of plan.tasks) {
132
+ if (task.status !== 'pending')
133
+ continue;
134
+ const dependenciesSatisfied = task.dependencies.every(depId => completedTaskIds.has(depId));
135
+ if (dependenciesSatisfied) {
136
+ return task;
137
+ }
138
+ }
139
+ return null;
140
+ }
141
+ /**
142
+ * Calculate progress percentage
143
+ */
144
+ calculateProgress(plan) {
145
+ const total = plan.tasks.length;
146
+ if (total === 0)
147
+ return 0;
148
+ const completed = plan.tasks.filter(t => t.status === 'completed').length;
149
+ const inProgress = plan.tasks.filter(t => t.status === 'in_progress').length;
150
+ // In-progress tasks count as 0.5
151
+ return ((completed + inProgress * 0.5) / total) * 100;
152
+ }
153
+ /**
154
+ * Get progress summary
155
+ */
156
+ getProgressSummary(plan) {
157
+ const total = plan.tasks.length;
158
+ const completed = plan.tasks.filter(t => t.status === 'completed').length;
159
+ const inProgress = plan.tasks.filter(t => t.status === 'in_progress').length;
160
+ const pending = plan.tasks.filter(t => t.status === 'pending').length;
161
+ const failed = plan.tasks.filter(t => t.status === 'failed').length;
162
+ const skipped = plan.tasks.filter(t => t.status === 'skipped').length;
163
+ return {
164
+ total,
165
+ completed,
166
+ inProgress,
167
+ pending,
168
+ failed,
169
+ skipped,
170
+ percentage: this.calculateProgress(plan)
171
+ };
172
+ }
173
+ /**
174
+ * Add task to plan
175
+ */
176
+ addTask(plan, task, afterTaskId) {
177
+ const newTask = {
178
+ ...task,
179
+ id: `task-${plan.tasks.length + 1}`,
180
+ createdAt: new Date()
181
+ };
182
+ if (afterTaskId) {
183
+ const index = plan.tasks.findIndex(t => t.id === afterTaskId);
184
+ if (index !== -1) {
185
+ plan.tasks.splice(index + 1, 0, newTask);
186
+ }
187
+ else {
188
+ plan.tasks.push(newTask);
189
+ }
190
+ }
191
+ else {
192
+ plan.tasks.push(newTask);
193
+ }
194
+ plan.updatedAt = new Date();
195
+ return plan;
196
+ }
197
+ /**
198
+ * Remove task from plan
199
+ */
200
+ removeTask(plan, taskId) {
201
+ const index = plan.tasks.findIndex(t => t.id === taskId);
202
+ if (index === -1) {
203
+ throw new Error(`Task ${taskId} not found`);
204
+ }
205
+ // Check if other tasks depend on this task
206
+ const dependentTasks = plan.tasks.filter(t => t.dependencies.includes(taskId));
207
+ if (dependentTasks.length > 0) {
208
+ throw new Error(`Cannot remove task ${taskId}: ${dependentTasks.length} tasks depend on it`);
209
+ }
210
+ plan.tasks.splice(index, 1);
211
+ plan.updatedAt = new Date();
212
+ return plan;
213
+ }
214
+ /**
215
+ * List all plans
216
+ */
217
+ async listPlans() {
218
+ const files = await fs_extra_1.default.readdir(this.plansDir);
219
+ const planFiles = files.filter(f => f.endsWith('.json') && !f.startsWith('template-'));
220
+ const plans = [];
221
+ for (const file of planFiles) {
222
+ const planPath = path_1.default.join(this.plansDir, file);
223
+ const serializable = await fs_extra_1.default.readJson(planPath);
224
+ plans.push(this.fromSerializable(serializable));
225
+ }
226
+ return plans.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
227
+ }
228
+ /**
229
+ * Delete plan
230
+ */
231
+ async deletePlan(planId) {
232
+ const planPath = path_1.default.join(this.plansDir, `${planId}.json`);
233
+ if (await fs_extra_1.default.pathExists(planPath)) {
234
+ await fs_extra_1.default.remove(planPath);
235
+ }
236
+ }
237
+ // ========== Template System ==========
238
+ /**
239
+ * Save plan as a template
240
+ */
241
+ async saveTemplate(plan, templateName) {
242
+ const template = {
243
+ name: templateName,
244
+ description: plan.goal,
245
+ tasks: plan.tasks.map(t => ({
246
+ title: t.title,
247
+ description: t.description,
248
+ priority: t.priority,
249
+ dependencies: t.dependencies,
250
+ acceptanceCriteria: t.acceptanceCriteria,
251
+ files: t.files,
252
+ estimatedTokens: t.estimatedTokens
253
+ })),
254
+ createdAt: new Date().toISOString(),
255
+ tags: this.inferTags(plan)
256
+ };
257
+ const templatePath = path_1.default.join(this.plansDir, 'templates', `${templateName}.json`);
258
+ await fs_extra_1.default.writeJson(templatePath, template, { spaces: 2 });
259
+ }
260
+ /**
261
+ * Load a template and create a new plan from it
262
+ */
263
+ async loadTemplate(templateName, customGoal) {
264
+ const templatePath = path_1.default.join(this.plansDir, 'templates', `${templateName}.json`);
265
+ if (!await fs_extra_1.default.pathExists(templatePath)) {
266
+ return null;
267
+ }
268
+ const template = await fs_extra_1.default.readJson(templatePath);
269
+ const plan = this.createPlan(customGoal || template.description, template.tasks.map(t => ({
270
+ ...t,
271
+ status: 'pending'
272
+ })));
273
+ return plan;
274
+ }
275
+ /**
276
+ * List all available templates
277
+ */
278
+ async listTemplates() {
279
+ const templatesDir = path_1.default.join(this.plansDir, 'templates');
280
+ await fs_extra_1.default.ensureDir(templatesDir);
281
+ const files = await fs_extra_1.default.readdir(templatesDir);
282
+ const templates = [];
283
+ for (const file of files) {
284
+ if (file.endsWith('.json')) {
285
+ const templatePath = path_1.default.join(templatesDir, file);
286
+ const template = await fs_extra_1.default.readJson(templatePath);
287
+ templates.push(template);
288
+ }
289
+ }
290
+ return templates.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
291
+ }
292
+ /**
293
+ * Delete a template
294
+ */
295
+ async deleteTemplate(templateName) {
296
+ const templatePath = path_1.default.join(this.plansDir, 'templates', `${templateName}.json`);
297
+ if (await fs_extra_1.default.pathExists(templatePath)) {
298
+ await fs_extra_1.default.remove(templatePath);
299
+ return true;
300
+ }
301
+ return false;
302
+ }
303
+ /**
304
+ * Get built-in templates
305
+ */
306
+ getBuiltInTemplates() {
307
+ return [
308
+ {
309
+ name: 'feature',
310
+ description: 'Standard feature implementation workflow',
311
+ tasks: [
312
+ {
313
+ title: 'Analyze requirements',
314
+ description: 'Understand the feature requirements and identify affected components',
315
+ priority: 'high',
316
+ dependencies: [],
317
+ acceptanceCriteria: ['Requirements are clear', 'Affected files identified'],
318
+ files: []
319
+ },
320
+ {
321
+ title: 'Design solution',
322
+ description: 'Design the technical approach and data structures',
323
+ priority: 'high',
324
+ dependencies: ['task-1'],
325
+ acceptanceCriteria: ['Technical design documented', 'Edge cases considered'],
326
+ files: []
327
+ },
328
+ {
329
+ title: 'Implement core functionality',
330
+ description: 'Implement the main feature logic',
331
+ priority: 'high',
332
+ dependencies: ['task-2'],
333
+ acceptanceCriteria: ['Core functionality works', 'Code follows project conventions'],
334
+ files: []
335
+ },
336
+ {
337
+ title: 'Add tests',
338
+ description: 'Write unit and integration tests',
339
+ priority: 'medium',
340
+ dependencies: ['task-3'],
341
+ acceptanceCriteria: ['Tests cover main scenarios', 'All tests pass'],
342
+ files: []
343
+ },
344
+ {
345
+ title: 'Review and refine',
346
+ description: 'Code review, documentation, and final polish',
347
+ priority: 'low',
348
+ dependencies: ['task-4'],
349
+ acceptanceCriteria: ['Code is clean', 'Documentation updated'],
350
+ files: []
351
+ }
352
+ ],
353
+ createdAt: '2026-01-01T00:00:00Z',
354
+ tags: ['feature', 'standard']
355
+ },
356
+ {
357
+ name: 'bugfix',
358
+ description: 'Bug fix workflow',
359
+ tasks: [
360
+ {
361
+ title: 'Reproduce the bug',
362
+ description: 'Understand and consistently reproduce the issue',
363
+ priority: 'high',
364
+ dependencies: [],
365
+ acceptanceCriteria: ['Bug can be reproduced', 'Steps documented'],
366
+ files: []
367
+ },
368
+ {
369
+ title: 'Identify root cause',
370
+ description: 'Debug and find the source of the problem',
371
+ priority: 'high',
372
+ dependencies: ['task-1'],
373
+ acceptanceCriteria: ['Root cause identified', 'Fix approach determined'],
374
+ files: []
375
+ },
376
+ {
377
+ title: 'Implement fix',
378
+ description: 'Fix the bug with minimal code changes',
379
+ priority: 'high',
380
+ dependencies: ['task-2'],
381
+ acceptanceCriteria: ['Bug is fixed', 'No regressions introduced'],
382
+ files: []
383
+ },
384
+ {
385
+ title: 'Add regression test',
386
+ description: 'Write a test to prevent the bug from recurring',
387
+ priority: 'medium',
388
+ dependencies: ['task-3'],
389
+ acceptanceCriteria: ['Test covers the bug scenario', 'Test passes'],
390
+ files: []
391
+ }
392
+ ],
393
+ createdAt: '2026-01-01T00:00:00Z',
394
+ tags: ['bugfix', 'standard']
395
+ },
396
+ {
397
+ name: 'refactor',
398
+ description: 'Code refactoring workflow',
399
+ tasks: [
400
+ {
401
+ title: 'Analyze current code',
402
+ description: 'Understand the existing implementation and its issues',
403
+ priority: 'high',
404
+ dependencies: [],
405
+ acceptanceCriteria: ['Current structure understood', 'Pain points identified'],
406
+ files: []
407
+ },
408
+ {
409
+ title: 'Design new structure',
410
+ description: 'Plan the improved architecture',
411
+ priority: 'high',
412
+ dependencies: ['task-1'],
413
+ acceptanceCriteria: ['New design documented', 'Migration path clear'],
414
+ files: []
415
+ },
416
+ {
417
+ title: 'Ensure test coverage',
418
+ description: 'Add tests for existing functionality before refactoring',
419
+ priority: 'high',
420
+ dependencies: ['task-2'],
421
+ acceptanceCriteria: ['Critical paths tested', 'Tests pass'],
422
+ files: []
423
+ },
424
+ {
425
+ title: 'Refactor incrementally',
426
+ description: 'Apply changes in small, verifiable steps',
427
+ priority: 'high',
428
+ dependencies: ['task-3'],
429
+ acceptanceCriteria: ['Code improved', 'Tests still pass'],
430
+ files: []
431
+ },
432
+ {
433
+ title: 'Verify and clean up',
434
+ description: 'Final verification and removal of old code',
435
+ priority: 'medium',
436
+ dependencies: ['task-4'],
437
+ acceptanceCriteria: ['All tests pass', 'No dead code remains'],
438
+ files: []
439
+ }
440
+ ],
441
+ createdAt: '2026-01-01T00:00:00Z',
442
+ tags: ['refactor', 'standard']
443
+ }
444
+ ];
445
+ }
446
+ /**
447
+ * Infer tags from plan content
448
+ */
449
+ inferTags(plan) {
450
+ const tags = [];
451
+ const goalLower = plan.goal.toLowerCase();
452
+ if (goalLower.includes('fix') || goalLower.includes('bug')) {
453
+ tags.push('bugfix');
454
+ }
455
+ if (goalLower.includes('feature') || goalLower.includes('implement') || goalLower.includes('add')) {
456
+ tags.push('feature');
457
+ }
458
+ if (goalLower.includes('refactor') || goalLower.includes('improve') || goalLower.includes('optimize')) {
459
+ tags.push('refactor');
460
+ }
461
+ if (goalLower.includes('test')) {
462
+ tags.push('testing');
463
+ }
464
+ if (goalLower.includes('doc')) {
465
+ tags.push('documentation');
466
+ }
467
+ return tags;
468
+ }
469
+ /**
470
+ * Convert TaskPlan to serializable format
471
+ */
472
+ toSerializable(plan) {
473
+ return {
474
+ ...plan,
475
+ tasks: plan.tasks.map(t => ({
476
+ ...t,
477
+ createdAt: t.createdAt.toISOString(),
478
+ startedAt: t.startedAt?.toISOString(),
479
+ completedAt: t.completedAt?.toISOString()
480
+ })),
481
+ createdAt: plan.createdAt.toISOString(),
482
+ updatedAt: plan.updatedAt.toISOString()
483
+ };
484
+ }
485
+ /**
486
+ * Convert serializable format to TaskPlan
487
+ */
488
+ fromSerializable(serializable) {
489
+ return {
490
+ ...serializable,
491
+ tasks: serializable.tasks.map(t => ({
492
+ ...t,
493
+ createdAt: new Date(t.createdAt),
494
+ startedAt: t.startedAt ? new Date(t.startedAt) : undefined,
495
+ completedAt: t.completedAt ? new Date(t.completedAt) : undefined
496
+ })),
497
+ createdAt: new Date(serializable.createdAt),
498
+ updatedAt: new Date(serializable.updatedAt)
499
+ };
500
+ }
501
+ }
502
+ exports.PlanManager = PlanManager;