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,117 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ConfigManager = void 0;
37
+ const fs = __importStar(require("fs-extra"));
38
+ const path = __importStar(require("path"));
39
+ const os = __importStar(require("os"));
40
+ const CONFIG_DIR = path.join(os.homedir(), '.mulby');
41
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
42
+ class ConfigManager {
43
+ constructor() {
44
+ this.config = {};
45
+ this.load();
46
+ }
47
+ static getInstance() {
48
+ if (!ConfigManager.instance) {
49
+ ConfigManager.instance = new ConfigManager();
50
+ }
51
+ return ConfigManager.instance;
52
+ }
53
+ load() {
54
+ try {
55
+ if (fs.existsSync(CONFIG_FILE)) {
56
+ this.config = fs.readJsonSync(CONFIG_FILE);
57
+ }
58
+ }
59
+ catch (error) {
60
+ console.error('加载配置文件失败:', error);
61
+ this.config = {};
62
+ }
63
+ }
64
+ save() {
65
+ try {
66
+ fs.ensureDirSync(CONFIG_DIR);
67
+ fs.writeJsonSync(CONFIG_FILE, this.config, { spaces: 2 });
68
+ }
69
+ catch (error) {
70
+ console.error('保存配置文件失败:', error);
71
+ }
72
+ }
73
+ get(key) {
74
+ const keys = key.split('.');
75
+ let current = this.config;
76
+ for (const k of keys) {
77
+ if (current === undefined || current === null)
78
+ return undefined;
79
+ current = current[k];
80
+ }
81
+ // Automatically convert numeric strings to numbers for specific keys
82
+ if (typeof current === 'string' && (key === 'ai.maxTokens' || key === 'ai.timeout' || key === 'ai.maxRetries')) {
83
+ const num = Number(current);
84
+ return (isNaN(num) ? current : num);
85
+ }
86
+ return current;
87
+ }
88
+ set(key, value) {
89
+ const keys = key.split('.');
90
+ let current = this.config;
91
+ for (let i = 0; i < keys.length - 1; i++) {
92
+ const k = keys[i];
93
+ if (current[k] === undefined) {
94
+ current[k] = {};
95
+ }
96
+ current = current[k];
97
+ }
98
+ current[keys[keys.length - 1]] = value;
99
+ this.save();
100
+ }
101
+ delete(key) {
102
+ const keys = key.split('.');
103
+ let current = this.config;
104
+ for (let i = 0; i < keys.length - 1; i++) {
105
+ const k = keys[i];
106
+ if (current[k] === undefined)
107
+ return; // Path doesn't exist
108
+ current = current[k];
109
+ }
110
+ delete current[keys[keys.length - 1]];
111
+ this.save();
112
+ }
113
+ getAll() {
114
+ return { ...this.config };
115
+ }
116
+ }
117
+ exports.ConfigManager = ConfigManager;
@@ -0,0 +1,236 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DependencyManager = void 0;
4
+ /**
5
+ * Dependency Manager - handles task dependency analysis and management
6
+ */
7
+ class DependencyManager {
8
+ /**
9
+ * Check if a task can be executed (all dependencies are satisfied)
10
+ */
11
+ canExecute(task, completedTasks) {
12
+ return task.dependencies.every(depId => completedTasks.has(depId));
13
+ }
14
+ /**
15
+ * Get all tasks that can be executed in parallel
16
+ */
17
+ getParallelTasks(tasks, completedTasks) {
18
+ return tasks.filter(t => t.status === 'pending' &&
19
+ this.canExecute(t, completedTasks));
20
+ }
21
+ /**
22
+ * Detect cyclic dependencies using DFS
23
+ * Returns the cycle path if found, null otherwise
24
+ */
25
+ detectCyclicDependency(tasks) {
26
+ const taskMap = new Map();
27
+ tasks.forEach(t => taskMap.set(t.id, t));
28
+ const visited = new Set();
29
+ const recursionStack = new Set();
30
+ const path = [];
31
+ const dfs = (taskId) => {
32
+ visited.add(taskId);
33
+ recursionStack.add(taskId);
34
+ path.push(taskId);
35
+ const task = taskMap.get(taskId);
36
+ if (task) {
37
+ for (const depId of task.dependencies) {
38
+ if (!visited.has(depId)) {
39
+ const cycle = dfs(depId);
40
+ if (cycle)
41
+ return cycle;
42
+ }
43
+ else if (recursionStack.has(depId)) {
44
+ // Found a cycle
45
+ const cycleStart = path.indexOf(depId);
46
+ return path.slice(cycleStart).concat(depId);
47
+ }
48
+ }
49
+ }
50
+ path.pop();
51
+ recursionStack.delete(taskId);
52
+ return null;
53
+ };
54
+ for (const task of tasks) {
55
+ if (!visited.has(task.id)) {
56
+ const cycle = dfs(task.id);
57
+ if (cycle)
58
+ return cycle;
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+ /**
64
+ * Topological sort of tasks based on dependencies
65
+ * Returns sorted task IDs or null if cycle detected
66
+ */
67
+ topologicalSort(tasks) {
68
+ const cycle = this.detectCyclicDependency(tasks);
69
+ if (cycle)
70
+ return null;
71
+ const taskMap = new Map();
72
+ tasks.forEach(t => taskMap.set(t.id, t));
73
+ const visited = new Set();
74
+ const result = [];
75
+ const visit = (taskId) => {
76
+ if (visited.has(taskId))
77
+ return;
78
+ visited.add(taskId);
79
+ const task = taskMap.get(taskId);
80
+ if (task) {
81
+ for (const depId of task.dependencies) {
82
+ visit(depId);
83
+ }
84
+ }
85
+ result.push(taskId);
86
+ };
87
+ for (const task of tasks) {
88
+ visit(task.id);
89
+ }
90
+ return result;
91
+ }
92
+ /**
93
+ * Get all tasks that depend on a given task (direct and indirect)
94
+ */
95
+ getDependentTasks(tasks, taskId) {
96
+ const dependents = [];
97
+ const visited = new Set();
98
+ const findDependents = (id) => {
99
+ for (const task of tasks) {
100
+ if (task.dependencies.includes(id) && !visited.has(task.id)) {
101
+ visited.add(task.id);
102
+ dependents.push(task);
103
+ findDependents(task.id);
104
+ }
105
+ }
106
+ };
107
+ findDependents(taskId);
108
+ return dependents;
109
+ }
110
+ /**
111
+ * Get all tasks that a given task depends on (direct and indirect)
112
+ */
113
+ getPrerequisiteTasks(tasks, taskId) {
114
+ const taskMap = new Map();
115
+ tasks.forEach(t => taskMap.set(t.id, t));
116
+ const prerequisites = [];
117
+ const visited = new Set();
118
+ const findPrerequisites = (id) => {
119
+ const task = taskMap.get(id);
120
+ if (!task)
121
+ return;
122
+ for (const depId of task.dependencies) {
123
+ if (!visited.has(depId)) {
124
+ visited.add(depId);
125
+ const depTask = taskMap.get(depId);
126
+ if (depTask) {
127
+ prerequisites.push(depTask);
128
+ findPrerequisites(depId);
129
+ }
130
+ }
131
+ }
132
+ };
133
+ findPrerequisites(taskId);
134
+ return prerequisites;
135
+ }
136
+ /**
137
+ * Calculate the critical path (longest dependency chain)
138
+ */
139
+ getCriticalPath(tasks) {
140
+ const taskMap = new Map();
141
+ tasks.forEach(t => taskMap.set(t.id, t));
142
+ const memo = new Map();
143
+ const getLongestPath = (taskId) => {
144
+ if (memo.has(taskId))
145
+ return memo.get(taskId);
146
+ const task = taskMap.get(taskId);
147
+ if (!task)
148
+ return [];
149
+ if (task.dependencies.length === 0) {
150
+ const path = [task];
151
+ memo.set(taskId, path);
152
+ return path;
153
+ }
154
+ let longestDepPath = [];
155
+ for (const depId of task.dependencies) {
156
+ const depPath = getLongestPath(depId);
157
+ if (depPath.length > longestDepPath.length) {
158
+ longestDepPath = depPath;
159
+ }
160
+ }
161
+ const path = [...longestDepPath, task];
162
+ memo.set(taskId, path);
163
+ return path;
164
+ };
165
+ let criticalPath = [];
166
+ for (const task of tasks) {
167
+ const path = getLongestPath(task.id);
168
+ if (path.length > criticalPath.length) {
169
+ criticalPath = path;
170
+ }
171
+ }
172
+ return criticalPath;
173
+ }
174
+ /**
175
+ * Generate dependency visualization (ASCII tree)
176
+ */
177
+ visualizeDependencies(tasks) {
178
+ const lines = [];
179
+ const taskMap = new Map();
180
+ tasks.forEach(t => taskMap.set(t.id, t));
181
+ // Find root tasks (no dependencies)
182
+ const rootTasks = tasks.filter(t => t.dependencies.length === 0);
183
+ const renderTask = (task, prefix, isLast) => {
184
+ const connector = isLast ? '└── ' : '├── ';
185
+ const statusIcon = this.getStatusIcon(task.status);
186
+ lines.push(`${prefix}${connector}${statusIcon} ${task.title}`);
187
+ // Find tasks that depend on this one
188
+ const children = tasks.filter(t => t.dependencies.includes(task.id));
189
+ const childPrefix = prefix + (isLast ? ' ' : '│ ');
190
+ children.forEach((child, idx) => {
191
+ renderTask(child, childPrefix, idx === children.length - 1);
192
+ });
193
+ };
194
+ lines.push('Dependency Tree:');
195
+ rootTasks.forEach((task, idx) => {
196
+ renderTask(task, '', idx === rootTasks.length - 1);
197
+ });
198
+ return lines.join('\n');
199
+ }
200
+ /**
201
+ * Validate dependencies (check for invalid references)
202
+ */
203
+ validateDependencies(tasks) {
204
+ const taskIds = new Set(tasks.map(t => t.id));
205
+ const errors = [];
206
+ for (const task of tasks) {
207
+ for (const depId of task.dependencies) {
208
+ if (!taskIds.has(depId)) {
209
+ errors.push(`Task "${task.title}" references non-existent dependency: ${depId}`);
210
+ }
211
+ if (depId === task.id) {
212
+ errors.push(`Task "${task.title}" has self-dependency`);
213
+ }
214
+ }
215
+ }
216
+ const cycle = this.detectCyclicDependency(tasks);
217
+ if (cycle) {
218
+ errors.push(`Cyclic dependency detected: ${cycle.join(' -> ')}`);
219
+ }
220
+ return {
221
+ valid: errors.length === 0,
222
+ errors
223
+ };
224
+ }
225
+ getStatusIcon(status) {
226
+ switch (status) {
227
+ case 'completed': return '✅';
228
+ case 'in_progress': return '🔄';
229
+ case 'pending': return '⏸️';
230
+ case 'failed': return '❌';
231
+ case 'skipped': return '⏭️';
232
+ default: return ' ';
233
+ }
234
+ }
235
+ }
236
+ exports.DependencyManager = DependencyManager;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.FileWriter = void 0;
37
+ const fs = __importStar(require("fs-extra"));
38
+ const path = __importStar(require("path"));
39
+ class FileWriter {
40
+ constructor(basePath) {
41
+ this.basePath = basePath;
42
+ }
43
+ async writeFile(relativePath, content) {
44
+ this.validatePath(relativePath);
45
+ const fullPath = path.join(this.basePath, relativePath);
46
+ await fs.ensureDir(path.dirname(fullPath));
47
+ // 简单的原子写入模拟:写 .tmp -> rename
48
+ const tmpPath = `${fullPath}.tmp`;
49
+ await fs.writeFile(tmpPath, content, 'utf-8');
50
+ await fs.move(tmpPath, fullPath, { overwrite: true });
51
+ }
52
+ validatePath(relativePath) {
53
+ const fullPath = path.resolve(this.basePath, relativePath);
54
+ if (!fullPath.startsWith(path.resolve(this.basePath))) {
55
+ throw new Error(`非法路径尝试: ${relativePath}`);
56
+ }
57
+ }
58
+ async exists(relativePath) {
59
+ const fullPath = path.join(this.basePath, relativePath);
60
+ return fs.pathExists(fullPath);
61
+ }
62
+ getBasePath() {
63
+ return this.basePath;
64
+ }
65
+ }
66
+ exports.FileWriter = FileWriter;
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PlanAdapter = void 0;
4
+ const dependency_manager_1 = require("./dependency-manager");
5
+ /**
6
+ * Plan Adapter - handles dynamic plan adjustments
7
+ */
8
+ class PlanAdapter {
9
+ constructor() {
10
+ this.dependencyManager = new dependency_manager_1.DependencyManager();
11
+ }
12
+ /**
13
+ * Handle task failure and generate recovery tasks
14
+ */
15
+ onTaskFailed(plan, task, error) {
16
+ const newTasks = [];
17
+ // 1. Create a fix task
18
+ const fixTask = {
19
+ id: `fix-${task.id}-${Date.now()}`,
20
+ title: `Fix: ${task.title}`,
21
+ description: `Resolve the error encountered in task "${task.title}": ${error}`,
22
+ status: 'pending',
23
+ priority: 'high',
24
+ dependencies: [],
25
+ acceptanceCriteria: [
26
+ 'Error is identified and understood',
27
+ 'Fix is implemented',
28
+ 'Original task can be retried'
29
+ ],
30
+ files: task.files,
31
+ createdAt: new Date()
32
+ };
33
+ newTasks.push(fixTask);
34
+ // 2. Create a retry task that depends on the fix
35
+ const retryTask = {
36
+ id: `retry-${task.id}-${Date.now()}`,
37
+ title: `Retry: ${task.title}`,
38
+ description: task.description,
39
+ status: 'pending',
40
+ priority: task.priority,
41
+ dependencies: [fixTask.id],
42
+ acceptanceCriteria: task.acceptanceCriteria,
43
+ files: task.files,
44
+ createdAt: new Date()
45
+ };
46
+ newTasks.push(retryTask);
47
+ // 3. Update dependent tasks to point to the retry task
48
+ const dependentTasks = this.dependencyManager.getDependentTasks(plan.tasks, task.id);
49
+ for (const depTask of dependentTasks) {
50
+ const idx = depTask.dependencies.indexOf(task.id);
51
+ if (idx !== -1) {
52
+ depTask.dependencies[idx] = retryTask.id;
53
+ }
54
+ }
55
+ return newTasks;
56
+ }
57
+ /**
58
+ * Analyze failure and suggest recovery strategy
59
+ */
60
+ analyzeFailure(task, error) {
61
+ const errorLower = error.toLowerCase();
62
+ // Categorize error types
63
+ let category = 'unknown';
64
+ let suggestions = [];
65
+ let autoRecoverable = false;
66
+ if (errorLower.includes('not found') || errorLower.includes('no such file')) {
67
+ category = 'missing_resource';
68
+ suggestions = [
69
+ 'Check if the file/resource was created in a previous task',
70
+ 'Verify file paths are correct',
71
+ 'Create missing resources first'
72
+ ];
73
+ autoRecoverable = true;
74
+ }
75
+ else if (errorLower.includes('permission') || errorLower.includes('access denied')) {
76
+ category = 'permission';
77
+ suggestions = [
78
+ 'Check file/directory permissions',
79
+ 'Run with elevated privileges if needed',
80
+ 'Verify user has write access'
81
+ ];
82
+ }
83
+ else if (errorLower.includes('timeout') || errorLower.includes('timed out')) {
84
+ category = 'timeout';
85
+ suggestions = [
86
+ 'Increase timeout duration',
87
+ 'Check network connectivity',
88
+ 'Retry the operation'
89
+ ];
90
+ autoRecoverable = true;
91
+ }
92
+ else if (errorLower.includes('syntax') || errorLower.includes('parse')) {
93
+ category = 'syntax_error';
94
+ suggestions = [
95
+ 'Check code syntax',
96
+ 'Validate configuration files',
97
+ 'Review recent changes'
98
+ ];
99
+ }
100
+ else if (errorLower.includes('dependency') || errorLower.includes('module not found')) {
101
+ category = 'dependency';
102
+ suggestions = [
103
+ 'Install missing dependencies',
104
+ 'Check package.json',
105
+ 'Run npm/yarn install'
106
+ ];
107
+ autoRecoverable = true;
108
+ }
109
+ else if (errorLower.includes('type') || errorLower.includes('typescript')) {
110
+ category = 'type_error';
111
+ suggestions = [
112
+ 'Fix TypeScript type errors',
113
+ 'Check interface definitions',
114
+ 'Verify function signatures'
115
+ ];
116
+ }
117
+ return {
118
+ category,
119
+ originalError: error,
120
+ suggestions,
121
+ autoRecoverable,
122
+ affectedTasks: this.dependencyManager.getDependentTasks([], task.id).map(t => t.id)
123
+ };
124
+ }
125
+ /**
126
+ * Reorder tasks based on priority and dependencies
127
+ */
128
+ optimizeTaskOrder(tasks) {
129
+ // First, ensure topological order
130
+ const sorted = this.dependencyManager.topologicalSort(tasks);
131
+ if (!sorted) {
132
+ // Cycle detected, return original order
133
+ return tasks;
134
+ }
135
+ const taskMap = new Map();
136
+ tasks.forEach(t => taskMap.set(t.id, t));
137
+ // Group by dependency level
138
+ const levels = [];
139
+ const assigned = new Set();
140
+ while (assigned.size < tasks.length) {
141
+ const currentLevel = [];
142
+ for (const taskId of sorted) {
143
+ if (assigned.has(taskId))
144
+ continue;
145
+ const task = taskMap.get(taskId);
146
+ const depsCompleted = task.dependencies.every(d => assigned.has(d));
147
+ if (depsCompleted) {
148
+ currentLevel.push(task);
149
+ }
150
+ }
151
+ // Sort current level by priority
152
+ currentLevel.sort((a, b) => {
153
+ const priorityOrder = { high: 0, medium: 1, low: 2 };
154
+ return priorityOrder[a.priority] - priorityOrder[b.priority];
155
+ });
156
+ currentLevel.forEach(t => assigned.add(t.id));
157
+ levels.push(currentLevel);
158
+ }
159
+ return levels.flat();
160
+ }
161
+ /**
162
+ * Suggest task breakdown for complex tasks
163
+ */
164
+ suggestBreakdown(task) {
165
+ const subtasks = [];
166
+ // Based on task description length and complexity indicators
167
+ const complexityIndicators = ['implement', 'create', 'design', 'refactor', 'migrate'];
168
+ const hasComplexity = complexityIndicators.some(i => task.title.toLowerCase().includes(i) ||
169
+ task.description.toLowerCase().includes(i));
170
+ if (!hasComplexity) {
171
+ return subtasks;
172
+ }
173
+ // Generate suggested subtasks
174
+ if (task.files.length > 2) {
175
+ // Multiple files - suggest per-file tasks
176
+ task.files.forEach((file, idx) => {
177
+ subtasks.push({
178
+ id: `${task.id}-sub-${idx + 1}`,
179
+ title: `Update ${file.split('/').pop()}`,
180
+ description: `Implement changes in ${file}`,
181
+ status: 'pending',
182
+ priority: task.priority,
183
+ dependencies: idx === 0 ? task.dependencies : [`${task.id}-sub-${idx}`],
184
+ acceptanceCriteria: [`Changes in ${file} are complete and working`],
185
+ files: [file],
186
+ createdAt: new Date()
187
+ });
188
+ });
189
+ }
190
+ return subtasks;
191
+ }
192
+ /**
193
+ * Estimate remaining work
194
+ */
195
+ estimateRemainingWork(plan) {
196
+ const pendingTasks = plan.tasks.filter(t => t.status === 'pending' || t.status === 'in_progress');
197
+ const completedTasks = plan.tasks.filter(t => t.status === 'completed');
198
+ // Calculate average completion time from completed tasks
199
+ let avgCompletionTime = 0;
200
+ if (completedTasks.length > 0) {
201
+ const completionTimes = completedTasks
202
+ .filter(t => t.startedAt && t.completedAt)
203
+ .map(t => t.completedAt.getTime() - t.startedAt.getTime());
204
+ if (completionTimes.length > 0) {
205
+ avgCompletionTime = completionTimes.reduce((a, b) => a + b, 0) / completionTimes.length;
206
+ }
207
+ }
208
+ // Estimate remaining time
209
+ const estimatedRemainingTime = avgCompletionTime * pendingTasks.length;
210
+ // Calculate by priority
211
+ const byPriority = {
212
+ high: pendingTasks.filter(t => t.priority === 'high').length,
213
+ medium: pendingTasks.filter(t => t.priority === 'medium').length,
214
+ low: pendingTasks.filter(t => t.priority === 'low').length
215
+ };
216
+ // Get critical path
217
+ const criticalPath = this.dependencyManager.getCriticalPath(pendingTasks);
218
+ return {
219
+ pendingTaskCount: pendingTasks.length,
220
+ estimatedTimeMs: estimatedRemainingTime,
221
+ estimatedTimeFormatted: this.formatDuration(estimatedRemainingTime),
222
+ byPriority,
223
+ criticalPathLength: criticalPath.length,
224
+ blockedTasks: pendingTasks.filter(t => !this.dependencyManager.canExecute(t, new Set(completedTasks.map(c => c.id)))).length
225
+ };
226
+ }
227
+ formatDuration(ms) {
228
+ if (ms === 0)
229
+ return 'Unknown';
230
+ const seconds = Math.floor(ms / 1000);
231
+ const minutes = Math.floor(seconds / 60);
232
+ const hours = Math.floor(minutes / 60);
233
+ if (hours > 0) {
234
+ return `~${hours}h ${minutes % 60}m`;
235
+ }
236
+ else if (minutes > 0) {
237
+ return `~${minutes}m`;
238
+ }
239
+ else {
240
+ return `~${seconds}s`;
241
+ }
242
+ }
243
+ }
244
+ exports.PlanAdapter = PlanAdapter;