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.
- package/PLUGIN_DEVELOP_PROMPT.md +1164 -0
- package/README.md +852 -0
- package/assets/default-icon.png +0 -0
- package/dist/commands/ai-session.js +44 -0
- package/dist/commands/build.js +111 -0
- package/dist/commands/config-ai.js +291 -0
- package/dist/commands/config.js +53 -0
- package/dist/commands/create/ai-create.js +183 -0
- package/dist/commands/create/assets.js +53 -0
- package/dist/commands/create/basic.js +72 -0
- package/dist/commands/create/index.js +73 -0
- package/dist/commands/create/react.js +136 -0
- package/dist/commands/create/templates/basic.js +383 -0
- package/dist/commands/create/templates/react/backend.js +72 -0
- package/dist/commands/create/templates/react/config.js +166 -0
- package/dist/commands/create/templates/react/docs.js +78 -0
- package/dist/commands/create/templates/react/hooks.js +469 -0
- package/dist/commands/create/templates/react/index.js +41 -0
- package/dist/commands/create/templates/react/types.js +1228 -0
- package/dist/commands/create/templates/react/ui.js +528 -0
- package/dist/commands/create/templates/react.js +1888 -0
- package/dist/commands/dev.js +141 -0
- package/dist/commands/pack.js +160 -0
- package/dist/commands/resume.js +97 -0
- package/dist/commands/test-ui.js +50 -0
- package/dist/index.js +71 -0
- package/dist/services/ai/PLUGIN_API.md +1102 -0
- package/dist/services/ai/PLUGIN_DEVELOP_PROMPT.md +1164 -0
- package/dist/services/ai/context-manager.js +639 -0
- package/dist/services/ai/index.js +88 -0
- package/dist/services/ai/knowledge.js +52 -0
- package/dist/services/ai/prompts.js +114 -0
- package/dist/services/ai/providers/base.js +38 -0
- package/dist/services/ai/providers/claude.js +284 -0
- package/dist/services/ai/providers/deepseek.js +28 -0
- package/dist/services/ai/providers/gemini.js +191 -0
- package/dist/services/ai/providers/glm.js +31 -0
- package/dist/services/ai/providers/minimax.js +27 -0
- package/dist/services/ai/providers/openai.js +177 -0
- package/dist/services/ai/tools.js +204 -0
- package/dist/services/ai-generator.js +968 -0
- package/dist/services/config-manager.js +117 -0
- package/dist/services/dependency-manager.js +236 -0
- package/dist/services/file-writer.js +66 -0
- package/dist/services/plan-adapter.js +244 -0
- package/dist/services/plan-command-handler.js +172 -0
- package/dist/services/plan-manager.js +502 -0
- package/dist/services/session-manager.js +113 -0
- package/dist/services/task-analyzer.js +136 -0
- package/dist/services/tui/index.js +57 -0
- package/dist/services/tui/store.js +123 -0
- package/dist/types/ai.js +172 -0
- package/dist/types/plan.js +2 -0
- package/dist/ui/Terminal.js +56 -0
- package/dist/ui/components/InputArea.js +176 -0
- package/dist/ui/components/LogArea.js +19 -0
- package/dist/ui/components/PlanPanel.js +69 -0
- package/dist/ui/components/SelectArea.js +13 -0
- 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;
|