@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.
@@ -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
+ }