@shakecodeslikecray/whiterose 1.0.5 → 1.0.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 +152 -28
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +66 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync, existsSync, mkdirSync, writeFileSync, readdirSync, statSync,
|
|
2
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync, rmSync, readdirSync, statSync, mkdtempSync, realpathSync } from 'fs';
|
|
3
3
|
import { join, dirname, isAbsolute, resolve, basename, relative } from 'path';
|
|
4
4
|
import chalk3 from 'chalk';
|
|
5
5
|
import * as readline from 'readline';
|
|
@@ -326,6 +326,20 @@ var ClaudeCodeExecutor = class {
|
|
|
326
326
|
reject: false
|
|
327
327
|
}
|
|
328
328
|
);
|
|
329
|
+
if (stderr) {
|
|
330
|
+
if (stderr.includes("429") || stderr.includes("rate limit") || stderr.includes("too many requests")) {
|
|
331
|
+
throw new Error("Claude API rate limit reached. Try again later.");
|
|
332
|
+
}
|
|
333
|
+
if (stderr.includes("401") || stderr.includes("unauthorized") || stderr.includes("invalid api key")) {
|
|
334
|
+
throw new Error("Claude API authentication failed. Check your API key.");
|
|
335
|
+
}
|
|
336
|
+
if (stderr.includes("402") || stderr.includes("insufficient") || stderr.includes("billing")) {
|
|
337
|
+
throw new Error("Claude API billing error. Check your account credits.");
|
|
338
|
+
}
|
|
339
|
+
if (stderr.includes("Error:") && !stdout) {
|
|
340
|
+
throw new Error(`Claude Code error: ${stderr.substring(0, 200)}`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
329
343
|
return {
|
|
330
344
|
output: stdout || "",
|
|
331
345
|
error: stderr || void 0
|
|
@@ -370,6 +384,19 @@ var CodexExecutor = class {
|
|
|
370
384
|
reject: false
|
|
371
385
|
}
|
|
372
386
|
);
|
|
387
|
+
if (stderr) {
|
|
388
|
+
if (stderr.includes("429") || stderr.includes("usage_limit") || stderr.includes("rate limit")) {
|
|
389
|
+
throw new Error("Codex API rate limit reached. Try again later or upgrade your plan.");
|
|
390
|
+
}
|
|
391
|
+
if (stderr.includes("401") || stderr.includes("unauthorized") || stderr.includes("authentication")) {
|
|
392
|
+
throw new Error("Codex API authentication failed. Check your API key.");
|
|
393
|
+
}
|
|
394
|
+
if (stderr.includes("ERROR:") || stderr.includes("error=http")) {
|
|
395
|
+
const errorMatch = stderr.match(/ERROR:\s*(.+?)(?:\n|$)/i) || stderr.match(/error=(.+?)(?:\n|$)/);
|
|
396
|
+
const errorMsg = errorMatch ? errorMatch[1].trim() : stderr.substring(0, 200);
|
|
397
|
+
throw new Error(`Codex API error: ${errorMsg}`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
373
400
|
let output = stdout || "";
|
|
374
401
|
if (existsSync(outputFile)) {
|
|
375
402
|
try {
|
|
@@ -2253,6 +2280,7 @@ var CoreScanner = class {
|
|
|
2253
2280
|
*/
|
|
2254
2281
|
async quickScan(context) {
|
|
2255
2282
|
const cwd = process.cwd();
|
|
2283
|
+
this.passErrors = [];
|
|
2256
2284
|
this.report(`
|
|
2257
2285
|
\u2550\u2550\u2550\u2550 QUICK SCAN \u2550\u2550\u2550\u2550`);
|
|
2258
2286
|
this.report(` Provider: ${this.executor.name}`);
|
|
@@ -2266,7 +2294,9 @@ var CoreScanner = class {
|
|
|
2266
2294
|
this.report(` Found ${bugs.length} bugs`);
|
|
2267
2295
|
return bugs;
|
|
2268
2296
|
} catch (error) {
|
|
2269
|
-
|
|
2297
|
+
const errorMsg = error.message || String(error);
|
|
2298
|
+
this.report(` Error: ${errorMsg}`);
|
|
2299
|
+
this.passErrors.push({ passName: "quick-scan", error: errorMsg });
|
|
2270
2300
|
return [];
|
|
2271
2301
|
}
|
|
2272
2302
|
}
|
|
@@ -2311,11 +2341,41 @@ var CoreScanner = class {
|
|
|
2311
2341
|
}
|
|
2312
2342
|
}
|
|
2313
2343
|
} else {
|
|
2314
|
-
const
|
|
2315
|
-
if (
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2344
|
+
const firstBrace = output.indexOf("{");
|
|
2345
|
+
if (firstBrace !== -1) {
|
|
2346
|
+
const substring = output.slice(firstBrace);
|
|
2347
|
+
let depth = 0;
|
|
2348
|
+
let inString = false;
|
|
2349
|
+
let escape = false;
|
|
2350
|
+
for (let i = 0; i < substring.length; i++) {
|
|
2351
|
+
const char = substring[i];
|
|
2352
|
+
if (escape) {
|
|
2353
|
+
escape = false;
|
|
2354
|
+
continue;
|
|
2355
|
+
}
|
|
2356
|
+
if (char === "\\" && inString) {
|
|
2357
|
+
escape = true;
|
|
2358
|
+
continue;
|
|
2359
|
+
}
|
|
2360
|
+
if (char === '"') {
|
|
2361
|
+
inString = !inString;
|
|
2362
|
+
continue;
|
|
2363
|
+
}
|
|
2364
|
+
if (inString) continue;
|
|
2365
|
+
if (char === "{") {
|
|
2366
|
+
depth++;
|
|
2367
|
+
} else if (char === "}") {
|
|
2368
|
+
depth--;
|
|
2369
|
+
if (depth === 0) {
|
|
2370
|
+
const candidate = substring.slice(0, i + 1);
|
|
2371
|
+
try {
|
|
2372
|
+
parsed = JSON.parse(candidate);
|
|
2373
|
+
break;
|
|
2374
|
+
} catch {
|
|
2375
|
+
depth = 1;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2319
2379
|
}
|
|
2320
2380
|
}
|
|
2321
2381
|
}
|
|
@@ -3343,6 +3403,9 @@ async function initCommand(options) {
|
|
|
3343
3403
|
}
|
|
3344
3404
|
const writeSpinner = p3.spinner();
|
|
3345
3405
|
writeSpinner.start("Creating configuration...");
|
|
3406
|
+
const whiteroseExistedBefore = existsSync(whiterosePath);
|
|
3407
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
3408
|
+
const originalGitignore = existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : null;
|
|
3346
3409
|
try {
|
|
3347
3410
|
mkdirSync(join(whiterosePath, "cache"), { recursive: true });
|
|
3348
3411
|
mkdirSync(join(whiterosePath, "reports"), { recursive: true });
|
|
@@ -3378,29 +3441,37 @@ async function initCommand(options) {
|
|
|
3378
3441
|
markdownPath: "BUGS.md"
|
|
3379
3442
|
}
|
|
3380
3443
|
};
|
|
3381
|
-
writeFileSync(join(whiterosePath, "config.yml"), YAML.stringify(config), "utf-8");
|
|
3382
|
-
writeFileSync(
|
|
3383
|
-
join(whiterosePath, "cache", "understanding.json"),
|
|
3384
|
-
JSON.stringify(understanding, null, 2),
|
|
3385
|
-
"utf-8"
|
|
3386
|
-
);
|
|
3387
3444
|
const intentDoc = generateIntentDocument(understanding);
|
|
3445
|
+
const configContent = YAML.stringify(config);
|
|
3446
|
+
const understandingContent = JSON.stringify(understanding, null, 2);
|
|
3447
|
+
const hashesContent = JSON.stringify({ version: "1", fileHashes: [], lastFullScan: null }, null, 2);
|
|
3448
|
+
writeFileSync(join(whiterosePath, "config.yml"), configContent, "utf-8");
|
|
3449
|
+
writeFileSync(join(whiterosePath, "cache", "understanding.json"), understandingContent, "utf-8");
|
|
3388
3450
|
writeFileSync(join(whiterosePath, "intent.md"), intentDoc, "utf-8");
|
|
3389
|
-
writeFileSync(
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
"utf-8"
|
|
3393
|
-
);
|
|
3394
|
-
const gitignorePath = join(cwd, ".gitignore");
|
|
3395
|
-
if (existsSync(gitignorePath)) {
|
|
3396
|
-
const gitignore = await import('fs').then((fs) => fs.readFileSync(gitignorePath, "utf-8"));
|
|
3397
|
-
if (!gitignore.includes(".whiterose/cache")) {
|
|
3398
|
-
writeFileSync(gitignorePath, gitignore + "\n# whiterose cache\n.whiterose/cache/\n", "utf-8");
|
|
3399
|
-
}
|
|
3451
|
+
writeFileSync(join(whiterosePath, "cache", "file-hashes.json"), hashesContent, "utf-8");
|
|
3452
|
+
if (originalGitignore !== null && !originalGitignore.includes(".whiterose/cache")) {
|
|
3453
|
+
writeFileSync(gitignorePath, originalGitignore + "\n# whiterose cache\n.whiterose/cache/\n", "utf-8");
|
|
3400
3454
|
}
|
|
3401
3455
|
writeSpinner.stop("Configuration created");
|
|
3402
3456
|
} catch (error) {
|
|
3403
3457
|
writeSpinner.stop("Failed to create configuration");
|
|
3458
|
+
if (!whiteroseExistedBefore && existsSync(whiterosePath)) {
|
|
3459
|
+
try {
|
|
3460
|
+
rmSync(whiterosePath, { recursive: true, force: true });
|
|
3461
|
+
p3.log.info("Rolled back: removed .whiterose directory");
|
|
3462
|
+
} catch {
|
|
3463
|
+
}
|
|
3464
|
+
}
|
|
3465
|
+
if (originalGitignore !== null && existsSync(gitignorePath)) {
|
|
3466
|
+
try {
|
|
3467
|
+
const currentGitignore = readFileSync(gitignorePath, "utf-8");
|
|
3468
|
+
if (currentGitignore !== originalGitignore) {
|
|
3469
|
+
writeFileSync(gitignorePath, originalGitignore, "utf-8");
|
|
3470
|
+
p3.log.info("Rolled back: restored .gitignore");
|
|
3471
|
+
}
|
|
3472
|
+
} catch {
|
|
3473
|
+
}
|
|
3474
|
+
}
|
|
3404
3475
|
p3.log.error(String(error));
|
|
3405
3476
|
process.exit(1);
|
|
3406
3477
|
}
|
|
@@ -5163,7 +5234,19 @@ async function scanCommand(paths, options) {
|
|
|
5163
5234
|
try {
|
|
5164
5235
|
config = await loadConfig(cwd);
|
|
5165
5236
|
understanding = await loadUnderstanding(cwd);
|
|
5166
|
-
} catch {
|
|
5237
|
+
} catch (err) {
|
|
5238
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
5239
|
+
if (!isQuiet) {
|
|
5240
|
+
p3.log.warn(`Failed to load config: ${errorMessage}`);
|
|
5241
|
+
p3.log.info('Continuing with default settings. Run "whiterose init" to fix.');
|
|
5242
|
+
} else if (options.ci) {
|
|
5243
|
+
console.error(JSON.stringify({
|
|
5244
|
+
error: "Config parse error",
|
|
5245
|
+
message: errorMessage,
|
|
5246
|
+
hint: 'Fix config.yml or run "whiterose init" to regenerate'
|
|
5247
|
+
}));
|
|
5248
|
+
process.exit(1);
|
|
5249
|
+
}
|
|
5167
5250
|
}
|
|
5168
5251
|
}
|
|
5169
5252
|
if (!understanding) {
|
|
@@ -5282,6 +5365,16 @@ async function scanCommand(paths, options) {
|
|
|
5282
5365
|
}
|
|
5283
5366
|
const totalTime = Math.floor((Date.now() - analysisStartTime) / 1e3);
|
|
5284
5367
|
llmSpinner.stop(`Found ${bugs.length} potential bugs (${totalTime}s)`);
|
|
5368
|
+
if (scanner.hasPassErrors()) {
|
|
5369
|
+
const errors = scanner.getPassErrors();
|
|
5370
|
+
p3.log.warn(`${errors.length} analysis pass(es) failed:`);
|
|
5371
|
+
for (const err of errors.slice(0, 5)) {
|
|
5372
|
+
console.log(chalk3.yellow(` - ${err.passName}: ${err.error}`));
|
|
5373
|
+
}
|
|
5374
|
+
if (errors.length > 5) {
|
|
5375
|
+
console.log(chalk3.yellow(` ... and ${errors.length - 5} more`));
|
|
5376
|
+
}
|
|
5377
|
+
}
|
|
5285
5378
|
} catch (error) {
|
|
5286
5379
|
llmSpinner.stop("Analysis failed");
|
|
5287
5380
|
p3.log.error(String(error));
|
|
@@ -5304,6 +5397,16 @@ async function scanCommand(paths, options) {
|
|
|
5304
5397
|
config
|
|
5305
5398
|
});
|
|
5306
5399
|
}
|
|
5400
|
+
if (options.ci && scanner.hasPassErrors()) {
|
|
5401
|
+
const errors = scanner.getPassErrors();
|
|
5402
|
+
if (bugs.length === 0) {
|
|
5403
|
+
console.error(JSON.stringify({
|
|
5404
|
+
error: "Analysis failed",
|
|
5405
|
+
passErrors: errors
|
|
5406
|
+
}));
|
|
5407
|
+
process.exit(1);
|
|
5408
|
+
}
|
|
5409
|
+
}
|
|
5307
5410
|
}
|
|
5308
5411
|
if (!isQuickScan) {
|
|
5309
5412
|
if (!isQuiet) {
|
|
@@ -5324,7 +5427,14 @@ async function scanCommand(paths, options) {
|
|
|
5324
5427
|
try {
|
|
5325
5428
|
const crossFileBugs = await analyzeCrossFile(cwd);
|
|
5326
5429
|
bugs.push(...crossFileBugs);
|
|
5327
|
-
} catch {
|
|
5430
|
+
} catch (err) {
|
|
5431
|
+
if (options.ci) {
|
|
5432
|
+
console.error(JSON.stringify({
|
|
5433
|
+
error: "Cross-file analysis failed",
|
|
5434
|
+
message: err instanceof Error ? err.message : String(err)
|
|
5435
|
+
}));
|
|
5436
|
+
process.exit(1);
|
|
5437
|
+
}
|
|
5328
5438
|
}
|
|
5329
5439
|
}
|
|
5330
5440
|
}
|
|
@@ -5347,7 +5457,14 @@ async function scanCommand(paths, options) {
|
|
|
5347
5457
|
try {
|
|
5348
5458
|
const contractBugs = await analyzeContracts(cwd);
|
|
5349
5459
|
bugs.push(...contractBugs);
|
|
5350
|
-
} catch {
|
|
5460
|
+
} catch (err) {
|
|
5461
|
+
if (options.ci) {
|
|
5462
|
+
console.error(JSON.stringify({
|
|
5463
|
+
error: "Contract analysis failed",
|
|
5464
|
+
message: err instanceof Error ? err.message : String(err)
|
|
5465
|
+
}));
|
|
5466
|
+
process.exit(1);
|
|
5467
|
+
}
|
|
5351
5468
|
}
|
|
5352
5469
|
}
|
|
5353
5470
|
}
|
|
@@ -5357,7 +5474,14 @@ async function scanCommand(paths, options) {
|
|
|
5357
5474
|
if (intentBugs.length > 0) {
|
|
5358
5475
|
bugs.push(...intentBugs);
|
|
5359
5476
|
}
|
|
5360
|
-
} catch {
|
|
5477
|
+
} catch (err) {
|
|
5478
|
+
if (options.ci) {
|
|
5479
|
+
console.error(JSON.stringify({
|
|
5480
|
+
error: "Intent validation failed",
|
|
5481
|
+
message: err instanceof Error ? err.message : String(err)
|
|
5482
|
+
}));
|
|
5483
|
+
process.exit(1);
|
|
5484
|
+
}
|
|
5361
5485
|
}
|
|
5362
5486
|
}
|
|
5363
5487
|
const confidenceOrder = { high: 3, medium: 2, low: 1 };
|