@shakecodeslikecray/whiterose 0.1.0 → 0.2.0
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/dist/cli/index.js +144 -91
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -715,8 +715,8 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
|
|
|
715
715
|
}
|
|
716
716
|
});
|
|
717
717
|
this.currentProcess.stderr?.on("data", (chunk) => {
|
|
718
|
-
const
|
|
719
|
-
if (
|
|
718
|
+
const text4 = chunk.toString().trim();
|
|
719
|
+
if (text4 && !text4.includes("Loading")) ;
|
|
720
720
|
});
|
|
721
721
|
await this.currentProcess;
|
|
722
722
|
if (buffer.trim()) {
|
|
@@ -903,16 +903,16 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
|
|
|
903
903
|
// ─────────────────────────────────────────────────────────────
|
|
904
904
|
// Utilities
|
|
905
905
|
// ─────────────────────────────────────────────────────────────
|
|
906
|
-
extractJson(
|
|
907
|
-
const codeBlockMatch =
|
|
906
|
+
extractJson(text4) {
|
|
907
|
+
const codeBlockMatch = text4.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
908
908
|
if (codeBlockMatch) {
|
|
909
909
|
return codeBlockMatch[1].trim();
|
|
910
910
|
}
|
|
911
|
-
const arrayMatch =
|
|
911
|
+
const arrayMatch = text4.match(/\[[\s\S]*\]/);
|
|
912
912
|
if (arrayMatch) {
|
|
913
913
|
return arrayMatch[0];
|
|
914
914
|
}
|
|
915
|
-
const objectMatch =
|
|
915
|
+
const objectMatch = text4.match(/\{[\s\S]*\}/);
|
|
916
916
|
if (objectMatch) {
|
|
917
917
|
return objectMatch[0];
|
|
918
918
|
}
|
|
@@ -1266,12 +1266,12 @@ Output JSON ONLY describing:
|
|
|
1266
1266
|
// ─────────────────────────────────────────────────────────────
|
|
1267
1267
|
// Utilities
|
|
1268
1268
|
// ─────────────────────────────────────────────────────────────
|
|
1269
|
-
extractJson(
|
|
1270
|
-
const codeBlockMatch =
|
|
1269
|
+
extractJson(text4) {
|
|
1270
|
+
const codeBlockMatch = text4.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
1271
1271
|
if (codeBlockMatch) return codeBlockMatch[1].trim();
|
|
1272
|
-
const arrayMatch =
|
|
1272
|
+
const arrayMatch = text4.match(/\[[\s\S]*\]/);
|
|
1273
1273
|
if (arrayMatch) return arrayMatch[0];
|
|
1274
|
-
const objectMatch =
|
|
1274
|
+
const objectMatch = text4.match(/\{[\s\S]*\}/);
|
|
1275
1275
|
if (objectMatch) return objectMatch[0];
|
|
1276
1276
|
return null;
|
|
1277
1277
|
}
|
|
@@ -2330,10 +2330,10 @@ async function scanCommand(paths, options) {
|
|
|
2330
2330
|
if (options.full || paths.length > 0) {
|
|
2331
2331
|
scanType = "full";
|
|
2332
2332
|
if (!isQuiet) {
|
|
2333
|
-
const
|
|
2334
|
-
|
|
2333
|
+
const spinner6 = p3.spinner();
|
|
2334
|
+
spinner6.start("Scanning files...");
|
|
2335
2335
|
filesToScan = paths.length > 0 ? paths : await scanCodebase(cwd, config);
|
|
2336
|
-
|
|
2336
|
+
spinner6.stop(`Found ${filesToScan.length} files to scan`);
|
|
2337
2337
|
} else {
|
|
2338
2338
|
filesToScan = paths.length > 0 ? paths : await scanCodebase(cwd, config);
|
|
2339
2339
|
}
|
|
@@ -2481,14 +2481,23 @@ async function scanCommand(paths, options) {
|
|
|
2481
2481
|
} else if (options.sarif) {
|
|
2482
2482
|
console.log(JSON.stringify(outputSarif(result), null, 2));
|
|
2483
2483
|
} else {
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2484
|
+
const outputDir = join(cwd, "whiterose-output");
|
|
2485
|
+
const reportsDir = join(whiterosePath, "reports");
|
|
2486
|
+
const { mkdirSync: mkdirSync2 } = await import('fs');
|
|
2487
|
+
try {
|
|
2488
|
+
mkdirSync2(outputDir, { recursive: true });
|
|
2489
|
+
mkdirSync2(reportsDir, { recursive: true });
|
|
2490
|
+
} catch {
|
|
2491
2491
|
}
|
|
2492
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
2493
|
+
const markdown = outputMarkdown(result);
|
|
2494
|
+
const mdPath = join(outputDir, "bugs.md");
|
|
2495
|
+
writeFileSync(mdPath, markdown);
|
|
2496
|
+
const sarifPath = join(outputDir, "bugs.sarif");
|
|
2497
|
+
writeFileSync(sarifPath, JSON.stringify(outputSarif(result), null, 2));
|
|
2498
|
+
const jsonPath = join(outputDir, "bugs.json");
|
|
2499
|
+
writeFileSync(jsonPath, JSON.stringify(result, null, 2));
|
|
2500
|
+
writeFileSync(join(reportsDir, `${timestamp}.sarif`), JSON.stringify(outputSarif(result), null, 2));
|
|
2492
2501
|
console.log();
|
|
2493
2502
|
p3.log.message(chalk.bold("Scan Results"));
|
|
2494
2503
|
console.log();
|
|
@@ -2499,6 +2508,11 @@ async function scanCommand(paths, options) {
|
|
|
2499
2508
|
console.log();
|
|
2500
2509
|
console.log(` ${chalk.bold("Total:")} ${result.summary.total} bugs found`);
|
|
2501
2510
|
console.log();
|
|
2511
|
+
p3.log.success("Reports saved:");
|
|
2512
|
+
console.log(` ${chalk.dim("\u251C")} ${chalk.cyan(mdPath)}`);
|
|
2513
|
+
console.log(` ${chalk.dim("\u251C")} ${chalk.cyan(sarifPath)}`);
|
|
2514
|
+
console.log(` ${chalk.dim("\u2514")} ${chalk.cyan(jsonPath)}`);
|
|
2515
|
+
console.log();
|
|
2502
2516
|
if (result.summary.total > 0) {
|
|
2503
2517
|
p3.log.info(`Run ${chalk.cyan("whiterose fix")} to fix bugs interactively.`);
|
|
2504
2518
|
}
|
|
@@ -3360,12 +3374,12 @@ function generateDiff(original, fixed, filename) {
|
|
|
3360
3374
|
return diff.join("\n");
|
|
3361
3375
|
}
|
|
3362
3376
|
async function startFixTUI(bugs, config, options) {
|
|
3363
|
-
return new Promise((
|
|
3377
|
+
return new Promise((resolve4) => {
|
|
3364
3378
|
const handleFix = async (bug) => {
|
|
3365
3379
|
await applyFix(bug, config, options);
|
|
3366
3380
|
};
|
|
3367
3381
|
const handleExit = () => {
|
|
3368
|
-
|
|
3382
|
+
resolve4();
|
|
3369
3383
|
};
|
|
3370
3384
|
const { unmount, waitUntilExit } = render(
|
|
3371
3385
|
/* @__PURE__ */ jsx(
|
|
@@ -3380,7 +3394,7 @@ async function startFixTUI(bugs, config, options) {
|
|
|
3380
3394
|
)
|
|
3381
3395
|
);
|
|
3382
3396
|
waitUntilExit().then(() => {
|
|
3383
|
-
|
|
3397
|
+
resolve4();
|
|
3384
3398
|
});
|
|
3385
3399
|
});
|
|
3386
3400
|
}
|
|
@@ -3702,21 +3716,21 @@ async function fixSingleBug(bug, config, options) {
|
|
|
3702
3716
|
console.log();
|
|
3703
3717
|
}
|
|
3704
3718
|
if (!options.dryRun) {
|
|
3705
|
-
const
|
|
3719
|
+
const confirm4 = await p3.confirm({
|
|
3706
3720
|
message: "Apply this fix?",
|
|
3707
3721
|
initialValue: true
|
|
3708
3722
|
});
|
|
3709
|
-
if (p3.isCancel(
|
|
3723
|
+
if (p3.isCancel(confirm4) || !confirm4) {
|
|
3710
3724
|
p3.cancel("Fix cancelled.");
|
|
3711
3725
|
process.exit(0);
|
|
3712
3726
|
}
|
|
3713
3727
|
}
|
|
3714
|
-
const
|
|
3715
|
-
|
|
3728
|
+
const spinner6 = p3.spinner();
|
|
3729
|
+
spinner6.start(options.dryRun ? "Generating fix preview..." : "Applying fix...");
|
|
3716
3730
|
try {
|
|
3717
3731
|
const result = await applyFix(bug, config, options);
|
|
3718
3732
|
if (result.success) {
|
|
3719
|
-
|
|
3733
|
+
spinner6.stop(options.dryRun ? "Fix preview generated" : "Fix applied");
|
|
3720
3734
|
if (result.diff) {
|
|
3721
3735
|
console.log();
|
|
3722
3736
|
console.log(chalk.dim(" Changes:"));
|
|
@@ -3736,12 +3750,12 @@ async function fixSingleBug(bug, config, options) {
|
|
|
3736
3750
|
}
|
|
3737
3751
|
p3.outro(chalk.green("Fix complete!"));
|
|
3738
3752
|
} else {
|
|
3739
|
-
|
|
3753
|
+
spinner6.stop("Fix failed");
|
|
3740
3754
|
p3.log.error(result.error || "Unknown error");
|
|
3741
3755
|
process.exit(1);
|
|
3742
3756
|
}
|
|
3743
3757
|
} catch (error) {
|
|
3744
|
-
|
|
3758
|
+
spinner6.stop("Fix failed");
|
|
3745
3759
|
p3.log.error(error.message);
|
|
3746
3760
|
process.exit(1);
|
|
3747
3761
|
}
|
|
@@ -3935,7 +3949,7 @@ ${chalk.red(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255
|
|
|
3935
3949
|
${chalk.dim(` "I've been staring at your code for a long time."`)}
|
|
3936
3950
|
`;
|
|
3937
3951
|
var program = new Command();
|
|
3938
|
-
program.name("whiterose").description("AI-powered bug hunter that uses your existing LLM subscription").version("0.
|
|
3952
|
+
program.name("whiterose").description("AI-powered bug hunter that uses your existing LLM subscription").version("0.2.0").hook("preAction", () => {
|
|
3939
3953
|
const args = process.argv.slice(2);
|
|
3940
3954
|
if (!args.includes("--help") && !args.includes("-h") && args.length > 0) {
|
|
3941
3955
|
console.log(BANNER);
|
|
@@ -3947,82 +3961,121 @@ program.command("fix [bugId]").description("Fix bugs interactively or by ID").op
|
|
|
3947
3961
|
program.command("refresh").description("Rebuild codebase understanding from scratch").option("--keep-config", "Keep existing config, only regenerate understanding").action(refreshCommand);
|
|
3948
3962
|
program.command("status").description("Show whiterose status (cache, last scan, provider)").action(statusCommand);
|
|
3949
3963
|
program.command("report").description("Generate BUGS.md from last scan").option("-o, --output <path>", "Output path", "BUGS.md").option("--format <format>", "Output format (markdown, sarif, json)", "markdown").action(reportCommand);
|
|
3950
|
-
async function
|
|
3964
|
+
async function showInteractiveWizard() {
|
|
3951
3965
|
console.log(BANNER);
|
|
3966
|
+
p3.intro(chalk.red("whiterose") + chalk.dim(" - AI Bug Hunter"));
|
|
3952
3967
|
const cwd = process.cwd();
|
|
3953
|
-
const
|
|
3968
|
+
const defaultPath = cwd;
|
|
3969
|
+
const repoPath = await p3.text({
|
|
3970
|
+
message: "Repository path",
|
|
3971
|
+
placeholder: defaultPath,
|
|
3972
|
+
initialValue: defaultPath,
|
|
3973
|
+
validate: (value) => {
|
|
3974
|
+
const path = value || defaultPath;
|
|
3975
|
+
if (!existsSync(path)) {
|
|
3976
|
+
return "Directory does not exist";
|
|
3977
|
+
}
|
|
3978
|
+
return void 0;
|
|
3979
|
+
}
|
|
3980
|
+
});
|
|
3981
|
+
if (p3.isCancel(repoPath)) {
|
|
3982
|
+
p3.cancel("Cancelled.");
|
|
3983
|
+
process.exit(0);
|
|
3984
|
+
}
|
|
3985
|
+
const targetPath = resolve(repoPath || defaultPath);
|
|
3986
|
+
const projectName = basename(targetPath);
|
|
3987
|
+
const whiterosePath = join(targetPath, ".whiterose");
|
|
3954
3988
|
const isInitialized = existsSync(whiterosePath);
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3989
|
+
const detectSpinner = p3.spinner();
|
|
3990
|
+
detectSpinner.start("Detecting LLM providers...");
|
|
3991
|
+
const availableProviders = await detectProvider();
|
|
3992
|
+
detectSpinner.stop(`Found ${availableProviders.length} provider(s)`);
|
|
3993
|
+
if (availableProviders.length === 0) {
|
|
3994
|
+
p3.log.error("No LLM providers detected on your system.");
|
|
3958
3995
|
console.log();
|
|
3959
|
-
|
|
3960
|
-
console.log(chalk.dim(
|
|
3961
|
-
console.log(chalk.dim(
|
|
3996
|
+
console.log(chalk.dim(" Supported providers:"));
|
|
3997
|
+
console.log(chalk.dim(" - claude-code: ") + chalk.cyan("npm install -g @anthropic-ai/claude-code"));
|
|
3998
|
+
console.log(chalk.dim(" - aider: ") + chalk.cyan("pip install aider-chat"));
|
|
3962
3999
|
console.log();
|
|
4000
|
+
p3.outro(chalk.red("Install a provider and try again."));
|
|
4001
|
+
process.exit(1);
|
|
4002
|
+
}
|
|
4003
|
+
let selectedProvider;
|
|
4004
|
+
if (availableProviders.length === 1) {
|
|
4005
|
+
selectedProvider = availableProviders[0];
|
|
4006
|
+
p3.log.info(`Using ${chalk.cyan(selectedProvider)} (only available provider)`);
|
|
4007
|
+
} else {
|
|
4008
|
+
const providerChoice = await p3.select({
|
|
4009
|
+
message: "Select LLM provider",
|
|
4010
|
+
options: availableProviders.map((provider) => ({
|
|
4011
|
+
value: provider,
|
|
4012
|
+
label: provider,
|
|
4013
|
+
hint: provider === "claude-code" ? "recommended" : void 0
|
|
4014
|
+
}))
|
|
4015
|
+
});
|
|
4016
|
+
if (p3.isCancel(providerChoice)) {
|
|
4017
|
+
p3.cancel("Cancelled.");
|
|
4018
|
+
process.exit(0);
|
|
4019
|
+
}
|
|
4020
|
+
selectedProvider = providerChoice;
|
|
3963
4021
|
}
|
|
3964
|
-
const menuOptions = [];
|
|
3965
4022
|
if (!isInitialized) {
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
4023
|
+
p3.log.warn(`Project "${projectName}" is not initialized.`);
|
|
4024
|
+
const shouldInit = await p3.confirm({
|
|
4025
|
+
message: "Initialize whiterose for this project?",
|
|
4026
|
+
initialValue: true
|
|
3970
4027
|
});
|
|
4028
|
+
if (p3.isCancel(shouldInit) || !shouldInit) {
|
|
4029
|
+
p3.cancel("Cannot scan without initialization.");
|
|
4030
|
+
process.exit(0);
|
|
4031
|
+
}
|
|
4032
|
+
process.chdir(targetPath);
|
|
4033
|
+
await initCommand({
|
|
4034
|
+
provider: selectedProvider,
|
|
4035
|
+
skipQuestions: false,
|
|
4036
|
+
force: false,
|
|
4037
|
+
unsafe: false
|
|
4038
|
+
});
|
|
4039
|
+
console.log();
|
|
3971
4040
|
} else {
|
|
3972
|
-
|
|
3973
|
-
{ value: "scan", label: "Scan", hint: "find bugs in the codebase" },
|
|
3974
|
-
{ value: "fix", label: "Fix", hint: "fix bugs interactively" },
|
|
3975
|
-
{ value: "status", label: "Status", hint: "show current status" },
|
|
3976
|
-
{ value: "report", label: "Report", hint: "generate bug report" },
|
|
3977
|
-
{ value: "refresh", label: "Refresh", hint: "rebuild codebase understanding" }
|
|
3978
|
-
);
|
|
4041
|
+
process.chdir(targetPath);
|
|
3979
4042
|
}
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
4043
|
+
const scanDepth = await p3.select({
|
|
4044
|
+
message: "Scan depth",
|
|
4045
|
+
options: [
|
|
4046
|
+
{
|
|
4047
|
+
value: "quick",
|
|
4048
|
+
label: "Quick scan",
|
|
4049
|
+
hint: "faster, incremental changes only"
|
|
4050
|
+
},
|
|
4051
|
+
{
|
|
4052
|
+
value: "deep",
|
|
4053
|
+
label: "Deep scan",
|
|
4054
|
+
hint: "thorough, full codebase analysis (recommended)"
|
|
4055
|
+
}
|
|
4056
|
+
],
|
|
4057
|
+
initialValue: "deep"
|
|
3985
4058
|
});
|
|
3986
|
-
if (p3.isCancel(
|
|
3987
|
-
p3.
|
|
4059
|
+
if (p3.isCancel(scanDepth)) {
|
|
4060
|
+
p3.cancel("Cancelled.");
|
|
3988
4061
|
process.exit(0);
|
|
3989
4062
|
}
|
|
3990
4063
|
console.log();
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
adversarial: true,
|
|
4004
|
-
unsafe: false
|
|
4005
|
-
});
|
|
4006
|
-
break;
|
|
4007
|
-
case "fix":
|
|
4008
|
-
await fixCommand(void 0, { dryRun: false });
|
|
4009
|
-
break;
|
|
4010
|
-
case "status":
|
|
4011
|
-
await statusCommand();
|
|
4012
|
-
break;
|
|
4013
|
-
case "report":
|
|
4014
|
-
await reportCommand({ output: "BUGS.md", format: "markdown" });
|
|
4015
|
-
break;
|
|
4016
|
-
case "refresh":
|
|
4017
|
-
await refreshCommand();
|
|
4018
|
-
break;
|
|
4019
|
-
case "help":
|
|
4020
|
-
program.help();
|
|
4021
|
-
break;
|
|
4022
|
-
}
|
|
4064
|
+
p3.log.step("Starting scan...");
|
|
4065
|
+
console.log();
|
|
4066
|
+
await scanCommand([], {
|
|
4067
|
+
full: scanDepth === "deep",
|
|
4068
|
+
json: false,
|
|
4069
|
+
sarif: false,
|
|
4070
|
+
provider: selectedProvider,
|
|
4071
|
+
category: void 0,
|
|
4072
|
+
minConfidence: "low",
|
|
4073
|
+
adversarial: true,
|
|
4074
|
+
unsafe: false
|
|
4075
|
+
});
|
|
4023
4076
|
}
|
|
4024
4077
|
if (process.argv.length === 2) {
|
|
4025
|
-
|
|
4078
|
+
showInteractiveWizard().catch((error) => {
|
|
4026
4079
|
console.error(chalk.red("Error:"), error.message);
|
|
4027
4080
|
process.exit(1);
|
|
4028
4081
|
});
|