@sschepis/robodev 1.0.0
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/ai.mjs +8 -0
- package/package.json +48 -0
- package/src/cli/cli-interface.mjs +271 -0
- package/src/config.mjs +64 -0
- package/src/core/ai-assistant.mjs +540 -0
- package/src/core/ai-provider.mjs +579 -0
- package/src/core/history-manager.mjs +330 -0
- package/src/core/system-prompt.mjs +182 -0
- package/src/custom-tools/custom-tools-manager.mjs +310 -0
- package/src/execution/tool-executor.mjs +892 -0
- package/src/lib/README.md +114 -0
- package/src/lib/adapters/console-status-adapter.mjs +48 -0
- package/src/lib/adapters/network-llm-adapter.mjs +37 -0
- package/src/lib/index.mjs +101 -0
- package/src/lib/interfaces.d.ts +98 -0
- package/src/main.mjs +61 -0
- package/src/package/package-manager.mjs +223 -0
- package/src/quality/code-validator.mjs +126 -0
- package/src/quality/quality-evaluator.mjs +248 -0
- package/src/reasoning/reasoning-system.mjs +258 -0
- package/src/structured-dev/flow-manager.mjs +321 -0
- package/src/structured-dev/implementation-planner.mjs +223 -0
- package/src/structured-dev/manifest-manager.mjs +423 -0
- package/src/structured-dev/plan-executor.mjs +113 -0
- package/src/structured-dev/project-bootstrapper.mjs +523 -0
- package/src/tools/desktop-automation-tools.mjs +172 -0
- package/src/tools/file-tools.mjs +141 -0
- package/src/tools/tool-definitions.mjs +872 -0
- package/src/ui/console-styler.mjs +503 -0
- package/src/workspace/workspace-manager.mjs +215 -0
- package/themes.json +66 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// Manifest Manager
|
|
2
|
+
// Handles the creation, reading, and updating of the SYSTEM_MAP.md file
|
|
3
|
+
// This file serves as the "Living Manifest" for the structured development process
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { consoleStyler } from '../ui/console-styler.mjs';
|
|
8
|
+
|
|
9
|
+
export class ManifestManager {
|
|
10
|
+
constructor(workingDir) {
|
|
11
|
+
this.workingDir = workingDir;
|
|
12
|
+
this.manifestPath = path.join(workingDir, 'SYSTEM_MAP.md');
|
|
13
|
+
this.snapshotsDir = path.join(workingDir, '.snapshots');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Check if the manifest exists
|
|
17
|
+
hasManifest() {
|
|
18
|
+
return fs.existsSync(this.manifestPath);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Initialize the manifest with a template
|
|
22
|
+
async initManifest() {
|
|
23
|
+
if (this.hasManifest()) {
|
|
24
|
+
return "Manifest already exists.";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Create snapshots directory
|
|
28
|
+
if (!fs.existsSync(this.snapshotsDir)) {
|
|
29
|
+
await fs.promises.mkdir(this.snapshotsDir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const template = `# System Manifest (SYSTEM_MAP.md)
|
|
33
|
+
Last Updated: ${new Date().toISOString()}
|
|
34
|
+
|
|
35
|
+
## 1. Global Invariants
|
|
36
|
+
| ID | Invariant | Description |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| INV-001 | No External Math Libs | Use standard Math library only. |
|
|
39
|
+
| INV-002 | Strict Typing | All interfaces must be defined in .d.ts files. |
|
|
40
|
+
|
|
41
|
+
## 2. Feature Registry
|
|
42
|
+
| Feature ID | Name | Status | Phase | Lock Level | Priority | Dependencies |
|
|
43
|
+
|---|---|---|---|---|---|---|
|
|
44
|
+
| FEAT-000 | System Init | Active | Discovery | None | High | - |
|
|
45
|
+
|
|
46
|
+
## 3. Dependency Graph
|
|
47
|
+
- FEAT-000: System Init
|
|
48
|
+
|
|
49
|
+
## 4. State Snapshots
|
|
50
|
+
- [${new Date().toISOString()}] Initial State Created
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
const cursorRulesTemplate = `# Roo Code - Structured Development Rules
|
|
54
|
+
|
|
55
|
+
You are an AI assistant operating within a **Structured Development Framework**.
|
|
56
|
+
Your behavior must be governed by the "Living Manifest" (SYSTEM_MAP.md) located in the project root.
|
|
57
|
+
|
|
58
|
+
## Core Directives
|
|
59
|
+
|
|
60
|
+
1. **Check the Manifest First**: Before answering any coding request, read \`SYSTEM_MAP.md\` to understand the current system state, locked features, and global invariants.
|
|
61
|
+
2. **Respect Locks**:
|
|
62
|
+
- If a feature is in **Interface** or **Implementation** phase, its API signatures are **LOCKED**. You cannot change them without explicit user override.
|
|
63
|
+
- If a feature is **Locked**, you cannot modify it.
|
|
64
|
+
3. **Follow the Flow**:
|
|
65
|
+
- **Discovery Phase**: Analyze requirements -> Call \`submit_technical_design\`.
|
|
66
|
+
- **Design Review**: Wait for user approval -> Call \`approve_design\`.
|
|
67
|
+
- **Interface Phase**: Define types -> Call \`lock_interfaces\`.
|
|
68
|
+
- **Implementation Phase**: Write code -> Call \`submit_critique\` -> Finalize.
|
|
69
|
+
|
|
70
|
+
## Tool Usage
|
|
71
|
+
|
|
72
|
+
- Use \`read_manifest\` to access the system map.
|
|
73
|
+
- Use \`submit_technical_design\`, \`approve_design\`, \`lock_interfaces\`, and \`submit_critique\` to move through the development phases.
|
|
74
|
+
`;
|
|
75
|
+
|
|
76
|
+
// Create example hooks file
|
|
77
|
+
const hooksExample = {
|
|
78
|
+
"on_lock": "echo 'Feature locked!'",
|
|
79
|
+
"on_phase_change": "echo 'Phase changed'"
|
|
80
|
+
};
|
|
81
|
+
const hooksDir = path.join(this.workingDir, '.ai-man');
|
|
82
|
+
if (!fs.existsSync(hooksDir)) {
|
|
83
|
+
await fs.promises.mkdir(hooksDir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
await fs.promises.writeFile(path.join(hooksDir, 'hooks.json.example'), JSON.stringify(hooksExample, null, 2), 'utf8');
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await fs.promises.writeFile(this.manifestPath, template, 'utf8');
|
|
89
|
+
consoleStyler.log('system', 'Created SYSTEM_MAP.md');
|
|
90
|
+
|
|
91
|
+
// Also create .cursorrules
|
|
92
|
+
const cursorRulesPath = path.join(this.workingDir, '.cursorrules');
|
|
93
|
+
if (!fs.existsSync(cursorRulesPath)) {
|
|
94
|
+
await fs.promises.writeFile(cursorRulesPath, cursorRulesTemplate, 'utf8');
|
|
95
|
+
consoleStyler.log('system', 'Created .cursorrules');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
await this.createSnapshot('Initial Init');
|
|
99
|
+
|
|
100
|
+
return "SYSTEM_MAP.md and .cursorrules created successfully.";
|
|
101
|
+
} catch (error) {
|
|
102
|
+
consoleStyler.log('error', `Failed to create manifest: ${error.message}`);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Initialize the manifest with pre-extracted design data
|
|
108
|
+
async initManifestWithData(features, invariants) {
|
|
109
|
+
if (this.hasManifest()) {
|
|
110
|
+
return "Manifest already exists.";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Create snapshots directory
|
|
114
|
+
if (!fs.existsSync(this.snapshotsDir)) {
|
|
115
|
+
await fs.promises.mkdir(this.snapshotsDir, { recursive: true });
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Build invariants table
|
|
119
|
+
let invariantsTable = '| ID | Invariant | Description |\n|---|---|---|';
|
|
120
|
+
if (invariants && invariants.length > 0) {
|
|
121
|
+
for (const inv of invariants) {
|
|
122
|
+
invariantsTable += `\n| ${inv.id} | ${inv.name} | ${inv.description} |`;
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
invariantsTable += '\n| INV-001 | (none extracted) | No invariants found in design document. |';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Build feature registry table
|
|
129
|
+
let featureTable = '| Feature ID | Name | Status | Phase | Lock Level | Priority | Dependencies |\n|---|---|---|---|---|---|---|';
|
|
130
|
+
if (features && features.length > 0) {
|
|
131
|
+
for (const feat of features) {
|
|
132
|
+
featureTable += `\n| ${feat.id} | ${feat.name} | Active | ${feat.phase} | None | ${feat.priority} | ${feat.dependencies} |`;
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
featureTable += '\n| FEAT-000 | System Init | Active | Discovery | None | High | - |';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Build dependency graph
|
|
139
|
+
let depGraph = '';
|
|
140
|
+
if (features && features.length > 0) {
|
|
141
|
+
for (const feat of features) {
|
|
142
|
+
depGraph += `- ${feat.id}: ${feat.name}\n`;
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
depGraph = '- FEAT-000: System Init\n';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const template = `# System Manifest (SYSTEM_MAP.md)
|
|
149
|
+
Last Updated: ${new Date().toISOString()}
|
|
150
|
+
|
|
151
|
+
## 1. Global Invariants
|
|
152
|
+
${invariantsTable}
|
|
153
|
+
|
|
154
|
+
## 2. Feature Registry
|
|
155
|
+
${featureTable}
|
|
156
|
+
|
|
157
|
+
## 3. Dependency Graph
|
|
158
|
+
${depGraph.trimEnd()}
|
|
159
|
+
|
|
160
|
+
## 4. State Snapshots
|
|
161
|
+
- [${new Date().toISOString()}] Initial State Created (bootstrapped from design document)
|
|
162
|
+
`;
|
|
163
|
+
|
|
164
|
+
const cursorRulesTemplate = `# Roo Code - Structured Development Rules
|
|
165
|
+
|
|
166
|
+
You are an AI assistant operating within a **Structured Development Framework**.
|
|
167
|
+
Your behavior must be governed by the "Living Manifest" (SYSTEM_MAP.md) located in the project root.
|
|
168
|
+
|
|
169
|
+
## Core Directives
|
|
170
|
+
|
|
171
|
+
1. **Check the Manifest First**: Before answering any coding request, read \`SYSTEM_MAP.md\` to understand the current system state, locked features, and global invariants.
|
|
172
|
+
2. **Respect Locks**:
|
|
173
|
+
- If a feature is in **Interface** or **Implementation** phase, its API signatures are **LOCKED**. You cannot change them without explicit user override.
|
|
174
|
+
- If a feature is **Locked**, you cannot modify it.
|
|
175
|
+
3. **Follow the Flow**:
|
|
176
|
+
- **Discovery Phase**: Analyze requirements -> Call \`submit_technical_design\`.
|
|
177
|
+
- **Design Review**: Wait for user approval -> Call \`approve_design\`.
|
|
178
|
+
- **Interface Phase**: Define types -> Call \`lock_interfaces\`.
|
|
179
|
+
- **Implementation Phase**: Write code -> Call \`submit_critique\` -> Finalize.
|
|
180
|
+
|
|
181
|
+
## Tool Usage
|
|
182
|
+
|
|
183
|
+
- Use \`read_manifest\` to access the system map.
|
|
184
|
+
- Use \`submit_technical_design\`, \`approve_design\`, \`lock_interfaces\`, and \`submit_critique\` to move through the development phases.
|
|
185
|
+
`;
|
|
186
|
+
|
|
187
|
+
// Create .ai-man directory and hooks example
|
|
188
|
+
const hooksExample = {
|
|
189
|
+
"on_lock": "echo 'Feature locked!'",
|
|
190
|
+
"on_phase_change": "echo 'Phase changed'"
|
|
191
|
+
};
|
|
192
|
+
const hooksDir = path.join(this.workingDir, '.ai-man');
|
|
193
|
+
if (!fs.existsSync(hooksDir)) {
|
|
194
|
+
await fs.promises.mkdir(hooksDir, { recursive: true });
|
|
195
|
+
}
|
|
196
|
+
await fs.promises.writeFile(path.join(hooksDir, 'hooks.json.example'), JSON.stringify(hooksExample, null, 2), 'utf8');
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
await fs.promises.writeFile(this.manifestPath, template, 'utf8');
|
|
200
|
+
consoleStyler.log('system', 'Created SYSTEM_MAP.md (bootstrapped from design document)');
|
|
201
|
+
|
|
202
|
+
// Also create .cursorrules
|
|
203
|
+
const cursorRulesPath = path.join(this.workingDir, '.cursorrules');
|
|
204
|
+
if (!fs.existsSync(cursorRulesPath)) {
|
|
205
|
+
await fs.promises.writeFile(cursorRulesPath, cursorRulesTemplate, 'utf8');
|
|
206
|
+
consoleStyler.log('system', 'Created .cursorrules');
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
await this.createSnapshot('Initial Init (bootstrapped)');
|
|
210
|
+
|
|
211
|
+
return "SYSTEM_MAP.md created with pre-populated design data.";
|
|
212
|
+
} catch (error) {
|
|
213
|
+
consoleStyler.log('error', `Failed to create manifest: ${error.message}`);
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Add an invariant to the Global Invariants section
|
|
219
|
+
async addInvariant(id, name, description) {
|
|
220
|
+
const currentManifest = await this.readManifest();
|
|
221
|
+
const invariantsRegex = /## 1. Global Invariants([\s\S]*?)(?=## 2|$)/;
|
|
222
|
+
const match = currentManifest.match(invariantsRegex);
|
|
223
|
+
|
|
224
|
+
if (!match) return `Error: Global Invariants section not found.`;
|
|
225
|
+
|
|
226
|
+
let rawTable = match[1].trim();
|
|
227
|
+
let rows = rawTable.split('\n').map(row => row.trim()).filter(row => row.length > 0);
|
|
228
|
+
|
|
229
|
+
// Headers
|
|
230
|
+
const headerRow = "| ID | Invariant | Description |";
|
|
231
|
+
const separatorRow = "|---|---|---|";
|
|
232
|
+
|
|
233
|
+
// Parse existing invariants
|
|
234
|
+
const invariantMap = new Map();
|
|
235
|
+
let startIndex = 0;
|
|
236
|
+
if (rows[0] && rows[0].startsWith('| ID')) startIndex = 2;
|
|
237
|
+
|
|
238
|
+
for (let i = startIndex; i < rows.length; i++) {
|
|
239
|
+
const row = rows[i];
|
|
240
|
+
if (!row.startsWith('|')) continue;
|
|
241
|
+
const cols = row.split('|').map(c => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
|
|
242
|
+
if (cols.length > 0) {
|
|
243
|
+
while (cols.length < 3) cols.push('-');
|
|
244
|
+
invariantMap.set(cols[0], cols);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Add or update
|
|
249
|
+
invariantMap.set(id, [id, name, description]);
|
|
250
|
+
|
|
251
|
+
// Reconstruct table
|
|
252
|
+
let newTable = `${headerRow}\n${separatorRow}`;
|
|
253
|
+
for (const [_, cols] of invariantMap) {
|
|
254
|
+
newTable += `\n| ${cols.join(' | ')} |`;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
await this.updateSection('1. Global Invariants', newTable);
|
|
258
|
+
return `Invariant ${id} added/updated.`;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Read the manifest content
|
|
262
|
+
async readManifest() {
|
|
263
|
+
if (!this.hasManifest()) {
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
return await fs.promises.readFile(this.manifestPath, 'utf8');
|
|
269
|
+
} catch (error) {
|
|
270
|
+
consoleStyler.log('error', `Failed to read manifest: ${error.message}`);
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Update a specific section of the manifest
|
|
276
|
+
async updateSection(sectionName, newContent) {
|
|
277
|
+
if (!this.hasManifest()) {
|
|
278
|
+
throw new Error("Manifest not found. Please initialize structured development first.");
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Create snapshot before update
|
|
282
|
+
await this.createSnapshot(`Pre-update: ${sectionName}`);
|
|
283
|
+
|
|
284
|
+
let content = await this.readManifest();
|
|
285
|
+
const sectionRegex = new RegExp(`## ${sectionName}[\\s\\S]*?(?=## |$)`, 'g');
|
|
286
|
+
|
|
287
|
+
if (!sectionRegex.test(content)) {
|
|
288
|
+
// Section doesn't exist, append it
|
|
289
|
+
content += `\n\n## ${sectionName}\n${newContent}`;
|
|
290
|
+
} else {
|
|
291
|
+
// Replace existing section
|
|
292
|
+
content = content.replace(sectionRegex, `## ${sectionName}\n${newContent}\n`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Update timestamp
|
|
296
|
+
content = content.replace(/Last Updated: .*/, `Last Updated: ${new Date().toISOString()}`);
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
await fs.promises.writeFile(this.manifestPath, content, 'utf8');
|
|
300
|
+
consoleStyler.log('system', `Updated section: ${sectionName}`);
|
|
301
|
+
return `Section '${sectionName}' updated successfully.`;
|
|
302
|
+
} catch (error) {
|
|
303
|
+
consoleStyler.log('error', `Failed to update manifest: ${error.message}`);
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Add a feature to the registry (Robust Parsing Implementation)
|
|
309
|
+
async addFeature(id, name, status = 'Active', phase = 'Discovery', lockLevel = 'None', priority = 'Medium', dependencies = '-') {
|
|
310
|
+
const currentManifest = await this.readManifest();
|
|
311
|
+
const registryRegex = /## 2. Feature Registry([\s\S]*?)(?=## 3|$)/;
|
|
312
|
+
const match = currentManifest.match(registryRegex);
|
|
313
|
+
|
|
314
|
+
if (!match) return `Error: Feature Registry section not found.`;
|
|
315
|
+
|
|
316
|
+
let rawTable = match[1].trim();
|
|
317
|
+
let rows = rawTable.split('\n').map(row => row.trim()).filter(row => row.length > 0);
|
|
318
|
+
|
|
319
|
+
// Ensure correct headers
|
|
320
|
+
const headerRow = "| Feature ID | Name | Status | Phase | Lock Level | Priority | Dependencies |";
|
|
321
|
+
const separatorRow = "|---|---|---|---|---|---|---|";
|
|
322
|
+
|
|
323
|
+
// Parse existing data into a map for easy updating
|
|
324
|
+
const featureMap = new Map();
|
|
325
|
+
|
|
326
|
+
// Skip header and separator (start at index 2 if they exist)
|
|
327
|
+
let startIndex = 0;
|
|
328
|
+
if (rows[0] && rows[0].startsWith('| Feature ID')) startIndex = 2;
|
|
329
|
+
|
|
330
|
+
for (let i = startIndex; i < rows.length; i++) {
|
|
331
|
+
const row = rows[i];
|
|
332
|
+
if (!row.startsWith('|')) continue;
|
|
333
|
+
|
|
334
|
+
const cols = row.split('|').map(c => c.trim()).filter((_, idx, arr) => idx > 0 && idx < arr.length - 1);
|
|
335
|
+
if (cols.length > 0) {
|
|
336
|
+
// Handle legacy rows by padding
|
|
337
|
+
while (cols.length < 7) cols.push('-');
|
|
338
|
+
featureMap.set(cols[0], cols);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Update or Add the feature
|
|
343
|
+
// Schema: [ID, Name, Status, Phase, Lock, Priority, Dependencies]
|
|
344
|
+
featureMap.set(id, [id, name, status, phase, lockLevel, priority, dependencies]);
|
|
345
|
+
|
|
346
|
+
// Reconstruct Table
|
|
347
|
+
let newTable = `${headerRow}\n${separatorRow}`;
|
|
348
|
+
for (const [_, cols] of featureMap) {
|
|
349
|
+
newTable += `\n| ${cols.join(' | ')} |`;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
await this.updateSection('2. Feature Registry', newTable);
|
|
353
|
+
return `Feature ${id} added/updated in registry.`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Create a snapshot of the current manifest
|
|
357
|
+
async createSnapshot(description) {
|
|
358
|
+
if (!this.hasManifest()) return;
|
|
359
|
+
|
|
360
|
+
try {
|
|
361
|
+
if (!fs.existsSync(this.snapshotsDir)) {
|
|
362
|
+
await fs.promises.mkdir(this.snapshotsDir, { recursive: true });
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
366
|
+
const snapshotFilename = `SYSTEM_MAP.${timestamp}.md`;
|
|
367
|
+
const snapshotPath = path.join(this.snapshotsDir, snapshotFilename);
|
|
368
|
+
|
|
369
|
+
await fs.promises.copyFile(this.manifestPath, snapshotPath);
|
|
370
|
+
|
|
371
|
+
// Append to State Snapshots section (without triggering another snapshot)
|
|
372
|
+
// We read file directly to avoid loop
|
|
373
|
+
let content = await fs.promises.readFile(this.manifestPath, 'utf8');
|
|
374
|
+
const snapshotLog = `- [${new Date().toISOString()}] Snapshot: ${description} (${snapshotFilename})`;
|
|
375
|
+
|
|
376
|
+
const snapshotsRegex = /## 4. State Snapshots([\s\S]*?)$/;
|
|
377
|
+
if (snapshotsRegex.test(content)) {
|
|
378
|
+
content = content.replace(snapshotsRegex, (match, p1) => {
|
|
379
|
+
return `## 4. State Snapshots${p1.trimEnd()}\n${snapshotLog}\n`;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
await fs.promises.writeFile(this.manifestPath, content, 'utf8');
|
|
383
|
+
|
|
384
|
+
} catch (error) {
|
|
385
|
+
consoleStyler.log('error', `Failed to create snapshot: ${error.message}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Restore a snapshot
|
|
390
|
+
async restoreSnapshot(snapshotId) {
|
|
391
|
+
// If snapshotId is not full path, look in dir
|
|
392
|
+
let targetPath = snapshotId;
|
|
393
|
+
if (!path.isAbsolute(snapshotId) && !snapshotId.includes('/')) {
|
|
394
|
+
targetPath = path.join(this.snapshotsDir, snapshotId);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!fs.existsSync(targetPath)) {
|
|
398
|
+
// Try searching for file containing timestamp if partial ID provided
|
|
399
|
+
const files = await fs.promises.readdir(this.snapshotsDir);
|
|
400
|
+
const match = files.find(f => f.includes(snapshotId));
|
|
401
|
+
if (match) {
|
|
402
|
+
targetPath = path.join(this.snapshotsDir, match);
|
|
403
|
+
} else {
|
|
404
|
+
return `Error: Snapshot ${snapshotId} not found.`;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
await fs.promises.copyFile(targetPath, this.manifestPath);
|
|
410
|
+
consoleStyler.log('system', `Restored manifest from ${path.basename(targetPath)}`);
|
|
411
|
+
return `System restored to state from ${path.basename(targetPath)}`;
|
|
412
|
+
} catch (error) {
|
|
413
|
+
return `Error restoring snapshot: ${error.message}`;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// List available snapshots
|
|
418
|
+
async listSnapshots() {
|
|
419
|
+
if (!fs.existsSync(this.snapshotsDir)) return [];
|
|
420
|
+
const files = await fs.promises.readdir(this.snapshotsDir);
|
|
421
|
+
return files.filter(f => f.endsWith('.md')).sort().reverse();
|
|
422
|
+
}
|
|
423
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Plan Executor
|
|
2
|
+
// Orchestrates the concurrent execution of multi-agent implementation plans.
|
|
3
|
+
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { consoleStyler } from '../ui/console-styler.mjs';
|
|
7
|
+
|
|
8
|
+
export class PlanExecutor {
|
|
9
|
+
constructor(manifestManager, aiAssistantClass) {
|
|
10
|
+
this.manifestManager = manifestManager;
|
|
11
|
+
this.AiAssistant = aiAssistantClass;
|
|
12
|
+
this.concurrencyLimit = 3; // Max concurrent agents
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Load and execute a plan
|
|
16
|
+
async executePlan(planPath) {
|
|
17
|
+
if (!fs.existsSync(planPath)) {
|
|
18
|
+
return { success: false, message: `Plan file not found: ${planPath}` };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const plan = JSON.parse(fs.readFileSync(planPath, 'utf8'));
|
|
22
|
+
const stages = plan.stages;
|
|
23
|
+
const totalStages = stages.length;
|
|
24
|
+
|
|
25
|
+
consoleStyler.log('system', `Starting execution of ${totalStages} stages...`, { box: true });
|
|
26
|
+
|
|
27
|
+
const results = [];
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < totalStages; i++) {
|
|
30
|
+
const stage = stages[i];
|
|
31
|
+
consoleStyler.log('system', `>>> Executing Stage ${stage.id}/${totalStages} with ${stage.tasks.length} tasks`, { box: true });
|
|
32
|
+
|
|
33
|
+
// Execute stage with concurrency control
|
|
34
|
+
const stageResults = await this.executeStage(stage.tasks);
|
|
35
|
+
results.push({ stage: stage.id, results: stageResults });
|
|
36
|
+
|
|
37
|
+
// Check for failures
|
|
38
|
+
const failures = stageResults.filter(r => !r.success);
|
|
39
|
+
if (failures.length > 0) {
|
|
40
|
+
consoleStyler.log('error', `Stage ${stage.id} failed with ${failures.length} errors. Stopping execution.`);
|
|
41
|
+
return { success: false, message: `Execution stopped at Stage ${stage.id} due to failures.`, details: results };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
consoleStyler.log('system', `✓ Stage ${stage.id} completed successfully.`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { success: true, message: "All stages completed successfully.", details: results };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Execute a list of tasks concurrently
|
|
51
|
+
async executeStage(tasks) {
|
|
52
|
+
// tasks is now an array of feature objects: { id, status, phase, dependencies }
|
|
53
|
+
const executions = tasks.map(task => this.executeTask(task));
|
|
54
|
+
return await Promise.all(executions);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Execute a single task with an isolated AI agent
|
|
58
|
+
async executeTask(task) {
|
|
59
|
+
const workingDir = this.manifestManager.workingDir;
|
|
60
|
+
const taskId = task.id || task; // Handle object or string ID for backward compat
|
|
61
|
+
const taskName = task.name || "Unknown Feature";
|
|
62
|
+
|
|
63
|
+
consoleStyler.log('working', `[${taskId}] Spawning agent for ${taskName}...`);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
// Instantiate a new AI agent for this task
|
|
67
|
+
const agent = new this.AiAssistant(workingDir);
|
|
68
|
+
|
|
69
|
+
// Context Prompt
|
|
70
|
+
const prompt = `
|
|
71
|
+
Task: Implement Feature ${taskId} (${taskName})
|
|
72
|
+
Role: You are a specialized implementation agent working in parallel with others.
|
|
73
|
+
Context:
|
|
74
|
+
- You are responsible ONLY for feature ${taskId}.
|
|
75
|
+
- Current Status: ${task.status || 'Active'}
|
|
76
|
+
- Phase: ${task.phase || 'Implementation'}
|
|
77
|
+
- Dependencies: ${task.dependencies ? task.dependencies.join(', ') : 'None'}
|
|
78
|
+
- Read the 'SYSTEM_MAP.md' to understand the full system context.
|
|
79
|
+
- Read any design docs or interfaces related to ${taskId} (check 'src/' or 'docs/').
|
|
80
|
+
|
|
81
|
+
Execution Steps:
|
|
82
|
+
1. **Project Context Analysis**: Scan the existing project structure (using \`list_files\`) to understand the directory layout, naming conventions, and architectural patterns. Derive the appropriate location for your new files based on this context.
|
|
83
|
+
2. **Implementation**: Write the core logic for the feature. Ensure strict adherence to interfaces.
|
|
84
|
+
3. **Unit Test Generation**: Create comprehensive unit tests for the implemented code. Aim for high coverage.
|
|
85
|
+
4. **Production Refinement**: Review your code for error handling, edge cases, and performance. Refactor for clarity and maintainability.
|
|
86
|
+
5. **Documentation**: Add JSDoc comments to all public functions and classes. Create or update a README.md for the module if appropriate.
|
|
87
|
+
6. **Finalize**: Update the SYSTEM_MAP.md status to 'Completed' only after all above steps are done.
|
|
88
|
+
|
|
89
|
+
Action: execute these steps now.
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
// Run the agent
|
|
93
|
+
// We use run() which runs the conversation loop until completion
|
|
94
|
+
const response = await agent.run(prompt);
|
|
95
|
+
|
|
96
|
+
consoleStyler.log('success', `[${taskId}] Agent finished: ${response.substring(0, 50)}...`);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
taskId: taskId,
|
|
100
|
+
success: true,
|
|
101
|
+
output: response
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
} catch (error) {
|
|
105
|
+
consoleStyler.log('error', `[${taskId}] Agent failed: ${error.message}`);
|
|
106
|
+
return {
|
|
107
|
+
taskId: taskId,
|
|
108
|
+
success: false,
|
|
109
|
+
error: error.message
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|