@shakecodeslikecray/whiterose 0.2.5 → 0.2.7

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
@@ -731,8 +731,8 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
731
731
  let buffer = "";
732
732
  this.currentProcess.stdout?.on("data", (chunk) => {
733
733
  lastActivity = Date.now();
734
- const text3 = chunk.toString();
735
- buffer += text3;
734
+ const text2 = chunk.toString();
735
+ buffer += text2;
736
736
  const lines = buffer.split("\n");
737
737
  buffer = lines.pop() || "";
738
738
  for (const line of lines) {
@@ -741,9 +741,9 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
741
741
  });
742
742
  this.currentProcess.stderr?.on("data", (chunk) => {
743
743
  lastActivity = Date.now();
744
- const text3 = chunk.toString().trim();
745
- if (text3) {
746
- this.reportProgress(`Claude: ${text3.slice(0, 50)}...`);
744
+ const text2 = chunk.toString().trim();
745
+ if (text2) {
746
+ this.reportProgress(`Claude: ${text2.slice(0, 50)}...`);
747
747
  }
748
748
  });
749
749
  await new Promise((resolve5, reject) => {
@@ -787,9 +787,9 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
787
787
  } else if (event.type === "stream_event") {
788
788
  const innerEvent = event.event;
789
789
  if (innerEvent?.type === "content_block_delta") {
790
- const text3 = innerEvent.delta?.text;
791
- if (text3) {
792
- this.processTextContent(text3, callbacks);
790
+ const text2 = innerEvent.delta?.text;
791
+ if (text2) {
792
+ this.processTextContent(text2, callbacks);
793
793
  }
794
794
  } else if (innerEvent?.type === "content_block_start") {
795
795
  if (innerEvent.content_block?.type === "tool_use") {
@@ -854,9 +854,9 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
854
854
  streamBuffer = "";
855
855
  // Full response text for fallback extraction
856
856
  fullResponseBuffer = "";
857
- processTextContent(text3, callbacks) {
858
- this.streamBuffer += text3;
859
- this.fullResponseBuffer += text3;
857
+ processTextContent(text2, callbacks) {
858
+ this.streamBuffer += text2;
859
+ this.fullResponseBuffer += text2;
860
860
  const lines = this.streamBuffer.split("\n");
861
861
  this.streamBuffer = lines.pop() || "";
862
862
  for (const line of lines) {
@@ -875,8 +875,8 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
875
875
  }
876
876
  }
877
877
  // Try to extract understanding JSON from text without markers
878
- tryExtractUnderstandingJson(text3, callbacks) {
879
- const codeBlockMatch = text3.match(/```(?:json)?\s*([\s\S]*?)```/);
878
+ tryExtractUnderstandingJson(text2, callbacks) {
879
+ const codeBlockMatch = text2.match(/```(?:json)?\s*([\s\S]*?)```/);
880
880
  if (codeBlockMatch) {
881
881
  const jsonStr = codeBlockMatch[1].trim();
882
882
  try {
@@ -893,8 +893,8 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
893
893
  let start = -1;
894
894
  let inString = false;
895
895
  let escapeNext = false;
896
- for (let i = 0; i < text3.length; i++) {
897
- const char = text3[i];
896
+ for (let i = 0; i < text2.length; i++) {
897
+ const char = text2[i];
898
898
  if (escapeNext) {
899
899
  escapeNext = false;
900
900
  continue;
@@ -916,7 +916,7 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
916
916
  } else if (char === "}") {
917
917
  depth--;
918
918
  if (depth === 0 && start !== -1) {
919
- const candidate = text3.slice(start, i + 1);
919
+ const candidate = text2.slice(start, i + 1);
920
920
  if (candidate.includes('"summary"') && candidate.includes('"type"')) {
921
921
  candidates.push(candidate);
922
922
  }
@@ -937,14 +937,14 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
937
937
  }
938
938
  }
939
939
  // Try to extract bug JSON objects from text without markers
940
- tryExtractBugsJson(text3, callbacks) {
940
+ tryExtractBugsJson(text2, callbacks) {
941
941
  const candidates = [];
942
942
  let depth = 0;
943
943
  let start = -1;
944
944
  let inString = false;
945
945
  let escapeNext = false;
946
- for (let i = 0; i < text3.length; i++) {
947
- const char = text3[i];
946
+ for (let i = 0; i < text2.length; i++) {
947
+ const char = text2[i];
948
948
  if (escapeNext) {
949
949
  escapeNext = false;
950
950
  continue;
@@ -966,7 +966,7 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
966
966
  } else if (char === "}") {
967
967
  depth--;
968
968
  if (depth === 0 && start !== -1) {
969
- const candidate = text3.slice(start, i + 1);
969
+ const candidate = text2.slice(start, i + 1);
970
970
  if (candidate.includes('"file"') && candidate.includes('"line"') && candidate.includes('"title"')) {
971
971
  candidates.push(candidate);
972
972
  }
@@ -1145,17 +1145,17 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
1145
1145
  // ─────────────────────────────────────────────────────────────
1146
1146
  // Utilities
1147
1147
  // ─────────────────────────────────────────────────────────────
1148
- extractJson(text3) {
1149
- const codeBlockMatch = text3.match(/```(?:json)?\s*([\s\S]*?)```/);
1148
+ extractJson(text2) {
1149
+ const codeBlockMatch = text2.match(/```(?:json)?\s*([\s\S]*?)```/);
1150
1150
  if (codeBlockMatch) {
1151
1151
  return codeBlockMatch[1].trim();
1152
1152
  }
1153
- return this.findBalancedJson(text3);
1153
+ return this.findBalancedJson(text2);
1154
1154
  }
1155
1155
  // Find the first balanced JSON object or array in text
1156
- findBalancedJson(text3) {
1157
- const objectStart = text3.indexOf("{");
1158
- const arrayStart = text3.indexOf("[");
1156
+ findBalancedJson(text2) {
1157
+ const objectStart = text2.indexOf("{");
1158
+ const arrayStart = text2.indexOf("[");
1159
1159
  let start = -1;
1160
1160
  let openChar = "{";
1161
1161
  let closeChar = "}";
@@ -1179,8 +1179,8 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
1179
1179
  let depth = 0;
1180
1180
  let inString = false;
1181
1181
  let escapeNext = false;
1182
- for (let i = start; i < text3.length; i++) {
1183
- const char = text3[i];
1182
+ for (let i = start; i < text2.length; i++) {
1183
+ const char = text2[i];
1184
1184
  if (escapeNext) {
1185
1185
  escapeNext = false;
1186
1186
  continue;
@@ -1199,7 +1199,7 @@ Set "survived": true if you CANNOT disprove it (it's a real bug).`;
1199
1199
  } else if (char === closeChar) {
1200
1200
  depth--;
1201
1201
  if (depth === 0) {
1202
- return text3.slice(start, i + 1);
1202
+ return text2.slice(start, i + 1);
1203
1203
  }
1204
1204
  }
1205
1205
  }
@@ -1553,12 +1553,12 @@ Output JSON ONLY describing:
1553
1553
  // ─────────────────────────────────────────────────────────────
1554
1554
  // Utilities
1555
1555
  // ─────────────────────────────────────────────────────────────
1556
- extractJson(text3) {
1557
- const codeBlockMatch = text3.match(/```(?:json)?\s*([\s\S]*?)```/);
1556
+ extractJson(text2) {
1557
+ const codeBlockMatch = text2.match(/```(?:json)?\s*([\s\S]*?)```/);
1558
1558
  if (codeBlockMatch) return codeBlockMatch[1].trim();
1559
- const arrayMatch = text3.match(/\[[\s\S]*\]/);
1559
+ const arrayMatch = text2.match(/\[[\s\S]*\]/);
1560
1560
  if (arrayMatch) return arrayMatch[0];
1561
- const objectMatch = text3.match(/\{[\s\S]*\}/);
1561
+ const objectMatch = text2.match(/\{[\s\S]*\}/);
1562
1562
  if (objectMatch) return objectMatch[0];
1563
1563
  return null;
1564
1564
  }
@@ -2129,7 +2129,8 @@ async function initCommand(options) {
2129
2129
  p3.log.error(String(error));
2130
2130
  process.exit(1);
2131
2131
  }
2132
- if (!options.skipQuestions) {
2132
+ const skipInteractive = options.skipQuestions || options.ci;
2133
+ if (!skipInteractive) {
2133
2134
  p3.log.message(chalk.bold("\nHere's what I understand about your codebase:\n"));
2134
2135
  p3.log.message(` ${chalk.cyan("Type:")} ${understanding.summary.type}`);
2135
2136
  p3.log.message(` ${chalk.cyan("Framework:")} ${understanding.summary.framework || "None detected"}`);
@@ -2161,33 +2162,9 @@ async function initCommand(options) {
2161
2162
  p3.log.info("You can edit .whiterose/intent.md after initialization to correct the understanding.");
2162
2163
  }
2163
2164
  const priorities = {};
2164
- for (const feature of understanding.features.filter((f) => f.priority === "critical").slice(0, 3)) {
2165
- const priority = await p3.select({
2166
- message: `I detected ${chalk.bold(feature.name)}. How should I prioritize bugs here?`,
2167
- options: [
2168
- { value: "critical", label: "Critical", hint: "highest priority" },
2169
- { value: "high", label: "High" },
2170
- { value: "medium", label: "Medium" },
2171
- { value: "low", label: "Low" },
2172
- { value: "ignore", label: "Ignore", hint: "skip this area" }
2173
- ],
2174
- initialValue: "critical"
2175
- });
2176
- if (p3.isCancel(priority)) {
2177
- p3.cancel("Initialization cancelled.");
2178
- process.exit(0);
2179
- }
2165
+ for (const feature of understanding.features) {
2180
2166
  for (const file of feature.relatedFiles) {
2181
- priorities[file] = priority;
2182
- }
2183
- }
2184
- const areasOfConcern = await p3.text({
2185
- message: "Any specific files or directories you want me to focus on? (comma-separated, or leave empty)",
2186
- placeholder: "src/api/checkout.ts, src/hooks/useAuth.ts"
2187
- });
2188
- if (!p3.isCancel(areasOfConcern) && areasOfConcern) {
2189
- for (const area of areasOfConcern.split(",").map((s) => s.trim())) {
2190
- priorities[area] = "critical";
2167
+ priorities[file] = feature.priority;
2191
2168
  }
2192
2169
  }
2193
2170
  understanding._userPriorities = priorities;
@@ -2599,7 +2576,7 @@ async function scanCommand(paths, options) {
2599
2576
  }
2600
2577
  process.exit(1);
2601
2578
  }
2602
- const isQuiet = options.json || options.sarif;
2579
+ const isQuiet = options.json || options.sarif || options.ci;
2603
2580
  if (!isQuiet) {
2604
2581
  p3.intro(chalk.red("whiterose") + chalk.dim(" - scanning for bugs"));
2605
2582
  }
@@ -2719,10 +2696,16 @@ async function scanCommand(paths, options) {
2719
2696
  total: bugs.length
2720
2697
  }
2721
2698
  };
2722
- if (options.json) {
2699
+ if (options.json || options.ci && !options.sarif) {
2723
2700
  console.log(JSON.stringify(result, null, 2));
2701
+ if (options.ci && result.summary.total > 0) {
2702
+ process.exit(1);
2703
+ }
2724
2704
  } else if (options.sarif) {
2725
2705
  console.log(JSON.stringify(outputSarif(result), null, 2));
2706
+ if (options.ci && result.summary.total > 0) {
2707
+ process.exit(1);
2708
+ }
2726
2709
  } else {
2727
2710
  const outputDir = join(cwd, "whiterose-output");
2728
2711
  const reportsDir = join(whiterosePath, "reports");
@@ -4346,14 +4329,14 @@ ${chalk.red(" \u255A\u2550\u2550\u255D\u255A\u2550\u2550\u255D \u255A\u2550\u255
4346
4329
  ${chalk.dim(` "I've been staring at your code for a long time."`)}
4347
4330
  `;
4348
4331
  var program = new Command();
4349
- program.name("whiterose").description("AI-powered bug hunter that uses your existing LLM subscription").version("0.2.5").hook("preAction", () => {
4332
+ program.name("whiterose").description("AI-powered bug hunter that uses your existing LLM subscription").version("0.2.7").hook("preAction", () => {
4350
4333
  const args = process.argv.slice(2);
4351
4334
  if (!args.includes("--help") && !args.includes("-h") && args.length > 0) {
4352
4335
  console.log(BANNER);
4353
4336
  }
4354
4337
  });
4355
- program.command("init").description("Initialize whiterose for this project (scans codebase, asks questions, generates config)").option("-p, --provider <provider>", "LLM provider to use", "claude-code").option("--skip-questions", "Skip interactive questions, use defaults").option("--force", "Overwrite existing .whiterose directory").action(initCommand);
4356
- program.command("scan [paths...]").description("Scan for bugs in the codebase (includes inline validation)").option("-f, --full", "Force full scan (ignore cache)").option("--json", "Output as JSON only").option("--sarif", "Output as SARIF only").option("-p, --provider <provider>", "Override LLM provider").option("-c, --category <categories...>", "Filter by bug categories").option("--min-confidence <level>", "Minimum confidence level to report", "low").action(scanCommand);
4338
+ program.command("init").description("Initialize whiterose for this project (scans codebase, generates config)").option("-p, --provider <provider>", "LLM provider to use", "claude-code").option("--force", "Overwrite existing .whiterose directory").option("--ci", "CI mode: non-interactive, use defaults (same as --skip-questions)").option("--skip-questions", "Skip interactive questions, use defaults").action(initCommand);
4339
+ program.command("scan [paths...]").description("Scan for bugs in the codebase (includes inline validation)").option("-f, --full", "Force full scan (ignore cache)").option("--json", "Output as JSON only").option("--sarif", "Output as SARIF only").option("-p, --provider <provider>", "Override LLM provider").option("-c, --category <categories...>", "Filter by bug categories").option("--min-confidence <level>", "Minimum confidence level to report", "low").option("--ci", "CI mode: non-interactive, exit code 1 if bugs found (for CI/CD and git hooks)").action(scanCommand);
4357
4340
  program.command("fix [bugId]").description("Fix bugs interactively or by ID").option("--dry-run", "Show proposed fixes without applying").option("--branch <name>", "Create fixes in a new branch").option("--sarif <path>", "Load bugs from an external SARIF file").option("--github <url>", "Load bug from a GitHub issue URL").option("--describe", "Manually describe a bug to fix").option("--unsafe", "Skip all permission prompts (full trust mode)").action(fixCommand);
4358
4341
  program.command("refresh").description("Rebuild codebase understanding from scratch").option("--keep-config", "Keep existing config, only regenerate understanding").action(refreshCommand);
4359
4342
  program.command("status").description("Show whiterose status (cache, last scan, provider)").action(statusCommand);
@@ -4438,31 +4421,12 @@ async function showInteractiveWizard() {
4438
4421
  } else {
4439
4422
  process.chdir(targetPath);
4440
4423
  }
4441
- const scanDepth = await p3.select({
4442
- message: "Scan depth",
4443
- options: [
4444
- {
4445
- value: "quick",
4446
- label: "Quick scan",
4447
- hint: "faster, incremental changes only"
4448
- },
4449
- {
4450
- value: "deep",
4451
- label: "Deep scan",
4452
- hint: "thorough, full codebase analysis (recommended)"
4453
- }
4454
- ],
4455
- initialValue: "deep"
4456
- });
4457
- if (p3.isCancel(scanDepth)) {
4458
- p3.cancel("Cancelled.");
4459
- process.exit(0);
4460
- }
4461
4424
  console.log();
4462
- p3.log.step("Starting scan...");
4425
+ p3.log.step("Starting bug hunt (thorough scan)...");
4463
4426
  console.log();
4464
4427
  await scanCommand([], {
4465
- full: scanDepth === "deep",
4428
+ full: true,
4429
+ // Always thorough for bug hunting
4466
4430
  json: false,
4467
4431
  sarif: false,
4468
4432
  provider: selectedProvider,