@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 CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync, existsSync, mkdirSync, writeFileSync, readdirSync, statSync, rmSync, mkdtempSync, realpathSync } from 'fs';
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
- this.report(` Error: ${error.message}`);
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 jsonObjectMatch = output.match(/\{[\s\S]*\}/);
2315
- if (jsonObjectMatch) {
2316
- try {
2317
- parsed = JSON.parse(jsonObjectMatch[0]);
2318
- } catch {
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
- join(whiterosePath, "cache", "file-hashes.json"),
3391
- JSON.stringify({ version: "1", fileHashes: [], lastFullScan: null }, null, 2),
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 };