bugzero 0.0.42 → 0.0.44

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 +20 -3
  2. package/dist/index.js +137 -25
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -114,6 +114,7 @@ bugzero rescue
114
114
  ### Integrations and output
115
115
 
116
116
  - **Markdown reports.** Every `hunt` and `rescue` run writes `bugzero-report.md` to the target project: a pass/fail summary of build, lint, and tests, the full error logs, the model used, token usage, and before/after diffs for every file patched. The terminal prints a clickable link straight to it.
117
+ - **JSON output.** Add `--json` to `hunt` or `rescue` to print the result as a single JSON document on stdout, with all human-readable logs moved to stderr and the exit code unchanged (0 on success, 1 otherwise), so scripts and CI can parse it. See [Scripting and CI](#scripting-and-ci).
117
118
  - **GitHub Action mode.** Run the same repair loop in CI. Instead of patching a local checkout, the Action commits every changed file in one atomic commit through the GitHub API and opens a pull request — see [GitHub Action Mode](#github-action-mode) below.
118
119
 
119
120
  ---
@@ -191,7 +192,7 @@ OPENROUTER_API_KEY=your_openrouter_api_key_here
191
192
  ## Commands
192
193
 
193
194
  ### `bugzero hunt`
194
- Run build, lint, and test checks. Reports all failures without modifying any files. Accepts `--timeout <seconds>`, like `rescue`.
195
+ Run build, lint, and test checks. Reports all failures without modifying any files. Accepts `--timeout <seconds>` and `--json`, like `rescue`.
195
196
 
196
197
  ```bash
197
198
  bugzero hunt --path /path/to/your/project
@@ -213,6 +214,7 @@ bugzero rescue --path /path/to/your/project
213
214
  | `--max-attempts <n>` | `-n` | `3` | Maximum AI fix iterations before giving up |
214
215
  | `--timeout <seconds>` | `-t` | none | Stop any build, lint, or test command that runs longer than this many seconds |
215
216
  | `--dry-run` | | `false` | Analyze only — no files will be written |
217
+ | `--json` | | `false` | Print the result as one JSON document on stdout (logs go to stderr) |
216
218
 
217
219
  </details>
218
220
 
@@ -236,6 +238,19 @@ bugzero vibe
236
238
 
237
239
  ---
238
240
 
241
+ ## Scripting and CI
242
+
243
+ `hunt` and `rescue` accept `--json`. Stdout then contains exactly one JSON document and every human-readable log line goes to stderr, so the output is safe to pipe. Exit codes don't change: `0` when everything passes or is fixed, `1` otherwise.
244
+
245
+ ```bash
246
+ bugzero hunt --json | jq '.steps'
247
+ bugzero rescue --json --dry-run | jq -r '.outcome'
248
+ ```
249
+
250
+ `hunt` reports each step (`build`, `lint`, `test`) as `passed`, `failed`, or `skipped`; failed steps include the command's output and whether it timed out. `rescue` reports an `outcome` of `already-passing`, `fixed`, `failed`, or `dry-run`, along with the model, token usage, and the files and root cause of any fix. Both include the path to the generated report. If a run can't start at all (for example, because of an invalid option), stdout is `{ "success": false, "error": "..." }`.
251
+
252
+ ---
253
+
239
254
  ## Configuration
240
255
 
241
256
  BugZero reads a `.bugzerorc` JSON file in your target project directory to pick up model preferences:
@@ -269,8 +284,10 @@ BugZero provides clear, actionable output for common failure modes:
269
284
  | Error | Message |
270
285
  |---|---|
271
286
  | Invalid API key (401) | Prompts you to check your `OPENROUTER_API_KEY` |
272
- | Rate limit exceeded (429) | Prompts you to check your OpenRouter credit balance |
273
- | Model not found | Displays the invalid model ID and suggests running `bugzero vibe` |
287
+ | Insufficient credits (402) | Says your OpenRouter balance is too low, links to the credits page, and suggests switching to a cheaper or free model with `bugzero vibe` |
288
+ | Rate limit exceeded (429) | Says OpenRouter is rate limiting and suggests waiting, switching models, or adding credits |
289
+ | Prompt too large for the model | Says the prompt exceeded the model's context window and suggests a larger-context model with `bugzero vibe` |
290
+ | Model not found | Names the unavailable model ID and suggests running `bugzero vibe` |
274
291
  | Patch not applied | Reports exact mismatch between AI suggestion and file content, after exhausting exact, whitespace, and fuzzy matching |
275
292
  | Max attempts reached | Exits cleanly with the last error log for manual review |
276
293
 
package/dist/index.js CHANGED
@@ -393,7 +393,7 @@ var require_dist = __commonJS({
393
393
  while (true) {
394
394
  const envPath = import_path2.default.join(dir, ".env");
395
395
  if (import_fs2.default.existsSync(envPath)) {
396
- import_dotenv.default.config({ path: envPath });
396
+ import_dotenv.default.config({ path: envPath, quiet: true });
397
397
  return;
398
398
  }
399
399
  const parent = import_path2.default.dirname(dir);
@@ -402,7 +402,7 @@ var require_dist = __commonJS({
402
402
  }
403
403
  dir = parent;
404
404
  }
405
- import_dotenv.default.config();
405
+ import_dotenv.default.config({ quiet: true });
406
406
  }
407
407
  loadEnv();
408
408
  var env = {
@@ -704,12 +704,47 @@ var require_dist3 = __commonJS({
704
704
  var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
705
705
  var index_exports = {};
706
706
  __export(index_exports, {
707
- OpenRouterProvider: () => OpenRouterProvider
707
+ OpenRouterProvider: () => OpenRouterProvider,
708
+ getErrorHints: () => getErrorHints
708
709
  });
709
710
  module2.exports = __toCommonJS(index_exports);
710
711
  var import_axios = __toESM2(require("axios"));
711
712
  var import_fs2 = __toESM2(require("fs"));
712
713
  var import_path2 = __toESM2(require("path"));
714
+ var CREDITS_PATTERN = /insufficient credits|credit balance|add more credits|requires more credits|token_limit_exceeded/i;
715
+ var CONTEXT_PATTERN = /context length|context window|maximum context|context_length_exceeded|string_too_long|too many tokens|reduce the length/i;
716
+ var MODEL_PATTERN = /not a valid model|no endpoints found|model.{0,40}(not found|does not exist|unavailable)/i;
717
+ function getErrorHints({ status, message, model }) {
718
+ if (status === 401) {
719
+ return ["\u{1F449} Please check if your OPENROUTER_API_KEY is correct and active."];
720
+ }
721
+ if (status === 402 || CREDITS_PATTERN.test(message)) {
722
+ return [
723
+ "\u{1F449} Your OpenRouter credit balance is too low for this request.",
724
+ " Add credits at https://openrouter.ai/settings/credits, or run 'bugzero vibe' to switch to a cheaper model",
725
+ " (models that cost nothing are listed at https://openrouter.ai/models?max_price=0)."
726
+ ];
727
+ }
728
+ if (status === 429) {
729
+ return [
730
+ "\u{1F449} OpenRouter is rate limiting these requests. Wait a moment and try again.",
731
+ " Free models have stricter limits; run 'bugzero vibe' to switch models, or add credits to raise them."
732
+ ];
733
+ }
734
+ if (CONTEXT_PATTERN.test(message)) {
735
+ return [
736
+ `\u{1F449} The prompt was too large for the context window of "${model}".`,
737
+ " Run 'bugzero vibe' to pick a model with a larger context window, or try again on a smaller failing scope."
738
+ ];
739
+ }
740
+ if (status === 404 || MODEL_PATTERN.test(message)) {
741
+ return [
742
+ `\u{1F449} The model "${model}" isn't available on OpenRouter (it wasn't found, or no provider currently serves it).`,
743
+ " Run 'bugzero vibe' to choose a different model."
744
+ ];
745
+ }
746
+ return [];
747
+ }
713
748
  function resolveModel(cwd) {
714
749
  try {
715
750
  const configPath = import_path2.default.resolve(cwd ?? process.cwd(), ".bugzerorc");
@@ -772,21 +807,9 @@ var require_dist3 = __commonJS({
772
807
  }
773
808
  console.error(`
774
809
  \u274C [BugZero] OpenRouter API Error: ${msg}`);
775
- if (error.response?.status === 401) {
776
- console.error(`\u{1F449} Please check if your OPENROUTER_API_KEY is correct and active.`);
777
- } else if (error.response?.status === 429 || error.response?.status === 402 || /credit/i.test(msg) || /token/i.test(msg)) {
778
- console.error(`
779
- \u{1F449} You have insufficient OpenRouter credits (or too low balance) for the selected model.`);
780
- console.error(`\u{1F449} We suggest using the 'bugzero vibe' command to change to a FREE model:`);
781
- console.error(` 1. Run the command: npx bugzero@latest vibe (or bugzero vibe)`);
782
- console.error(` 2. Select "Custom Model" and enter one of these free model IDs:`);
783
- console.error(` - google/gemma-2-9b-it:free`);
784
- console.error(` - meta-llama/llama-3-8b-instruct:free`);
785
- console.error(` - mistralai/mistral-7b-instruct:free`);
786
- console.error(` - microsoft/phi-3-medium-128k-instruct:free`);
787
- console.error(` - nvidia/llama-3.1-nemotron-70b-instruct:free
788
- `);
789
- }
810
+ const hints = getErrorHints({ status: error.response?.status, message: msg, model });
811
+ for (const line of hints) console.error(line);
812
+ if (hints.length > 0) console.error();
790
813
  throw new Error(`OpenRouter API failed: ${msg}`);
791
814
  }
792
815
  }
@@ -1494,6 +1517,7 @@ var require_dist7 = __commonJS({
1494
1517
  var index_exports = {};
1495
1518
  __export(index_exports, {
1496
1519
  DEFAULT_MAX_ATTEMPTS: () => DEFAULT_MAX_ATTEMPTS,
1520
+ REPORT_FILENAME: () => REPORT_FILENAME2,
1497
1521
  detectFramework: () => detectFramework2,
1498
1522
  detectPackageManager: () => import_analyzers2.detectPackageManager,
1499
1523
  orchestrate: () => orchestrate2,
@@ -1602,6 +1626,7 @@ Re-run without \`--dry-run\` to let BugZero attempt a fix.
1602
1626
  }
1603
1627
  }
1604
1628
  var import_ai_provider = require_dist3();
1629
+ var REPORT_FILENAME2 = "bugzero-report.md";
1605
1630
  function statusLabel(result) {
1606
1631
  if (result.skipped) return "\u23ED\uFE0F Skipped (no script defined)";
1607
1632
  return result.success ? "\u2705 Passed" : "\u274C Failed";
@@ -1900,7 +1925,7 @@ ${rawOutput}
1900
1925
 
1901
1926
  ---
1902
1927
  *Report generated by **BugZero** on ${(/* @__PURE__ */ new Date()).toLocaleString()}*`;
1903
- const reportPath = import_path3.default.resolve(cwd, "bugzero-report.md");
1928
+ const reportPath = import_path3.default.resolve(cwd, REPORT_FILENAME2);
1904
1929
  try {
1905
1930
  import_fs3.default.writeFileSync(reportPath, content, "utf-8");
1906
1931
  const clickableUrl = (0, import_url.pathToFileURL)(reportPath).toString();
@@ -1929,6 +1954,62 @@ var import_fs = __toESM(require("fs"));
1929
1954
  var import_path = __toESM(require("path"));
1930
1955
  var import_core = __toESM(require_dist7());
1931
1956
 
1957
+ // src/json-output.ts
1958
+ function stepStatus(step) {
1959
+ if (step.skipped) return "skipped";
1960
+ return step.success ? "passed" : "failed";
1961
+ }
1962
+ function stepJson(step) {
1963
+ const status = stepStatus(step);
1964
+ if (status !== "failed") return { status };
1965
+ return { status, timedOut: step.timedOut === true, output: step.output ?? "" };
1966
+ }
1967
+ function buildHuntJson(info, steps) {
1968
+ return {
1969
+ command: "hunt",
1970
+ success: steps.build.success && steps.lint.success && steps.test.success,
1971
+ path: info.path,
1972
+ framework: info.framework,
1973
+ packageManager: info.packageManager,
1974
+ steps: { build: stepJson(steps.build), lint: stepJson(steps.lint), test: stepJson(steps.test) },
1975
+ reportPath: info.reportPath
1976
+ };
1977
+ }
1978
+ function buildRescueJson(result, info) {
1979
+ let outcome;
1980
+ if (result.success && result.attempts === 0) outcome = "already-passing";
1981
+ else if (result.success) outcome = "fixed";
1982
+ else if (info.dryRun) outcome = "dry-run";
1983
+ else outcome = "failed";
1984
+ const tokens = result.promptTokens !== void 0 && result.completionTokens !== void 0 ? {
1985
+ prompt: result.promptTokens,
1986
+ completion: result.completionTokens,
1987
+ total: result.promptTokens + result.completionTokens
1988
+ } : null;
1989
+ return {
1990
+ command: "rescue",
1991
+ outcome,
1992
+ success: result.success,
1993
+ dryRun: info.dryRun,
1994
+ path: info.path,
1995
+ framework: info.framework,
1996
+ packageManager: info.packageManager,
1997
+ attempts: result.attempts,
1998
+ model: result.modelUsed || null,
1999
+ tokens,
2000
+ fix: result.appliedFix ? {
2001
+ rootCause: result.appliedFix.rootCause,
2002
+ explanation: result.appliedFix.explanation,
2003
+ files: result.appliedFix.patches.map((p) => p.filePath)
2004
+ } : null,
2005
+ error: result.error ?? null,
2006
+ reportPath: info.reportPath
2007
+ };
2008
+ }
2009
+ function buildErrorJson(message) {
2010
+ return { success: false, error: message };
2011
+ }
2012
+
1932
2013
  // src/summary.ts
1933
2014
  function tokenLines(result) {
1934
2015
  if (result.promptTokens === void 0 || result.completionTokens === void 0) return [];
@@ -1988,9 +2069,23 @@ function parseTimeout(raw) {
1988
2069
  (0, import_core.resolveTimeoutMs)(seconds);
1989
2070
  return seconds;
1990
2071
  }
2072
+ var JSON_DESCRIPTION = "Print the result as one JSON document on stdout (human-readable logs go to stderr)";
2073
+ function routeLogsToStderr() {
2074
+ const toStderr = (...args) => console.error(...args);
2075
+ console.log = toStderr;
2076
+ console.info = toStderr;
2077
+ console.debug = toStderr;
2078
+ }
2079
+ function finish(exitCode, json) {
2080
+ if (json === void 0) process.exit(exitCode);
2081
+ process.stdout.write(`${JSON.stringify(json, null, 2)}
2082
+ `, () => process.exit(exitCode));
2083
+ }
1991
2084
  var program = new import_commander.Command();
1992
2085
  program.name("bugzero").description("AI-powered build issue fixer \u2014 detects and repairs build errors automatically").version(version);
1993
- program.command("hunt").description("Run build, lint, and tests \u2014 report issues without modifying files").option("-p, --path <dir>", "Path to the target project", ".").option("-t, --timeout <seconds>", TIMEOUT_DESCRIPTION).action(async (opts) => {
2086
+ program.command("hunt").description("Run build, lint, and tests \u2014 report issues without modifying files").option("-p, --path <dir>", "Path to the target project", ".").option("-t, --timeout <seconds>", TIMEOUT_DESCRIPTION).option("--json", JSON_DESCRIPTION).action(async (opts) => {
2087
+ const json = opts.json === true;
2088
+ if (json) routeLogsToStderr();
1994
2089
  try {
1995
2090
  const cwd = import_path.default.resolve(opts.path);
1996
2091
  const timeoutSeconds = parseTimeout(opts.timeout);
@@ -2014,14 +2109,22 @@ program.command("hunt").description("Run build, lint, and tests \u2014 report is
2014
2109
  console.log(`
2015
2110
  ${allPassed ? "\u2705 All checks passed" : "\u274C Issues found \u2014 run 'bugzero rescue' to repair"}
2016
2111
  `);
2017
- process.exit(allPassed ? 0 : 1);
2112
+ finish(
2113
+ allPassed ? 0 : 1,
2114
+ json ? buildHuntJson(
2115
+ { path: cwd, framework, packageManager, reportPath: import_path.default.resolve(cwd, import_core.REPORT_FILENAME) },
2116
+ { build, lint, test }
2117
+ ) : void 0
2118
+ );
2018
2119
  } catch (error) {
2019
2120
  console.error(`
2020
2121
  \u274C [BugZero] Execution failed: ${error.message || error}`);
2021
- process.exit(1);
2122
+ finish(1, json ? buildErrorJson(String(error.message || error)) : void 0);
2022
2123
  }
2023
2124
  });
2024
- 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("-t, --timeout <seconds>", TIMEOUT_DESCRIPTION).option("--dry-run", "Analyse only \u2014 print what would be fixed without writing files", false).action(async (opts) => {
2125
+ 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("-t, --timeout <seconds>", TIMEOUT_DESCRIPTION).option("--dry-run", "Analyse only \u2014 print what would be fixed without writing files", false).option("--json", JSON_DESCRIPTION).action(async (opts) => {
2126
+ const json = opts.json === true;
2127
+ if (json) routeLogsToStderr();
2025
2128
  try {
2026
2129
  const cwd = import_path.default.resolve(opts.path);
2027
2130
  const maxAttempts = (0, import_core.resolveMaxAttempts)(Number(opts.maxAttempts));
@@ -2043,11 +2146,20 @@ program.command("rescue").description("Detect build failures and automatically a
2043
2146
  const summary = buildRescueSummary(result, { dryRun });
2044
2147
  for (const line of summary.lines) console.log(line);
2045
2148
  console.log();
2046
- process.exit(summary.exitCode);
2149
+ finish(
2150
+ summary.exitCode,
2151
+ json ? buildRescueJson(result, {
2152
+ path: cwd,
2153
+ framework,
2154
+ packageManager,
2155
+ dryRun,
2156
+ reportPath: import_path.default.resolve(cwd, import_core.REPORT_FILENAME)
2157
+ }) : void 0
2158
+ );
2047
2159
  } catch (error) {
2048
2160
  console.error(`
2049
2161
  \u274C [BugZero] Execution failed: ${error.message || error}`);
2050
- process.exit(1);
2162
+ finish(1, json ? buildErrorJson(String(error.message || error)) : void 0);
2051
2163
  }
2052
2164
  });
2053
2165
  program.command("protect").description("[Deprecated] Use 'hunt' instead").action(async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bugzero",
3
- "version": "0.0.42",
3
+ "version": "0.0.44",
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)",