bugzero 0.0.41 → 0.0.42

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 +106 -42
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -102,6 +102,7 @@ bugzero rescue
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 (it still writes a report that diagnoses the failures it found). It exits with code 1 if it found failures and 0 if everything already passes, so you can use it as a check in scripts.
104
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.
105
+ - **Command timeouts.** `--timeout <seconds>` (available on both `hunt` and `rescue`) stops any build, lint, or test command that runs longer than that. It kills the whole process tree rather than just the shell, so a hung test run cannot linger in the background, and the step is reported as failed along with whatever output it produced. Without it, a command that hangs will hang BugZero too, so it is worth setting in CI.
105
106
 
106
107
  ### Project awareness
107
108
 
@@ -190,7 +191,7 @@ OPENROUTER_API_KEY=your_openrouter_api_key_here
190
191
  ## Commands
191
192
 
192
193
  ### `bugzero hunt`
193
- Run build, lint, and test checks. Reports all failures without modifying any files.
194
+ Run build, lint, and test checks. Reports all failures without modifying any files. Accepts `--timeout <seconds>`, like `rescue`.
194
195
 
195
196
  ```bash
196
197
  bugzero hunt --path /path/to/your/project
@@ -210,6 +211,7 @@ bugzero rescue --path /path/to/your/project
210
211
  |---|---|---|---|
211
212
  | `--path <dir>` | `-p` | `.` | Path to the target project |
212
213
  | `--max-attempts <n>` | `-n` | `3` | Maximum AI fix iterations before giving up |
214
+ | `--timeout <seconds>` | `-t` | none | Stop any build, lint, or test command that runs longer than this many seconds |
213
215
  | `--dry-run` | | `false` | Analyze only — no files will be written |
214
216
 
215
217
  </details>
package/dist/index.js CHANGED
@@ -479,9 +479,9 @@ var require_dist2 = __commonJS({
479
479
  });
480
480
  module2.exports = __toCommonJS(index_exports);
481
481
  var import_fs2 = __toESM2(require("fs"));
482
- var import_path2 = __toESM2(require("path"));
483
- var import_fs3 = __toESM2(require("fs"));
484
482
  var import_path3 = __toESM2(require("path"));
483
+ var import_fs3 = __toESM2(require("fs"));
484
+ var import_path2 = __toESM2(require("path"));
485
485
  var VALID_MANAGERS = ["npm", "pnpm", "yarn", "bun"];
486
486
  var LOCKFILE_MANAGERS = [
487
487
  { file: "pnpm-workspace.yaml", manager: "pnpm" },
@@ -493,7 +493,7 @@ var require_dist2 = __commonJS({
493
493
  ];
494
494
  function detectPackageManager3(cwd = process.cwd()) {
495
495
  try {
496
- const pkgPath = import_path3.default.join(cwd, "package.json");
496
+ const pkgPath = import_path2.default.join(cwd, "package.json");
497
497
  if (import_fs3.default.existsSync(pkgPath)) {
498
498
  const pkg = JSON.parse(import_fs3.default.readFileSync(pkgPath, "utf-8"));
499
499
  if (typeof pkg.packageManager === "string") {
@@ -506,39 +506,76 @@ var require_dist2 = __commonJS({
506
506
  } catch {
507
507
  }
508
508
  for (const { file, manager } of LOCKFILE_MANAGERS) {
509
- if (import_fs3.default.existsSync(import_path3.default.join(cwd, file))) return manager;
509
+ if (import_fs3.default.existsSync(import_path2.default.join(cwd, file))) return manager;
510
510
  }
511
511
  return "npm";
512
512
  }
513
513
  function getRunScriptCommand(manager, script) {
514
514
  return `${manager} run ${script}`;
515
515
  }
516
- async function runCommand(command, cwd) {
516
+ var import_child_process = require("child_process");
517
+ var import_path22 = __toESM2(require("path"));
518
+ var import_tree_kill = __toESM2(require("tree-kill"));
519
+ function killProcessTree(pid) {
520
+ if (process.platform === "win32") {
521
+ const systemRoot = process.env.SystemRoot ?? process.env.windir ?? "C:\\Windows";
522
+ const taskkill = import_path22.default.join(systemRoot, "System32", "taskkill.exe");
523
+ (0, import_child_process.spawnSync)(taskkill, ["/pid", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
524
+ return;
525
+ }
526
+ (0, import_tree_kill.default)(pid, "SIGKILL");
527
+ }
528
+ var KILL_GRACE_MS = 5e3;
529
+ async function runCommand(command, cwd, options = {}) {
517
530
  const { execa } = await import("execa");
518
- try {
519
- const result = await execa(command, {
520
- shell: true,
521
- cwd: cwd ?? process.cwd(),
522
- all: true,
523
- env: {
524
- ...process.env,
525
- CI: "true"
526
- }
527
- });
528
- return {
529
- success: true,
530
- output: result.all ?? result.stdout
531
- };
532
- } catch (err) {
531
+ const subprocess = execa(command, {
532
+ shell: true,
533
+ cwd: cwd ?? process.cwd(),
534
+ all: true,
535
+ env: {
536
+ ...process.env,
537
+ CI: "true"
538
+ }
539
+ });
540
+ const timedOutResult = (output, confirmedStopped) => {
541
+ const seconds = options.timeoutMs / 1e3;
542
+ const note = confirmedStopped ? "and was stopped" : "and could not be confirmed stopped";
533
543
  return {
534
544
  success: false,
535
- output: err.all ?? err.stderr ?? err.stdout ?? err.message
545
+ timedOut: true,
546
+ output: `${output}
547
+
548
+ [BugZero] Command timed out after ${seconds}s ${note}: ${command}`
536
549
  };
550
+ };
551
+ let timedOut = false;
552
+ let timer;
553
+ let graceTimer;
554
+ const gaveUp = new Promise((resolve) => {
555
+ if (options.timeoutMs === void 0) return;
556
+ timer = setTimeout(() => {
557
+ timedOut = true;
558
+ if (subprocess.pid !== void 0) killProcessTree(subprocess.pid);
559
+ graceTimer = setTimeout(() => resolve(timedOutResult("", false)), KILL_GRACE_MS);
560
+ }, options.timeoutMs);
561
+ });
562
+ const finished = subprocess.then(
563
+ (result) => ({ success: true, output: result.all ?? result.stdout }),
564
+ (err) => {
565
+ const output = err.all ?? err.stderr ?? err.stdout ?? err.message;
566
+ return timedOut ? timedOutResult(output, true) : { success: false, output };
567
+ }
568
+ );
569
+ try {
570
+ return await Promise.race([finished, gaveUp]);
571
+ } finally {
572
+ clearTimeout(timer);
573
+ clearTimeout(graceTimer);
537
574
  }
538
575
  }
539
576
  function readScripts(cwd = process.cwd()) {
540
577
  try {
541
- const pkgPath = import_path2.default.join(cwd, "package.json");
578
+ const pkgPath = import_path3.default.join(cwd, "package.json");
542
579
  if (!import_fs2.default.existsSync(pkgPath)) return [];
543
580
  const pkg = JSON.parse(import_fs2.default.readFileSync(pkgPath, "utf-8"));
544
581
  if (typeof pkg.scripts !== "object" || pkg.scripts === null) return [];
@@ -547,15 +584,15 @@ var require_dist2 = __commonJS({
547
584
  return [];
548
585
  }
549
586
  }
550
- async function runScript(script, cwd) {
587
+ async function runScript(script, cwd, options) {
551
588
  if (!readScripts(cwd).includes(script)) {
552
589
  return { success: true, output: "", skipped: true };
553
590
  }
554
- return runCommand(getRunScriptCommand(detectPackageManager3(cwd), script), cwd);
591
+ return runCommand(getRunScriptCommand(detectPackageManager3(cwd), script), cwd, options);
555
592
  }
556
- var runBuild = (cwd) => runScript("build", cwd);
557
- var runLint = (cwd) => runScript("lint", cwd);
558
- var runTests = (cwd) => runScript("test", cwd);
593
+ var runBuild = (cwd, options) => runScript("build", cwd, options);
594
+ var runLint = (cwd, options) => runScript("lint", cwd, options);
595
+ var runTests = (cwd, options) => runScript("test", cwd, options);
559
596
  var TS_ERROR_RE = /^(.+)\((\d+),(\d+)\):\s+error\s+TS\d+:\s+(.+)$/gm;
560
597
  var ESLINT_ERROR_RE = /^\s+(\d+):(\d+)\s+error\s+(.+)$/gm;
561
598
  var JEST_FAIL_RE = /● (.+)/g;
@@ -610,7 +647,7 @@ var require_dist2 = __commonJS({
610
647
  { step: "lint", label: "lint", run: runLint },
611
648
  { step: "test", label: "tests", run: runTests }
612
649
  ];
613
- async function runValidationPipeline(cwd) {
650
+ async function runValidationPipeline(cwd, options) {
614
651
  const targetCwd = cwd ?? process.cwd();
615
652
  const scripts = readScripts(targetCwd);
616
653
  let ranAnyStep = false;
@@ -621,7 +658,7 @@ var require_dist2 = __commonJS({
621
658
  }
622
659
  ranAnyStep = true;
623
660
  console.log(`[BugZero] Running ${label}...`);
624
- const result = await run(targetCwd);
661
+ const result = await run(targetCwd, options);
625
662
  if (!result.success) {
626
663
  return { success: false, step, output: result.output };
627
664
  }
@@ -1307,6 +1344,11 @@ var require_dist6 = __commonJS({
1307
1344
  completionTokens: (0, import_langgraph.Annotation)({
1308
1345
  reducer: (old, value) => old + value,
1309
1346
  default: () => 0
1347
+ }),
1348
+ /** Per-command timeout in milliseconds for validation steps (undefined = no limit) */
1349
+ timeoutMs: (0, import_langgraph.Annotation)({
1350
+ reducer: (_, value) => value,
1351
+ default: () => void 0
1310
1352
  })
1311
1353
  });
1312
1354
  var import_ai_provider = require_dist3();
@@ -1387,7 +1429,7 @@ Any other files from that same suggestion were rolled back to keep the change at
1387
1429
  };
1388
1430
  }
1389
1431
  console.log("[BugZero] Re-running validation pipeline to verify fix\u2026");
1390
- const result = await (0, import_analyzers.runValidationPipeline)(state.cwd);
1432
+ const result = await (0, import_analyzers.runValidationPipeline)(state.cwd, { timeoutMs: state.timeoutMs });
1391
1433
  if (result.success) {
1392
1434
  console.log("[BugZero] \u2705 Validation succeeded!");
1393
1435
  } else {
@@ -1456,7 +1498,8 @@ var require_dist7 = __commonJS({
1456
1498
  detectPackageManager: () => import_analyzers2.detectPackageManager,
1457
1499
  orchestrate: () => orchestrate2,
1458
1500
  orchestrateAndFix: () => orchestrateAndFix2,
1459
- resolveMaxAttempts: () => resolveMaxAttempts2
1501
+ resolveMaxAttempts: () => resolveMaxAttempts2,
1502
+ resolveTimeoutMs: () => resolveTimeoutMs2
1460
1503
  });
1461
1504
  module2.exports = __toCommonJS(index_exports);
1462
1505
  var import_fs2 = __toESM2(require("fs"));
@@ -1537,6 +1580,13 @@ Re-run without \`--dry-run\` to let BugZero attempt a fix.
1537
1580
  `;
1538
1581
  }
1539
1582
  var DEFAULT_MAX_ATTEMPTS = 3;
1583
+ function resolveTimeoutMs2(seconds) {
1584
+ if (seconds === void 0) return void 0;
1585
+ if (!Number.isFinite(seconds) || seconds <= 0) {
1586
+ throw new Error(`--timeout must be a positive number of seconds (received ${seconds}).`);
1587
+ }
1588
+ return Math.round(seconds * 1e3);
1589
+ }
1540
1590
  function resolveMaxAttempts2(value) {
1541
1591
  const attempts = value ?? DEFAULT_MAX_ATTEMPTS;
1542
1592
  if (!Number.isInteger(attempts) || attempts < 1) {
@@ -1559,10 +1609,11 @@ Re-run without \`--dry-run\` to let BugZero attempt a fix.
1559
1609
  async function orchestrate2(options = {}) {
1560
1610
  const cwd = options.cwd ?? process.cwd();
1561
1611
  assertNodeProject(cwd);
1612
+ const timeoutMs = resolveTimeoutMs2(options.timeoutSeconds);
1562
1613
  const [build, lint, test] = await Promise.all([
1563
- (0, import_analyzers.runBuild)(cwd),
1564
- (0, import_analyzers.runLint)(cwd),
1565
- (0, import_analyzers.runTests)(cwd)
1614
+ (0, import_analyzers.runBuild)(cwd, { timeoutMs }),
1615
+ (0, import_analyzers.runLint)(cwd, { timeoutMs }),
1616
+ (0, import_analyzers.runTests)(cwd, { timeoutMs })
1566
1617
  ]);
1567
1618
  await writeReport(cwd, "hunt", {
1568
1619
  success: build.success && lint.success && test.success,
@@ -1576,8 +1627,9 @@ Re-run without \`--dry-run\` to let BugZero attempt a fix.
1576
1627
  const cwd = options.cwd ?? process.cwd();
1577
1628
  assertNodeProject(cwd);
1578
1629
  const maxAttempts = resolveMaxAttempts2(options.maxAttempts);
1630
+ const timeoutMs = resolveTimeoutMs2(options.timeoutSeconds);
1579
1631
  console.log(`[BugZero] Running validation pipeline in: ${cwd}`);
1580
- const pipelineResult = await (0, import_analyzers.runValidationPipeline)(cwd);
1632
+ const pipelineResult = await (0, import_analyzers.runValidationPipeline)(cwd, { timeoutMs });
1581
1633
  if (pipelineResult.success) {
1582
1634
  console.log("[BugZero] Validation already passing \u2014 nothing to fix.");
1583
1635
  await writeReport(cwd, "rescue", {
@@ -1610,7 +1662,8 @@ Re-run without \`--dry-run\` to let BugZero attempt a fix.
1610
1662
  const finalState = await (0, import_agents.runRepairWorkflow)({
1611
1663
  cwd,
1612
1664
  logs: pipelineResult.output,
1613
- maxAttempts
1665
+ maxAttempts,
1666
+ timeoutMs
1614
1667
  });
1615
1668
  const result = {
1616
1669
  success: finalState.success,
@@ -1928,20 +1981,29 @@ function buildRescueSummary(result, options) {
1928
1981
 
1929
1982
  // src/index.ts
1930
1983
  var { version } = JSON.parse(import_fs.default.readFileSync(import_path.default.join(__dirname, "..", "package.json"), "utf-8"));
1984
+ var TIMEOUT_DESCRIPTION = "Stop any build/lint/test command that runs longer than this many seconds";
1985
+ function parseTimeout(raw) {
1986
+ if (raw === void 0) return void 0;
1987
+ const seconds = Number(raw);
1988
+ (0, import_core.resolveTimeoutMs)(seconds);
1989
+ return seconds;
1990
+ }
1931
1991
  var program = new import_commander.Command();
1932
1992
  program.name("bugzero").description("AI-powered build issue fixer \u2014 detects and repairs build errors automatically").version(version);
1933
- program.command("hunt").description("Run build, lint, and tests \u2014 report issues without modifying files").option("-p, --path <dir>", "Path to the target project", ".").action(async (opts) => {
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) => {
1934
1994
  try {
1935
1995
  const cwd = import_path.default.resolve(opts.path);
1996
+ const timeoutSeconds = parseTimeout(opts.timeout);
1936
1997
  const framework = (0, import_core.detectFramework)(cwd);
1937
1998
  const packageManager = (0, import_core.detectPackageManager)(cwd);
1938
1999
  console.log(`
1939
2000
  \u{1F50D} BugZero \u2014 Analyzing project`);
1940
2001
  console.log(` Path: ${cwd}`);
1941
2002
  console.log(` Framework: ${framework}`);
1942
- console.log(` Package manager: ${packageManager}
1943
- `);
1944
- const { build, lint, test } = await (0, import_core.orchestrate)({ cwd });
2003
+ console.log(` Package manager: ${packageManager}`);
2004
+ if (timeoutSeconds !== void 0) console.log(` Timeout: ${timeoutSeconds}s per command`);
2005
+ console.log();
2006
+ const { build, lint, test } = await (0, import_core.orchestrate)({ cwd, timeoutSeconds });
1945
2007
  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");
1946
2008
  const label = (r) => r.skipped ? "\u23ED\uFE0F Skipped (no script)" : r.success ? "\u2705 Passed" : "\u274C Failed (see report)";
1947
2009
  console.log(` Build: ${label(build)}`);
@@ -1959,10 +2021,11 @@ ${allPassed ? "\u2705 All checks passed" : "\u274C Issues found \u2014 run 'bugz
1959
2021
  process.exit(1);
1960
2022
  }
1961
2023
  });
1962
- 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) => {
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) => {
1963
2025
  try {
1964
2026
  const cwd = import_path.default.resolve(opts.path);
1965
2027
  const maxAttempts = (0, import_core.resolveMaxAttempts)(Number(opts.maxAttempts));
2028
+ const timeoutSeconds = parseTimeout(opts.timeout);
1966
2029
  const dryRun = opts.dryRun;
1967
2030
  const framework = (0, import_core.detectFramework)(cwd);
1968
2031
  const packageManager = (0, import_core.detectPackageManager)(cwd);
@@ -1972,9 +2035,10 @@ program.command("rescue").description("Detect build failures and automatically a
1972
2035
  console.log(` Framework: ${framework}`);
1973
2036
  console.log(` Package manager: ${packageManager}`);
1974
2037
  console.log(` Max attempts: ${maxAttempts}`);
2038
+ if (timeoutSeconds !== void 0) console.log(` Timeout: ${timeoutSeconds}s per command`);
1975
2039
  if (dryRun) console.log(` Mode: DRY RUN (no files will be modified)`);
1976
2040
  console.log();
1977
- const result = await (0, import_core.orchestrateAndFix)({ cwd, maxAttempts, dryRun });
2041
+ const result = await (0, import_core.orchestrateAndFix)({ cwd, maxAttempts, dryRun, timeoutSeconds });
1978
2042
  console.log("\n\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");
1979
2043
  const summary = buildRescueSummary(result, { dryRun });
1980
2044
  for (const line of summary.lines) console.log(line);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bugzero",
3
- "version": "0.0.41",
3
+ "version": "0.0.42",
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)",
@@ -48,7 +48,8 @@
48
48
  "@langchain/langgraph": "^1.3.2",
49
49
  "axios": "^1.16.1",
50
50
  "commander": "^15.0.0",
51
- "execa": "^7.2.0"
51
+ "execa": "^7.2.0",
52
+ "tree-kill": "^1.2.2"
52
53
  },
53
54
  "devDependencies": {
54
55
  "@opspilot/agents": "workspace:*",