@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 +51 -87
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
735
|
-
buffer +=
|
|
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
|
|
745
|
-
if (
|
|
746
|
-
this.reportProgress(`Claude: ${
|
|
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
|
|
791
|
-
if (
|
|
792
|
-
this.processTextContent(
|
|
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(
|
|
858
|
-
this.streamBuffer +=
|
|
859
|
-
this.fullResponseBuffer +=
|
|
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(
|
|
879
|
-
const codeBlockMatch =
|
|
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 <
|
|
897
|
-
const char =
|
|
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 =
|
|
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(
|
|
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 <
|
|
947
|
-
const char =
|
|
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 =
|
|
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(
|
|
1149
|
-
const codeBlockMatch =
|
|
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(
|
|
1153
|
+
return this.findBalancedJson(text2);
|
|
1154
1154
|
}
|
|
1155
1155
|
// Find the first balanced JSON object or array in text
|
|
1156
|
-
findBalancedJson(
|
|
1157
|
-
const objectStart =
|
|
1158
|
-
const arrayStart =
|
|
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 <
|
|
1183
|
-
const char =
|
|
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
|
|
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(
|
|
1557
|
-
const codeBlockMatch =
|
|
1556
|
+
extractJson(text2) {
|
|
1557
|
+
const codeBlockMatch = text2.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
1558
1558
|
if (codeBlockMatch) return codeBlockMatch[1].trim();
|
|
1559
|
-
const arrayMatch =
|
|
1559
|
+
const arrayMatch = text2.match(/\[[\s\S]*\]/);
|
|
1560
1560
|
if (arrayMatch) return arrayMatch[0];
|
|
1561
|
-
const objectMatch =
|
|
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
|
-
|
|
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
|
|
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.
|
|
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,
|
|
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:
|
|
4428
|
+
full: true,
|
|
4429
|
+
// Always thorough for bug hunting
|
|
4466
4430
|
json: false,
|
|
4467
4431
|
sarif: false,
|
|
4468
4432
|
provider: selectedProvider,
|