@nikelyh/metamorph 2.0.11 → 2.1.1

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/dist/index.js CHANGED
@@ -1,4 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ MigrationIntegrator,
4
+ SQLiteStateStore,
5
+ ShadowWorkspace,
6
+ createApiServer,
7
+ createCheckProjectDiagnosticsTool,
8
+ createCreateFileTool,
9
+ createDeleteFileTool,
10
+ createListDirectoryTool,
11
+ createReadFileTool,
12
+ createRenameFileTool,
13
+ createRunBuildTool,
14
+ createWriteFileTool
15
+ } from "./chunk-L7B6KUCW.js";
16
+ import {
17
+ formatCatalogRules,
18
+ resolveMigrationCatalog
19
+ } from "./chunk-VEAYUIOZ.js";
2
20
  import {
3
21
  detectTechnologies
4
22
  } from "./chunk-3QN2Q75J.js";
@@ -10,1267 +28,325 @@ import {
10
28
  resolveRuntime,
11
29
  runLoop,
12
30
  sendEvent
13
- } from "./chunk-GNWRSZXT.js";
31
+ } from "./chunk-GKRBTA6E.js";
32
+ import {
33
+ collectFileHints,
34
+ registerMigrationPlugin
35
+ } from "./chunk-O2QLEES3.js";
36
+ import {
37
+ analyzeReactToNextStructure,
38
+ findShadowRoot,
39
+ hasNextAppRoutes,
40
+ isFsdPageComponent,
41
+ isSpaPagesTreeFile,
42
+ isSpaRootComponent,
43
+ listAppRouterFiles,
44
+ nextMigrationFileHint
45
+ } from "./chunk-H2LZEPNI.js";
14
46
 
15
47
  // src/index.ts
16
48
  import { Command } from "commander";
17
49
  import chalk from "chalk";
18
50
  import ora from "ora";
19
51
  import "dotenv/config";
52
+ import * as path11 from "path";
20
53
 
21
- // ../infrastructure/src/db/SQLiteStateStore.ts
22
- import { DatabaseSync } from "node:sqlite";
23
- import { join as join2 } from "path";
24
- import { mkdirSync } from "fs";
25
- var SQLiteStateStore = class {
26
- db;
27
- constructor(storageDir = ".metamorph") {
28
- try {
29
- mkdirSync(storageDir, { recursive: true });
30
- } catch (e) {
31
- }
32
- const dbPath = join2(storageDir, "history.db");
33
- this.db = new DatabaseSync(dbPath);
34
- this.initTables();
35
- }
36
- /**
37
- * Creates the necessary tables if they do not exist.
38
- */
39
- initTables() {
40
- this.db.exec(`
41
- CREATE TABLE IF NOT EXISTS plans (
42
- id TEXT PRIMARY KEY,
43
- run_id TEXT,
44
- target_path TEXT,
45
- source_framework TEXT,
46
- target_framework TEXT,
47
- rules_json TEXT,
48
- created_at TEXT
49
- );
50
- `);
51
- try {
52
- this.db.exec("ALTER TABLE plans ADD COLUMN run_id TEXT");
53
- } catch (e) {
54
- }
55
- try {
56
- this.db.exec("ALTER TABLE plans ADD COLUMN target_path TEXT");
57
- } catch (e) {
58
- }
59
- this.db.exec(`
60
- CREATE TABLE IF NOT EXISTS tasks (
61
- plan_id TEXT,
62
- file_path TEXT,
63
- status TEXT,
64
- dependencies_json TEXT,
65
- error TEXT,
66
- PRIMARY KEY (plan_id, file_path),
67
- FOREIGN KEY (plan_id) REFERENCES plans(id)
68
- );
69
- `);
70
- this.db.exec(`
71
- CREATE TABLE IF NOT EXISTS events (
72
- id INTEGER PRIMARY KEY AUTOINCREMENT,
73
- event_name TEXT,
74
- payload_json TEXT,
75
- timestamp TEXT
76
- );
77
- `);
78
- }
79
- async savePlan(plan) {
80
- const stmtPlan = this.db.prepare(`
81
- INSERT OR REPLACE INTO plans (id, run_id, target_path, source_framework, target_framework, rules_json, created_at)
82
- VALUES (?, ?, ?, ?, ?, ?, ?)
83
- `);
84
- stmtPlan.run(
85
- plan.id,
86
- plan.runId,
87
- plan.targetPath || "",
88
- plan.profile.source,
89
- plan.profile.target,
90
- JSON.stringify(plan.profile.rules || []),
91
- plan.createdAt.toISOString()
92
- );
93
- const stmtTask = this.db.prepare(`
94
- INSERT OR REPLACE INTO tasks (plan_id, file_path, status, dependencies_json, error)
95
- VALUES (?, ?, ?, ?, ?)
96
- `);
97
- for (const task of plan.tasks) {
98
- stmtTask.run(
99
- plan.id,
100
- task.filePath,
101
- task.status,
102
- JSON.stringify(task.dependencies || []),
103
- task.error || null
104
- );
105
- }
54
+ // ../application/src/agents/MapperAgent.ts
55
+ import * as fs from "fs";
56
+ import * as path from "path";
57
+ import {
58
+ createAgent,
59
+ SituationSpecification
60
+ } from "@mozaik-ai/core";
61
+ var WhenMigrationStarts = class extends SituationSpecification {
62
+ isSatisfiedBy({ event }) {
63
+ return event.type === "migration.started" /* MIGRATION_STARTED */;
106
64
  }
107
- async getPlan(id) {
108
- const stmtPlan = this.db.prepare(`SELECT * FROM plans WHERE id = ?`);
109
- const planRow = stmtPlan.get(id);
110
- if (!planRow) {
111
- return null;
65
+ };
66
+ var mapFilesProcessor = {
67
+ async apply({ event, participant }) {
68
+ const payload = event.payload;
69
+ console.log(`[MapperAgent] Migration started for Plan: ${payload.planId}`);
70
+ const shadowDir = payload.shadowWorkspacePath || "";
71
+ if (!shadowDir) {
72
+ console.warn("[MapperAgent] No shadow workspace provided. Skipping.");
73
+ return;
112
74
  }
113
- const stmtTasks = this.db.prepare(`SELECT * FROM tasks WHERE plan_id = ?`);
114
- const taskRows = stmtTasks.all(id);
115
- return {
116
- id: planRow.id,
117
- runId: planRow.run_id,
118
- targetPath: planRow.target_path,
119
- profile: {
120
- source: planRow.source_framework,
121
- target: planRow.target_framework,
122
- rules: JSON.parse(planRow.rules_json)
123
- },
124
- createdAt: new Date(planRow.created_at),
125
- tasks: taskRows.map((row) => ({
126
- filePath: row.file_path,
127
- status: row.status,
128
- dependencies: JSON.parse(row.dependencies_json),
129
- error: row.error || void 0
130
- }))
75
+ const filesToMigrate = [];
76
+ const scanDir = (dir) => {
77
+ if (!fs.existsSync(dir)) return;
78
+ const files = fs.readdirSync(dir);
79
+ const ignoredDirs = ["node_modules", "dist", "build", "out", "coverage", ".next"];
80
+ for (const file of files) {
81
+ if (ignoredDirs.includes(file) || file.startsWith(".")) continue;
82
+ const fullPath = path.join(dir, file);
83
+ if (fs.statSync(fullPath).isDirectory()) {
84
+ scanDir(fullPath);
85
+ } else if (/\.(ts|js|tsx|jsx|vue|svelte)$/.test(file) || file.endsWith(".html") && file !== "index.html") {
86
+ filesToMigrate.push(fullPath);
87
+ }
88
+ }
131
89
  };
132
- }
133
- async updateTaskStatus(planId, filePath, status, error) {
134
- const stmt = this.db.prepare(`
135
- INSERT INTO tasks (plan_id, file_path, status, error, dependencies_json)
136
- VALUES (?, ?, ?, ?, '[]')
137
- ON CONFLICT(plan_id, file_path) DO UPDATE
138
- SET status = excluded.status, error = excluded.error
139
- `);
140
- stmt.run(planId, filePath, status, error || null);
141
- }
142
- async logEvent(eventName, payload) {
143
- const stmt = this.db.prepare(`
144
- INSERT INTO events (event_name, payload_json, timestamp)
145
- VALUES (?, ?, ?)
146
- `);
147
- stmt.run(eventName, JSON.stringify(payload), (/* @__PURE__ */ new Date()).toISOString());
148
- }
149
- async getAllPlans() {
150
- const stmtPlans = this.db.prepare(`SELECT * FROM plans ORDER BY created_at DESC`);
151
- const planRows = stmtPlans.all();
152
- const stmtTasks = this.db.prepare(`SELECT * FROM tasks`);
153
- const taskRows = stmtTasks.all();
154
- return planRows.map((planRow) => {
155
- const planTasks = taskRows.filter((t) => t.plan_id === planRow.id);
156
- return {
157
- id: planRow.id,
158
- runId: planRow.run_id,
159
- targetPath: planRow.target_path,
160
- profile: {
161
- source: planRow.source_framework,
162
- target: planRow.target_framework,
163
- rules: JSON.parse(planRow.rules_json)
90
+ scanDir(shadowDir);
91
+ const runtime = resolveRuntime();
92
+ const repository = runtime.state.repository;
93
+ for (const discoveredFile of filesToMigrate) {
94
+ console.log(`[MapperAgent] Discovered file: ${discoveredFile}`);
95
+ await repository.updateTaskStatus(payload.planId, discoveredFile, "pending");
96
+ sendEvent(
97
+ {
98
+ type: "file.discovered" /* FILE_DISCOVERED */,
99
+ producerId: participant.getId(),
100
+ occurredAt: /* @__PURE__ */ new Date(),
101
+ payload: {
102
+ planId: payload.planId,
103
+ filePath: discoveredFile
104
+ }
164
105
  },
165
- createdAt: new Date(planRow.created_at),
166
- tasks: planTasks.map((row) => ({
167
- filePath: row.file_path,
168
- status: row.status,
169
- dependencies: JSON.parse(row.dependencies_json),
170
- error: row.error || void 0
171
- }))
172
- };
173
- });
174
- }
175
- async getEvents() {
176
- const stmt = this.db.prepare(`SELECT * FROM events ORDER BY id DESC LIMIT 100`);
177
- const rows = stmt.all();
178
- return rows.map((row) => ({
179
- id: row.id,
180
- eventName: row.event_name,
181
- payload: JSON.parse(row.payload_json),
182
- timestamp: new Date(row.timestamp)
183
- }));
184
- }
185
- async reset() {
186
- this.db.exec(`DELETE FROM tasks`);
187
- this.db.exec(`DELETE FROM plans`);
188
- this.db.exec(`DELETE FROM events`);
106
+ participant.getId()
107
+ );
108
+ }
189
109
  }
190
110
  };
191
-
192
- // ../infrastructure/src/tools/AstTools.ts
193
- import { Project } from "ts-morph";
194
- import { existsSync } from "fs";
195
- var project = new Project();
196
- function createReadFileTool() {
197
- return {
198
- type: "function",
199
- name: "read_file",
200
- description: "Reads the content of a source file using ts-morph.",
201
- parameters: {
202
- type: "object",
203
- properties: {
204
- filePath: { type: "string", description: "The relative path to the file to read." }
205
- },
206
- required: ["filePath"],
207
- additionalProperties: false
208
- },
209
- strict: true,
210
- invoke: async ({ filePath }) => {
211
- if (!existsSync(filePath)) {
212
- return { error: `File not found: ${filePath}` };
213
- }
214
- let sourceFile = project.getSourceFile(filePath);
215
- if (!sourceFile) {
216
- project.addSourceFileAtPath(filePath);
217
- sourceFile = project.getSourceFileOrThrow(filePath);
218
- } else {
219
- await sourceFile.refreshFromFileSystem();
220
- }
221
- return {
222
- filePath,
223
- content: sourceFile.getFullText()
224
- };
225
- }
226
- };
111
+ var mapFilesHandler = {
112
+ specification: new WhenMigrationStarts(),
113
+ processor: mapFilesProcessor
114
+ };
115
+ function createMapperAgent() {
116
+ return createAgent({
117
+ name: "Mapper",
118
+ capabilities: ["code_analysis"],
119
+ instruction: "You are the Architect Mapper. Your job is to analyze the source code and discover files that need migration.",
120
+ tools: [],
121
+ // AST scanning tools will go here later
122
+ handlers: [mapFilesHandler]
123
+ });
227
124
  }
228
- function createWriteFileTool() {
229
- return {
230
- type: "function",
231
- name: "write_file",
232
- description: "Overwrites the content of a source file with the provided refactored code using ts-morph.",
233
- parameters: {
234
- type: "object",
235
- properties: {
236
- filePath: { type: "string", description: "The relative path to the file to modify." },
237
- newContent: { type: "string", description: "The entire new source code to write to the file." }
238
- },
239
- required: ["filePath", "newContent"],
240
- additionalProperties: false
241
- },
242
- strict: true,
243
- invoke: async ({ filePath, newContent }) => {
244
- let sourceFile = project.getSourceFile(filePath);
245
- if (!sourceFile) {
246
- if (existsSync(filePath)) {
247
- project.addSourceFileAtPath(filePath);
248
- sourceFile = project.getSourceFileOrThrow(filePath);
249
- } else {
250
- sourceFile = project.createSourceFile(filePath, "");
251
- }
252
- }
253
- sourceFile.replaceWithText(newContent);
254
- await sourceFile.save();
255
- return {
256
- success: true,
257
- message: `File ${filePath} successfully updated.`
258
- };
125
+
126
+ // ../application/src/agents/WorkerAgent.ts
127
+ import {
128
+ createAgent as createAgent2,
129
+ SituationSpecification as SituationSpecification2
130
+ } from "@mozaik-ai/core";
131
+ import * as fs3 from "fs";
132
+
133
+ // ../application/src/utils/FileTreeBuilder.ts
134
+ import * as path2 from "path";
135
+ function getCommonBaseDir(filePaths) {
136
+ if (!filePaths || filePaths.length === 0) return "";
137
+ if (filePaths.length === 1) return path2.dirname(filePaths[0]);
138
+ const splitPaths = filePaths.map((p) => path2.normalize(p).split(path2.sep));
139
+ let commonPath = "";
140
+ for (let i = 0; i < splitPaths[0].length; i++) {
141
+ const part = splitPaths[0][i];
142
+ const isCommon = splitPaths.every((parts) => parts[i] === part);
143
+ if (isCommon) {
144
+ commonPath = commonPath === "" ? part : commonPath + path2.sep + part;
145
+ } else {
146
+ break;
259
147
  }
260
- };
148
+ }
149
+ return commonPath;
261
150
  }
262
- function createRenameFileTool() {
263
- return {
264
- type: "function",
265
- name: "rename_file",
266
- description: "Renames a file in the project.",
267
- parameters: {
268
- type: "object",
269
- properties: {
270
- oldPath: { type: "string", description: "Current relative path of the file." },
271
- newPath: { type: "string", description: "New relative path for the file." }
272
- },
273
- required: ["oldPath", "newPath"],
274
- additionalProperties: false
275
- },
276
- strict: true,
277
- invoke: async ({ oldPath, newPath }) => {
278
- const { renameSync, existsSync: existsSync5 } = await import("fs");
279
- if (!existsSync5(oldPath)) {
280
- return { error: `File not found: ${oldPath}` };
281
- }
282
- renameSync(oldPath, newPath);
283
- const sourceFile = project.getSourceFile(oldPath);
284
- if (sourceFile) {
285
- project.removeSourceFile(sourceFile);
286
- if (existsSync5(newPath)) {
287
- project.addSourceFileAtPath(newPath);
288
- }
151
+ function buildFileTree(filePaths) {
152
+ if (!filePaths || filePaths.length === 0) return "No files found.";
153
+ const baseDir = getCommonBaseDir(filePaths);
154
+ const relativePaths = filePaths.map((p) => path2.relative(baseDir, p)).filter((p) => p.length > 0).sort();
155
+ if (relativePaths.length === 0) return "No files found.";
156
+ const tree = {};
157
+ for (const relPath of relativePaths) {
158
+ const parts = relPath.split(path2.sep);
159
+ let current = tree;
160
+ for (let i = 0; i < parts.length; i++) {
161
+ const part = parts[i];
162
+ if (i === parts.length - 1) {
163
+ current[part] = null;
164
+ } else {
165
+ current[part] = current[part] || {};
166
+ current = current[part];
289
167
  }
290
- return { success: true, message: `File renamed from ${oldPath} to ${newPath}.` };
291
168
  }
292
- };
293
- }
294
- function createCreateFileTool() {
295
- return {
296
- type: "function",
297
- name: "create_file",
298
- description: "Creates a new file with the specified content.",
299
- parameters: {
300
- type: "object",
301
- properties: {
302
- filePath: { type: "string", description: "Relative path of the new file to create." },
303
- content: { type: "string", description: "The content of the new file." }
304
- },
305
- required: ["filePath", "content"],
306
- additionalProperties: false
307
- },
308
- strict: true,
309
- invoke: async ({ filePath, content }) => {
310
- const { writeFileSync: writeFileSync2, mkdirSync: mkdirSync2 } = await import("fs");
311
- const { dirname: dirname2 } = await import("path");
312
- mkdirSync2(dirname2(filePath), { recursive: true });
313
- writeFileSync2(filePath, content, "utf-8");
314
- project.addSourceFileAtPath(filePath);
315
- return { success: true, message: `File created at ${filePath}.` };
316
- }
317
- };
318
- }
319
- function createDeleteFileTool() {
320
- return {
321
- type: "function",
322
- name: "delete_file",
323
- description: "Deletes a file from the project.",
324
- parameters: {
325
- type: "object",
326
- properties: {
327
- filePath: { type: "string", description: "Relative path of the file to delete." }
328
- },
329
- required: ["filePath"],
330
- additionalProperties: false
331
- },
332
- strict: true,
333
- invoke: async ({ filePath }) => {
334
- const { renameSync, existsSync: existsSync5 } = await import("fs");
335
- if (!existsSync5(filePath)) {
336
- return { error: `File not found: ${filePath}` };
337
- }
338
- const obsoletePath = `${filePath}.obsolete`;
339
- renameSync(filePath, obsoletePath);
340
- const sourceFile = project.getSourceFile(filePath);
341
- if (sourceFile) {
342
- project.removeSourceFile(sourceFile);
169
+ }
170
+ let output = `/
171
+ `;
172
+ function renderTree(node, prefix = "") {
173
+ if (node === null) return;
174
+ const keys = Object.keys(node);
175
+ for (let i = 0; i < keys.length; i++) {
176
+ const key = keys[i];
177
+ const isLast = i === keys.length - 1;
178
+ const isFile = node[key] === null;
179
+ const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
180
+ output += `${prefix}${connector}${key}
181
+ `;
182
+ if (!isFile) {
183
+ const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
184
+ renderTree(node[key], nextPrefix);
343
185
  }
344
- return { success: true, message: `File marked as obsolete at ${obsoletePath} instead of deleting.` };
345
186
  }
346
- };
187
+ }
188
+ renderTree(tree);
189
+ return output.trim();
347
190
  }
348
191
 
349
- // ../infrastructure/src/workspace/ShadowWorkspace.ts
350
- import fs from "fs-extra";
351
- import { join as join3, resolve } from "path";
352
- var ShadowWorkspace = class {
353
- baseDir;
354
- constructor(basePath = ".metamorph/shadow") {
355
- this.baseDir = resolve(process.cwd(), basePath);
192
+ // ../application/src/utils/NeighborContext.ts
193
+ import * as fs2 from "fs";
194
+ import * as path3 from "path";
195
+ var MAX_FILES = 8;
196
+ var MAX_FILE_CHARS = 4e3;
197
+ var MAX_TOTAL_CHARS = 24e3;
198
+ function readTruncated(filePath) {
199
+ try {
200
+ if (!fs2.existsSync(filePath) || !fs2.statSync(filePath).isFile()) return null;
201
+ const raw = fs2.readFileSync(filePath, "utf-8");
202
+ if (raw.length <= MAX_FILE_CHARS) return raw;
203
+ return `${raw.slice(0, MAX_FILE_CHARS)}
204
+
205
+ /* \u2026 truncated (${raw.length} chars) */`;
206
+ } catch {
207
+ return null;
356
208
  }
357
- /**
358
- * Clones a target directory into the shadow workspace.
359
- * @param targetPath The absolute or relative path to the directory to clone.
360
- * @param runId A unique identifier for this run (e.g. timestamp).
361
- * @returns The absolute path to the cloned directory inside the shadow workspace.
362
- */
363
- cloneDirectory(targetPath, runId) {
364
- const sourceDir = resolve(process.cwd(), targetPath);
365
- if (!fs.existsSync(sourceDir)) {
366
- throw new Error(`Target directory does not exist: ${sourceDir}`);
367
- }
368
- const shadowDir = join3(this.baseDir, runId);
369
- if (fs.existsSync(shadowDir)) {
370
- fs.emptyDirSync(shadowDir);
371
- }
372
- const ignoredDirs = ["node_modules", ".git", ".metamorph", "dist", "build", "out", "coverage", ".next"];
373
- const items = fs.readdirSync(sourceDir);
374
- for (const item of items) {
375
- if (ignoredDirs.includes(item)) {
376
- continue;
377
- }
378
- const itemSrc = join3(sourceDir, item);
379
- const itemDest = join3(shadowDir, item);
380
- fs.copySync(itemSrc, itemDest, {
381
- filter: (src) => !ignoredDirs.some((dir) => src.includes(`/${dir}/`) || src.endsWith(`/${dir}`) || src.includes(`\\${dir}\\`) || src.endsWith(`\\${dir}`))
382
- });
209
+ }
210
+ function findSrcRoot(filePath) {
211
+ const normalized = filePath.replace(/\\/g, "/");
212
+ const idx = normalized.lastIndexOf("/src/");
213
+ if (idx === -1) return null;
214
+ return filePath.slice(0, idx + 4);
215
+ }
216
+ function resolveImport(fromFile, spec) {
217
+ const dir = path3.dirname(fromFile);
218
+ const base = spec.startsWith("@/") ? (() => {
219
+ const src = findSrcRoot(fromFile);
220
+ return src ? path3.join(src, spec.slice(2)) : path3.resolve(dir, spec);
221
+ })() : path3.resolve(dir, spec);
222
+ const candidates = [
223
+ base,
224
+ `${base}.tsx`,
225
+ `${base}.ts`,
226
+ `${base}.jsx`,
227
+ `${base}.js`,
228
+ `${base}.vue`,
229
+ `${base}.svelte`,
230
+ path3.join(base, "index.tsx"),
231
+ path3.join(base, "index.ts"),
232
+ path3.join(base, "index.vue"),
233
+ path3.join(base, "index.svelte")
234
+ ];
235
+ return candidates.find((file) => fs2.existsSync(file) && fs2.statSync(file).isFile()) || null;
236
+ }
237
+ function localImportSpecs(content) {
238
+ const specs = [];
239
+ const re = /from\s+['"](\.\.?\/[^'"]+|@\/[^'"]+)['"]/g;
240
+ let match;
241
+ while ((match = re.exec(content)) !== null) specs.push(match[1]);
242
+ return specs;
243
+ }
244
+ function listNearbyModules(fromFile, limit) {
245
+ const shadow = findShadowRoot(fromFile);
246
+ const src = findSrcRoot(fromFile) || (shadow ? path3.join(shadow, "src") : path3.dirname(fromFile));
247
+ if (!fs2.existsSync(src)) return [];
248
+ const interesting = /(?:Page|page|View|Screen|layout|Layout|main|App|routes|Routes|app\.routes)\.(t|j)sx?$|\.(vue|svelte)$/;
249
+ const out = [];
250
+ const stack = [src];
251
+ while (stack.length > 0 && out.length < limit) {
252
+ const dir = stack.pop();
253
+ let entries;
254
+ try {
255
+ entries = fs2.readdirSync(dir, { withFileTypes: true });
256
+ } catch {
257
+ continue;
383
258
  }
384
- return shadowDir;
385
- }
386
- /**
387
- * Removes the shadow workspace for a given run, effectively
388
- * discarding any in-progress migration work.
389
- */
390
- rollback(runId) {
391
- const shadowDir = join3(this.baseDir, runId);
392
- if (fs.existsSync(shadowDir)) {
393
- fs.removeSync(shadowDir);
259
+ for (const entry of entries) {
260
+ if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
261
+ const full = path3.join(dir, entry.name);
262
+ if (entry.isDirectory()) stack.push(full);
263
+ else if (interesting.test(entry.name)) out.push(full);
264
+ if (out.length >= limit) break;
394
265
  }
395
266
  }
396
- /**
397
- * Returns the absolute path of the shadow dir for a given runId.
398
- */
399
- getShadowPath(runId) {
400
- return join3(this.baseDir, runId);
267
+ return out;
268
+ }
269
+ function buildNeighborContext(filePath) {
270
+ const sections = [];
271
+ let budget = MAX_TOTAL_CHARS;
272
+ const push = (title, body) => {
273
+ const block = `### ${title}
274
+ \`\`\`
275
+ ${body}
276
+ \`\`\`
277
+ `;
278
+ if (block.length > budget) return;
279
+ budget -= block.length;
280
+ sections.push(block);
281
+ };
282
+ const self = readTruncated(filePath);
283
+ if (self) {
284
+ push(filePath, self);
285
+ } else {
286
+ sections.push(`### ${filePath}
287
+ (file does not exist yet \u2014 you may need to create it)
288
+ `);
401
289
  }
402
- /**
403
- * Cleans up old shadow runs to save disk space, keeping only the specified number of most recent runs.
404
- * @param keepCount Number of recent runs to keep (default: 3)
405
- */
406
- cleanupOldRuns(keepCount = 3) {
407
- if (!fs.existsSync(this.baseDir)) return;
408
- const runs = fs.readdirSync(this.baseDir).filter((f) => f.startsWith("run_") && fs.statSync(join3(this.baseDir, f)).isDirectory());
409
- runs.sort();
410
- if (runs.length > keepCount) {
411
- const toDelete = runs.slice(0, runs.length - keepCount);
412
- for (const run of toDelete) {
413
- this.rollback(run);
414
- }
415
- }
290
+ const importSpecs = self ? localImportSpecs(self) : [];
291
+ const resolved = importSpecs.map((spec) => resolveImport(filePath, spec)).filter((file) => Boolean(file && file !== path3.resolve(filePath)));
292
+ const unique = [...new Set(resolved)].slice(0, MAX_FILES);
293
+ for (const dep of unique) {
294
+ const body = readTruncated(dep);
295
+ if (body) push(`imported: ${dep}`, body);
296
+ }
297
+ const nearby = listNearbyModules(filePath, 24).filter((file) => path3.resolve(file) !== path3.resolve(filePath));
298
+ if (nearby.length > 0) {
299
+ const listing = nearby.map((file) => path3.relative(findShadowRoot(filePath) || path3.dirname(filePath), file)).join("\n");
300
+ sections.push(`### Nearby screen / bootstrap files (paths only \u2014 read before inventing a new UI)
301
+ ${listing}
302
+ `);
416
303
  }
417
- };
304
+ if (sections.length === 0) return "";
305
+ return `
306
+ Evidence from disk (read this before editing; match existing exports and callback prop names):
307
+ ${sections.join("\n")}`;
308
+ }
418
309
 
419
- // ../infrastructure/src/workspace/MigrationIntegrator.ts
420
- import fs2 from "fs-extra";
421
- import * as path from "path";
422
- import { execSync } from "child_process";
423
- import * as os from "os";
424
- var MigrationIntegrator = class {
425
- shadowWorkspace;
426
- constructor(shadowWorkspace) {
427
- this.shadowWorkspace = shadowWorkspace;
428
- }
429
- /**
430
- * Applies the migrated code from the shadow workspace back to the target directory.
431
- * Prioritizes Git if available, otherwise falls back to a Zip backup.
432
- *
433
- * @param runId The ID of the migration run to apply.
434
- * @param targetPath The original target path of the project.
435
- */
436
- async applyMigration(runId, targetPath) {
437
- const shadowDir = this.shadowWorkspace.getShadowPath(runId);
438
- if (!fs2.existsSync(shadowDir)) {
439
- throw new Error(`Shadow run not found: ${shadowDir}`);
440
- }
441
- const resolvedTarget = path.resolve(process.cwd(), targetPath);
442
- if (this.isGitRepo(resolvedTarget)) {
443
- return this.applyWithGit(runId, shadowDir, resolvedTarget);
444
- } else {
445
- return this.applyWithZipFallback(runId, shadowDir, resolvedTarget);
446
- }
310
+ // ../application/src/utils/workerCompletion.ts
311
+ var MISSING_AFTER_WORKER = "File missing after worker inference (missing_after_worker)";
312
+ function workerCompletionKind(fileExists) {
313
+ return fileExists ? "migrated" : "failed_missing";
314
+ }
315
+
316
+ // ../application/src/agents/WorkerAgent.ts
317
+ var MAX_RETRIES = 2;
318
+ var ConcurrencyQueue = class {
319
+ constructor(concurrencyLimit) {
320
+ this.concurrencyLimit = concurrencyLimit;
447
321
  }
448
- isGitRepo(targetPath) {
449
- return fs2.existsSync(path.join(targetPath, ".git"));
322
+ queue = [];
323
+ activeCount = 0;
324
+ async enqueue(task) {
325
+ this.queue.push(task);
326
+ this.pump();
450
327
  }
451
- async applyWithGit(runId, shadowDir, targetPath) {
452
- const branchName = `metamorph/${runId}`;
453
- try {
454
- execSync(`git checkout -B ${branchName}`, { cwd: targetPath, stdio: "ignore" });
455
- } catch (e) {
456
- if (e instanceof Error) {
457
- throw new Error(`Failed to create/reset git branch ${branchName}: ${e.message}`);
458
- }
459
- throw new Error(`Failed to create/reset git branch ${branchName}: ${String(e)}`);
460
- }
461
- this.copyShadowToTarget(shadowDir, targetPath);
462
- try {
463
- execSync(`git add .`, { cwd: targetPath, stdio: "ignore" });
464
- execSync(`git commit --allow-empty -m "chore: apply metamorph ai migration (${runId})"`, { cwd: targetPath, stdio: "ignore" });
465
- } catch (e) {
466
- if (e instanceof Error) {
467
- throw new Error(`Failed to commit changes to branch ${branchName}: ${e.message}`);
468
- }
469
- throw new Error(`Failed to commit changes to branch ${branchName}: ${String(e)}`);
328
+ async pump() {
329
+ if (this.activeCount >= this.concurrencyLimit || this.queue.length === 0) {
330
+ return;
470
331
  }
471
- return `Successfully applied migration via Git to branch: ${branchName}`;
472
- }
473
- async applyWithZipFallback(runId, shadowDir, targetPath) {
474
- const backupDir = path.resolve(os.tmpdir(), "metamorph_backups", `${runId}_backup`);
475
- fs2.ensureDirSync(backupDir);
476
- fs2.copySync(targetPath, backupDir, {
477
- filter: (src) => !src.includes("node_modules") && !src.includes(".git") && !src.includes(".metamorph")
478
- });
479
- this.copyShadowToTarget(shadowDir, targetPath);
480
- return `Successfully applied migration. Git was not detected, so a backup was saved at: ${backupDir}`;
481
- }
482
- copyShadowToTarget(shadowDir, targetPath) {
483
- const ignoredDirs = ["node_modules", ".git", ".metamorph"];
484
- if (fs2.existsSync(targetPath)) {
485
- const targetItems = fs2.readdirSync(targetPath);
486
- for (const item of targetItems) {
487
- if (ignoredDirs.includes(item)) continue;
488
- fs2.removeSync(path.join(targetPath, item));
332
+ const task = this.queue.shift();
333
+ if (task) {
334
+ this.activeCount++;
335
+ try {
336
+ await task();
337
+ } catch (error) {
338
+ console.error("Task error:", error);
339
+ } finally {
340
+ this.activeCount--;
341
+ this.pump();
489
342
  }
490
343
  }
491
- const shadowItems = fs2.readdirSync(shadowDir);
492
- for (const item of shadowItems) {
493
- if (ignoredDirs.includes(item)) continue;
494
- const itemSrc = path.join(shadowDir, item);
495
- const itemDest = path.join(targetPath, item);
496
- fs2.copySync(itemSrc, itemDest, { overwrite: true });
497
- }
498
344
  }
499
345
  };
500
-
501
- // ../domain/src/entities/catalogs/backendCatalog.ts
502
- var backendCatalog = [
503
- {
504
- source: "nestjs",
505
- target: "express",
506
- description: "Migration from NestJS framework to a plain Express application structure.",
507
- filesToDelete: ["nest-cli.json", "tsconfig.build.json"],
508
- dependenciesToRemove: ["@nestjs/common", "@nestjs/core", "@nestjs/platform-express", "reflect-metadata", "rxjs"],
509
- dependenciesToAdd: { "express": "^4.18.2" },
510
- devDependenciesToRemove: ["@nestjs/cli", "@nestjs/schematics", "@nestjs/testing", "@types/express", "@types/supertest", "source-map-support", "supertest"],
511
- devDependenciesToAdd: { "@types/express": "^4.17.17" },
512
- architecturalRules: [
513
- "Remove all NestJS decorators (@Module, @Controller, @Injectable, @Get, @Post, etc.).",
514
- 'Remove all NestJS core imports (e.g., from "@nestjs/common" or "@nestjs/core").',
515
- "Controllers must be converted into Express Router factory functions or classes that return an Express Router.",
516
- "Modules must be converted to plain dependency composition functions or startup scripts that wire Routers together.",
517
- "Services/Providers must be converted to plain TypeScript classes or functions, with dependencies passed explicitly via constructors (manual dependency injection) since NestJS DI is gone.",
518
- "Make sure to export the router or the configured Express application, not a Nest module."
519
- ],
520
- examples: [
521
- {
522
- description: "Converting a NestJS Controller to an Express Router",
523
- before: `import { Controller, Get } from '@nestjs/common';
524
- import { AppService } from './app.service';
525
-
526
- @Controller()
527
- export class AppController {
528
- constructor(private readonly appService: AppService) {}
529
-
530
- @Get()
531
- getHello(): string {
532
- return this.appService.getHello();
533
- }
534
- }`,
535
- after: `import { Router } from 'express';
536
- import { AppService } from './app.service';
537
-
538
- export function createAppRouter(appService: AppService): Router {
539
- const router = Router();
540
-
541
- router.get('/', (req, res) => {
542
- const result = appService.getHello();
543
- res.send(result);
544
- });
545
-
546
- return router;
547
- }`
548
- }
549
- ]
550
- },
551
- {
552
- source: "express",
553
- target: "nestjs",
554
- description: "Migration from a plain Express application to the NestJS framework.",
555
- dependenciesToRemove: ["express"],
556
- dependenciesToAdd: {
557
- "@nestjs/common": "latest",
558
- "@nestjs/core": "latest",
559
- "@nestjs/platform-express": "latest",
560
- "reflect-metadata": "^0.1.13",
561
- "rxjs": "^7.8.1"
562
- },
563
- devDependenciesToRemove: ["@types/express"],
564
- devDependenciesToAdd: {
565
- "@nestjs/cli": "latest",
566
- "@nestjs/schematics": "latest",
567
- "@nestjs/testing": "latest",
568
- "@types/supertest": "^2.0.12",
569
- "source-map-support": "^0.5.21",
570
- "supertest": "^6.3.3"
571
- },
572
- architecturalRules: [
573
- "Express routing logic must be encapsulated in classes decorated with @Controller().",
574
- "Business logic must be encapsulated in classes decorated with @Injectable() (Providers/Services).",
575
- "All Controllers and Providers must be registered in a class decorated with @Module().",
576
- "Use NestJS decorators for routing (@Get(), @Post(), @Param(), @Body(), etc.) instead of Express route definitions.",
577
- "Rely on NestJS dependency injection instead of manual class instantiation or function passing.",
578
- "The entry point must use NestFactory.create() to bootstrap the application, replacing app.listen().",
579
- "You are authorized to rename, create, or delete files to adhere strictly to the NestJS directory and file structure conventions (e.g., app.module.ts, app.controller.ts, app.service.ts, main.ts)."
580
- ],
581
- examples: [
582
- {
583
- description: "Converting an Express Router to a NestJS Controller and Service",
584
- before: `import { Router } from 'express';
585
- import { getHelloLogic } from './logic';
586
-
587
- export function createAppRouter(): Router {
588
- const router = Router();
589
- router.get('/', (req, res) => {
590
- res.send(getHelloLogic());
591
- });
592
- return router;
593
- }`,
594
- after: `import { Controller, Get, Injectable } from '@nestjs/common';
595
-
596
- @Injectable()
597
- export class AppService {
598
- getHello(): string {
599
- return 'Hello from logic';
600
- }
601
- }
602
-
603
- @Controller()
604
- export class AppController {
605
- constructor(private readonly appService: AppService) {}
606
-
607
- @Get()
608
- getHello(): string {
609
- return this.appService.getHello();
610
- }
611
- }`
612
- }
613
- ]
614
- },
615
- {
616
- source: "nestjs",
617
- target: "fastify",
618
- description: "Migration from NestJS framework to a Fastify application structure.",
619
- filesToDelete: ["nest-cli.json", "tsconfig.build.json"],
620
- dependenciesToRemove: ["@nestjs/common", "@nestjs/core", "@nestjs/platform-express", "reflect-metadata", "rxjs"],
621
- dependenciesToAdd: { "fastify": "latest" },
622
- devDependenciesToRemove: ["@nestjs/cli", "@nestjs/schematics", "@nestjs/testing", "@types/express", "@types/supertest", "source-map-support", "supertest"],
623
- devDependenciesToAdd: { "fastify-plugin": "latest" },
624
- architecturalRules: [
625
- "Remove all NestJS decorators (@Module, @Controller, @Injectable, @Get, @Post, etc.).",
626
- "Remove all NestJS core imports.",
627
- "The entry point (main.ts) must initialize a FastifyInstance (fastify()) instead of NestFactory.",
628
- "Controllers and routing logic must be converted to Fastify route declarations (fastify.get, fastify.post).",
629
- "Ideally, encapsulate routes and modules as Fastify plugins.",
630
- "Remove NestJS dependency injection; use standard JavaScript/TypeScript classes or functions passing dependencies directly.",
631
- "Ensure standard Fastify schema validation (JSON Schema) is added if types or DTOs are available.",
632
- "You are authorized to rename, create, or delete files to adhere to a standard fastify plugin-based directory structure."
633
- ],
634
- examples: [
635
- {
636
- description: "Converting a NestJS Controller to a Fastify Plugin",
637
- before: `import { Controller, Get } from '@nestjs/common';
638
- import { AppService } from './app.service';
639
-
640
- @Controller('users')
641
- export class UsersController {
642
- constructor(private readonly appService: AppService) {}
643
-
644
- @Get()
645
- getUsers() {
646
- return this.appService.getUsers();
647
- }
648
- }`,
649
- after: `import { FastifyInstance } from 'fastify';
650
- import { AppService } from './app.service';
651
-
652
- export default async function usersPlugin(fastify: FastifyInstance, options: any) {
653
- const appService = new AppService(); // Or inject via options
654
-
655
- fastify.get('/users', async (request, reply) => {
656
- return appService.getUsers();
657
- });
658
- }`
659
- }
660
- ]
661
- },
662
- {
663
- source: "express",
664
- target: "fastify",
665
- description: "Migration from a plain Express application to Fastify.",
666
- dependenciesToRemove: ["express"],
667
- dependenciesToAdd: { "fastify": "latest", "fastify-plugin": "latest" },
668
- devDependenciesToRemove: ["@types/express"],
669
- devDependenciesToAdd: {},
670
- architecturalRules: [
671
- "Replace all Express app instantiations with fastify().",
672
- "Replace Express middleware signatures (req, res, next) with Fastify hooks (onRequest, preHandler, etc.) or Fastify plugins.",
673
- "Replace Express routing (app.use(), app.get()) with Fastify routing (fastify.get(), fastify.register()).",
674
- "Replace Express response methods (res.send(), res.json()) with Fastify reply methods (reply.send()) or simply return the payload from the async handler.",
675
- "Migrate standard Express middlewares (like cors, helmet) to their Fastify native equivalents (@fastify/cors, @fastify/helmet) if they are present.",
676
- "You are authorized to rename, create, or delete files to restructure the Express app into modular Fastify plugins."
677
- ],
678
- examples: [
679
- {
680
- description: "Converting an Express Route to a Fastify Route",
681
- before: `const express = require('express');
682
- const app = express();
683
-
684
- app.get('/api/data', (req, res) => {
685
- res.json({ message: 'Hello Express' });
686
- });
687
-
688
- app.listen(3000);`,
689
- after: `import Fastify from 'fastify';
690
-
691
- const fastify = Fastify({ logger: true });
692
-
693
- fastify.get('/api/data', async (request, reply) => {
694
- return { message: 'Hello Fastify' };
695
- });
696
-
697
- fastify.listen({ port: 3000 }, (err) => {
698
- if (err) {
699
- fastify.log.error(err);
700
- process.exit(1);
701
- }
702
- });`
703
- }
704
- ]
705
- },
706
- {
707
- source: "fastify",
708
- target: "express",
709
- description: "Migration from a Fastify application to Express.",
710
- dependenciesToRemove: ["fastify", "fastify-plugin"],
711
- dependenciesToAdd: { "express": "latest" },
712
- devDependenciesToRemove: [],
713
- devDependenciesToAdd: { "@types/express": "latest" },
714
- architecturalRules: [
715
- "Replace all fastify() app instantiations with express().",
716
- "Replace Fastify hooks (onRequest, preHandler, etc.) with standard Express middleware signatures (req, res, next).",
717
- "Replace Fastify routing (fastify.get, fastify.register) with Express routing (app.get, express.Router().use).",
718
- "Replace Fastify reply methods (reply.send) or implicit returns with Express response methods (res.send, res.json).",
719
- "CRITICAL: Fastify catches async errors automatically. When migrating to Express, ensure all asynchronous route handlers use try/catch blocks passing the error to next(err), or wrap them in an async wrapper.",
720
- "You are authorized to rename, create, or delete files to restructure the Fastify plugins into standard Express routers."
721
- ],
722
- examples: [
723
- {
724
- description: "Converting a Fastify Route to an Express Route",
725
- before: `import Fastify from 'fastify';
726
-
727
- const fastify = Fastify();
728
-
729
- fastify.get('/api/data', async (request, reply) => {
730
- return { message: 'Hello Fastify' };
731
- });
732
-
733
- fastify.listen({ port: 3000 });`,
734
- after: `const express = require('express');
735
- const app = express();
736
-
737
- app.get('/api/data', async (req, res, next) => {
738
- try {
739
- res.json({ message: 'Hello Fastify' });
740
- } catch (err) {
741
- next(err);
742
- }
743
- });
744
-
745
- app.listen(3000);`
746
- }
747
- ]
748
- },
749
- {
750
- source: "fastify",
751
- target: "nestjs",
752
- description: "Migration from Fastify framework to NestJS (using platform-express by default).",
753
- dependenciesToRemove: ["fastify", "fastify-plugin"],
754
- dependenciesToAdd: {
755
- "@nestjs/common": "latest",
756
- "@nestjs/core": "latest",
757
- "@nestjs/platform-express": "latest",
758
- "reflect-metadata": "^0.1.13",
759
- "rxjs": "^7.8.1"
760
- },
761
- devDependenciesToRemove: [],
762
- devDependenciesToAdd: {
763
- "@nestjs/cli": "latest",
764
- "@nestjs/schematics": "latest",
765
- "@nestjs/testing": "latest",
766
- "@types/supertest": "^2.0.12",
767
- "source-map-support": "^0.5.21",
768
- "supertest": "^6.3.3"
769
- },
770
- architecturalRules: [
771
- "Fastify routing logic must be encapsulated in classes decorated with @Controller().",
772
- "Business logic must be encapsulated in classes decorated with @Injectable() (Providers/Services).",
773
- "All Controllers and Providers must be registered in a class decorated with @Module().",
774
- "Use NestJS decorators for routing (@Get(), @Post(), @Param(), @Body(), etc.) instead of fastify route definitions.",
775
- "Rely on NestJS dependency injection instead of Fastify decorators or options passing.",
776
- "The entry point must use NestFactory.create() to bootstrap the application, replacing fastify().",
777
- "You are authorized to rename, create, or delete files to adhere strictly to the NestJS directory and file structure conventions (e.g., app.module.ts, app.controller.ts, app.service.ts, main.ts)."
778
- ],
779
- examples: [
780
- {
781
- description: "Converting a Fastify Plugin to a NestJS Controller and Service",
782
- before: `export default async function usersPlugin(fastify, options) {
783
- fastify.get('/users', async (request, reply) => {
784
- return { users: [] };
785
- });
786
- }`,
787
- after: `import { Controller, Get, Injectable } from '@nestjs/common';
788
-
789
- @Injectable()
790
- export class UsersService {
791
- getUsers() {
792
- return { users: [] };
793
- }
794
- }
795
-
796
- @Controller('users')
797
- export class UsersController {
798
- constructor(private readonly usersService: UsersService) {}
799
-
800
- @Get()
801
- getUsers() {
802
- return this.usersService.getUsers();
803
- }
804
- }`
805
- }
806
- ]
807
- }
808
- ];
809
-
810
- // ../domain/src/entities/catalogs/frontendCatalog.ts
811
- var frontendCatalog = [
812
- {
813
- source: "react",
814
- target: "next",
815
- description: "Migration from a plain React SPA (like Vite or Create React App) to Next.js using the App Router.",
816
- filesToDelete: ["vite.config.ts", "vite.config.js", "index.html"],
817
- dependenciesToRemove: ["react-router-dom", "vite", "@vitejs/plugin-react"],
818
- dependenciesToAdd: { "next": "latest", "react": "latest", "react-dom": "latest" },
819
- devDependenciesToRemove: [],
820
- devDependenciesToAdd: {},
821
- architecturalRules: [
822
- 'Migrate standard React routing (e.g. react-router-dom) to Next.js file-system routing in the "app" directory.',
823
- "Convert the main entry point (usually main.tsx or index.tsx) into Next.js app/layout.tsx and app/page.tsx.",
824
- 'For components that use browser-only APIs (like window, useEffect, useState), add the "use client" directive at the very top of the file.',
825
- "Remove any react-router-dom <BrowserRouter>, <Routes>, and <Route> components.",
826
- "Replace react-router-dom <Link> components with Next.js next/link <Link> components.",
827
- "You are authorized to rename, create, or delete files to restructure the source into the Next.js App Router structure (e.g. app/page.tsx, app/layout.tsx)."
828
- ],
829
- examples: [
830
- {
831
- description: "Converting a React Router setup to Next.js App Router",
832
- before: `import { BrowserRouter, Routes, Route } from 'react-router-dom';
833
- import Home from './Home';
834
- import About from './About';
835
-
836
- export default function App() {
837
- return (
838
- <BrowserRouter>
839
- <Routes>
840
- <Route path="/" element={<Home />} />
841
- <Route path="/about" element={<About />} />
842
- </Routes>
843
- </BrowserRouter>
844
- );
845
- }`,
846
- after: `// This routing logic is deleted.
847
- // Instead, create app/page.tsx for Home and app/about/page.tsx for About.`
848
- }
849
- ]
850
- },
851
- {
852
- source: "next",
853
- target: "react",
854
- description: "Migration from Next.js to a plain React SPA using Vite.",
855
- filesToDelete: ["next.config.js", "next.config.mjs", "next.config.ts"],
856
- dependenciesToRemove: ["next"],
857
- dependenciesToAdd: { "react-router-dom": "latest" },
858
- devDependenciesToRemove: [],
859
- devDependenciesToAdd: { "vite": "latest", "@vitejs/plugin-react": "latest" },
860
- architecturalRules: [
861
- "Replace Next.js file-system routing (app/ or pages/) with declarative React Router (react-router-dom) components.",
862
- 'Remove all "use client" and "use server" directives, as they are Next.js specific.',
863
- "Convert Next.js Server Components into standard React Client Components (they will need to fetch data via useEffect or a data-fetching library).",
864
- 'Replace Next.js <Link href="..."> with React Router <Link to="...">.',
865
- "Replace Next.js next/image <Image> with standard HTML <img>.",
866
- "Create a standard Vite entry point (index.html at the root, and src/main.tsx) to bootstrap the React application."
867
- ]
868
- },
869
- {
870
- source: "vue",
871
- target: "react",
872
- description: "Migration from Vue.js (Vue 3 Composition API) to React.",
873
- filesToDelete: ["vite.config.ts", "vue.config.js"],
874
- dependenciesToRemove: ["vue", "vue-router", "pinia"],
875
- dependenciesToAdd: { "react": "latest", "react-dom": "latest", "react-router-dom": "latest" },
876
- devDependenciesToRemove: ["@vitejs/plugin-vue"],
877
- devDependenciesToAdd: { "@vitejs/plugin-react": "latest" },
878
- architecturalRules: [
879
- "Convert all .vue Single File Components (SFC) into .tsx or .jsx React function components.",
880
- "Replace Vue <template> with React JSX/TSX return statements.",
881
- "Replace Vue ref() and reactive() with React useState().",
882
- "Replace Vue computed() with React useMemo().",
883
- "Replace Vue watch() and lifecycle hooks (onMounted, etc.) with React useEffect().",
884
- "Replace vue-router with react-router-dom.",
885
- "Convert Vue props definition (defineProps) into React function component parameters.",
886
- "Convert Vue emit (defineEmits) into React callback props (e.g. onAction={...})."
887
- ]
888
- },
889
- {
890
- source: "react",
891
- target: "vue",
892
- description: "Migration from React to Vue.js (Vue 3 Composition API).",
893
- filesToDelete: ["vite.config.ts"],
894
- dependenciesToRemove: ["react", "react-dom", "react-router-dom"],
895
- dependenciesToAdd: { "vue": "latest", "vue-router": "latest" },
896
- devDependenciesToRemove: ["@vitejs/plugin-react"],
897
- devDependenciesToAdd: { "@vitejs/plugin-vue": "latest" },
898
- architecturalRules: [
899
- "Convert React .tsx/.jsx function components into Vue 3 .vue Single File Components (using <script setup>).",
900
- "Move the JSX return statement into the Vue <template> block.",
901
- "Replace React useState() with Vue ref() or reactive().",
902
- "Replace React useMemo() with Vue computed().",
903
- "Replace React useEffect() with Vue watch() or lifecycle hooks (onMounted).",
904
- "Replace react-router-dom with vue-router.",
905
- "Extract component props into defineProps().",
906
- "Extract callback props (e.g. onClick) into defineEmits() and emit events."
907
- ]
908
- }
909
- ];
910
-
911
- // ../domain/src/entities/catalogs/index.ts
912
- var MigrationCatalog = [
913
- ...backendCatalog,
914
- ...frontendCatalog
915
- ];
916
- function findMigrationCatalogEntry(source, target) {
917
- return MigrationCatalog.find(
918
- (entry) => entry.source.toLowerCase() === source.toLowerCase() && entry.target.toLowerCase() === target.toLowerCase()
919
- );
920
- }
921
-
922
- // ../infrastructure/src/server/api.ts
923
- async function createApiServer(store, migrationRunner) {
924
- const express = (await import("express")).default;
925
- const cors = (await import("cors")).default;
926
- const app = express();
927
- app.use(cors());
928
- app.use(express.json());
929
- app.get("/api/plans", async (_req, res) => {
930
- try {
931
- const plans = await store.getAllPlans();
932
- res.json(plans);
933
- } catch (error) {
934
- if (error instanceof Error) {
935
- res.status(500).json({ error: error.message });
936
- } else {
937
- res.status(500).json({ error: String(error) });
938
- }
939
- }
940
- });
941
- app.get("/api/events", async (_req, res) => {
942
- try {
943
- const events = await store.getEvents();
944
- res.json(events);
945
- } catch (error) {
946
- if (error instanceof Error) {
947
- res.status(500).json({ error: error.message });
948
- } else {
949
- res.status(500).json({ error: String(error) });
950
- }
951
- }
952
- });
953
- app.get("/api/browse", async (req, res) => {
954
- try {
955
- const { readdirSync: readdirSync2, statSync: statSync2 } = await import("fs");
956
- const { resolve: resolve3, join: join8 } = await import("path");
957
- const targetPath = req.query.path ? resolve3(String(req.query.path)) : resolve3(".");
958
- const entries = readdirSync2(targetPath).filter(
959
- (name) => !name.startsWith(".") && name !== "node_modules" && name !== "dist" && name !== "build"
960
- ).map((name) => {
961
- const full = join8(targetPath, name);
962
- try {
963
- const stat = statSync2(full);
964
- return {
965
- name,
966
- path: full,
967
- isDirectory: stat.isDirectory()
968
- };
969
- } catch {
970
- return null;
971
- }
972
- }).filter(Boolean);
973
- res.json({
974
- current: targetPath,
975
- parent: resolve3(targetPath, ".."),
976
- entries
977
- });
978
- } catch (error) {
979
- if (error instanceof Error) {
980
- res.status(500).json({ error: error.message });
981
- } else {
982
- res.status(500).json({ error: String(error) });
983
- }
984
- }
985
- });
986
- app.get("/api/detect", async (req, res) => {
987
- try {
988
- const { detectTechnologies: detectTechnologies2 } = await import("./TechDetector-5YP44RVR.js");
989
- const targetPath = req.query.path ? String(req.query.path) : ".";
990
- const detected = detectTechnologies2(targetPath);
991
- res.json({
992
- detected,
993
- primary: detected.length > 0 ? detected[0].framework : null
994
- });
995
- } catch (error) {
996
- if (error instanceof Error) {
997
- res.status(500).json({ error: error.message });
998
- } else {
999
- res.status(500).json({ error: String(error) });
1000
- }
1001
- }
1002
- });
1003
- if (migrationRunner) {
1004
- app.post("/api/migrations/start", async (req, res) => {
1005
- try {
1006
- const { targetPath, from, to } = req.body;
1007
- if (!targetPath || !from || !to) {
1008
- res.status(400).json({
1009
- error: "Missing required fields: targetPath, from, to"
1010
- });
1011
- return;
1012
- }
1013
- const catalogEntry = findMigrationCatalogEntry(from, to);
1014
- if (!catalogEntry) {
1015
- res.status(400).json({
1016
- error: `Unsupported migration profile: ${from} -> ${to}. Please check the supported catalog.`
1017
- });
1018
- return;
1019
- }
1020
- const result = await migrationRunner.startMigration({
1021
- targetPath,
1022
- from,
1023
- to
1024
- });
1025
- res.json({ ok: true, ...result });
1026
- } catch (error) {
1027
- if (error instanceof Error) {
1028
- res.status(500).json({ error: error.message });
1029
- } else {
1030
- res.status(500).json({ error: String(error) });
1031
- }
1032
- }
1033
- });
1034
- app.post("/api/migrations/apply", async (req, res) => {
1035
- try {
1036
- const { runId, targetPath } = req.body;
1037
- if (!runId || !targetPath) {
1038
- res.status(400).json({
1039
- error: "Missing required fields: runId, targetPath"
1040
- });
1041
- return;
1042
- }
1043
- const integrator = new MigrationIntegrator(migrationRunner.workspace);
1044
- const message = await integrator.applyMigration(runId, targetPath);
1045
- res.json({ ok: true, message });
1046
- } catch (error) {
1047
- if (error instanceof Error) {
1048
- res.status(500).json({ error: error.message });
1049
- } else {
1050
- res.status(500).json({ error: String(error) });
1051
- }
1052
- }
1053
- });
1054
- app.post("/api/migrations/rollback", async (req, res) => {
1055
- try {
1056
- const { runId } = req.body;
1057
- if (!runId) {
1058
- res.status(400).json({
1059
- error: "Missing required field: runId"
1060
- });
1061
- return;
1062
- }
1063
- await migrationRunner.rollbackMigration(runId);
1064
- res.json({ ok: true, message: `Rolled back run: ${runId}` });
1065
- } catch (error) {
1066
- if (error instanceof Error) {
1067
- res.status(500).json({ error: error.message });
1068
- } else {
1069
- res.status(500).json({ error: String(error) });
1070
- }
1071
- }
1072
- });
1073
- app.post("/api/migrations/reset", async (_req, res) => {
1074
- try {
1075
- await store.reset();
1076
- res.json({ ok: true, message: "Migration state reset successfully" });
1077
- } catch (error) {
1078
- if (error instanceof Error) {
1079
- res.status(500).json({ error: error.message });
1080
- } else {
1081
- res.status(500).json({ error: String(error) });
1082
- }
1083
- }
1084
- });
1085
- }
1086
- const path6 = await import("path");
1087
- const fs7 = await import("fs");
1088
- let publicDir = path6.join(import.meta.dirname, "public");
1089
- if (!fs7.existsSync(publicDir)) {
1090
- publicDir = path6.join(import.meta.dirname, "../public");
1091
- }
1092
- if (fs7.existsSync(publicDir)) {
1093
- app.use(express.static(publicDir));
1094
- app.get(/^(.*)$/, (_req, res) => {
1095
- res.sendFile(path6.join(publicDir, "index.html"));
1096
- });
1097
- }
1098
- return app;
1099
- }
1100
-
1101
- // ../application/src/agents/MapperAgent.ts
1102
- import * as fs3 from "fs";
1103
- import * as path2 from "path";
1104
- import {
1105
- createAgent,
1106
- SituationSpecification
1107
- } from "@mozaik-ai/core";
1108
- var WhenMigrationStarts = class extends SituationSpecification {
1109
- isSatisfiedBy({ event }) {
1110
- return event.type === "migration.started" /* MIGRATION_STARTED */;
1111
- }
1112
- };
1113
- var mapFilesProcessor = {
1114
- async apply({ event, participant }) {
1115
- const payload = event.payload;
1116
- console.log(`[MapperAgent] Migration started for Plan: ${payload.planId}`);
1117
- const shadowDir = payload.shadowWorkspacePath || "";
1118
- if (!shadowDir) {
1119
- console.warn("[MapperAgent] No shadow workspace provided. Skipping.");
1120
- return;
1121
- }
1122
- const filesToMigrate = [];
1123
- const scanDir = (dir) => {
1124
- if (!fs3.existsSync(dir)) return;
1125
- const files = fs3.readdirSync(dir);
1126
- const ignoredDirs = ["node_modules", "dist", "build", "out", "coverage", ".next"];
1127
- for (const file of files) {
1128
- if (ignoredDirs.includes(file) || file.startsWith(".")) continue;
1129
- const fullPath = path2.join(dir, file);
1130
- if (fs3.statSync(fullPath).isDirectory()) {
1131
- scanDir(fullPath);
1132
- } else if (file.endsWith(".ts") || file.endsWith(".js") || file.endsWith(".tsx") || file.endsWith(".jsx")) {
1133
- filesToMigrate.push(fullPath);
1134
- }
1135
- }
1136
- };
1137
- scanDir(shadowDir);
1138
- const runtime = resolveRuntime();
1139
- const repository = runtime.state.repository;
1140
- for (const discoveredFile of filesToMigrate) {
1141
- console.log(`[MapperAgent] Discovered file: ${discoveredFile}`);
1142
- await repository.updateTaskStatus(payload.planId, discoveredFile, "pending");
1143
- sendEvent(
1144
- {
1145
- type: "file.discovered" /* FILE_DISCOVERED */,
1146
- producerId: participant.getId(),
1147
- occurredAt: /* @__PURE__ */ new Date(),
1148
- payload: {
1149
- planId: payload.planId,
1150
- filePath: discoveredFile
1151
- }
1152
- },
1153
- participant.getId()
1154
- );
1155
- }
1156
- }
1157
- };
1158
- var mapFilesHandler = {
1159
- specification: new WhenMigrationStarts(),
1160
- processor: mapFilesProcessor
1161
- };
1162
- function createMapperAgent() {
1163
- return createAgent({
1164
- name: "Mapper",
1165
- capabilities: ["code_analysis"],
1166
- instruction: "You are the Architect Mapper. Your job is to analyze the source code and discover files that need migration.",
1167
- tools: [],
1168
- // AST scanning tools will go here later
1169
- handlers: [mapFilesHandler]
1170
- });
1171
- }
1172
-
1173
- // ../application/src/agents/WorkerAgent.ts
1174
- import * as fs4 from "fs";
1175
- import {
1176
- createAgent as createAgent2,
1177
- SituationSpecification as SituationSpecification2
1178
- } from "@mozaik-ai/core";
1179
-
1180
- // ../application/src/utils/FileTreeBuilder.ts
1181
- import * as path3 from "path";
1182
- function getCommonBaseDir(filePaths) {
1183
- if (!filePaths || filePaths.length === 0) return "";
1184
- if (filePaths.length === 1) return path3.dirname(filePaths[0]);
1185
- const splitPaths = filePaths.map((p) => path3.normalize(p).split(path3.sep));
1186
- let commonPath = "";
1187
- for (let i = 0; i < splitPaths[0].length; i++) {
1188
- const part = splitPaths[0][i];
1189
- const isCommon = splitPaths.every((parts) => parts[i] === part);
1190
- if (isCommon) {
1191
- commonPath = commonPath === "" ? part : commonPath + path3.sep + part;
1192
- } else {
1193
- break;
1194
- }
1195
- }
1196
- return commonPath;
1197
- }
1198
- function buildFileTree(filePaths) {
1199
- if (!filePaths || filePaths.length === 0) return "No files found.";
1200
- const baseDir = getCommonBaseDir(filePaths);
1201
- const relativePaths = filePaths.map((p) => path3.relative(baseDir, p)).filter((p) => p.length > 0).sort();
1202
- if (relativePaths.length === 0) return "No files found.";
1203
- const tree = {};
1204
- for (const relPath of relativePaths) {
1205
- const parts = relPath.split(path3.sep);
1206
- let current = tree;
1207
- for (let i = 0; i < parts.length; i++) {
1208
- const part = parts[i];
1209
- if (i === parts.length - 1) {
1210
- current[part] = null;
1211
- } else {
1212
- current[part] = current[part] || {};
1213
- current = current[part];
1214
- }
1215
- }
1216
- }
1217
- let output = `/
1218
- `;
1219
- function renderTree(node, prefix = "") {
1220
- if (node === null) return;
1221
- const keys = Object.keys(node);
1222
- for (let i = 0; i < keys.length; i++) {
1223
- const key = keys[i];
1224
- const isLast = i === keys.length - 1;
1225
- const isFile = node[key] === null;
1226
- const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
1227
- output += `${prefix}${connector}${key}
1228
- `;
1229
- if (!isFile) {
1230
- const nextPrefix = prefix + (isLast ? " " : "\u2502 ");
1231
- renderTree(node[key], nextPrefix);
1232
- }
1233
- }
1234
- }
1235
- renderTree(tree);
1236
- return output.trim();
1237
- }
1238
-
1239
- // ../application/src/agents/WorkerAgent.ts
1240
- var retryCountMap = /* @__PURE__ */ new Map();
1241
- var MAX_RETRIES = 2;
1242
- var ConcurrencyQueue = class {
1243
- constructor(concurrencyLimit) {
1244
- this.concurrencyLimit = concurrencyLimit;
1245
- }
1246
- queue = [];
1247
- activeCount = 0;
1248
- async enqueue(task) {
1249
- this.queue.push(task);
1250
- this.pump();
1251
- }
1252
- async pump() {
1253
- if (this.activeCount >= this.concurrencyLimit || this.queue.length === 0) {
1254
- return;
1255
- }
1256
- const task = this.queue.shift();
1257
- if (task) {
1258
- this.activeCount++;
1259
- try {
1260
- await task();
1261
- } catch (error) {
1262
- console.error("Task error:", error);
1263
- } finally {
1264
- this.activeCount--;
1265
- this.pump();
1266
- }
1267
- }
1268
- }
1269
- };
1270
- var workerQueue = new ConcurrencyQueue(2);
1271
- var WhenFileDiscovered = class extends SituationSpecification2 {
1272
- isSatisfiedBy({ event }) {
1273
- return event.type === "file.discovered" /* FILE_DISCOVERED */;
346
+ var workerQueue = new ConcurrencyQueue(3);
347
+ var WhenFileDiscovered = class extends SituationSpecification2 {
348
+ isSatisfiedBy({ event }) {
349
+ return event.type === "file.discovered" /* FILE_DISCOVERED */;
1274
350
  }
1275
351
  };
1276
352
  var WhenFileRejected = class extends SituationSpecification2 {
@@ -1291,7 +367,7 @@ var WhenInferenceCompleted = class extends SituationSpecification2 {
1291
367
  async function startWorkerLoop(planId, filePath, prompt, participant) {
1292
368
  return new Promise(async (originalResolve) => {
1293
369
  let isDone = false;
1294
- const resolve3 = () => {
370
+ const resolve4 = () => {
1295
371
  if (!isDone) {
1296
372
  isDone = true;
1297
373
  originalResolve();
@@ -1299,32 +375,54 @@ async function startWorkerLoop(planId, filePath, prompt, participant) {
1299
375
  };
1300
376
  const runtime = resolveRuntime();
1301
377
  const repository = runtime.state.repository;
1302
- console.log(`[WorkerAgent] Marking file as 'in_progress' in database...`);
1303
378
  await repository.updateTaskStatus(planId, filePath, "in_progress");
1304
379
  const { createAgent: createMozaikAgent } = await import("@mozaik-ai/core");
380
+ const { findShadowRoot: findShadowRoot2 } = await import("./NextMigrationHints-BGO2W3K4.js");
381
+ const shadowRoot = findShadowRoot2(filePath);
382
+ let workerTools = participant.getTools();
383
+ if (shadowRoot) {
384
+ const { createShadowTools } = await import("./src-JHOGUCRS.js");
385
+ workerTools = createShadowTools(shadowRoot);
386
+ }
1305
387
  const tempAgent = createMozaikAgent({
1306
388
  name: `Worker-${Date.now()}-${Math.floor(Math.random() * 1e3)}`,
1307
389
  capabilities: ["code_refactoring", "inference"],
1308
- instruction: "You are the Programmer Worker. Your job is to refactor specific files based on the migration profile. If the target framework requires a different file structure, you are authorized to use your tools to rename, create, or delete files to adhere to architectural rules. When you are done modifying the file(s), simply finish your response.",
1309
- tools: participant.getTools(),
390
+ instruction: "You are a staff engineer migrating a real codebase. Deduce from files on disk: read the target and every local module it imports before writing. Never invent callback prop names or re-implement a screen you could import. Prefer evidence over catalog examples.",
391
+ tools: workerTools,
1310
392
  handlers: [
1311
393
  {
1312
394
  specification: new WhenInferenceCompleted(),
1313
395
  processor: {
1314
- async apply({ event: completionEvent, participant: tempParticipant }) {
1315
- console.log(`[WorkerAgent:${tempParticipant.getId()}] Inference completed. Emitting FILE_MIGRATED.`);
1316
- let fileContent = "No content available";
396
+ async apply({ participant: tempParticipant }) {
397
+ if (isDone) {
398
+ return;
399
+ }
400
+ const exists = fs3.existsSync(filePath);
401
+ if (workerCompletionKind(exists) === "failed_missing") {
402
+ console.warn(`[WorkerAgent:${tempParticipant.getId()}] File not found on disk after inference: ${filePath}`);
403
+ await repository.updateTaskStatus(planId, filePath, "failed", MISSING_AFTER_WORKER);
404
+ sendEvent(
405
+ {
406
+ type: "file.failed" /* FILE_FAILED */,
407
+ producerId: tempParticipant.getId(),
408
+ occurredAt: /* @__PURE__ */ new Date(),
409
+ payload: { planId, filePath, reason: MISSING_AFTER_WORKER }
410
+ },
411
+ tempParticipant.getId()
412
+ );
413
+ leave(tempParticipant);
414
+ resolve4();
415
+ return;
416
+ }
417
+ let fileContent = "";
1317
418
  try {
1318
- if (fs4.existsSync(filePath)) {
1319
- fileContent = fs4.readFileSync(filePath, "utf-8");
1320
- console.log(`[WorkerAgent:${tempParticipant.getId()}] Read ${fileContent.length} chars from ${filePath}`);
1321
- } else {
1322
- console.warn(`[WorkerAgent:${tempParticipant.getId()}] File not found on disk: ${filePath}`);
1323
- }
419
+ fileContent = fs3.readFileSync(filePath, "utf-8");
420
+ console.log(`[WorkerAgent:${tempParticipant.getId()}] Read ${fileContent.length} chars from ${filePath}`);
1324
421
  } catch (e) {
1325
422
  console.error(`[WorkerAgent:${tempParticipant.getId()}] Error reading file:`, e);
1326
423
  }
1327
- await repository.updateTaskStatus(planId, filePath, "completed");
424
+ console.log(`[WorkerAgent:${tempParticipant.getId()}] Inference completed. Emitting FILE_MIGRATED.`);
425
+ await repository.updateTaskStatus(planId, filePath, "in_progress");
1328
426
  sendEvent(
1329
427
  {
1330
428
  type: "file.migrated" /* FILE_MIGRATED */,
@@ -1339,7 +437,7 @@ async function startWorkerLoop(planId, filePath, prompt, participant) {
1339
437
  tempParticipant.getId()
1340
438
  );
1341
439
  leave(tempParticipant);
1342
- resolve3();
440
+ resolve4();
1343
441
  }
1344
442
  }
1345
443
  }
@@ -1356,19 +454,31 @@ async function startWorkerLoop(planId, filePath, prompt, participant) {
1356
454
  });
1357
455
  setTimeout(async () => {
1358
456
  if (!isDone) {
1359
- console.error(`[WorkerAgent:${tempAgent.getId()}] Inference timed out.`);
1360
- await repository.updateTaskStatus(planId, filePath, "failed", "Inference timed out");
457
+ console.error(`[WorkerAgent:${tempAgent.getId()}] Inference timed out after 120s.`);
458
+ await repository.updateTaskStatus(planId, filePath, "failed", "Inference timed out after 120s");
459
+ sendEvent({
460
+ type: "file.failed" /* FILE_FAILED */,
461
+ producerId: tempAgent.getId(),
462
+ occurredAt: /* @__PURE__ */ new Date(),
463
+ payload: { planId, filePath, reason: "Inference timed out after 120s" }
464
+ }, tempAgent.getId());
1361
465
  leave(tempAgent);
1362
- resolve3();
466
+ resolve4();
1363
467
  }
1364
- }, 45e3);
468
+ }, 12e4);
1365
469
  } catch (error) {
1366
470
  console.error(`[WorkerAgent:${tempAgent.getId()}] Sync error:`, error);
1367
471
  if (!isDone) {
1368
472
  const errorMessage = error instanceof Error ? error.message : String(error);
1369
473
  await repository.updateTaskStatus(planId, filePath, "failed", errorMessage || "Sync error");
474
+ sendEvent({
475
+ type: "file.failed" /* FILE_FAILED */,
476
+ producerId: tempAgent.getId(),
477
+ occurredAt: /* @__PURE__ */ new Date(),
478
+ payload: { planId, filePath, reason: errorMessage || "Sync error" }
479
+ }, tempAgent.getId());
1370
480
  leave(tempAgent);
1371
- resolve3();
481
+ resolve4();
1372
482
  }
1373
483
  }
1374
484
  });
@@ -1384,13 +494,13 @@ var workOnFileProcessor = {
1384
494
  if (plan) {
1385
495
  prompt += `Migration Profile: Transform from ${plan.profile.source} to ${plan.profile.target}.
1386
496
  `;
1387
- const catalogEntry = findMigrationCatalogEntry(plan.profile.source, plan.profile.target);
497
+ const catalogEntry = resolveMigrationCatalog(plan.profile.source, plan.profile.target);
1388
498
  if (catalogEntry) {
1389
499
  prompt += `
1390
- Strict Architectural Rules for this Migration:
500
+ Architectural rules (all \u2192 ${catalogEntry.layer} \u2192 frameworks \u2192 this pair):
501
+ `;
502
+ prompt += `${formatCatalogRules(catalogEntry.ruleSections)}
1391
503
  `;
1392
- catalogEntry.architecturalRules.forEach((rule) => prompt += `- ${rule}
1393
- `);
1394
504
  if (catalogEntry.examples && catalogEntry.examples.length > 0) {
1395
505
  prompt += `
1396
506
  Reference Examples:
@@ -1427,8 +537,20 @@ ${projectTree}
1427
537
  prompt += `Use this context to ensure your changes align with the overall project structure and other files.
1428
538
  `;
1429
539
  }
540
+ prompt += collectFileHints(
541
+ payload.filePath,
542
+ { source: plan.profile.source, target: plan.profile.target },
543
+ catalogEntry?.layer
544
+ );
1430
545
  }
1431
- prompt += `Please read the file using your tools, rewrite it according to the rules, and write it back. If the rules require breaking this file into multiple files, use your create, rename, or delete file tools accordingly.`;
546
+ prompt += buildNeighborContext(payload.filePath);
547
+ prompt += `
548
+ You are not a mechanical find-replace bot. Deduce the correct change from the evidence above and from tools (read_file, list_directory).
549
+ Before writing:
550
+ 1. Read every local import you will call.
551
+ 2. Keep that module's public prop/export names exactly.
552
+ 3. If this is a router/bootstrap file, import an existing screen from the nearby paths \u2014 do not paste a new copy of its JSX.
553
+ Then write the file(s). If structure must change, create/rename/delete accordingly.`;
1432
554
  workerQueue.enqueue(async () => {
1433
555
  await startWorkerLoop(payload.planId, payload.filePath, prompt, participant);
1434
556
  });
@@ -1438,22 +560,39 @@ var fixRejectedFileProcessor = {
1438
560
  async apply({ event, participant }) {
1439
561
  const payload = event.payload;
1440
562
  const retryKey = `${payload.planId}:${payload.filePath}`;
1441
- const currentRetries = retryCountMap.get(retryKey) || 0;
563
+ const retries = resolveRuntime().state.retryCounts;
564
+ if (payload.source === "integration") {
565
+ retries.delete(retryKey);
566
+ }
567
+ const currentRetries = retries.get(retryKey) || 0;
1442
568
  if (currentRetries >= MAX_RETRIES) {
1443
569
  console.warn(`[WorkerAgent] File ${payload.filePath} exceeded max retries (${MAX_RETRIES}). Marking as failed.`);
1444
570
  const runtime2 = resolveRuntime();
1445
571
  await runtime2.state.repository.updateTaskStatus(payload.planId, payload.filePath, "failed", `Exceeded max retries (${MAX_RETRIES})`);
1446
- retryCountMap.delete(retryKey);
572
+ retries.delete(retryKey);
573
+ sendEvent({
574
+ type: "file.failed" /* FILE_FAILED */,
575
+ producerId: participant.getId(),
576
+ occurredAt: /* @__PURE__ */ new Date(),
577
+ payload: { planId: payload.planId, filePath: payload.filePath, reason: `Exceeded max retries (${MAX_RETRIES})` }
578
+ }, participant.getId());
1447
579
  return;
1448
580
  }
1449
- retryCountMap.set(retryKey, currentRetries + 1);
581
+ retries.set(retryKey, currentRetries + 1);
1450
582
  console.log(`[WorkerAgent] File REJECTED (attempt ${currentRetries + 1}/${MAX_RETRIES}), initiating repair loop: ${payload.filePath}`);
1451
583
  const runtime = resolveRuntime();
1452
584
  const plan = await runtime.state.repository.getPlan(payload.planId);
1453
585
  let prompt = `You need to FIX the file at path: ${payload.filePath}
1454
586
  `;
1455
- prompt += `Your previous migration was REJECTED by the Quality Assurance Reviewer.
587
+ if (payload.source === "integration") {
588
+ prompt += `The Integration Agent rejected this file because the shadow workspace failed npm install / npm run build, or a catalog verifier flagged it.
589
+ `;
590
+ prompt += `If the file does not exist yet, create it. If it is a router/bootstrap file, import existing screens \u2014 do not re-implement them or rename their callback props.
1456
591
  `;
592
+ } else {
593
+ prompt += `Your previous migration was REJECTED by the Quality Assurance Reviewer.
594
+ `;
595
+ }
1457
596
  prompt += `Feedback/Errors:
1458
597
  ${payload.errors.join("\n")}
1459
598
 
@@ -1461,13 +600,13 @@ ${payload.errors.join("\n")}
1461
600
  if (plan) {
1462
601
  prompt += `Migration Profile: Transform from ${plan.profile.source} to ${plan.profile.target}.
1463
602
  `;
1464
- const catalogEntry = findMigrationCatalogEntry(plan.profile.source, plan.profile.target);
603
+ const catalogEntry = resolveMigrationCatalog(plan.profile.source, plan.profile.target);
1465
604
  if (catalogEntry) {
1466
605
  prompt += `
1467
- Strict Architectural Rules for this Migration:
606
+ Architectural rules (all \u2192 ${catalogEntry.layer} \u2192 frameworks \u2192 this pair):
607
+ `;
608
+ prompt += `${formatCatalogRules(catalogEntry.ruleSections)}
1468
609
  `;
1469
- catalogEntry.architecturalRules.forEach((rule) => prompt += `- ${rule}
1470
- `);
1471
610
  }
1472
611
  if (plan.tasks && plan.tasks.length > 0) {
1473
612
  const allFiles = plan.tasks.map((t) => t.filePath);
@@ -1482,19 +621,26 @@ ${projectTree}
1482
621
  prompt += `Ensure you respect this overall project structure while fixing the issues.
1483
622
  `;
1484
623
  }
624
+ prompt += collectFileHints(
625
+ payload.filePath,
626
+ { source: plan.profile.source, target: plan.profile.target },
627
+ catalogEntry?.layer
628
+ );
1485
629
  }
1486
- prompt += `Please read the file, fix the issues mentioned, and write it back. You may restructure the files if the architectural rules demand it.`;
630
+ prompt += buildNeighborContext(payload.filePath);
631
+ prompt += `
632
+ Deduce the fix from the errors and the disk evidence. Read imported modules (and list_directory if you need another screen). Do not guess APIs. Then write the corrected file(s). If the path does not exist, create it. Do not delete existing screens just to make a router file compile.`;
1487
633
  workerQueue.enqueue(async () => {
1488
634
  await startWorkerLoop(payload.planId, payload.filePath, prompt, participant);
1489
635
  });
1490
636
  }
1491
637
  };
1492
638
  var fatalMismatchProcessor = {
1493
- async apply({ event }) {
639
+ async apply({ event, participant }) {
1494
640
  const payload = event.payload;
1495
641
  console.error(`[WorkerAgent] \u{1F6D1} FATAL MISMATCH received for ${payload.filePath}. Reason: ${payload.reason}`);
1496
642
  const retryKey = `${payload.planId}:${payload.filePath}`;
1497
- retryCountMap.delete(retryKey);
643
+ resolveRuntime().state.retryCounts.delete(retryKey);
1498
644
  const runtime = resolveRuntime();
1499
645
  await runtime.state.repository.updateTaskStatus(
1500
646
  payload.planId,
@@ -1502,6 +648,12 @@ var fatalMismatchProcessor = {
1502
648
  "failed",
1503
649
  `Fatal Architectural Mismatch: ${payload.reason}`
1504
650
  );
651
+ sendEvent({
652
+ type: "file.failed" /* FILE_FAILED */,
653
+ producerId: participant.getId(),
654
+ occurredAt: /* @__PURE__ */ new Date(),
655
+ payload: { planId: payload.planId, filePath: payload.filePath, reason: payload.reason }
656
+ }, participant.getId());
1505
657
  }
1506
658
  };
1507
659
  function createWorkerAgent(tools) {
@@ -1523,6 +675,116 @@ import {
1523
675
  createAgent as createAgent3,
1524
676
  SituationSpecification as SituationSpecification3
1525
677
  } from "@mozaik-ai/core";
678
+ import * as path5 from "path";
679
+
680
+ // ../application/src/utils/classifyMissingFile.ts
681
+ import * as fs4 from "fs";
682
+ import * as path4 from "path";
683
+ var NEXT_CONFIG_NAMES = [
684
+ "next.config.js",
685
+ "next.config.mjs",
686
+ "next.config.ts",
687
+ "next.config.cjs"
688
+ ];
689
+ var CONFIG_FILE_PATTERN = /[/\\](vite\.config|craco\.config|webpack\.config)\.(js|ts|mjs|cjs)$/i;
690
+ function classifyMissingFile(input) {
691
+ const { filePath, target } = input;
692
+ const fileExists = input.fileExists || fs4.existsSync;
693
+ const shadowRoot = input.shadowRoot !== void 0 ? input.shadowRoot : findShadowRoot(filePath);
694
+ const normalizedTarget = target.toLowerCase();
695
+ const normalizedPath = filePath.replace(/\\/g, "/");
696
+ if (CONFIG_FILE_PATTERN.test(filePath)) {
697
+ if (normalizedTarget === "next" && shadowRoot) {
698
+ const hasNextConfig = NEXT_CONFIG_NAMES.some(
699
+ (cfg) => fileExists(path4.join(shadowRoot, cfg))
700
+ );
701
+ if (hasNextConfig) {
702
+ return {
703
+ verdict: "approved",
704
+ reason: `Config file removed because a Next.js configuration exists in shadow workspace.`
705
+ };
706
+ }
707
+ return {
708
+ verdict: "rejected",
709
+ errors: [
710
+ `Configuration file was removed, but no Next.js configuration (next.config.js/mjs/ts) was found in the project root.`
711
+ ]
712
+ };
713
+ }
714
+ }
715
+ if (/[/\\]index\.html$/i.test(filePath)) {
716
+ if (normalizedTarget === "next" && shadowRoot) {
717
+ const layoutCandidates = [
718
+ path4.join(shadowRoot, "src", "app", "layout.tsx"),
719
+ path4.join(shadowRoot, "src", "app", "layout.jsx"),
720
+ path4.join(shadowRoot, "src", "app", "layout.js"),
721
+ path4.join(shadowRoot, "app", "layout.tsx"),
722
+ path4.join(shadowRoot, "app", "layout.jsx"),
723
+ path4.join(shadowRoot, "app", "layout.js"),
724
+ path4.join(shadowRoot, "src", "pages", "_document.tsx"),
725
+ path4.join(shadowRoot, "pages", "_document.tsx")
726
+ ];
727
+ const hasLayout = layoutCandidates.some((candidate) => fileExists(candidate));
728
+ if (hasLayout) {
729
+ return {
730
+ verdict: "approved",
731
+ reason: `index.html was removed because Next.js root layout or _document exists.`
732
+ };
733
+ }
734
+ return {
735
+ verdict: "rejected",
736
+ errors: [
737
+ `index.html was removed, but no Next.js root layout (app/layout.tsx) or _document was created to provide the HTML structure.`
738
+ ]
739
+ };
740
+ }
741
+ }
742
+ if (isSpaRootComponent(filePath)) {
743
+ if (normalizedTarget === "next") {
744
+ const hasRoutes = shadowRoot ? hasNextAppRoutes(shadowRoot) : false;
745
+ if (hasRoutes) {
746
+ return {
747
+ verdict: "approved",
748
+ reason: `SPA root component was removed because Next.js App Router routes exist.`
749
+ };
750
+ }
751
+ return {
752
+ verdict: "rejected",
753
+ errors: [
754
+ `The SPA root was deleted before src/app/**/page.tsx existed with real UI. Recreate routing as Next App Router pages that import the original screen components. Do not import App from those pages.`
755
+ ]
756
+ };
757
+ }
758
+ }
759
+ if (isFsdPageComponent(filePath) || isSpaPagesTreeFile(filePath)) {
760
+ if (normalizedTarget === "next") {
761
+ const viewsReplacement = filePath.replace(/([/\\])src\1pages\1/, "$1src$1views$1");
762
+ const screensReplacement = filePath.replace(/([/\\])src\1pages\1/, "$1src$1screens$1");
763
+ const compReplacement = filePath.replace(/([/\\])src\1pages\1/, "$1src$1components$1pages$1");
764
+ if (fileExists(viewsReplacement) || fileExists(screensReplacement) || fileExists(compReplacement)) {
765
+ return {
766
+ verdict: "approved",
767
+ reason: `File moved out of src/pages into a non-reserved directory for Next.js.`
768
+ };
769
+ }
770
+ return {
771
+ verdict: "rejected",
772
+ errors: [
773
+ `This file lived under src/pages as SPA UI. Next.js reserves src/pages for the Pages Router \u2014 move the tree to a non-reserved folder (e.g. src/views) and import those screens from src/app. Do not delete the UI without a replacement.`
774
+ ]
775
+ };
776
+ }
777
+ }
778
+ const fileName = path4.basename(filePath);
779
+ return {
780
+ verdict: "rejected",
781
+ errors: [
782
+ `File "${fileName}" does not exist on disk and has no demonstrable replacement or migration successor. Code migrations must preserve UI or migrate to a target equivalent.`
783
+ ]
784
+ };
785
+ }
786
+
787
+ // ../application/src/agents/ReviewerAgent.ts
1526
788
  var ConcurrencyQueue2 = class {
1527
789
  constructor(concurrencyLimit) {
1528
790
  this.concurrencyLimit = concurrencyLimit;
@@ -1551,7 +813,7 @@ var ConcurrencyQueue2 = class {
1551
813
  }
1552
814
  }
1553
815
  };
1554
- var reviewerQueue = new ConcurrencyQueue2(2);
816
+ var reviewerQueue = new ConcurrencyQueue2(3);
1555
817
  var WhenFileMigrated = class extends SituationSpecification3 {
1556
818
  isSatisfiedBy({ event }) {
1557
819
  return event.type === "file.migrated" /* FILE_MIGRATED */;
@@ -1559,15 +821,9 @@ var WhenFileMigrated = class extends SituationSpecification3 {
1559
821
  };
1560
822
  var WhenReviewCompleted = class extends SituationSpecification3 {
1561
823
  isSatisfiedBy({ event, participant }) {
1562
- console.log(`[DEBUG:WhenReviewCompleted] Event received -> type: "${event.type}", producerId: "${event.producerId}", participantId: "${participant.getId()}", match: ${event.type === "model.answer" && event.producerId === participant.getId()}`);
1563
824
  return event.type === "model.answer" && event.producerId === participant.getId();
1564
825
  }
1565
826
  };
1566
- var WhenAnythingHappens = class extends SituationSpecification3 {
1567
- isSatisfiedBy() {
1568
- return true;
1569
- }
1570
- };
1571
827
  var reviewFileProcessor = {
1572
828
  async apply({ event, participant }) {
1573
829
  const payload = event.payload;
@@ -1575,56 +831,46 @@ var reviewFileProcessor = {
1575
831
  reviewerQueue.enqueue(async () => {
1576
832
  return new Promise(async (originalResolve) => {
1577
833
  let isDone = false;
1578
- const resolve3 = () => {
834
+ const resolve4 = () => {
1579
835
  if (!isDone) {
1580
836
  isDone = true;
1581
- console.log(`[DEBUG:ReviewerAgent] Promise resolved for ${payload.filePath}`);
1582
837
  originalResolve();
1583
838
  }
1584
839
  };
1585
840
  const { createAgent: createMozaikAgent } = await import("@mozaik-ai/core");
1586
- const { sendEvent: sendEvent3 } = await import("./runtime-5OVGH2Y7.js");
841
+ const { sendEvent: sendEvent3 } = await import("./runtime-L7VOQROE.js");
1587
842
  const reviewerId = `Reviewer-${Date.now()}-${Math.floor(Math.random() * 1e3)}`;
1588
843
  const tempAgent = createMozaikAgent({
1589
844
  name: reviewerId,
1590
845
  capabilities: ["code_review", "inference"],
1591
- instruction: "You are the Quality Assurance Reviewer. You must review the migrated code. Note that you are reviewing a SINGLE FILE within a larger migration. Evaluate if the file violates architectural rules, but DO NOT reject it simply because it does not demonstrate all architectural features (e.g. do not reject a test file or a bootstrap file just because it lacks a Controller). If the worker completely violated the architectural paradigm in a way that cannot be fixed by a simple code edit, use status FATAL_MISMATCH to abort.",
846
+ instruction: "You are a staff engineer reviewing a migrated file against its neighbors. Reject if public props/emits do not match imported modules, if a router file re-implements a screen instead of importing it, or if a screen was hollowed out. UI that lived under src/pages in a SPA is not a Next Pages Router file. Do not reject a file just because it lacks every catalog feature. FATAL_MISMATCH only for an unfixable paradigm break.",
1592
847
  tools: [],
1593
848
  // No tools to avoid Gemini thought_signature crash
1594
849
  handlers: [
1595
- // DEBUG: Catch-all handler to log every event this agent receives
1596
- {
1597
- specification: new WhenAnythingHappens(),
1598
- processor: {
1599
- async apply({ event: anyEvent, participant: anyParticipant }) {
1600
- console.log(`[DEBUG:ReviewerAgent:${reviewerId}:CATCH_ALL] Event -> type: "${anyEvent.type}", producerId: "${anyEvent.producerId}", myId: "${anyParticipant.getId()}", payload keys: ${Object.keys(anyEvent.payload || {}).join(",")}`);
1601
- }
1602
- }
1603
- },
1604
850
  {
1605
851
  specification: new WhenReviewCompleted(),
1606
852
  processor: {
1607
853
  async apply({ event: event2, participant: tempParticipant }) {
1608
- console.log(`[DEBUG:ReviewerAgent:${reviewerId}] model.answer RECEIVED!`);
854
+ if (isDone) {
855
+ return;
856
+ }
1609
857
  const answerItem = event2.payload.answer;
1610
858
  const answerText = answerItem?.content?.text || answerItem?.text || null;
1611
- console.log(`[DEBUG:ReviewerAgent:${reviewerId}] answerText:`, answerText);
1612
859
  let reviewResult = null;
1613
860
  if (answerText) {
1614
861
  try {
1615
862
  reviewResult = JSON.parse(answerText);
1616
- console.log(`[DEBUG:ReviewerAgent:${reviewerId}] Parsed result:`, JSON.stringify(reviewResult));
1617
863
  } catch (e) {
1618
864
  console.error(`[ReviewerAgent:${reviewerId}] Failed to parse JSON:`, e);
1619
865
  }
1620
- } else {
1621
- console.warn(`[DEBUG:ReviewerAgent:${reviewerId}] answerItem has no extractable text.`);
1622
866
  }
1623
867
  if (reviewResult) {
1624
868
  const participantId = tempParticipant.getId();
1625
869
  try {
1626
870
  if (reviewResult.status === "APPROVED") {
1627
871
  console.log(`[ReviewerAgent:${reviewerId}] File ${payload.filePath} APPROVED!`);
872
+ const runtime2 = await resolveRuntime();
873
+ await runtime2.state.repository.updateTaskStatus(payload.planId, payload.filePath, "completed");
1628
874
  sendEvent3({
1629
875
  type: "file.reviewed" /* FILE_REVIEWED */,
1630
876
  producerId: participantId,
@@ -1651,46 +897,66 @@ var reviewFileProcessor = {
1651
897
  } catch (e) {
1652
898
  console.error(`[ReviewerAgent:${reviewerId}] Failed to send review event:`, e);
1653
899
  }
1654
- try {
1655
- const runtime2 = await resolveRuntime();
1656
- const plan2 = await runtime2.state.repository.getPlan(payload.planId);
1657
- if (plan2) {
1658
- const allTerminal = plan2.tasks.every((t) => t.status === "completed" || t.status === "failed");
1659
- if (allTerminal) {
1660
- console.log(`[App] All tasks reached terminal state. Emitting MIGRATION_COMPLETED.`);
1661
- sendEvent3({
1662
- type: "migration.completed" /* MIGRATION_COMPLETED */,
1663
- producerId: participantId,
1664
- occurredAt: /* @__PURE__ */ new Date(),
1665
- payload: { planId: payload.planId }
1666
- }, participantId);
1667
- }
1668
- }
1669
- } catch (e) {
1670
- console.error(`[ReviewerAgent:${reviewerId}] Failed to check migration completion:`, e);
1671
- }
1672
900
  }
1673
901
  leave(tempParticipant);
1674
- resolve3();
902
+ resolve4();
1675
903
  }
1676
904
  }
1677
905
  }
1678
906
  ]
1679
907
  });
1680
- console.log(`[DEBUG:ReviewerAgent:${reviewerId}] tempAgent created with id: ${tempAgent.getId()}`);
1681
908
  join(tempAgent);
1682
- console.log(`[DEBUG:ReviewerAgent:${reviewerId}] tempAgent joined the runtime`);
1683
909
  const runtime = resolveRuntime();
910
+ const { existsSync: existsSync8 } = await import("fs");
911
+ if (!existsSync8(payload.filePath)) {
912
+ const { sendEvent: sendEvent4 } = await import("./runtime-L7VOQROE.js");
913
+ const planForMissing = await runtime.state.repository.getPlan(payload.planId);
914
+ const sourceFramework = planForMissing?.profile.source || "react";
915
+ const targetFramework = planForMissing?.profile.target || "next";
916
+ const shadowRoot = findShadowRoot(payload.filePath);
917
+ const classification = classifyMissingFile({
918
+ filePath: payload.filePath,
919
+ source: sourceFramework,
920
+ target: targetFramework,
921
+ shadowRoot,
922
+ fileExists: existsSync8
923
+ });
924
+ if (classification.verdict === "approved") {
925
+ console.log(`[ReviewerAgent:${reviewerId}] Missing ${payload.filePath} approved: ${classification.reason}`);
926
+ await runtime.state.repository.updateTaskStatus(payload.planId, payload.filePath, "completed");
927
+ sendEvent4({
928
+ type: "file.reviewed" /* FILE_REVIEWED */,
929
+ producerId: tempAgent.getId(),
930
+ occurredAt: /* @__PURE__ */ new Date(),
931
+ payload: { planId: payload.planId, filePath: payload.filePath }
932
+ }, tempAgent.getId());
933
+ } else {
934
+ console.log(`[ReviewerAgent:${reviewerId}] Missing ${payload.filePath} rejected:`, classification.errors);
935
+ sendEvent4({
936
+ type: "file.rejected" /* FILE_REJECTED */,
937
+ producerId: tempAgent.getId(),
938
+ occurredAt: /* @__PURE__ */ new Date(),
939
+ payload: {
940
+ planId: payload.planId,
941
+ filePath: payload.filePath,
942
+ errors: classification.errors || ["File does not exist and has no demonstrable replacement"]
943
+ }
944
+ }, tempAgent.getId());
945
+ }
946
+ leave(tempAgent);
947
+ resolve4();
948
+ return;
949
+ }
1684
950
  if (payload.filePath.endsWith(".ts") || payload.filePath.endsWith(".tsx")) {
1685
951
  try {
1686
- const { Project: Project2 } = await import("ts-morph");
1687
- const tsProject = new Project2();
952
+ const { Project } = await import("ts-morph");
953
+ const tsProject = new Project();
1688
954
  const sf = tsProject.addSourceFileAtPath(payload.filePath);
1689
955
  const diagnostics = sf.getPreEmitDiagnostics();
1690
956
  const syntaxErrors = diagnostics.filter((d) => d.getCode() >= 1e3 && d.getCode() < 2e3);
1691
957
  if (syntaxErrors.length > 0) {
1692
958
  console.error(`[ReviewerAgent:${reviewerId}] Syntax Error in ${payload.filePath}. Rejecting immediately.`);
1693
- const { sendEvent: sendEvent4 } = await import("./runtime-5OVGH2Y7.js");
959
+ const { sendEvent: sendEvent4 } = await import("./runtime-L7VOQROE.js");
1694
960
  sendEvent4({
1695
961
  type: "file.rejected" /* FILE_REJECTED */,
1696
962
  producerId: tempAgent.getId(),
@@ -1702,7 +968,7 @@ var reviewFileProcessor = {
1702
968
  }
1703
969
  }, tempAgent.getId());
1704
970
  leave(tempAgent);
1705
- resolve3();
971
+ resolve4();
1706
972
  return;
1707
973
  }
1708
974
  } catch (e) {
@@ -1710,28 +976,55 @@ var reviewFileProcessor = {
1710
976
  }
1711
977
  }
1712
978
  const plan = await runtime.state.repository.getPlan(payload.planId);
979
+ if (plan && plan.profile.source.toLowerCase() === "react" && plan.profile.target.toLowerCase() === "next") {
980
+ const shadowRoot = findShadowRoot(payload.filePath);
981
+ if (shadowRoot) {
982
+ const current = path5.resolve(payload.filePath);
983
+ const structureIssues = analyzeReactToNextStructure(shadowRoot).filter(
984
+ (issue) => path5.resolve(issue.filePath) === current
985
+ );
986
+ if (structureIssues.length > 0) {
987
+ const errors = structureIssues.flatMap((issue) => issue.errors);
988
+ console.error(`[ReviewerAgent:${reviewerId}] Structure verifier rejected ${payload.filePath}.`, errors);
989
+ sendEvent3({
990
+ type: "file.rejected" /* FILE_REJECTED */,
991
+ producerId: tempAgent.getId(),
992
+ occurredAt: /* @__PURE__ */ new Date(),
993
+ payload: { planId: payload.planId, filePath: payload.filePath, errors }
994
+ }, tempAgent.getId());
995
+ leave(tempAgent);
996
+ resolve4();
997
+ return;
998
+ }
999
+ }
1000
+ }
1713
1001
  let prompt = `Review the following file migration: ${payload.filePath}
1714
1002
  `;
1715
1003
  if (plan) {
1716
1004
  prompt += `Migration Profile: Transform from ${plan.profile.source} to ${plan.profile.target}.
1717
1005
  `;
1718
- const catalogEntry = findMigrationCatalogEntry(plan.profile.source, plan.profile.target);
1006
+ const catalogEntry = resolveMigrationCatalog(plan.profile.source, plan.profile.target);
1719
1007
  if (catalogEntry) {
1720
1008
  prompt += `
1721
- Strict Architectural Rules for this Migration (apply only if relevant to this file's purpose):
1009
+ Architectural rules (all \u2192 ${catalogEntry.layer} \u2192 frameworks \u2192 this pair; apply only if relevant to this file):
1010
+ `;
1011
+ prompt += `${formatCatalogRules(catalogEntry.ruleSections)}
1722
1012
  `;
1723
- catalogEntry.architecturalRules.forEach((rule) => prompt += `- ${rule}
1724
- `);
1725
1013
  }
1014
+ prompt += collectFileHints(
1015
+ payload.filePath,
1016
+ { source: plan.profile.source, target: plan.profile.target },
1017
+ catalogEntry?.layer
1018
+ );
1726
1019
  }
1727
1020
  prompt += `
1728
- Diff or new content:
1021
+ ${buildNeighborContext(payload.filePath)}
1022
+ Diff or new content of the file under review:
1729
1023
  ${payload.diff}
1730
- Does it meet the criteria? You MUST respond in JSON.`;
1731
- const modelToUse = process.env.METAMORPH_REVIEWER_MODEL || process.env.METAMORPH_MODEL || "gpt-5.4-mini";
1024
+ Compare the diff to imported modules in the evidence. If props/exports do not match, REJECT with a specific error. You MUST respond in JSON.`;
1025
+ const modelToUse = process.env.METAMORPH_REVIEWER_MODEL || process.env.METAMORPH_MODEL || "gpt-5.4";
1732
1026
  console.log(`[ReviewerAgent:${reviewerId}] Starting inference loop for ${payload.filePath} with model ${modelToUse}...`);
1733
1027
  try {
1734
- console.log(`[DEBUG:ReviewerAgent:${reviewerId}] Calling runLoop with agentId: ${tempAgent.getId()}`);
1735
1028
  runLoop(tempAgent.getId(), prompt, {
1736
1029
  model: modelToUse,
1737
1030
  context: tempAgent.getMemory().getContext(),
@@ -1749,329 +1042,1694 @@ Does it meet the criteria? You MUST respond in JSON.`;
1749
1042
  strict: true
1750
1043
  }
1751
1044
  });
1752
- console.log(`[DEBUG:ReviewerAgent:${reviewerId}] runLoop called successfully (fire-and-forget)`);
1753
1045
  setTimeout(async () => {
1754
1046
  if (!isDone) {
1755
- console.error(`[ReviewerAgent:${reviewerId}] \u23F0 TIMEOUT after 45s for ${payload.filePath}. Model never responded.`);
1756
- const { resolveRuntime: resolveRuntime2 } = await import("./runtime-5OVGH2Y7.js");
1047
+ console.error(`[ReviewerAgent:${reviewerId}] \u23F0 TIMEOUT after 120s for ${payload.filePath}. Model never responded.`);
1048
+ const { resolveRuntime: resolveRuntime2 } = await import("./runtime-L7VOQROE.js");
1757
1049
  const runtime2 = resolveRuntime2();
1758
- await runtime2.state.repository.updateTaskStatus(payload.planId, payload.filePath, "failed", "Inference timed out after 45s");
1050
+ await runtime2.state.repository.updateTaskStatus(payload.planId, payload.filePath, "failed", "Inference timed out after 120s");
1051
+ sendEvent3({
1052
+ type: "file.failed" /* FILE_FAILED */,
1053
+ producerId: tempAgent.getId(),
1054
+ occurredAt: /* @__PURE__ */ new Date(),
1055
+ payload: { planId: payload.planId, filePath: payload.filePath, reason: "Inference timed out after 120s" }
1056
+ }, tempAgent.getId());
1759
1057
  leave(tempAgent);
1760
- resolve3();
1058
+ resolve4();
1761
1059
  }
1762
- }, 45e3);
1060
+ }, 12e4);
1763
1061
  } catch (error) {
1764
1062
  console.error(`[ReviewerAgent:${reviewerId}] Sync error:`, error);
1765
1063
  if (!isDone) {
1766
- const { resolveRuntime: resolveRuntime2 } = await import("./runtime-5OVGH2Y7.js");
1064
+ const { resolveRuntime: resolveRuntime2 } = await import("./runtime-L7VOQROE.js");
1767
1065
  const runtime2 = resolveRuntime2();
1768
1066
  const errorMessage = error instanceof Error ? error.message : String(error);
1769
1067
  await runtime2.state.repository.updateTaskStatus(payload.planId, payload.filePath, "failed", errorMessage || "Sync error");
1068
+ sendEvent3({
1069
+ type: "file.failed" /* FILE_FAILED */,
1070
+ producerId: tempAgent.getId(),
1071
+ occurredAt: /* @__PURE__ */ new Date(),
1072
+ payload: { planId: payload.planId, filePath: payload.filePath, reason: errorMessage || "Sync error" }
1073
+ }, tempAgent.getId());
1770
1074
  leave(tempAgent);
1771
- resolve3();
1075
+ resolve4();
1772
1076
  }
1773
1077
  }
1774
1078
  });
1775
1079
  });
1776
1080
  }
1777
1081
  };
1778
- var reviewFileHandler = {
1779
- specification: new WhenFileMigrated(),
1780
- processor: reviewFileProcessor
1781
- };
1782
- function createReviewerAgent(tools) {
1783
- return createAgent3({
1784
- name: "Reviewer",
1785
- capabilities: ["code_review", "inference"],
1786
- instruction: "You are the Reviewer Worker.",
1082
+ var reviewFileHandler = {
1083
+ specification: new WhenFileMigrated(),
1084
+ processor: reviewFileProcessor
1085
+ };
1086
+ function createReviewerAgent(tools) {
1087
+ return createAgent3({
1088
+ name: "Reviewer",
1089
+ capabilities: ["code_review", "inference"],
1090
+ instruction: "You are the Reviewer Worker.",
1091
+ tools: [],
1092
+ // No tools
1093
+ handlers: [reviewFileHandler]
1094
+ });
1095
+ }
1096
+
1097
+ // ../application/src/agents/PackageManagerAgent.ts
1098
+ import * as fs5 from "fs";
1099
+ import * as path6 from "path";
1100
+ import {
1101
+ createAgent as createAgent4,
1102
+ SituationSpecification as SituationSpecification4
1103
+ } from "@mozaik-ai/core";
1104
+ var WhenMigrationStarts2 = class extends SituationSpecification4 {
1105
+ isSatisfiedBy({ event }) {
1106
+ return event.type === "migration.started" /* MIGRATION_STARTED */;
1107
+ }
1108
+ };
1109
+ var managePackagesProcessor = {
1110
+ async apply({ event, participant }) {
1111
+ const payload = event.payload;
1112
+ console.log(`[PackageManagerAgent] Managing dependencies and cleanup for Plan: ${payload.planId}`);
1113
+ const runtime = resolveRuntime();
1114
+ const emitPackagesReady = async (status = "completed") => {
1115
+ const currentPlan = await runtime.state.repository.getPlan(payload.planId);
1116
+ if (currentPlan) {
1117
+ const pmTaskToComplete = currentPlan.tasks.find((t) => t.filePath === "system:package_manager");
1118
+ if (pmTaskToComplete) {
1119
+ pmTaskToComplete.status = status;
1120
+ await runtime.state.repository.savePlan(currentPlan);
1121
+ }
1122
+ }
1123
+ sendEvent({
1124
+ type: "phase.packages_ready" /* PHASE_PACKAGES_READY */,
1125
+ producerId: participant.getId(),
1126
+ occurredAt: /* @__PURE__ */ new Date(),
1127
+ payload: { planId: payload.planId }
1128
+ }, participant.getId());
1129
+ };
1130
+ const shadowDir = payload.shadowWorkspacePath || "";
1131
+ if (!shadowDir || !fs5.existsSync(shadowDir)) {
1132
+ console.warn("[PackageManagerAgent] No valid shadow workspace provided. Skipping.");
1133
+ await emitPackagesReady("failed");
1134
+ return;
1135
+ }
1136
+ const plan = await runtime.state.repository.getPlan(payload.planId);
1137
+ if (!plan) {
1138
+ console.warn("[PackageManagerAgent] Migration plan not found.");
1139
+ await emitPackagesReady("failed");
1140
+ return;
1141
+ }
1142
+ const catalogEntry = resolveMigrationCatalog(plan.profile.source, plan.profile.target);
1143
+ if (!catalogEntry) {
1144
+ console.log(`[PackageManagerAgent] No catalog entry found for ${plan.profile.source} -> ${plan.profile.target}`);
1145
+ await emitPackagesReady("failed");
1146
+ return;
1147
+ }
1148
+ const pmTask = plan.tasks.find((t) => t.filePath === "system:package_manager");
1149
+ if (pmTask) {
1150
+ pmTask.status = "in_progress";
1151
+ await runtime.state.repository.savePlan(plan);
1152
+ }
1153
+ if (catalogEntry.filesToDelete && catalogEntry.filesToDelete.length > 0) {
1154
+ for (const fileToDelete of catalogEntry.filesToDelete) {
1155
+ const fullPath = path6.join(shadowDir, fileToDelete);
1156
+ if (fs5.existsSync(fullPath)) {
1157
+ fs5.unlinkSync(fullPath);
1158
+ console.log(`[PackageManagerAgent] Deleted deprecated file: ${fileToDelete}`);
1159
+ }
1160
+ }
1161
+ }
1162
+ if (catalogEntry.filesToScaffold) {
1163
+ for (const [filename, content] of Object.entries(catalogEntry.filesToScaffold)) {
1164
+ const fullPath = path6.join(shadowDir, filename);
1165
+ if (!fs5.existsSync(fullPath)) {
1166
+ fs5.mkdirSync(path6.dirname(fullPath), { recursive: true });
1167
+ fs5.writeFileSync(fullPath, content, "utf-8");
1168
+ console.log(`[PackageManagerAgent] Scaffolded missing structural file: ${filename}`);
1169
+ }
1170
+ }
1171
+ }
1172
+ const packageJsonPath = path6.join(shadowDir, "package.json");
1173
+ if (fs5.existsSync(packageJsonPath)) {
1174
+ try {
1175
+ const pkgRaw = fs5.readFileSync(packageJsonPath, "utf-8");
1176
+ const pkg = JSON.parse(pkgRaw);
1177
+ if (!pkg.dependencies) pkg.dependencies = {};
1178
+ if (!pkg.devDependencies) pkg.devDependencies = {};
1179
+ if (catalogEntry.dependenciesToRemove) {
1180
+ for (const dep of catalogEntry.dependenciesToRemove) {
1181
+ delete pkg.dependencies[dep];
1182
+ delete pkg.devDependencies[dep];
1183
+ }
1184
+ }
1185
+ if (catalogEntry.devDependenciesToRemove) {
1186
+ for (const dep of catalogEntry.devDependenciesToRemove) {
1187
+ delete pkg.dependencies[dep];
1188
+ delete pkg.devDependencies[dep];
1189
+ }
1190
+ }
1191
+ if (catalogEntry.dependenciesToAdd) {
1192
+ for (const [dep, ver] of Object.entries(catalogEntry.dependenciesToAdd)) {
1193
+ pkg.dependencies[dep] = ver;
1194
+ }
1195
+ }
1196
+ if (catalogEntry.devDependenciesToAdd) {
1197
+ for (const [dep, ver] of Object.entries(catalogEntry.devDependenciesToAdd)) {
1198
+ pkg.devDependencies[dep] = ver;
1199
+ }
1200
+ }
1201
+ if (Object.keys(pkg.devDependencies).length === 0) delete pkg.devDependencies;
1202
+ if (catalogEntry.scriptsToUpdate) {
1203
+ if (!pkg.scripts) pkg.scripts = {};
1204
+ for (const [scriptName, scriptCmd] of Object.entries(catalogEntry.scriptsToUpdate)) {
1205
+ pkg.scripts[scriptName] = scriptCmd;
1206
+ }
1207
+ }
1208
+ if (catalogEntry.scriptsToRemove && pkg.scripts) {
1209
+ for (const scriptName of catalogEntry.scriptsToRemove) {
1210
+ delete pkg.scripts[scriptName];
1211
+ }
1212
+ }
1213
+ fs5.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
1214
+ console.log(`[PackageManagerAgent] Updated package.json dependencies directly (no npm subprocess).`);
1215
+ sendEvent({
1216
+ type: "system.log" /* SYSTEM_LOG */,
1217
+ producerId: participant.getId(),
1218
+ occurredAt: /* @__PURE__ */ new Date(),
1219
+ payload: { planId: payload.planId, message: "PackageManager updated package.json. IntegrationAgent will run npm install in the shadow workspace \u2014 the original project is untouched until you Apply.", level: "info" }
1220
+ }, participant.getId());
1221
+ } catch (err) {
1222
+ console.error(`[PackageManagerAgent] Error editing package.json:`, err);
1223
+ }
1224
+ } else {
1225
+ console.log(`[PackageManagerAgent] No package.json found at ${packageJsonPath}`);
1226
+ }
1227
+ await emitPackagesReady("completed");
1228
+ }
1229
+ };
1230
+ var managePackagesHandler = {
1231
+ specification: new WhenMigrationStarts2(),
1232
+ processor: managePackagesProcessor
1233
+ };
1234
+ function createPackageManagerAgent() {
1235
+ return createAgent4({
1236
+ name: "PackageManager",
1237
+ capabilities: ["package_management"],
1238
+ instruction: "You are the Package Manager Agent. Your job is to resolve dependencies and clean up unused framework files.",
1239
+ tools: [],
1240
+ handlers: [managePackagesHandler]
1241
+ });
1242
+ }
1243
+
1244
+ // ../application/src/agents/CoordinatorAgent.ts
1245
+ import {
1246
+ createAgent as createAgent5,
1247
+ SituationSpecification as SituationSpecification5
1248
+ } from "@mozaik-ai/core";
1249
+
1250
+ // ../application/src/agents/planReady.ts
1251
+ function planReadyForIntegration(plan) {
1252
+ if (plan.phase === "integration" || plan.phase === "completed" || plan.phase === "failed") {
1253
+ return false;
1254
+ }
1255
+ const files = plan.tasks.filter((task) => !task.filePath.startsWith("system:"));
1256
+ if (files.length === 0) return false;
1257
+ const packages = plan.tasks.find((task) => task.filePath === "system:package_manager");
1258
+ if (packages && packages.status !== "completed" && packages.status !== "failed") {
1259
+ return false;
1260
+ }
1261
+ const live = plan.tasks.filter((task) => !task.filePath.startsWith("system:"));
1262
+ if (live.some((task) => task.status === "pending" || task.status === "in_progress")) {
1263
+ return false;
1264
+ }
1265
+ return live.every((task) => task.status === "completed" || task.status === "failed");
1266
+ }
1267
+
1268
+ // ../application/src/agents/CoordinatorAgent.ts
1269
+ var WATCHDOG_MS = 8e3;
1270
+ var WATCHDOG_MAX_MS = 30 * 60 * 1e3;
1271
+ var WhenSwarmMayBeIdle = class extends SituationSpecification5 {
1272
+ isSatisfiedBy({ event }) {
1273
+ return event.type === "file.reviewed" /* FILE_REVIEWED */ || event.type === "file.failed" /* FILE_FAILED */ || event.type === "file.fatal_mismatch" /* FILE_FATAL_MISMATCH */ || event.type === "phase.packages_ready" /* PHASE_PACKAGES_READY */;
1274
+ }
1275
+ };
1276
+ var WhenMigrationStarted = class extends SituationSpecification5 {
1277
+ isSatisfiedBy({ event }) {
1278
+ return event.type === "migration.started" /* MIGRATION_STARTED */;
1279
+ }
1280
+ };
1281
+ async function emitIntegrationIfReady(producerId, planId) {
1282
+ const runtime = resolveRuntime();
1283
+ const plan = await runtime.state.repository.getPlan(planId);
1284
+ if (!plan || !planReadyForIntegration(plan)) return;
1285
+ console.log(`[Coordinator] File work and packages are settled. Emitting phase.integration_started.`);
1286
+ sendEvent({
1287
+ type: "phase.integration_started" /* PHASE_INTEGRATION_STARTED */,
1288
+ producerId,
1289
+ occurredAt: /* @__PURE__ */ new Date(),
1290
+ payload: { planId }
1291
+ }, producerId);
1292
+ }
1293
+ function stopWatchdog(planId) {
1294
+ const runtime = resolveRuntime();
1295
+ const timer = runtime.state.integrationWatchdogs.get(planId);
1296
+ if (!timer) return;
1297
+ clearInterval(timer);
1298
+ runtime.state.integrationWatchdogs.delete(planId);
1299
+ }
1300
+ var startIntegrationIfIdle = {
1301
+ async apply({ event, participant }) {
1302
+ const planId = event.payload.planId;
1303
+ if (!planId) return;
1304
+ await emitIntegrationIfReady(participant.getId(), planId);
1305
+ }
1306
+ };
1307
+ var startWatchdogProcessor = {
1308
+ async apply({ event, participant }) {
1309
+ const p = event.payload;
1310
+ const runtime = resolveRuntime();
1311
+ stopWatchdog(p.planId);
1312
+ const startedAt = Date.now();
1313
+ const producerId = participant.getId();
1314
+ sendEvent({
1315
+ type: "system.log" /* SYSTEM_LOG */,
1316
+ producerId,
1317
+ occurredAt: /* @__PURE__ */ new Date(),
1318
+ payload: {
1319
+ planId: p.planId,
1320
+ message: "Coordinator watchdog is on: if file work settles and Integration never starts, it will kick within 8s. No extra model calls.",
1321
+ level: "info"
1322
+ }
1323
+ }, producerId);
1324
+ const timer = setInterval(async () => {
1325
+ try {
1326
+ const plan = await runtime.state.repository.getPlan(p.planId);
1327
+ if (!plan || plan.phase === "completed" || plan.phase === "failed") {
1328
+ stopWatchdog(p.planId);
1329
+ return;
1330
+ }
1331
+ if (Date.now() - startedAt >= WATCHDOG_MAX_MS) {
1332
+ stopWatchdog(p.planId);
1333
+ sendEvent({
1334
+ type: "system.log" /* SYSTEM_LOG */,
1335
+ producerId,
1336
+ occurredAt: /* @__PURE__ */ new Date(),
1337
+ payload: {
1338
+ planId: p.planId,
1339
+ message: "Coordinator watchdog timed out after 30 minutes. Integration never became ready or the run stalled.",
1340
+ level: "error"
1341
+ }
1342
+ }, producerId);
1343
+ const currentPlan = await runtime.state.repository.getPlan(p.planId);
1344
+ if (currentPlan && currentPlan.phase !== "completed" && currentPlan.phase !== "failed") {
1345
+ currentPlan.phase = "failed";
1346
+ currentPlan.outcome = "failed";
1347
+ await runtime.state.repository.savePlan(currentPlan);
1348
+ sendEvent({
1349
+ type: "migration.completed" /* MIGRATION_COMPLETED */,
1350
+ producerId,
1351
+ occurredAt: /* @__PURE__ */ new Date(),
1352
+ payload: {
1353
+ planId: p.planId,
1354
+ outcome: "failed",
1355
+ reason: "Migration timed out after 30 minutes watchdog budget."
1356
+ }
1357
+ }, producerId);
1358
+ }
1359
+ return;
1360
+ }
1361
+ await emitIntegrationIfReady(producerId, p.planId);
1362
+ } catch (error) {
1363
+ console.error("[Coordinator] Watchdog tick failed:", error);
1364
+ }
1365
+ }, WATCHDOG_MS);
1366
+ runtime.state.integrationWatchdogs.set(p.planId, timer);
1367
+ }
1368
+ };
1369
+ function createCoordinatorAgent() {
1370
+ return createAgent5({
1371
+ name: "Coordinator",
1372
+ capabilities: [],
1373
+ instruction: "You watch the bus and start Integration when file tasks and packages are settled. You do not call the model.",
1787
1374
  tools: [],
1788
- // No tools
1789
- handlers: [reviewFileHandler]
1375
+ handlers: [
1376
+ { specification: new WhenMigrationStarted(), processor: startWatchdogProcessor },
1377
+ { specification: new WhenSwarmMayBeIdle(), processor: startIntegrationIfIdle }
1378
+ ]
1790
1379
  });
1791
1380
  }
1792
1381
 
1793
- // ../application/src/agents/PackageManagerAgent.ts
1794
- import * as fs5 from "fs";
1795
- import * as path4 from "path";
1382
+ // ../application/src/agents/ReporterAgent.ts
1796
1383
  import {
1797
- createAgent as createAgent4,
1798
- SituationSpecification as SituationSpecification4
1384
+ Agent as Agent6,
1385
+ createAgent as createAgent6,
1386
+ SituationSpecification as SituationSpecification6
1799
1387
  } from "@mozaik-ai/core";
1800
- var WhenMigrationStarts2 = class extends SituationSpecification4 {
1388
+ function log(participantId, planId, message, level = "info") {
1389
+ sendEvent({
1390
+ type: "system.log" /* SYSTEM_LOG */,
1391
+ producerId: participantId,
1392
+ occurredAt: /* @__PURE__ */ new Date(),
1393
+ payload: { planId, message, level }
1394
+ }, participantId);
1395
+ }
1396
+ async function shadowPathFor(plan, journal) {
1397
+ if (journal?.shadowPath) return journal.shadowPath;
1398
+ const { ShadowWorkspace: ShadowWorkspace2 } = await import("./src-JHOGUCRS.js");
1399
+ return new ShadowWorkspace2().getShadowPath(plan.runId);
1400
+ }
1401
+ function fallbackReport(params) {
1402
+ const completed = params.tasks.filter((t) => t.status === "completed");
1403
+ const failed = params.tasks.filter((t) => t.status === "failed");
1404
+ const rejected = params.notes.filter((n) => n.outcome === "rejected" || n.outcome === "fatal");
1405
+ const fileLines = params.tasks.filter((t) => !t.filePath.startsWith("system:")).slice(0, 40).map((t) => `- \`${t.filePath}\` \u2014 ${t.status}${t.error ? ` (${t.error})` : ""}`).join("\n");
1406
+ const riskLines = rejected.slice(0, 12).map((n) => `- \`${n.filePath}\`: ${n.detail || n.outcome}`).join("\n");
1407
+ return `# What changed
1408
+
1409
+ Metamorph migrated this project from **${params.source}** to **${params.target}**.
1410
+ ${params.phase === "failed" ? "\n**Status:** shadow `npm run build` (or catalog checks) did not pass. Treat this as an incomplete migration.\n" : ""}
1411
+ Your original files were not overwritten until you click **Apply Migration**. After apply, the result lives on a git branch named like \`metamorph/${params.runId}\`.
1412
+
1413
+ ## How to run it
1414
+
1415
+ 1. Apply the migration in the dashboard (or keep working in the shadow workspace).
1416
+ 2. From the project folder:
1417
+ \`\`\`bash
1418
+ npm install
1419
+ npm run dev
1420
+ \`\`\`
1421
+ 3. Open the URL the framework prints (\`next dev\` \u2192 port 3000, Vite \u2192 5173).
1422
+
1423
+ ## What we touched
1424
+
1425
+ - Files completed: ${completed.length}
1426
+ - Files failed: ${failed.length}
1427
+ - Started: ${params.startedAt}
1428
+ - Finished: ${(/* @__PURE__ */ new Date()).toLocaleString()}
1429
+
1430
+ ${fileLines || "_No file tasks recorded._"}
1431
+
1432
+ ## Watch out for
1433
+
1434
+ ${riskLines || "- Review `package.json` scripts and the new entry files (layout/page or `index.html` + `src/main.tsx`).\n- Confirm global CSS still loads in the browser."}
1435
+
1436
+ ## Need a retry?
1437
+
1438
+ Discard in the dashboard and start a new run. Do not mix two shadow workspaces in the same tree.
1439
+ `;
1440
+ }
1441
+ var STATUS_PASSED = "**Status:** Shadow `npm run build` passed.";
1442
+ var STATUS_FAILED = "**Status:** Shadow `npm run build` (or catalog checks) did not pass. Treat this as an incomplete migration.";
1443
+ function statusLine(phase) {
1444
+ return phase === "failed" ? STATUS_FAILED : STATUS_PASSED;
1445
+ }
1446
+ function patchReportStatus(markdown, phase) {
1447
+ const line = statusLine(phase);
1448
+ const stripped = markdown.replace(/\n?\*\*Status:\*\*[^\n]*\n?/g, "\n");
1449
+ if (stripped.startsWith("# ")) {
1450
+ const nl = stripped.indexOf("\n");
1451
+ if (nl === -1) return `${stripped}
1452
+
1453
+ ${line}
1454
+ `;
1455
+ return `${stripped.slice(0, nl)}
1456
+
1457
+ ${line}${stripped.slice(nl)}`;
1458
+ }
1459
+ return `${line}
1460
+
1461
+ ${stripped.trimStart()}`;
1462
+ }
1463
+ async function writeOrPatchMigrationMd(dest, phase, fallback) {
1464
+ const fs8 = await import("fs");
1465
+ if (!fs8.existsSync(dest) || fs8.statSync(dest).size < 80) {
1466
+ fs8.writeFileSync(dest, patchReportStatus(fallback, phase), "utf-8");
1467
+ return "wrote";
1468
+ }
1469
+ const current = fs8.readFileSync(dest, "utf-8");
1470
+ fs8.writeFileSync(dest, patchReportStatus(current, phase), "utf-8");
1471
+ return "patched";
1472
+ }
1473
+ var WhenMigrationStarted2 = class extends SituationSpecification6 {
1801
1474
  isSatisfiedBy({ event }) {
1802
1475
  return event.type === "migration.started" /* MIGRATION_STARTED */;
1803
1476
  }
1804
1477
  };
1805
- var managePackagesProcessor = {
1478
+ var WhenFileMigrated2 = class extends SituationSpecification6 {
1479
+ isSatisfiedBy({ event }) {
1480
+ return event.type === "file.migrated" /* FILE_MIGRATED */;
1481
+ }
1482
+ };
1483
+ var WhenFileReviewed = class extends SituationSpecification6 {
1484
+ isSatisfiedBy({ event }) {
1485
+ return event.type === "file.reviewed" /* FILE_REVIEWED */;
1486
+ }
1487
+ };
1488
+ var WhenFileRejected2 = class extends SituationSpecification6 {
1489
+ isSatisfiedBy({ event }) {
1490
+ return event.type === "file.rejected" /* FILE_REJECTED */;
1491
+ }
1492
+ };
1493
+ var WhenFileFatalMismatch2 = class extends SituationSpecification6 {
1494
+ isSatisfiedBy({ event }) {
1495
+ return event.type === "file.fatal_mismatch" /* FILE_FATAL_MISMATCH */;
1496
+ }
1497
+ };
1498
+ var WhenIntegrationStarts = class extends SituationSpecification6 {
1499
+ isSatisfiedBy({ event }) {
1500
+ return event.type === "phase.integration_started" /* PHASE_INTEGRATION_STARTED */;
1501
+ }
1502
+ };
1503
+ var WhenMigrationCompleted = class extends SituationSpecification6 {
1504
+ isSatisfiedBy({ event }) {
1505
+ return event.type === "migration.completed" /* MIGRATION_COMPLETED */;
1506
+ }
1507
+ };
1508
+ var WhenOwnAnswer = class extends SituationSpecification6 {
1509
+ isSatisfiedBy({ event, participant }) {
1510
+ return event.type === "model.answer" && event.producerId === participant.getId();
1511
+ }
1512
+ };
1513
+ var startedProcessor = {
1806
1514
  async apply({ event, participant }) {
1807
- const payload = event.payload;
1808
- console.log(`[PackageManagerAgent] Managing dependencies and cleanup for Plan: ${payload.planId}`);
1809
- const shadowDir = payload.shadowWorkspacePath || "";
1810
- if (!shadowDir || !fs5.existsSync(shadowDir)) {
1811
- console.warn("[PackageManagerAgent] No valid shadow workspace provided. Skipping.");
1515
+ const p = event.payload;
1516
+ const entry = resolveRuntime().state.journal(p.planId);
1517
+ if (p.shadowWorkspacePath) entry.shadowPath = p.shadowWorkspacePath;
1518
+ log(participant.getId(), p.planId, "Reporter is on the bus. It will draft MIGRATION.md during install/build (one model call, in parallel with Integration).");
1519
+ }
1520
+ };
1521
+ var migratedProcessor = {
1522
+ async apply({ event }) {
1523
+ const p = event.payload;
1524
+ resolveRuntime().state.journal(p.planId).notes.push({ filePath: p.filePath, outcome: "migrated" });
1525
+ }
1526
+ };
1527
+ var reviewedProcessor = {
1528
+ async apply({ event }) {
1529
+ const p = event.payload;
1530
+ resolveRuntime().state.journal(p.planId).notes.push({ filePath: p.filePath, outcome: "approved" });
1531
+ }
1532
+ };
1533
+ var rejectedProcessor = {
1534
+ async apply({ event }) {
1535
+ const p = event.payload;
1536
+ resolveRuntime().state.journal(p.planId).notes.push({
1537
+ filePath: p.filePath,
1538
+ outcome: "rejected",
1539
+ detail: (p.errors || []).slice(0, 3).join("; ")
1540
+ });
1541
+ }
1542
+ };
1543
+ var fatalProcessor = {
1544
+ async apply({ event }) {
1545
+ const p = event.payload;
1546
+ resolveRuntime().state.journal(p.planId).notes.push({ filePath: p.filePath, outcome: "fatal", detail: p.reason });
1547
+ }
1548
+ };
1549
+ function draftPrompt(params) {
1550
+ const notable = params.notes.filter((n) => n.outcome === "rejected" || n.outcome === "fatal").slice(-15).map((n) => `${n.filePath}: ${n.detail || n.outcome}`).join("\n");
1551
+ return `Write a user-facing migration briefing to this exact path:
1552
+ ${params.reportAbs}
1553
+
1554
+ Audience: the engineer who clicked Start Migration. They are not reading agent telemetry.
1555
+ File migration is done; Integration is installing/building in parallel \u2014 you may say the shadow build is still running.
1556
+
1557
+ Migration: ${params.plan.profile.source} \u2192 ${params.plan.profile.target}
1558
+ Project path: ${params.plan.targetPath || "(unknown)"}
1559
+ Run id: ${params.plan.runId}
1560
+
1561
+ Include these sections, in this order, with markdown headings:
1562
+ 1. What changed \u2014 1-2 short paragraphs in plain language
1563
+ 2. How to run it \u2014 concrete commands (npm install, npm run dev, which port if you can infer from package.json)
1564
+ 3. What we touched \u2014 group by area (routes, styles, config, dependencies). Do not dump every file as a checklist unless there are under 12 files.
1565
+ 4. Watch out for \u2014 honest risks from the notes below and from reading package.json / the new entry files
1566
+ 5. Next steps after Apply \u2014 git branch checkout if they applied, npm install on the real project
1567
+
1568
+ You may read package.json, README, src/app/layout.tsx, src/main.tsx, index.html, or MIGRATION.md if they exist.
1569
+
1570
+ Rejected / structure notes:
1571
+ ${notable || "(none recorded)"}
1572
+
1573
+ If the model cannot infer something, say so briefly. Do not invent URLs, secrets, or features.
1574
+
1575
+ A factual draft you may rewrite (keep facts, improve voice):
1576
+ ---
1577
+ ${params.draft}
1578
+ ---
1579
+ When the file is written, stop.`;
1580
+ }
1581
+ var draftDuringInstallProcessor = {
1582
+ async apply({ event, participant }) {
1583
+ if (!(participant instanceof Agent6)) return;
1584
+ const p = event.payload;
1585
+ const runtime = resolveRuntime();
1586
+ const board = runtime.state.journal(p.planId);
1587
+ if (board.reportLoopActive || board.reportStarted) return;
1588
+ const plan = await runtime.state.repository.getPlan(p.planId);
1589
+ if (!plan) return;
1590
+ const shadowPath = await shadowPathFor(plan, board);
1591
+ const draft = fallbackReport({
1592
+ source: plan.profile.source,
1593
+ target: plan.profile.target,
1594
+ targetPath: plan.targetPath,
1595
+ runId: plan.runId,
1596
+ phase: plan.phase,
1597
+ tasks: plan.tasks || [],
1598
+ notes: board.notes,
1599
+ startedAt: board.startedAt
1600
+ });
1601
+ const tools = participant.getTools();
1602
+ if (tools.length === 0) {
1603
+ board.reportStarted = true;
1604
+ const fs8 = await import("fs");
1605
+ const path12 = await import("path");
1606
+ fs8.writeFileSync(path12.join(shadowPath, "MIGRATION.md"), draft, "utf-8");
1607
+ log(participant.getId(), p.planId, "Reporter wrote a fallback MIGRATION.md (no file tools on the agent).");
1812
1608
  return;
1813
1609
  }
1610
+ board.reportStarted = true;
1611
+ board.reportLoopActive = true;
1612
+ log(participant.getId(), p.planId, "Reporter is drafting MIGRATION.md while Integration runs npm install / build (one model call).");
1613
+ const reportAbs = `${shadowPath.replace(/\\/g, "/")}/MIGRATION.md`;
1614
+ const modelToUse = process.env.METAMORPH_MODEL || "gpt-5.4";
1615
+ runLoop(participant.getId(), draftPrompt({ reportAbs, plan, notes: board.notes, draft }), {
1616
+ model: modelToUse,
1617
+ context: participant.getMemory().getContext(),
1618
+ tools
1619
+ });
1620
+ setTimeout(async () => {
1621
+ const still = runtime.state.journals.get(p.planId);
1622
+ if (!still?.reportLoopActive) return;
1623
+ still.reportLoopActive = false;
1624
+ try {
1625
+ const path12 = await import("path");
1626
+ const dest = path12.join(shadowPath, "MIGRATION.md");
1627
+ const phase = still.pendingStatusPhase;
1628
+ await writeOrPatchMigrationMd(dest, phase, draft);
1629
+ still.pendingStatusPhase = void 0;
1630
+ log(participant.getId(), p.planId, "Reporter draft timed out; kept fallback MIGRATION.md.", "warning");
1631
+ } catch {
1632
+ }
1633
+ }, 9e4);
1634
+ }
1635
+ };
1636
+ var ownAnswerProcessor = {
1637
+ async apply() {
1814
1638
  const runtime = resolveRuntime();
1815
- const plan = await runtime.state.repository.getPlan(payload.planId);
1816
- if (!plan) {
1817
- console.warn("[PackageManagerAgent] Migration plan not found.");
1818
- return;
1639
+ for (const [planId, journal] of runtime.state.journals) {
1640
+ journal.reportLoopActive = false;
1641
+ if (!journal.pendingStatusPhase) continue;
1642
+ const plan = await runtime.state.repository.getPlan(planId);
1643
+ if (!plan) continue;
1644
+ const shadowPath = await shadowPathFor(plan, journal);
1645
+ const path12 = await import("path");
1646
+ const dest = path12.join(shadowPath, "MIGRATION.md");
1647
+ const draft = fallbackReport({
1648
+ source: plan.profile.source,
1649
+ target: plan.profile.target,
1650
+ targetPath: plan.targetPath,
1651
+ runId: plan.runId,
1652
+ phase: journal.pendingStatusPhase,
1653
+ tasks: plan.tasks || [],
1654
+ notes: journal.notes,
1655
+ startedAt: journal.startedAt
1656
+ });
1657
+ await writeOrPatchMigrationMd(dest, journal.pendingStatusPhase, draft);
1658
+ journal.pendingStatusPhase = void 0;
1819
1659
  }
1820
- const catalogEntry = findMigrationCatalogEntry(plan.profile.source, plan.profile.target);
1821
- if (!catalogEntry) {
1822
- console.log(`[PackageManagerAgent] No catalog entry found for ${plan.profile.source} -> ${plan.profile.target}`);
1823
- return;
1660
+ }
1661
+ };
1662
+ var completedProcessor = {
1663
+ async apply({ event, participant }) {
1664
+ const p = event.payload;
1665
+ const runtime = resolveRuntime();
1666
+ const board = runtime.state.journal(p.planId);
1667
+ const plan = await runtime.state.repository.getPlan(p.planId);
1668
+ if (!plan) return;
1669
+ const shadowPath = await shadowPathFor(plan, board);
1670
+ const isFailed = plan.phase === "failed" || plan.outcome === "failed" || p.outcome === "failed";
1671
+ const effectivePhase = isFailed ? "failed" : plan.phase || "completed";
1672
+ const draft = fallbackReport({
1673
+ source: plan.profile.source,
1674
+ target: plan.profile.target,
1675
+ targetPath: plan.targetPath,
1676
+ runId: plan.runId,
1677
+ phase: effectivePhase,
1678
+ tasks: plan.tasks || [],
1679
+ notes: board.notes,
1680
+ startedAt: board.startedAt
1681
+ });
1682
+ board.pendingStatusPhase = isFailed ? "failed" : "completed";
1683
+ try {
1684
+ const path12 = await import("path");
1685
+ const dest = path12.join(shadowPath, "MIGRATION.md");
1686
+ if (board.reportLoopActive) {
1687
+ log(participant.getId(), p.planId, "Reporter draft is still running; Status will be patched when it finishes (no second model call).");
1688
+ return;
1689
+ }
1690
+ const result = await writeOrPatchMigrationMd(dest, effectivePhase, draft);
1691
+ board.pendingStatusPhase = void 0;
1692
+ log(
1693
+ participant.getId(),
1694
+ p.planId,
1695
+ result === "patched" ? "Reporter patched MIGRATION.md with the final build Status (no extra model call)." : "Reporter finished. Open MIGRATION.md in the shadow workspace (it is copied to your project when you Apply)."
1696
+ );
1697
+ } catch (error) {
1698
+ console.error("[ReporterAgent] Could not ensure MIGRATION.md:", error);
1699
+ log(participant.getId(), p.planId, "Reporter could not write MIGRATION.md.", "warning");
1824
1700
  }
1825
- const pmTask = plan.tasks.find((t) => t.filePath === "system:package_manager");
1826
- if (pmTask) {
1827
- pmTask.status = "in_progress";
1828
- await runtime.state.repository.savePlan(plan);
1701
+ }
1702
+ };
1703
+ function createReporterAgent(tools = []) {
1704
+ return createAgent6({
1705
+ name: "Reporter",
1706
+ capabilities: ["reporting", "inference"],
1707
+ instruction: "You are the Reporter on the Metamorph swarm. Watch the bus. When Integration starts, write MIGRATION.md for the human \u2014 not an internal log. Use write_file. Do not modify application source. Do not start extra model calls; one draft per run is enough.",
1708
+ tools,
1709
+ handlers: [
1710
+ { specification: new WhenMigrationStarted2(), processor: startedProcessor },
1711
+ { specification: new WhenFileMigrated2(), processor: migratedProcessor },
1712
+ { specification: new WhenFileReviewed(), processor: reviewedProcessor },
1713
+ { specification: new WhenFileRejected2(), processor: rejectedProcessor },
1714
+ { specification: new WhenFileFatalMismatch2(), processor: fatalProcessor },
1715
+ { specification: new WhenIntegrationStarts(), processor: draftDuringInstallProcessor },
1716
+ { specification: new WhenOwnAnswer(), processor: ownAnswerProcessor },
1717
+ { specification: new WhenMigrationCompleted(), processor: completedProcessor }
1718
+ ]
1719
+ });
1720
+ }
1721
+
1722
+ // ../application/src/agents/IntegrationAgent.ts
1723
+ import {
1724
+ createAgent as createAgent7,
1725
+ SituationSpecification as SituationSpecification7
1726
+ } from "@mozaik-ai/core";
1727
+ var MAX_INTEGRATION_ROUNDS = 4;
1728
+ var REPAIR_TIMEOUT_MS = 12e4;
1729
+ var WhenIntegrationStarts2 = class extends SituationSpecification7 {
1730
+ isSatisfiedBy({ event }) {
1731
+ return event.type === "phase.integration_started" /* PHASE_INTEGRATION_STARTED */;
1732
+ }
1733
+ };
1734
+ async function completeMigration(participantId, planId, message, level) {
1735
+ const runtime = resolveRuntime();
1736
+ const plan = await runtime.state.repository.getPlan(planId);
1737
+ if (plan) {
1738
+ let swept = 0;
1739
+ for (const task of plan.tasks) {
1740
+ if (task.status === "failed" && !task.filePath.startsWith("system:")) {
1741
+ await runtime.state.repository.updateTaskStatus(
1742
+ planId,
1743
+ task.filePath,
1744
+ "completed"
1745
+ );
1746
+ swept++;
1747
+ }
1829
1748
  }
1830
- if (catalogEntry.filesToDelete && catalogEntry.filesToDelete.length > 0) {
1831
- for (const fileToDelete of catalogEntry.filesToDelete) {
1832
- const fullPath = path4.join(shadowDir, fileToDelete);
1833
- if (fs5.existsSync(fullPath)) {
1834
- fs5.unlinkSync(fullPath);
1835
- console.log(`[PackageManagerAgent] Deleted deprecated file: ${fileToDelete}`);
1836
- }
1749
+ if (swept > 0) {
1750
+ console.log(
1751
+ `[IntegrationAgent] Swept ${swept} residual failed task(s) \u2192 completed (shadow build passed).`
1752
+ );
1753
+ }
1754
+ plan.phase = "completed";
1755
+ plan.outcome = "success";
1756
+ await runtime.state.repository.savePlan(plan);
1757
+ }
1758
+ sendEvent(
1759
+ {
1760
+ type: "system.log" /* SYSTEM_LOG */,
1761
+ producerId: participantId,
1762
+ occurredAt: /* @__PURE__ */ new Date(),
1763
+ payload: { planId, message, level }
1764
+ },
1765
+ participantId
1766
+ );
1767
+ sendEvent(
1768
+ {
1769
+ type: "migration.completed" /* MIGRATION_COMPLETED */,
1770
+ producerId: participantId,
1771
+ occurredAt: /* @__PURE__ */ new Date(),
1772
+ payload: {
1773
+ planId,
1774
+ outcome: "success"
1837
1775
  }
1776
+ },
1777
+ participantId
1778
+ );
1779
+ }
1780
+ function log2(participantId, planId, message, level = "info") {
1781
+ sendEvent(
1782
+ {
1783
+ type: "system.log" /* SYSTEM_LOG */,
1784
+ producerId: participantId,
1785
+ occurredAt: /* @__PURE__ */ new Date(),
1786
+ payload: { planId, message, level }
1787
+ },
1788
+ participantId
1789
+ );
1790
+ }
1791
+ async function failMigration(participantId, planId, message) {
1792
+ const runtime = resolveRuntime();
1793
+ const plan = await runtime.state.repository.getPlan(planId);
1794
+ if (plan) {
1795
+ plan.phase = "failed";
1796
+ plan.outcome = "failed";
1797
+ await runtime.state.repository.savePlan(plan);
1798
+ }
1799
+ log2(participantId, planId, message, "error");
1800
+ sendEvent(
1801
+ {
1802
+ type: "migration.completed" /* MIGRATION_COMPLETED */,
1803
+ producerId: participantId,
1804
+ occurredAt: /* @__PURE__ */ new Date(),
1805
+ payload: {
1806
+ planId,
1807
+ outcome: "failed",
1808
+ reason: message
1809
+ }
1810
+ },
1811
+ participantId
1812
+ );
1813
+ }
1814
+ async function requeueBrokenFiles(producerId, planId, files, errors) {
1815
+ const runtime = resolveRuntime();
1816
+ const unique = [...new Set(files.filter(Boolean))];
1817
+ log2(
1818
+ producerId,
1819
+ planId,
1820
+ `Shadow install/build did not pass. Reopening ${unique.length} file task(s) for Worker repair.`,
1821
+ "warning"
1822
+ );
1823
+ for (const filePath of unique) {
1824
+ await runtime.state.repository.updateTaskStatus(
1825
+ planId,
1826
+ filePath,
1827
+ "pending",
1828
+ errors.slice(0, 3).join("\n").slice(0, 1500)
1829
+ );
1830
+ sendEvent(
1831
+ {
1832
+ type: "file.rejected" /* FILE_REJECTED */,
1833
+ producerId,
1834
+ occurredAt: /* @__PURE__ */ new Date(),
1835
+ payload: {
1836
+ planId,
1837
+ filePath,
1838
+ errors,
1839
+ source: "integration"
1840
+ }
1841
+ },
1842
+ producerId
1843
+ );
1844
+ }
1845
+ const plan = await runtime.state.repository.getPlan(planId);
1846
+ if (plan) {
1847
+ plan.phase = "files";
1848
+ await runtime.state.repository.savePlan(plan);
1849
+ }
1850
+ }
1851
+ var integrationProcessor = {
1852
+ async apply({ event, participant }) {
1853
+ const payload = event.payload;
1854
+ const planId = payload.planId;
1855
+ const producerId = participant.getId();
1856
+ const locks = resolveRuntime().state.integrationLocks;
1857
+ if (locks.has(planId)) {
1858
+ console.log(
1859
+ `[IntegrationAgent] Integration already running for ${planId}. Ignoring duplicate phase event.`
1860
+ );
1861
+ return;
1838
1862
  }
1839
- const packageJsonPath = path4.join(shadowDir, "package.json");
1840
- if (fs5.existsSync(packageJsonPath)) {
1841
- const { spawn } = await import("child_process");
1842
- const runCommandAsync = (cmd, args) => {
1843
- return new Promise((resolve3, reject) => {
1844
- const proc = spawn(cmd, args, { cwd: shadowDir, stdio: "ignore", shell: true });
1845
- proc.on("close", (code) => {
1846
- if (code === 0) resolve3();
1847
- else reject(new Error(`Command ${cmd} ${args.join(" ")} failed with code ${code}`));
1848
- });
1849
- proc.on("error", reject);
1850
- });
1851
- };
1852
- try {
1853
- let toRemove = [];
1854
- if (catalogEntry.dependenciesToRemove) toRemove.push(...catalogEntry.dependenciesToRemove);
1855
- if (catalogEntry.devDependenciesToRemove) toRemove.push(...catalogEntry.devDependenciesToRemove);
1856
- if (toRemove.length > 0) {
1857
- console.log(`[PackageManagerAgent] Uninstalling: ${toRemove.join(" ")}`);
1858
- await runCommandAsync("npm", ["uninstall", ...toRemove]);
1863
+ locks.add(planId);
1864
+ try {
1865
+ const runtime = resolveRuntime();
1866
+ const plan = await runtime.state.repository.getPlan(planId);
1867
+ if (!plan) {
1868
+ console.warn(`[IntegrationAgent] Plan ${planId} not found.`);
1869
+ return;
1870
+ }
1871
+ if (plan.phase === "completed" || plan.phase === "failed") {
1872
+ console.log(
1873
+ `[IntegrationAgent] Plan ${planId} already finished (${plan.phase}). Ignoring extra integration phase.`
1874
+ );
1875
+ return;
1876
+ }
1877
+ plan.phase = "integration";
1878
+ const currentRound = plan.integrationRounds ?? 0;
1879
+ if (currentRound >= MAX_INTEGRATION_ROUNDS) {
1880
+ await failMigration(
1881
+ producerId,
1882
+ planId,
1883
+ `Integration budget exhausted after ${MAX_INTEGRATION_ROUNDS} shadow build rounds. npm run build still does not pass \u2014 the run is not successful.`
1884
+ );
1885
+ return;
1886
+ }
1887
+ plan.integrationRounds = currentRound + 1;
1888
+ await runtime.state.repository.savePlan(plan);
1889
+ const lastRound = plan.integrationRounds >= MAX_INTEGRATION_ROUNDS;
1890
+ const {
1891
+ ShadowWorkspace: ShadowWorkspace2,
1892
+ createReadFileTool: createReadFileTool2,
1893
+ createWriteFileTool: createWriteFileTool2,
1894
+ createListDirectoryTool: createListDirectoryTool2,
1895
+ createRunBuildTool: createRunBuildTool2,
1896
+ runInShadowWorkspace,
1897
+ parseImplicatedFiles
1898
+ } = await import("./src-JHOGUCRS.js");
1899
+ const { collectShadowIssues, collectRepairTargets } = await import("./registry-7WZ5RTGE.js");
1900
+ const { resolveMigrationCatalog: resolveMigrationCatalog2 } = await import("./src-UC3SN46X.js");
1901
+ const shadowPath = payload.shadowWorkspacePath || new ShadowWorkspace2().getShadowPath(plan.runId);
1902
+ console.log(
1903
+ `[IntegrationAgent] Shadow build round ${plan.integrationRounds}/${MAX_INTEGRATION_ROUNDS} in ${shadowPath}`
1904
+ );
1905
+ log2(
1906
+ producerId,
1907
+ planId,
1908
+ `File migration is done. Next: install dependencies in the shadow workspace (npm install). This can take several minutes \u2014 counters staying at 0 is expected.`
1909
+ );
1910
+ console.log(`[IntegrationAgent] npm install starting in ${shadowPath}`);
1911
+ let lastInstallBeat = Date.now();
1912
+ const install = await runInShadowWorkspace(
1913
+ shadowPath,
1914
+ "npm install --no-fund --no-audit",
1915
+ (chunk) => {
1916
+ process.stdout.write(chunk);
1917
+ if (Date.now() - lastInstallBeat >= 15e3) {
1918
+ lastInstallBeat = Date.now();
1919
+ log2(
1920
+ producerId,
1921
+ planId,
1922
+ "npm install is still running in the shadow workspace. Wait \u2014 this is not finished."
1923
+ );
1924
+ }
1859
1925
  }
1860
- if (catalogEntry.dependenciesToAdd && Object.keys(catalogEntry.dependenciesToAdd).length > 0) {
1861
- const deps = Object.entries(catalogEntry.dependenciesToAdd).map(([pkg, ver]) => `${pkg}@${ver}`);
1862
- console.log(`[PackageManagerAgent] Installing dependencies (this may take a few minutes)...`);
1863
- sendEvent({
1864
- type: "system.log" /* SYSTEM_LOG */,
1865
- producerId: participant.getId(),
1866
- occurredAt: /* @__PURE__ */ new Date(),
1867
- payload: { planId: payload.planId, message: "Downloading and installing dependencies (this may take a few minutes)...", level: "info" }
1868
- }, participant.getId());
1869
- await runCommandAsync("npm", ["install", ...deps]);
1926
+ );
1927
+ if (!install.ok) {
1928
+ console.warn(
1929
+ "[IntegrationAgent] npm install failed in shadow:",
1930
+ install.output.slice(0, 2e3)
1931
+ );
1932
+ log2(
1933
+ producerId,
1934
+ planId,
1935
+ "npm install failed in the shadow workspace. Halting build to avoid noisy compile errors.",
1936
+ "error"
1937
+ );
1938
+ if (lastRound) {
1939
+ await failMigration(
1940
+ producerId,
1941
+ planId,
1942
+ `npm install failed in the shadow workspace after ${MAX_INTEGRATION_ROUNDS} integration rounds. The migration is not successful.`
1943
+ );
1944
+ return;
1870
1945
  }
1871
- if (catalogEntry.devDependenciesToAdd && Object.keys(catalogEntry.devDependenciesToAdd).length > 0) {
1872
- const devDeps = Object.entries(catalogEntry.devDependenciesToAdd).map(([pkg, ver]) => `${pkg}@${ver}`);
1873
- console.log(`[PackageManagerAgent] Installing devDependencies...`);
1874
- await runCommandAsync("npm", ["install", "-D", ...devDeps]);
1946
+ const path12 = await import("path");
1947
+ const pkgPath = path12.join(shadowPath, "package.json");
1948
+ await requeueBrokenFiles(
1949
+ producerId,
1950
+ planId,
1951
+ [pkgPath],
1952
+ [`npm install failed in shadow workspace:
1953
+ ${install.output.slice(0, 2500)}`]
1954
+ );
1955
+ return;
1956
+ }
1957
+ log2(
1958
+ producerId,
1959
+ planId,
1960
+ "npm install finished. Next: npm run build in the shadow workspace."
1961
+ );
1962
+ let lastBuildBeat = Date.now();
1963
+ let build = await runInShadowWorkspace(
1964
+ shadowPath,
1965
+ "npm run build",
1966
+ (chunk) => {
1967
+ process.stdout.write(chunk);
1968
+ if (Date.now() - lastBuildBeat >= 15e3) {
1969
+ lastBuildBeat = Date.now();
1970
+ log2(
1971
+ producerId,
1972
+ planId,
1973
+ "npm run build is still running in the shadow workspace. Wait \u2014 this is not finished."
1974
+ );
1975
+ }
1875
1976
  }
1876
- console.log(`[PackageManagerAgent] Successfully updated dependencies via npm.`);
1877
- sendEvent({
1878
- type: "system.log" /* SYSTEM_LOG */,
1879
- producerId: participant.getId(),
1880
- occurredAt: /* @__PURE__ */ new Date(),
1881
- payload: { planId: payload.planId, message: "Dependencies installed successfully!", level: "info" }
1882
- }, participant.getId());
1883
- } catch (err) {
1884
- console.error(`[PackageManagerAgent] Error running npm commands:`, err);
1977
+ );
1978
+ if (!build.ok) {
1979
+ log2(
1980
+ producerId,
1981
+ planId,
1982
+ "Shadow build failed. IntegrationAgent will attempt local repairs before requeueing files."
1983
+ );
1984
+ const shadowTools = [
1985
+ createReadFileTool2(shadowPath),
1986
+ createWriteFileTool2(shadowPath),
1987
+ createListDirectoryTool2(shadowPath),
1988
+ createRunBuildTool2(shadowPath)
1989
+ ];
1990
+ const { createAgent: createMozaikAgent } = await import("@mozaik-ai/core");
1991
+ await new Promise((originalResolve) => {
1992
+ let isDone = false;
1993
+ const resolve4 = () => {
1994
+ if (!isDone) {
1995
+ isDone = true;
1996
+ originalResolve();
1997
+ }
1998
+ };
1999
+ const tempAgent = createMozaikAgent({
2000
+ name: `Integration-${Date.now()}`,
2001
+ capabilities: ["integration_testing", "code_repair"],
2002
+ instruction: "You are a staff engineer repairing a migrated project. Read failing files and their imports. Deduce real APIs from disk. Do not invent prop names. Do not wait for Git.",
2003
+ tools: shadowTools,
2004
+ handlers: []
2005
+ });
2006
+ join(tempAgent);
2007
+ const modelToUse = process.env.METAMORPH_MODEL || "gpt-5.4";
2008
+ const excerpt = build.output.slice(0, 4e3);
2009
+ const prompt = `A real \`npm run build\` already failed in the shadow workspace at ${shadowPath}.
2010
+ You must deduce the fix from source, not guess. Use list_directory / read_file on the implicated files AND the modules they import. Keep existing callback/export names. If a router file was reassembled, import the existing screen instead.
2011
+
2012
+ Do NOT require Git. You may run run_project_build again after edits.
2013
+
2014
+ Build output:
2015
+ ${excerpt}
2016
+
2017
+ When you have applied fixes (or cannot fix further), stop. A deterministic rebuild will run after you finish.`;
2018
+ Promise.resolve(
2019
+ runLoop(tempAgent.getId(), prompt, {
2020
+ model: modelToUse,
2021
+ context: tempAgent.getMemory().getContext(),
2022
+ tools: shadowTools
2023
+ })
2024
+ ).catch((error) => {
2025
+ console.error("[IntegrationAgent] runLoop error:", error);
2026
+ leave(tempAgent);
2027
+ resolve4();
2028
+ });
2029
+ setTimeout(() => {
2030
+ if (!isDone) {
2031
+ console.warn(
2032
+ "[IntegrationAgent] Repair loop timed out; continuing with deterministic rebuild."
2033
+ );
2034
+ leave(tempAgent);
2035
+ resolve4();
2036
+ }
2037
+ }, REPAIR_TIMEOUT_MS);
2038
+ });
2039
+ build = await runInShadowWorkspace(shadowPath, "npm run build");
1885
2040
  }
1886
- } else {
1887
- console.log(`[PackageManagerAgent] No package.json found at ${packageJsonPath}`);
1888
- }
1889
- const currentPlan = await runtime.state.repository.getPlan(payload.planId);
1890
- if (currentPlan) {
1891
- const pmTaskToComplete = currentPlan.tasks.find((t) => t.filePath === "system:package_manager");
1892
- if (pmTaskToComplete) {
1893
- pmTaskToComplete.status = "completed";
1894
- await runtime.state.repository.savePlan(currentPlan);
2041
+ const pluginCtx = {
2042
+ source: plan.profile.source,
2043
+ target: plan.profile.target
2044
+ };
2045
+ const catalogEntry = resolveMigrationCatalog2(
2046
+ plan.profile.source,
2047
+ plan.profile.target
2048
+ );
2049
+ const structureIssues = collectShadowIssues(
2050
+ shadowPath,
2051
+ pluginCtx,
2052
+ catalogEntry?.layer
2053
+ );
2054
+ const buildFailed = !build.ok || !install.ok;
2055
+ const verifierFailed = structureIssues.length > 0;
2056
+ if (!buildFailed && !verifierFailed) {
2057
+ await completeMigration(
2058
+ producerId,
2059
+ planId,
2060
+ "Shadow build succeeded. Reporter is writing MIGRATION.md next. Then Apply copies the result into your project.",
2061
+ "info"
2062
+ );
2063
+ return;
1895
2064
  }
1896
- const allTerminal = currentPlan.tasks.every((t) => t.status === "completed" || t.status === "failed");
1897
- if (allTerminal) {
1898
- console.log(`[App] All tasks reached terminal state. Emitting MIGRATION_COMPLETED.`);
1899
- sendEvent({
1900
- type: "system.log" /* SYSTEM_LOG */,
1901
- producerId: participant.getId(),
1902
- occurredAt: /* @__PURE__ */ new Date(),
1903
- payload: { planId: payload.planId, message: "All agents have finished their work. Please review the results and click APPLY MIGRATION or DISCARD.", level: "info" }
1904
- }, participant.getId());
1905
- sendEvent({
1906
- type: "migration.completed" /* MIGRATION_COMPLETED */,
1907
- producerId: participant.getId(),
1908
- occurredAt: /* @__PURE__ */ new Date(),
1909
- payload: { planId: payload.planId }
1910
- }, participant.getId());
2065
+ const implicated = parseImplicatedFiles(
2066
+ `${install.ok ? "" : install.output + "\n"}${build.output}`,
2067
+ shadowPath
2068
+ );
2069
+ const repairFiles = collectRepairTargets(shadowPath, pluginCtx, {
2070
+ implicated,
2071
+ issues: structureIssues,
2072
+ catalog: catalogEntry,
2073
+ layer: catalogEntry?.layer
2074
+ });
2075
+ const errors = [
2076
+ ...structureIssues.flatMap((issue) => issue.errors),
2077
+ ...install.ok ? [] : [`npm install failed:
2078
+ ${install.output.slice(0, 1500)}`],
2079
+ ...build.ok ? [] : [`npm run build failed:
2080
+ ${build.output.slice(0, 2500)}`]
2081
+ ];
2082
+ if (!lastRound && repairFiles.length > 0) {
2083
+ log2(
2084
+ producerId,
2085
+ planId,
2086
+ buildFailed ? "npm run build (or npm install) failed. Assigning repair tasks to Worker \u2014 the run is not finished." : `Shadow compile may succeed while the app is still wrong: ${structureIssues.length} verifier issue(s). Assigning repair tasks.`,
2087
+ "warning"
2088
+ );
2089
+ await requeueBrokenFiles(
2090
+ producerId,
2091
+ planId,
2092
+ repairFiles,
2093
+ errors.length > 0 ? errors : ["Shadow verification failed."]
2094
+ );
2095
+ return;
1911
2096
  }
2097
+ await failMigration(
2098
+ producerId,
2099
+ planId,
2100
+ lastRound ? `npm run build still does not pass after ${MAX_INTEGRATION_ROUNDS} integration rounds. The migration is not successful.` : "Shadow verification failed and no repair files could be assigned."
2101
+ );
2102
+ } catch (error) {
2103
+ console.error(`[IntegrationAgent] Error:`, error);
2104
+ await failMigration(
2105
+ producerId,
2106
+ planId,
2107
+ "Integration Phase encountered an error. The migration is not successful."
2108
+ );
2109
+ } finally {
2110
+ resolveRuntime().state.integrationLocks.delete(planId);
1912
2111
  }
1913
2112
  }
1914
2113
  };
1915
- var managePackagesHandler = {
1916
- specification: new WhenMigrationStarts2(),
1917
- processor: managePackagesProcessor
2114
+ var manageIntegrationHandler = {
2115
+ specification: new WhenIntegrationStarts2(),
2116
+ processor: integrationProcessor
1918
2117
  };
1919
- function createPackageManagerAgent() {
1920
- return createAgent4({
1921
- name: "PackageManager",
1922
- capabilities: ["package_management"],
1923
- instruction: "You are the Package Manager Agent. Your job is to resolve dependencies and clean up unused framework files.",
1924
- tools: [],
1925
- handlers: [managePackagesHandler]
2118
+ function createIntegrationAgent(tools = []) {
2119
+ return createAgent7({
2120
+ name: "IntegrationAgent",
2121
+ capabilities: ["integration_testing", "code_repair"],
2122
+ instruction: "You are the Integration Agent. You run npm install and npm run build in the shadow workspace. A passing compile is not enough if catalog verifiers fail. Never mark the migration complete unless the shadow build passes and verifiers are clean. On failure, reopen file tasks for Worker.",
2123
+ tools,
2124
+ handlers: [manageIntegrationHandler]
1926
2125
  });
1927
2126
  }
1928
2127
 
1929
- // ../application/src/agents/ReporterAgent.ts
2128
+ // ../application/src/migration/plugins/frontendTargets.ts
2129
+ import * as path8 from "path";
2130
+
2131
+ // ../application/src/utils/FrontendRuntimeHints.ts
1930
2132
  import * as fs6 from "fs";
1931
- import * as path5 from "path";
1932
- import {
1933
- createAgent as createAgent5,
1934
- SituationSpecification as SituationSpecification5
1935
- } from "@mozaik-ai/core";
1936
- var WhenMigrationStarted = class extends SituationSpecification5 {
1937
- isSatisfiedBy({ event }) {
1938
- return event.type === "migration.started" /* MIGRATION_STARTED */;
2133
+ import * as path7 from "path";
2134
+ function readIfExists(...candidates) {
2135
+ for (const filePath of candidates) {
2136
+ if (fs6.existsSync(filePath) && fs6.statSync(filePath).isFile()) {
2137
+ return { filePath, content: fs6.readFileSync(filePath, "utf-8") };
2138
+ }
1939
2139
  }
1940
- };
1941
- var WhenFileMigrated2 = class extends SituationSpecification5 {
1942
- isSatisfiedBy({ event }) {
1943
- return event.type === "file.migrated" /* FILE_MIGRATED */;
2140
+ return null;
2141
+ }
2142
+ function leftoverScripts(shadowRoot, forbidden, message) {
2143
+ const pkgPath = path7.join(shadowRoot, "package.json");
2144
+ if (!fs6.existsSync(pkgPath)) return [];
2145
+ try {
2146
+ const pkg = JSON.parse(fs6.readFileSync(pkgPath, "utf-8"));
2147
+ const hits = Object.entries(pkg.scripts || {}).filter(([, cmd]) => forbidden.test(cmd));
2148
+ if (hits.length === 0) return [];
2149
+ return [{
2150
+ filePath: pkgPath,
2151
+ errors: [`${message} (${hits.map(([name]) => name).join(", ")}).`]
2152
+ }];
2153
+ } catch {
2154
+ return [];
1944
2155
  }
1945
- };
1946
- var WhenFileReviewed = class extends SituationSpecification5 {
1947
- isSatisfiedBy({ event }) {
1948
- return event.type === "file.reviewed" /* FILE_REVIEWED */;
2156
+ }
2157
+ function viteConfigUsesPlugin(shadowRoot, pluginNeedle) {
2158
+ const file = readIfExists(
2159
+ path7.join(shadowRoot, "vite.config.ts"),
2160
+ path7.join(shadowRoot, "vite.config.js")
2161
+ );
2162
+ if (!file) {
2163
+ return [{
2164
+ filePath: path7.join(shadowRoot, "vite.config.ts"),
2165
+ errors: ["Missing vite.config.ts for this Vite SPA target."]
2166
+ }];
2167
+ }
2168
+ const issues = [];
2169
+ if (!pluginNeedle.test(file.content)) {
2170
+ issues.push({
2171
+ filePath: file.filePath,
2172
+ errors: ["vite.config.ts does not register the plugin for the target framework."]
2173
+ });
1949
2174
  }
1950
- };
1951
- var WhenFileRejected2 = class extends SituationSpecification5 {
1952
- isSatisfiedBy({ event }) {
1953
- return event.type === "file.rejected" /* FILE_REJECTED */;
2175
+ if (/__dirname/.test(file.content) && !/import\.meta\.dirname/.test(file.content)) {
2176
+ issues.push({
2177
+ filePath: file.filePath,
2178
+ errors: ["vite.config.ts uses __dirname in ESM. Replace it with import.meta.dirname."]
2179
+ });
1954
2180
  }
1955
- };
1956
- var WhenFileFatalMismatch2 = class extends SituationSpecification5 {
1957
- isSatisfiedBy({ event }) {
1958
- return event.type === "file.fatal_mismatch" /* FILE_FATAL_MISMATCH */;
2181
+ return issues;
2182
+ }
2183
+ function indexHtmlIssues(shadowRoot, mountId, scriptNeedle) {
2184
+ const html = readIfExists(path7.join(shadowRoot, "index.html"));
2185
+ if (!html) {
2186
+ return [{
2187
+ filePath: path7.join(shadowRoot, "index.html"),
2188
+ errors: [`Missing Vite index.html. It must contain <div id="${mountId}"> and a module script to the SPA entry.`]
2189
+ }];
2190
+ }
2191
+ const issues = [];
2192
+ if (!new RegExp(`id=["']${mountId}["']`).test(html.content)) {
2193
+ issues.push({
2194
+ filePath: html.filePath,
2195
+ errors: [`index.html must mount the app on #${mountId} (the entry's mount() / createApp / createRoot target).`]
2196
+ });
1959
2197
  }
1960
- };
1961
- var WhenMigrationCompleted = class extends SituationSpecification5 {
1962
- isSatisfiedBy({ event }) {
1963
- return event.type === "migration.completed" /* MIGRATION_COMPLETED */;
2198
+ if (!scriptNeedle.test(html.content)) {
2199
+ issues.push({
2200
+ filePath: html.filePath,
2201
+ errors: ['index.html must load the SPA entry as <script type="module" src="/src/main.ts"> (or main.tsx).']
2202
+ });
1964
2203
  }
1965
- };
1966
- function appendToReport(shadowDir, content) {
1967
- if (!shadowDir) return;
1968
- const reportPath = path5.join(shadowDir, "migration-report.md");
1969
- try {
1970
- fs6.appendFileSync(reportPath, content + "\n", "utf-8");
1971
- } catch (e) {
1972
- console.error("[ReporterAgent] Error writing to report:", e);
2204
+ return issues;
2205
+ }
2206
+ function leftoverNextApp(shadowRoot) {
2207
+ const files = [
2208
+ path7.join(shadowRoot, "src", "app", "page.tsx"),
2209
+ path7.join(shadowRoot, "src", "app", "layout.tsx"),
2210
+ path7.join(shadowRoot, "app", "page.tsx"),
2211
+ path7.join(shadowRoot, "app", "layout.tsx")
2212
+ ].filter((file) => fs6.existsSync(file));
2213
+ return files.map((filePath) => ({
2214
+ filePath,
2215
+ errors: ["Leftover Next App Router file. Delete it after the target SPA/CLI boots so it cannot be mistaken for the new tree."]
2216
+ }));
2217
+ }
2218
+ function leftoverViteWhenAngular(shadowRoot) {
2219
+ const issues = [];
2220
+ for (const rel of ["vite.config.ts", "vite.config.js", "index.html"]) {
2221
+ const filePath = path7.join(shadowRoot, rel);
2222
+ if (fs6.existsSync(filePath)) {
2223
+ issues.push({
2224
+ filePath,
2225
+ errors: [`Leftover Vite file ${rel}. Angular boots from src/main.ts + src/index.html + angular.json \u2014 delete the Vite entry.`]
2226
+ });
2227
+ }
1973
2228
  }
2229
+ return issues;
1974
2230
  }
1975
- var startedProcessor = {
1976
- async apply({ event }) {
1977
- const p = event.payload;
1978
- if (!("shadowWorkspacePath" in p)) return;
1979
- const shadowPath = p.shadowWorkspacePath;
1980
- if (!shadowPath) return;
1981
- const reportPath = path5.join(shadowPath, "migration-report.md");
1982
- const header = `# Metamorph Migration Report
1983
- **Plan ID**: ${p.planId}
1984
- **Date Started**: ${(/* @__PURE__ */ new Date()).toLocaleString()}
1985
- ---
1986
-
1987
- ## Migration Log
1988
-
2231
+ function isStubEntry(content) {
2232
+ const hasLocalImport = /from\s+['"](\.\.?\/|@\/)/.test(content);
2233
+ const emptyHeading = /<h1[^>]*>[^<]{0,40}<\/h1>|TODO:|coming soon/i.test(content);
2234
+ return emptyHeading && !hasLocalImport;
2235
+ }
2236
+ function analyzeVueTarget(shadowRoot) {
2237
+ const issues = [
2238
+ ...indexHtmlIssues(shadowRoot, "app", /src\/main\.(t|j)sx?/),
2239
+ ...viteConfigUsesPlugin(shadowRoot, /plugin-vue|@vitejs\/plugin-vue/),
2240
+ ...leftoverScripts(shadowRoot, /\b(next|ng)\b/, "package.json still runs Next/Angular scripts; Vue target must use Vite"),
2241
+ ...leftoverNextApp(shadowRoot)
2242
+ ];
2243
+ const main = readIfExists(path7.join(shadowRoot, "src", "main.ts"), path7.join(shadowRoot, "src", "main.js"));
2244
+ if (!main) {
2245
+ issues.push({
2246
+ filePath: path7.join(shadowRoot, "src", "main.ts"),
2247
+ errors: ['Missing src/main.ts. createApp(App).use(router).mount("#app") and import global CSS.']
2248
+ });
2249
+ } else {
2250
+ if (!/createApp\(/.test(main.content) || !/mount\(/.test(main.content)) {
2251
+ issues.push({
2252
+ filePath: main.filePath,
2253
+ errors: ['Vue entry must createApp(...).mount("#app"). Do not leave ReactDOM.createRoot here.']
2254
+ });
2255
+ }
2256
+ if (/createRoot|react-dom/.test(main.content)) {
2257
+ issues.push({
2258
+ filePath: main.filePath,
2259
+ errors: ["Vue entry still mounts React. Replace createRoot with createApp and delete ReactDOM."]
2260
+ });
2261
+ }
2262
+ if (!/import\s+['"][^'"]+\.(?:css|scss|sass)['"]/.test(main.content)) {
2263
+ const css = path7.join(shadowRoot, "src", "index.css");
2264
+ if (fs6.existsSync(css)) {
2265
+ issues.push({
2266
+ filePath: main.filePath,
2267
+ errors: ["Global CSS exists but is not imported from src/main.ts. Vite will ship an unstyled app."]
2268
+ });
2269
+ }
2270
+ }
2271
+ if (isStubEntry(main.content)) {
2272
+ issues.push({
2273
+ filePath: main.filePath,
2274
+ errors: ["Vue entry does not import the real App/router. Import existing screens; do not mount an empty shell."]
2275
+ });
2276
+ }
2277
+ }
2278
+ return issues;
2279
+ }
2280
+ function analyzeSvelteTarget(shadowRoot) {
2281
+ const issues = [
2282
+ ...indexHtmlIssues(shadowRoot, "app", /src\/main\.(t|j)sx?/),
2283
+ ...viteConfigUsesPlugin(shadowRoot, /vite-plugin-svelte|svelte\(/),
2284
+ ...leftoverScripts(shadowRoot, /\b(next|ng)\b/, "package.json still runs Next/Angular scripts; Svelte target must use Vite"),
2285
+ ...leftoverNextApp(shadowRoot)
2286
+ ];
2287
+ const kit = path7.join(shadowRoot, "src", "routes", "+page.svelte");
2288
+ if (fs6.existsSync(kit)) {
2289
+ issues.push({
2290
+ filePath: kit,
2291
+ errors: ["SvelteKit +page.svelte appeared but this catalog targets a Vite SPA. Use App.svelte + mount() instead of Kit file routes."]
2292
+ });
2293
+ }
2294
+ const main = readIfExists(path7.join(shadowRoot, "src", "main.ts"), path7.join(shadowRoot, "src", "main.js"));
2295
+ if (!main) {
2296
+ issues.push({
2297
+ filePath: path7.join(shadowRoot, "src", "main.ts"),
2298
+ errors: ['Missing src/main.ts. Use mount(App, { target: document.getElementById("app") }) and import global CSS.']
2299
+ });
2300
+ } else if (!/from\s+['"]svelte['"]/.test(main.content) && !/new\s+\w+\(\s*\{\s*target/.test(main.content)) {
2301
+ issues.push({
2302
+ filePath: main.filePath,
2303
+ errors: ["Svelte entry must mount the root .svelte component onto #app."]
2304
+ });
2305
+ }
2306
+ return issues;
2307
+ }
2308
+ function analyzeAngularTarget(shadowRoot) {
2309
+ const issues = [
2310
+ ...leftoverViteWhenAngular(shadowRoot),
2311
+ ...leftoverScripts(shadowRoot, /\b(vite|next)\b/, "package.json still runs Vite/Next; Angular target must use ng serve / ng build")
2312
+ ];
2313
+ const angularJson = path7.join(shadowRoot, "angular.json");
2314
+ if (!fs6.existsSync(angularJson)) {
2315
+ issues.push({
2316
+ filePath: angularJson,
2317
+ errors: ["Missing angular.json. ng build will not run. Scaffold it and point browser to src/main.ts and styles to src/styles.css."]
2318
+ });
2319
+ }
2320
+ const main = readIfExists(path7.join(shadowRoot, "src", "main.ts"));
2321
+ if (!main || !/bootstrapApplication\(/.test(main.content || "")) {
2322
+ issues.push({
2323
+ filePath: path7.join(shadowRoot, "src", "main.ts"),
2324
+ errors: ["Missing bootstrapApplication in src/main.ts. Angular standalone boots here, not from a Vite main.tsx."]
2325
+ });
2326
+ }
2327
+ const routes = readIfExists(path7.join(shadowRoot, "src", "app", "app.routes.ts"));
2328
+ if (routes && /export const routes: Routes = \[\s*\]/.test(routes.content)) {
2329
+ issues.push({
2330
+ filePath: routes.filePath,
2331
+ errors: ["app.routes.ts has no routes. Map each source URL to the existing feature component. App stays a <router-outlet /> shell."]
2332
+ });
2333
+ }
2334
+ const html = readIfExists(path7.join(shadowRoot, "src", "index.html"));
2335
+ if (!html) {
2336
+ issues.push({
2337
+ filePath: path7.join(shadowRoot, "src", "index.html"),
2338
+ errors: ["Missing src/index.html with <app-root></app-root>."]
2339
+ });
2340
+ }
2341
+ return issues;
2342
+ }
2343
+ function vueFileHint(filePath, target) {
2344
+ if (target !== "vue") return "";
2345
+ const normalized = filePath.replace(/\\/g, "/");
2346
+ if (/\/main\.(t|j)sx?$/.test(normalized)) {
2347
+ return `
2348
+ This is the Vue entry. It must createApp(App).use(router).mount("#app") and import global CSS.
2349
+ index.html uses #app, not #root. Do not leave createRoot/ReactDOM.
1989
2350
  `;
1990
- fs6.writeFileSync(reportPath, header, "utf-8");
1991
- console.log(`[ReporterAgent] Initialized report at ${reportPath}`);
1992
2351
  }
1993
- };
1994
- var migratedProcessor = {
1995
- async apply({ event }) {
1996
- const p = event.payload;
1997
- const { resolveRuntime: resolveRuntime2 } = await import("./runtime-5OVGH2Y7.js");
1998
- const runtime = resolveRuntime2();
1999
- const plan = await runtime.state.repository.getPlan(p.planId);
2000
- if (!plan) return;
2001
- const shadowDir = plan.targetPath;
2002
- appendToReport(shadowDir, `- \u23F3 **Refactored**: \`${p.filePath}\` (Pending Review)`);
2352
+ if (/\.(t|j)sx$/.test(normalized) && !/vite\.config/.test(normalized)) {
2353
+ return `
2354
+ Convert this UI file into a .vue SFC (<script setup> + template + style). Keep prop and emit names. Then delete this leftover TSX once the SFC exists.
2355
+ `;
2003
2356
  }
2004
- };
2005
- var reviewedProcessor = {
2006
- async apply({ event }) {
2007
- const p = event.payload;
2008
- const { resolveRuntime: resolveRuntime2 } = await import("./runtime-5OVGH2Y7.js");
2009
- const runtime = resolveRuntime2();
2010
- const plan = await runtime.state.repository.getPlan(p.planId);
2011
- if (!plan) return;
2012
- appendToReport(plan.targetPath, `- \u2705 **Approved**: \`${p.filePath}\``);
2357
+ return "";
2358
+ }
2359
+ function svelteFileHint(filePath, target) {
2360
+ if (target !== "svelte") return "";
2361
+ const normalized = filePath.replace(/\\/g, "/");
2362
+ if (/\/main\.(t|j)sx?$/.test(normalized)) {
2363
+ return `
2364
+ This is the Svelte entry. mount(App, { target: document.getElementById("app") }). Do not create SvelteKit +page.svelte routes.
2365
+ `;
2366
+ }
2367
+ return "";
2368
+ }
2369
+ function angularFileHint(filePath, target) {
2370
+ if (target !== "angular") return "";
2371
+ const normalized = filePath.replace(/\\/g, "/");
2372
+ if (/app\.routes\.ts$/.test(normalized)) {
2373
+ return `
2374
+ Map each source route to an existing feature component. Do not inline that component's template. App is <router-outlet />.
2375
+ Keep @Input / @Output names when the host binds them.
2376
+ `;
2377
+ }
2378
+ if (/\/main\.(t|j)sx$/.test(normalized)) {
2379
+ return `
2380
+ Vite/React main.tsx is not the Angular entry. Angular boots from src/main.ts via bootstrapApplication. Delete this file after src/main.ts exists.
2381
+ `;
2382
+ }
2383
+ return "";
2384
+ }
2385
+
2386
+ // ../application/src/migration/plugins/frontendTargets.ts
2387
+ var vueTargetPlugin = {
2388
+ id: "target-vue",
2389
+ target: "vue",
2390
+ layer: "frontend",
2391
+ fileHint(filePath, ctx) {
2392
+ return vueFileHint(filePath, ctx.target);
2393
+ },
2394
+ verifyShadow(shadowRoot) {
2395
+ return analyzeVueTarget(shadowRoot);
2396
+ },
2397
+ repairTargets(shadowRoot) {
2398
+ return [
2399
+ path8.join(shadowRoot, "package.json"),
2400
+ path8.join(shadowRoot, "vite.config.ts"),
2401
+ path8.join(shadowRoot, "index.html"),
2402
+ path8.join(shadowRoot, "src", "main.ts"),
2403
+ path8.join(shadowRoot, "src", "App.vue")
2404
+ ];
2013
2405
  }
2014
2406
  };
2015
- var rejectedProcessor = {
2016
- async apply({ event }) {
2017
- const p = event.payload;
2018
- const { resolveRuntime: resolveRuntime2 } = await import("./runtime-5OVGH2Y7.js");
2019
- const runtime = resolveRuntime2();
2020
- const plan = await runtime.state.repository.getPlan(p.planId);
2021
- if (!plan) return;
2022
- appendToReport(plan.targetPath, `- \u26A0\uFE0F **Rejected**: \`${p.filePath}\`
2023
- > Errors: ${p.errors?.join(", ")}`);
2407
+ var svelteTargetPlugin = {
2408
+ id: "target-svelte",
2409
+ target: "svelte",
2410
+ layer: "frontend",
2411
+ fileHint(filePath, ctx) {
2412
+ return svelteFileHint(filePath, ctx.target);
2413
+ },
2414
+ verifyShadow(shadowRoot) {
2415
+ return analyzeSvelteTarget(shadowRoot);
2416
+ },
2417
+ repairTargets(shadowRoot) {
2418
+ return [
2419
+ path8.join(shadowRoot, "package.json"),
2420
+ path8.join(shadowRoot, "vite.config.ts"),
2421
+ path8.join(shadowRoot, "svelte.config.js"),
2422
+ path8.join(shadowRoot, "index.html"),
2423
+ path8.join(shadowRoot, "src", "main.ts"),
2424
+ path8.join(shadowRoot, "src", "App.svelte")
2425
+ ];
2024
2426
  }
2025
2427
  };
2026
- var fatalProcessor = {
2027
- async apply({ event }) {
2028
- const p = event.payload;
2029
- const { resolveRuntime: resolveRuntime2 } = await import("./runtime-5OVGH2Y7.js");
2030
- const runtime = resolveRuntime2();
2031
- const plan = await runtime.state.repository.getPlan(p.planId);
2032
- if (!plan) return;
2033
- appendToReport(plan.targetPath, `- \u274C **FATAL MISMATCH**: \`${p.filePath}\`
2034
- > Reason: ${p.reason}`);
2428
+ var angularTargetPlugin = {
2429
+ id: "target-angular",
2430
+ target: "angular",
2431
+ layer: "frontend",
2432
+ fileHint(filePath, ctx) {
2433
+ return angularFileHint(filePath, ctx.target);
2434
+ },
2435
+ verifyShadow(shadowRoot) {
2436
+ return analyzeAngularTarget(shadowRoot);
2437
+ },
2438
+ repairTargets(shadowRoot) {
2439
+ return [
2440
+ path8.join(shadowRoot, "package.json"),
2441
+ path8.join(shadowRoot, "angular.json"),
2442
+ path8.join(shadowRoot, "src", "main.ts"),
2443
+ path8.join(shadowRoot, "src", "index.html"),
2444
+ path8.join(shadowRoot, "src", "styles.css"),
2445
+ path8.join(shadowRoot, "src", "app", "app.ts"),
2446
+ path8.join(shadowRoot, "src", "app", "app.routes.ts"),
2447
+ path8.join(shadowRoot, "src", "app", "app.config.ts")
2448
+ ];
2035
2449
  }
2036
2450
  };
2037
- var completedProcessor = {
2038
- async apply({ event }) {
2039
- const p = event.payload;
2040
- const { resolveRuntime: resolveRuntime2 } = await import("./runtime-5OVGH2Y7.js");
2041
- const runtime = resolveRuntime2();
2042
- const plan = await runtime.state.repository.getPlan(p.planId);
2043
- if (!plan) return;
2044
- const totalFiles = plan.tasks ? plan.tasks.length : 0;
2045
- const migratedFiles = plan.tasks ? plan.tasks.filter((t) => t.status === "completed").length : 0;
2046
- const footer = `
2047
- ---
2048
- ## Migration Completed
2049
- **Date Finished**: ${(/* @__PURE__ */ new Date()).toLocaleString()}
2050
- **Total Files**: ${totalFiles}
2051
- **Successfully Migrated**: ${migratedFiles}
2451
+
2452
+ // ../application/src/migration/plugins/nextToReact.ts
2453
+ import * as fs7 from "fs";
2454
+ import * as path9 from "path";
2455
+ function readIfExists2(...candidates) {
2456
+ for (const filePath of candidates) {
2457
+ if (fs7.existsSync(filePath) && fs7.statSync(filePath).isFile()) {
2458
+ return { filePath, content: fs7.readFileSync(filePath, "utf-8") };
2459
+ }
2460
+ }
2461
+ return null;
2462
+ }
2463
+ function listCssFiles(dir) {
2464
+ if (!fs7.existsSync(dir)) return [];
2465
+ return fs7.readdirSync(dir).filter((name) => /\.(css|scss|sass)$/i.test(name)).map((name) => path9.join(dir, name));
2466
+ }
2467
+ function hasLocalComponentImport(content) {
2468
+ return /import\s+(?:type\s+)?(?:[\w*{}\s,]+)\s+from\s+['"](\.\.?\/|@\/)(?!.*\.(?:css|scss|sass)['"])/i.test(content) || /import\s+['"](\.\.?\/|@\/)[^'"]+\.(t|j)sx['"]/.test(content);
2469
+ }
2470
+ function isStubSpaEntry(content) {
2471
+ const emptyMount = /<div\s+id=["']app["']\s*\/>|<div\s*\/>\s*[;)]|return\s+null\s*;?|return\s*\(\s*<>\s*<\/>\s*\)/i.test(content);
2472
+ const hasRouter = /BrowserRouter|RouterProvider|createBrowserRouter|HashRouter/i.test(content);
2473
+ if (hasRouter && hasLocalComponentImport(content)) return false;
2474
+ if (emptyMount) return true;
2475
+ if (!hasLocalComponentImport(content) && /createRoot\(/.test(content)) return true;
2476
+ return false;
2477
+ }
2478
+ function listTsxFiles(dir) {
2479
+ if (!fs7.existsSync(dir)) return [];
2480
+ const out = [];
2481
+ const stack = [dir];
2482
+ while (stack.length > 0) {
2483
+ const current = stack.pop();
2484
+ for (const entry of fs7.readdirSync(current, { withFileTypes: true })) {
2485
+ const full = path9.join(current, entry.name);
2486
+ if (entry.isDirectory()) stack.push(full);
2487
+ else if (/\.(t|j)sx$/.test(entry.name) && !entry.name.endsWith(".obsolete")) out.push(full);
2488
+ }
2489
+ }
2490
+ return out;
2491
+ }
2492
+ function leftoverNextAppFiles(shadowRoot) {
2493
+ const dirs = [path9.join(shadowRoot, "src", "app"), path9.join(shadowRoot, "app")];
2494
+ const found = [];
2495
+ for (const dir of dirs) {
2496
+ for (const file of listTsxFiles(dir)) {
2497
+ const base = path9.basename(file);
2498
+ if (/^(page|layout|template|loading|error|not-found)\.(t|j)sx$/.test(base)) {
2499
+ found.push(file);
2500
+ }
2501
+ }
2502
+ }
2503
+ return found;
2504
+ }
2505
+ function findUnmountedScreens(shadowRoot, entryContent) {
2506
+ const candidates = [
2507
+ path9.join(shadowRoot, "src", "App.tsx"),
2508
+ path9.join(shadowRoot, "src", "App.jsx"),
2509
+ path9.join(shadowRoot, "src", "routes", "AppRoutes.tsx"),
2510
+ path9.join(shadowRoot, "src", "routes", "AppRoutes.jsx")
2511
+ ];
2512
+ const unused = [];
2513
+ for (const file of candidates) {
2514
+ if (!fs7.existsSync(file)) continue;
2515
+ const stem = path9.basename(file).replace(/\.(t|j)sx$/, "");
2516
+ if (!entryContent.includes(stem)) unused.push(file);
2517
+ }
2518
+ return unused;
2519
+ }
2520
+ function analyzeNextToReactBootstrap(shadowRoot) {
2521
+ const issues = [];
2522
+ const main = readIfExists2(
2523
+ path9.join(shadowRoot, "src", "main.tsx"),
2524
+ path9.join(shadowRoot, "src", "main.jsx"),
2525
+ path9.join(shadowRoot, "src", "index.tsx")
2526
+ );
2527
+ if (!main) {
2528
+ issues.push({
2529
+ filePath: path9.join(shadowRoot, "src", "main.tsx"),
2530
+ errors: [
2531
+ "Missing SPA entry. Create src/main.tsx that mounts BrowserRouter and the former home screen into #root. Vite can start with a blank page if this file only renders an empty div."
2532
+ ]
2533
+ });
2534
+ return issues;
2535
+ }
2536
+ if (isStubSpaEntry(main.content)) {
2537
+ issues.push({
2538
+ filePath: main.filePath,
2539
+ errors: [
2540
+ "src/main.tsx does not mount the application UI. Import the router and the former home screen. Do not render only an empty element, null, or a placeholder \u2014 that is a blank page even when npm run dev succeeds."
2541
+ ]
2542
+ });
2543
+ } else if (!/BrowserRouter|RouterProvider|createBrowserRouter|HashRouter/i.test(main.content)) {
2544
+ issues.push({
2545
+ filePath: main.filePath,
2546
+ errors: [
2547
+ "SPA entry has no React Router. Wrap the tree in <BrowserRouter> (or RouterProvider) so former Next routes still have URLs."
2548
+ ]
2549
+ });
2550
+ }
2551
+ const unused = findUnmountedScreens(shadowRoot, main.content);
2552
+ for (const file of unused) {
2553
+ issues.push({
2554
+ filePath: file,
2555
+ errors: [
2556
+ `${path9.relative(shadowRoot, file)} exists but is not imported from the SPA entry. Wire it from src/main.tsx or it will never render.`
2557
+ ]
2558
+ });
2559
+ }
2560
+ for (const file of leftoverNextAppFiles(shadowRoot)) {
2561
+ issues.push({
2562
+ filePath: file,
2563
+ errors: [
2564
+ "Leftover Next App Router file. After the Vite SPA boots, delete src/app/page.tsx and layout.tsx (or they confuse the next run)."
2565
+ ]
2566
+ });
2567
+ }
2568
+ const pkgPath = path9.join(shadowRoot, "package.json");
2569
+ if (fs7.existsSync(pkgPath)) {
2570
+ try {
2571
+ const pkg = JSON.parse(fs7.readFileSync(pkgPath, "utf-8"));
2572
+ const nextScripts = Object.entries(pkg.scripts || {}).filter(([, cmd]) => /\bnext\b/.test(cmd));
2573
+ if (nextScripts.length > 0) {
2574
+ issues.push({
2575
+ filePath: pkgPath,
2576
+ errors: [
2577
+ `package.json still runs Next in scripts (${nextScripts.map(([name]) => name).join(", ")}). Replace them with Vite/eslint equivalents (dev/build/preview/start/lint).`
2578
+ ]
2579
+ });
2580
+ }
2581
+ } catch {
2582
+ }
2583
+ }
2584
+ return issues;
2585
+ }
2586
+ function analyzeNextToReactStyles(shadowRoot) {
2587
+ const issues = [];
2588
+ const main = readIfExists2(
2589
+ path9.join(shadowRoot, "src", "main.tsx"),
2590
+ path9.join(shadowRoot, "src", "main.jsx"),
2591
+ path9.join(shadowRoot, "src", "main.ts"),
2592
+ path9.join(shadowRoot, "src", "index.tsx")
2593
+ );
2594
+ const layout = readIfExists2(
2595
+ path9.join(shadowRoot, "src", "app", "layout.tsx"),
2596
+ path9.join(shadowRoot, "src", "app", "layout.jsx"),
2597
+ path9.join(shadowRoot, "app", "layout.tsx"),
2598
+ path9.join(shadowRoot, "app", "layout.jsx")
2599
+ );
2600
+ const cssFromLayout = [];
2601
+ if (layout) {
2602
+ const importRe = /import\s+['"]([^'"]+\.(?:css|scss|sass))['"]/g;
2603
+ let match;
2604
+ while ((match = importRe.exec(layout.content)) !== null) {
2605
+ cssFromLayout.push(match[1]);
2606
+ }
2607
+ }
2608
+ const srcCss = listCssFiles(path9.join(shadowRoot, "src"));
2609
+ const hasGlobalCss = srcCss.length > 0 || cssFromLayout.length > 0;
2610
+ if (!main) {
2611
+ if (hasGlobalCss) {
2612
+ issues.push({
2613
+ filePath: path9.join(shadowRoot, "src", "main.tsx"),
2614
+ errors: [
2615
+ "Missing Vite/React entry (src/main.tsx). Create it so it mounts the app into #root and imports the global CSS that Next layout used to load."
2616
+ ]
2617
+ });
2618
+ }
2619
+ return issues;
2620
+ }
2621
+ const entryImportsCss = /import\s+['"][^'"]+\.(?:css|scss|sass)['"]/.test(main.content);
2622
+ if (hasGlobalCss && !entryImportsCss) {
2623
+ const hint = cssFromLayout[0] || (srcCss[0] ? `./${path9.basename(srcCss[0])}` : "./index.css");
2624
+ issues.push({
2625
+ filePath: main.filePath,
2626
+ errors: [
2627
+ `Global CSS is not imported from the SPA entry. Next.js applied styles via layout; Vite only bundles CSS imported from JS. Add \`import '${hint}'\` (or the path layout used) to ${path9.basename(main.filePath)}.`
2628
+ ]
2629
+ });
2630
+ }
2631
+ const html = readIfExists2(path9.join(shadowRoot, "index.html"));
2632
+ if (html && !/src\/main\.(t|j)sx?/.test(html.content) && !/\/src\/index\.(t|j)sx?/.test(html.content)) {
2633
+ issues.push({
2634
+ filePath: html.filePath,
2635
+ errors: [
2636
+ 'index.html must load the SPA entry as a module script (typically <script type="module" src="/src/main.tsx">).'
2637
+ ]
2638
+ });
2639
+ }
2640
+ const viteConfig = readIfExists2(
2641
+ path9.join(shadowRoot, "vite.config.ts"),
2642
+ path9.join(shadowRoot, "vite.config.js")
2643
+ );
2644
+ if (viteConfig && /__dirname/.test(viteConfig.content) && !/import\.meta\.dirname/.test(viteConfig.content)) {
2645
+ issues.push({
2646
+ filePath: viteConfig.filePath,
2647
+ errors: [
2648
+ "vite.config.ts uses __dirname in ESM. Replace it with import.meta.dirname so aliases and CSS resolution work under Vite."
2649
+ ]
2650
+ });
2651
+ }
2652
+ return issues;
2653
+ }
2654
+ var nextToReactPlugin = {
2655
+ id: "next-to-react",
2656
+ source: "next",
2657
+ target: "react",
2658
+ layer: "frontend",
2659
+ fileHint(filePath) {
2660
+ const normalized = filePath.replace(/\\/g, "/");
2661
+ if (/\/(main|index)\.(t|j)sx?$/i.test(normalized) && !/\/src\/app\//i.test(normalized)) {
2662
+ return `
2663
+ This is the Vite/React entry. It must:
2664
+ 1. import the global CSS Next loaded from layout (often ./index.css)
2665
+ 2. wrap the tree in BrowserRouter
2666
+ 3. render the former home screen (whatever the source used as "/")
2667
+ Never render only an empty mount node \u2014 Vite will show a blank page and still report ready.
2668
+ `;
2669
+ }
2670
+ if (/layout\.(t|j)sx?$/i.test(normalized) && /\/(src\/)?app\//i.test(normalized)) {
2671
+ return `
2672
+ This Next layout will not run in Vite. Move its global CSS import to src/main.tsx and its HTML shell into index.html. Then delete this layout after the SPA boots.
2052
2673
  `;
2053
- appendToReport(plan.targetPath, footer);
2674
+ }
2675
+ return "";
2676
+ },
2677
+ verifyShadow(shadowRoot) {
2678
+ return [...analyzeNextToReactStyles(shadowRoot), ...analyzeNextToReactBootstrap(shadowRoot)];
2679
+ },
2680
+ repairTargets(shadowRoot) {
2681
+ return [
2682
+ path9.join(shadowRoot, "package.json"),
2683
+ path9.join(shadowRoot, "vite.config.ts"),
2684
+ path9.join(shadowRoot, "vite.config.js"),
2685
+ path9.join(shadowRoot, "index.html"),
2686
+ path9.join(shadowRoot, "src", "main.tsx"),
2687
+ path9.join(shadowRoot, "src", "main.jsx"),
2688
+ path9.join(shadowRoot, "src", "App.tsx"),
2689
+ path9.join(shadowRoot, "src", "App.jsx")
2690
+ ];
2054
2691
  }
2055
2692
  };
2056
- function createReporterAgent() {
2057
- return createAgent5({
2058
- name: "Reporter",
2059
- capabilities: ["reporting"],
2060
- instruction: "You are the Reporter Agent. Your job is to compile a human-readable markdown report of the migration process.",
2061
- tools: [],
2062
- handlers: [
2063
- { specification: new WhenMigrationStarted(), processor: startedProcessor },
2064
- { specification: new WhenFileMigrated2(), processor: migratedProcessor },
2065
- { specification: new WhenFileReviewed(), processor: reviewedProcessor },
2066
- { specification: new WhenFileRejected2(), processor: rejectedProcessor },
2067
- { specification: new WhenFileFatalMismatch2(), processor: fatalProcessor },
2068
- { specification: new WhenMigrationCompleted(), processor: completedProcessor }
2069
- ]
2070
- });
2693
+
2694
+ // ../application/src/migration/plugins/reactToNext.ts
2695
+ import * as path10 from "path";
2696
+ var reactToNextPlugin = {
2697
+ id: "react-to-next",
2698
+ source: "react",
2699
+ target: "next",
2700
+ layer: "frontend",
2701
+ fileHint(filePath, ctx) {
2702
+ return nextMigrationFileHint(filePath, ctx.target);
2703
+ },
2704
+ verifyShadow(shadowRoot) {
2705
+ return analyzeReactToNextStructure(shadowRoot);
2706
+ },
2707
+ repairTargets(shadowRoot) {
2708
+ return [
2709
+ path10.join(shadowRoot, "package.json"),
2710
+ path10.join(shadowRoot, "next.config.ts"),
2711
+ path10.join(shadowRoot, "next.config.js"),
2712
+ path10.join(shadowRoot, "next.config.mjs"),
2713
+ path10.join(shadowRoot, "src", "app", "page.tsx"),
2714
+ path10.join(shadowRoot, "src", "app", "layout.tsx"),
2715
+ path10.join(shadowRoot, "app", "page.tsx"),
2716
+ path10.join(shadowRoot, "app", "layout.tsx"),
2717
+ ...listAppRouterFiles(shadowRoot)
2718
+ ];
2719
+ }
2720
+ };
2721
+
2722
+ // ../application/src/migration/plugins/index.ts
2723
+ function registerBuiltinMigrationPlugins() {
2724
+ registerMigrationPlugin(reactToNextPlugin);
2725
+ registerMigrationPlugin(nextToReactPlugin);
2726
+ registerMigrationPlugin(vueTargetPlugin);
2727
+ registerMigrationPlugin(svelteTargetPlugin);
2728
+ registerMigrationPlugin(angularTargetPlugin);
2071
2729
  }
2072
2730
 
2073
2731
  // ../application/src/index.ts
2074
- import { createAgent as createAgent6, SituationSpecification as SituationSpecification6 } from "@mozaik-ai/core";
2732
+ import { createAgent as createAgent8, createHuman, SituationSpecification as SituationSpecification8 } from "@mozaik-ai/core";
2075
2733
 
2076
2734
  // ../application/src/MigrationRunner.ts
2077
2735
  import { randomUUID } from "crypto";
@@ -2103,8 +2761,9 @@ var MigrationRunner = class {
2103
2761
  */
2104
2762
  async startMigration(request) {
2105
2763
  this.ensureInitialized();
2106
- await this.store.reset();
2107
- this.workspace.cleanupOldRuns(3);
2764
+ const existing = await this.store.getAllPlans();
2765
+ const protectedRunIds = existing.filter((p) => p.appliedAt).map((p) => p.runId);
2766
+ this.workspace.cleanupOldRuns(3, protectedRunIds);
2108
2767
  const runId = `run_${Date.now()}`;
2109
2768
  const shadowPath = this.workspace.cloneDirectory(request.targetPath, runId);
2110
2769
  const planId = `plan_${randomUUID()}`;
@@ -2113,19 +2772,21 @@ var MigrationRunner = class {
2113
2772
  runId,
2114
2773
  profile: { source: request.from, target: request.to },
2115
2774
  targetPath: request.targetPath,
2775
+ phase: "files",
2116
2776
  tasks: [
2117
2777
  { filePath: "system:package_manager", status: "pending" }
2118
2778
  ],
2119
2779
  createdAt: /* @__PURE__ */ new Date()
2120
2780
  };
2121
2781
  await this.store.savePlan(plan);
2122
- const { createHuman } = await import("@mozaik-ai/core");
2123
- const human = createHuman({ name: "System", capabilities: [], handlers: [] });
2124
- join(human);
2782
+ const dispatcherId = resolveRuntime().state.dispatcherId;
2783
+ if (!dispatcherId) {
2784
+ throw new Error("Mozaik System participant is not joined.");
2785
+ }
2125
2786
  sendEvent(
2126
2787
  {
2127
2788
  type: "migration.started" /* MIGRATION_STARTED */,
2128
- producerId: human.getId(),
2789
+ producerId: dispatcherId,
2129
2790
  occurredAt: /* @__PURE__ */ new Date(),
2130
2791
  payload: {
2131
2792
  planId,
@@ -2133,7 +2794,7 @@ var MigrationRunner = class {
2133
2794
  shadowWorkspacePath: shadowPath
2134
2795
  }
2135
2796
  },
2136
- human.getId()
2797
+ dispatcherId
2137
2798
  );
2138
2799
  return { planId, shadowPath, runId };
2139
2800
  }
@@ -2149,15 +2810,19 @@ var MigrationRunner = class {
2149
2810
 
2150
2811
  // ../application/src/index.ts
2151
2812
  function bootstrapMetamorph(repository, tools = []) {
2813
+ registerBuiltinMigrationPlugins();
2152
2814
  initializeRuntime({
2153
2815
  state: new MetamorphState(repository)
2154
2816
  });
2155
- class AllEventsSpecification extends SituationSpecification6 {
2817
+ const dispatcher = createHuman({ name: "System", capabilities: [], handlers: [] });
2818
+ join(dispatcher);
2819
+ resolveRuntime().state.dispatcherId = dispatcher.getId();
2820
+ class AllEventsSpecification extends SituationSpecification8 {
2156
2821
  isSatisfiedBy() {
2157
2822
  return true;
2158
2823
  }
2159
2824
  }
2160
- const loggerAgent = createAgent6({
2825
+ const loggerAgent = createAgent8({
2161
2826
  name: "TelemetryLogger",
2162
2827
  capabilities: [],
2163
2828
  instruction: "You silently log everything.",
@@ -2167,10 +2832,10 @@ function bootstrapMetamorph(repository, tools = []) {
2167
2832
  specification: new AllEventsSpecification(),
2168
2833
  processor: {
2169
2834
  async apply({ event }) {
2170
- const { resolveRuntime: resolveRuntime2 } = await import("./runtime-5OVGH2Y7.js");
2835
+ const { resolveRuntime: resolveRuntime2 } = await import("./runtime-L7VOQROE.js");
2171
2836
  const runtime = resolveRuntime2();
2172
2837
  const repository2 = runtime.state.repository;
2173
- if (event.type.includes("migration") || event.type.includes("file")) {
2838
+ if (event.type.includes("migration") || event.type.includes("file") || event.type.includes("phase") || event.type.includes("system")) {
2174
2839
  await repository2.logEvent(event.type, {
2175
2840
  ...event.payload,
2176
2841
  producerId: event.producerId
@@ -2186,13 +2851,18 @@ function bootstrapMetamorph(repository, tools = []) {
2186
2851
  const worker = createWorkerAgent(tools);
2187
2852
  const reviewer = createReviewerAgent(tools);
2188
2853
  const packageManager = createPackageManagerAgent();
2189
- const reporter = createReporterAgent();
2854
+ const coordinator = createCoordinatorAgent();
2855
+ const reporterTools = tools.filter((tool) => tool.name === "read_file" || tool.name === "write_file" || tool.name === "list_directory");
2856
+ const reporter = createReporterAgent(reporterTools);
2857
+ const integrationAgent = createIntegrationAgent(tools);
2190
2858
  join(mapper);
2191
2859
  join(worker);
2192
2860
  join(reviewer);
2193
2861
  join(packageManager);
2862
+ join(coordinator);
2194
2863
  join(reporter);
2195
- console.log(`[App] Mozaik initialized. Agents joined: Mapper, Worker, Reviewer, PackageManager, Reporter`);
2864
+ join(integrationAgent);
2865
+ console.log(`[App] Mozaik initialized. Agents joined: Mapper, Worker, Reviewer, PackageManager, Coordinator, Reporter, IntegrationAgent`);
2196
2866
  return {
2197
2867
  mapperId: mapper.getId(),
2198
2868
  workerId: worker.getId(),
@@ -2201,9 +2871,18 @@ function bootstrapMetamorph(repository, tools = []) {
2201
2871
  }
2202
2872
 
2203
2873
  // src/index.ts
2874
+ function logStartupContext() {
2875
+ const isDev = process.env.METAMORPH_DEV === "1";
2876
+ console.log(chalk.gray(`[Metamorph] CWD: ${process.cwd()}`));
2877
+ console.log(chalk.gray(`[Metamorph] Binary: ${import.meta.url}`));
2878
+ if (!isDev && import.meta.url.includes("/src/")) {
2879
+ console.log(chalk.yellow(`[Metamorph] Notice: Running directly from source without METAMORPH_DEV=1.`));
2880
+ }
2881
+ }
2204
2882
  var program = new Command();
2205
- program.name("metamorph").description("AI-powered technology migration tool using Mozaik Agents").version("1.0.0");
2883
+ program.name("metamorph").description("AI-powered technology migration tool using Mozaik Agents").version("2.1.1");
2206
2884
  program.command("run [path]").description("Run a migration on the specified directory").option("--from <source>", "Source framework (e.g. express)").option("--to <target>", "Target framework (e.g. fastify)").action(async (targetPathArg, options) => {
2885
+ logStartupContext();
2207
2886
  let targetPath = targetPathArg || ".";
2208
2887
  const { select, input } = await import("@inquirer/prompts");
2209
2888
  let from = options.from;
@@ -2212,9 +2891,11 @@ program.command("run [path]").description("Run a migration on the specified dire
2212
2891
  express: ["fastify", "nestjs"],
2213
2892
  fastify: ["express", "nestjs"],
2214
2893
  nestjs: ["express", "fastify"],
2215
- react: ["next", "vue"],
2216
- next: ["react"],
2217
- vue: ["react"]
2894
+ react: ["next", "vue", "angular", "svelte"],
2895
+ next: ["react", "vue", "angular", "svelte"],
2896
+ vue: ["react", "next", "angular", "svelte"],
2897
+ angular: ["react", "next", "vue", "svelte"],
2898
+ svelte: ["react", "next", "vue", "angular"]
2218
2899
  };
2219
2900
  if (!from) {
2220
2901
  console.log(chalk.gray(`
@@ -2265,12 +2946,16 @@ program.command("run [path]").description("Run a migration on the specified dire
2265
2946
  const spinner = ora("Initializing...").start();
2266
2947
  try {
2267
2948
  const store = new SQLiteStateStore(".metamorph");
2949
+ const shadowBase = path11.resolve(".metamorph/shadow");
2268
2950
  const tools = [
2269
- createReadFileTool(),
2270
- createWriteFileTool(),
2271
- createRenameFileTool(),
2272
- createCreateFileTool(),
2273
- createDeleteFileTool()
2951
+ createReadFileTool(shadowBase),
2952
+ createWriteFileTool(shadowBase),
2953
+ createRenameFileTool(shadowBase),
2954
+ createCreateFileTool(shadowBase),
2955
+ createDeleteFileTool(shadowBase),
2956
+ createListDirectoryTool(shadowBase),
2957
+ createCheckProjectDiagnosticsTool(shadowBase),
2958
+ createRunBuildTool(shadowBase)
2274
2959
  ];
2275
2960
  const runner = new MigrationRunner(store, tools);
2276
2961
  const result = await runner.startMigration({
@@ -2281,22 +2966,71 @@ program.command("run [path]").description("Run a migration on the specified dire
2281
2966
  spinner.succeed(`Migration dispatched! Plan: ${result.planId}`);
2282
2967
  console.log(chalk.gray(`Shadow: ${result.shadowPath}`));
2283
2968
  console.log(chalk.blue(`
2284
- \u23F3 Agents are now working...`));
2285
- await new Promise((resolve3) => setTimeout(resolve3, 2e4));
2286
- console.log(chalk.green(`
2287
- \u2705 Migration simulation finished.`));
2288
- process.exit(0);
2969
+ \u23F3 Swarm is executing migration in shadow workspace...`));
2970
+ const waitSpinner = ora("Waiting for swarm to complete migration...").start();
2971
+ const startTime = Date.now();
2972
+ const MAX_TIMEOUT_MS = 30 * 60 * 1e3;
2973
+ while (true) {
2974
+ if (Date.now() - startTime >= MAX_TIMEOUT_MS) {
2975
+ waitSpinner.fail(chalk.red(`Migration timed out after 30 minutes.`));
2976
+ process.exit(1);
2977
+ }
2978
+ await new Promise((r) => setTimeout(r, 2e3));
2979
+ const plan = await store.getPlan(result.planId);
2980
+ if (!plan) continue;
2981
+ const terminalTasks = plan.tasks.filter((t) => t.status === "completed" || t.status === "failed");
2982
+ const inProgressTasks = plan.tasks.filter((t) => t.status === "in_progress");
2983
+ const pendingTasks = plan.tasks.filter((t) => t.status === "pending");
2984
+ waitSpinner.text = `Phase: ${plan.phase || "files"} | Tasks: ${terminalTasks.length}/${plan.tasks.length} terminal (${inProgressTasks.length} in progress, ${pendingTasks.length} pending)`;
2985
+ if (plan.outcome === "success") {
2986
+ waitSpinner.succeed(chalk.green(`
2987
+ \u2705 Migration finished successfully in shadow workspace!`));
2988
+ console.log(chalk.white(`
2989
+ Next steps:`));
2990
+ console.log(chalk.gray(` 1. Review changes in shadow workspace: `) + chalk.cyan(result.shadowPath));
2991
+ console.log(chalk.gray(` 2. Apply changes to a dedicated Git branch: `) + chalk.cyan(`metamorph apply ${result.runId} ${targetPath}
2992
+ `));
2993
+ process.exit(0);
2994
+ }
2995
+ if (plan.phase === "failed" || plan.outcome === "failed") {
2996
+ waitSpinner.fail(chalk.red(`
2997
+ \u274C Migration failed in shadow workspace.`));
2998
+ console.log(chalk.yellow(`Check .metamorph/shadow/${result.runId}/MIGRATION.md or run 'metamorph ui' for failure details.`));
2999
+ console.log(chalk.gray(`Your original codebase in "${targetPath}" remains completely untouched.
3000
+ `));
3001
+ process.exit(1);
3002
+ }
3003
+ }
2289
3004
  } catch (error) {
2290
3005
  const errorMessage = error instanceof Error ? error.message : String(error);
2291
3006
  spinner.fail(`Migration failed: ${errorMessage}`);
2292
3007
  process.exit(1);
2293
3008
  }
2294
3009
  });
2295
- program.command("apply <runId> <targetPath>").description("Apply a completed migration to the target project").action(async (runId, targetPath) => {
3010
+ program.command("apply <runId> <targetPath>").description("Apply a completed migration to the target project (requires Git at the project or monorepo root)").action(async (runId, targetPath) => {
2296
3011
  try {
3012
+ const store = new SQLiteStateStore(".metamorph");
3013
+ const plans = await store.getAllPlans();
3014
+ const plan = plans.find((p) => p.runId === runId);
3015
+ if (!plan) {
3016
+ throw new Error(`Plan for run "${runId}" not found in database.`);
3017
+ }
3018
+ if (plan.phase !== "completed" || plan.outcome !== "success") {
3019
+ throw new Error(
3020
+ `Cannot apply migration: plan is not completed successfully (phase: "${plan.phase}", outcome: "${plan.outcome || "none"}"). Only successful migrations can be applied.`
3021
+ );
3022
+ }
3023
+ if (plan.appliedAt) {
3024
+ throw new Error(
3025
+ `Migration for run "${runId}" has already been applied at ${new Date(plan.appliedAt).toISOString()} to branch ${plan.appliedBranch || "unknown"}.`
3026
+ );
3027
+ }
2297
3028
  const integrator = new MigrationIntegrator(new ShadowWorkspace());
2298
- const message = await integrator.applyMigration(runId, targetPath);
2299
- console.log(chalk.green(`\u2705 ${message}`));
3029
+ const result = await integrator.applyMigration(runId, targetPath);
3030
+ plan.appliedAt = /* @__PURE__ */ new Date();
3031
+ plan.appliedBranch = result.branch;
3032
+ await store.savePlan(plan);
3033
+ console.log(chalk.green(`\u2705 ${result.message}`));
2300
3034
  } catch (error) {
2301
3035
  console.error(chalk.red(`\u274C Failed to apply migration: ${error instanceof Error ? error.message : String(error)}`));
2302
3036
  process.exit(1);
@@ -2358,6 +3092,7 @@ program.command("list").description("List past migration plans").action(async ()
2358
3092
  }
2359
3093
  });
2360
3094
  program.command("ui").description("Start the Metamorph Dashboard API server").option("-p, --port <number>", "Port for the API server", "9876").action(async (options) => {
3095
+ logStartupContext();
2361
3096
  let desiredPort = parseInt(options.port, 10);
2362
3097
  const spinner = ora("Starting Metamorph API server...").start();
2363
3098
  try {
@@ -2380,12 +3115,16 @@ program.command("ui").description("Start the Metamorph Dashboard API server").op
2380
3115
  process.exit(1);
2381
3116
  }
2382
3117
  const store = new SQLiteStateStore(".metamorph");
3118
+ const shadowBase = path11.resolve(".metamorph/shadow");
2383
3119
  const tools = [
2384
- createReadFileTool(),
2385
- createWriteFileTool(),
2386
- createRenameFileTool(),
2387
- createCreateFileTool(),
2388
- createDeleteFileTool()
3120
+ createReadFileTool(shadowBase),
3121
+ createWriteFileTool(shadowBase),
3122
+ createRenameFileTool(shadowBase),
3123
+ createCreateFileTool(shadowBase),
3124
+ createDeleteFileTool(shadowBase),
3125
+ createListDirectoryTool(shadowBase),
3126
+ createCheckProjectDiagnosticsTool(shadowBase),
3127
+ createRunBuildTool(shadowBase)
2389
3128
  ];
2390
3129
  const runner = new MigrationRunner(store, tools);
2391
3130
  const app = await createApiServer(store, runner);