@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 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 text3 = chunk.toString().trim();
719
- if (text3 && !text3.includes("Loading")) ;
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(text3) {
907
- const codeBlockMatch = text3.match(/```(?:json)?\s*([\s\S]*?)```/);
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 = text3.match(/\[[\s\S]*\]/);
911
+ const arrayMatch = text4.match(/\[[\s\S]*\]/);
912
912
  if (arrayMatch) {
913
913
  return arrayMatch[0];
914
914
  }
915
- const objectMatch = text3.match(/\{[\s\S]*\}/);
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(text3) {
1270
- const codeBlockMatch = text3.match(/```(?:json)?\s*([\s\S]*?)```/);
1269
+ extractJson(text4) {
1270
+ const codeBlockMatch = text4.match(/```(?:json)?\s*([\s\S]*?)```/);
1271
1271
  if (codeBlockMatch) return codeBlockMatch[1].trim();
1272
- const arrayMatch = text3.match(/\[[\s\S]*\]/);
1272
+ const arrayMatch = text4.match(/\[[\s\S]*\]/);
1273
1273
  if (arrayMatch) return arrayMatch[0];
1274
- const objectMatch = text3.match(/\{[\s\S]*\}/);
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 spinner5 = p3.spinner();
2334
- spinner5.start("Scanning files...");
2333
+ const spinner6 = p3.spinner();
2334
+ spinner6.start("Scanning files...");
2335
2335
  filesToScan = paths.length > 0 ? paths : await scanCodebase(cwd, config);
2336
- spinner5.stop(`Found ${filesToScan.length} files to scan`);
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
- if (config.output.sarif) {
2485
- const sarifPath = join(whiterosePath, "reports", `${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.sarif`);
2486
- writeFileSync(sarifPath, JSON.stringify(outputSarif(result), null, 2));
2487
- }
2488
- if (config.output.markdown) {
2489
- const markdown = outputMarkdown(result);
2490
- writeFileSync(join(cwd, config.output.markdownPath), markdown);
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((resolve3) => {
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
- resolve3();
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
- resolve3();
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 confirm3 = await p3.confirm({
3719
+ const confirm4 = await p3.confirm({
3706
3720
  message: "Apply this fix?",
3707
3721
  initialValue: true
3708
3722
  });
3709
- if (p3.isCancel(confirm3) || !confirm3) {
3723
+ if (p3.isCancel(confirm4) || !confirm4) {
3710
3724
  p3.cancel("Fix cancelled.");
3711
3725
  process.exit(0);
3712
3726
  }
3713
3727
  }
3714
- const spinner5 = p3.spinner();
3715
- spinner5.start(options.dryRun ? "Generating fix preview..." : "Applying fix...");
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
- spinner5.stop(options.dryRun ? "Fix preview generated" : "Fix applied");
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
- spinner5.stop("Fix failed");
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
- spinner5.stop("Fix failed");
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.1.0").hook("preAction", () => {
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 showInteractiveMenu() {
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 whiterosePath = join(cwd, ".whiterose");
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
- if (isInitialized) {
3956
- console.log(chalk.dim(` Project: ${chalk.white(cwd.split("/").pop())}`));
3957
- console.log(chalk.dim(` Status: ${chalk.green("initialized")}`));
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
- } else {
3960
- console.log(chalk.dim(` Project: ${chalk.white(cwd.split("/").pop())}`));
3961
- console.log(chalk.dim(` Status: ${chalk.yellow("not initialized")}`));
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
- menuOptions.push({
3967
- value: "init",
3968
- label: "Initialize",
3969
- hint: "set up whiterose for this project"
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
- menuOptions.push(
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
- menuOptions.push({ value: "help", label: "Help", hint: "show all commands" });
3981
- menuOptions.push({ value: "exit", label: "Exit" });
3982
- const choice = await p3.select({
3983
- message: "What would you like to do?",
3984
- options: menuOptions
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(choice) || choice === "exit") {
3987
- p3.outro(chalk.dim("Goodbye."));
4059
+ if (p3.isCancel(scanDepth)) {
4060
+ p3.cancel("Cancelled.");
3988
4061
  process.exit(0);
3989
4062
  }
3990
4063
  console.log();
3991
- switch (choice) {
3992
- case "init":
3993
- await initCommand({ provider: "claude-code", skipQuestions: false, force: false, unsafe: false });
3994
- break;
3995
- case "scan":
3996
- await scanCommand([], {
3997
- full: false,
3998
- json: false,
3999
- sarif: false,
4000
- provider: void 0,
4001
- category: void 0,
4002
- minConfidence: "low",
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
- showInteractiveMenu().catch((error) => {
4078
+ showInteractiveWizard().catch((error) => {
4026
4079
  console.error(chalk.red("Error:"), error.message);
4027
4080
  process.exit(1);
4028
4081
  });