bugzero 0.0.35 → 0.0.37
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.
- package/LICENSE +21 -0
- package/README.md +27 -17
- package/dist/index.js +136 -85
- package/package.json +41 -4
- package/.turbo/turbo-build.log +0 -14
- package/demo.svg +0 -81
- package/src/index.ts +0 -202
- package/tsconfig.json +0 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Saksham Agarwal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://www.npmjs.com/package/bugzero)
|
|
9
9
|
[](https://github.com/Saksham1800t/Bugzero/actions/workflows/ci.yml)
|
|
10
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
|
-
[](https://nodejs.org)
|
|
12
12
|
[](https://pnpm.io/)
|
|
13
13
|
|
|
14
14
|
**BugZero** is an autonomous, AI-powered CLI that detects and repairs build, lint, and test failures — locally or as a GitHub Action that opens the fix as a pull request.
|
|
@@ -88,22 +88,32 @@ bugzero rescue
|
|
|
88
88
|
|
|
89
89
|
---
|
|
90
90
|
|
|
91
|
-
##
|
|
91
|
+
## Features
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
93
|
+
### Repair engine
|
|
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.
|
|
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
|
+
- **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
|
+
- **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`.
|
|
99
|
+
|
|
100
|
+
### Safety
|
|
101
|
+
|
|
102
|
+
- **Backups on every patch.** Each file BugZero modifies gets a `.opspilot.bak` copy of its original content first.
|
|
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.
|
|
105
|
+
|
|
106
|
+
### Project awareness
|
|
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.
|
|
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.
|
|
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.
|
|
112
|
+
|
|
113
|
+
### Integrations and output
|
|
114
|
+
|
|
115
|
+
- **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.
|
|
116
|
+
- **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.
|
|
107
117
|
|
|
108
118
|
---
|
|
109
119
|
|
|
@@ -270,7 +280,7 @@ BugZero provides clear, actionable output for common failure modes:
|
|
|
270
280
|
Clone the repository and install dependencies. This project uses a pnpm monorepo managed by Turborepo, with a Vitest test suite and GitHub Actions CI.
|
|
271
281
|
|
|
272
282
|
### Prerequisites
|
|
273
|
-
- Node.js
|
|
283
|
+
- Node.js 22.12 or later
|
|
274
284
|
- pnpm
|
|
275
285
|
|
|
276
286
|
### Install & build
|
package/dist/index.js
CHANGED
|
@@ -30,7 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
var require_main = __commonJS({
|
|
31
31
|
"../../node_modules/.pnpm/dotenv@17.4.2/node_modules/dotenv/lib/main.js"(exports2, module2) {
|
|
32
32
|
"use strict";
|
|
33
|
-
var
|
|
33
|
+
var fs2 = require("fs");
|
|
34
34
|
var path2 = require("path");
|
|
35
35
|
var os = require("os");
|
|
36
36
|
var crypto = require("crypto");
|
|
@@ -162,7 +162,7 @@ var require_main = __commonJS({
|
|
|
162
162
|
if (options && options.path && options.path.length > 0) {
|
|
163
163
|
if (Array.isArray(options.path)) {
|
|
164
164
|
for (const filepath of options.path) {
|
|
165
|
-
if (
|
|
165
|
+
if (fs2.existsSync(filepath)) {
|
|
166
166
|
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
167
167
|
}
|
|
168
168
|
}
|
|
@@ -172,7 +172,7 @@ var require_main = __commonJS({
|
|
|
172
172
|
} else {
|
|
173
173
|
possibleVaultPath = path2.resolve(process.cwd(), ".env.vault");
|
|
174
174
|
}
|
|
175
|
-
if (
|
|
175
|
+
if (fs2.existsSync(possibleVaultPath)) {
|
|
176
176
|
return possibleVaultPath;
|
|
177
177
|
}
|
|
178
178
|
return null;
|
|
@@ -225,7 +225,7 @@ var require_main = __commonJS({
|
|
|
225
225
|
const parsedAll = {};
|
|
226
226
|
for (const path3 of optionPaths) {
|
|
227
227
|
try {
|
|
228
|
-
const parsed = DotenvModule.parse(
|
|
228
|
+
const parsed = DotenvModule.parse(fs2.readFileSync(path3, { encoding }));
|
|
229
229
|
DotenvModule.populate(parsedAll, parsed, options);
|
|
230
230
|
} catch (e) {
|
|
231
231
|
if (debug) {
|
|
@@ -386,13 +386,13 @@ var require_dist = __commonJS({
|
|
|
386
386
|
});
|
|
387
387
|
module2.exports = __toCommonJS(index_exports);
|
|
388
388
|
var import_dotenv = __toESM2(require_main());
|
|
389
|
-
var
|
|
389
|
+
var import_fs2 = __toESM2(require("fs"));
|
|
390
390
|
var import_path2 = __toESM2(require("path"));
|
|
391
391
|
function loadEnv() {
|
|
392
392
|
let dir = process.cwd();
|
|
393
393
|
while (true) {
|
|
394
394
|
const envPath = import_path2.default.join(dir, ".env");
|
|
395
|
-
if (
|
|
395
|
+
if (import_fs2.default.existsSync(envPath)) {
|
|
396
396
|
import_dotenv.default.config({ path: envPath });
|
|
397
397
|
return;
|
|
398
398
|
}
|
|
@@ -408,14 +408,14 @@ var require_dist = __commonJS({
|
|
|
408
408
|
var env = {
|
|
409
409
|
openRouterKey: process.env.OPENROUTER_API_KEY || ""
|
|
410
410
|
};
|
|
411
|
-
var
|
|
411
|
+
var import_fs22 = __toESM2(require("fs"));
|
|
412
412
|
var import_path22 = __toESM2(require("path"));
|
|
413
413
|
var CONFIG_FILENAME = ".bugzerorc";
|
|
414
414
|
function getLocalConfig(cwd = process.cwd()) {
|
|
415
415
|
try {
|
|
416
416
|
const configPath = import_path22.default.resolve(cwd, CONFIG_FILENAME);
|
|
417
|
-
if (
|
|
418
|
-
const content =
|
|
417
|
+
if (import_fs22.default.existsSync(configPath)) {
|
|
418
|
+
const content = import_fs22.default.readFileSync(configPath, "utf-8");
|
|
419
419
|
return JSON.parse(content);
|
|
420
420
|
}
|
|
421
421
|
} catch (e) {
|
|
@@ -424,7 +424,7 @@ var require_dist = __commonJS({
|
|
|
424
424
|
}
|
|
425
425
|
function saveLocalConfig(config, cwd = process.cwd()) {
|
|
426
426
|
const configPath = import_path22.default.resolve(cwd, CONFIG_FILENAME);
|
|
427
|
-
|
|
427
|
+
import_fs22.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
|
|
428
428
|
}
|
|
429
429
|
function getSelectedModel(cwd = process.cwd()) {
|
|
430
430
|
const config = getLocalConfig(cwd);
|
|
@@ -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
|
-
var
|
|
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 =
|
|
493
|
-
if (
|
|
494
|
-
const pkg = JSON.parse(
|
|
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 (
|
|
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
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
|
588
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if (
|
|
595
|
-
|
|
596
|
-
|
|
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
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
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 (
|
|
613
|
-
console.
|
|
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
|
}
|
|
@@ -658,13 +671,13 @@ var require_dist3 = __commonJS({
|
|
|
658
671
|
});
|
|
659
672
|
module2.exports = __toCommonJS(index_exports);
|
|
660
673
|
var import_axios = __toESM2(require("axios"));
|
|
661
|
-
var
|
|
674
|
+
var import_fs2 = __toESM2(require("fs"));
|
|
662
675
|
var import_path2 = __toESM2(require("path"));
|
|
663
676
|
function resolveModel(cwd) {
|
|
664
677
|
try {
|
|
665
678
|
const configPath = import_path2.default.resolve(cwd ?? process.cwd(), ".bugzerorc");
|
|
666
|
-
if (
|
|
667
|
-
const config = JSON.parse(
|
|
679
|
+
if (import_fs2.default.existsSync(configPath)) {
|
|
680
|
+
const config = JSON.parse(import_fs2.default.readFileSync(configPath, "utf-8"));
|
|
668
681
|
if (config.model) return config.model;
|
|
669
682
|
}
|
|
670
683
|
} catch {
|
|
@@ -777,16 +790,31 @@ 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
|
-
var
|
|
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) {
|
|
788
816
|
try {
|
|
789
|
-
if (
|
|
817
|
+
if (import_fs2.default.existsSync(current)) {
|
|
790
818
|
return current;
|
|
791
819
|
}
|
|
792
820
|
} catch {
|
|
@@ -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
|
-
|
|
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
|
\`\`\`
|
|
@@ -858,18 +892,18 @@ ${content}
|
|
|
858
892
|
};
|
|
859
893
|
for (const absPath of resolvedPaths) {
|
|
860
894
|
try {
|
|
861
|
-
const stats =
|
|
895
|
+
const stats = import_fs2.default.statSync(absPath);
|
|
862
896
|
if (stats.isFile()) {
|
|
863
897
|
addFileToContext(absPath);
|
|
864
898
|
const parentDir = import_path2.default.dirname(absPath);
|
|
865
|
-
const filesInDir =
|
|
899
|
+
const filesInDir = import_fs2.default.readdirSync(parentDir);
|
|
866
900
|
const relParentDir = import_path2.default.relative(cwd, parentDir) || ".";
|
|
867
901
|
context += `
|
|
868
902
|
--- Directory files in "${relParentDir}": ---
|
|
869
903
|
`;
|
|
870
904
|
for (const file of filesInDir) {
|
|
871
905
|
try {
|
|
872
|
-
const fileStats =
|
|
906
|
+
const fileStats = import_fs2.default.statSync(import_path2.default.join(parentDir, file));
|
|
873
907
|
const marker = fileStats.isDirectory() ? "/" : "";
|
|
874
908
|
context += `- ${file}${marker}
|
|
875
909
|
`;
|
|
@@ -881,13 +915,13 @@ ${content}
|
|
|
881
915
|
} else if (stats.isDirectory()) {
|
|
882
916
|
const relParentDir = import_path2.default.relative(cwd, absPath) || ".";
|
|
883
917
|
if (relParentDir !== ".") {
|
|
884
|
-
const filesInDir =
|
|
918
|
+
const filesInDir = import_fs2.default.readdirSync(absPath);
|
|
885
919
|
context += `
|
|
886
920
|
--- Directory files in "${relParentDir}": ---
|
|
887
921
|
`;
|
|
888
922
|
for (const file of filesInDir) {
|
|
889
923
|
try {
|
|
890
|
-
const fileStats =
|
|
924
|
+
const fileStats = import_fs2.default.statSync(import_path2.default.join(absPath, file));
|
|
891
925
|
const marker = fileStats.isDirectory() ? "/" : "";
|
|
892
926
|
context += `- ${file}${marker}
|
|
893
927
|
`;
|
|
@@ -901,7 +935,7 @@ ${content}
|
|
|
901
935
|
const filePathFull = import_path2.default.join(absPath, file);
|
|
902
936
|
const fileExt = import_path2.default.extname(file).toLowerCase();
|
|
903
937
|
try {
|
|
904
|
-
if (
|
|
938
|
+
if (import_fs2.default.statSync(filePathFull).isFile() && [".js", ".jsx", ".ts", ".tsx", ".json", ".css"].includes(fileExt)) {
|
|
905
939
|
addFileToContext(filePathFull);
|
|
906
940
|
}
|
|
907
941
|
} catch {
|
|
@@ -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
|
|
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}
|
|
@@ -1008,7 +1043,7 @@ var require_dist5 = __commonJS({
|
|
|
1008
1043
|
lineSimilarity: () => lineSimilarity
|
|
1009
1044
|
});
|
|
1010
1045
|
module2.exports = __toCommonJS(index_exports);
|
|
1011
|
-
var
|
|
1046
|
+
var import_fs2 = __toESM2(require("fs"));
|
|
1012
1047
|
var import_path2 = __toESM2(require("path"));
|
|
1013
1048
|
var SIMILARITY_THRESHOLD = 0.85;
|
|
1014
1049
|
var AMBIGUITY_MARGIN = 0.05;
|
|
@@ -1111,13 +1146,13 @@ var require_dist5 = __commonJS({
|
|
|
1111
1146
|
}
|
|
1112
1147
|
function applyPatch(absoluteFilePath, search, replace) {
|
|
1113
1148
|
try {
|
|
1114
|
-
if (!
|
|
1149
|
+
if (!import_fs2.default.existsSync(absoluteFilePath)) {
|
|
1115
1150
|
return {
|
|
1116
1151
|
success: false,
|
|
1117
1152
|
error: `File not found: ${absoluteFilePath}`
|
|
1118
1153
|
};
|
|
1119
1154
|
}
|
|
1120
|
-
const original =
|
|
1155
|
+
const original = import_fs2.default.readFileSync(absoluteFilePath, "utf-8");
|
|
1121
1156
|
const match = findMatch(original, search);
|
|
1122
1157
|
if (!match) {
|
|
1123
1158
|
return {
|
|
@@ -1126,26 +1161,26 @@ var require_dist5 = __commonJS({
|
|
|
1126
1161
|
};
|
|
1127
1162
|
}
|
|
1128
1163
|
const backupPath = absoluteFilePath + ".opspilot.bak";
|
|
1129
|
-
|
|
1164
|
+
import_fs2.default.writeFileSync(backupPath, original, "utf-8");
|
|
1130
1165
|
const fixed = original.slice(0, match.start) + replace + original.slice(match.end);
|
|
1131
|
-
|
|
1166
|
+
import_fs2.default.writeFileSync(absoluteFilePath, fixed, "utf-8");
|
|
1132
1167
|
return { success: true, backupPath, matchStrategy: match.strategy };
|
|
1133
1168
|
} catch (err) {
|
|
1134
1169
|
return { success: false, error: err.message };
|
|
1135
1170
|
}
|
|
1136
1171
|
}
|
|
1137
|
-
var
|
|
1172
|
+
var import_fs22 = __toESM2(require("fs"));
|
|
1138
1173
|
var import_path22 = __toESM2(require("path"));
|
|
1139
1174
|
function rollback(originalContent, backupPaths) {
|
|
1140
1175
|
for (const [absolutePath, content] of originalContent) {
|
|
1141
1176
|
try {
|
|
1142
|
-
|
|
1177
|
+
import_fs22.default.writeFileSync(absolutePath, content, "utf-8");
|
|
1143
1178
|
} catch {
|
|
1144
1179
|
}
|
|
1145
1180
|
}
|
|
1146
1181
|
for (const backupPath of backupPaths) {
|
|
1147
1182
|
try {
|
|
1148
|
-
|
|
1183
|
+
import_fs22.default.unlinkSync(backupPath);
|
|
1149
1184
|
} catch {
|
|
1150
1185
|
}
|
|
1151
1186
|
}
|
|
@@ -1161,7 +1196,7 @@ var require_dist5 = __commonJS({
|
|
|
1161
1196
|
const absolutePath = import_path22.default.resolve(cwd, patch.filePath);
|
|
1162
1197
|
if (!originalContent.has(absolutePath)) {
|
|
1163
1198
|
try {
|
|
1164
|
-
originalContent.set(absolutePath,
|
|
1199
|
+
originalContent.set(absolutePath, import_fs22.default.readFileSync(absolutePath, "utf-8"));
|
|
1165
1200
|
} catch {
|
|
1166
1201
|
rollback(originalContent, backupPaths);
|
|
1167
1202
|
return {
|
|
@@ -1415,10 +1450,10 @@ var require_dist7 = __commonJS({
|
|
|
1415
1450
|
orchestrateAndFix: () => orchestrateAndFix2
|
|
1416
1451
|
});
|
|
1417
1452
|
module2.exports = __toCommonJS(index_exports);
|
|
1418
|
-
var
|
|
1453
|
+
var import_fs2 = __toESM2(require("fs"));
|
|
1419
1454
|
var import_path2 = __toESM2(require("path"));
|
|
1420
1455
|
function detectFramework2(cwd = process.cwd()) {
|
|
1421
|
-
const has = (...files) => files.some((f) =>
|
|
1456
|
+
const has = (...files) => files.some((f) => import_fs2.default.existsSync(import_path2.default.join(cwd, f)));
|
|
1422
1457
|
if (has("next.config.js", "next.config.ts", "next.config.mjs")) return "nextjs";
|
|
1423
1458
|
if (has("nest-cli.json")) return "nestjs";
|
|
1424
1459
|
if (has("vite.config.ts", "vite.config.js", "vite.config.mts")) return "vite";
|
|
@@ -1427,9 +1462,9 @@ var require_dist7 = __commonJS({
|
|
|
1427
1462
|
if (has("svelte.config.js", "svelte.config.ts")) return "sveltekit";
|
|
1428
1463
|
if (has("nuxt.config.ts", "nuxt.config.js")) return "nuxt";
|
|
1429
1464
|
const pkgPath = import_path2.default.join(cwd, "package.json");
|
|
1430
|
-
if (
|
|
1465
|
+
if (import_fs2.default.existsSync(pkgPath)) {
|
|
1431
1466
|
try {
|
|
1432
|
-
const pkg = JSON.parse(
|
|
1467
|
+
const pkg = JSON.parse(import_fs2.default.readFileSync(pkgPath, "utf-8"));
|
|
1433
1468
|
const deps = {
|
|
1434
1469
|
...pkg.dependencies ?? {},
|
|
1435
1470
|
...pkg.devDependencies ?? {}
|
|
@@ -1444,12 +1479,24 @@ var require_dist7 = __commonJS({
|
|
|
1444
1479
|
}
|
|
1445
1480
|
var import_analyzers = require_dist2();
|
|
1446
1481
|
var import_agents = require_dist6();
|
|
1447
|
-
var
|
|
1448
|
-
var
|
|
1482
|
+
var import_fs3 = __toESM2(require("fs"));
|
|
1483
|
+
var import_path3 = __toESM2(require("path"));
|
|
1449
1484
|
var import_url = require("url");
|
|
1485
|
+
var import_fs22 = __toESM2(require("fs"));
|
|
1486
|
+
var import_path22 = __toESM2(require("path"));
|
|
1487
|
+
function assertNodeProject(cwd) {
|
|
1488
|
+
if (!import_fs22.default.existsSync(import_path22.default.join(cwd, "package.json"))) {
|
|
1489
|
+
throw new Error(`No package.json found in ${cwd}. Point --path at a Node.js project.`);
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1450
1492
|
var import_ai_provider = require_dist3();
|
|
1493
|
+
function statusLabel(result) {
|
|
1494
|
+
if (result.skipped) return "\u23ED\uFE0F Skipped (no script defined)";
|
|
1495
|
+
return result.success ? "\u2705 Passed" : "\u274C Failed";
|
|
1496
|
+
}
|
|
1451
1497
|
async function orchestrate2(options = {}) {
|
|
1452
1498
|
const cwd = options.cwd ?? process.cwd();
|
|
1499
|
+
assertNodeProject(cwd);
|
|
1453
1500
|
const [build, lint, test] = await Promise.all([
|
|
1454
1501
|
(0, import_analyzers.runBuild)(cwd),
|
|
1455
1502
|
(0, import_analyzers.runLint)(cwd),
|
|
@@ -1465,6 +1512,7 @@ var require_dist7 = __commonJS({
|
|
|
1465
1512
|
}
|
|
1466
1513
|
async function orchestrateAndFix2(options = {}) {
|
|
1467
1514
|
const cwd = options.cwd ?? process.cwd();
|
|
1515
|
+
assertNodeProject(cwd);
|
|
1468
1516
|
const maxAttempts = options.maxAttempts ?? 3;
|
|
1469
1517
|
console.log(`[BugZero] Running validation pipeline in: ${cwd}`);
|
|
1470
1518
|
const pipelineResult = await (0, import_analyzers.runValidationPipeline)(cwd);
|
|
@@ -1597,9 +1645,9 @@ Generate the Markdown report directly. Do not wrap it in markdown block fences (
|
|
|
1597
1645
|
**Framework:** \`${detectFramework2(cwd)}\`
|
|
1598
1646
|
|
|
1599
1647
|
## \u{1F4CA} Status Summary
|
|
1600
|
-
- **Build:** ${build
|
|
1601
|
-
- **Lint:** ${lint
|
|
1602
|
-
- **Test:** ${test
|
|
1648
|
+
- **Build:** ${statusLabel(build)}
|
|
1649
|
+
- **Lint:** ${statusLabel(lint)}
|
|
1650
|
+
- **Test:** ${statusLabel(test)}
|
|
1603
1651
|
|
|
1604
1652
|
## \u274C Failed Checks & Error Output
|
|
1605
1653
|
${logs}
|
|
@@ -1618,9 +1666,9 @@ ${logs}
|
|
|
1618
1666
|
**Framework:** \`${detectFramework2(cwd)}\`
|
|
1619
1667
|
|
|
1620
1668
|
## \u{1F4CA} Status Summary
|
|
1621
|
-
- **Build:**
|
|
1622
|
-
- **Lint:**
|
|
1623
|
-
- **Test:**
|
|
1669
|
+
- **Build:** ${statusLabel(build)}
|
|
1670
|
+
- **Lint:** ${statusLabel(lint)}
|
|
1671
|
+
- **Test:** ${statusLabel(test)}
|
|
1624
1672
|
|
|
1625
1673
|
\u{1F389} **All checks passed! Your project is in perfect shape.**`;
|
|
1626
1674
|
}
|
|
@@ -1725,9 +1773,9 @@ ${rawOutput}
|
|
|
1725
1773
|
|
|
1726
1774
|
---
|
|
1727
1775
|
*Report generated by **BugZero** on ${(/* @__PURE__ */ new Date()).toLocaleString()}*`;
|
|
1728
|
-
const reportPath =
|
|
1776
|
+
const reportPath = import_path3.default.resolve(cwd, "bugzero-report.md");
|
|
1729
1777
|
try {
|
|
1730
|
-
|
|
1778
|
+
import_fs3.default.writeFileSync(reportPath, content, "utf-8");
|
|
1731
1779
|
const clickableUrl = (0, import_url.pathToFileURL)(reportPath).toString();
|
|
1732
1780
|
console.log(`
|
|
1733
1781
|
\u{1F4DD} Generated report successfully!
|
|
@@ -1750,10 +1798,12 @@ ${rawOutput}
|
|
|
1750
1798
|
// src/index.ts
|
|
1751
1799
|
var import_config = __toESM(require_dist());
|
|
1752
1800
|
var import_commander = require("commander");
|
|
1801
|
+
var import_fs = __toESM(require("fs"));
|
|
1753
1802
|
var import_path = __toESM(require("path"));
|
|
1754
1803
|
var import_core = __toESM(require_dist7());
|
|
1804
|
+
var { version } = JSON.parse(import_fs.default.readFileSync(import_path.default.join(__dirname, "..", "package.json"), "utf-8"));
|
|
1755
1805
|
var program = new import_commander.Command();
|
|
1756
|
-
program.name("bugzero").description("AI-powered build issue fixer \u2014 detects and repairs build errors automatically").version(
|
|
1806
|
+
program.name("bugzero").description("AI-powered build issue fixer \u2014 detects and repairs build errors automatically").version(version);
|
|
1757
1807
|
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) => {
|
|
1758
1808
|
try {
|
|
1759
1809
|
const cwd = import_path.default.resolve(opts.path);
|
|
@@ -1767,9 +1817,10 @@ program.command("hunt").description("Run build, lint, and tests \u2014 report is
|
|
|
1767
1817
|
`);
|
|
1768
1818
|
const { build, lint, test } = await (0, import_core.orchestrate)({ cwd });
|
|
1769
1819
|
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");
|
|
1770
|
-
|
|
1771
|
-
console.log(`
|
|
1772
|
-
console.log(`
|
|
1820
|
+
const label = (r) => r.skipped ? "\u23ED\uFE0F Skipped (no script)" : r.success ? "\u2705 Passed" : "\u274C Failed (see report)";
|
|
1821
|
+
console.log(` Build: ${label(build)}`);
|
|
1822
|
+
console.log(` Lint: ${label(lint)}`);
|
|
1823
|
+
console.log(` Tests: ${label(test)}`);
|
|
1773
1824
|
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");
|
|
1774
1825
|
const allPassed = build.success && lint.success && test.success;
|
|
1775
1826
|
console.log(`
|
package/package.json
CHANGED
|
@@ -1,11 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bugzero",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.37",
|
|
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
|
+
"author": "Saksham Agarwal (https://github.com/Saksham1800t)",
|
|
7
|
+
"homepage": "https://github.com/Saksham1800t/Bugzero#readme",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Saksham1800t/Bugzero.git",
|
|
11
|
+
"directory": "apps/bugzero"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Saksham1800t/Bugzero/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ai",
|
|
18
|
+
"llm",
|
|
19
|
+
"autofix",
|
|
20
|
+
"auto-fix",
|
|
21
|
+
"build-fixer",
|
|
22
|
+
"bug-fix",
|
|
23
|
+
"code-repair",
|
|
24
|
+
"self-healing",
|
|
25
|
+
"typescript",
|
|
26
|
+
"eslint",
|
|
27
|
+
"jest",
|
|
28
|
+
"cli",
|
|
29
|
+
"langgraph",
|
|
30
|
+
"openrouter",
|
|
31
|
+
"github-action",
|
|
32
|
+
"ci",
|
|
33
|
+
"developer-tools"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=22.12.0"
|
|
37
|
+
},
|
|
6
38
|
"bin": {
|
|
7
39
|
"bugzero": "dist/index.js"
|
|
8
40
|
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE"
|
|
45
|
+
],
|
|
9
46
|
"dependencies": {
|
|
10
47
|
"@langchain/core": "^1.1.48",
|
|
11
48
|
"@langchain/langgraph": "^1.3.2",
|
|
@@ -21,6 +58,6 @@
|
|
|
21
58
|
"main": "./dist/index.js",
|
|
22
59
|
"types": "./dist/index.d.ts",
|
|
23
60
|
"scripts": {
|
|
24
|
-
"build": "tsup src/index.ts --format cjs --dts"
|
|
61
|
+
"build": "tsup src/index.ts --format cjs --dts --clean"
|
|
25
62
|
}
|
|
26
|
-
}
|
|
63
|
+
}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> bugzero@0.0.35 build /home/runner/work/Bugzero/Bugzero/apps/bugzero
|
|
3
|
-
> tsup src/index.ts --format cjs --dts
|
|
4
|
-
|
|
5
|
-
[34mCLI[39m Building entry: src/index.ts
|
|
6
|
-
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
|
-
[34mCLI[39m tsup v8.5.1
|
|
8
|
-
[34mCLI[39m Target: es2022
|
|
9
|
-
[34mCJS[39m Build start
|
|
10
|
-
[32mCJS[39m [1mdist/index.js [22m[32m79.68 KB[39m
|
|
11
|
-
[32mCJS[39m ⚡️ Build success in 54ms
|
|
12
|
-
[34mDTS[39m Build start
|
|
13
|
-
[32mDTS[39m ⚡️ Build success in 1265ms
|
|
14
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[32m20.00 B[39m
|
package/demo.svg
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
<svg viewBox="0 0 700 520" xmlns="http://www.w3.org/2000/svg" font-family="SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace">
|
|
2
|
-
<title>bugzero rescue demo</title>
|
|
3
|
-
<desc>Illustrative animation of "bugzero rescue" fixing a small TypeScript error. Log lines are the tool's real output format; the example error is a stand-in, not a captured recording.</desc>
|
|
4
|
-
|
|
5
|
-
<style>
|
|
6
|
-
text { font-size: 13px; }
|
|
7
|
-
.prompt { fill: #58a6ff; }
|
|
8
|
-
.out { fill: #c9d1d9; }
|
|
9
|
-
.ok { fill: #3fb950; }
|
|
10
|
-
.dim { fill: #8b949e; }
|
|
11
|
-
.dot { }
|
|
12
|
-
.titlebar-label { fill: #8b949e; font-size: 12px; }
|
|
13
|
-
|
|
14
|
-
@keyframes gA { 0% { opacity: 0; } 2% { opacity: 1; } 90% { opacity: 1; } 97% { opacity: 0; } 100% { opacity: 0; } }
|
|
15
|
-
@keyframes gB { 0%, 6% { opacity: 0; } 9% { opacity: 1; } 90% { opacity: 1; } 97% { opacity: 0; } 100% { opacity: 0; } }
|
|
16
|
-
@keyframes gC { 0%, 16% { opacity: 0; } 19% { opacity: 1; } 90% { opacity: 1; } 97% { opacity: 0; } 100% { opacity: 0; } }
|
|
17
|
-
@keyframes gD { 0%, 26% { opacity: 0; } 29% { opacity: 1; } 90% { opacity: 1; } 97% { opacity: 0; } 100% { opacity: 0; } }
|
|
18
|
-
@keyframes gE { 0%, 38% { opacity: 0; } 41% { opacity: 1; } 90% { opacity: 1; } 97% { opacity: 0; } 100% { opacity: 0; } }
|
|
19
|
-
@keyframes gF { 0%, 48% { opacity: 0; } 50% { opacity: 1; } 90% { opacity: 1; } 97% { opacity: 0; } 100% { opacity: 0; } }
|
|
20
|
-
@keyframes gG { 0%, 56% { opacity: 0; } 59% { opacity: 1; } 90% { opacity: 1; } 97% { opacity: 0; } 100% { opacity: 0; } }
|
|
21
|
-
|
|
22
|
-
.groupA { animation: gA 14s ease-in-out infinite; }
|
|
23
|
-
.groupB { animation: gB 14s ease-in-out infinite; }
|
|
24
|
-
.groupC { animation: gC 14s ease-in-out infinite; }
|
|
25
|
-
.groupD { animation: gD 14s ease-in-out infinite; }
|
|
26
|
-
.groupE { animation: gE 14s ease-in-out infinite; }
|
|
27
|
-
.groupF { animation: gF 14s ease-in-out infinite; }
|
|
28
|
-
.groupG { animation: gG 14s ease-in-out infinite; }
|
|
29
|
-
</style>
|
|
30
|
-
|
|
31
|
-
<rect x="0" y="0" width="700" height="520" rx="10" fill="#0d1117" />
|
|
32
|
-
<rect x="0" y="0" width="700" height="34" rx="10" fill="#161b22" />
|
|
33
|
-
<rect x="0" y="24" width="700" height="10" fill="#161b22" />
|
|
34
|
-
|
|
35
|
-
<circle class="dot" cx="20" cy="17" r="6" fill="#ff5f56" />
|
|
36
|
-
<circle class="dot" cx="40" cy="17" r="6" fill="#ffbd2e" />
|
|
37
|
-
<circle class="dot" cx="60" cy="17" r="6" fill="#27c93f" />
|
|
38
|
-
<text class="titlebar-label" x="350" y="21" text-anchor="middle">bugzero rescue</text>
|
|
39
|
-
|
|
40
|
-
<g class="groupA">
|
|
41
|
-
<text class="prompt" x="18" y="54">$ bugzero rescue --path ./demo-project</text>
|
|
42
|
-
</g>
|
|
43
|
-
|
|
44
|
-
<g class="groupB">
|
|
45
|
-
<text class="out" x="18" y="83">🤖 BugZero Fix</text>
|
|
46
|
-
<text class="out" x="18" y="102"> Path: ./demo-project</text>
|
|
47
|
-
<text class="out" x="18" y="121"> Framework: typescript</text>
|
|
48
|
-
<text class="out" x="18" y="140"> Max attempts: 3</text>
|
|
49
|
-
</g>
|
|
50
|
-
|
|
51
|
-
<g class="groupC">
|
|
52
|
-
<text class="out" x="18" y="169">[BugZero] Running validation pipeline in: ./demo-project</text>
|
|
53
|
-
<text class="out" x="18" y="188">[BugZero] Running build...</text>
|
|
54
|
-
<text class="out" x="18" y="207">[BugZero] Detected 1 error(s) (type: typescript)</text>
|
|
55
|
-
</g>
|
|
56
|
-
|
|
57
|
-
<g class="groupD">
|
|
58
|
-
<text class="out" x="18" y="236">[BugZero] Attempt 1/3 — asking AI…</text>
|
|
59
|
-
</g>
|
|
60
|
-
|
|
61
|
-
<g class="groupE">
|
|
62
|
-
<text class="out" x="18" y="265">[BugZero] Patching 1 file: src/sum.ts</text>
|
|
63
|
-
<text class="out" x="18" y="284">[BugZero] Reason: Fixed subtraction typo in the sum() function</text>
|
|
64
|
-
<text class="out" x="18" y="303">[BugZero] Backup saved to: src/sum.ts.opspilot.bak</text>
|
|
65
|
-
<text class="out" x="18" y="322">[BugZero] Re-running validation pipeline to verify fix…</text>
|
|
66
|
-
</g>
|
|
67
|
-
|
|
68
|
-
<g class="groupF">
|
|
69
|
-
<text class="ok" x="18" y="351">[BugZero] ✅ Validation succeeded!</text>
|
|
70
|
-
</g>
|
|
71
|
-
|
|
72
|
-
<g class="groupG">
|
|
73
|
-
<text class="dim" x="18" y="380">──────────────────────────────────────────────</text>
|
|
74
|
-
<text class="ok" x="18" y="399">✅ Build fixed after 1 attempt(s)!</text>
|
|
75
|
-
<text class="out" x="18" y="418"> Model: deepseek/deepseek-chat</text>
|
|
76
|
-
<text class="out" x="18" y="437"> Tokens: Prompt: 812 | Completion: 96 | Total: 908</text>
|
|
77
|
-
<text class="out" x="18" y="456"> File: src/sum.ts</text>
|
|
78
|
-
<text class="out" x="18" y="475"> Reason: Fixed subtraction typo in the sum() function</text>
|
|
79
|
-
<text class="out" x="18" y="494"> Root cause: sum() used the - operator instead of +</text>
|
|
80
|
-
</g>
|
|
81
|
-
</svg>
|
package/src/index.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import "@opspilot/config"; // loads .env before anything else
|
|
4
|
-
import { Command } from "commander";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import { orchestrate, orchestrateAndFix, detectFramework, detectPackageManager } from "@opspilot/core";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const program = new Command();
|
|
10
|
-
|
|
11
|
-
program
|
|
12
|
-
.name("bugzero")
|
|
13
|
-
.description("AI-powered build issue fixer — detects and repairs build errors automatically")
|
|
14
|
-
.version("0.0.1");
|
|
15
|
-
|
|
16
|
-
// ── bugzero hunt ─────────────────────────────────────────────────────────────
|
|
17
|
-
program
|
|
18
|
-
.command("hunt")
|
|
19
|
-
.description("Run build, lint, and tests — report issues without modifying files")
|
|
20
|
-
.option("-p, --path <dir>", "Path to the target project", ".")
|
|
21
|
-
.action(async (opts) => {
|
|
22
|
-
try {
|
|
23
|
-
const cwd = path.resolve(opts.path);
|
|
24
|
-
const framework = detectFramework(cwd);
|
|
25
|
-
const packageManager = detectPackageManager(cwd);
|
|
26
|
-
|
|
27
|
-
console.log(`\n🔍 BugZero — Analyzing project`);
|
|
28
|
-
console.log(` Path: ${cwd}`);
|
|
29
|
-
console.log(` Framework: ${framework}`);
|
|
30
|
-
console.log(` Package manager: ${packageManager}\n`);
|
|
31
|
-
|
|
32
|
-
const { build, lint, test } = await orchestrate({ cwd });
|
|
33
|
-
|
|
34
|
-
console.log("── Pipeline Summary ───────────────────────────");
|
|
35
|
-
console.log(` Build: ${build.success ? "✅ Passed" : "❌ Failed (see report)"}`);
|
|
36
|
-
console.log(` Lint: ${lint.success ? "✅ Passed" : "❌ Failed (see report)"}`);
|
|
37
|
-
console.log(` Tests: ${test.success ? "✅ Passed" : "❌ Failed (see report)"}`);
|
|
38
|
-
console.log("───────────────────────────────────────────────");
|
|
39
|
-
|
|
40
|
-
const allPassed = build.success && lint.success && test.success;
|
|
41
|
-
console.log(`\n${allPassed ? "✅ All checks passed" : "❌ Issues found — run 'bugzero rescue' to repair"}\n`);
|
|
42
|
-
process.exit(allPassed ? 0 : 1);
|
|
43
|
-
} catch (error: any) {
|
|
44
|
-
console.error(`\n❌ [BugZero] Execution failed: ${error.message || error}`);
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// ── bugzero rescue ───────────────────────────────────────────────────────────
|
|
50
|
-
program
|
|
51
|
-
.command("rescue")
|
|
52
|
-
.description("Detect build failures and automatically apply AI-generated fixes")
|
|
53
|
-
.option("-p, --path <dir>", "Path to the target project", ".")
|
|
54
|
-
.option("-n, --max-attempts <number>", "Maximum AI fix attempts", "3")
|
|
55
|
-
.option("--dry-run", "Analyse only — print what would be fixed without writing files", false)
|
|
56
|
-
.action(async (opts) => {
|
|
57
|
-
try {
|
|
58
|
-
const cwd = path.resolve(opts.path);
|
|
59
|
-
const maxAttempts = parseInt(opts.maxAttempts, 10);
|
|
60
|
-
const dryRun: boolean = opts.dryRun;
|
|
61
|
-
const framework = detectFramework(cwd);
|
|
62
|
-
const packageManager = detectPackageManager(cwd);
|
|
63
|
-
|
|
64
|
-
console.log(`\n🤖 BugZero Fix`);
|
|
65
|
-
console.log(` Path: ${cwd}`);
|
|
66
|
-
console.log(` Framework: ${framework}`);
|
|
67
|
-
console.log(` Package manager: ${packageManager}`);
|
|
68
|
-
console.log(` Max attempts: ${maxAttempts}`);
|
|
69
|
-
if (dryRun) console.log(` Mode: DRY RUN (no files will be modified)`);
|
|
70
|
-
console.log();
|
|
71
|
-
|
|
72
|
-
const result = await orchestrateAndFix({ cwd, maxAttempts, dryRun });
|
|
73
|
-
|
|
74
|
-
console.log("\n──────────────────────────────────────────────");
|
|
75
|
-
if (result.success) {
|
|
76
|
-
console.log(`✅ Build fixed after ${result.attempts} attempt(s)!`);
|
|
77
|
-
console.log(` Model: ${result.modelUsed || "unknown"}`);
|
|
78
|
-
if (result.promptTokens !== undefined && result.completionTokens !== undefined) {
|
|
79
|
-
console.log(` Tokens: Prompt: ${result.promptTokens} | Completion: ${result.completionTokens} | Total: ${result.promptTokens + result.completionTokens}`);
|
|
80
|
-
}
|
|
81
|
-
if (result.appliedFix) {
|
|
82
|
-
const files = result.appliedFix.patches.map((p) => p.filePath).join(", ");
|
|
83
|
-
console.log(` File${result.appliedFix.patches.length === 1 ? "" : "s"}: ${files}`);
|
|
84
|
-
console.log(` Reason: ${result.appliedFix.explanation}`);
|
|
85
|
-
console.log(` Root cause: ${result.appliedFix.rootCause}`);
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
console.log(`❌ Could not fix build after ${result.attempts} attempt(s).`);
|
|
89
|
-
console.log(` Model: ${result.modelUsed || "unknown"}`);
|
|
90
|
-
if (result.promptTokens !== undefined && result.completionTokens !== undefined) {
|
|
91
|
-
console.log(` Tokens: Prompt: ${result.promptTokens} | Completion: ${result.completionTokens} | Total: ${result.promptTokens + result.completionTokens}`);
|
|
92
|
-
}
|
|
93
|
-
if (result.error) console.log(` ${result.error}`);
|
|
94
|
-
console.log(`\n👉 Full unresolved error logs have been listed in the generated report.`);
|
|
95
|
-
}
|
|
96
|
-
console.log();
|
|
97
|
-
|
|
98
|
-
process.exit(result.success ? 0 : 1);
|
|
99
|
-
} catch (error: any) {
|
|
100
|
-
console.error(`\n❌ [BugZero] Execution failed: ${error.message || error}`);
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
// ── bugzero protect (legacy) ────────────────────────────────────────────────
|
|
106
|
-
program
|
|
107
|
-
.command("protect")
|
|
108
|
-
.description("[Deprecated] Use 'hunt' instead")
|
|
109
|
-
.action(async () => {
|
|
110
|
-
console.warn("⚠️ 'protect' is deprecated — use 'bugzero hunt' instead.\n");
|
|
111
|
-
const result = await orchestrate();
|
|
112
|
-
console.log(result);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// ── bugzero vibe ─────────────────────────────────────────────────────────────
|
|
116
|
-
program
|
|
117
|
-
.command("vibe")
|
|
118
|
-
.description("Select which AI model (brain) to use for resolving bugs")
|
|
119
|
-
.action(async () => {
|
|
120
|
-
const { getLocalConfig, saveLocalConfig } = await import("@opspilot/config");
|
|
121
|
-
const readline = await import("readline");
|
|
122
|
-
const axios = (await import("axios")).default;
|
|
123
|
-
|
|
124
|
-
console.log(`\n🧠 BugZero Brain Selector`);
|
|
125
|
-
console.log(`──────────────────────────────────────────────`);
|
|
126
|
-
|
|
127
|
-
let popularModels: any[] = [];
|
|
128
|
-
try {
|
|
129
|
-
console.log("🌀 Fetching active models list from OpenRouter...");
|
|
130
|
-
const response = await axios.get("https://openrouter.ai/api/v1/models", { timeout: 8000 });
|
|
131
|
-
const allModels = response.data?.data || [];
|
|
132
|
-
const popularModelIds = [
|
|
133
|
-
"deepseek/deepseek-chat",
|
|
134
|
-
"deepseek/deepseek-r1",
|
|
135
|
-
"anthropic/claude-3.5-sonnet",
|
|
136
|
-
"google/gemini-2.5-pro",
|
|
137
|
-
"google/gemini-2.5-flash",
|
|
138
|
-
"openai/gpt-4o",
|
|
139
|
-
"meta-llama/llama-3.3-70b-instruct"
|
|
140
|
-
];
|
|
141
|
-
popularModels = allModels.filter((m: any) => popularModelIds.includes(m.id));
|
|
142
|
-
} catch (e: any) {
|
|
143
|
-
console.warn("⚠️ Failed to fetch models list from OpenRouter. Using cached default list.");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (popularModels.length === 0) {
|
|
147
|
-
popularModels = [
|
|
148
|
-
{ id: "deepseek/deepseek-chat", name: "DeepSeek V3 (Chat)" },
|
|
149
|
-
{ id: "deepseek/deepseek-r1", name: "DeepSeek R1" },
|
|
150
|
-
{ id: "anthropic/claude-3.5-sonnet", name: "Claude 3.5 Sonnet" },
|
|
151
|
-
{ id: "google/gemini-2.5-pro", name: "Gemini 2.5 Pro" },
|
|
152
|
-
{ id: "google/gemini-2.5-flash", name: "Gemini 2.5 Flash" },
|
|
153
|
-
{ id: "openai/gpt-4o", name: "GPT-4o" },
|
|
154
|
-
{ id: "meta-llama/llama-3.3-70b-instruct", name: "Llama 3.3 Instruct" }
|
|
155
|
-
];
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const currentModel = getLocalConfig().model || "deepseek/deepseek-chat";
|
|
159
|
-
console.log(`\nCurrent active model: \x1b[36m${currentModel}\x1b[0m\n`);
|
|
160
|
-
|
|
161
|
-
popularModels.forEach((m, idx) => {
|
|
162
|
-
console.log(` [${idx + 1}] ${m.name} (${m.id})`);
|
|
163
|
-
});
|
|
164
|
-
console.log(` [${popularModels.length + 1}] 🖋️ Custom Model (Type manually)`);
|
|
165
|
-
console.log();
|
|
166
|
-
|
|
167
|
-
const rl = readline.createInterface({
|
|
168
|
-
input: process.stdin,
|
|
169
|
-
output: process.stdout,
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
rl.question(`Select a number (1-${popularModels.length + 1}): `, (answer) => {
|
|
173
|
-
const choice = parseInt(answer.trim(), 10);
|
|
174
|
-
if (isNaN(choice) || choice < 1 || choice > popularModels.length + 1) {
|
|
175
|
-
console.log("❌ Invalid selection. Keeping current model.");
|
|
176
|
-
rl.close();
|
|
177
|
-
process.exit(1);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
if (choice === popularModels.length + 1) {
|
|
181
|
-
rl.question("Enter custom OpenRouter Model ID (e.g. microsoft/phi-3-medium-128k-instruct): ", (customModel) => {
|
|
182
|
-
const selected = customModel.trim();
|
|
183
|
-
if (!selected) {
|
|
184
|
-
console.log("❌ No model entered. Keeping current model.");
|
|
185
|
-
} else {
|
|
186
|
-
saveLocalConfig({ model: selected });
|
|
187
|
-
console.log(`\n✅ Saved! Model set to: \x1b[32m${selected}\x1b[0m`);
|
|
188
|
-
console.log(` Saved config to: .bugzerorc`);
|
|
189
|
-
}
|
|
190
|
-
rl.close();
|
|
191
|
-
});
|
|
192
|
-
} else {
|
|
193
|
-
const selected = popularModels[choice - 1].id;
|
|
194
|
-
saveLocalConfig({ model: selected });
|
|
195
|
-
console.log(`\n✅ Saved! Model set to: \x1b[32m${selected}\x1b[0m`);
|
|
196
|
-
console.log(` Saved config to: .bugzerorc`);
|
|
197
|
-
rl.close();
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
program.parse();
|