greenbump 0.2.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/README.md +2 -0
  3. package/action-dist/index.cjs +1054 -19
  4. package/competitive-analysis.md +304 -0
  5. package/dist/cli.js +8 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/engine/change-detector.js +152 -0
  8. package/dist/engine/change-detector.js.map +1 -0
  9. package/dist/engine/dep-graph.js +101 -0
  10. package/dist/engine/dep-graph.js.map +1 -0
  11. package/dist/engine/git.js +10 -0
  12. package/dist/engine/git.js.map +1 -1
  13. package/dist/engine/perf/metrics.js +171 -0
  14. package/dist/engine/perf/metrics.js.map +1 -0
  15. package/dist/engine/perf/regression.js +115 -0
  16. package/dist/engine/perf/regression.js.map +1 -0
  17. package/dist/engine/run.js +71 -2
  18. package/dist/engine/run.js.map +1 -1
  19. package/dist/engine/sandbox/docker-manager.js +215 -0
  20. package/dist/engine/sandbox/docker-manager.js.map +1 -0
  21. package/dist/engine/sandbox/orchestrator.js +107 -0
  22. package/dist/engine/sandbox/orchestrator.js.map +1 -0
  23. package/dist/engine/sandbox/service-detector.js +147 -0
  24. package/dist/engine/sandbox/service-detector.js.map +1 -0
  25. package/dist/engine/sandbox/template-generator.js +241 -0
  26. package/dist/engine/sandbox/template-generator.js.map +1 -0
  27. package/dist/engine/staged-fix.js +108 -0
  28. package/dist/engine/staged-fix.js.map +1 -0
  29. package/dist/engine/stages.js +125 -0
  30. package/dist/engine/stages.js.map +1 -0
  31. package/dist/engine/verify.js +121 -0
  32. package/dist/engine/verify.js.map +1 -0
  33. package/dist/format.js +20 -0
  34. package/dist/format.js.map +1 -1
  35. package/optimization-roadmap.md +772 -0
  36. package/package.json +4 -1
@@ -6503,8 +6503,8 @@ async function pathExists(p2) {
6503
6503
  // dist/engine/ecosystems/npm.js
6504
6504
  async function readScripts(cwd) {
6505
6505
  try {
6506
- const { readFile: readFile11 } = await import("node:fs/promises");
6507
- const raw = await readFile11((0, import_node_path.join)(cwd, "package.json"), "utf8");
6506
+ const { readFile: readFile13 } = await import("node:fs/promises");
6507
+ const raw = await readFile13((0, import_node_path.join)(cwd, "package.json"), "utf8");
6508
6508
  const pkg = JSON.parse(raw);
6509
6509
  return { build: pkg.scripts?.build, test: pkg.scripts?.test };
6510
6510
  } catch {
@@ -6546,8 +6546,8 @@ var npmAdapter = {
6546
6546
  var import_node_path2 = require("node:path");
6547
6547
  async function readScripts2(cwd) {
6548
6548
  try {
6549
- const { readFile: readFile11 } = await import("node:fs/promises");
6550
- const raw = await readFile11((0, import_node_path2.join)(cwd, "package.json"), "utf8");
6549
+ const { readFile: readFile13 } = await import("node:fs/promises");
6550
+ const raw = await readFile13((0, import_node_path2.join)(cwd, "package.json"), "utf8");
6551
6551
  const pkg = JSON.parse(raw);
6552
6552
  return { build: pkg.scripts?.build, test: pkg.scripts?.test };
6553
6553
  } catch {
@@ -6598,8 +6598,8 @@ var yarnAdapter = {
6598
6598
  var import_node_path3 = require("node:path");
6599
6599
  async function readScripts3(cwd) {
6600
6600
  try {
6601
- const { readFile: readFile11 } = await import("node:fs/promises");
6602
- const raw = await readFile11((0, import_node_path3.join)(cwd, "package.json"), "utf8");
6601
+ const { readFile: readFile13 } = await import("node:fs/promises");
6602
+ const raw = await readFile13((0, import_node_path3.join)(cwd, "package.json"), "utf8");
6603
6603
  const pkg = JSON.parse(raw);
6604
6604
  return { build: pkg.scripts?.build, test: pkg.scripts?.test };
6605
6605
  } catch {
@@ -6992,14 +6992,14 @@ var bundlerAdapter = {
6992
6992
  }
6993
6993
  };
6994
6994
  async function pinInGemfile(cwd, name, version, fallback) {
6995
- const { readFile: readFile11, writeFile: writeFile8 } = await import("node:fs/promises");
6995
+ const { readFile: readFile13, writeFile: writeFile10 } = await import("node:fs/promises");
6996
6996
  try {
6997
6997
  const path2 = (0, import_node_path11.join)(cwd, "Gemfile");
6998
- const raw = await readFile11(path2, "utf8");
6998
+ const raw = await readFile13(path2, "utf8");
6999
6999
  const re2 = new RegExp(`^(\\s*gem\\s+["']${name}["'])(.*)$`, "m");
7000
7000
  if (re2.test(raw)) {
7001
7001
  const next = raw.replace(re2, `$1, "= ${version}"`);
7002
- await writeFile8(path2, next, "utf8");
7002
+ await writeFile10(path2, next, "utf8");
7003
7003
  }
7004
7004
  } catch {
7005
7005
  }
@@ -7087,7 +7087,7 @@ var mavenAdapter = {
7087
7087
  return out;
7088
7088
  },
7089
7089
  async install(cwd, name, version) {
7090
- const { readFile: rf, writeFile: writeFile8 } = await import("node:fs/promises");
7090
+ const { readFile: rf, writeFile: writeFile10 } = await import("node:fs/promises");
7091
7091
  const path2 = (0, import_node_path13.join)(cwd, "pom.xml");
7092
7092
  const [, artifactId] = name.split(":");
7093
7093
  const raw = await rf(path2, "utf8");
@@ -7095,7 +7095,7 @@ var mavenAdapter = {
7095
7095
  if (!re2.test(raw)) {
7096
7096
  return { code: 1, stdout: "", stderr: `could not find ${name} in pom.xml`, combined: `could not find ${name} in pom.xml` };
7097
7097
  }
7098
- await writeFile8(path2, raw.replace(re2, `$1${version}$3`), "utf8");
7098
+ await writeFile10(path2, raw.replace(re2, `$1${version}$3`), "utf8");
7099
7099
  return { code: 0, stdout: `pinned ${name} to ${version} in pom.xml`, stderr: "", combined: "" };
7100
7100
  },
7101
7101
  async defaultCheckCommands() {
@@ -7278,14 +7278,14 @@ var mixAdapter = {
7278
7278
  return parseMixOutdated(r2.stdout);
7279
7279
  },
7280
7280
  async install(cwd, name, version) {
7281
- const { readFile: readFile11, writeFile: writeFile8 } = await import("node:fs/promises");
7281
+ const { readFile: readFile13, writeFile: writeFile10 } = await import("node:fs/promises");
7282
7282
  const path2 = (0, import_node_path15.join)(cwd, "mix.exs");
7283
- const raw = await readFile11(path2, "utf8");
7283
+ const raw = await readFile13(path2, "utf8");
7284
7284
  const re2 = new RegExp(`(\\{:${name},\\s*)"[^"]+"`);
7285
7285
  if (!re2.test(raw)) {
7286
7286
  return { code: 1, stdout: "", stderr: `could not find :${name} in mix.exs`, combined: `could not find :${name} in mix.exs` };
7287
7287
  }
7288
- await writeFile8(path2, raw.replace(re2, `$1"== ${version}"`), "utf8");
7288
+ await writeFile10(path2, raw.replace(re2, `$1"== ${version}"`), "utf8");
7289
7289
  return { code: 0, stdout: `pinned ${name} to ${version} in mix.exs`, stderr: "", combined: "" };
7290
7290
  },
7291
7291
  async defaultCheckCommands() {
@@ -7616,8 +7616,8 @@ var elmAdapter = {
7616
7616
  return { code: 1, stdout: "", stderr: `${name} is not a direct dependency in elm.json`, combined: `${name} is not a direct dependency in elm.json` };
7617
7617
  }
7618
7618
  json.dependencies.direct[name] = version;
7619
- const { writeFile: writeFile8 } = await import("node:fs/promises");
7620
- await writeFile8(path2, JSON.stringify(json, null, 4), "utf8");
7619
+ const { writeFile: writeFile10 } = await import("node:fs/promises");
7620
+ await writeFile10(path2, JSON.stringify(json, null, 4), "utf8");
7621
7621
  return { code: 0, stdout: `pinned ${name} to ${version} in elm.json`, stderr: "", combined: "" };
7622
7622
  },
7623
7623
  async defaultCheckCommands() {
@@ -19295,6 +19295,986 @@ ${listProviders()}`);
19295
19295
  return new OpenAICompatProvider(presetName, apiKey, baseURL, model);
19296
19296
  }
19297
19297
 
19298
+ // dist/engine/verify.js
19299
+ var import_node_child_process2 = require("node:child_process");
19300
+ var import_node_util = require("node:util");
19301
+ var import_node_path22 = require("node:path");
19302
+ var execAsync = (0, import_node_util.promisify)(import_node_child_process2.exec);
19303
+ async function runStaticAnalysis(cwd, options = {}) {
19304
+ const { checkTypes = true, checkLint = true, strictLint = false } = options;
19305
+ const results = [];
19306
+ if (checkTypes && await hasTsConfig(cwd)) {
19307
+ try {
19308
+ const { stdout, stderr } = await execAsync("npx tsc --noEmit", {
19309
+ cwd,
19310
+ timeout: 6e4
19311
+ // 1 minute timeout
19312
+ });
19313
+ results.push({
19314
+ passed: true,
19315
+ stage: "types",
19316
+ output: stdout + stderr
19317
+ });
19318
+ } catch (err) {
19319
+ const output = err.stdout + err.stderr;
19320
+ results.push({
19321
+ passed: false,
19322
+ stage: "types",
19323
+ output,
19324
+ warnings: parseTypeErrors(output)
19325
+ });
19326
+ }
19327
+ }
19328
+ if (checkLint && await hasEslintConfig(cwd)) {
19329
+ try {
19330
+ const { stdout, stderr } = await execAsync("npx eslint . --ext .ts,.tsx,.js,.jsx --format compact", {
19331
+ cwd,
19332
+ timeout: 6e4
19333
+ });
19334
+ results.push({
19335
+ passed: true,
19336
+ stage: "lint",
19337
+ output: stdout + stderr
19338
+ });
19339
+ } catch (err) {
19340
+ const output = err.stdout + err.stderr;
19341
+ results.push({
19342
+ passed: strictLint ? false : true,
19343
+ // Lint warnings don't fail by default
19344
+ stage: "lint",
19345
+ output,
19346
+ warnings: parseLintWarnings(output)
19347
+ });
19348
+ }
19349
+ }
19350
+ return results;
19351
+ }
19352
+ async function hasTsConfig(cwd) {
19353
+ return await pathExists((0, import_node_path22.join)(cwd, "tsconfig.json"));
19354
+ }
19355
+ async function hasEslintConfig(cwd) {
19356
+ const configs = [
19357
+ ".eslintrc.js",
19358
+ ".eslintrc.cjs",
19359
+ ".eslintrc.json",
19360
+ ".eslintrc.yml",
19361
+ ".eslintrc.yaml"
19362
+ ];
19363
+ for (const cfg of configs) {
19364
+ if (await pathExists((0, import_node_path22.join)(cwd, cfg)))
19365
+ return true;
19366
+ }
19367
+ try {
19368
+ const pkgPath = (0, import_node_path22.join)(cwd, "package.json");
19369
+ if (await pathExists(pkgPath)) {
19370
+ const pkg = JSON.parse(await import("node:fs/promises").then((fs2) => fs2.readFile(pkgPath, "utf8")));
19371
+ if (pkg.eslintConfig)
19372
+ return true;
19373
+ }
19374
+ } catch {
19375
+ }
19376
+ return false;
19377
+ }
19378
+ function parseTypeErrors(output) {
19379
+ const lines = output.split("\n");
19380
+ const errors = [];
19381
+ for (const line of lines) {
19382
+ if (/error TS\d+:/.test(line)) {
19383
+ const match = line.match(/^(.+?\.\w+)\(\d+,\d+\): (.+)$/);
19384
+ if (match) {
19385
+ errors.push(`${match[1]}: ${match[2]}`);
19386
+ } else {
19387
+ errors.push(line.trim());
19388
+ }
19389
+ }
19390
+ }
19391
+ return errors.slice(0, 10);
19392
+ }
19393
+ function parseLintWarnings(output) {
19394
+ const lines = output.split("\n");
19395
+ const warnings = [];
19396
+ for (const line of lines) {
19397
+ if (line.includes("Error") || line.includes("Warning")) {
19398
+ warnings.push(line.trim());
19399
+ }
19400
+ }
19401
+ return warnings.slice(0, 10);
19402
+ }
19403
+
19404
+ // dist/engine/change-detector.js
19405
+ var import_node_child_process3 = require("node:child_process");
19406
+ var import_node_util2 = require("node:util");
19407
+ var execAsync2 = (0, import_node_util2.promisify)(import_node_child_process3.exec);
19408
+ async function detectSuspiciousChanges(cwd) {
19409
+ const changes = [];
19410
+ try {
19411
+ const { stdout: diff } = await execAsync2("git diff HEAD", { cwd });
19412
+ if (!diff.trim())
19413
+ return [];
19414
+ const testFileChanges = detectTestFileChanges(diff);
19415
+ changes.push(...testFileChanges);
19416
+ const largeDeletions = detectLargeDeletions(diff);
19417
+ changes.push(...largeDeletions);
19418
+ const commentedTests = detectCommentedTests(diff);
19419
+ changes.push(...commentedTests);
19420
+ const removedTests = detectRemovedTests(diff);
19421
+ changes.push(...removedTests);
19422
+ } catch (err) {
19423
+ }
19424
+ return changes;
19425
+ }
19426
+ function detectTestFileChanges(diff) {
19427
+ const changes = [];
19428
+ const testFilePattern = /\.(test|spec)\.(ts|js|tsx|jsx)$/;
19429
+ const fileHeaderPattern = /^diff --git a\/(.+?) b\/(.+?)$/gm;
19430
+ let match;
19431
+ while ((match = fileHeaderPattern.exec(diff)) !== null) {
19432
+ const filePath = match[2];
19433
+ if (testFilePattern.test(filePath)) {
19434
+ changes.push({
19435
+ type: "test-modified",
19436
+ file: filePath,
19437
+ description: "Test file was modified during fix loop",
19438
+ severity: "critical"
19439
+ });
19440
+ }
19441
+ }
19442
+ return changes;
19443
+ }
19444
+ function detectLargeDeletions(diff) {
19445
+ const changes = [];
19446
+ const fileDiffs = diff.split(/^diff --git /m).slice(1);
19447
+ for (const fileDiff of fileDiffs) {
19448
+ const fileMatch = fileDiff.match(/^a\/(.+?) b\/(.+?)$/m);
19449
+ if (!fileMatch)
19450
+ continue;
19451
+ const filePath = fileMatch[2];
19452
+ const lines = fileDiff.split("\n");
19453
+ let deletions = 0;
19454
+ for (const line of lines) {
19455
+ if (line.startsWith("-") && !line.startsWith("---")) {
19456
+ deletions++;
19457
+ }
19458
+ }
19459
+ if (deletions > 50) {
19460
+ changes.push({
19461
+ type: "large-deletion",
19462
+ file: filePath,
19463
+ description: `${deletions} lines deleted (possible over-aggressive fix)`,
19464
+ severity: "warning"
19465
+ });
19466
+ }
19467
+ }
19468
+ return changes;
19469
+ }
19470
+ function detectCommentedTests(diff) {
19471
+ const changes = [];
19472
+ const lines = diff.split("\n");
19473
+ for (let i2 = 0; i2 < lines.length - 1; i2++) {
19474
+ const prevLine = lines[i2];
19475
+ const currLine = lines[i2 + 1];
19476
+ if (prevLine.startsWith("-") && /\b(it|test|describe)\(/.test(prevLine) && currLine.startsWith("+") && (/^[+]\s*(\/\/|\/\*)/.test(currLine) || currLine.includes("// it(") || currLine.includes("// test(") || currLine.includes("/* it(") || currLine.includes("/* test("))) {
19477
+ const fileContext = findFileContext(diff, i2);
19478
+ changes.push({
19479
+ type: "test-commented",
19480
+ file: fileContext || "unknown",
19481
+ description: "Test case appears to have been commented out",
19482
+ severity: "critical"
19483
+ });
19484
+ }
19485
+ }
19486
+ return changes;
19487
+ }
19488
+ function detectRemovedTests(diff) {
19489
+ const changes = [];
19490
+ const lines = diff.split("\n");
19491
+ let removedTestCount = 0;
19492
+ let currentFile = "unknown";
19493
+ for (const line of lines) {
19494
+ const fileMatch = line.match(/^diff --git a\/(.+?) b\/(.+?)$/);
19495
+ if (fileMatch) {
19496
+ currentFile = fileMatch[2];
19497
+ removedTestCount = 0;
19498
+ }
19499
+ if (line.startsWith("-") && !line.startsWith("---") && /\b(it|test)\s*\(/.test(line)) {
19500
+ removedTestCount++;
19501
+ }
19502
+ }
19503
+ if (removedTestCount > 0) {
19504
+ changes.push({
19505
+ type: "test-removed",
19506
+ file: currentFile,
19507
+ description: `${removedTestCount} test case(s) were deleted`,
19508
+ severity: "critical"
19509
+ });
19510
+ }
19511
+ return changes;
19512
+ }
19513
+ function findFileContext(diff, lineIndex) {
19514
+ const lines = diff.split("\n");
19515
+ for (let i2 = lineIndex; i2 >= 0; i2--) {
19516
+ const match = lines[i2].match(/^diff --git a\/(.+?) b\/(.+?)$/);
19517
+ if (match)
19518
+ return match[2];
19519
+ }
19520
+ return null;
19521
+ }
19522
+
19523
+ // dist/engine/sandbox/orchestrator.js
19524
+ var import_path12 = require("path");
19525
+ var import_promises14 = require("fs/promises");
19526
+
19527
+ // dist/engine/sandbox/docker-manager.js
19528
+ var import_fs2 = require("fs");
19529
+ var import_path9 = require("path");
19530
+ var import_promises12 = require("fs/promises");
19531
+ async function checkDockerAvailable() {
19532
+ const info2 = {
19533
+ available: false,
19534
+ composeAvailable: false
19535
+ };
19536
+ try {
19537
+ const versionResult = await exec("docker", ["--version"], { cwd: process.cwd() });
19538
+ info2.version = versionResult.stdout.trim();
19539
+ await exec("docker", ["ps"], { cwd: process.cwd() });
19540
+ info2.available = true;
19541
+ try {
19542
+ await exec("docker-compose", ["--version"], { cwd: process.cwd() });
19543
+ info2.composeAvailable = true;
19544
+ } catch {
19545
+ }
19546
+ } catch {
19547
+ }
19548
+ return info2;
19549
+ }
19550
+ async function buildDockerImage(cwd, imageName, dockerfilePath) {
19551
+ console.log(`\u{1F433} Building Docker image: ${imageName}...`);
19552
+ await exec("docker", [
19553
+ "build",
19554
+ "-t",
19555
+ imageName,
19556
+ "-f",
19557
+ dockerfilePath,
19558
+ "."
19559
+ ], { cwd });
19560
+ }
19561
+ async function runVerificationInContainer(cwd, options) {
19562
+ const args = [
19563
+ "run",
19564
+ "--rm",
19565
+ "-v",
19566
+ `${cwd}:/workspace`,
19567
+ "-w",
19568
+ "/workspace"
19569
+ ];
19570
+ if (options.networkMode) {
19571
+ args.push("--network", options.networkMode);
19572
+ }
19573
+ if (options.env) {
19574
+ for (const [key, value] of Object.entries(options.env)) {
19575
+ args.push("-e", `${key}=${value}`);
19576
+ }
19577
+ }
19578
+ args.push(options.image);
19579
+ args.push("sh", "-c", "npm install && npm run build 2>&1 && npm test 2>&1");
19580
+ let exitCode = 0;
19581
+ let stdout = "";
19582
+ let stderr = "";
19583
+ let buildSuccess = false;
19584
+ let testsPassed = false;
19585
+ try {
19586
+ const result = await exec("docker", args, {
19587
+ cwd,
19588
+ timeout: options.timeout || 6e5
19589
+ // 10 minutes default
19590
+ });
19591
+ stdout = result.stdout;
19592
+ stderr = result.stderr;
19593
+ exitCode = 0;
19594
+ buildSuccess = true;
19595
+ testsPassed = true;
19596
+ } catch (error) {
19597
+ exitCode = error.exitCode || 1;
19598
+ stdout = error.stdout || "";
19599
+ stderr = error.stderr || "";
19600
+ buildSuccess = !stderr.includes("npm run build") && !stdout.includes("build failed");
19601
+ testsPassed = false;
19602
+ }
19603
+ return {
19604
+ exitCode,
19605
+ stdout,
19606
+ stderr,
19607
+ buildSuccess,
19608
+ testsPassed
19609
+ };
19610
+ }
19611
+ async function startComposeServices(cwd, composeFile) {
19612
+ console.log("\u{1F680} Starting docker-compose services...");
19613
+ const projectName = `greenbump-${Date.now()}`;
19614
+ await exec("docker-compose", [
19615
+ "-f",
19616
+ composeFile,
19617
+ "-p",
19618
+ projectName,
19619
+ "up",
19620
+ "-d"
19621
+ ], { cwd });
19622
+ return projectName;
19623
+ }
19624
+ async function checkServiceHealth(projectName, serviceName) {
19625
+ try {
19626
+ const result = await exec("docker-compose", ["-p", projectName, "ps", "--filter", `name=${serviceName}`, "--format", "json"], { cwd: process.cwd() });
19627
+ const output = result.stdout.trim();
19628
+ if (!output)
19629
+ return false;
19630
+ return output.includes('"State":"running"') || output.includes("healthy");
19631
+ } catch {
19632
+ return false;
19633
+ }
19634
+ }
19635
+ async function waitForServices(projectName, services, timeout = 60) {
19636
+ console.log("\u23F3 Waiting for services to become healthy...");
19637
+ const deadline = Date.now() + timeout * 1e3;
19638
+ const healthyServices = /* @__PURE__ */ new Set();
19639
+ while (Date.now() < deadline && healthyServices.size < services.length) {
19640
+ for (const service of services) {
19641
+ if (healthyServices.has(service))
19642
+ continue;
19643
+ const healthy = await checkServiceHealth(projectName, service);
19644
+ if (healthy) {
19645
+ console.log(`\u2713 ${service} is healthy`);
19646
+ healthyServices.add(service);
19647
+ }
19648
+ }
19649
+ if (healthyServices.size < services.length) {
19650
+ await sleep3(2e3);
19651
+ }
19652
+ }
19653
+ if (healthyServices.size < services.length) {
19654
+ const unhealthy = services.filter((s2) => !healthyServices.has(s2));
19655
+ throw new Error(`Timeout waiting for services to become healthy: ${unhealthy.join(", ")}`);
19656
+ }
19657
+ }
19658
+ async function stopComposeServices(cwd, composeFile, projectName) {
19659
+ console.log("\u{1F6D1} Stopping docker-compose services...");
19660
+ try {
19661
+ await exec("docker-compose", [
19662
+ "-f",
19663
+ composeFile,
19664
+ "-p",
19665
+ projectName,
19666
+ "down",
19667
+ "-v"
19668
+ ], { cwd });
19669
+ } catch (error) {
19670
+ console.warn("Warning: Failed to stop compose services:", error);
19671
+ }
19672
+ }
19673
+ async function removeDockerImage(imageName) {
19674
+ try {
19675
+ await exec("docker", ["rmi", imageName], { cwd: process.cwd() });
19676
+ } catch {
19677
+ }
19678
+ }
19679
+ async function cleanupDocker(cwd, imageName, composeFile, projectName) {
19680
+ console.log("\u{1F9F9} Cleaning up Docker resources...");
19681
+ if (composeFile && projectName) {
19682
+ await stopComposeServices(cwd, composeFile, projectName);
19683
+ }
19684
+ await removeDockerImage(imageName);
19685
+ const tempFiles = [
19686
+ (0, import_path9.join)(cwd, ".greenbump-Dockerfile"),
19687
+ (0, import_path9.join)(cwd, ".greenbump-compose.yml")
19688
+ ];
19689
+ for (const file of tempFiles) {
19690
+ try {
19691
+ if ((0, import_fs2.existsSync)(file)) {
19692
+ await (0, import_promises12.rm)(file);
19693
+ }
19694
+ } catch {
19695
+ }
19696
+ }
19697
+ }
19698
+ function sleep3(ms) {
19699
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
19700
+ }
19701
+
19702
+ // dist/engine/sandbox/template-generator.js
19703
+ var import_fs3 = require("fs");
19704
+ var import_path10 = require("path");
19705
+ async function detectProjectType(cwd) {
19706
+ if ((0, import_fs3.existsSync)((0, import_path10.join)(cwd, "package.json"))) {
19707
+ return "nodejs";
19708
+ }
19709
+ if ((0, import_fs3.existsSync)((0, import_path10.join)(cwd, "requirements.txt")) || (0, import_fs3.existsSync)((0, import_path10.join)(cwd, "pyproject.toml")) || (0, import_fs3.existsSync)((0, import_path10.join)(cwd, "setup.py"))) {
19710
+ return "python";
19711
+ }
19712
+ return "unknown";
19713
+ }
19714
+ async function generateDockerfile(projectType, cwd) {
19715
+ if (projectType === "nodejs") {
19716
+ return await generateNodeJsDockerfile(cwd);
19717
+ }
19718
+ if (projectType === "python") {
19719
+ return await generatePythonDockerfile(cwd);
19720
+ }
19721
+ return await generateNodeJsDockerfile(cwd);
19722
+ }
19723
+ async function generateNodeJsDockerfile(cwd) {
19724
+ const hasPnpm = (0, import_fs3.existsSync)((0, import_path10.join)(cwd, "pnpm-lock.yaml"));
19725
+ const hasYarn = (0, import_fs3.existsSync)((0, import_path10.join)(cwd, "yarn.lock"));
19726
+ let installCmd = "npm ci";
19727
+ let copyLockFile = "COPY package*.json ./";
19728
+ if (hasPnpm) {
19729
+ installCmd = "corepack enable && pnpm install --frozen-lockfile";
19730
+ copyLockFile = "COPY package*.json pnpm-lock.yaml ./";
19731
+ } else if (hasYarn) {
19732
+ installCmd = "yarn install --frozen-lockfile";
19733
+ copyLockFile = "COPY package*.json yarn.lock ./";
19734
+ }
19735
+ return `FROM node:20-slim
19736
+
19737
+ WORKDIR /workspace
19738
+
19739
+ # Copy package files
19740
+ ${copyLockFile}
19741
+
19742
+ # Install dependencies
19743
+ RUN ${installCmd}
19744
+
19745
+ # Copy source code
19746
+ COPY . .
19747
+
19748
+ # Build if build script exists
19749
+ RUN if grep -q '"build":' package.json; then npm run build || true; fi
19750
+
19751
+ # Default command: run tests
19752
+ CMD ["npm", "test"]
19753
+ `;
19754
+ }
19755
+ async function generatePythonDockerfile(cwd) {
19756
+ const hasRequirements = (0, import_fs3.existsSync)((0, import_path10.join)(cwd, "requirements.txt"));
19757
+ const hasPyproject = (0, import_fs3.existsSync)((0, import_path10.join)(cwd, "pyproject.toml"));
19758
+ let installCmd = "";
19759
+ let copyFiles = "";
19760
+ if (hasRequirements) {
19761
+ copyFiles = "COPY requirements*.txt ./";
19762
+ installCmd = "pip install --no-cache-dir -r requirements.txt";
19763
+ } else if (hasPyproject) {
19764
+ copyFiles = "COPY pyproject.toml setup.py* ./";
19765
+ installCmd = "pip install --no-cache-dir -e .";
19766
+ }
19767
+ return `FROM python:3.11-slim
19768
+
19769
+ WORKDIR /workspace
19770
+
19771
+ # Copy dependency files
19772
+ ${copyFiles}
19773
+
19774
+ # Install dependencies
19775
+ RUN ${installCmd}
19776
+
19777
+ # Copy source code
19778
+ COPY . .
19779
+
19780
+ # Default command: run tests
19781
+ CMD ["pytest"]
19782
+ `;
19783
+ }
19784
+ async function generateComposeFile(services) {
19785
+ const serviceConfigs = services.map(getServiceConfig).filter((config) => config !== null);
19786
+ if (serviceConfigs.length === 0) {
19787
+ return "";
19788
+ }
19789
+ const servicesYaml = serviceConfigs.map(formatServiceConfig).join("\n");
19790
+ return `version: '3.8'
19791
+
19792
+ services:
19793
+ ${servicesYaml}
19794
+
19795
+ networks:
19796
+ default:
19797
+ name: greenbump_network
19798
+ `;
19799
+ }
19800
+ function getServiceConfig(serviceName) {
19801
+ const configs = {
19802
+ postgres: {
19803
+ name: "postgres",
19804
+ image: "postgres:16-alpine",
19805
+ ports: ["5432:5432"],
19806
+ environment: {
19807
+ POSTGRES_USER: "test",
19808
+ POSTGRES_PASSWORD: "test",
19809
+ POSTGRES_DB: "test"
19810
+ },
19811
+ healthcheck: {
19812
+ test: ["CMD-SHELL", "pg_isready -U test"],
19813
+ interval: "5s",
19814
+ timeout: "5s",
19815
+ retries: 5
19816
+ }
19817
+ },
19818
+ mysql: {
19819
+ name: "mysql",
19820
+ image: "mysql:8",
19821
+ ports: ["3306:3306"],
19822
+ environment: {
19823
+ MYSQL_ROOT_PASSWORD: "test",
19824
+ MYSQL_DATABASE: "test"
19825
+ },
19826
+ healthcheck: {
19827
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost"],
19828
+ interval: "5s",
19829
+ timeout: "5s",
19830
+ retries: 5
19831
+ }
19832
+ },
19833
+ redis: {
19834
+ name: "redis",
19835
+ image: "redis:7-alpine",
19836
+ ports: ["6379:6379"],
19837
+ healthcheck: {
19838
+ test: ["CMD", "redis-cli", "ping"],
19839
+ interval: "5s",
19840
+ timeout: "5s",
19841
+ retries: 5
19842
+ }
19843
+ },
19844
+ mongodb: {
19845
+ name: "mongodb",
19846
+ image: "mongo:7",
19847
+ ports: ["27017:27017"],
19848
+ environment: {
19849
+ MONGO_INITDB_ROOT_USERNAME: "test",
19850
+ MONGO_INITDB_ROOT_PASSWORD: "test"
19851
+ },
19852
+ healthcheck: {
19853
+ test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"],
19854
+ interval: "5s",
19855
+ timeout: "5s",
19856
+ retries: 5
19857
+ }
19858
+ }
19859
+ };
19860
+ return configs[serviceName] || null;
19861
+ }
19862
+ function formatServiceConfig(config) {
19863
+ const lines = [` ${config.name}:`];
19864
+ lines.push(` image: ${config.image}`);
19865
+ if (config.environment) {
19866
+ lines.push(" environment:");
19867
+ for (const [key, value] of Object.entries(config.environment)) {
19868
+ lines.push(` ${key}: ${value}`);
19869
+ }
19870
+ }
19871
+ if (config.ports.length > 0) {
19872
+ lines.push(" ports:");
19873
+ for (const port of config.ports) {
19874
+ lines.push(` - "${port}"`);
19875
+ }
19876
+ }
19877
+ lines.push(" healthcheck:");
19878
+ lines.push(` test: ${JSON.stringify(config.healthcheck.test)}`);
19879
+ lines.push(` interval: ${config.healthcheck.interval}`);
19880
+ lines.push(` timeout: ${config.healthcheck.timeout}`);
19881
+ lines.push(` retries: ${config.healthcheck.retries}`);
19882
+ return lines.join("\n");
19883
+ }
19884
+
19885
+ // dist/engine/sandbox/service-detector.js
19886
+ var import_fs4 = require("fs");
19887
+ var import_path11 = require("path");
19888
+ var import_promises13 = require("fs/promises");
19889
+ async function detectRequiredServices(cwd) {
19890
+ const services = /* @__PURE__ */ new Set();
19891
+ await detectFromPackageJson(cwd, services);
19892
+ await detectFromDockerCompose(cwd, services);
19893
+ await detectFromEnvFiles(cwd, services);
19894
+ return Array.from(services);
19895
+ }
19896
+ async function detectFromPackageJson(cwd, services) {
19897
+ const pkgPath = (0, import_path11.join)(cwd, "package.json");
19898
+ if (!(0, import_fs4.existsSync)(pkgPath))
19899
+ return;
19900
+ try {
19901
+ const content = await (0, import_promises13.readFile)(pkgPath, "utf-8");
19902
+ const pkg = JSON.parse(content);
19903
+ const allDeps = {
19904
+ ...pkg.dependencies,
19905
+ ...pkg.devDependencies
19906
+ };
19907
+ if (allDeps.pg || allDeps["pg-promise"] || allDeps.postgres || allDeps.sequelize) {
19908
+ services.add("postgres");
19909
+ }
19910
+ if (allDeps.mysql || allDeps.mysql2) {
19911
+ services.add("mysql");
19912
+ }
19913
+ if (allDeps.redis || allDeps.ioredis) {
19914
+ services.add("redis");
19915
+ }
19916
+ if (allDeps.mongodb || allDeps.mongoose) {
19917
+ services.add("mongodb");
19918
+ }
19919
+ } catch {
19920
+ }
19921
+ }
19922
+ async function detectFromDockerCompose(cwd, services) {
19923
+ const composeFiles = [
19924
+ "docker-compose.yml",
19925
+ "docker-compose.yaml",
19926
+ "docker-compose.test.yml",
19927
+ "docker-compose.test.yaml"
19928
+ ];
19929
+ for (const file of composeFiles) {
19930
+ const composePath = (0, import_path11.join)(cwd, file);
19931
+ if (!(0, import_fs4.existsSync)(composePath))
19932
+ continue;
19933
+ try {
19934
+ const content = await (0, import_promises13.readFile)(composePath, "utf-8");
19935
+ if (/image:\s*postgres/i.test(content)) {
19936
+ services.add("postgres");
19937
+ }
19938
+ if (/image:\s*mysql/i.test(content)) {
19939
+ services.add("mysql");
19940
+ }
19941
+ if (/image:\s*redis/i.test(content)) {
19942
+ services.add("redis");
19943
+ }
19944
+ if (/image:\s*mongo/i.test(content)) {
19945
+ services.add("mongodb");
19946
+ }
19947
+ } catch {
19948
+ }
19949
+ }
19950
+ }
19951
+ async function detectFromEnvFiles(cwd, services) {
19952
+ const envFiles = [
19953
+ ".env",
19954
+ ".env.test",
19955
+ ".env.local",
19956
+ ".env.development"
19957
+ ];
19958
+ for (const file of envFiles) {
19959
+ const envPath = (0, import_path11.join)(cwd, file);
19960
+ if (!(0, import_fs4.existsSync)(envPath))
19961
+ continue;
19962
+ try {
19963
+ const content = await (0, import_promises13.readFile)(envPath, "utf-8");
19964
+ if (/postgres:\/\//i.test(content) || /postgresql:\/\//i.test(content)) {
19965
+ services.add("postgres");
19966
+ }
19967
+ if (/mysql:\/\//i.test(content)) {
19968
+ services.add("mysql");
19969
+ }
19970
+ if (/redis:\/\//i.test(content)) {
19971
+ services.add("redis");
19972
+ }
19973
+ if (/mongodb:\/\//i.test(content)) {
19974
+ services.add("mongodb");
19975
+ }
19976
+ if (/POSTGRES_HOST|PGHOST|DATABASE_URL.*postgres/i.test(content)) {
19977
+ services.add("postgres");
19978
+ }
19979
+ if (/MYSQL_HOST|DATABASE_URL.*mysql/i.test(content)) {
19980
+ services.add("mysql");
19981
+ }
19982
+ if (/REDIS_HOST|REDIS_URL/i.test(content)) {
19983
+ services.add("redis");
19984
+ }
19985
+ if (/MONGO_HOST|MONGODB_URL|MONGO_URL/i.test(content)) {
19986
+ services.add("mongodb");
19987
+ }
19988
+ } catch {
19989
+ }
19990
+ }
19991
+ }
19992
+ function getServiceDisplayName(service) {
19993
+ const names = {
19994
+ postgres: "PostgreSQL",
19995
+ mysql: "MySQL",
19996
+ redis: "Redis",
19997
+ mongodb: "MongoDB"
19998
+ };
19999
+ return names[service] || service;
20000
+ }
20001
+
20002
+ // dist/engine/sandbox/orchestrator.js
20003
+ async function runInSandbox(cwd, options) {
20004
+ const dockerInfo = await checkDockerAvailable();
20005
+ if (!dockerInfo.available) {
20006
+ console.warn("\u26A0\uFE0F Docker not available, skipping sandbox verification");
20007
+ console.warn(" Install Docker for isolated testing: https://docs.docker.com/get-docker/");
20008
+ return {
20009
+ passed: false,
20010
+ buildSuccess: false,
20011
+ testsPassed: false,
20012
+ services: [],
20013
+ logs: "",
20014
+ exitCode: -1,
20015
+ skipped: true,
20016
+ skipReason: "docker_unavailable"
20017
+ };
20018
+ }
20019
+ console.log("\u{1F433} Starting sandbox environment...");
20020
+ console.log(` Docker version: ${dockerInfo.version}`);
20021
+ const projectType = await detectProjectType(cwd);
20022
+ console.log(` Project type: ${projectType}`);
20023
+ const services = options.services || await detectRequiredServices(cwd);
20024
+ if (services.length > 0) {
20025
+ console.log(` Required services: ${services.map(getServiceDisplayName).join(", ")}`);
20026
+ }
20027
+ const imageName = options.dockerImage || "greenbump-verify";
20028
+ const dockerfilePath = (0, import_path12.join)(cwd, ".greenbump-Dockerfile");
20029
+ const composeFilePath = (0, import_path12.join)(cwd, ".greenbump-compose.yml");
20030
+ let projectName;
20031
+ const serviceHealths = [];
20032
+ try {
20033
+ const dockerfile = await generateDockerfile(projectType, cwd);
20034
+ await (0, import_promises14.writeFile)(dockerfilePath, dockerfile);
20035
+ let composeFile = "";
20036
+ if (services.length > 0) {
20037
+ composeFile = await generateComposeFile(services);
20038
+ if (composeFile) {
20039
+ await (0, import_promises14.writeFile)(composeFilePath, composeFile);
20040
+ }
20041
+ }
20042
+ await buildDockerImage(cwd, imageName, dockerfilePath);
20043
+ if (services.length > 0 && composeFile && dockerInfo.composeAvailable) {
20044
+ projectName = await startComposeServices(cwd, composeFilePath);
20045
+ await waitForServices(projectName, services, options.timeout ? options.timeout / 1e3 : 60);
20046
+ for (const service of services) {
20047
+ serviceHealths.push({
20048
+ name: service,
20049
+ healthy: true,
20050
+ displayName: getServiceDisplayName(service)
20051
+ });
20052
+ }
20053
+ } else if (services.length > 0 && !dockerInfo.composeAvailable) {
20054
+ console.warn("\u26A0\uFE0F docker-compose not available, skipping service startup");
20055
+ }
20056
+ console.log("\u{1F50D} Running verification in container...");
20057
+ const networkMode = projectName ? `${projectName}_default` : "bridge";
20058
+ const result = await runVerificationInContainer(cwd, {
20059
+ image: imageName,
20060
+ networkMode,
20061
+ timeout: options.timeout
20062
+ });
20063
+ if (result.exitCode === 0) {
20064
+ console.log("\u2705 Verification passed in sandbox");
20065
+ } else {
20066
+ console.error("\u274C Verification failed in sandbox");
20067
+ console.error(result.stderr);
20068
+ }
20069
+ return {
20070
+ passed: result.exitCode === 0,
20071
+ buildSuccess: result.buildSuccess,
20072
+ testsPassed: result.testsPassed,
20073
+ services: serviceHealths,
20074
+ logs: result.stdout + "\n" + result.stderr,
20075
+ exitCode: result.exitCode
20076
+ };
20077
+ } finally {
20078
+ if (!options.keepContainer) {
20079
+ await cleanupDocker(cwd, imageName, composeFilePath, projectName);
20080
+ } else {
20081
+ console.log("\u{1F527} Container kept for debugging (use --keep-container=false to cleanup)");
20082
+ }
20083
+ }
20084
+ }
20085
+
20086
+ // dist/engine/perf/metrics.js
20087
+ var import_fs5 = require("fs");
20088
+ var import_path13 = require("path");
20089
+ var import_promises15 = require("fs/promises");
20090
+ async function hasBuildScript(cwd) {
20091
+ const pkgPath = (0, import_path13.join)(cwd, "package.json");
20092
+ if (!(0, import_fs5.existsSync)(pkgPath))
20093
+ return false;
20094
+ try {
20095
+ const content = await (0, import_promises15.readFile)(pkgPath, "utf-8");
20096
+ const pkg = JSON.parse(content);
20097
+ return pkg.scripts?.build !== void 0;
20098
+ } catch {
20099
+ return false;
20100
+ }
20101
+ }
20102
+ async function hasTestScript(cwd) {
20103
+ const pkgPath = (0, import_path13.join)(cwd, "package.json");
20104
+ if (!(0, import_fs5.existsSync)(pkgPath))
20105
+ return false;
20106
+ try {
20107
+ const content = await (0, import_promises15.readFile)(pkgPath, "utf-8");
20108
+ const pkg = JSON.parse(content);
20109
+ return pkg.scripts?.test !== void 0;
20110
+ } catch {
20111
+ return false;
20112
+ }
20113
+ }
20114
+ async function getTotalSize(dirPath) {
20115
+ if (!(0, import_fs5.existsSync)(dirPath))
20116
+ return 0;
20117
+ try {
20118
+ const result = await exec("du", ["-sb", dirPath], { cwd: dirPath });
20119
+ const match = result.stdout.match(/^(\d+)/);
20120
+ return match ? parseInt(match[1], 10) : 0;
20121
+ } catch {
20122
+ return 0;
20123
+ }
20124
+ }
20125
+ async function captureBaseline(cwd) {
20126
+ console.log("\u{1F4CA} Capturing performance baseline...");
20127
+ const metrics = {
20128
+ timestamp: /* @__PURE__ */ new Date()
20129
+ };
20130
+ try {
20131
+ console.log(" Measuring install time...");
20132
+ const installStart = Date.now();
20133
+ await exec("npm", ["install"], { cwd });
20134
+ metrics.installTime = (Date.now() - installStart) / 1e3;
20135
+ console.log(` \u2713 Install: ${metrics.installTime.toFixed(1)}s`);
20136
+ if (await hasBuildScript(cwd)) {
20137
+ console.log(" Measuring build time...");
20138
+ const buildStart = Date.now();
20139
+ await exec("npm", ["run", "build"], { cwd });
20140
+ metrics.buildTime = (Date.now() - buildStart) / 1e3;
20141
+ console.log(` \u2713 Build: ${metrics.buildTime.toFixed(1)}s`);
20142
+ const distDir = (0, import_path13.join)(cwd, "dist");
20143
+ if ((0, import_fs5.existsSync)(distDir)) {
20144
+ metrics.bundleSize = await getTotalSize(distDir);
20145
+ console.log(` \u2713 Bundle: ${(metrics.bundleSize / 1024).toFixed(0)} KB`);
20146
+ }
20147
+ }
20148
+ if (await hasTestScript(cwd)) {
20149
+ console.log(" Measuring test time...");
20150
+ const testStart = Date.now();
20151
+ await exec("npm", ["test"], { cwd });
20152
+ metrics.testTime = (Date.now() - testStart) / 1e3;
20153
+ console.log(` \u2713 Test: ${metrics.testTime.toFixed(1)}s`);
20154
+ }
20155
+ } catch (error) {
20156
+ console.warn("\u26A0\uFE0F Warning: Failed to capture complete baseline:", error.message);
20157
+ }
20158
+ const greenbumpDir = (0, import_path13.join)(cwd, ".greenbump");
20159
+ if (!(0, import_fs5.existsSync)(greenbumpDir)) {
20160
+ await (0, import_promises15.mkdir)(greenbumpDir, { recursive: true });
20161
+ }
20162
+ const baselinePath = (0, import_path13.join)(greenbumpDir, "perf-baseline.json");
20163
+ await (0, import_promises15.writeFile)(baselinePath, JSON.stringify(metrics, null, 2));
20164
+ return metrics;
20165
+ }
20166
+ async function loadBaseline(cwd) {
20167
+ const baselinePath = (0, import_path13.join)(cwd, ".greenbump", "perf-baseline.json");
20168
+ if (!(0, import_fs5.existsSync)(baselinePath)) {
20169
+ return null;
20170
+ }
20171
+ try {
20172
+ const content = await (0, import_promises15.readFile)(baselinePath, "utf-8");
20173
+ const baseline = JSON.parse(content);
20174
+ if (baseline.timestamp) {
20175
+ baseline.timestamp = new Date(baseline.timestamp);
20176
+ }
20177
+ return baseline;
20178
+ } catch {
20179
+ return null;
20180
+ }
20181
+ }
20182
+
20183
+ // dist/engine/perf/regression.js
20184
+ var DEFAULT_THRESHOLDS = {
20185
+ installTime: 0.3,
20186
+ // 30% slower
20187
+ buildTime: 0.2,
20188
+ // 20% slower
20189
+ testTime: 0.3,
20190
+ // 30% slower
20191
+ bundleSize: 0.15,
20192
+ // 15% larger
20193
+ memoryPeak: 0.4
20194
+ // 40% more memory
20195
+ };
20196
+ function comparePerformance(baseline, current, thresholds = {}) {
20197
+ const mergedThresholds = { ...DEFAULT_THRESHOLDS, ...thresholds };
20198
+ const comparisons = [];
20199
+ for (const [metric, threshold] of Object.entries(mergedThresholds)) {
20200
+ const before = baseline[metric];
20201
+ const after = current[metric];
20202
+ if (before === void 0 || after === void 0)
20203
+ continue;
20204
+ if (before === 0)
20205
+ continue;
20206
+ const delta = after - before;
20207
+ const percentChange = delta / before * 100;
20208
+ const isRegression = percentChange > threshold * 100;
20209
+ comparisons.push({
20210
+ metric,
20211
+ before,
20212
+ after,
20213
+ delta,
20214
+ percentChange,
20215
+ isRegression,
20216
+ severity: isRegression ? percentChange > threshold * 200 ? "critical" : "warning" : "ok"
20217
+ });
20218
+ }
20219
+ return comparisons;
20220
+ }
20221
+ function hasRegressions(comparisons) {
20222
+ return comparisons.some((c2) => c2.isRegression);
20223
+ }
20224
+ function getMetricDisplayName(metric) {
20225
+ const names = {
20226
+ installTime: "Install time",
20227
+ buildTime: "Build time",
20228
+ testTime: "Test time",
20229
+ bundleSize: "Bundle size",
20230
+ memoryPeak: "Memory usage"
20231
+ };
20232
+ return names[metric] || metric;
20233
+ }
20234
+ function formatMetricValue(metric, value) {
20235
+ if (metric === "bundleSize") {
20236
+ if (value < 1024 * 1024) {
20237
+ return `${(value / 1024).toFixed(0)} KB`;
20238
+ }
20239
+ return `${(value / (1024 * 1024)).toFixed(1)} MB`;
20240
+ }
20241
+ if (metric === "memoryPeak") {
20242
+ return `${value.toFixed(0)} MB`;
20243
+ }
20244
+ return `${value.toFixed(1)}s`;
20245
+ }
20246
+ function formatPercentChange(percentChange) {
20247
+ const sign = percentChange > 0 ? "+" : "";
20248
+ return `${sign}${percentChange.toFixed(1)}%`;
20249
+ }
20250
+ function displayPerformanceComparison(comparisons) {
20251
+ if (comparisons.length === 0) {
20252
+ console.log("\u{1F4CA} No performance metrics to compare");
20253
+ return;
20254
+ }
20255
+ console.log("\n\u{1F4CA} Performance Comparison:");
20256
+ for (const comparison of comparisons) {
20257
+ const name = getMetricDisplayName(comparison.metric);
20258
+ const before = formatMetricValue(comparison.metric, comparison.before);
20259
+ const after = formatMetricValue(comparison.metric, comparison.after);
20260
+ const change = formatPercentChange(comparison.percentChange);
20261
+ if (comparison.isRegression) {
20262
+ const icon = comparison.severity === "critical" ? "\u{1F534}" : "\u26A0\uFE0F";
20263
+ console.log(` ${icon} ${name}: ${before} \u2192 ${after} (${change}) - REGRESSION`);
20264
+ } else {
20265
+ console.log(` \u2713 ${name}: ${before} \u2192 ${after} (${change})`);
20266
+ }
20267
+ }
20268
+ const regressionCount = comparisons.filter((c2) => c2.isRegression).length;
20269
+ if (regressionCount > 0) {
20270
+ console.log(`
20271
+ \u26A0\uFE0F ${regressionCount} performance regression(s) detected!`);
20272
+ console.log(" Review changes before committing.\n");
20273
+ } else {
20274
+ console.log("\n\u2705 No performance regressions detected\n");
20275
+ }
20276
+ }
20277
+
19298
20278
  // dist/engine/run.js
19299
20279
  var RunError = class extends Error {
19300
20280
  };
@@ -19338,6 +20318,11 @@ async function run(opts) {
19338
20318
  await createBranch(cwd, branch);
19339
20319
  log(`created branch ${branch}`);
19340
20320
  }
20321
+ let perfBaseline;
20322
+ if (opts.detectRegressions) {
20323
+ log("capturing performance baseline\u2026");
20324
+ perfBaseline = await captureBaseline(cwd);
20325
+ }
19341
20326
  log("running baseline build + tests\u2026");
19342
20327
  const baseline = await runChecks(pm, cwd, checkOverrides);
19343
20328
  if (!baseline.ok && !baseline.unverifiable) {
@@ -19412,13 +20397,63 @@ ${up.output}`);
19412
20397
  summary.usage = fix.usage;
19413
20398
  summary.editedFiles = fix.editedFiles;
19414
20399
  summary.testFilesTouched = fix.editedFiles.filter((f2) => /(^|\/)(test|tests|__tests__|spec)(\/|\.)|\.(test|spec)\./i.test(f2));
20400
+ log("running static analysis\u2026");
20401
+ const staticResults = await runStaticAnalysis(cwd, { strictLint: false });
20402
+ const typesFailed = staticResults.some((r2) => r2.stage === "types" && !r2.passed);
20403
+ if (typesFailed) {
20404
+ log("\u26A0\uFE0F TypeScript type check failed after fix");
20405
+ summary.needsReview = true;
20406
+ summary.staticAnalysisWarnings = staticResults.filter((r2) => !r2.passed).flatMap((r2) => r2.warnings || []);
20407
+ }
20408
+ const suspiciousChanges = await detectSuspiciousChanges(cwd);
20409
+ const criticalChanges = suspiciousChanges.filter((c2) => c2.severity === "critical");
20410
+ if (criticalChanges.length > 0) {
20411
+ log("\u26A0\uFE0F Suspicious changes detected (test modifications, large deletions)");
20412
+ summary.needsReview = true;
20413
+ summary.suspiciousChanges = suspiciousChanges.map((c2) => `${c2.type}: ${c2.file} - ${c2.description}`);
20414
+ }
20415
+ if (opts.sandbox) {
20416
+ log("running sandbox verification\u2026");
20417
+ const sandboxResult = await runInSandbox(cwd, {
20418
+ enabled: true,
20419
+ services: opts.services,
20420
+ keepContainer: opts.keepContainer,
20421
+ timeout: 600
20422
+ });
20423
+ if (!sandboxResult.skipped) {
20424
+ summary.sandboxResult = {
20425
+ passed: sandboxResult.passed,
20426
+ services: sandboxResult.services.map((s2) => s2.displayName)
20427
+ };
20428
+ if (!sandboxResult.passed) {
20429
+ log("\u26A0\uFE0F Sandbox verification failed");
20430
+ summary.needsReview = true;
20431
+ }
20432
+ }
20433
+ }
20434
+ if (opts.detectRegressions && perfBaseline) {
20435
+ log("checking for performance regressions\u2026");
20436
+ const currentMetrics = await loadBaseline(cwd);
20437
+ if (currentMetrics) {
20438
+ const comparisons = comparePerformance(perfBaseline, currentMetrics);
20439
+ displayPerformanceComparison(comparisons);
20440
+ if (hasRegressions(comparisons)) {
20441
+ log("\u26A0\uFE0F Performance regressions detected");
20442
+ summary.needsReview = true;
20443
+ summary.performanceRegression = {
20444
+ detected: true,
20445
+ details: comparisons.filter((c2) => c2.isRegression).map((c2) => `${c2.metric}: ${c2.percentChange.toFixed(1)}% slower/larger`)
20446
+ };
20447
+ }
20448
+ }
20449
+ }
19415
20450
  summary.needsReview = computeNeedsReview({
19416
20451
  unverifiable: false,
19417
20452
  neededFix: true,
19418
20453
  fixed: fix.fixed,
19419
20454
  testFilesTouched: summary.testFilesTouched,
19420
20455
  budgetExceeded: fix.budgetExceeded
19421
- });
20456
+ }) || summary.needsReview;
19422
20457
  await maybeCommit(cwd, summary, fix.fixed);
19423
20458
  summary.durationMs = Date.now() - startedAt;
19424
20459
  return summary;
@@ -19494,7 +20529,7 @@ function renderPrBody(s2) {
19494
20529
  }
19495
20530
 
19496
20531
  // dist/report.js
19497
- var import_promises12 = require("node:fs/promises");
20532
+ var import_promises16 = require("node:fs/promises");
19498
20533
  var REPORT_SCHEMA_VERSION = 1;
19499
20534
  function buildReport(runs) {
19500
20535
  return {
@@ -19504,7 +20539,7 @@ function buildReport(runs) {
19504
20539
  };
19505
20540
  }
19506
20541
  async function writeReport(path2, envelope) {
19507
- await (0, import_promises12.writeFile)(path2, JSON.stringify(envelope, null, 2) + "\n", "utf8");
20542
+ await (0, import_promises16.writeFile)(path2, JSON.stringify(envelope, null, 2) + "\n", "utf8");
19508
20543
  }
19509
20544
 
19510
20545
  // dist/action/github.js