bugzero 0.0.31 → 0.0.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
 
2
- > bugzero@0.0.31 build /home/runner/work/Bugzero/Bugzero/apps/bugzero
2
+ > bugzero@0.0.32 build /home/runner/work/Bugzero/Bugzero/apps/bugzero
3
3
  > tsup src/index.ts --format cjs --dts
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -7,8 +7,8 @@
7
7
  CLI tsup v8.5.1
8
8
  CLI Target: es2022
9
9
  CJS Build start
10
- CJS dist/index.js 74.91 KB
11
- CJS ⚡️ Build success in 34ms
10
+ CJS dist/index.js 77.80 KB
11
+ CJS ⚡️ Build success in 32ms
12
12
  DTS Build start
13
- DTS ⚡️ Build success in 1100ms
13
+ DTS ⚡️ Build success in 1160ms
14
14
  DTS dist/index.d.ts 20.00 B
package/README.md CHANGED
@@ -75,6 +75,7 @@ bugzero rescue
75
75
  | Framework detection | Automatically identifies your project's tooling setup |
76
76
  | Safe file patching | Every modified file receives a `.opspilot.bak` backup |
77
77
  | Fuzzy patch matching | Falls back from exact match to whitespace-normalized and similarity-scored matching when the AI's snippet doesn't match the file verbatim |
78
+ | Multi-file patches | A single fix attempt can edit several files at once, applied atomically — if any file's patch fails, every file touched in that attempt is rolled back |
78
79
  | Error type parsing | Parses TypeScript, ESLint, and Jest error formats |
79
80
  | Dry-run mode | Analyze and report without writing any files |
80
81
  | Local model config | Save preferred model to `.bugzerorc` per project |
package/dist/index.js CHANGED
@@ -908,21 +908,27 @@ ${compilerLogs}
908
908
  Your task:
909
909
  1. Root Cause: Identify the root cause of the build failure.
910
910
  2. Codebase Context: Read the provided file contents and directory listings under "\u{1F4C1} Codebase Context" to locate the correct files, imports, and file paths. Do not guess filenames or contents that are not in the context.
911
- 3. Patch: Provide the minimal, precise fix.
911
+ 3. Patch: Provide the minimal, precise fix. If the fix genuinely requires changes in more than one file (for example, a renamed export and every place that imports it), include one patch entry per file \u2014 all patches in your response are applied together, atomically.
912
912
 
913
913
  Reply with ONLY a valid JSON object in this exact shape \u2014 no markdown fences, no extra text:
914
914
  {
915
915
  "rootCause": "One-sentence explanation of the error",
916
- "filePath": "relative/path/from/project/root/to/file.ts",
917
- "search": "exact code that currently exists in the file and must be replaced",
918
- "replace": "the corrected code that replaces the search string",
919
- "explanation": "short explanation of what you changed and why"
916
+ "explanation": "short explanation of what you changed and why",
917
+ "patches": [
918
+ {
919
+ "filePath": "relative/path/from/project/root/to/file.ts",
920
+ "search": "exact code that currently exists in the file and must be replaced",
921
+ "replace": "the corrected code that replaces the search string"
922
+ }
923
+ ]
920
924
  }
921
925
 
922
926
  Critical rules:
923
- - "filePath" must be a relative path from the project root (no leading /).
924
- - "search" must be an EXACT character-for-character substring of the current file content.
925
- - "replace" must be syntactically valid code.
927
+ - "patches" must contain at least one entry. Only include more than one when the fix truly requires editing multiple files \u2014 most fixes need just one.
928
+ - Each "filePath" must be a relative path from the project root (no leading /).
929
+ - Each "search" must be an EXACT character-for-character substring of that file's current content.
930
+ - Each "replace" must be syntactically valid code.
931
+ - If two patches target the same file, list them in the order they should be applied, and make sure each "search" reflects the file's content *after* the previous patch in this list has been applied to it.
926
932
  - Do NOT add backticks, markdown, or any text outside the JSON object.`;
927
933
  }
928
934
  }
@@ -962,6 +968,7 @@ var require_dist5 = __commonJS({
962
968
  var index_exports = {};
963
969
  __export(index_exports, {
964
970
  applyPatch: () => applyPatch,
971
+ applyPatches: () => applyPatches,
965
972
  findMatch: () => findMatch,
966
973
  lineSimilarity: () => lineSimilarity
967
974
  });
@@ -1092,6 +1099,62 @@ var require_dist5 = __commonJS({
1092
1099
  return { success: false, error: err.message };
1093
1100
  }
1094
1101
  }
1102
+ var import_fs2 = __toESM2(require("fs"));
1103
+ var import_path22 = __toESM2(require("path"));
1104
+ function rollback(originalContent, backupPaths) {
1105
+ for (const [absolutePath, content] of originalContent) {
1106
+ try {
1107
+ import_fs2.default.writeFileSync(absolutePath, content, "utf-8");
1108
+ } catch {
1109
+ }
1110
+ }
1111
+ for (const backupPath of backupPaths) {
1112
+ try {
1113
+ import_fs2.default.unlinkSync(backupPath);
1114
+ } catch {
1115
+ }
1116
+ }
1117
+ }
1118
+ function applyPatches(cwd, patches) {
1119
+ if (patches.length === 0) {
1120
+ return { success: false, error: "No patches to apply.", applied: [] };
1121
+ }
1122
+ const originalContent = /* @__PURE__ */ new Map();
1123
+ const backupPaths = /* @__PURE__ */ new Set();
1124
+ const applied = [];
1125
+ for (const patch of patches) {
1126
+ const absolutePath = import_path22.default.resolve(cwd, patch.filePath);
1127
+ if (!originalContent.has(absolutePath)) {
1128
+ try {
1129
+ originalContent.set(absolutePath, import_fs2.default.readFileSync(absolutePath, "utf-8"));
1130
+ } catch {
1131
+ rollback(originalContent, backupPaths);
1132
+ return {
1133
+ success: false,
1134
+ error: `Failed to patch "${patch.filePath}": File not found: ${absolutePath}`,
1135
+ applied: []
1136
+ };
1137
+ }
1138
+ }
1139
+ const result = applyPatch(absolutePath, patch.search, patch.replace);
1140
+ if (result.backupPath) backupPaths.add(result.backupPath);
1141
+ if (!result.success) {
1142
+ rollback(originalContent, backupPaths);
1143
+ return {
1144
+ success: false,
1145
+ error: `Failed to patch "${patch.filePath}": ${result.error}`,
1146
+ applied: []
1147
+ };
1148
+ }
1149
+ applied.push({
1150
+ filePath: patch.filePath,
1151
+ absolutePath,
1152
+ backupPath: result.backupPath,
1153
+ matchStrategy: result.matchStrategy
1154
+ });
1155
+ }
1156
+ return { success: true, applied };
1157
+ }
1095
1158
  }
1096
1159
  });
1097
1160
 
@@ -1099,11 +1162,9 @@ var require_dist5 = __commonJS({
1099
1162
  var require_dist6 = __commonJS({
1100
1163
  "../../packages/agents/dist/index.js"(exports2, module2) {
1101
1164
  "use strict";
1102
- var __create2 = Object.create;
1103
1165
  var __defProp2 = Object.defineProperty;
1104
1166
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
1105
1167
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
1106
- var __getProtoOf2 = Object.getPrototypeOf;
1107
1168
  var __hasOwnProp2 = Object.prototype.hasOwnProperty;
1108
1169
  var __export = (target, all) => {
1109
1170
  for (var name in all)
@@ -1117,14 +1178,6 @@ var require_dist6 = __commonJS({
1117
1178
  }
1118
1179
  return to;
1119
1180
  };
1120
- var __toESM2 = (mod, isNodeMode, target) => (target = mod != null ? __create2(__getProtoOf2(mod)) : {}, __copyProps2(
1121
- // If the importer is in node compatibility mode or this is not an ESM
1122
- // file that has been converted to a CommonJS file using a Babel-
1123
- // compatible transform (i.e. "__esModule" has not been set), then set
1124
- // "default" to the CommonJS "module.exports" for node compatibility.
1125
- isNodeMode || !mod || !mod.__esModule ? __defProp2(target, "default", { value: mod, enumerable: true }) : target,
1126
- mod
1127
- ));
1128
1181
  var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
1129
1182
  var index_exports = {};
1130
1183
  __export(index_exports, {
@@ -1132,7 +1185,6 @@ var require_dist6 = __commonJS({
1132
1185
  });
1133
1186
  module2.exports = __toCommonJS(index_exports);
1134
1187
  var import_langgraph2 = require("@langchain/langgraph");
1135
- var import_path2 = __toESM2(require("path"));
1136
1188
  var import_langgraph = require("@langchain/langgraph");
1137
1189
  var GraphState = import_langgraph.Annotation.Root({
1138
1190
  /** Absolute path to the project being fixed */
@@ -1204,7 +1256,12 @@ var require_dist6 = __commonJS({
1204
1256
  const cleaned = response.replace(/```json\n?/g, "").replace(/```\n?/g, "").trim();
1205
1257
  const jsonMatch = cleaned.match(/\{[\s\S]*\}/);
1206
1258
  if (jsonMatch) {
1207
- fixSuggestion = JSON.parse(jsonMatch[0]);
1259
+ const parsed = JSON.parse(jsonMatch[0]);
1260
+ if (Array.isArray(parsed.patches) && parsed.patches.length > 0) {
1261
+ fixSuggestion = parsed;
1262
+ } else {
1263
+ console.warn('[BugZero] AI response JSON did not contain a non-empty "patches" array.');
1264
+ }
1208
1265
  } else {
1209
1266
  console.warn("[BugZero] AI response did not contain a valid JSON object.");
1210
1267
  }
@@ -1225,26 +1282,27 @@ var require_dist6 = __commonJS({
1225
1282
  console.warn("[BugZero] No fix suggestion available \u2014 skipping patch.");
1226
1283
  return {};
1227
1284
  }
1228
- const { filePath, search, replace, explanation } = state.fixSuggestion;
1229
- const absolutePath = import_path2.default.resolve(state.cwd, filePath);
1230
- console.log(`[BugZero] Patching: ${filePath}`);
1285
+ const { patches, explanation } = state.fixSuggestion;
1286
+ const fileList = patches.map((p) => p.filePath).join(", ");
1287
+ console.log(`[BugZero] Patching ${patches.length} file${patches.length === 1 ? "" : "s"}: ${fileList}`);
1231
1288
  console.log(`[BugZero] Reason: ${explanation}`);
1232
- const result = (0, import_patcher.applyPatch)(absolutePath, search, replace);
1289
+ const result = (0, import_patcher.applyPatches)(state.cwd, patches);
1233
1290
  if (!result.success) {
1234
1291
  console.error(`[BugZero] Patch failed: ${result.error}`);
1235
1292
  const updatedLogs = `${state.logs}
1236
1293
 
1237
- \u26A0\uFE0F [BugZero System Warning] Your previous patch suggestion for "${filePath}" failed with the following error:
1294
+ \u26A0\uFE0F [BugZero System Warning] Your previous patch suggestion failed with the following error:
1238
1295
  - ${result.error}
1239
1296
 
1240
- Please check if the file path is correct relative to the project root and make sure you target the correct file that contains the build error.`;
1297
+ Any other files from that same suggestion were rolled back to keep the change atomic. Please check if the file path(s) are correct relative to the project root and make sure you target the correct file(s) that contain the build error.`;
1241
1298
  return {
1242
1299
  logs: updatedLogs
1243
1300
  };
1244
- } else {
1245
- console.log(`[BugZero] Backup saved to: ${result.backupPath}`);
1246
- if (result.matchStrategy && result.matchStrategy !== "exact") {
1247
- console.log(`[BugZero] Note: exact match failed \u2014 patch applied via ${result.matchStrategy} matching.`);
1301
+ }
1302
+ for (const applied of result.applied) {
1303
+ console.log(`[BugZero] Backup saved to: ${applied.backupPath}`);
1304
+ if (applied.matchStrategy !== "exact") {
1305
+ console.log(`[BugZero] Note: exact match failed for ${applied.filePath} \u2014 patch applied via ${applied.matchStrategy} matching.`);
1248
1306
  }
1249
1307
  }
1250
1308
  return {};
@@ -1432,6 +1490,30 @@ var require_dist7 = __commonJS({
1432
1490
  });
1433
1491
  return result;
1434
1492
  }
1493
+ function formatPatchesForPrompt(appliedFix) {
1494
+ return appliedFix.patches.map(
1495
+ (p, i) => `File ${i + 1}: ${p.filePath}
1496
+ - Search code:
1497
+ ${p.search}
1498
+ - Replace code:
1499
+ ${p.replace}`
1500
+ ).join("\n\n");
1501
+ }
1502
+ function formatPatchesMarkdown(appliedFix) {
1503
+ return appliedFix.patches.map(
1504
+ (p) => `**File:** \`${p.filePath}\`
1505
+
1506
+ **Search (Before):**
1507
+ \`\`\`typescript
1508
+ ${p.search}
1509
+ \`\`\`
1510
+
1511
+ **Replace (After):**
1512
+ \`\`\`typescript
1513
+ ${p.replace}
1514
+ \`\`\``
1515
+ ).join("\n\n---\n\n");
1516
+ }
1435
1517
  async function writeReport(cwd, type, data) {
1436
1518
  let content = "";
1437
1519
  const provider = new import_ai_provider.OpenRouterProvider();
@@ -1510,14 +1592,12 @@ ${logs}
1510
1592
  const { success, attempts, maxAttempts, appliedFix, error, rawOutput, modelUsed, promptTokens, completionTokens } = data;
1511
1593
  if (success && appliedFix) {
1512
1594
  console.log("[BugZero] Calling AI to write solution summary report...");
1513
- const prompt = `You are an expert software engineer assistant. An automated AI agent successfully resolved a build/lint/test failure in a project located at "${cwd}" after ${attempts} attempts by applying a patch to "${appliedFix.filePath}".
1595
+ const patchedFiles = appliedFix.patches.map((p) => p.filePath).join(", ");
1596
+ const prompt = `You are an expert software engineer assistant. An automated AI agent successfully resolved a build/lint/test failure in a project located at "${cwd}" after ${attempts} attempts by applying a patch to: ${patchedFiles}.
1514
1597
  Here is the patch details:
1515
1598
  - Root Cause: ${appliedFix.rootCause}
1516
1599
  - Explanation: ${appliedFix.explanation}
1517
- - Search code:
1518
- ${appliedFix.search}
1519
- - Replace code:
1520
- ${appliedFix.replace}
1600
+ ${formatPatchesForPrompt(appliedFix)}
1521
1601
 
1522
1602
  Please generate a professional, beautifully formatted Markdown report summarizing:
1523
1603
  1. **Repair Summary:** Celebrate the success and explain the fix.
@@ -1550,20 +1630,12 @@ Generate the Markdown report directly. Do not wrap it in markdown block fences (
1550
1630
  - **Token Usage:** ${promptTokens !== void 0 && completionTokens !== void 0 ? `Prompt: **${promptTokens}** | Completion: **${completionTokens}** | Total: **${promptTokens + completionTokens}**` : "*N/A*"}
1551
1631
 
1552
1632
  ## \u{1F6E0}\uFE0F Applied Solution
1553
- - **Target File:** \`${appliedFix.filePath}\`
1633
+ - **Target File(s):** ${appliedFix.patches.map((p) => `\`${p.filePath}\``).join(", ")}
1554
1634
  - **Root Cause:** ${appliedFix.rootCause}
1555
1635
  - **Explanation:** ${appliedFix.explanation}
1556
1636
 
1557
1637
  ### \u{1F4DD} Code Changes
1558
- **Search (Before):**
1559
- \`\`\`typescript
1560
- ${appliedFix.search}
1561
- \`\`\`
1562
-
1563
- **Replace (After):**
1564
- \`\`\`typescript
1565
- ${appliedFix.replace}
1566
- \`\`\`
1638
+ ${formatPatchesMarkdown(appliedFix)}
1567
1639
  `;
1568
1640
  }
1569
1641
  } else {
@@ -1693,7 +1765,8 @@ program.command("rescue").description("Detect build failures and automatically a
1693
1765
  console.log(` Tokens: Prompt: ${result.promptTokens} | Completion: ${result.completionTokens} | Total: ${result.promptTokens + result.completionTokens}`);
1694
1766
  }
1695
1767
  if (result.appliedFix) {
1696
- console.log(` File: ${result.appliedFix.filePath}`);
1768
+ const files = result.appliedFix.patches.map((p) => p.filePath).join(", ");
1769
+ console.log(` File${result.appliedFix.patches.length === 1 ? "" : "s"}: ${files}`);
1697
1770
  console.log(` Reason: ${result.appliedFix.explanation}`);
1698
1771
  console.log(` Root cause: ${result.appliedFix.rootCause}`);
1699
1772
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bugzero",
3
- "version": "0.0.31",
3
+ "version": "0.0.32",
4
4
  "description": "An autonomous, AI-powered CLI tool for automatic build error detection and repair.",
5
5
  "license": "MIT",
6
6
  "bin": {
package/src/index.ts CHANGED
@@ -75,7 +75,8 @@ program
75
75
  console.log(` Tokens: Prompt: ${result.promptTokens} | Completion: ${result.completionTokens} | Total: ${result.promptTokens + result.completionTokens}`);
76
76
  }
77
77
  if (result.appliedFix) {
78
- console.log(` File: ${result.appliedFix.filePath}`);
78
+ const files = result.appliedFix.patches.map((p) => p.filePath).join(", ");
79
+ console.log(` File${result.appliedFix.patches.length === 1 ? "" : "s"}: ${files}`);
79
80
  console.log(` Reason: ${result.appliedFix.explanation}`);
80
81
  console.log(` Root cause: ${result.appliedFix.rootCause}`);
81
82
  }