bugzero 0.0.36 → 0.0.38

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 (3) hide show
  1. package/README.md +3 -1
  2. package/dist/index.js +119 -55
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -92,7 +92,7 @@ bugzero rescue
92
92
 
93
93
  ### Repair engine
94
94
 
95
- - **Autonomous repair loop.** `bugzero rescue` runs your build, lint, and tests. When something fails, it sends the error output — along with the relevant source files and directory listings as context — to an LLM, applies the fix the model proposes, and re-runs validation. It repeats, feeding any patch failures back to the model, until everything passes or the attempt limit is reached (3 by default; change it with `--max-attempts`). The loop is a stateful LangGraph graph: analyze, patch, verify.
95
+ - **Autonomous repair loop.** `bugzero rescue` runs your build, lint, and tests. When something fails, it sends the error output — along with the relevant source files and directory listings as context — to an LLM, applies the fix the model proposes, and re-runs validation. It repeats, feeding any patch failures back to the model, until everything passes or the attempt limit is reached (3 by default; change it with `--max-attempts`, which accepts any whole number of 1 or more). The loop is a stateful LangGraph graph: analyze, patch, verify.
96
96
  - **Multi-file patches.** One fix can edit several files at once — for example, a renamed export plus every file that imports it. All patches in an attempt are applied as a single atomic unit: if any one of them fails to apply, every file touched in that attempt is restored to its original content, so you never end up with a half-applied fix.
97
97
  - **Fuzzy patch matching.** LLMs rarely reproduce code byte-for-byte, so BugZero locates each patch in three tiers: an exact match first, then a whitespace-normalized line match (tolerates indentation and spacing drift), then a similarity-scored match (average per-line similarity of at least 85%). If two locations look equally plausible, it refuses to guess and reports a failed patch instead of risking an edit in the wrong place.
98
98
  - **Multi-model support.** Works with any model available on OpenRouter — DeepSeek, Claude, Gemini, GPT-4o, Llama, and more. Choose one interactively with `bugzero vibe`; the choice is saved per project in `.bugzerorc`.
@@ -101,11 +101,13 @@ bugzero rescue
101
101
 
102
102
  - **Backups on every patch.** Each file BugZero modifies gets a `.opspilot.bak` copy of its original content first.
103
103
  - **Dry-run mode.** `bugzero rescue --dry-run` runs validation and error analysis, but makes no fix attempts and never modifies your source files (a report is still written).
104
+ - **Prompt size limits.** Very long error output and very large source files are shortened before being sent to the model (for logs, the start and end are kept), so a big failure can't exceed the model's context window or waste tokens.
104
105
 
105
106
  ### Project awareness
106
107
 
107
108
  - **Framework detection.** Identifies Next.js, NestJS, Vite, Remix, Astro, SvelteKit, Nuxt, Angular, Create React App, or a plain TypeScript project from its config files and dependencies, and shows the result in the run banner.
108
109
  - **Package manager detection.** Runs build, lint, and test with the tool your project actually uses — npm, pnpm, yarn, or bun. It checks the `packageManager` field in `package.json` first, then `pnpm-workspace.yaml` or a lockfile, and falls back to npm.
110
+ - **Script-aware validation.** Only runs the build, lint, and test scripts your `package.json` actually defines. A missing script is reported as skipped rather than failed, so a project without a `lint` script no longer shows a false lint failure. Pointing `--path` at a directory with no `package.json` stops immediately with a clear error.
109
111
  - **Error parsing.** Recognizes TypeScript compiler errors (file, line, column, message), ESLint errors, and Jest failures to classify what broke and how many errors were found. Output in any other format is still passed to the model as raw logs.
110
112
 
111
113
  ### Integrations and output
package/dist/index.js CHANGED
@@ -469,15 +469,19 @@ var require_dist2 = __commonJS({
469
469
  detectPackageManager: () => detectPackageManager3,
470
470
  getRunScriptCommand: () => getRunScriptCommand,
471
471
  parseErrors: () => parseErrors,
472
+ readScripts: () => readScripts,
472
473
  runBuild: () => runBuild,
473
474
  runCommand: () => runCommand,
474
475
  runLint: () => runLint,
476
+ runScript: () => runScript,
475
477
  runTests: () => runTests,
476
478
  runValidationPipeline: () => runValidationPipeline
477
479
  });
478
480
  module2.exports = __toCommonJS(index_exports);
479
481
  var import_fs2 = __toESM2(require("fs"));
480
482
  var import_path2 = __toESM2(require("path"));
483
+ var import_fs3 = __toESM2(require("fs"));
484
+ var import_path3 = __toESM2(require("path"));
481
485
  var VALID_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
482
486
  var LOCKFILE_MANAGERS = [
483
487
  { file: "pnpm-workspace.yaml", manager: "pnpm" },
@@ -489,9 +493,9 @@ var require_dist2 = __commonJS({
489
493
  ];
490
494
  function detectPackageManager3(cwd = process.cwd()) {
491
495
  try {
492
- const pkgPath = import_path2.default.join(cwd, "package.json");
493
- if (import_fs2.default.existsSync(pkgPath)) {
494
- const pkg = JSON.parse(import_fs2.default.readFileSync(pkgPath, "utf-8"));
496
+ const pkgPath = import_path3.default.join(cwd, "package.json");
497
+ if (import_fs3.default.existsSync(pkgPath)) {
498
+ const pkg = JSON.parse(import_fs3.default.readFileSync(pkgPath, "utf-8"));
495
499
  if (typeof pkg.packageManager === "string") {
496
500
  const name = pkg.packageManager.split("@")[0].trim();
497
501
  if (VALID_MANAGERS.includes(name)) {
@@ -502,7 +506,7 @@ var require_dist2 = __commonJS({
502
506
  } catch {
503
507
  }
504
508
  for (const { file, manager } of LOCKFILE_MANAGERS) {
505
- if (import_fs2.default.existsSync(import_path2.default.join(cwd, file))) return manager;
509
+ if (import_fs3.default.existsSync(import_path3.default.join(cwd, file))) return manager;
506
510
  }
507
511
  return "npm";
508
512
  }
@@ -532,9 +536,26 @@ var require_dist2 = __commonJS({
532
536
  };
533
537
  }
534
538
  }
535
- var runBuild = (cwd) => runCommand(getRunScriptCommand(detectPackageManager3(cwd), "build"), cwd);
536
- var runLint = (cwd) => runCommand(getRunScriptCommand(detectPackageManager3(cwd), "lint"), cwd);
537
- var runTests = (cwd) => runCommand(getRunScriptCommand(detectPackageManager3(cwd), "test"), cwd);
539
+ function readScripts(cwd = process.cwd()) {
540
+ try {
541
+ const pkgPath = import_path2.default.join(cwd, "package.json");
542
+ if (!import_fs2.default.existsSync(pkgPath)) return [];
543
+ const pkg = JSON.parse(import_fs2.default.readFileSync(pkgPath, "utf-8"));
544
+ if (typeof pkg.scripts !== "object" || pkg.scripts === null) return [];
545
+ return Object.keys(pkg.scripts);
546
+ } catch {
547
+ return [];
548
+ }
549
+ }
550
+ async function runScript(script, cwd) {
551
+ if (!readScripts(cwd).includes(script)) {
552
+ return { success: true, output: "", skipped: true };
553
+ }
554
+ return runCommand(getRunScriptCommand(detectPackageManager3(cwd), script), cwd);
555
+ }
556
+ var runBuild = (cwd) => runScript("build", cwd);
557
+ var runLint = (cwd) => runScript("lint", cwd);
558
+ var runTests = (cwd) => runScript("test", cwd);
538
559
  var TS_ERROR_RE = /^(.+)\((\d+),(\d+)\):\s+error\s+TS\d+:\s+(.+)$/gm;
539
560
  var ESLINT_ERROR_RE = /^\s+(\d+):(\d+)\s+error\s+(.+)$/gm;
540
561
  var JEST_FAIL_RE = /● (.+)/g;
@@ -584,37 +605,29 @@ var require_dist2 = __commonJS({
584
605
  }
585
606
  return errors;
586
607
  }
587
- var import_fs22 = __toESM2(require("fs"));
588
- var import_path22 = __toESM2(require("path"));
608
+ var STEPS = [
609
+ { step: "build", label: "build", run: runBuild },
610
+ { step: "lint", label: "lint", run: runLint },
611
+ { step: "test", label: "tests", run: runTests }
612
+ ];
589
613
  async function runValidationPipeline(cwd) {
590
614
  const targetCwd = cwd ?? process.cwd();
591
- let scripts = [];
592
- try {
593
- const pkgPath = import_path22.default.resolve(targetCwd, "package.json");
594
- if (import_fs22.default.existsSync(pkgPath)) {
595
- const pkg = JSON.parse(import_fs22.default.readFileSync(pkgPath, "utf-8"));
596
- scripts = Object.keys(pkg.scripts || {});
615
+ const scripts = readScripts(targetCwd);
616
+ let ranAnyStep = false;
617
+ for (const { step, label, run } of STEPS) {
618
+ if (!scripts.includes(step)) {
619
+ console.log(`[BugZero] Skipping ${label}: no "${step}" script in package.json.`);
620
+ continue;
597
621
  }
598
- } catch {
599
- }
600
- console.log(`[BugZero] Running build...`);
601
- const buildRes = await runBuild(targetCwd);
602
- if (!buildRes.success) {
603
- return { success: false, step: "build", output: buildRes.output };
604
- }
605
- if (scripts.includes("lint")) {
606
- console.log(`[BugZero] Running lint...`);
607
- const lintRes = await runLint(targetCwd);
608
- if (!lintRes.success) {
609
- return { success: false, step: "lint", output: lintRes.output };
622
+ ranAnyStep = true;
623
+ console.log(`[BugZero] Running ${label}...`);
624
+ const result = await run(targetCwd);
625
+ if (!result.success) {
626
+ return { success: false, step, output: result.output };
610
627
  }
611
628
  }
612
- if (scripts.includes("test")) {
613
- console.log(`[BugZero] Running tests...`);
614
- const testRes = await runTests(targetCwd);
615
- if (!testRes.success) {
616
- return { success: false, step: "test", output: testRes.output };
617
- }
629
+ if (!ranAnyStep) {
630
+ console.warn(`[BugZero] No build, lint, or test scripts found in package.json \u2014 nothing to validate.`);
618
631
  }
619
632
  return { success: true, step: "none", output: "" };
620
633
  }
@@ -777,11 +790,26 @@ var require_dist4 = __commonJS({
777
790
  var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
778
791
  var index_exports = {};
779
792
  __export(index_exports, {
780
- buildAnalysisPrompt: () => buildAnalysisPrompt
793
+ buildAnalysisPrompt: () => buildAnalysisPrompt,
794
+ truncateLogs: () => truncateLogs
781
795
  });
782
796
  module2.exports = __toCommonJS(index_exports);
783
797
  var import_fs2 = __toESM2(require("fs"));
784
798
  var import_path2 = __toESM2(require("path"));
799
+ var MAX_LOG_CHARS = 2e4;
800
+ var MAX_FILE_CHARS = 2e4;
801
+ var MAX_CONTEXT_CHARS = 6e4;
802
+ function truncateLogs(logs, maxChars = MAX_LOG_CHARS) {
803
+ if (logs.length <= maxChars) return logs;
804
+ const head = logs.slice(0, Math.floor(maxChars * 0.6));
805
+ const tail = logs.slice(logs.length - Math.floor(maxChars * 0.4));
806
+ const omitted = logs.length - head.length - tail.length;
807
+ return `${head}
808
+
809
+ ... [${omitted} characters of log output omitted to fit the model's context] ...
810
+
811
+ ${tail}`;
812
+ }
785
813
  function resolveExistingPathPrefix(candidate) {
786
814
  let current = candidate.replace(/(?::\d+)+$/, "").replace(/['"()\[\]`]+$/, "").trim();
787
815
  while (current.length > 0) {
@@ -844,9 +872,15 @@ var require_dist4 = __commonJS({
844
872
  return;
845
873
  }
846
874
  if (processedFiles.has(filePath)) return;
875
+ if (context.length >= MAX_CONTEXT_CHARS) return;
847
876
  processedFiles.add(filePath);
848
877
  try {
849
- const content = import_fs2.default.readFileSync(filePath, "utf-8");
878
+ let content = import_fs2.default.readFileSync(filePath, "utf-8");
879
+ if (content.length > MAX_FILE_CHARS) {
880
+ const omitted = content.length - MAX_FILE_CHARS;
881
+ content = `${content.slice(0, MAX_FILE_CHARS)}
882
+ ... [file truncated: ${omitted} more characters not shown]`;
883
+ }
850
884
  context += `
851
885
  --- File: ${relPath} ---
852
886
  \`\`\`
@@ -931,10 +965,11 @@ ${w}`).join("\n\n")}
931
965
 
932
966
  **CRITICAL RULE:** Do NOT suggest modifying or creating the files listed under "Previous Failed Attempts" if they failed with a "File not found" or "Search string not found" error. You must inspect the compiler error logs below to find the actual file that exists and contains the error.`;
933
967
  }
968
+ compilerLogs = truncateLogs(compilerLogs);
934
969
  const contextSection = getContextFiles(compilerLogs, cwd);
935
970
  return `You are an expert software engineer specialising in fixing build, lint, and test failures.
936
971
 
937
- A project located at "${cwd}" failed to build. Here is the complete error output:
972
+ A project located at "${cwd}" failed to build. Here is the error output (very long output is shortened in the middle):
938
973
 
939
974
  \`\`\`
940
975
  ${compilerLogs}
@@ -1216,6 +1251,7 @@ var require_dist6 = __commonJS({
1216
1251
  var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
1217
1252
  var index_exports = {};
1218
1253
  __export(index_exports, {
1254
+ runRepairWorkflow: () => runRepairWorkflow,
1219
1255
  workflow: () => workflow
1220
1256
  });
1221
1257
  module2.exports = __toCommonJS(index_exports);
@@ -1373,6 +1409,12 @@ Any other files from that same suggestion were rolled back to keep the change at
1373
1409
  return "analyze";
1374
1410
  }
1375
1411
  var workflow = new import_langgraph2.StateGraph(GraphState).addNode("analyze", analyzeNode).addNode("apply_fix", applyFixNode).addNode("verify", verifyNode).addEdge(import_langgraph2.START, "analyze").addEdge("analyze", "apply_fix").addEdge("apply_fix", "verify").addConditionalEdges("verify", shouldRetry).compile();
1412
+ function runRepairWorkflow(input) {
1413
+ return workflow.invoke(
1414
+ { ...input, attempts: 0, success: false },
1415
+ { recursionLimit: input.maxAttempts * 3 + 5 }
1416
+ );
1417
+ }
1376
1418
  }
1377
1419
  });
1378
1420
 
@@ -1409,10 +1451,12 @@ var require_dist7 = __commonJS({
1409
1451
  var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
1410
1452
  var index_exports = {};
1411
1453
  __export(index_exports, {
1454
+ DEFAULT_MAX_ATTEMPTS: () => DEFAULT_MAX_ATTEMPTS,
1412
1455
  detectFramework: () => detectFramework2,
1413
1456
  detectPackageManager: () => import_analyzers2.detectPackageManager,
1414
1457
  orchestrate: () => orchestrate2,
1415
- orchestrateAndFix: () => orchestrateAndFix2
1458
+ orchestrateAndFix: () => orchestrateAndFix2,
1459
+ resolveMaxAttempts: () => resolveMaxAttempts2
1416
1460
  });
1417
1461
  module2.exports = __toCommonJS(index_exports);
1418
1462
  var import_fs2 = __toESM2(require("fs"));
@@ -1444,12 +1488,32 @@ var require_dist7 = __commonJS({
1444
1488
  }
1445
1489
  var import_analyzers = require_dist2();
1446
1490
  var import_agents = require_dist6();
1491
+ var import_fs3 = __toESM2(require("fs"));
1492
+ var import_path3 = __toESM2(require("path"));
1493
+ var import_url = require("url");
1494
+ var DEFAULT_MAX_ATTEMPTS = 3;
1495
+ function resolveMaxAttempts2(value) {
1496
+ const attempts = value ?? DEFAULT_MAX_ATTEMPTS;
1497
+ if (!Number.isInteger(attempts) || attempts < 1) {
1498
+ throw new Error(`--max-attempts must be a whole number of 1 or more (received ${value}).`);
1499
+ }
1500
+ return attempts;
1501
+ }
1447
1502
  var import_fs22 = __toESM2(require("fs"));
1448
1503
  var import_path22 = __toESM2(require("path"));
1449
- var import_url = require("url");
1504
+ function assertNodeProject(cwd) {
1505
+ if (!import_fs22.default.existsSync(import_path22.default.join(cwd, "package.json"))) {
1506
+ throw new Error(`No package.json found in ${cwd}. Point --path at a Node.js project.`);
1507
+ }
1508
+ }
1450
1509
  var import_ai_provider = require_dist3();
1510
+ function statusLabel(result) {
1511
+ if (result.skipped) return "\u23ED\uFE0F Skipped (no script defined)";
1512
+ return result.success ? "\u2705 Passed" : "\u274C Failed";
1513
+ }
1451
1514
  async function orchestrate2(options = {}) {
1452
1515
  const cwd = options.cwd ?? process.cwd();
1516
+ assertNodeProject(cwd);
1453
1517
  const [build, lint, test] = await Promise.all([
1454
1518
  (0, import_analyzers.runBuild)(cwd),
1455
1519
  (0, import_analyzers.runLint)(cwd),
@@ -1465,7 +1529,8 @@ var require_dist7 = __commonJS({
1465
1529
  }
1466
1530
  async function orchestrateAndFix2(options = {}) {
1467
1531
  const cwd = options.cwd ?? process.cwd();
1468
- const maxAttempts = options.maxAttempts ?? 3;
1532
+ assertNodeProject(cwd);
1533
+ const maxAttempts = resolveMaxAttempts2(options.maxAttempts);
1469
1534
  console.log(`[BugZero] Running validation pipeline in: ${cwd}`);
1470
1535
  const pipelineResult = await (0, import_analyzers.runValidationPipeline)(cwd);
1471
1536
  if (pipelineResult.success) {
@@ -1496,12 +1561,10 @@ var require_dist7 = __commonJS({
1496
1561
  error: "Dry-run: analysis complete, no files modified."
1497
1562
  };
1498
1563
  }
1499
- const finalState = await import_agents.workflow.invoke({
1564
+ const finalState = await (0, import_agents.runRepairWorkflow)({
1500
1565
  cwd,
1501
1566
  logs: pipelineResult.output,
1502
- maxAttempts,
1503
- attempts: 0,
1504
- success: false
1567
+ maxAttempts
1505
1568
  });
1506
1569
  const result = {
1507
1570
  success: finalState.success,
@@ -1597,9 +1660,9 @@ Generate the Markdown report directly. Do not wrap it in markdown block fences (
1597
1660
  **Framework:** \`${detectFramework2(cwd)}\`
1598
1661
 
1599
1662
  ## \u{1F4CA} Status Summary
1600
- - **Build:** ${build.success ? "\u2705 Passed" : "\u274C Failed"}
1601
- - **Lint:** ${lint.success ? "\u2705 Passed" : "\u274C Failed"}
1602
- - **Test:** ${test.success ? "\u2705 Passed" : "\u274C Failed"}
1663
+ - **Build:** ${statusLabel(build)}
1664
+ - **Lint:** ${statusLabel(lint)}
1665
+ - **Test:** ${statusLabel(test)}
1603
1666
 
1604
1667
  ## \u274C Failed Checks & Error Output
1605
1668
  ${logs}
@@ -1618,9 +1681,9 @@ ${logs}
1618
1681
  **Framework:** \`${detectFramework2(cwd)}\`
1619
1682
 
1620
1683
  ## \u{1F4CA} Status Summary
1621
- - **Build:** \u2705 Passed
1622
- - **Lint:** \u2705 Passed
1623
- - **Test:** \u2705 Passed
1684
+ - **Build:** ${statusLabel(build)}
1685
+ - **Lint:** ${statusLabel(lint)}
1686
+ - **Test:** ${statusLabel(test)}
1624
1687
 
1625
1688
  \u{1F389} **All checks passed! Your project is in perfect shape.**`;
1626
1689
  }
@@ -1725,9 +1788,9 @@ ${rawOutput}
1725
1788
 
1726
1789
  ---
1727
1790
  *Report generated by **BugZero** on ${(/* @__PURE__ */ new Date()).toLocaleString()}*`;
1728
- const reportPath = import_path22.default.resolve(cwd, "bugzero-report.md");
1791
+ const reportPath = import_path3.default.resolve(cwd, "bugzero-report.md");
1729
1792
  try {
1730
- import_fs22.default.writeFileSync(reportPath, content, "utf-8");
1793
+ import_fs3.default.writeFileSync(reportPath, content, "utf-8");
1731
1794
  const clickableUrl = (0, import_url.pathToFileURL)(reportPath).toString();
1732
1795
  console.log(`
1733
1796
  \u{1F4DD} Generated report successfully!
@@ -1769,9 +1832,10 @@ program.command("hunt").description("Run build, lint, and tests \u2014 report is
1769
1832
  `);
1770
1833
  const { build, lint, test } = await (0, import_core.orchestrate)({ cwd });
1771
1834
  console.log("\u2500\u2500 Pipeline Summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1772
- console.log(` Build: ${build.success ? "\u2705 Passed" : "\u274C Failed (see report)"}`);
1773
- console.log(` Lint: ${lint.success ? "\u2705 Passed" : "\u274C Failed (see report)"}`);
1774
- console.log(` Tests: ${test.success ? "\u2705 Passed" : "\u274C Failed (see report)"}`);
1835
+ const label = (r) => r.skipped ? "\u23ED\uFE0F Skipped (no script)" : r.success ? "\u2705 Passed" : "\u274C Failed (see report)";
1836
+ console.log(` Build: ${label(build)}`);
1837
+ console.log(` Lint: ${label(lint)}`);
1838
+ console.log(` Tests: ${label(test)}`);
1775
1839
  console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1776
1840
  const allPassed = build.success && lint.success && test.success;
1777
1841
  console.log(`
@@ -1787,7 +1851,7 @@ ${allPassed ? "\u2705 All checks passed" : "\u274C Issues found \u2014 run 'bugz
1787
1851
  program.command("rescue").description("Detect build failures and automatically apply AI-generated fixes").option("-p, --path <dir>", "Path to the target project", ".").option("-n, --max-attempts <number>", "Maximum AI fix attempts", "3").option("--dry-run", "Analyse only \u2014 print what would be fixed without writing files", false).action(async (opts) => {
1788
1852
  try {
1789
1853
  const cwd = import_path.default.resolve(opts.path);
1790
- const maxAttempts = parseInt(opts.maxAttempts, 10);
1854
+ const maxAttempts = (0, import_core.resolveMaxAttempts)(Number(opts.maxAttempts));
1791
1855
  const dryRun = opts.dryRun;
1792
1856
  const framework = (0, import_core.detectFramework)(cwd);
1793
1857
  const packageManager = (0, import_core.detectPackageManager)(cwd);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bugzero",
3
- "version": "0.0.36",
3
+ "version": "0.0.38",
4
4
  "description": "Autonomous AI-powered CLI and GitHub Action that detects build, lint, and test failures, generates multi-file patches, applies them atomically with fuzzy matching, and re-verifies until your project is green.",
5
5
  "license": "MIT",
6
6
  "author": "Saksham Agarwal (https://github.com/Saksham1800t)",