@nikelyh/metamorph 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,9 @@
1
1
  import {
2
- resolveMigrationCatalog
3
- } from "./chunk-VEAYUIOZ.js";
2
+ DEFAULT_METAMORPH_CONFIG,
3
+ createEmptyMigrationCostSummary,
4
+ resolveMigrationCatalog,
5
+ validateDisabledAgents
6
+ } from "./chunk-7LIGC5J3.js";
4
7
 
5
8
  // ../infrastructure/src/db/SQLiteStateStore.ts
6
9
  import { DatabaseSync } from "node:sqlite";
@@ -62,6 +65,10 @@ var SQLiteStateStore = class {
62
65
  this.db.exec("ALTER TABLE plans ADD COLUMN applied_branch TEXT");
63
66
  } catch (e) {
64
67
  }
68
+ try {
69
+ this.db.exec("ALTER TABLE plans ADD COLUMN package_manager TEXT DEFAULT 'npm'");
70
+ } catch (e) {
71
+ }
65
72
  this.db.exec(`
66
73
  CREATE TABLE IF NOT EXISTS tasks (
67
74
  plan_id TEXT,
@@ -81,11 +88,29 @@ var SQLiteStateStore = class {
81
88
  timestamp TEXT
82
89
  );
83
90
  `);
91
+ this.db.exec(`
92
+ CREATE TABLE IF NOT EXISTS token_usage (
93
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
94
+ run_id TEXT NOT NULL,
95
+ plan_id TEXT NOT NULL,
96
+ agent_role TEXT NOT NULL,
97
+ model_id TEXT NOT NULL,
98
+ prompt_tokens INTEGER NOT NULL,
99
+ completion_tokens INTEGER NOT NULL,
100
+ total_tokens INTEGER NOT NULL,
101
+ cost_usd REAL NOT NULL,
102
+ timestamp TEXT NOT NULL
103
+ );
104
+ `);
105
+ try {
106
+ this.db.exec("CREATE INDEX IF NOT EXISTS idx_token_usage_run ON token_usage(run_id);");
107
+ } catch (e) {
108
+ }
84
109
  }
85
110
  async savePlan(plan) {
86
111
  const stmtPlan = this.db.prepare(`
87
- INSERT OR REPLACE INTO plans (id, run_id, target_path, source_framework, target_framework, rules_json, created_at, integration_rounds, phase, outcome, applied_at, applied_branch)
88
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
112
+ INSERT OR REPLACE INTO plans (id, run_id, target_path, source_framework, target_framework, rules_json, created_at, integration_rounds, phase, outcome, applied_at, applied_branch, package_manager)
113
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
89
114
  `);
90
115
  stmtPlan.run(
91
116
  plan.id,
@@ -99,7 +124,8 @@ var SQLiteStateStore = class {
99
124
  plan.phase || "files",
100
125
  plan.outcome || null,
101
126
  plan.appliedAt ? plan.appliedAt.toISOString() : null,
102
- plan.appliedBranch || null
127
+ plan.appliedBranch || null,
128
+ plan.packageManager || "npm"
103
129
  );
104
130
  const stmtTask = this.db.prepare(`
105
131
  INSERT OR REPLACE INTO tasks (plan_id, file_path, status, dependencies_json, error)
@@ -120,6 +146,7 @@ var SQLiteStateStore = class {
120
146
  id: planRow.id,
121
147
  runId: planRow.run_id,
122
148
  targetPath: planRow.target_path,
149
+ packageManager: planRow.package_manager || "npm",
123
150
  profile: {
124
151
  source: planRow.source_framework,
125
152
  target: planRow.target_framework,
@@ -185,10 +212,65 @@ var SQLiteStateStore = class {
185
212
  timestamp: new Date(row.timestamp)
186
213
  }));
187
214
  }
215
+ async recordTokenUsage(record) {
216
+ const stmt = this.db.prepare(`
217
+ INSERT INTO token_usage (run_id, plan_id, agent_role, model_id, prompt_tokens, completion_tokens, total_tokens, cost_usd, timestamp)
218
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
219
+ `);
220
+ const timestamp = (record.timestamp || /* @__PURE__ */ new Date()).toISOString();
221
+ stmt.run(
222
+ record.runId,
223
+ record.planId,
224
+ record.agentRole,
225
+ record.modelId,
226
+ record.promptTokens,
227
+ record.completionTokens,
228
+ record.totalTokens,
229
+ record.costUsd,
230
+ timestamp
231
+ );
232
+ }
233
+ async getCostSummary(runId) {
234
+ const stmt = this.db.prepare(`
235
+ SELECT * FROM token_usage WHERE run_id = ? ORDER BY id ASC
236
+ `);
237
+ const rows = stmt.all(runId);
238
+ const planId = rows.length > 0 ? rows[0].plan_id : "";
239
+ const summary = createEmptyMigrationCostSummary(runId, planId);
240
+ for (const row of rows) {
241
+ summary.totalTokens += row.total_tokens;
242
+ summary.promptTokens += row.prompt_tokens;
243
+ summary.completionTokens += row.completion_tokens;
244
+ summary.totalCostUsd += row.cost_usd;
245
+ const role = row.agent_role;
246
+ if (summary.byAgentRole[role]) {
247
+ summary.byAgentRole[role].tokens += row.total_tokens;
248
+ summary.byAgentRole[role].promptTokens += row.prompt_tokens;
249
+ summary.byAgentRole[role].completionTokens += row.completion_tokens;
250
+ summary.byAgentRole[role].costUsd += row.cost_usd;
251
+ summary.byAgentRole[role].executions += 1;
252
+ }
253
+ if (!summary.byModel[row.model_id]) {
254
+ summary.byModel[row.model_id] = {
255
+ promptTokens: 0,
256
+ completionTokens: 0,
257
+ totalTokens: 0,
258
+ costUsd: 0
259
+ };
260
+ }
261
+ summary.byModel[row.model_id].promptTokens += row.prompt_tokens;
262
+ summary.byModel[row.model_id].completionTokens += row.completion_tokens;
263
+ summary.byModel[row.model_id].totalTokens += row.total_tokens;
264
+ summary.byModel[row.model_id].costUsd += row.cost_usd;
265
+ }
266
+ summary.totalCostUsd = Math.round(summary.totalCostUsd * 1e6) / 1e6;
267
+ return summary;
268
+ }
188
269
  async reset() {
189
270
  this.db.exec(`DELETE FROM tasks`);
190
271
  this.db.exec(`DELETE FROM plans`);
191
272
  this.db.exec(`DELETE FROM events`);
273
+ this.db.exec(`DELETE FROM token_usage`);
192
274
  }
193
275
  };
194
276
 
@@ -300,9 +382,9 @@ function createRenameFileTool(sandboxDir) {
300
382
  if (!fsExists(resolvedOld)) {
301
383
  return { error: `File not found: ${resolvedOld}` };
302
384
  }
303
- const { mkdirSync: mkdirSync2 } = await import("fs");
304
- const { dirname } = await import("path");
305
- mkdirSync2(dirname(resolvedNew), { recursive: true });
385
+ const { mkdirSync: mkdirSync3 } = await import("fs");
386
+ const { dirname: dirname2 } = await import("path");
387
+ mkdirSync3(dirname2(resolvedNew), { recursive: true });
306
388
  renameSync(resolvedOld, resolvedNew);
307
389
  const sourceFile = project.getSourceFile(resolvedOld);
308
390
  if (sourceFile) {
@@ -332,10 +414,10 @@ function createCreateFileTool(sandboxDir) {
332
414
  strict: true,
333
415
  invoke: async ({ filePath, content }) => {
334
416
  const resolvedPath = assertSandbox(filePath, sandboxDir);
335
- const { writeFileSync, mkdirSync: mkdirSync2 } = await import("fs");
336
- const { dirname } = await import("path");
337
- mkdirSync2(dirname(resolvedPath), { recursive: true });
338
- writeFileSync(resolvedPath, content, "utf-8");
417
+ const { writeFileSync: writeFileSync2, mkdirSync: mkdirSync3 } = await import("fs");
418
+ const { dirname: dirname2 } = await import("path");
419
+ mkdirSync3(dirname2(resolvedPath), { recursive: true });
420
+ writeFileSync2(resolvedPath, content, "utf-8");
339
421
  project.addSourceFileAtPath(resolvedPath);
340
422
  return { success: true, message: `File created at ${resolvedPath}.` };
341
423
  }
@@ -670,15 +752,28 @@ async function createApiServer(store, migrationRunner) {
670
752
  }
671
753
  }
672
754
  });
755
+ app.get("/api/cost/:runId", async (req, res) => {
756
+ try {
757
+ const runId = String(req.params.runId);
758
+ const summary = await store.getCostSummary(runId);
759
+ res.json(summary);
760
+ } catch (error) {
761
+ if (error instanceof Error) {
762
+ res.status(500).json({ error: error.message });
763
+ } else {
764
+ res.status(500).json({ error: String(error) });
765
+ }
766
+ }
767
+ });
673
768
  app.get("/api/browse", async (req, res) => {
674
769
  try {
675
770
  const { readdirSync, statSync: statSync2 } = await import("fs");
676
- const { resolve: resolve5, join: join7 } = await import("path");
771
+ const { resolve: resolve5, join: join8 } = await import("path");
677
772
  const targetPath = req.query.path ? resolve5(String(req.query.path)) : resolve5(".");
678
773
  const entries = readdirSync(targetPath).filter(
679
774
  (name) => !name.startsWith(".") && name !== "node_modules" && name !== "dist" && name !== "build"
680
775
  ).map((name) => {
681
- const full = join7(targetPath, name);
776
+ const full = join8(targetPath, name);
682
777
  try {
683
778
  const stat = statSync2(full);
684
779
  return {
@@ -705,12 +800,16 @@ async function createApiServer(store, migrationRunner) {
705
800
  });
706
801
  app.get("/api/detect", async (req, res) => {
707
802
  try {
708
- const { detectTechnologies } = await import("./TechDetector-5YP44RVR.js");
803
+ const { detectTechnologies: detectTechnologies2, inspectProject: inspectProject2, detectMonorepo } = await import("./ProjectDetector-TPATF25O.js");
709
804
  const targetPath = req.query.path ? String(req.query.path) : ".";
710
- const detected = detectTechnologies(targetPath);
805
+ const monorepo = detectMonorepo(targetPath);
806
+ const profiles = inspectProject2(targetPath);
807
+ const detected = detectTechnologies2(targetPath);
711
808
  res.json({
712
809
  detected,
713
- primary: detected.length > 0 ? detected[0].framework : null
810
+ profiles,
811
+ monorepo: monorepo.isMonorepo ? monorepo : null,
812
+ primary: profiles.length > 0 ? profiles[0].framework : null
714
813
  });
715
814
  } catch (error) {
716
815
  if (error instanceof Error) {
@@ -825,16 +924,16 @@ async function createApiServer(store, migrationRunner) {
825
924
  }
826
925
  });
827
926
  }
828
- const path5 = await import("path");
829
- const fs4 = await import("fs");
830
- let publicDir = path5.join(import.meta.dirname, "public");
831
- if (!fs4.existsSync(publicDir)) {
832
- publicDir = path5.join(import.meta.dirname, "../public");
927
+ const path6 = await import("path");
928
+ const fs5 = await import("fs");
929
+ let publicDir = path6.join(import.meta.dirname, "public");
930
+ if (!fs5.existsSync(publicDir)) {
931
+ publicDir = path6.join(import.meta.dirname, "../public");
833
932
  }
834
- if (fs4.existsSync(publicDir)) {
933
+ if (fs5.existsSync(publicDir)) {
835
934
  app.use(express.static(publicDir));
836
935
  app.get(/^(.*)$/, (_req, res) => {
837
- res.sendFile(path5.join(publicDir, "index.html"));
936
+ res.sendFile(path6.join(publicDir, "index.html"));
838
937
  });
839
938
  }
840
939
  return app;
@@ -997,6 +1096,156 @@ ${result.output}`;
997
1096
  }
998
1097
  });
999
1098
 
1099
+ // ../infrastructure/src/config/ConfigStore.ts
1100
+ import * as fs4 from "fs";
1101
+ import * as path5 from "path";
1102
+ import * as os from "os";
1103
+ var CONFIG_FILE_NAME = ".metamorphrc.json";
1104
+ var ConfigStore = class {
1105
+ /**
1106
+ * Retrieves the absolute path to the local or global configuration file.
1107
+ */
1108
+ static getConfigPath(isGlobal = false, cwd = process.cwd()) {
1109
+ return isGlobal ? path5.join(os.homedir(), CONFIG_FILE_NAME) : path5.join(cwd, CONFIG_FILE_NAME);
1110
+ }
1111
+ /**
1112
+ * Reads a JSON configuration file safely.
1113
+ */
1114
+ static readConfigFile(filePath) {
1115
+ if (!fs4.existsSync(filePath)) {
1116
+ return null;
1117
+ }
1118
+ try {
1119
+ const raw = fs4.readFileSync(filePath, "utf-8");
1120
+ const parsed = JSON.parse(raw);
1121
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1122
+ return null;
1123
+ }
1124
+ if (Array.isArray(parsed.disabledAgents)) {
1125
+ const validation = validateDisabledAgents(parsed.disabledAgents);
1126
+ if (validation.valid) {
1127
+ parsed.disabledAgents = validation.disabled;
1128
+ } else {
1129
+ delete parsed.disabledAgents;
1130
+ }
1131
+ }
1132
+ return parsed;
1133
+ } catch {
1134
+ return null;
1135
+ }
1136
+ }
1137
+ /**
1138
+ * Writes a JSON configuration file safely, creating parent folders if necessary.
1139
+ */
1140
+ static writeConfigFile(filePath, config) {
1141
+ const dir = path5.dirname(filePath);
1142
+ if (!fs4.existsSync(dir)) {
1143
+ fs4.mkdirSync(dir, { recursive: true });
1144
+ }
1145
+ fs4.writeFileSync(filePath, JSON.stringify(config, null, 2), "utf-8");
1146
+ }
1147
+ /**
1148
+ * Reads the local configuration file (.metamorphrc.json) from the target directory.
1149
+ */
1150
+ static loadLocalConfig(cwd = process.cwd()) {
1151
+ return this.readConfigFile(this.getConfigPath(false, cwd));
1152
+ }
1153
+ /**
1154
+ * Reads the global configuration file (~/.metamorphrc.json) from the user's home directory.
1155
+ */
1156
+ static loadGlobalConfig() {
1157
+ return this.readConfigFile(this.getConfigPath(true));
1158
+ }
1159
+ /**
1160
+ * Persists a key-value update into the local configuration file.
1161
+ */
1162
+ static saveLocalConfig(config, cwd = process.cwd()) {
1163
+ const existing = this.loadLocalConfig(cwd) || {};
1164
+ const updated = { ...existing, ...config };
1165
+ this.writeConfigFile(this.getConfigPath(false, cwd), updated);
1166
+ }
1167
+ /**
1168
+ * Persists a key-value update into the global configuration file.
1169
+ */
1170
+ static saveGlobalConfig(config) {
1171
+ const existing = this.loadGlobalConfig() || {};
1172
+ const updated = { ...existing, ...config };
1173
+ this.writeConfigFile(this.getConfigPath(true), updated);
1174
+ }
1175
+ /**
1176
+ * Extracts environment variable configurations if present.
1177
+ */
1178
+ static loadEnvConfig(env = process.env) {
1179
+ const envConfig = {};
1180
+ if (env.METAMORPH_MODEL) {
1181
+ envConfig.model = env.METAMORPH_MODEL;
1182
+ }
1183
+ if (env.METAMORPH_REVIEWER_MODEL) {
1184
+ envConfig.reviewerModel = env.METAMORPH_REVIEWER_MODEL;
1185
+ }
1186
+ if (env.METAMORPH_CONCURRENCY) {
1187
+ const parsed = parseInt(env.METAMORPH_CONCURRENCY, 10);
1188
+ if (!Number.isNaN(parsed) && parsed > 0) {
1189
+ envConfig.concurrency = parsed;
1190
+ }
1191
+ }
1192
+ if (env.METAMORPH_INFERENCE_TIMEOUT_MS) {
1193
+ const parsed = parseInt(env.METAMORPH_INFERENCE_TIMEOUT_MS, 10);
1194
+ if (!Number.isNaN(parsed) && parsed > 0) {
1195
+ envConfig.inferenceTimeoutMs = parsed;
1196
+ }
1197
+ }
1198
+ if (env.METAMORPH_MAX_RETRIES) {
1199
+ const parsed = parseInt(env.METAMORPH_MAX_RETRIES, 10);
1200
+ if (!Number.isNaN(parsed) && parsed >= 0) {
1201
+ envConfig.maxRetries = parsed;
1202
+ }
1203
+ }
1204
+ if (env.METAMORPH_INTEGRATION_ROUNDS) {
1205
+ const parsed = parseInt(env.METAMORPH_INTEGRATION_ROUNDS, 10);
1206
+ if (!Number.isNaN(parsed) && parsed > 0) {
1207
+ envConfig.maxIntegrationRounds = parsed;
1208
+ }
1209
+ }
1210
+ if (env.METAMORPH_DISABLED_AGENTS) {
1211
+ const parts = env.METAMORPH_DISABLED_AGENTS.split(",").map((s) => s.trim());
1212
+ const validation = validateDisabledAgents(parts);
1213
+ if (validation.valid) {
1214
+ envConfig.disabledAgents = validation.disabled;
1215
+ }
1216
+ }
1217
+ return envConfig;
1218
+ }
1219
+ /**
1220
+ * Resolves the final effective MetamorphConfig applying the cascading precedence:
1221
+ * CLI Flags > Environment Variables > Local Config > Global Config > Defaults.
1222
+ */
1223
+ static resolveConfig(options = {}) {
1224
+ const cwd = options.cwd || process.cwd();
1225
+ const globalConfig = this.loadGlobalConfig() || {};
1226
+ const localConfig = this.loadLocalConfig(cwd) || {};
1227
+ const envConfig = this.loadEnvConfig(options.env || process.env);
1228
+ const cliFlags = options.cliFlags || {};
1229
+ const clean = (obj) => {
1230
+ const res = {};
1231
+ for (const [k, v] of Object.entries(obj)) {
1232
+ if (v !== void 0 && v !== null) {
1233
+ res[k] = v;
1234
+ }
1235
+ }
1236
+ return res;
1237
+ };
1238
+ const merged = {
1239
+ ...DEFAULT_METAMORPH_CONFIG,
1240
+ ...clean(globalConfig),
1241
+ ...clean(localConfig),
1242
+ ...clean(envConfig),
1243
+ ...clean(cliFlags)
1244
+ };
1245
+ return merged;
1246
+ }
1247
+ };
1248
+
1000
1249
  export {
1001
1250
  SQLiteStateStore,
1002
1251
  assertSandbox,
@@ -1014,5 +1263,7 @@ export {
1014
1263
  createCheckProjectDiagnosticsTool,
1015
1264
  runInShadowWorkspace,
1016
1265
  parseImplicatedFiles,
1017
- createRunBuildTool
1266
+ createRunBuildTool,
1267
+ CONFIG_FILE_NAME,
1268
+ ConfigStore
1018
1269
  };
@@ -1,3 +1,41 @@
1
+ // ../domain/src/entities/PackageManagerCommands.ts
2
+ function resolvePackageManagerCommands(pm) {
3
+ switch (pm) {
4
+ case "pnpm":
5
+ return {
6
+ name: "pnpm",
7
+ install: "pnpm install",
8
+ cleanInstall: "pnpm install",
9
+ runBuild: "pnpm run build",
10
+ runDev: "pnpm dev"
11
+ };
12
+ case "yarn":
13
+ return {
14
+ name: "yarn",
15
+ install: "yarn install",
16
+ cleanInstall: "yarn install",
17
+ runBuild: "yarn build",
18
+ runDev: "yarn dev"
19
+ };
20
+ case "bun":
21
+ return {
22
+ name: "bun",
23
+ install: "bun install",
24
+ cleanInstall: "bun install",
25
+ runBuild: "bun run build",
26
+ runDev: "bun dev"
27
+ };
28
+ default:
29
+ return {
30
+ name: "npm",
31
+ install: "npm install",
32
+ cleanInstall: "npm install --no-fund --no-audit",
33
+ runBuild: "npm run build",
34
+ runDev: "npm run dev"
35
+ };
36
+ }
37
+ }
38
+
1
39
  // ../domain/src/entities/catalogs/backendCatalog.ts
2
40
  var backendCatalog = [
3
41
  {
@@ -65,10 +103,14 @@ export function createAppRouter(appService: AppService): Router {
65
103
  "@nestjs/cli": "latest",
66
104
  "@nestjs/schematics": "latest",
67
105
  "@nestjs/testing": "latest",
106
+ "@types/node": "^20.0.0",
68
107
  "@types/supertest": "^2.0.12",
69
108
  "source-map-support": "^0.5.21",
70
109
  "supertest": "^6.3.3"
71
110
  },
111
+ scriptsToUpdate: {
112
+ "build": "tsc"
113
+ },
72
114
  architecturalRules: [
73
115
  "Express routing logic must be encapsulated in classes decorated with @Controller().",
74
116
  "Business logic must be encapsulated in classes decorated with @Injectable() (Providers/Services).",
@@ -263,10 +305,15 @@ app.listen(3000);`
263
305
  "@nestjs/cli": "latest",
264
306
  "@nestjs/schematics": "latest",
265
307
  "@nestjs/testing": "latest",
308
+ "@types/node": "^20.0.0",
266
309
  "@types/supertest": "^2.0.12",
267
310
  "source-map-support": "^0.5.21",
268
311
  "supertest": "^6.3.3"
269
312
  },
313
+ scriptsToUpdate: {
314
+ "build": "tsc"
315
+ },
316
+ scriptsToRemove: ["dev:start", "watch:ts"],
270
317
  architecturalRules: [
271
318
  "Fastify routing logic must be encapsulated in classes decorated with @Controller().",
272
319
  "Business logic must be encapsulated in classes decorated with @Injectable() (Providers/Services).",
@@ -1218,6 +1265,198 @@ function findMigrationCatalogEntry(source, target) {
1218
1265
  return resolveMigrationCatalog(source, target);
1219
1266
  }
1220
1267
 
1268
+ // ../domain/src/entities/MetamorphConfig.ts
1269
+ var DEFAULT_METAMORPH_CONFIG = {
1270
+ model: "gpt-5.4",
1271
+ concurrency: 3,
1272
+ inferenceTimeoutMs: 12e4,
1273
+ maxRetries: 2,
1274
+ maxIntegrationRounds: 4,
1275
+ disabledAgents: []
1276
+ };
1277
+ var MOZAIK_BUNDLED_MODELS = [
1278
+ // OpenAI
1279
+ "gpt-5.4",
1280
+ "gpt-5.4-mini",
1281
+ "gpt-5.4-nano",
1282
+ "gpt-5.5",
1283
+ // Anthropic
1284
+ "claude-sonnet-4-6",
1285
+ "claude-haiku-4-5",
1286
+ "claude-opus-4-7",
1287
+ "claude-opus-4-8"
1288
+ ];
1289
+ function isMozaikBundledModel(model) {
1290
+ return MOZAIK_BUNDLED_MODELS.includes(model);
1291
+ }
1292
+ function isMetamorphConfigKey(key) {
1293
+ return [
1294
+ "model",
1295
+ "reviewerModel",
1296
+ "concurrency",
1297
+ "inferenceTimeoutMs",
1298
+ "maxRetries",
1299
+ "maxIntegrationRounds",
1300
+ "disabledAgents"
1301
+ ].includes(key);
1302
+ }
1303
+
1304
+ // ../domain/src/entities/AgentTaxonomy.ts
1305
+ var CORE_AGENTS = [
1306
+ "coordinator",
1307
+ "mapper",
1308
+ "worker",
1309
+ "packagemanager",
1310
+ "integration"
1311
+ ];
1312
+ var AUXILIARY_AGENTS = [
1313
+ "reporter",
1314
+ "reviewer",
1315
+ "accounting"
1316
+ ];
1317
+ var ALL_SWARM_AGENTS = [
1318
+ ...CORE_AGENTS,
1319
+ ...AUXILIARY_AGENTS
1320
+ ];
1321
+ function isCoreAgentId(id) {
1322
+ return CORE_AGENTS.includes(id.toLowerCase());
1323
+ }
1324
+ function isAuxiliaryAgentId(id) {
1325
+ return AUXILIARY_AGENTS.includes(id.toLowerCase());
1326
+ }
1327
+ function validateDisabledAgents(agentNames) {
1328
+ const normalized = [];
1329
+ for (const rawName of agentNames) {
1330
+ const clean = rawName.trim().toLowerCase();
1331
+ if (!clean) continue;
1332
+ if (isCoreAgentId(clean)) {
1333
+ return {
1334
+ valid: false,
1335
+ disabled: [],
1336
+ error: `Cannot disable core agent '${clean}'. Only auxiliary agents [${AUXILIARY_AGENTS.join(", ")}] can be disabled.`
1337
+ };
1338
+ }
1339
+ if (!isAuxiliaryAgentId(clean)) {
1340
+ return {
1341
+ valid: false,
1342
+ disabled: [],
1343
+ error: `Unknown agent '${clean}'. Available auxiliary agents to disable: [${AUXILIARY_AGENTS.join(", ")}].`
1344
+ };
1345
+ }
1346
+ if (!normalized.includes(clean)) {
1347
+ normalized.push(clean);
1348
+ }
1349
+ }
1350
+ return {
1351
+ valid: true,
1352
+ disabled: normalized
1353
+ };
1354
+ }
1355
+
1356
+ // ../domain/src/entities/TokenUsage.ts
1357
+ function createEmptyAgentRoleCost() {
1358
+ return {
1359
+ tokens: 0,
1360
+ promptTokens: 0,
1361
+ completionTokens: 0,
1362
+ costUsd: 0,
1363
+ executions: 0
1364
+ };
1365
+ }
1366
+ function createEmptyMigrationCostSummary(runId = "", planId = "") {
1367
+ return {
1368
+ runId,
1369
+ planId,
1370
+ totalTokens: 0,
1371
+ promptTokens: 0,
1372
+ completionTokens: 0,
1373
+ totalCostUsd: 0,
1374
+ byAgentRole: {
1375
+ worker: createEmptyAgentRoleCost(),
1376
+ reviewer: createEmptyAgentRoleCost(),
1377
+ integration: createEmptyAgentRoleCost(),
1378
+ coordinator: createEmptyAgentRoleCost(),
1379
+ mapper: createEmptyAgentRoleCost()
1380
+ },
1381
+ byModel: {}
1382
+ };
1383
+ }
1384
+
1385
+ // ../domain/src/entities/PricingCatalog.ts
1386
+ var BUNDLED_MODEL_PRICING = {
1387
+ // OpenAI
1388
+ "gpt-5.4": {
1389
+ modelId: "gpt-5.4",
1390
+ inputPricePerMillion: 2.5,
1391
+ outputPricePerMillion: 10,
1392
+ currency: "USD"
1393
+ },
1394
+ "gpt-5.4-mini": {
1395
+ modelId: "gpt-5.4-mini",
1396
+ inputPricePerMillion: 0.15,
1397
+ outputPricePerMillion: 0.6,
1398
+ currency: "USD"
1399
+ },
1400
+ "gpt-5.4-nano": {
1401
+ modelId: "gpt-5.4-nano",
1402
+ inputPricePerMillion: 0.05,
1403
+ outputPricePerMillion: 0.2,
1404
+ currency: "USD"
1405
+ },
1406
+ "gpt-5.5": {
1407
+ modelId: "gpt-5.5",
1408
+ inputPricePerMillion: 3,
1409
+ outputPricePerMillion: 12,
1410
+ currency: "USD"
1411
+ },
1412
+ // Anthropic
1413
+ "claude-sonnet-4-6": {
1414
+ modelId: "claude-sonnet-4-6",
1415
+ inputPricePerMillion: 3,
1416
+ outputPricePerMillion: 15,
1417
+ currency: "USD"
1418
+ },
1419
+ "claude-haiku-4-5": {
1420
+ modelId: "claude-haiku-4-5",
1421
+ inputPricePerMillion: 0.8,
1422
+ outputPricePerMillion: 4,
1423
+ currency: "USD"
1424
+ },
1425
+ "claude-opus-4-7": {
1426
+ modelId: "claude-opus-4-7",
1427
+ inputPricePerMillion: 15,
1428
+ outputPricePerMillion: 75,
1429
+ currency: "USD"
1430
+ },
1431
+ "claude-opus-4-8": {
1432
+ modelId: "claude-opus-4-8",
1433
+ inputPricePerMillion: 15,
1434
+ outputPricePerMillion: 75,
1435
+ currency: "USD"
1436
+ }
1437
+ };
1438
+ function resolvePricingTier(modelId, customPricing) {
1439
+ if (customPricing && customPricing[modelId]) {
1440
+ return customPricing[modelId];
1441
+ }
1442
+ if (modelId in BUNDLED_MODEL_PRICING) {
1443
+ return BUNDLED_MODEL_PRICING[modelId];
1444
+ }
1445
+ return {
1446
+ modelId,
1447
+ inputPricePerMillion: 0,
1448
+ outputPricePerMillion: 0,
1449
+ currency: "USD"
1450
+ };
1451
+ }
1452
+ function calculateTokenCost(modelId, promptTokens, completionTokens, customPricing) {
1453
+ const tier = resolvePricingTier(modelId, customPricing);
1454
+ const inputCost = promptTokens / 1e6 * tier.inputPricePerMillion;
1455
+ const outputCost = completionTokens / 1e6 * tier.outputPricePerMillion;
1456
+ const totalCost = inputCost + outputCost;
1457
+ return Math.round(totalCost * 1e6) / 1e6;
1458
+ }
1459
+
1221
1460
  // ../domain/src/events/SemanticEvents.ts
1222
1461
  var SemanticEventName = /* @__PURE__ */ ((SemanticEventName2) => {
1223
1462
  SemanticEventName2["MIGRATION_STARTED"] = "migration.started";
@@ -1230,11 +1469,14 @@ var SemanticEventName = /* @__PURE__ */ ((SemanticEventName2) => {
1230
1469
  SemanticEventName2["PHASE_PACKAGES_READY"] = "phase.packages_ready";
1231
1470
  SemanticEventName2["PHASE_INTEGRATION_STARTED"] = "phase.integration_started";
1232
1471
  SemanticEventName2["MIGRATION_COMPLETED"] = "migration.completed";
1472
+ SemanticEventName2["TOKENS_REPORTED"] = "tokens.reported";
1473
+ SemanticEventName2["TOKENS_CONSUMED"] = "tokens.consumed";
1233
1474
  SemanticEventName2["SYSTEM_LOG"] = "system.log";
1234
1475
  return SemanticEventName2;
1235
1476
  })(SemanticEventName || {});
1236
1477
 
1237
1478
  export {
1479
+ resolvePackageManagerCommands,
1238
1480
  FRAMEWORK_PACKS,
1239
1481
  rulesForFramework,
1240
1482
  packForFramework,
@@ -1252,5 +1494,20 @@ export {
1252
1494
  resolveMigrationCatalog,
1253
1495
  MigrationCatalog,
1254
1496
  findMigrationCatalogEntry,
1497
+ DEFAULT_METAMORPH_CONFIG,
1498
+ MOZAIK_BUNDLED_MODELS,
1499
+ isMozaikBundledModel,
1500
+ isMetamorphConfigKey,
1501
+ CORE_AGENTS,
1502
+ AUXILIARY_AGENTS,
1503
+ ALL_SWARM_AGENTS,
1504
+ isCoreAgentId,
1505
+ isAuxiliaryAgentId,
1506
+ validateDisabledAgents,
1507
+ createEmptyAgentRoleCost,
1508
+ createEmptyMigrationCostSummary,
1509
+ BUNDLED_MODEL_PRICING,
1510
+ resolvePricingTier,
1511
+ calculateTokenCost,
1255
1512
  SemanticEventName
1256
1513
  };