@automigrate/cli 0.1.2

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 ADDED
@@ -0,0 +1,3231 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ Anthropic,
4
+ __commonJS,
5
+ __toESM
6
+ } from "./chunk-ISRV2HNG.js";
7
+
8
+ // ../../node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js
9
+ var require_ignore = __commonJS({
10
+ "../../node_modules/.pnpm/ignore@7.0.5/node_modules/ignore/index.js"(exports, module) {
11
+ "use strict";
12
+ function makeArray(subject) {
13
+ return Array.isArray(subject) ? subject : [subject];
14
+ }
15
+ var UNDEFINED = void 0;
16
+ var EMPTY = "";
17
+ var SPACE = " ";
18
+ var ESCAPE = "\\";
19
+ var REGEX_TEST_BLANK_LINE = /^\s+$/;
20
+ var REGEX_INVALID_TRAILING_BACKSLASH = /(?:[^\\]|^)\\$/;
21
+ var REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/;
22
+ var REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/;
23
+ var REGEX_SPLITALL_CRLF = /\r?\n/g;
24
+ var REGEX_TEST_INVALID_PATH = /^\.{0,2}\/|^\.{1,2}$/;
25
+ var REGEX_TEST_TRAILING_SLASH = /\/$/;
26
+ var SLASH = "/";
27
+ var TMP_KEY_IGNORE = "node-ignore";
28
+ if (typeof Symbol !== "undefined") {
29
+ TMP_KEY_IGNORE = /* @__PURE__ */ Symbol.for("node-ignore");
30
+ }
31
+ var KEY_IGNORE = TMP_KEY_IGNORE;
32
+ var define = (object, key, value) => {
33
+ Object.defineProperty(object, key, { value });
34
+ return value;
35
+ };
36
+ var REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g;
37
+ var RETURN_FALSE = () => false;
38
+ var sanitizeRange = (range) => range.replace(
39
+ REGEX_REGEXP_RANGE,
40
+ (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) ? match : EMPTY
41
+ );
42
+ var cleanRangeBackSlash = (slashes) => {
43
+ const { length } = slashes;
44
+ return slashes.slice(0, length - length % 2);
45
+ };
46
+ var REPLACERS = [
47
+ [
48
+ // Remove BOM
49
+ // TODO:
50
+ // Other similar zero-width characters?
51
+ /^\uFEFF/,
52
+ () => EMPTY
53
+ ],
54
+ // > Trailing spaces are ignored unless they are quoted with backslash ("\")
55
+ [
56
+ // (a\ ) -> (a )
57
+ // (a ) -> (a)
58
+ // (a ) -> (a)
59
+ // (a \ ) -> (a )
60
+ /((?:\\\\)*?)(\\?\s+)$/,
61
+ (_, m1, m2) => m1 + (m2.indexOf("\\") === 0 ? SPACE : EMPTY)
62
+ ],
63
+ // Replace (\ ) with ' '
64
+ // (\ ) -> ' '
65
+ // (\\ ) -> '\\ '
66
+ // (\\\ ) -> '\\ '
67
+ [
68
+ /(\\+?)\s/g,
69
+ (_, m1) => {
70
+ const { length } = m1;
71
+ return m1.slice(0, length - length % 2) + SPACE;
72
+ }
73
+ ],
74
+ // Escape metacharacters
75
+ // which is written down by users but means special for regular expressions.
76
+ // > There are 12 characters with special meanings:
77
+ // > - the backslash \,
78
+ // > - the caret ^,
79
+ // > - the dollar sign $,
80
+ // > - the period or dot .,
81
+ // > - the vertical bar or pipe symbol |,
82
+ // > - the question mark ?,
83
+ // > - the asterisk or star *,
84
+ // > - the plus sign +,
85
+ // > - the opening parenthesis (,
86
+ // > - the closing parenthesis ),
87
+ // > - and the opening square bracket [,
88
+ // > - the opening curly brace {,
89
+ // > These special characters are often called "metacharacters".
90
+ [
91
+ /[\\$.|*+(){^]/g,
92
+ (match) => `\\${match}`
93
+ ],
94
+ [
95
+ // > a question mark (?) matches a single character
96
+ /(?!\\)\?/g,
97
+ () => "[^/]"
98
+ ],
99
+ // leading slash
100
+ [
101
+ // > A leading slash matches the beginning of the pathname.
102
+ // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
103
+ // A leading slash matches the beginning of the pathname
104
+ /^\//,
105
+ () => "^"
106
+ ],
107
+ // replace special metacharacter slash after the leading slash
108
+ [
109
+ /\//g,
110
+ () => "\\/"
111
+ ],
112
+ [
113
+ // > A leading "**" followed by a slash means match in all directories.
114
+ // > For example, "**/foo" matches file or directory "foo" anywhere,
115
+ // > the same as pattern "foo".
116
+ // > "**/foo/bar" matches file or directory "bar" anywhere that is directly
117
+ // > under directory "foo".
118
+ // Notice that the '*'s have been replaced as '\\*'
119
+ /^\^*\\\*\\\*\\\//,
120
+ // '**/foo' <-> 'foo'
121
+ () => "^(?:.*\\/)?"
122
+ ],
123
+ // starting
124
+ [
125
+ // there will be no leading '/'
126
+ // (which has been replaced by section "leading slash")
127
+ // If starts with '**', adding a '^' to the regular expression also works
128
+ /^(?=[^^])/,
129
+ function startingReplacer() {
130
+ return !/\/(?!$)/.test(this) ? "(?:^|\\/)" : "^";
131
+ }
132
+ ],
133
+ // two globstars
134
+ [
135
+ // Use lookahead assertions so that we could match more than one `'/**'`
136
+ /\\\/\\\*\\\*(?=\\\/|$)/g,
137
+ // Zero, one or several directories
138
+ // should not use '*', or it will be replaced by the next replacer
139
+ // Check if it is not the last `'/**'`
140
+ (_, index, str) => index + 6 < str.length ? "(?:\\/[^\\/]+)*" : "\\/.+"
141
+ ],
142
+ // normal intermediate wildcards
143
+ [
144
+ // Never replace escaped '*'
145
+ // ignore rule '\*' will match the path '*'
146
+ // 'abc.*/' -> go
147
+ // 'abc.*' -> skip this rule,
148
+ // coz trailing single wildcard will be handed by [trailing wildcard]
149
+ /(^|[^\\]+)(\\\*)+(?=.+)/g,
150
+ // '*.js' matches '.js'
151
+ // '*.js' doesn't match 'abc'
152
+ (_, p1, p2) => {
153
+ const unescaped = p2.replace(/\\\*/g, "[^\\/]*");
154
+ return p1 + unescaped;
155
+ }
156
+ ],
157
+ [
158
+ // unescape, revert step 3 except for back slash
159
+ // For example, if a user escape a '\\*',
160
+ // after step 3, the result will be '\\\\\\*'
161
+ /\\\\\\(?=[$.|*+(){^])/g,
162
+ () => ESCAPE
163
+ ],
164
+ [
165
+ // '\\\\' -> '\\'
166
+ /\\\\/g,
167
+ () => ESCAPE
168
+ ],
169
+ [
170
+ // > The range notation, e.g. [a-zA-Z],
171
+ // > can be used to match one of the characters in a range.
172
+ // `\` is escaped by step 3
173
+ /(\\)?\[([^\]/]*?)(\\*)($|\])/g,
174
+ (match, leadEscape, range, endEscape, close) => leadEscape === ESCAPE ? `\\[${range}${cleanRangeBackSlash(endEscape)}${close}` : close === "]" ? endEscape.length % 2 === 0 ? `[${sanitizeRange(range)}${endEscape}]` : "[]" : "[]"
175
+ ],
176
+ // ending
177
+ [
178
+ // 'js' will not match 'js.'
179
+ // 'ab' will not match 'abc'
180
+ /(?:[^*])$/,
181
+ // WTF!
182
+ // https://git-scm.com/docs/gitignore
183
+ // changes in [2.22.1](https://git-scm.com/docs/gitignore/2.22.1)
184
+ // which re-fixes #24, #38
185
+ // > If there is a separator at the end of the pattern then the pattern
186
+ // > will only match directories, otherwise the pattern can match both
187
+ // > files and directories.
188
+ // 'js*' will not match 'a.js'
189
+ // 'js/' will not match 'a.js'
190
+ // 'js' will match 'a.js' and 'a.js/'
191
+ (match) => /\/$/.test(match) ? `${match}$` : `${match}(?=$|\\/$)`
192
+ ]
193
+ ];
194
+ var REGEX_REPLACE_TRAILING_WILDCARD = /(^|\\\/)?\\\*$/;
195
+ var MODE_IGNORE = "regex";
196
+ var MODE_CHECK_IGNORE = "checkRegex";
197
+ var UNDERSCORE = "_";
198
+ var TRAILING_WILD_CARD_REPLACERS = {
199
+ [MODE_IGNORE](_, p1) {
200
+ const prefix = p1 ? `${p1}[^/]+` : "[^/]*";
201
+ return `${prefix}(?=$|\\/$)`;
202
+ },
203
+ [MODE_CHECK_IGNORE](_, p1) {
204
+ const prefix = p1 ? `${p1}[^/]*` : "[^/]*";
205
+ return `${prefix}(?=$|\\/$)`;
206
+ }
207
+ };
208
+ var makeRegexPrefix = (pattern) => REPLACERS.reduce(
209
+ (prev, [matcher, replacer]) => prev.replace(matcher, replacer.bind(pattern)),
210
+ pattern
211
+ );
212
+ var isString = (subject) => typeof subject === "string";
213
+ var checkPattern = (pattern) => pattern && isString(pattern) && !REGEX_TEST_BLANK_LINE.test(pattern) && !REGEX_INVALID_TRAILING_BACKSLASH.test(pattern) && pattern.indexOf("#") !== 0;
214
+ var splitPattern = (pattern) => pattern.split(REGEX_SPLITALL_CRLF).filter(Boolean);
215
+ var IgnoreRule = class {
216
+ constructor(pattern, mark, body, ignoreCase, negative, prefix) {
217
+ this.pattern = pattern;
218
+ this.mark = mark;
219
+ this.negative = negative;
220
+ define(this, "body", body);
221
+ define(this, "ignoreCase", ignoreCase);
222
+ define(this, "regexPrefix", prefix);
223
+ }
224
+ get regex() {
225
+ const key = UNDERSCORE + MODE_IGNORE;
226
+ if (this[key]) {
227
+ return this[key];
228
+ }
229
+ return this._make(MODE_IGNORE, key);
230
+ }
231
+ get checkRegex() {
232
+ const key = UNDERSCORE + MODE_CHECK_IGNORE;
233
+ if (this[key]) {
234
+ return this[key];
235
+ }
236
+ return this._make(MODE_CHECK_IGNORE, key);
237
+ }
238
+ _make(mode, key) {
239
+ const str = this.regexPrefix.replace(
240
+ REGEX_REPLACE_TRAILING_WILDCARD,
241
+ // It does not need to bind pattern
242
+ TRAILING_WILD_CARD_REPLACERS[mode]
243
+ );
244
+ const regex = this.ignoreCase ? new RegExp(str, "i") : new RegExp(str);
245
+ return define(this, key, regex);
246
+ }
247
+ };
248
+ var createRule = ({
249
+ pattern,
250
+ mark
251
+ }, ignoreCase) => {
252
+ let negative = false;
253
+ let body = pattern;
254
+ if (body.indexOf("!") === 0) {
255
+ negative = true;
256
+ body = body.substr(1);
257
+ }
258
+ body = body.replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, "!").replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, "#");
259
+ const regexPrefix = makeRegexPrefix(body);
260
+ return new IgnoreRule(
261
+ pattern,
262
+ mark,
263
+ body,
264
+ ignoreCase,
265
+ negative,
266
+ regexPrefix
267
+ );
268
+ };
269
+ var RuleManager = class {
270
+ constructor(ignoreCase) {
271
+ this._ignoreCase = ignoreCase;
272
+ this._rules = [];
273
+ }
274
+ _add(pattern) {
275
+ if (pattern && pattern[KEY_IGNORE]) {
276
+ this._rules = this._rules.concat(pattern._rules._rules);
277
+ this._added = true;
278
+ return;
279
+ }
280
+ if (isString(pattern)) {
281
+ pattern = {
282
+ pattern
283
+ };
284
+ }
285
+ if (checkPattern(pattern.pattern)) {
286
+ const rule = createRule(pattern, this._ignoreCase);
287
+ this._added = true;
288
+ this._rules.push(rule);
289
+ }
290
+ }
291
+ // @param {Array<string> | string | Ignore} pattern
292
+ add(pattern) {
293
+ this._added = false;
294
+ makeArray(
295
+ isString(pattern) ? splitPattern(pattern) : pattern
296
+ ).forEach(this._add, this);
297
+ return this._added;
298
+ }
299
+ // Test one single path without recursively checking parent directories
300
+ //
301
+ // - checkUnignored `boolean` whether should check if the path is unignored,
302
+ // setting `checkUnignored` to `false` could reduce additional
303
+ // path matching.
304
+ // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
305
+ // @returns {TestResult} true if a file is ignored
306
+ test(path10, checkUnignored, mode) {
307
+ let ignored = false;
308
+ let unignored = false;
309
+ let matchedRule;
310
+ this._rules.forEach((rule) => {
311
+ const { negative } = rule;
312
+ if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
313
+ return;
314
+ }
315
+ const matched = rule[mode].test(path10);
316
+ if (!matched) {
317
+ return;
318
+ }
319
+ ignored = !negative;
320
+ unignored = negative;
321
+ matchedRule = negative ? UNDEFINED : rule;
322
+ });
323
+ const ret = {
324
+ ignored,
325
+ unignored
326
+ };
327
+ if (matchedRule) {
328
+ ret.rule = matchedRule;
329
+ }
330
+ return ret;
331
+ }
332
+ };
333
+ var throwError = (message, Ctor) => {
334
+ throw new Ctor(message);
335
+ };
336
+ var checkPath = (path10, originalPath, doThrow) => {
337
+ if (!isString(path10)) {
338
+ return doThrow(
339
+ `path must be a string, but got \`${originalPath}\``,
340
+ TypeError
341
+ );
342
+ }
343
+ if (!path10) {
344
+ return doThrow(`path must not be empty`, TypeError);
345
+ }
346
+ if (checkPath.isNotRelative(path10)) {
347
+ const r = "`path.relative()`d";
348
+ return doThrow(
349
+ `path should be a ${r} string, but got "${originalPath}"`,
350
+ RangeError
351
+ );
352
+ }
353
+ return true;
354
+ };
355
+ var isNotRelative = (path10) => REGEX_TEST_INVALID_PATH.test(path10);
356
+ checkPath.isNotRelative = isNotRelative;
357
+ checkPath.convert = (p) => p;
358
+ var Ignore = class {
359
+ constructor({
360
+ ignorecase = true,
361
+ ignoreCase = ignorecase,
362
+ allowRelativePaths = false
363
+ } = {}) {
364
+ define(this, KEY_IGNORE, true);
365
+ this._rules = new RuleManager(ignoreCase);
366
+ this._strictPathCheck = !allowRelativePaths;
367
+ this._initCache();
368
+ }
369
+ _initCache() {
370
+ this._ignoreCache = /* @__PURE__ */ Object.create(null);
371
+ this._testCache = /* @__PURE__ */ Object.create(null);
372
+ }
373
+ add(pattern) {
374
+ if (this._rules.add(pattern)) {
375
+ this._initCache();
376
+ }
377
+ return this;
378
+ }
379
+ // legacy
380
+ addPattern(pattern) {
381
+ return this.add(pattern);
382
+ }
383
+ // @returns {TestResult}
384
+ _test(originalPath, cache, checkUnignored, slices) {
385
+ const path10 = originalPath && checkPath.convert(originalPath);
386
+ checkPath(
387
+ path10,
388
+ originalPath,
389
+ this._strictPathCheck ? throwError : RETURN_FALSE
390
+ );
391
+ return this._t(path10, cache, checkUnignored, slices);
392
+ }
393
+ checkIgnore(path10) {
394
+ if (!REGEX_TEST_TRAILING_SLASH.test(path10)) {
395
+ return this.test(path10);
396
+ }
397
+ const slices = path10.split(SLASH).filter(Boolean);
398
+ slices.pop();
399
+ if (slices.length) {
400
+ const parent = this._t(
401
+ slices.join(SLASH) + SLASH,
402
+ this._testCache,
403
+ true,
404
+ slices
405
+ );
406
+ if (parent.ignored) {
407
+ return parent;
408
+ }
409
+ }
410
+ return this._rules.test(path10, false, MODE_CHECK_IGNORE);
411
+ }
412
+ _t(path10, cache, checkUnignored, slices) {
413
+ if (path10 in cache) {
414
+ return cache[path10];
415
+ }
416
+ if (!slices) {
417
+ slices = path10.split(SLASH).filter(Boolean);
418
+ }
419
+ slices.pop();
420
+ if (!slices.length) {
421
+ return cache[path10] = this._rules.test(path10, checkUnignored, MODE_IGNORE);
422
+ }
423
+ const parent = this._t(
424
+ slices.join(SLASH) + SLASH,
425
+ cache,
426
+ checkUnignored,
427
+ slices
428
+ );
429
+ return cache[path10] = parent.ignored ? parent : this._rules.test(path10, checkUnignored, MODE_IGNORE);
430
+ }
431
+ ignores(path10) {
432
+ return this._test(path10, this._ignoreCache, false).ignored;
433
+ }
434
+ createFilter() {
435
+ return (path10) => !this.ignores(path10);
436
+ }
437
+ filter(paths) {
438
+ return makeArray(paths).filter(this.createFilter());
439
+ }
440
+ // @returns {TestResult}
441
+ test(path10) {
442
+ return this._test(path10, this._testCache, true);
443
+ }
444
+ };
445
+ var factory = (options) => new Ignore(options);
446
+ var isPathValid = (path10) => checkPath(path10 && checkPath.convert(path10), path10, RETURN_FALSE);
447
+ var setupWindows = () => {
448
+ const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
449
+ checkPath.convert = makePosix;
450
+ const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
451
+ checkPath.isNotRelative = (path10) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path10) || isNotRelative(path10);
452
+ };
453
+ if (
454
+ // Detect `process` so that it can run in browsers.
455
+ typeof process !== "undefined" && process.platform === "win32"
456
+ ) {
457
+ setupWindows();
458
+ }
459
+ module.exports = factory;
460
+ factory.default = factory;
461
+ module.exports.isPathValid = isPathValid;
462
+ define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
463
+ }
464
+ });
465
+
466
+ // src/index.ts
467
+ import { readFile } from "fs/promises";
468
+ import { Command } from "commander";
469
+
470
+ // src/commands/config.ts
471
+ import chalk from "chalk";
472
+
473
+ // src/state.ts
474
+ import fs from "fs/promises";
475
+ import os from "os";
476
+ import path from "path";
477
+ function stateDir() {
478
+ return process.env.AUTOMIGRATE_HOME ?? path.join(os.homedir(), ".automigrate");
479
+ }
480
+ function backupDir(jobId) {
481
+ return path.join(stateDir(), "backup", jobId);
482
+ }
483
+ function checkpointPath(jobId) {
484
+ return path.join(stateDir(), "checkpoints", `${jobId}.json`);
485
+ }
486
+ function newJobId() {
487
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
488
+ }
489
+ async function readConfig() {
490
+ const parsed = await readJson(path.join(stateDir(), "config.json"));
491
+ return parsed ?? {};
492
+ }
493
+ async function writeConfig(config2) {
494
+ const filePath = path.join(stateDir(), "config.json");
495
+ await fs.mkdir(stateDir(), { recursive: true });
496
+ await fs.writeFile(filePath, JSON.stringify(config2, null, 2), "utf8");
497
+ await fs.chmod(filePath, 384);
498
+ }
499
+ async function readJobs() {
500
+ const parsed = await readJson(path.join(stateDir(), "jobs.json"));
501
+ return Array.isArray(parsed) ? parsed : [];
502
+ }
503
+ async function saveJob(job) {
504
+ const jobs = await readJobs();
505
+ const index = jobs.findIndex((j) => j.id === job.id);
506
+ if (index === -1) jobs.push(job);
507
+ else jobs[index] = job;
508
+ await fs.mkdir(stateDir(), { recursive: true });
509
+ await fs.writeFile(path.join(stateDir(), "jobs.json"), JSON.stringify(jobs, null, 2), "utf8");
510
+ }
511
+ async function acquireLock(rootPath) {
512
+ const lockPath = path.join(rootPath, ".automigrate-lock");
513
+ try {
514
+ await fs.writeFile(lockPath, String(process.pid), { flag: "wx" });
515
+ } catch (error) {
516
+ if (error.code === "EEXIST") {
517
+ throw new Error(
518
+ `Another migration appears to be running in this directory (${lockPath} exists). Remove the lock file if that migration is no longer active.`
519
+ );
520
+ }
521
+ throw error;
522
+ }
523
+ }
524
+ async function releaseLock(rootPath) {
525
+ await fs.rm(path.join(rootPath, ".automigrate-lock"), { force: true });
526
+ }
527
+ async function readJson(filePath) {
528
+ try {
529
+ return JSON.parse(await fs.readFile(filePath, "utf8"));
530
+ } catch {
531
+ return null;
532
+ }
533
+ }
534
+
535
+ // src/commands/config.ts
536
+ async function setKeyCommand(apiKey) {
537
+ const config2 = await readConfig();
538
+ await writeConfig({ ...config2, apiKey });
539
+ const masked = apiKey.length > 8 ? `${apiKey.slice(0, 4)}\u2026${apiKey.slice(-4)}` : "****";
540
+ console.log(`${chalk.green("Saved")} API key ${masked} to ${stateDir()}/config.json`);
541
+ }
542
+
543
+ // src/commands/init.ts
544
+ import fs5 from "fs/promises";
545
+ import path5 from "path";
546
+ import chalk2 from "chalk";
547
+ import ora from "ora";
548
+
549
+ // ../engine/dist/ingestion/index.js
550
+ var import_ignore = __toESM(require_ignore(), 1);
551
+ import fs2 from "fs/promises";
552
+ import path2 from "path";
553
+ var DEFAULT_IGNORE_PATTERNS = [
554
+ "node_modules",
555
+ ".git",
556
+ "dist",
557
+ "build",
558
+ "vendor",
559
+ "coverage",
560
+ // AutoMigrate's own artifacts — a re-run must never re-ingest prior output.
561
+ "automigrate.config.json",
562
+ ".automigrate-checkpoint.json",
563
+ ".automigrate-checkpoint.json.repair-*",
564
+ ".automigrate-lock",
565
+ ".automigrateignore",
566
+ "migrated",
567
+ "output.patch"
568
+ ];
569
+ var IGNORE_FILES = [".gitignore", ".automigrateignore"];
570
+ var DEFAULT_MAX_FILE_SIZE_MB = 5;
571
+ var BINARY_CHECK_BYTES = 8e3;
572
+ function estimateTokens(content) {
573
+ return Math.ceil(content.length / 4);
574
+ }
575
+ async function readCodebase(dirPath, options = {}) {
576
+ const rootPath = path2.resolve(dirPath);
577
+ const ig = (0, import_ignore.default)();
578
+ ig.add(DEFAULT_IGNORE_PATTERNS);
579
+ for (const name of IGNORE_FILES) {
580
+ const content = await readOptionalFile(path2.join(rootPath, name));
581
+ if (content !== null)
582
+ ig.add(content);
583
+ }
584
+ if (options.ignorePaths !== void 0)
585
+ ig.add(options.ignorePaths);
586
+ const files = await collectFiles(rootPath, ig, options);
587
+ return {
588
+ rootPath,
589
+ files,
590
+ totalLines: files.reduce((sum, f) => sum + f.lines, 0),
591
+ totalTokens: files.reduce((sum, f) => sum + f.tokens, 0),
592
+ detectedStack: detectStack(files),
593
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
594
+ };
595
+ }
596
+ function detectStack(manifest) {
597
+ const byPath = new Map(manifest.map((f) => [f.relativePath, f]));
598
+ const packageJson = byPath.get("package.json");
599
+ if (packageJson !== void 0)
600
+ return detectNodeStack(packageJson, byPath);
601
+ const pyproject = byPath.get("pyproject.toml");
602
+ if (pyproject !== void 0)
603
+ return detectPythonStack(pyproject, byPath);
604
+ const goMod = byPath.get("go.mod");
605
+ if (goMod !== void 0)
606
+ return detectGoStack(goMod);
607
+ const pubspec = byPath.get("pubspec.yaml");
608
+ if (pubspec !== void 0)
609
+ return detectDartStack(pubspec);
610
+ const cargo = byPath.get("Cargo.toml");
611
+ if (cargo !== void 0)
612
+ return detectRustStack(cargo);
613
+ const composer = byPath.get("composer.json");
614
+ if (composer !== void 0)
615
+ return detectPhpStack(composer);
616
+ return detectFromExtensions(manifest);
617
+ }
618
+ async function readOptionalFile(filePath) {
619
+ try {
620
+ return await fs2.readFile(filePath, "utf8");
621
+ } catch {
622
+ return null;
623
+ }
624
+ }
625
+ async function collectFiles(rootPath, ig, options) {
626
+ const maxBytes = (options.maxFileSizeMB ?? DEFAULT_MAX_FILE_SIZE_MB) * 1024 * 1024;
627
+ const includeExtensions = options.includeExtensions?.map(normalizeExtension);
628
+ const files = [];
629
+ async function walk(dir) {
630
+ const entries = await fs2.readdir(dir, { withFileTypes: true });
631
+ for (const entry of entries) {
632
+ const absPath = path2.join(dir, entry.name);
633
+ const relPath = path2.relative(rootPath, absPath).split(path2.sep).join("/");
634
+ if (entry.isSymbolicLink())
635
+ continue;
636
+ if (entry.isDirectory()) {
637
+ if (ig.ignores(relPath) || ig.ignores(`${relPath}/`))
638
+ continue;
639
+ await walk(absPath);
640
+ continue;
641
+ }
642
+ if (!entry.isFile())
643
+ continue;
644
+ if (ig.ignores(relPath))
645
+ continue;
646
+ const extension = path2.extname(entry.name).toLowerCase();
647
+ if (includeExtensions !== void 0 && !includeExtensions.includes(extension))
648
+ continue;
649
+ const stat = await fs2.stat(absPath);
650
+ if (stat.size > maxBytes)
651
+ continue;
652
+ const buffer = Buffer.from(await fs2.readFile(absPath));
653
+ if (isBinary(buffer))
654
+ continue;
655
+ const content = buffer.toString("utf8");
656
+ files.push({
657
+ path: absPath,
658
+ relativePath: relPath,
659
+ extension,
660
+ content,
661
+ lines: countLines(content),
662
+ tokens: estimateTokens(content),
663
+ size: stat.size
664
+ });
665
+ }
666
+ }
667
+ await walk(rootPath);
668
+ files.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
669
+ return files;
670
+ }
671
+ function normalizeExtension(ext) {
672
+ const lower = ext.toLowerCase();
673
+ return lower.startsWith(".") ? lower : `.${lower}`;
674
+ }
675
+ function countLines(content) {
676
+ if (content === "")
677
+ return 0;
678
+ const segments = content.split("\n").length;
679
+ return content.endsWith("\n") ? segments - 1 : segments;
680
+ }
681
+ function isBinary(buffer) {
682
+ return buffer.subarray(0, BINARY_CHECK_BYTES).includes(0);
683
+ }
684
+ var NODE_FRAMEWORK_DEPS = {
685
+ next: "next.js",
686
+ react: "react",
687
+ vue: "vue",
688
+ "@angular/core": "angular",
689
+ svelte: "svelte",
690
+ express: "express",
691
+ hono: "hono",
692
+ fastify: "fastify",
693
+ "@nestjs/core": "nestjs"
694
+ };
695
+ var NODE_BUILD_TOOLS = ["vite", "webpack", "rollup", "esbuild", "tsup", "parcel"];
696
+ var NODE_TEST_FRAMEWORKS = ["vitest", "jest", "mocha", "ava", "jasmine"];
697
+ function detectNodeStack(pkgEntry, byPath) {
698
+ let pkg2 = {};
699
+ try {
700
+ pkg2 = JSON.parse(pkgEntry.content);
701
+ } catch {
702
+ }
703
+ const deps = {
704
+ ...pkg2.dependencies,
705
+ ...pkg2.devDependencies
706
+ };
707
+ const has = (name) => name in deps;
708
+ const frameworks = Object.entries(NODE_FRAMEWORK_DEPS).filter(([dep]) => has(dep)).map(([, name]) => name);
709
+ let packageManager = null;
710
+ if (typeof pkg2.packageManager === "string") {
711
+ packageManager = pkg2.packageManager.split("@")[0] ?? null;
712
+ } else if (byPath.has("pnpm-lock.yaml")) {
713
+ packageManager = "pnpm";
714
+ } else if (byPath.has("yarn.lock")) {
715
+ packageManager = "yarn";
716
+ } else if (byPath.has("bun.lockb") || byPath.has("bun.lock")) {
717
+ packageManager = "bun";
718
+ } else if (byPath.has("package-lock.json")) {
719
+ packageManager = "npm";
720
+ }
721
+ return {
722
+ primaryLanguage: has("typescript") || byPath.has("tsconfig.json") ? "typescript" : "javascript",
723
+ frameworks,
724
+ buildTool: NODE_BUILD_TOOLS.find(has) ?? null,
725
+ testFramework: NODE_TEST_FRAMEWORKS.find(has) ?? null,
726
+ packageManager
727
+ };
728
+ }
729
+ function detectPythonStack(pyproject, byPath) {
730
+ const content = pyproject.content;
731
+ let packageManager = "pip";
732
+ if (content.includes("[tool.poetry]"))
733
+ packageManager = "poetry";
734
+ else if (byPath.has("uv.lock") || content.includes("[tool.uv]"))
735
+ packageManager = "uv";
736
+ return {
737
+ primaryLanguage: "python",
738
+ frameworks: ["django", "flask", "fastapi"].filter((f) => content.includes(f)),
739
+ buildTool: null,
740
+ testFramework: content.includes("pytest") ? "pytest" : null,
741
+ packageManager
742
+ };
743
+ }
744
+ var GO_FRAMEWORK_MODULES = {
745
+ "github.com/gin-gonic/gin": "gin",
746
+ "github.com/labstack/echo": "echo",
747
+ "github.com/gofiber/fiber": "fiber"
748
+ };
749
+ function detectGoStack(goMod) {
750
+ const frameworks = Object.entries(GO_FRAMEWORK_MODULES).filter(([module]) => goMod.content.includes(module)).map(([, name]) => name);
751
+ return {
752
+ primaryLanguage: "go",
753
+ frameworks,
754
+ buildTool: "go",
755
+ testFramework: "go test",
756
+ packageManager: "go modules"
757
+ };
758
+ }
759
+ function detectDartStack(pubspec) {
760
+ const isFlutter = /\bflutter\s*:/.test(pubspec.content);
761
+ return {
762
+ primaryLanguage: "dart",
763
+ frameworks: isFlutter ? ["flutter"] : [],
764
+ buildTool: null,
765
+ testFramework: isFlutter ? "flutter test" : "dart test",
766
+ packageManager: "pub"
767
+ };
768
+ }
769
+ var RUST_FRAMEWORK_CRATES = ["axum", "actix-web", "rocket"];
770
+ function detectRustStack(cargo) {
771
+ return {
772
+ primaryLanguage: "rust",
773
+ frameworks: RUST_FRAMEWORK_CRATES.filter((c) => cargo.content.includes(c)),
774
+ buildTool: "cargo",
775
+ testFramework: "cargo test",
776
+ packageManager: "cargo"
777
+ };
778
+ }
779
+ function detectPhpStack(composer) {
780
+ let parsed = {};
781
+ try {
782
+ parsed = JSON.parse(composer.content);
783
+ } catch {
784
+ }
785
+ const deps = {
786
+ ...parsed.require,
787
+ ...parsed["require-dev"]
788
+ };
789
+ const depNames = Object.keys(deps);
790
+ const frameworks = [];
791
+ if (depNames.includes("laravel/framework"))
792
+ frameworks.push("laravel");
793
+ if (depNames.some((d) => d.startsWith("symfony/")))
794
+ frameworks.push("symfony");
795
+ return {
796
+ primaryLanguage: "php",
797
+ frameworks,
798
+ buildTool: null,
799
+ testFramework: depNames.includes("phpunit/phpunit") ? "phpunit" : null,
800
+ packageManager: "composer"
801
+ };
802
+ }
803
+ var EXTENSION_LANGUAGES = {
804
+ ".ts": "typescript",
805
+ ".tsx": "typescript",
806
+ ".js": "javascript",
807
+ ".jsx": "javascript",
808
+ ".mjs": "javascript",
809
+ ".cjs": "javascript",
810
+ ".py": "python",
811
+ ".go": "go",
812
+ ".rs": "rust",
813
+ ".dart": "dart",
814
+ ".php": "php",
815
+ ".rb": "ruby",
816
+ ".java": "java",
817
+ ".cs": "csharp"
818
+ };
819
+ function detectFromExtensions(manifest) {
820
+ const counts = /* @__PURE__ */ new Map();
821
+ for (const file of manifest) {
822
+ const language = EXTENSION_LANGUAGES[file.extension];
823
+ if (language !== void 0)
824
+ counts.set(language, (counts.get(language) ?? 0) + 1);
825
+ }
826
+ let primaryLanguage = "unknown";
827
+ let best = 0;
828
+ for (const [language, count] of counts) {
829
+ if (count > best) {
830
+ primaryLanguage = language;
831
+ best = count;
832
+ }
833
+ }
834
+ return {
835
+ primaryLanguage,
836
+ frameworks: [],
837
+ buildTool: null,
838
+ testFramework: null,
839
+ packageManager: null
840
+ };
841
+ }
842
+
843
+ // ../engine/dist/llm/index.js
844
+ var OPUS_MODEL = "claude-opus-4-8";
845
+ var AnthropicAdapter = class {
846
+ model;
847
+ client;
848
+ constructor(options) {
849
+ this.model = options.model ?? OPUS_MODEL;
850
+ this.client = new Anthropic({ apiKey: options.apiKey });
851
+ }
852
+ async complete(request) {
853
+ const stream = this.client.messages.stream({
854
+ model: this.model,
855
+ max_tokens: request.maxTokens,
856
+ system: request.systemPrompt,
857
+ messages: [{ role: "user", content: request.userPrompt }]
858
+ });
859
+ if (request.onText !== void 0)
860
+ stream.on("text", request.onText);
861
+ const message = await stream.finalMessage();
862
+ const text = message.content.map((block) => block.type === "text" ? block.text : "").join("");
863
+ return {
864
+ text,
865
+ inputTokens: message.usage.input_tokens,
866
+ outputTokens: message.usage.output_tokens,
867
+ model: message.model
868
+ };
869
+ }
870
+ };
871
+
872
+ // ../engine/dist/planning/index.js
873
+ var PLANNING_MAX_OUTPUT_TOKENS = 8e3;
874
+ var PLANNING_CONTEXT_LIMIT_TOKENS = 8e5;
875
+ var PLANNING_BATCH_OVERLAP_TOKENS = 5e4;
876
+ function planOutputPath(entry) {
877
+ return entry.outputPath ?? entry.path;
878
+ }
879
+ var PlanningError = class extends Error {
880
+ rawResponse;
881
+ constructor(message, rawResponse) {
882
+ super(message);
883
+ this.name = "PlanningError";
884
+ this.rawResponse = rawResponse;
885
+ }
886
+ };
887
+ async function planMigration(manifest, migrationTarget, apiKey, options = {}) {
888
+ const adapter = options.adapter ?? new AnthropicAdapter({ apiKey });
889
+ const limit = options.contextLimitTokens ?? PLANNING_CONTEXT_LIMIT_TOKENS;
890
+ const overlap = options.overlapTokens ?? PLANNING_BATCH_OVERLAP_TOKENS;
891
+ const batches = buildBatches(manifest.files, limit, overlap);
892
+ const systemPrompt = buildSystemPrompt(migrationTarget);
893
+ const plans = [];
894
+ for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
895
+ const batch = batches[batchIndex];
896
+ let charsStreamed = 0;
897
+ options.onProgress?.({ batchIndex, batchCount: batches.length, charsStreamed });
898
+ const response = await adapter.complete({
899
+ systemPrompt,
900
+ userPrompt: buildUserPrompt(manifest, migrationTarget, batch, batchIndex, batches.length),
901
+ maxTokens: PLANNING_MAX_OUTPUT_TOKENS,
902
+ onText: (chunk2) => {
903
+ charsStreamed += chunk2.length;
904
+ options.onProgress?.({ batchIndex, batchCount: batches.length, charsStreamed });
905
+ }
906
+ });
907
+ plans.push(parsePlanResponse(response.text, migrationTarget));
908
+ }
909
+ return mergePlans(plans, migrationTarget);
910
+ }
911
+ function buildBatches(files, limitTokens, overlapTokens) {
912
+ const totalTokens = files.reduce((sum, f) => sum + f.tokens, 0);
913
+ if (totalTokens <= limitTokens || files.length === 0)
914
+ return [files];
915
+ const batches = [];
916
+ let current = [];
917
+ let currentTokens = 0;
918
+ let newInCurrent = 0;
919
+ for (const file of files) {
920
+ if (newInCurrent > 0 && currentTokens + file.tokens > limitTokens) {
921
+ batches.push(current);
922
+ const seed = [];
923
+ let seedTokens = 0;
924
+ for (let i = current.length - 1; i >= 0 && seedTokens < overlapTokens; i--) {
925
+ const overlapFile = current[i];
926
+ seed.unshift(overlapFile);
927
+ seedTokens += overlapFile.tokens;
928
+ }
929
+ current = seed;
930
+ currentTokens = seedTokens;
931
+ newInCurrent = 0;
932
+ }
933
+ current.push(file);
934
+ currentTokens += file.tokens;
935
+ newInCurrent++;
936
+ }
937
+ batches.push(current);
938
+ return batches;
939
+ }
940
+ function buildSystemPrompt(target) {
941
+ return [
942
+ `You are a senior software engineer producing a migration plan for a ${target.type} migration from "${target.from}" to "${target.to}".`,
943
+ "You will receive the full contents of a codebase. Read every file and trace cross-file dependencies before planning.",
944
+ "",
945
+ "Respond with a single raw JSON object and nothing else \u2014 no markdown fences, no preamble, no commentary.",
946
+ "The JSON object must have exactly these fields:",
947
+ '- "summary": string \u2014 one-paragraph description of the migration approach',
948
+ `- "migrationTarget": { "from": "${target.from}", "to": "${target.to}", "type": "${target.type}" }`,
949
+ '- "estimatedEffort": "hours" | "days" | "weeks"',
950
+ '- "riskLevel": "low" | "medium" | "high"',
951
+ '- "filesToModify": array of { "path": string, "outputPath": string, "complexity": "low" | "medium" | "high", "description": string }',
952
+ '- "filesToCreate": array of { "path": string, "complexity": "low" | "medium" | "high", "description": string } \u2014 new config files, type definition files, etc.',
953
+ '- "filesToDelete": array of string paths \u2014 obsolete configs, deprecated files',
954
+ "",
955
+ "Path rules for filesToModify:",
956
+ '- "path" is always the existing source file. "outputPath" is where the transformed file lands.',
957
+ '- A rename (e.g. converting src/app.js to src/app.ts) is ONE filesToModify entry with "path" set to the source and "outputPath" set to the new name. Do NOT also list the source in filesToDelete and do NOT list the new name in filesToCreate.',
958
+ '- For in-place modifications, set "outputPath" equal to "path" (or omit it).',
959
+ "- filesToDelete is only for files genuinely removed with no replacement.",
960
+ '- "dependencyChanges": array of { "name": string, "action": "add" | "remove" | "update", "currentVersion": string | null, "targetVersion": string | null }',
961
+ '- "breakingChanges": array of { "description": string, "severity": "low" | "medium" | "high", "affectedFiles": string[] }',
962
+ '- "tokenBudgetEstimate": number \u2014 estimated output tokens the execution phase will need',
963
+ "",
964
+ "Order filesToModify so that files others depend on come first. Flag every breaking change you can identify, including test coverage gaps."
965
+ ].join("\n");
966
+ }
967
+ function buildUserPrompt(manifest, target, batch, batchIndex, batchCount) {
968
+ const header = [
969
+ `Migration target: ${target.from} -> ${target.to} (${target.type})`,
970
+ `Detected stack: ${JSON.stringify(manifest.detectedStack)}`,
971
+ `Codebase: ${manifest.files.length} files, ${manifest.totalLines} lines, ~${manifest.totalTokens} tokens.`
972
+ ];
973
+ if (batchCount > 1) {
974
+ header.push(`NOTE: the codebase exceeds the context budget, so it is split into ${batchCount} overlapping batches. This is batch ${batchIndex + 1} of ${batchCount} (${batch.length} files). Plan only for the files in this batch; overlapping files from the previous batch are included for cross-file context.`);
975
+ }
976
+ const files = batch.map((f) => `<file path="${f.relativePath}">
977
+ ${f.content}
978
+ </file>`);
979
+ return [...header, "", ...files].join("\n");
980
+ }
981
+ function parsePlanResponse(text, migrationTarget) {
982
+ const start = text.indexOf("{");
983
+ const end = text.lastIndexOf("}");
984
+ if (start === -1 || end <= start) {
985
+ throw new PlanningError("No JSON object found in planning response", text);
986
+ }
987
+ let raw;
988
+ try {
989
+ raw = JSON.parse(text.slice(start, end + 1));
990
+ } catch (error) {
991
+ throw new PlanningError(`Planning response is not valid JSON: ${error instanceof Error ? error.message : String(error)}`, text);
992
+ }
993
+ return normalizePlan(raw, migrationTarget, text);
994
+ }
995
+ function normalizePlan(raw, migrationTarget, rawText) {
996
+ if (typeof raw !== "object" || raw === null) {
997
+ throw new PlanningError("Planning response JSON is not an object", rawText);
998
+ }
999
+ const obj = raw;
1000
+ const estimatedEffort = obj.estimatedEffort;
1001
+ if (estimatedEffort !== "hours" && estimatedEffort !== "days" && estimatedEffort !== "weeks") {
1002
+ throw new PlanningError(`Invalid estimatedEffort: ${JSON.stringify(estimatedEffort)}`, rawText);
1003
+ }
1004
+ const riskLevel = obj.riskLevel;
1005
+ if (riskLevel !== "low" && riskLevel !== "medium" && riskLevel !== "high") {
1006
+ throw new PlanningError(`Invalid riskLevel: ${JSON.stringify(riskLevel)}`, rawText);
1007
+ }
1008
+ return sanitizePlan({
1009
+ summary: typeof obj.summary === "string" ? obj.summary : "",
1010
+ migrationTarget,
1011
+ estimatedEffort,
1012
+ riskLevel,
1013
+ filesToModify: normalizeFilePlanEntries(obj.filesToModify),
1014
+ filesToCreate: normalizeFilePlanEntries(obj.filesToCreate),
1015
+ filesToDelete: asStringArray(obj.filesToDelete),
1016
+ dependencyChanges: normalizeDependencyChanges(obj.dependencyChanges),
1017
+ breakingChanges: normalizeBreakingChanges(obj.breakingChanges),
1018
+ tokenBudgetEstimate: typeof obj.tokenBudgetEstimate === "number" && Number.isFinite(obj.tokenBudgetEstimate) ? obj.tokenBudgetEstimate : 0
1019
+ });
1020
+ }
1021
+ function sanitizePlan(plan) {
1022
+ const modifySources = new Set(plan.filesToModify.map((e) => e.path));
1023
+ const modifyOutputs = new Set(plan.filesToModify.map(planOutputPath));
1024
+ return {
1025
+ ...plan,
1026
+ filesToCreate: plan.filesToCreate.filter((e) => !modifyOutputs.has(e.path) && !modifySources.has(e.path)),
1027
+ filesToDelete: plan.filesToDelete.filter((p) => !modifySources.has(p) && !modifyOutputs.has(p))
1028
+ };
1029
+ }
1030
+ function isComplexity(value) {
1031
+ return value === "low" || value === "medium" || value === "high";
1032
+ }
1033
+ function asStringArray(value) {
1034
+ if (!Array.isArray(value))
1035
+ return [];
1036
+ return value.filter((item) => typeof item === "string");
1037
+ }
1038
+ function normalizeFilePlanEntries(value) {
1039
+ if (!Array.isArray(value))
1040
+ return [];
1041
+ const entries = [];
1042
+ for (const item of value) {
1043
+ if (typeof item !== "object" || item === null)
1044
+ continue;
1045
+ const record = item;
1046
+ if (typeof record.path !== "string" || record.path === "")
1047
+ continue;
1048
+ entries.push({
1049
+ path: record.path,
1050
+ outputPath: typeof record.outputPath === "string" && record.outputPath !== "" ? record.outputPath : record.path,
1051
+ complexity: isComplexity(record.complexity) ? record.complexity : "medium",
1052
+ description: typeof record.description === "string" ? record.description : ""
1053
+ });
1054
+ }
1055
+ return entries;
1056
+ }
1057
+ function normalizeDependencyChanges(value) {
1058
+ if (!Array.isArray(value))
1059
+ return [];
1060
+ const changes = [];
1061
+ for (const item of value) {
1062
+ if (typeof item !== "object" || item === null)
1063
+ continue;
1064
+ const record = item;
1065
+ if (typeof record.name !== "string" || record.name === "")
1066
+ continue;
1067
+ const action = record.action;
1068
+ if (action !== "add" && action !== "remove" && action !== "update")
1069
+ continue;
1070
+ changes.push({
1071
+ name: record.name,
1072
+ action,
1073
+ currentVersion: typeof record.currentVersion === "string" ? record.currentVersion : null,
1074
+ targetVersion: typeof record.targetVersion === "string" ? record.targetVersion : null
1075
+ });
1076
+ }
1077
+ return changes;
1078
+ }
1079
+ function normalizeBreakingChanges(value) {
1080
+ if (!Array.isArray(value))
1081
+ return [];
1082
+ const changes = [];
1083
+ for (const item of value) {
1084
+ if (typeof item !== "object" || item === null)
1085
+ continue;
1086
+ const record = item;
1087
+ if (typeof record.description !== "string" || record.description === "")
1088
+ continue;
1089
+ changes.push({
1090
+ description: record.description,
1091
+ severity: isComplexity(record.severity) ? record.severity : "medium",
1092
+ affectedFiles: asStringArray(record.affectedFiles)
1093
+ });
1094
+ }
1095
+ return changes;
1096
+ }
1097
+ var COMPLEXITY_RANK = { low: 0, medium: 1, high: 2 };
1098
+ var EFFORT_RANK = {
1099
+ hours: 0,
1100
+ days: 1,
1101
+ weeks: 2
1102
+ };
1103
+ function mergePlans(plans, migrationTarget) {
1104
+ const first = plans[0];
1105
+ if (first === void 0) {
1106
+ throw new PlanningError("No plans produced", "");
1107
+ }
1108
+ if (plans.length === 1)
1109
+ return first;
1110
+ const modify = /* @__PURE__ */ new Map();
1111
+ const create = /* @__PURE__ */ new Map();
1112
+ const dependencyChanges = /* @__PURE__ */ new Map();
1113
+ const breakingChanges = /* @__PURE__ */ new Map();
1114
+ const filesToDelete = /* @__PURE__ */ new Set();
1115
+ let estimatedEffort = "hours";
1116
+ let riskLevel = "low";
1117
+ let tokenBudgetEstimate = 0;
1118
+ for (const plan of plans) {
1119
+ for (const entry of plan.filesToModify) {
1120
+ const existing = modify.get(entry.path);
1121
+ if (existing === void 0 || COMPLEXITY_RANK[entry.complexity] > COMPLEXITY_RANK[existing.complexity]) {
1122
+ modify.set(entry.path, entry);
1123
+ }
1124
+ }
1125
+ for (const entry of plan.filesToCreate) {
1126
+ if (!create.has(entry.path))
1127
+ create.set(entry.path, entry);
1128
+ }
1129
+ for (const file of plan.filesToDelete)
1130
+ filesToDelete.add(file);
1131
+ for (const change of plan.dependencyChanges) {
1132
+ dependencyChanges.set(`${change.name}:${change.action}`, change);
1133
+ }
1134
+ for (const change of plan.breakingChanges) {
1135
+ breakingChanges.set(change.description, change);
1136
+ }
1137
+ if (EFFORT_RANK[plan.estimatedEffort] > EFFORT_RANK[estimatedEffort]) {
1138
+ estimatedEffort = plan.estimatedEffort;
1139
+ }
1140
+ if (COMPLEXITY_RANK[plan.riskLevel] > COMPLEXITY_RANK[riskLevel]) {
1141
+ riskLevel = plan.riskLevel;
1142
+ }
1143
+ tokenBudgetEstimate += plan.tokenBudgetEstimate;
1144
+ }
1145
+ return sanitizePlan({
1146
+ summary: plans.map((p, i) => `[Batch ${i + 1}/${plans.length}] ${p.summary}`).join("\n"),
1147
+ migrationTarget,
1148
+ estimatedEffort,
1149
+ riskLevel,
1150
+ filesToModify: [...modify.values()],
1151
+ filesToCreate: [...create.values()],
1152
+ filesToDelete: [...filesToDelete],
1153
+ dependencyChanges: [...dependencyChanges.values()],
1154
+ breakingChanges: [...breakingChanges.values()],
1155
+ tokenBudgetEstimate
1156
+ });
1157
+ }
1158
+
1159
+ // ../engine/dist/execution/index.js
1160
+ import fs3 from "fs/promises";
1161
+ import path3 from "path";
1162
+
1163
+ // ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/base.js
1164
+ var Diff = class {
1165
+ diff(oldStr, newStr, options = {}) {
1166
+ let callback;
1167
+ if (typeof options === "function") {
1168
+ callback = options;
1169
+ options = {};
1170
+ } else if ("callback" in options) {
1171
+ callback = options.callback;
1172
+ }
1173
+ const oldString = this.castInput(oldStr, options);
1174
+ const newString = this.castInput(newStr, options);
1175
+ const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
1176
+ const newTokens = this.removeEmpty(this.tokenize(newString, options));
1177
+ return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
1178
+ }
1179
+ diffWithOptionsObj(oldTokens, newTokens, options, callback) {
1180
+ var _a;
1181
+ const done = (value) => {
1182
+ value = this.postProcess(value, options);
1183
+ if (callback) {
1184
+ setTimeout(function() {
1185
+ callback(value);
1186
+ }, 0);
1187
+ return void 0;
1188
+ } else {
1189
+ return value;
1190
+ }
1191
+ };
1192
+ const newLen = newTokens.length, oldLen = oldTokens.length;
1193
+ let editLength = 1;
1194
+ let maxEditLength = newLen + oldLen;
1195
+ if (options.maxEditLength != null) {
1196
+ maxEditLength = Math.min(maxEditLength, options.maxEditLength);
1197
+ }
1198
+ const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
1199
+ const abortAfterTimestamp = Date.now() + maxExecutionTime;
1200
+ const bestPath = [{ oldPos: -1, lastComponent: void 0 }];
1201
+ let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
1202
+ if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
1203
+ return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
1204
+ }
1205
+ let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
1206
+ const execEditLength = () => {
1207
+ for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
1208
+ let basePath;
1209
+ const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
1210
+ if (removePath) {
1211
+ bestPath[diagonalPath - 1] = void 0;
1212
+ }
1213
+ let canAdd = false;
1214
+ if (addPath) {
1215
+ const addPathNewPos = addPath.oldPos - diagonalPath;
1216
+ canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
1217
+ }
1218
+ const canRemove = removePath && removePath.oldPos + 1 < oldLen;
1219
+ if (!canAdd && !canRemove) {
1220
+ bestPath[diagonalPath] = void 0;
1221
+ continue;
1222
+ }
1223
+ if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
1224
+ basePath = this.addToPath(addPath, true, false, 0, options);
1225
+ } else {
1226
+ basePath = this.addToPath(removePath, false, true, 1, options);
1227
+ }
1228
+ newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
1229
+ if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
1230
+ return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
1231
+ } else {
1232
+ bestPath[diagonalPath] = basePath;
1233
+ if (basePath.oldPos + 1 >= oldLen) {
1234
+ maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
1235
+ }
1236
+ if (newPos + 1 >= newLen) {
1237
+ minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
1238
+ }
1239
+ }
1240
+ }
1241
+ editLength++;
1242
+ };
1243
+ if (callback) {
1244
+ (function exec() {
1245
+ setTimeout(function() {
1246
+ if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
1247
+ return callback(void 0);
1248
+ }
1249
+ if (!execEditLength()) {
1250
+ exec();
1251
+ }
1252
+ }, 0);
1253
+ })();
1254
+ } else {
1255
+ while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
1256
+ const ret = execEditLength();
1257
+ if (ret) {
1258
+ return ret;
1259
+ }
1260
+ }
1261
+ }
1262
+ }
1263
+ addToPath(path10, added, removed, oldPosInc, options) {
1264
+ const last = path10.lastComponent;
1265
+ if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
1266
+ return {
1267
+ oldPos: path10.oldPos + oldPosInc,
1268
+ lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
1269
+ };
1270
+ } else {
1271
+ return {
1272
+ oldPos: path10.oldPos + oldPosInc,
1273
+ lastComponent: { count: 1, added, removed, previousComponent: last }
1274
+ };
1275
+ }
1276
+ }
1277
+ extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
1278
+ const newLen = newTokens.length, oldLen = oldTokens.length;
1279
+ let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
1280
+ while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
1281
+ newPos++;
1282
+ oldPos++;
1283
+ commonCount++;
1284
+ if (options.oneChangePerToken) {
1285
+ basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
1286
+ }
1287
+ }
1288
+ if (commonCount && !options.oneChangePerToken) {
1289
+ basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
1290
+ }
1291
+ basePath.oldPos = oldPos;
1292
+ return newPos;
1293
+ }
1294
+ equals(left, right, options) {
1295
+ if (options.comparator) {
1296
+ return options.comparator(left, right);
1297
+ } else {
1298
+ return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
1299
+ }
1300
+ }
1301
+ removeEmpty(array) {
1302
+ const ret = [];
1303
+ for (let i = 0; i < array.length; i++) {
1304
+ if (array[i]) {
1305
+ ret.push(array[i]);
1306
+ }
1307
+ }
1308
+ return ret;
1309
+ }
1310
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1311
+ castInput(value, options) {
1312
+ return value;
1313
+ }
1314
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1315
+ tokenize(value, options) {
1316
+ return Array.from(value);
1317
+ }
1318
+ join(chars) {
1319
+ return chars.join("");
1320
+ }
1321
+ postProcess(changeObjects, options) {
1322
+ return changeObjects;
1323
+ }
1324
+ get useLongestToken() {
1325
+ return false;
1326
+ }
1327
+ buildValues(lastComponent, newTokens, oldTokens) {
1328
+ const components = [];
1329
+ let nextComponent;
1330
+ while (lastComponent) {
1331
+ components.push(lastComponent);
1332
+ nextComponent = lastComponent.previousComponent;
1333
+ delete lastComponent.previousComponent;
1334
+ lastComponent = nextComponent;
1335
+ }
1336
+ components.reverse();
1337
+ const componentLen = components.length;
1338
+ let componentPos = 0, newPos = 0, oldPos = 0;
1339
+ for (; componentPos < componentLen; componentPos++) {
1340
+ const component = components[componentPos];
1341
+ if (!component.removed) {
1342
+ if (!component.added && this.useLongestToken) {
1343
+ let value = newTokens.slice(newPos, newPos + component.count);
1344
+ value = value.map(function(value2, i) {
1345
+ const oldValue = oldTokens[oldPos + i];
1346
+ return oldValue.length > value2.length ? oldValue : value2;
1347
+ });
1348
+ component.value = this.join(value);
1349
+ } else {
1350
+ component.value = this.join(newTokens.slice(newPos, newPos + component.count));
1351
+ }
1352
+ newPos += component.count;
1353
+ if (!component.added) {
1354
+ oldPos += component.count;
1355
+ }
1356
+ } else {
1357
+ component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
1358
+ oldPos += component.count;
1359
+ }
1360
+ }
1361
+ return components;
1362
+ }
1363
+ };
1364
+
1365
+ // ../../node_modules/.pnpm/diff@9.0.0/node_modules/diff/libesm/diff/line.js
1366
+ var LineDiff = class extends Diff {
1367
+ constructor() {
1368
+ super(...arguments);
1369
+ this.tokenize = tokenize;
1370
+ }
1371
+ equals(left, right, options) {
1372
+ if (options.ignoreWhitespace) {
1373
+ if (!options.newlineIsToken || !left.includes("\n")) {
1374
+ left = left.trim();
1375
+ }
1376
+ if (!options.newlineIsToken || !right.includes("\n")) {
1377
+ right = right.trim();
1378
+ }
1379
+ } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
1380
+ if (left.endsWith("\n")) {
1381
+ left = left.slice(0, -1);
1382
+ }
1383
+ if (right.endsWith("\n")) {
1384
+ right = right.slice(0, -1);
1385
+ }
1386
+ }
1387
+ return super.equals(left, right, options);
1388
+ }
1389
+ };
1390
+ var lineDiff = new LineDiff();
1391
+ function diffLines(oldStr, newStr, options) {
1392
+ return lineDiff.diff(oldStr, newStr, options);
1393
+ }
1394
+ function tokenize(value, options) {
1395
+ if (options.stripTrailingCr) {
1396
+ value = value.replace(/\r\n/g, "\n");
1397
+ }
1398
+ const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
1399
+ if (!linesAndNewlines[linesAndNewlines.length - 1]) {
1400
+ linesAndNewlines.pop();
1401
+ }
1402
+ for (let i = 0; i < linesAndNewlines.length; i++) {
1403
+ const line = linesAndNewlines[i];
1404
+ if (i % 2 && !options.newlineIsToken) {
1405
+ retLines[retLines.length - 1] += line;
1406
+ } else {
1407
+ retLines.push(line);
1408
+ }
1409
+ }
1410
+ return retLines;
1411
+ }
1412
+
1413
+ // ../engine/dist/execution/index.js
1414
+ var EXECUTION_MAX_OUTPUT_TOKENS = 64e3;
1415
+ var MEDIUM_BATCH_SIZE = 5;
1416
+ var LOW_BATCH_SIZE = 10;
1417
+ var DEFAULT_MAX_RETRIES = 3;
1418
+ var DEFAULT_RETRY_BASE_DELAY_MS = 1e3;
1419
+ var ExecutionError = class extends Error {
1420
+ checkpointPath;
1421
+ constructor(message, checkpointPath2) {
1422
+ super(message);
1423
+ this.name = "ExecutionError";
1424
+ this.checkpointPath = checkpointPath2;
1425
+ }
1426
+ };
1427
+ async function executeMigration(manifest, plan, apiKey, onProgress, options = {}) {
1428
+ const outputDir = options.outputDir ?? path3.join(manifest.rootPath, "migrated");
1429
+ const checkpointPath2 = options.checkpointPath ?? path3.join(manifest.rootPath, ".automigrate-checkpoint.json");
1430
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1431
+ const checkpoint = {
1432
+ version: 1,
1433
+ rootPath: manifest.rootPath,
1434
+ outputDir,
1435
+ plan,
1436
+ batchStatus: buildExecutionBatches(plan).map(() => "pending"),
1437
+ completedFiles: [],
1438
+ failedFiles: [],
1439
+ filesModified: 0,
1440
+ filesCreated: 0,
1441
+ linesAdded: 0,
1442
+ linesRemoved: 0,
1443
+ tokensUsed: 0,
1444
+ createdAt: now,
1445
+ updatedAt: now
1446
+ };
1447
+ return runExecution(manifest, checkpoint, checkpointPath2, apiKey, onProgress, options);
1448
+ }
1449
+ async function resumeMigration(checkpointPath2, manifest, apiKey, onProgress, options = {}) {
1450
+ const checkpoint = JSON.parse(await fs3.readFile(checkpointPath2, "utf8"));
1451
+ return runExecution(manifest, checkpoint, checkpointPath2, apiKey, onProgress, options);
1452
+ }
1453
+ function buildExecutionBatches(plan) {
1454
+ const entries = [
1455
+ ...plan.filesToModify.map((entry) => ({ plan: entry, action: "modify" })),
1456
+ ...plan.filesToCreate.map((entry) => ({ plan: entry, action: "create" }))
1457
+ ];
1458
+ const high = entries.filter((e) => e.plan.complexity === "high");
1459
+ const medium = entries.filter((e) => e.plan.complexity === "medium");
1460
+ const low = entries.filter((e) => e.plan.complexity === "low");
1461
+ const batches = [];
1462
+ for (const entry of high)
1463
+ batches.push({ entries: [entry] });
1464
+ for (const group of chunk(medium, MEDIUM_BATCH_SIZE))
1465
+ batches.push({ entries: group });
1466
+ for (const group of chunk(low, LOW_BATCH_SIZE))
1467
+ batches.push({ entries: group });
1468
+ return batches;
1469
+ }
1470
+ function chunk(items, size) {
1471
+ const groups = [];
1472
+ for (let i = 0; i < items.length; i += size)
1473
+ groups.push(items.slice(i, i + size));
1474
+ return groups;
1475
+ }
1476
+ function parseTransformedFiles(text) {
1477
+ const blocks = /* @__PURE__ */ new Map();
1478
+ const pattern = /<transformed_file path="([^"]+)">([\s\S]*?)<\/transformed_file>/g;
1479
+ let match;
1480
+ while ((match = pattern.exec(text)) !== null) {
1481
+ let content = match[2];
1482
+ if (content.startsWith("\n"))
1483
+ content = content.slice(1);
1484
+ blocks.set(match[1], content);
1485
+ }
1486
+ return blocks;
1487
+ }
1488
+ async function runExecution(manifest, checkpoint, checkpointPath2, apiKey, onProgress, options) {
1489
+ const adapter = options.adapter ?? new AnthropicAdapter({ apiKey });
1490
+ const maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
1491
+ const retryBaseDelayMs = options.retryBaseDelayMs ?? DEFAULT_RETRY_BASE_DELAY_MS;
1492
+ const maxOutputTokens = options.maxOutputTokens ?? EXECUTION_MAX_OUTPUT_TOKENS;
1493
+ const batches = buildExecutionBatches(checkpoint.plan);
1494
+ const filesTotal = batches.reduce((sum, b) => sum + b.entries.length, 0);
1495
+ const originals = new Map(manifest.files.map((f) => [f.relativePath, f]));
1496
+ const completedSet = new Set(checkpoint.completedFiles);
1497
+ const systemPrompt = buildSystemPrompt2(checkpoint.plan);
1498
+ const emit = (stage, currentFile) => {
1499
+ onProgress({
1500
+ stage,
1501
+ currentFile,
1502
+ filesComplete: checkpoint.completedFiles.length,
1503
+ filesTotal,
1504
+ tokensUsed: checkpoint.tokensUsed
1505
+ });
1506
+ };
1507
+ emit("preparing", null);
1508
+ try {
1509
+ for (let i = 0; i < batches.length; i++) {
1510
+ if (checkpoint.batchStatus[i] === "complete")
1511
+ continue;
1512
+ const batch = batches[i];
1513
+ emit("transforming", batch.entries[0].plan.path);
1514
+ const response = await completeWithRetry(adapter, {
1515
+ systemPrompt,
1516
+ userPrompt: buildBatchPrompt(checkpoint.plan, batch, originals),
1517
+ maxTokens: maxOutputTokens
1518
+ }, maxRetries, retryBaseDelayMs);
1519
+ checkpoint.tokensUsed += response.inputTokens + response.outputTokens;
1520
+ const blocks = parseTransformedFiles(response.text);
1521
+ for (const entry of batch.entries) {
1522
+ if (completedSet.has(entry.plan.path))
1523
+ continue;
1524
+ const outputPath = planOutputPath(entry.plan);
1525
+ const content = blocks.get(outputPath) ?? blocks.get(entry.plan.path);
1526
+ const targetPath = content === void 0 ? null : safeOutputPath(checkpoint.outputDir, outputPath);
1527
+ if (content === void 0 || targetPath === null) {
1528
+ if (!checkpoint.failedFiles.includes(entry.plan.path)) {
1529
+ checkpoint.failedFiles.push(entry.plan.path);
1530
+ }
1531
+ continue;
1532
+ }
1533
+ await fs3.mkdir(path3.dirname(targetPath), { recursive: true });
1534
+ await fs3.writeFile(targetPath, content, "utf8");
1535
+ const original = originals.get(entry.plan.path);
1536
+ const { added, removed } = countDiffLines(original?.content ?? "", content);
1537
+ checkpoint.linesAdded += added;
1538
+ checkpoint.linesRemoved += removed;
1539
+ if (entry.action === "modify")
1540
+ checkpoint.filesModified++;
1541
+ else
1542
+ checkpoint.filesCreated++;
1543
+ checkpoint.completedFiles.push(entry.plan.path);
1544
+ completedSet.add(entry.plan.path);
1545
+ checkpoint.failedFiles = checkpoint.failedFiles.filter((p) => p !== entry.plan.path);
1546
+ emit("writing", entry.plan.path);
1547
+ }
1548
+ checkpoint.batchStatus[i] = "complete";
1549
+ await saveCheckpoint(checkpointPath2, checkpoint);
1550
+ }
1551
+ } catch (error) {
1552
+ await saveCheckpoint(checkpointPath2, checkpoint);
1553
+ emit("failed", null);
1554
+ const message = error instanceof Error ? error.message : String(error);
1555
+ throw new ExecutionError(`Migration execution failed (progress saved to checkpoint): ${message}`, checkpointPath2);
1556
+ }
1557
+ emit("complete", null);
1558
+ const deletedLines = checkpoint.plan.filesToDelete.reduce((sum, filePath) => sum + (originals.get(filePath)?.lines ?? 0), 0);
1559
+ return {
1560
+ filesModified: checkpoint.filesModified,
1561
+ filesCreated: checkpoint.filesCreated,
1562
+ filesDeleted: checkpoint.plan.filesToDelete.length,
1563
+ linesAdded: checkpoint.linesAdded,
1564
+ linesRemoved: checkpoint.linesRemoved + deletedLines,
1565
+ tokensUsed: checkpoint.tokensUsed,
1566
+ failedFiles: [...checkpoint.failedFiles],
1567
+ checkpointPath: checkpointPath2,
1568
+ outputDir: checkpoint.outputDir
1569
+ };
1570
+ }
1571
+ function buildSystemPrompt2(plan) {
1572
+ const { from, to, type } = plan.migrationTarget;
1573
+ return [
1574
+ `You are a senior software engineer executing an approved migration plan: a ${type} migration from "${from}" to "${to}".`,
1575
+ "Transform each requested file completely and return EVERY requested file in this exact format:",
1576
+ '<transformed_file path="exact/requested/OUTPUT/path">',
1577
+ "...full transformed file content...",
1578
+ "</transformed_file>",
1579
+ "",
1580
+ "Rules:",
1581
+ '- Return every requested file exactly once. Each file request states "read from <source path>, return as <output path>" \u2014 always use the OUTPUT path in the transformed_file tag, even when the migration renames the file.',
1582
+ '- Output the COMPLETE file content \u2014 no truncation, no placeholders, no "rest of file unchanged" comments.',
1583
+ "- No markdown fences and no commentary outside the transformed_file tags.",
1584
+ "- Preserve behavior unless the migration target requires changing it.",
1585
+ "- Keep types, imports, and naming consistent across all files in this batch and with the migration plan."
1586
+ ].join("\n");
1587
+ }
1588
+ function buildBatchPrompt(plan, batch, originals) {
1589
+ const lines = [
1590
+ `Migration plan summary:
1591
+ ${plan.summary}`,
1592
+ `Planned dependency changes: ${JSON.stringify(plan.dependencyChanges)}`,
1593
+ "",
1594
+ "Files to transform in this batch:"
1595
+ ];
1596
+ for (const entry of batch.entries) {
1597
+ const outputPath = planOutputPath(entry.plan);
1598
+ const action = entry.action === "create" ? "CREATE new file" : outputPath === entry.plan.path ? "MODIFY" : "MODIFY and RENAME";
1599
+ lines.push(`- read from "${entry.plan.path}", return as "${outputPath}" [${action}, complexity: ${entry.plan.complexity}] ${entry.plan.description}`);
1600
+ }
1601
+ lines.push("", "Original file contents:");
1602
+ for (const entry of batch.entries) {
1603
+ if (entry.action === "create") {
1604
+ lines.push(`<file path="${entry.plan.path}">
1605
+ (new file \u2014 no original content; create it per the plan description)
1606
+ </file>`);
1607
+ continue;
1608
+ }
1609
+ const original = originals.get(entry.plan.path);
1610
+ lines.push(`<file path="${entry.plan.path}">
1611
+ ${original?.content ?? ""}
1612
+ </file>`);
1613
+ }
1614
+ return lines.join("\n");
1615
+ }
1616
+ async function completeWithRetry(adapter, request, maxRetries, baseDelayMs) {
1617
+ let lastError;
1618
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
1619
+ if (attempt > 0)
1620
+ await sleep(baseDelayMs * 2 ** (attempt - 1));
1621
+ try {
1622
+ return await adapter.complete(request);
1623
+ } catch (error) {
1624
+ lastError = error;
1625
+ }
1626
+ }
1627
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
1628
+ }
1629
+ function sleep(ms) {
1630
+ return new Promise((resolve) => setTimeout(resolve, ms));
1631
+ }
1632
+ function safeOutputPath(outputDir, relPath) {
1633
+ const root = path3.resolve(outputDir);
1634
+ const resolved = path3.resolve(root, relPath);
1635
+ return resolved.startsWith(root + path3.sep) ? resolved : null;
1636
+ }
1637
+ function countDiffLines(oldContent, newContent) {
1638
+ let added = 0;
1639
+ let removed = 0;
1640
+ for (const part of diffLines(oldContent, newContent)) {
1641
+ if (part.added)
1642
+ added += part.count ?? 0;
1643
+ else if (part.removed)
1644
+ removed += part.count ?? 0;
1645
+ }
1646
+ return { added, removed };
1647
+ }
1648
+ async function saveCheckpoint(checkpointPath2, checkpoint) {
1649
+ checkpoint.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1650
+ await fs3.mkdir(path3.dirname(checkpointPath2), { recursive: true });
1651
+ await fs3.writeFile(checkpointPath2, JSON.stringify(checkpoint, null, 2), "utf8");
1652
+ }
1653
+
1654
+ // ../engine/dist/verification/index.js
1655
+ import { spawn } from "child_process";
1656
+ import fs4 from "fs/promises";
1657
+ import path4 from "path";
1658
+ var VERIFICATION_MAX_OUTPUT_TOKENS = 4e3;
1659
+ var DEFAULT_SELF_REPAIR_THRESHOLD = 0.9;
1660
+ var DEFAULT_MAX_SELF_REPAIR_ATTEMPTS = 2;
1661
+ var DEFAULT_MAX_REVIEW_FILES = 20;
1662
+ var COMPILE_FAIL_CONFIDENCE_CAP = 0.3;
1663
+ var TEST_OUTPUT_EXCERPT_CHARS = 4e3;
1664
+ var COMMAND_OUTPUT_EXCERPT_CHARS = 600;
1665
+ async function verifyMigration(originalDir, migratedDir, plan, executionResult, apiKey, options = {}) {
1666
+ const adapter = options.adapter ?? new AnthropicAdapter({ apiKey });
1667
+ const runCommand2 = options.commandRunner ?? defaultCommandRunner(options.commandTimeoutMs);
1668
+ const selfRepairEnabled = options.selfRepair ?? true;
1669
+ const threshold = options.selfRepairThreshold ?? DEFAULT_SELF_REPAIR_THRESHOLD;
1670
+ const maxAttempts = options.maxSelfRepairAttempts ?? DEFAULT_MAX_SELF_REPAIR_ATTEMPTS;
1671
+ const setup = await detectTestSetup(migratedDir);
1672
+ let testPassRate = null;
1673
+ let testOutput = "";
1674
+ let testCommand = null;
1675
+ if (setup !== null) {
1676
+ testCommand = [setup.testCommand[0], ...setup.testCommand[1]].join(" ");
1677
+ if (setup.installCommand !== null && options.skipInstall !== true) {
1678
+ await runCommand2(setup.installCommand[0], setup.installCommand[1], migratedDir);
1679
+ }
1680
+ ({ testPassRate, testOutput } = await runTests(setup, migratedDir, runCommand2));
1681
+ }
1682
+ let selfRepairAttempts = 0;
1683
+ let failedFiles = executionResult.failedFiles;
1684
+ while (selfRepairEnabled && options.manifest !== void 0 && failedFiles.length > 0 && testPassRate !== null && testPassRate < threshold && selfRepairAttempts < maxAttempts && setup !== null) {
1685
+ selfRepairAttempts++;
1686
+ const repairResult = await repairFailedFiles(options.manifest, plan, failedFiles, executionResult, apiKey, adapter, selfRepairAttempts);
1687
+ failedFiles = repairResult.failedFiles;
1688
+ ({ testPassRate, testOutput } = await runTests(setup, migratedDir, runCommand2));
1689
+ }
1690
+ const descriptor = staticCheckForTarget(plan.migrationTarget.to);
1691
+ const installedForTests = setup?.installCommand !== null && setup !== null;
1692
+ let compileCheck = descriptor === null ? {
1693
+ ran: false,
1694
+ ok: false,
1695
+ errors: [],
1696
+ skippedReason: `no static check registered for target "${plan.migrationTarget.to}"`
1697
+ } : await runStaticCheck(migratedDir, descriptor, runCommand2, {
1698
+ skipInstall: options.skipInstall === true || installedForTests
1699
+ });
1700
+ let compileRepairs = 0;
1701
+ while (selfRepairEnabled && options.manifest !== void 0 && descriptor !== null && compileCheck.ran && !compileCheck.ok && selfRepairAttempts < maxAttempts) {
1702
+ const repairTargets = mapCompileErrorsToEntries(compileCheck.errors, plan);
1703
+ if (repairTargets.size === 0)
1704
+ break;
1705
+ selfRepairAttempts++;
1706
+ compileRepairs++;
1707
+ await repairCompileErrors(options.manifest, plan, repairTargets, executionResult, apiKey, adapter, selfRepairAttempts);
1708
+ compileCheck = await runStaticCheck(migratedDir, descriptor, runCommand2, {
1709
+ skipInstall: true
1710
+ });
1711
+ }
1712
+ if (compileRepairs > 0 && setup !== null) {
1713
+ ({ testPassRate, testOutput } = await runTests(setup, migratedDir, runCommand2));
1714
+ }
1715
+ const review = await selfReview(originalDir, migratedDir, plan, `${testOutput}
1716
+
1717
+ ${describeCompileCheck(compileCheck)}`.trim(), adapter, options.maxReviewFiles ?? DEFAULT_MAX_REVIEW_FILES);
1718
+ const diffStats = await computeDiffStats(originalDir, migratedDir, plan);
1719
+ let confidenceScore = review.confidenceScore;
1720
+ const flaggedIssues = [...review.issues];
1721
+ if (compileCheck.ran && !compileCheck.ok) {
1722
+ confidenceScore = Math.min(confidenceScore, COMPILE_FAIL_CONFIDENCE_CAP);
1723
+ const files = [...new Set(compileCheck.errors.map((e) => e.file).filter((f) => f !== ""))];
1724
+ flaggedIssues.unshift({
1725
+ description: `Output does not compile: ${compileCheck.errors.length} compiler error(s)` + (selfRepairAttempts > 0 ? ` remain after ${selfRepairAttempts} self-repair attempt(s)` : "") + `. ${compileCheck.errors.map(formatCompileError).join("; ")}`,
1726
+ severity: "high",
1727
+ category: "conversion",
1728
+ affectedFiles: files
1729
+ });
1730
+ } else if (!compileCheck.ran) {
1731
+ flaggedIssues.push({
1732
+ description: `Objective compile check unavailable (${compileCheck.skippedReason ?? "unknown reason"}); confidence rests on model self-review only \u2014 manual verification recommended.`,
1733
+ severity: "medium",
1734
+ category: "environment",
1735
+ affectedFiles: []
1736
+ });
1737
+ }
1738
+ return {
1739
+ testPassRate,
1740
+ testOutput,
1741
+ testCommand,
1742
+ confidenceScore,
1743
+ compileCheck,
1744
+ flaggedIssues,
1745
+ diffStats,
1746
+ selfRepairAttempts,
1747
+ reportGeneratedAt: (/* @__PURE__ */ new Date()).toISOString()
1748
+ };
1749
+ }
1750
+ async function detectTestSetup(dir) {
1751
+ const pkgContent = await readOptional(path4.join(dir, "package.json"));
1752
+ if (pkgContent !== null) {
1753
+ let pkg2 = {};
1754
+ try {
1755
+ pkg2 = JSON.parse(pkgContent);
1756
+ } catch {
1757
+ return null;
1758
+ }
1759
+ const scripts = pkg2.scripts ?? {};
1760
+ const testScript = typeof scripts.test === "string" ? scripts.test : "";
1761
+ if (testScript === "" || testScript.includes("no test specified"))
1762
+ return null;
1763
+ const pm = await detectNodePackageManager(dir);
1764
+ return {
1765
+ installCommand: [pm, ["install"]],
1766
+ testCommand: [pm, ["test"]],
1767
+ label: "node"
1768
+ };
1769
+ }
1770
+ const pyproject = await readOptional(path4.join(dir, "pyproject.toml"));
1771
+ const hasPytest = pyproject !== null && pyproject.includes("pytest") || await exists(path4.join(dir, "pytest.ini")) || await exists(path4.join(dir, "conftest.py"));
1772
+ if (hasPytest) {
1773
+ return { installCommand: null, testCommand: ["pytest", []], label: "python" };
1774
+ }
1775
+ if (await exists(path4.join(dir, "pubspec.yaml"))) {
1776
+ return { installCommand: null, testCommand: ["flutter", ["test"]], label: "flutter" };
1777
+ }
1778
+ if (await exists(path4.join(dir, "go.mod"))) {
1779
+ return { installCommand: null, testCommand: ["go", ["test", "./..."]], label: "go" };
1780
+ }
1781
+ if (await exists(path4.join(dir, "Cargo.toml"))) {
1782
+ return { installCommand: null, testCommand: ["cargo", ["test"]], label: "rust" };
1783
+ }
1784
+ return null;
1785
+ }
1786
+ async function detectNodePackageManager(dir) {
1787
+ if (await exists(path4.join(dir, "pnpm-lock.yaml")))
1788
+ return "pnpm";
1789
+ if (await exists(path4.join(dir, "yarn.lock")))
1790
+ return "yarn";
1791
+ return "npm";
1792
+ }
1793
+ var STATIC_CHECKS = {
1794
+ typescript: {
1795
+ installCmd: ["npm", ["install"]],
1796
+ checkCmd: ["npx", ["tsc", "--noEmit"]],
1797
+ parse: parseTscOutput
1798
+ }
1799
+ };
1800
+ function staticCheckForTarget(to) {
1801
+ return STATIC_CHECKS[to.trim().toLowerCase()] ?? null;
1802
+ }
1803
+ function parseTscOutput(output) {
1804
+ const located = /^(.+?)[(:](\d+)[,:](\d+)\)?\s*[-:]?\s*error\s+(TS\d+):\s*(.+)$/;
1805
+ const bare = /^error\s+(TS\d+):\s*(.+)$/;
1806
+ const errors = [];
1807
+ for (const rawLine of output.split("\n")) {
1808
+ const line = rawLine.trim();
1809
+ const withLocation = located.exec(line);
1810
+ if (withLocation !== null) {
1811
+ errors.push({
1812
+ file: withLocation[1].trim(),
1813
+ line: Number(withLocation[2]),
1814
+ col: Number(withLocation[3]),
1815
+ code: withLocation[4],
1816
+ message: withLocation[5].trim()
1817
+ });
1818
+ continue;
1819
+ }
1820
+ const withoutLocation = bare.exec(line);
1821
+ if (withoutLocation !== null) {
1822
+ errors.push({ file: "", code: withoutLocation[1], message: withoutLocation[2].trim() });
1823
+ }
1824
+ }
1825
+ return errors;
1826
+ }
1827
+ function formatCompileError(error) {
1828
+ const location = error.file === "" ? "" : `${error.file}${error.line === void 0 ? "" : `:${error.line}:${error.col ?? 0}`} `;
1829
+ return `${location}${error.code === void 0 ? "" : `${error.code}: `}${error.message}`;
1830
+ }
1831
+ async function runStaticCheck(migratedDir, descriptor, runCommand2, options = {}) {
1832
+ const skipped = (skippedReason) => ({
1833
+ ran: false,
1834
+ ok: false,
1835
+ errors: [],
1836
+ skippedReason
1837
+ });
1838
+ try {
1839
+ if (descriptor.installCmd !== void 0 && options.skipInstall !== true && await exists(path4.join(migratedDir, "package.json"))) {
1840
+ const pm = await detectNodePackageManager(migratedDir);
1841
+ const install = await runCommand2(pm, descriptor.installCmd[1], migratedDir);
1842
+ if (install.exitCode !== 0) {
1843
+ return skipped(`dependency install failed (${pm} install exit ${install.exitCode}): ${excerpt(install.stderr || install.stdout)}`);
1844
+ }
1845
+ }
1846
+ const result = await runCommand2(descriptor.checkCmd[0], descriptor.checkCmd[1], migratedDir);
1847
+ if (result.exitCode === 0)
1848
+ return { ran: true, ok: true, errors: [] };
1849
+ const output = `${result.stdout}
1850
+ ${result.stderr}`;
1851
+ const errors = descriptor.parse(output);
1852
+ if (errors.length === 0) {
1853
+ return skipped(`check command could not run (${descriptor.checkCmd[0]} exit ${result.exitCode}): ${excerpt(result.stderr || result.stdout)}`);
1854
+ }
1855
+ return { ran: true, ok: false, errors };
1856
+ } catch (error) {
1857
+ return skipped(`toolchain unavailable: ${excerpt(String(error))}`);
1858
+ }
1859
+ }
1860
+ function excerpt(text) {
1861
+ const trimmed = text.trim();
1862
+ return trimmed.length <= COMMAND_OUTPUT_EXCERPT_CHARS ? trimmed : `${trimmed.slice(0, COMMAND_OUTPUT_EXCERPT_CHARS)}\u2026`;
1863
+ }
1864
+ function describeCompileCheck(check) {
1865
+ if (!check.ran)
1866
+ return `Compile check skipped: ${check.skippedReason ?? "unavailable"}`;
1867
+ if (check.ok)
1868
+ return "Compile check: passed (output compiles cleanly).";
1869
+ return `Compile check FAILED with ${check.errors.length} error(s):
1870
+ ${check.errors.map(formatCompileError).join("\n")}`;
1871
+ }
1872
+ function mapCompileErrorsToEntries(errors, plan) {
1873
+ const byOutputPath = /* @__PURE__ */ new Map();
1874
+ for (const entry of [...plan.filesToModify, ...plan.filesToCreate]) {
1875
+ byOutputPath.set(normalizeRelPath(planOutputPath(entry)), entry);
1876
+ }
1877
+ const targets = /* @__PURE__ */ new Map();
1878
+ for (const error of errors) {
1879
+ if (error.file === "")
1880
+ continue;
1881
+ const entry = byOutputPath.get(normalizeRelPath(error.file));
1882
+ if (entry === void 0)
1883
+ continue;
1884
+ const existing = targets.get(entry.path);
1885
+ if (existing === void 0)
1886
+ targets.set(entry.path, { entry, errors: [error] });
1887
+ else
1888
+ existing.errors.push(error);
1889
+ }
1890
+ return targets;
1891
+ }
1892
+ function normalizeRelPath(relPath) {
1893
+ return relPath.replaceAll("\\", "/").replace(/^\.\//, "");
1894
+ }
1895
+ var REPAIR_CONTEXT_MAX_IMPORTS = 8;
1896
+ var REPAIR_CONTEXT_FILE_MAX_CHARS = 12e3;
1897
+ var RESOLVE_EXTENSIONS = ["", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json"];
1898
+ function parseImportSpecifiers(source) {
1899
+ const patterns = [
1900
+ /\b(?:import|export)\b[^'"`;()]*?\bfrom\s*['"]([^'"]+)['"]/g,
1901
+ /\bimport\s*['"]([^'"]+)['"]/g,
1902
+ /\b(?:require|import)\s*\(\s*['"]([^'"]+)['"]\s*\)/g
1903
+ ];
1904
+ const specifiers = [];
1905
+ for (const pattern of patterns) {
1906
+ for (const match of source.matchAll(pattern)) {
1907
+ if (!specifiers.includes(match[1]))
1908
+ specifiers.push(match[1]);
1909
+ }
1910
+ }
1911
+ return specifiers;
1912
+ }
1913
+ function resolveRelativeImport(fromRelPath, specifier, knownPaths) {
1914
+ if (!specifier.startsWith("."))
1915
+ return null;
1916
+ const base = path4.posix.normalize(path4.posix.join(path4.posix.dirname(normalizeRelPath(fromRelPath)), specifier)).replace(/^\.\//, "");
1917
+ for (const ext of RESOLVE_EXTENSIONS) {
1918
+ if (knownPaths.has(base + ext))
1919
+ return base + ext;
1920
+ }
1921
+ for (const ext of RESOLVE_EXTENSIONS.slice(1)) {
1922
+ if (knownPaths.has(`${base}/index${ext}`))
1923
+ return `${base}/index${ext}`;
1924
+ }
1925
+ const tsForJs = base.replace(/\.([mc]?)js$/, ".$1ts");
1926
+ if (tsForJs !== base && knownPaths.has(tsForJs))
1927
+ return tsForJs;
1928
+ return null;
1929
+ }
1930
+ async function collectRepairContext(manifest, plan, targets, migratedDir) {
1931
+ const bySourcePath = new Map(manifest.files.map((f) => [normalizeRelPath(f.relativePath), f]));
1932
+ const knownPaths = new Set(bySourcePath.keys());
1933
+ const entriesBySource = /* @__PURE__ */ new Map();
1934
+ for (const entry of [...plan.filesToModify, ...plan.filesToCreate]) {
1935
+ entriesBySource.set(normalizeRelPath(entry.path), entry);
1936
+ }
1937
+ const targetPaths = new Set([...targets.keys()].map(normalizeRelPath));
1938
+ const blocks = [];
1939
+ const included = /* @__PURE__ */ new Set();
1940
+ for (const sourcePath of targets.keys()) {
1941
+ const file = bySourcePath.get(normalizeRelPath(sourcePath));
1942
+ if (file === void 0)
1943
+ continue;
1944
+ let count = 0;
1945
+ for (const specifier of parseImportSpecifiers(file.content)) {
1946
+ if (count >= REPAIR_CONTEXT_MAX_IMPORTS)
1947
+ break;
1948
+ const resolved = resolveRelativeImport(file.relativePath, specifier, knownPaths);
1949
+ if (resolved === null || targetPaths.has(resolved) || included.has(resolved))
1950
+ continue;
1951
+ const importedEntry = entriesBySource.get(resolved);
1952
+ const migratedRelPath = importedEntry === void 0 ? resolved : normalizeRelPath(planOutputPath(importedEntry));
1953
+ const migrated = await readOptional(path4.join(migratedDir, migratedRelPath));
1954
+ const content = migrated ?? bySourcePath.get(resolved).content;
1955
+ const displayPath = migrated === null ? resolved : migratedRelPath;
1956
+ included.add(resolved);
1957
+ count++;
1958
+ const body = content.length <= REPAIR_CONTEXT_FILE_MAX_CHARS ? content : `${content.slice(0, REPAIR_CONTEXT_FILE_MAX_CHARS)}
1959
+ \u2026 (truncated)`;
1960
+ blocks.push(`<context_file path="${displayPath}" imported_by="${sourcePath}">
1961
+ ${body}
1962
+ </context_file>`);
1963
+ }
1964
+ }
1965
+ if (blocks.length === 0)
1966
+ return "";
1967
+ return [
1968
+ "",
1969
+ "",
1970
+ "READ-ONLY CONTEXT \u2014 files directly imported by the file(s) being repaired, as they exist in the migrated output. Do NOT return these files; they are not part of this batch. Use them to match imported declarations and signatures EXACTLY. When a compiler error involves a symbol imported from one of these files, fix the CALL SITE in the file you are repairing so it satisfies the imported signature as shown (for example, narrow an unknown value with a typeof check before passing it).",
1971
+ ...blocks
1972
+ ].join("\n");
1973
+ }
1974
+ async function repairCompileErrors(manifest, plan, targets, executionResult, apiKey, adapter, attempt) {
1975
+ const annotate = (entry) => {
1976
+ const target = targets.get(entry.path);
1977
+ if (target === void 0)
1978
+ return entry;
1979
+ return {
1980
+ ...entry,
1981
+ description: `${entry.description} \u2014 REPAIR: the previous transformation of this file does not compile. Fix these exact compiler errors: ${target.errors.map(formatCompileError).join("; ")}`
1982
+ };
1983
+ };
1984
+ const crossFileContext = await collectRepairContext(manifest, plan, targets, executionResult.outputDir);
1985
+ const repairPlan = {
1986
+ ...plan,
1987
+ summary: `${plan.summary}
1988
+
1989
+ REPAIR PASS ${attempt}: a previous transformation produced code that fails to compile. Re-transform the listed files, fixing the compiler errors given per file.${crossFileContext}`,
1990
+ filesToModify: plan.filesToModify.filter((f) => targets.has(f.path)).map(annotate),
1991
+ filesToCreate: plan.filesToCreate.filter((f) => targets.has(f.path)).map(annotate),
1992
+ filesToDelete: []
1993
+ };
1994
+ return executeMigration(manifest, repairPlan, apiKey, () => {
1995
+ }, {
1996
+ adapter,
1997
+ outputDir: executionResult.outputDir,
1998
+ checkpointPath: `${executionResult.checkpointPath}.compile-repair-${attempt}`
1999
+ });
2000
+ }
2001
+ function parseTestOutput(output) {
2002
+ const testsLine = output.split("\n").find((line) => /^\s*Tests[:\s]/.test(line));
2003
+ const source = testsLine ?? output;
2004
+ const passedMatch = /(\d+)\s+pass(?:ed|ing)?/i.exec(source);
2005
+ const failedMatch = /(\d+)\s+fail(?:ed|ing)?/i.exec(source);
2006
+ if (passedMatch === null && failedMatch === null)
2007
+ return null;
2008
+ return {
2009
+ passed: passedMatch === null ? 0 : Number(passedMatch[1]),
2010
+ failed: failedMatch === null ? 0 : Number(failedMatch[1])
2011
+ };
2012
+ }
2013
+ function categorizeIssue(description) {
2014
+ const environmentPattern = /cannot find module|module not found|TS2307|TS2591|not installed|missing dependenc|ERR_MODULE_NOT_FOUND|command not found|ENOENT|types? package/i;
2015
+ return environmentPattern.test(description) ? "environment" : "conversion";
2016
+ }
2017
+ async function runTests(setup, migratedDir, runCommand2) {
2018
+ const result = await runCommand2(setup.testCommand[0], setup.testCommand[1], migratedDir);
2019
+ const testOutput = `${result.stdout}
2020
+ ${result.stderr}`.trim();
2021
+ const counts = parseTestOutput(testOutput);
2022
+ if (counts === null || counts.passed + counts.failed === 0) {
2023
+ return { testPassRate: result.exitCode === 0 ? 1 : 0, testOutput };
2024
+ }
2025
+ return { testPassRate: counts.passed / (counts.passed + counts.failed), testOutput };
2026
+ }
2027
+ async function repairFailedFiles(manifest, plan, failedFiles, executionResult, apiKey, adapter, attempt) {
2028
+ const failedSet = new Set(failedFiles);
2029
+ const repairPlan = {
2030
+ ...plan,
2031
+ filesToModify: plan.filesToModify.filter((f) => failedSet.has(f.path)),
2032
+ filesToCreate: plan.filesToCreate.filter((f) => failedSet.has(f.path)),
2033
+ filesToDelete: []
2034
+ };
2035
+ return executeMigration(manifest, repairPlan, apiKey, () => {
2036
+ }, {
2037
+ adapter,
2038
+ outputDir: executionResult.outputDir,
2039
+ checkpointPath: `${executionResult.checkpointPath}.repair-${attempt}`
2040
+ });
2041
+ }
2042
+ async function selfReview(originalDir, migratedDir, plan, testOutput, adapter, maxReviewFiles) {
2043
+ const samples = await readReviewSamples(originalDir, migratedDir, plan, maxReviewFiles);
2044
+ const { from, to, type } = plan.migrationTarget;
2045
+ const systemPrompt = [
2046
+ `You are a senior software engineer reviewing a completed ${type} migration from "${from}" to "${to}".`,
2047
+ "Review the migration for missed cases, type errors, and breaking changes. Rate your confidence that the migration is correct from 0.0 to 1.0.",
2048
+ 'Categorise every issue: "environment" for setup/dependency problems (missing packages, module resolution), "conversion" for real migration errors.',
2049
+ "Respond with a single raw JSON object and nothing else:",
2050
+ '{ "confidenceScore": number, "issues": [{ "description": string, "severity": "low" | "medium" | "high", "category": "environment" | "conversion", "affectedFiles": string[] }] }'
2051
+ ].join("\n");
2052
+ const userPrompt = [
2053
+ `Migration plan summary:
2054
+ ${plan.summary}`,
2055
+ "",
2056
+ `Test run output (excerpt):
2057
+ ${testOutput.slice(-TEST_OUTPUT_EXCERPT_CHARS) || "(no test suite detected)"}`,
2058
+ "",
2059
+ `Sampled file pairs (${samples.length} of the migrated files):`,
2060
+ ...samples.map((s) => `<original_file path="${s.path}">
2061
+ ${s.original}
2062
+ </original_file>
2063
+ <migrated_file path="${s.outputPath}">
2064
+ ${s.migrated}
2065
+ </migrated_file>`)
2066
+ ].join("\n");
2067
+ const response = await adapter.complete({
2068
+ systemPrompt,
2069
+ userPrompt,
2070
+ maxTokens: VERIFICATION_MAX_OUTPUT_TOKENS
2071
+ });
2072
+ return parseReviewResponse(response.text);
2073
+ }
2074
+ function parseReviewResponse(text) {
2075
+ const start = text.indexOf("{");
2076
+ const end = text.lastIndexOf("}");
2077
+ if (start === -1 || end <= start)
2078
+ return unparseableReview();
2079
+ let raw;
2080
+ try {
2081
+ raw = JSON.parse(text.slice(start, end + 1));
2082
+ } catch {
2083
+ return unparseableReview();
2084
+ }
2085
+ if (typeof raw !== "object" || raw === null)
2086
+ return unparseableReview();
2087
+ const obj = raw;
2088
+ const confidenceScore = typeof obj.confidenceScore === "number" && Number.isFinite(obj.confidenceScore) ? Math.min(1, Math.max(0, obj.confidenceScore)) : 0;
2089
+ const issues = [];
2090
+ if (Array.isArray(obj.issues)) {
2091
+ for (const item of obj.issues) {
2092
+ if (typeof item !== "object" || item === null)
2093
+ continue;
2094
+ const record = item;
2095
+ if (typeof record.description !== "string" || record.description === "")
2096
+ continue;
2097
+ const severity = record.severity;
2098
+ const category = record.category;
2099
+ issues.push({
2100
+ description: record.description,
2101
+ severity: severity === "low" || severity === "medium" || severity === "high" ? severity : "medium",
2102
+ category: category === "environment" || category === "conversion" ? category : categorizeIssue(record.description),
2103
+ affectedFiles: Array.isArray(record.affectedFiles) ? record.affectedFiles.filter((f) => typeof f === "string") : []
2104
+ });
2105
+ }
2106
+ }
2107
+ return { confidenceScore, issues };
2108
+ }
2109
+ function unparseableReview() {
2110
+ return {
2111
+ confidenceScore: 0,
2112
+ issues: [
2113
+ {
2114
+ description: "Self-review response could not be parsed; manual review recommended.",
2115
+ severity: "medium",
2116
+ category: "environment",
2117
+ affectedFiles: []
2118
+ }
2119
+ ]
2120
+ };
2121
+ }
2122
+ async function readReviewSamples(originalDir, migratedDir, plan, maxFiles) {
2123
+ const rank = { high: 0, medium: 1, low: 2 };
2124
+ const candidates = [...plan.filesToModify].sort((a, b) => rank[a.complexity] - rank[b.complexity]);
2125
+ const samples = [];
2126
+ for (const entry of candidates) {
2127
+ if (samples.length >= maxFiles)
2128
+ break;
2129
+ const outputPath = planOutputPath(entry);
2130
+ const migrated = await readOptional(path4.join(migratedDir, outputPath));
2131
+ if (migrated === null)
2132
+ continue;
2133
+ const original = await readOptional(path4.join(originalDir, entry.path));
2134
+ samples.push({ path: entry.path, outputPath, original: original ?? "(new file)", migrated });
2135
+ }
2136
+ return samples;
2137
+ }
2138
+ async function computeDiffStats(originalDir, migratedDir, plan) {
2139
+ const pairs = [
2140
+ ...plan.filesToModify.map((f) => ({ source: f.path, output: planOutputPath(f) })),
2141
+ ...plan.filesToCreate.map((f) => ({ source: f.path, output: f.path }))
2142
+ ];
2143
+ const stats = [];
2144
+ for (const pair of pairs) {
2145
+ const migrated = await readOptional(path4.join(migratedDir, pair.output));
2146
+ if (migrated === null)
2147
+ continue;
2148
+ const original = await readOptional(path4.join(originalDir, pair.source)) ?? "";
2149
+ let linesAdded = 0;
2150
+ let linesRemoved = 0;
2151
+ for (const part of diffLines(original, migrated)) {
2152
+ if (part.added)
2153
+ linesAdded += part.count ?? 0;
2154
+ else if (part.removed)
2155
+ linesRemoved += part.count ?? 0;
2156
+ }
2157
+ if (linesAdded + linesRemoved > 0)
2158
+ stats.push({ path: pair.output, linesAdded, linesRemoved });
2159
+ }
2160
+ return {
2161
+ totalFilesChanged: stats.length,
2162
+ linesAdded: stats.reduce((sum, s) => sum + s.linesAdded, 0),
2163
+ linesRemoved: stats.reduce((sum, s) => sum + s.linesRemoved, 0),
2164
+ largestDiffs: [...stats].sort((a, b) => b.linesAdded + b.linesRemoved - (a.linesAdded + a.linesRemoved)).slice(0, 5)
2165
+ };
2166
+ }
2167
+ function defaultCommandRunner(timeoutMs = 10 * 60 * 1e3) {
2168
+ return (command, args, cwd) => new Promise((resolve) => {
2169
+ const child = spawn(command, args, { cwd, shell: false });
2170
+ let stdout = "";
2171
+ let stderr = "";
2172
+ const timer = setTimeout(() => {
2173
+ child.kill("SIGKILL");
2174
+ stderr += `
2175
+ (command timed out after ${timeoutMs}ms)`;
2176
+ }, timeoutMs);
2177
+ child.stdout.on("data", (data) => stdout += data.toString());
2178
+ child.stderr.on("data", (data) => stderr += data.toString());
2179
+ child.on("error", (error) => {
2180
+ clearTimeout(timer);
2181
+ resolve({ exitCode: 127, stdout, stderr: `${stderr}
2182
+ ${String(error)}` });
2183
+ });
2184
+ child.on("close", (code) => {
2185
+ clearTimeout(timer);
2186
+ resolve({ exitCode: code ?? 1, stdout, stderr });
2187
+ });
2188
+ });
2189
+ }
2190
+ async function readOptional(filePath) {
2191
+ try {
2192
+ return await fs4.readFile(filePath, "utf8");
2193
+ } catch {
2194
+ return null;
2195
+ }
2196
+ }
2197
+ async function exists(filePath) {
2198
+ try {
2199
+ await fs4.stat(filePath);
2200
+ return true;
2201
+ } catch {
2202
+ return false;
2203
+ }
2204
+ }
2205
+
2206
+ // ../engine/dist/orchestrator/index.js
2207
+ var MODEL_PRICING = {
2208
+ "claude-sonnet-4-6": { input: 3, output: 15 },
2209
+ "claude-opus-4-8": { input: 15, output: 75 }
2210
+ };
2211
+ var DEFAULT_PRICING = MODEL_PRICING["claude-opus-4-8"];
2212
+
2213
+ // src/targets.ts
2214
+ var KNOWN_TARGETS = {
2215
+ "javascript-to-typescript": { from: "javascript", to: "typescript", type: "language" },
2216
+ "flow-to-typescript": { from: "flow", to: "typescript", type: "language" },
2217
+ "python-2-to-3": { from: "python 2", to: "python 3", type: "language" },
2218
+ "react-17-to-19": { from: "react 17", to: "react 19", type: "framework" },
2219
+ "express-to-hono": { from: "express", to: "hono", type: "framework" },
2220
+ "vue-2-to-3": { from: "vue 2", to: "vue 3", type: "framework" }
2221
+ };
2222
+ var LANGUAGES = /* @__PURE__ */ new Set([
2223
+ "javascript",
2224
+ "typescript",
2225
+ "flow",
2226
+ "python",
2227
+ "go",
2228
+ "rust",
2229
+ "dart",
2230
+ "php",
2231
+ "ruby",
2232
+ "java"
2233
+ ]);
2234
+ function parseTarget(value) {
2235
+ const known = KNOWN_TARGETS[value];
2236
+ if (known !== void 0) return known;
2237
+ const separator = value.indexOf("-to-");
2238
+ if (separator <= 0 || separator + 4 >= value.length) {
2239
+ throw new Error(
2240
+ `Invalid migration target "${value}". Use <from>-to-<to>, e.g. javascript-to-typescript.`
2241
+ );
2242
+ }
2243
+ const from = value.slice(0, separator);
2244
+ const to = value.slice(separator + 4);
2245
+ return {
2246
+ from,
2247
+ to,
2248
+ type: LANGUAGES.has(from) && LANGUAGES.has(to) ? "language" : "framework"
2249
+ };
2250
+ }
2251
+ function suggestTargets(stack) {
2252
+ const suggestions = [];
2253
+ if (stack.primaryLanguage === "javascript") suggestions.push("javascript-to-typescript");
2254
+ if (stack.frameworks.includes("react")) suggestions.push("react-17-to-19");
2255
+ if (stack.frameworks.includes("express")) suggestions.push("express-to-hono");
2256
+ if (stack.frameworks.includes("vue")) suggestions.push("vue-2-to-3");
2257
+ return suggestions;
2258
+ }
2259
+
2260
+ // src/commands/init.ts
2261
+ async function initCommand() {
2262
+ const rootPath = process.cwd();
2263
+ const spinner = ora("Analyzing codebase\u2026").start();
2264
+ const manifest = await readCodebase(rootPath, { ignorePaths: ["migrated"] });
2265
+ spinner.succeed(
2266
+ `Analyzed ${manifest.files.length} files (${manifest.totalLines.toLocaleString("en-US")} lines)`
2267
+ );
2268
+ const stack = manifest.detectedStack;
2269
+ console.log(`
2270
+ ${chalk2.bold("Detected stack")}`);
2271
+ console.log(` Language: ${chalk2.cyan(stack.primaryLanguage)}`);
2272
+ console.log(
2273
+ ` Frameworks: ${stack.frameworks.length > 0 ? stack.frameworks.join(", ") : chalk2.dim("none detected")}`
2274
+ );
2275
+ console.log(` Build tool: ${stack.buildTool ?? chalk2.dim("none detected")}`);
2276
+ console.log(` Test framework: ${stack.testFramework ?? chalk2.dim("none detected")}`);
2277
+ console.log(` Package manager: ${stack.packageManager ?? chalk2.dim("none detected")}`);
2278
+ const suggestions = suggestTargets(stack);
2279
+ console.log(`
2280
+ ${chalk2.bold("Suggested migration targets")}`);
2281
+ if (suggestions.length === 0) {
2282
+ console.log(chalk2.dim(" No specific suggestions \u2014 any <from>-to-<to> target works."));
2283
+ } else {
2284
+ for (const target of suggestions) console.log(` ${chalk2.green("\u2192")} ${target}`);
2285
+ }
2286
+ const configPath = path5.join(rootPath, "automigrate.config.json");
2287
+ try {
2288
+ await fs5.readFile(configPath, "utf8");
2289
+ console.log(chalk2.dim(`
2290
+ automigrate.config.json already exists \u2014 left unchanged.`));
2291
+ } catch {
2292
+ const config2 = { target: suggestions[0] ?? "", ignorePaths: [] };
2293
+ await fs5.writeFile(configPath, `${JSON.stringify(config2, null, 2)}
2294
+ `, "utf8");
2295
+ console.log(`
2296
+ Wrote ${chalk2.cyan("automigrate.config.json")}`);
2297
+ }
2298
+ console.log(
2299
+ chalk2.dim("Next: automigrate run --dry-run (review the plan), then automigrate run.")
2300
+ );
2301
+ }
2302
+
2303
+ // src/commands/rollback.ts
2304
+ import fs7 from "fs/promises";
2305
+ import path7 from "path";
2306
+ import chalk3 from "chalk";
2307
+
2308
+ // src/workspace.ts
2309
+ import fs6 from "fs/promises";
2310
+ import path6 from "path";
2311
+ async function backupFiles(rootPath, relPaths, destDir) {
2312
+ let count = 0;
2313
+ for (const rel of relPaths) {
2314
+ let content;
2315
+ try {
2316
+ content = Buffer.from(await fs6.readFile(path6.join(rootPath, rel)));
2317
+ } catch {
2318
+ continue;
2319
+ }
2320
+ const target = path6.join(destDir, rel);
2321
+ await fs6.mkdir(path6.dirname(target), { recursive: true });
2322
+ await fs6.writeFile(target, content);
2323
+ count++;
2324
+ }
2325
+ return count;
2326
+ }
2327
+ async function restoreBackup(backupRoot, rootPath) {
2328
+ let count = 0;
2329
+ async function walk(dir) {
2330
+ let entries;
2331
+ try {
2332
+ entries = await fs6.readdir(dir, { withFileTypes: true });
2333
+ } catch {
2334
+ return;
2335
+ }
2336
+ for (const entry of entries) {
2337
+ const abs = path6.join(dir, entry.name);
2338
+ if (entry.isDirectory()) {
2339
+ await walk(abs);
2340
+ continue;
2341
+ }
2342
+ if (!entry.isFile()) continue;
2343
+ const rel = path6.relative(backupRoot, abs);
2344
+ const target = path6.join(rootPath, rel);
2345
+ await fs6.mkdir(path6.dirname(target), { recursive: true });
2346
+ await fs6.writeFile(target, Buffer.from(await fs6.readFile(abs)));
2347
+ count++;
2348
+ }
2349
+ }
2350
+ await walk(backupRoot);
2351
+ return count;
2352
+ }
2353
+ function copyExclusions(plan) {
2354
+ const exclude = new Set(plan.filesToDelete);
2355
+ for (const entry of plan.filesToModify) {
2356
+ if (planOutputPath(entry) !== entry.path) exclude.add(entry.path);
2357
+ }
2358
+ return exclude;
2359
+ }
2360
+ async function copyUnchangedFiles(manifest, outputDir, exclude) {
2361
+ let count = 0;
2362
+ for (const file of manifest.files) {
2363
+ if (exclude.has(file.relativePath)) continue;
2364
+ const target = path6.join(outputDir, file.relativePath);
2365
+ if (await exists2(target)) continue;
2366
+ await fs6.mkdir(path6.dirname(target), { recursive: true });
2367
+ await fs6.writeFile(target, file.content, "utf8");
2368
+ count++;
2369
+ }
2370
+ return count;
2371
+ }
2372
+ async function removeDir(dirPath) {
2373
+ await fs6.rm(dirPath, { recursive: true, force: true });
2374
+ }
2375
+ async function exists2(filePath) {
2376
+ try {
2377
+ await fs6.stat(filePath);
2378
+ return true;
2379
+ } catch {
2380
+ return false;
2381
+ }
2382
+ }
2383
+
2384
+ // src/commands/rollback.ts
2385
+ async function rollbackCommand(options) {
2386
+ const jobs = await readJobs();
2387
+ const job = options.job !== void 0 ? jobs.find((j) => j.id === options.job) : [...jobs].reverse().find((j) => j.status === "complete" || j.status === "failed");
2388
+ if (job === void 0) {
2389
+ console.error(
2390
+ chalk3.red(
2391
+ options.job !== void 0 ? `Job ${options.job} not found.` : "No job to roll back."
2392
+ )
2393
+ );
2394
+ process.exitCode = 1;
2395
+ return;
2396
+ }
2397
+ const restored = await restoreBackup(backupDir(job.id), job.rootPath);
2398
+ let removed = 0;
2399
+ for (const created of job.createdFiles ?? []) {
2400
+ try {
2401
+ await fs7.rm(path7.join(job.rootPath, created));
2402
+ removed++;
2403
+ } catch {
2404
+ }
2405
+ }
2406
+ await removeDir(path7.join(job.rootPath, "migrated"));
2407
+ await fs7.rm(path7.join(job.rootPath, "output.patch"), { force: true });
2408
+ job.status = "rolled_back";
2409
+ await saveJob(job);
2410
+ console.log(
2411
+ `${chalk3.green("Rolled back")} job ${job.id}: restored ${restored} files${removed > 0 ? `, removed ${removed} created files` : ""}, removed ${path7.join(job.rootPath, "migrated")}`
2412
+ );
2413
+ if (restored === 0 && removed === 0) {
2414
+ console.log(
2415
+ chalk3.dim("No files needed restoring (the migration never modified originals in place).")
2416
+ );
2417
+ }
2418
+ }
2419
+
2420
+ // src/commands/run.ts
2421
+ import fs9 from "fs/promises";
2422
+ import path9 from "path";
2423
+ import chalk4 from "chalk";
2424
+ import cliProgress from "cli-progress";
2425
+ import ora2 from "ora";
2426
+
2427
+ // src/buildInfo.ts
2428
+ var CLI_VERSION = "0.1.2";
2429
+ var CLI_BUILD_ID = "2026-07-11T08:07:48.185Z";
2430
+ function buildIdentity() {
2431
+ return `automigrate v${CLI_VERSION} (build ${CLI_BUILD_ID})`;
2432
+ }
2433
+
2434
+ // src/patch.ts
2435
+ import fs8 from "fs/promises";
2436
+ import path8 from "path";
2437
+
2438
+ // ../../node_modules/.pnpm/diff@8.0.4/node_modules/diff/libesm/diff/base.js
2439
+ var Diff2 = class {
2440
+ diff(oldStr, newStr, options = {}) {
2441
+ let callback;
2442
+ if (typeof options === "function") {
2443
+ callback = options;
2444
+ options = {};
2445
+ } else if ("callback" in options) {
2446
+ callback = options.callback;
2447
+ }
2448
+ const oldString = this.castInput(oldStr, options);
2449
+ const newString = this.castInput(newStr, options);
2450
+ const oldTokens = this.removeEmpty(this.tokenize(oldString, options));
2451
+ const newTokens = this.removeEmpty(this.tokenize(newString, options));
2452
+ return this.diffWithOptionsObj(oldTokens, newTokens, options, callback);
2453
+ }
2454
+ diffWithOptionsObj(oldTokens, newTokens, options, callback) {
2455
+ var _a;
2456
+ const done = (value) => {
2457
+ value = this.postProcess(value, options);
2458
+ if (callback) {
2459
+ setTimeout(function() {
2460
+ callback(value);
2461
+ }, 0);
2462
+ return void 0;
2463
+ } else {
2464
+ return value;
2465
+ }
2466
+ };
2467
+ const newLen = newTokens.length, oldLen = oldTokens.length;
2468
+ let editLength = 1;
2469
+ let maxEditLength = newLen + oldLen;
2470
+ if (options.maxEditLength != null) {
2471
+ maxEditLength = Math.min(maxEditLength, options.maxEditLength);
2472
+ }
2473
+ const maxExecutionTime = (_a = options.timeout) !== null && _a !== void 0 ? _a : Infinity;
2474
+ const abortAfterTimestamp = Date.now() + maxExecutionTime;
2475
+ const bestPath = [{ oldPos: -1, lastComponent: void 0 }];
2476
+ let newPos = this.extractCommon(bestPath[0], newTokens, oldTokens, 0, options);
2477
+ if (bestPath[0].oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
2478
+ return done(this.buildValues(bestPath[0].lastComponent, newTokens, oldTokens));
2479
+ }
2480
+ let minDiagonalToConsider = -Infinity, maxDiagonalToConsider = Infinity;
2481
+ const execEditLength = () => {
2482
+ for (let diagonalPath = Math.max(minDiagonalToConsider, -editLength); diagonalPath <= Math.min(maxDiagonalToConsider, editLength); diagonalPath += 2) {
2483
+ let basePath;
2484
+ const removePath = bestPath[diagonalPath - 1], addPath = bestPath[diagonalPath + 1];
2485
+ if (removePath) {
2486
+ bestPath[diagonalPath - 1] = void 0;
2487
+ }
2488
+ let canAdd = false;
2489
+ if (addPath) {
2490
+ const addPathNewPos = addPath.oldPos - diagonalPath;
2491
+ canAdd = addPath && 0 <= addPathNewPos && addPathNewPos < newLen;
2492
+ }
2493
+ const canRemove = removePath && removePath.oldPos + 1 < oldLen;
2494
+ if (!canAdd && !canRemove) {
2495
+ bestPath[diagonalPath] = void 0;
2496
+ continue;
2497
+ }
2498
+ if (!canRemove || canAdd && removePath.oldPos < addPath.oldPos) {
2499
+ basePath = this.addToPath(addPath, true, false, 0, options);
2500
+ } else {
2501
+ basePath = this.addToPath(removePath, false, true, 1, options);
2502
+ }
2503
+ newPos = this.extractCommon(basePath, newTokens, oldTokens, diagonalPath, options);
2504
+ if (basePath.oldPos + 1 >= oldLen && newPos + 1 >= newLen) {
2505
+ return done(this.buildValues(basePath.lastComponent, newTokens, oldTokens)) || true;
2506
+ } else {
2507
+ bestPath[diagonalPath] = basePath;
2508
+ if (basePath.oldPos + 1 >= oldLen) {
2509
+ maxDiagonalToConsider = Math.min(maxDiagonalToConsider, diagonalPath - 1);
2510
+ }
2511
+ if (newPos + 1 >= newLen) {
2512
+ minDiagonalToConsider = Math.max(minDiagonalToConsider, diagonalPath + 1);
2513
+ }
2514
+ }
2515
+ }
2516
+ editLength++;
2517
+ };
2518
+ if (callback) {
2519
+ (function exec() {
2520
+ setTimeout(function() {
2521
+ if (editLength > maxEditLength || Date.now() > abortAfterTimestamp) {
2522
+ return callback(void 0);
2523
+ }
2524
+ if (!execEditLength()) {
2525
+ exec();
2526
+ }
2527
+ }, 0);
2528
+ })();
2529
+ } else {
2530
+ while (editLength <= maxEditLength && Date.now() <= abortAfterTimestamp) {
2531
+ const ret = execEditLength();
2532
+ if (ret) {
2533
+ return ret;
2534
+ }
2535
+ }
2536
+ }
2537
+ }
2538
+ addToPath(path10, added, removed, oldPosInc, options) {
2539
+ const last = path10.lastComponent;
2540
+ if (last && !options.oneChangePerToken && last.added === added && last.removed === removed) {
2541
+ return {
2542
+ oldPos: path10.oldPos + oldPosInc,
2543
+ lastComponent: { count: last.count + 1, added, removed, previousComponent: last.previousComponent }
2544
+ };
2545
+ } else {
2546
+ return {
2547
+ oldPos: path10.oldPos + oldPosInc,
2548
+ lastComponent: { count: 1, added, removed, previousComponent: last }
2549
+ };
2550
+ }
2551
+ }
2552
+ extractCommon(basePath, newTokens, oldTokens, diagonalPath, options) {
2553
+ const newLen = newTokens.length, oldLen = oldTokens.length;
2554
+ let oldPos = basePath.oldPos, newPos = oldPos - diagonalPath, commonCount = 0;
2555
+ while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(oldTokens[oldPos + 1], newTokens[newPos + 1], options)) {
2556
+ newPos++;
2557
+ oldPos++;
2558
+ commonCount++;
2559
+ if (options.oneChangePerToken) {
2560
+ basePath.lastComponent = { count: 1, previousComponent: basePath.lastComponent, added: false, removed: false };
2561
+ }
2562
+ }
2563
+ if (commonCount && !options.oneChangePerToken) {
2564
+ basePath.lastComponent = { count: commonCount, previousComponent: basePath.lastComponent, added: false, removed: false };
2565
+ }
2566
+ basePath.oldPos = oldPos;
2567
+ return newPos;
2568
+ }
2569
+ equals(left, right, options) {
2570
+ if (options.comparator) {
2571
+ return options.comparator(left, right);
2572
+ } else {
2573
+ return left === right || !!options.ignoreCase && left.toLowerCase() === right.toLowerCase();
2574
+ }
2575
+ }
2576
+ removeEmpty(array) {
2577
+ const ret = [];
2578
+ for (let i = 0; i < array.length; i++) {
2579
+ if (array[i]) {
2580
+ ret.push(array[i]);
2581
+ }
2582
+ }
2583
+ return ret;
2584
+ }
2585
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2586
+ castInput(value, options) {
2587
+ return value;
2588
+ }
2589
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
2590
+ tokenize(value, options) {
2591
+ return Array.from(value);
2592
+ }
2593
+ join(chars) {
2594
+ return chars.join("");
2595
+ }
2596
+ postProcess(changeObjects, options) {
2597
+ return changeObjects;
2598
+ }
2599
+ get useLongestToken() {
2600
+ return false;
2601
+ }
2602
+ buildValues(lastComponent, newTokens, oldTokens) {
2603
+ const components = [];
2604
+ let nextComponent;
2605
+ while (lastComponent) {
2606
+ components.push(lastComponent);
2607
+ nextComponent = lastComponent.previousComponent;
2608
+ delete lastComponent.previousComponent;
2609
+ lastComponent = nextComponent;
2610
+ }
2611
+ components.reverse();
2612
+ const componentLen = components.length;
2613
+ let componentPos = 0, newPos = 0, oldPos = 0;
2614
+ for (; componentPos < componentLen; componentPos++) {
2615
+ const component = components[componentPos];
2616
+ if (!component.removed) {
2617
+ if (!component.added && this.useLongestToken) {
2618
+ let value = newTokens.slice(newPos, newPos + component.count);
2619
+ value = value.map(function(value2, i) {
2620
+ const oldValue = oldTokens[oldPos + i];
2621
+ return oldValue.length > value2.length ? oldValue : value2;
2622
+ });
2623
+ component.value = this.join(value);
2624
+ } else {
2625
+ component.value = this.join(newTokens.slice(newPos, newPos + component.count));
2626
+ }
2627
+ newPos += component.count;
2628
+ if (!component.added) {
2629
+ oldPos += component.count;
2630
+ }
2631
+ } else {
2632
+ component.value = this.join(oldTokens.slice(oldPos, oldPos + component.count));
2633
+ oldPos += component.count;
2634
+ }
2635
+ }
2636
+ return components;
2637
+ }
2638
+ };
2639
+
2640
+ // ../../node_modules/.pnpm/diff@8.0.4/node_modules/diff/libesm/diff/line.js
2641
+ var LineDiff2 = class extends Diff2 {
2642
+ constructor() {
2643
+ super(...arguments);
2644
+ this.tokenize = tokenize2;
2645
+ }
2646
+ equals(left, right, options) {
2647
+ if (options.ignoreWhitespace) {
2648
+ if (!options.newlineIsToken || !left.includes("\n")) {
2649
+ left = left.trim();
2650
+ }
2651
+ if (!options.newlineIsToken || !right.includes("\n")) {
2652
+ right = right.trim();
2653
+ }
2654
+ } else if (options.ignoreNewlineAtEof && !options.newlineIsToken) {
2655
+ if (left.endsWith("\n")) {
2656
+ left = left.slice(0, -1);
2657
+ }
2658
+ if (right.endsWith("\n")) {
2659
+ right = right.slice(0, -1);
2660
+ }
2661
+ }
2662
+ return super.equals(left, right, options);
2663
+ }
2664
+ };
2665
+ var lineDiff2 = new LineDiff2();
2666
+ function diffLines2(oldStr, newStr, options) {
2667
+ return lineDiff2.diff(oldStr, newStr, options);
2668
+ }
2669
+ function tokenize2(value, options) {
2670
+ if (options.stripTrailingCr) {
2671
+ value = value.replace(/\r\n/g, "\n");
2672
+ }
2673
+ const retLines = [], linesAndNewlines = value.split(/(\n|\r\n)/);
2674
+ if (!linesAndNewlines[linesAndNewlines.length - 1]) {
2675
+ linesAndNewlines.pop();
2676
+ }
2677
+ for (let i = 0; i < linesAndNewlines.length; i++) {
2678
+ const line = linesAndNewlines[i];
2679
+ if (i % 2 && !options.newlineIsToken) {
2680
+ retLines[retLines.length - 1] += line;
2681
+ } else {
2682
+ retLines.push(line);
2683
+ }
2684
+ }
2685
+ return retLines;
2686
+ }
2687
+
2688
+ // ../../node_modules/.pnpm/diff@8.0.4/node_modules/diff/libesm/patch/create.js
2689
+ function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) {
2690
+ let optionsObj;
2691
+ if (!options) {
2692
+ optionsObj = {};
2693
+ } else if (typeof options === "function") {
2694
+ optionsObj = { callback: options };
2695
+ } else {
2696
+ optionsObj = options;
2697
+ }
2698
+ if (typeof optionsObj.context === "undefined") {
2699
+ optionsObj.context = 4;
2700
+ }
2701
+ const context = optionsObj.context;
2702
+ if (optionsObj.newlineIsToken) {
2703
+ throw new Error("newlineIsToken may not be used with patch-generation functions, only with diffing functions");
2704
+ }
2705
+ if (!optionsObj.callback) {
2706
+ return diffLinesResultToPatch(diffLines2(oldStr, newStr, optionsObj));
2707
+ } else {
2708
+ const { callback } = optionsObj;
2709
+ diffLines2(oldStr, newStr, Object.assign(Object.assign({}, optionsObj), { callback: (diff) => {
2710
+ const patch = diffLinesResultToPatch(diff);
2711
+ callback(patch);
2712
+ } }));
2713
+ }
2714
+ function diffLinesResultToPatch(diff) {
2715
+ if (!diff) {
2716
+ return;
2717
+ }
2718
+ diff.push({ value: "", lines: [] });
2719
+ function contextLines(lines) {
2720
+ return lines.map(function(entry) {
2721
+ return " " + entry;
2722
+ });
2723
+ }
2724
+ const hunks = [];
2725
+ let oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1;
2726
+ for (let i = 0; i < diff.length; i++) {
2727
+ const current = diff[i], lines = current.lines || splitLines(current.value);
2728
+ current.lines = lines;
2729
+ if (current.added || current.removed) {
2730
+ if (!oldRangeStart) {
2731
+ const prev = diff[i - 1];
2732
+ oldRangeStart = oldLine;
2733
+ newRangeStart = newLine;
2734
+ if (prev) {
2735
+ curRange = context > 0 ? contextLines(prev.lines.slice(-context)) : [];
2736
+ oldRangeStart -= curRange.length;
2737
+ newRangeStart -= curRange.length;
2738
+ }
2739
+ }
2740
+ for (const line of lines) {
2741
+ curRange.push((current.added ? "+" : "-") + line);
2742
+ }
2743
+ if (current.added) {
2744
+ newLine += lines.length;
2745
+ } else {
2746
+ oldLine += lines.length;
2747
+ }
2748
+ } else {
2749
+ if (oldRangeStart) {
2750
+ if (lines.length <= context * 2 && i < diff.length - 2) {
2751
+ for (const line of contextLines(lines)) {
2752
+ curRange.push(line);
2753
+ }
2754
+ } else {
2755
+ const contextSize = Math.min(lines.length, context);
2756
+ for (const line of contextLines(lines.slice(0, contextSize))) {
2757
+ curRange.push(line);
2758
+ }
2759
+ const hunk = {
2760
+ oldStart: oldRangeStart,
2761
+ oldLines: oldLine - oldRangeStart + contextSize,
2762
+ newStart: newRangeStart,
2763
+ newLines: newLine - newRangeStart + contextSize,
2764
+ lines: curRange
2765
+ };
2766
+ hunks.push(hunk);
2767
+ oldRangeStart = 0;
2768
+ newRangeStart = 0;
2769
+ curRange = [];
2770
+ }
2771
+ }
2772
+ oldLine += lines.length;
2773
+ newLine += lines.length;
2774
+ }
2775
+ }
2776
+ for (const hunk of hunks) {
2777
+ for (let i = 0; i < hunk.lines.length; i++) {
2778
+ if (hunk.lines[i].endsWith("\n")) {
2779
+ hunk.lines[i] = hunk.lines[i].slice(0, -1);
2780
+ } else {
2781
+ hunk.lines.splice(i + 1, 0, "\");
2782
+ i++;
2783
+ }
2784
+ }
2785
+ }
2786
+ return {
2787
+ oldFileName,
2788
+ newFileName,
2789
+ oldHeader,
2790
+ newHeader,
2791
+ hunks
2792
+ };
2793
+ }
2794
+ }
2795
+ function splitLines(text) {
2796
+ const hasTrailingNl = text.endsWith("\n");
2797
+ const result = text.split("\n").map((line) => line + "\n");
2798
+ if (hasTrailingNl) {
2799
+ result.pop();
2800
+ } else {
2801
+ result.push(result.pop().slice(0, -1));
2802
+ }
2803
+ return result;
2804
+ }
2805
+
2806
+ // src/patch.ts
2807
+ function buildPatch(entries) {
2808
+ let output = "";
2809
+ for (const entry of normalizeEntries(entries)) {
2810
+ const oldName = entry.oldContent === null ? "/dev/null" : `a/${entry.path}`;
2811
+ const newName = entry.newContent === null ? "/dev/null" : `b/${entry.path}`;
2812
+ const patch = structuredPatch(
2813
+ oldName,
2814
+ newName,
2815
+ entry.oldContent ?? "",
2816
+ entry.newContent ?? "",
2817
+ void 0,
2818
+ void 0,
2819
+ { context: 3 }
2820
+ );
2821
+ if (patch.hunks.length === 0) continue;
2822
+ output += `diff --git a/${entry.path} b/${entry.path}
2823
+ `;
2824
+ if (entry.oldContent === null) output += "new file mode 100644\n";
2825
+ if (entry.newContent === null) output += "deleted file mode 100644\n";
2826
+ output += `--- ${oldName}
2827
+ +++ ${newName}
2828
+ `;
2829
+ for (const hunk of patch.hunks) {
2830
+ output += `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@
2831
+ `;
2832
+ output += `${hunk.lines.join("\n")}
2833
+ `;
2834
+ }
2835
+ }
2836
+ return output;
2837
+ }
2838
+ function normalizeEntries(entries) {
2839
+ const normalized = [];
2840
+ for (const entry of entries) {
2841
+ if (entry.oldPath === void 0 || entry.oldPath === entry.path) {
2842
+ normalized.push({
2843
+ path: entry.path,
2844
+ oldContent: entry.oldContent,
2845
+ newContent: entry.newContent
2846
+ });
2847
+ continue;
2848
+ }
2849
+ if (entry.oldContent !== null) {
2850
+ normalized.push({ path: entry.oldPath, oldContent: entry.oldContent, newContent: null });
2851
+ }
2852
+ if (entry.newContent !== null) {
2853
+ normalized.push({ path: entry.path, oldContent: null, newContent: entry.newContent });
2854
+ }
2855
+ }
2856
+ return normalized;
2857
+ }
2858
+ async function collectPatchEntries(manifest, plan, migratedDir) {
2859
+ const originals = new Map(manifest.files.map((f) => [f.relativePath, f.content]));
2860
+ const entries = [];
2861
+ for (const file of plan.filesToModify) {
2862
+ const outputPath = planOutputPath(file);
2863
+ const newContent = await readOptional2(path8.join(migratedDir, outputPath));
2864
+ if (newContent === null) continue;
2865
+ entries.push({
2866
+ path: outputPath,
2867
+ ...outputPath !== file.path ? { oldPath: file.path } : {},
2868
+ oldContent: originals.get(file.path) ?? null,
2869
+ newContent
2870
+ });
2871
+ }
2872
+ for (const file of plan.filesToCreate) {
2873
+ const newContent = await readOptional2(path8.join(migratedDir, file.path));
2874
+ if (newContent === null) continue;
2875
+ entries.push({ path: file.path, oldContent: null, newContent });
2876
+ }
2877
+ for (const filePath of plan.filesToDelete) {
2878
+ const oldContent = originals.get(filePath);
2879
+ if (oldContent === void 0) continue;
2880
+ entries.push({ path: filePath, oldContent, newContent: null });
2881
+ }
2882
+ return entries;
2883
+ }
2884
+ async function readOptional2(filePath) {
2885
+ try {
2886
+ return await fs8.readFile(filePath, "utf8");
2887
+ } catch {
2888
+ return null;
2889
+ }
2890
+ }
2891
+
2892
+ // src/commands/run.ts
2893
+ async function runCommand(options) {
2894
+ console.log(chalk4.dim(buildIdentity()));
2895
+ const rootPath = process.cwd();
2896
+ const config2 = await readConfig();
2897
+ const apiKey = config2.apiKey ?? process.env.ANTHROPIC_API_KEY;
2898
+ if (apiKey === void 0 || apiKey === "") {
2899
+ console.error(
2900
+ chalk4.red("No API key configured.") + " Run `automigrate config set-key <key>` or set ANTHROPIC_API_KEY."
2901
+ );
2902
+ process.exitCode = 1;
2903
+ return;
2904
+ }
2905
+ const projectConfig = await readProjectConfig(rootPath);
2906
+ const targetSpec = options.target ?? projectConfig?.target;
2907
+ if (targetSpec === void 0 || targetSpec === "") {
2908
+ console.error(
2909
+ chalk4.red("No migration target.") + " Pass --target <from-to> or run `automigrate init` first."
2910
+ );
2911
+ process.exitCode = 1;
2912
+ return;
2913
+ }
2914
+ const target = parseTarget(targetSpec);
2915
+ const ingestSpinner = ora2("Reading codebase\u2026").start();
2916
+ const manifest = await readCodebase(rootPath, {
2917
+ ignorePaths: [...config2.ignorePaths ?? [], ...projectConfig?.ignorePaths ?? [], "migrated"]
2918
+ });
2919
+ ingestSpinner.succeed(
2920
+ `Read ${manifest.files.length} files (${manifest.totalLines.toLocaleString("en-US")} lines, ~${manifest.totalTokens.toLocaleString("en-US")} tokens)`
2921
+ );
2922
+ if (options.resume === true) {
2923
+ await resumeRun(rootPath, manifest, apiKey, targetSpec);
2924
+ return;
2925
+ }
2926
+ const planSpinner = ora2("Planning migration (Pass 1)\u2026").start();
2927
+ const plan = await planMigration(manifest, target, apiKey, {
2928
+ onProgress: (p) => {
2929
+ planSpinner.text = `Planning migration (Pass 1)\u2026 batch ${p.batchIndex + 1}/${p.batchCount}, ${p.charsStreamed.toLocaleString("en-US")} chars`;
2930
+ }
2931
+ });
2932
+ planSpinner.succeed(
2933
+ `Plan ready: ${plan.filesToModify.length} to modify, ${plan.filesToCreate.length} to create, ${plan.filesToDelete.length} to delete (risk: ${plan.riskLevel}, effort: ${plan.estimatedEffort})`
2934
+ );
2935
+ if (options.dryRun === true) {
2936
+ printPlan(plan);
2937
+ return;
2938
+ }
2939
+ const jobId = newJobId();
2940
+ const job = {
2941
+ id: jobId,
2942
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
2943
+ rootPath,
2944
+ target: targetSpec,
2945
+ status: "executing",
2946
+ filesChanged: 0,
2947
+ linesAdded: 0,
2948
+ linesRemoved: 0,
2949
+ tokensUsed: 0,
2950
+ testPassRate: null,
2951
+ confidenceScore: null
2952
+ };
2953
+ await acquireLock(rootPath);
2954
+ try {
2955
+ const affected = [...plan.filesToModify.map((f) => f.path), ...plan.filesToDelete];
2956
+ const backed = await backupFiles(rootPath, affected, backupDir(jobId));
2957
+ console.log(chalk4.dim(`Backed up ${backed} files to ${backupDir(jobId)}`));
2958
+ await saveJob(job);
2959
+ const result = await runExecutionWithProgress(
2960
+ () => executeMigration(manifest, plan, apiKey, progressBarCallback(), {
2961
+ outputDir: path9.join(rootPath, "migrated"),
2962
+ checkpointPath: checkpointPath(jobId)
2963
+ })
2964
+ );
2965
+ await finishRun(rootPath, manifest, plan, result, apiKey, job);
2966
+ } catch (error) {
2967
+ job.status = "failed";
2968
+ job.error = error instanceof Error ? error.message : String(error);
2969
+ await saveJob(job);
2970
+ if (error instanceof ExecutionError) {
2971
+ console.error(chalk4.red(`
2972
+ ${error.message}`));
2973
+ console.error(chalk4.yellow("Resume with: automigrate run --resume"));
2974
+ } else {
2975
+ console.error(chalk4.red(`
2976
+ Migration failed: ${job.error}`));
2977
+ }
2978
+ process.exitCode = 1;
2979
+ } finally {
2980
+ await releaseLock(rootPath);
2981
+ }
2982
+ }
2983
+ async function resumeRun(rootPath, manifest, apiKey, targetSpec) {
2984
+ const jobs = await readJobs();
2985
+ const job = [...jobs].reverse().find((j) => j.rootPath === rootPath && j.status === "failed");
2986
+ if (job === void 0) {
2987
+ console.error(chalk4.red("No failed migration found for this directory to resume."));
2988
+ process.exitCode = 1;
2989
+ return;
2990
+ }
2991
+ const ckptPath = checkpointPath(job.id);
2992
+ let checkpoint;
2993
+ try {
2994
+ checkpoint = JSON.parse(await fs9.readFile(ckptPath, "utf8"));
2995
+ } catch {
2996
+ console.error(chalk4.red(`Checkpoint for job ${job.id} not found or unreadable (${ckptPath}).`));
2997
+ process.exitCode = 1;
2998
+ return;
2999
+ }
3000
+ console.log(chalk4.dim(`Resuming job ${job.id} (${job.target || targetSpec})`));
3001
+ job.status = "executing";
3002
+ await acquireLock(rootPath);
3003
+ try {
3004
+ await saveJob(job);
3005
+ const result = await runExecutionWithProgress(
3006
+ () => resumeMigration(ckptPath, manifest, apiKey, progressBarCallback())
3007
+ );
3008
+ await finishRun(rootPath, manifest, checkpoint.plan, result, apiKey, job);
3009
+ } catch (error) {
3010
+ job.status = "failed";
3011
+ job.error = error instanceof Error ? error.message : String(error);
3012
+ await saveJob(job);
3013
+ console.error(chalk4.red(`
3014
+ Resume failed: ${job.error}`));
3015
+ if (error instanceof ExecutionError) {
3016
+ console.error(chalk4.yellow("You can resume again with: automigrate run --resume"));
3017
+ }
3018
+ process.exitCode = 1;
3019
+ } finally {
3020
+ await releaseLock(rootPath);
3021
+ }
3022
+ }
3023
+ async function finishRun(rootPath, manifest, plan, result, apiKey, job) {
3024
+ const migratedDir = result.outputDir;
3025
+ const copied = await copyUnchangedFiles(manifest, migratedDir, copyExclusions(plan));
3026
+ console.log(chalk4.dim(`Copied ${copied} unchanged files into ${migratedDir}`));
3027
+ const verifySpinner = ora2("Verifying migration (Pass 3)\u2026").start();
3028
+ const report = await verifyMigration(rootPath, migratedDir, plan, result, apiKey, { manifest });
3029
+ verifySpinner.succeed("Verification complete");
3030
+ const patchEntries = await collectPatchEntries(manifest, plan, migratedDir);
3031
+ const patchPath = path9.join(rootPath, "output.patch");
3032
+ await fs9.writeFile(patchPath, buildPatch(patchEntries), "utf8");
3033
+ job.status = "complete";
3034
+ job.createdFiles = [
3035
+ ...plan.filesToModify.filter((f) => planOutputPath(f) !== f.path).map(planOutputPath),
3036
+ ...plan.filesToCreate.map((f) => f.path)
3037
+ ];
3038
+ job.filesChanged = result.filesModified + result.filesCreated + result.filesDeleted;
3039
+ job.linesAdded = report.diffStats.linesAdded;
3040
+ job.linesRemoved = report.diffStats.linesRemoved;
3041
+ job.tokensUsed = result.tokensUsed;
3042
+ job.testPassRate = report.testPassRate;
3043
+ job.confidenceScore = report.confidenceScore;
3044
+ await saveJob(job);
3045
+ printSummary(report, result, patchPath, migratedDir, job.id);
3046
+ }
3047
+ function progressBarCallback() {
3048
+ const bar = new cliProgress.SingleBar(
3049
+ {
3050
+ format: `Transforming (Pass 2) ${chalk4.cyan("{bar}")} {value}/{total} files \xB7 {tokens} tokens \xB7 {file}`,
3051
+ hideCursor: true
3052
+ },
3053
+ cliProgress.Presets.shades_classic
3054
+ );
3055
+ let started = false;
3056
+ return (progress) => {
3057
+ if (!started && progress.filesTotal > 0) {
3058
+ bar.start(progress.filesTotal, 0, { tokens: 0, file: "" });
3059
+ started = true;
3060
+ }
3061
+ bar.update(progress.filesComplete, {
3062
+ tokens: progress.tokensUsed.toLocaleString("en-US"),
3063
+ file: progress.currentFile ?? ""
3064
+ });
3065
+ if (progress.stage === "complete" || progress.stage === "failed") bar.stop();
3066
+ };
3067
+ }
3068
+ async function runExecutionWithProgress(run) {
3069
+ const result = await run();
3070
+ console.log(
3071
+ chalk4.green(
3072
+ `Transformed ${result.filesModified + result.filesCreated} files (${result.tokensUsed.toLocaleString("en-US")} tokens)`
3073
+ )
3074
+ );
3075
+ return result;
3076
+ }
3077
+ function printPlan(plan) {
3078
+ console.log(`
3079
+ ${chalk4.bold("Migration plan (dry run)")}`);
3080
+ console.log(plan.summary);
3081
+ console.log(
3082
+ `
3083
+ Risk: ${riskColor(plan.riskLevel)} Effort: ${chalk4.bold(plan.estimatedEffort)} Est. tokens: ${plan.tokenBudgetEstimate.toLocaleString("en-US")}`
3084
+ );
3085
+ if (plan.filesToModify.length > 0) {
3086
+ console.log(`
3087
+ ${chalk4.bold("Files to modify:")}`);
3088
+ for (const f of plan.filesToModify) {
3089
+ const rename = planOutputPath(f) !== f.path ? chalk4.cyan(` \u2192 ${planOutputPath(f)}`) : "";
3090
+ console.log(
3091
+ ` ${complexityBadge(f.complexity)} ${f.path}${rename} ${chalk4.dim(f.description)}`
3092
+ );
3093
+ }
3094
+ }
3095
+ if (plan.filesToCreate.length > 0) {
3096
+ console.log(`
3097
+ ${chalk4.bold("Files to create:")}`);
3098
+ for (const f of plan.filesToCreate) console.log(` ${chalk4.green("+")} ${f.path}`);
3099
+ }
3100
+ if (plan.filesToDelete.length > 0) {
3101
+ console.log(`
3102
+ ${chalk4.bold("Files to delete:")}`);
3103
+ for (const f of plan.filesToDelete) console.log(` ${chalk4.red("-")} ${f}`);
3104
+ }
3105
+ if (plan.dependencyChanges.length > 0) {
3106
+ console.log(`
3107
+ ${chalk4.bold("Dependency changes:")}`);
3108
+ for (const d of plan.dependencyChanges) {
3109
+ console.log(
3110
+ ` ${d.action} ${d.name} ${chalk4.dim(`${d.currentVersion ?? ""} \u2192 ${d.targetVersion ?? ""}`)}`
3111
+ );
3112
+ }
3113
+ }
3114
+ if (plan.breakingChanges.length > 0) {
3115
+ console.log(`
3116
+ ${chalk4.bold("Breaking changes flagged:")}`);
3117
+ for (const b of plan.breakingChanges) {
3118
+ console.log(` ${complexityBadge(b.severity)} ${b.description}`);
3119
+ }
3120
+ }
3121
+ console.log(chalk4.dim("\nRun without --dry-run to execute this plan."));
3122
+ }
3123
+ function printSummary(report, result, patchPath, migratedDir, jobId) {
3124
+ const passRate = report.testPassRate === null ? chalk4.dim("no tests found") : report.testPassRate >= 0.9 ? chalk4.green(`${Math.round(report.testPassRate * 100)}%`) : chalk4.red(`${Math.round(report.testPassRate * 100)}%`);
3125
+ const conversionIssues = report.flaggedIssues.filter((i) => i.category === "conversion");
3126
+ const environmentIssues = report.flaggedIssues.filter((i) => i.category === "environment");
3127
+ const compile = report.compileCheck;
3128
+ const compileLine = compile === void 0 || !compile.ran ? chalk4.yellow(`skipped (${compile?.skippedReason ?? "not available"})`) : compile.ok ? chalk4.green("\u2713 passed") : chalk4.red(
3129
+ `\u2717 ${compile.errors.length} error(s) (${[...new Set(compile.errors.map((e) => e.file).filter((f) => f !== ""))].join(", ")})`
3130
+ );
3131
+ console.log(`
3132
+ ${chalk4.bold("Migration complete")} ${chalk4.dim(`(job ${jobId})`)}`);
3133
+ console.log(
3134
+ ` Files changed: ${chalk4.bold(String(result.filesModified + result.filesCreated + result.filesDeleted))} ${chalk4.green(`+${report.diffStats.linesAdded}`)} ${chalk4.red(`-${report.diffStats.linesRemoved}`)}`
3135
+ );
3136
+ console.log(` Compile check: ${compileLine}`);
3137
+ console.log(
3138
+ ` Test pass rate: ${passRate} Confidence: ${Math.round(report.confidenceScore * 100)}%`
3139
+ );
3140
+ if (compile !== void 0 && compile.ran && !compile.ok) {
3141
+ console.log(
3142
+ chalk4.red.bold("\n \u26A0 The migrated output does NOT compile.") + chalk4.red(" Review the errors above before applying output.patch.")
3143
+ );
3144
+ }
3145
+ if (report.selfRepairAttempts > 0) {
3146
+ console.log(` Self-repair attempts: ${report.selfRepairAttempts}`);
3147
+ }
3148
+ if (result.failedFiles.length > 0) {
3149
+ console.log(
3150
+ chalk4.red(` Failed files (${result.failedFiles.length}): ${result.failedFiles.join(", ")}`)
3151
+ );
3152
+ }
3153
+ if (conversionIssues.length > 0) {
3154
+ console.log(`
3155
+ ${chalk4.bold("Conversion issues:")}`);
3156
+ for (const issue of conversionIssues) {
3157
+ console.log(` ${complexityBadge(issue.severity)} ${issue.description}`);
3158
+ }
3159
+ }
3160
+ if (environmentIssues.length > 0) {
3161
+ console.log(chalk4.dim(` Environment setup items: ${environmentIssues.length} (see report)`));
3162
+ }
3163
+ console.log(`
3164
+ Review: ${chalk4.cyan(patchPath)} (apply with: git apply output.patch)`);
3165
+ console.log(` Output: ${chalk4.cyan(migratedDir)}`);
3166
+ console.log(` Rollback: ${chalk4.cyan(`automigrate rollback --job ${jobId}`)}
3167
+ `);
3168
+ }
3169
+ function riskColor(risk) {
3170
+ if (risk === "low") return chalk4.green(risk);
3171
+ if (risk === "medium") return chalk4.yellow(risk);
3172
+ return chalk4.red(risk);
3173
+ }
3174
+ function complexityBadge(level) {
3175
+ if (level === "low") return chalk4.green("\u25CF");
3176
+ if (level === "medium") return chalk4.yellow("\u25CF");
3177
+ return chalk4.red("\u25CF");
3178
+ }
3179
+ async function readProjectConfig(rootPath) {
3180
+ try {
3181
+ return JSON.parse(
3182
+ await fs9.readFile(path9.join(rootPath, "automigrate.config.json"), "utf8")
3183
+ );
3184
+ } catch {
3185
+ return null;
3186
+ }
3187
+ }
3188
+
3189
+ // src/commands/status.ts
3190
+ import chalk5 from "chalk";
3191
+ async function statusCommand() {
3192
+ const jobs = await readJobs();
3193
+ if (jobs.length === 0) {
3194
+ console.log(chalk5.dim("No migrations recorded yet. Run `automigrate run` to start one."));
3195
+ return;
3196
+ }
3197
+ console.log(chalk5.bold(`Last ${Math.min(jobs.length, 5)} migration jobs:
3198
+ `));
3199
+ for (const job of jobs.slice(-5).reverse()) {
3200
+ const date = job.createdAt.slice(0, 16).replace("T", " ");
3201
+ const stats = job.status === "complete" ? `${job.filesChanged} files ${chalk5.green(`+${job.linesAdded}`)} ${chalk5.red(`-${job.linesRemoved}`)}` + (job.testPassRate === null ? "" : ` tests ${Math.round(job.testPassRate * 100)}%`) : job.error ?? "";
3202
+ console.log(` ${statusBadge(job.status)} ${chalk5.bold(job.id)} ${date} ${job.target}`);
3203
+ console.log(` ${chalk5.dim(job.rootPath)} ${stats}
3204
+ `);
3205
+ }
3206
+ }
3207
+ function statusBadge(status) {
3208
+ switch (status) {
3209
+ case "complete":
3210
+ return chalk5.green("\u2714");
3211
+ case "failed":
3212
+ return chalk5.red("\u2716");
3213
+ case "rolled_back":
3214
+ return chalk5.yellow("\u21A9");
3215
+ default:
3216
+ return chalk5.cyan("\u2026");
3217
+ }
3218
+ }
3219
+
3220
+ // src/index.ts
3221
+ var pkg = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
3222
+ var program = new Command();
3223
+ program.name("automigrate").description("Autonomous codebase migration agent \u2014 plan, transform, verify").version(pkg.version);
3224
+ program.command("init").description("Detect the stack in the current directory and suggest migration targets").action(initCommand);
3225
+ program.command("run").description("Run a full migration: plan, execute, verify").option("--target <from-to>", "migration target, e.g. javascript-to-typescript").option("--dry-run", "plan only, print the plan, do not execute").option("--resume", "resume the last failed migration from its checkpoint").action(runCommand);
3226
+ program.command("rollback").description("Restore original files from the pre-migration backup and remove /migrated").option("--job <id>", "job id to roll back (defaults to the most recent)").action(rollbackCommand);
3227
+ program.command("status").description("Show the last 5 migration jobs").action(statusCommand);
3228
+ var config = program.command("config").description("Manage AutoMigrate configuration");
3229
+ config.command("set-key <apiKey>").description("Save the Anthropic API key to ~/.automigrate/config.json").action(setKeyCommand);
3230
+ await program.parseAsync();
3231
+ //# sourceMappingURL=index.js.map