@securitychecks/cli 0.1.1-rc.1 → 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/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import pc from 'picocolors';
3
+ import pc2 from 'picocolors';
4
4
  import { mkdir, writeFile, readFile, chmod, unlink } from 'fs/promises';
5
5
  import { join, resolve, dirname, extname, relative } from 'path';
6
6
  import { resolveTargetPath, getInvariantById, ALL_INVARIANTS, collect, loadConfig, DEFAULT_PATTERN_CONFIG, ARTIFACT_SCHEMA_VERSION } from '@securitychecks/collector';
@@ -3311,7 +3311,7 @@ function generateFindingId(finding) {
3311
3311
  }
3312
3312
 
3313
3313
  // src/baseline/storage.ts
3314
- var CLI_VERSION3 = "0.1.1-rc.1";
3314
+ var CLI_VERSION3 = "0.2.0";
3315
3315
  var SCHECK_DIR = ".scheck";
3316
3316
  var BASELINE_FILE = "baseline.json";
3317
3317
  var WAIVER_FILE = "waivers.json";
@@ -4045,7 +4045,7 @@ function toObservation(correlation, framework) {
4045
4045
  signals: correlation.compoundingEffect.signals
4046
4046
  },
4047
4047
  meta: {
4048
- clientVersion: "0.1.1-rc.1",
4048
+ clientVersion: "0.2.0",
4049
4049
  requestId: randomUUID(),
4050
4050
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4051
4051
  }
@@ -4063,7 +4063,7 @@ async function reportCorrelations(result, config, framework) {
4063
4063
  correlations: observations,
4064
4064
  summary: result.stats,
4065
4065
  meta: {
4066
- clientVersion: "0.1.1-rc.1",
4066
+ clientVersion: "0.2.0",
4067
4067
  framework
4068
4068
  }
4069
4069
  };
@@ -4075,7 +4075,7 @@ async function reportCorrelations(result, config, framework) {
4075
4075
  headers: {
4076
4076
  "Content-Type": "application/json",
4077
4077
  ...config.apiKey && { Authorization: `Bearer ${config.apiKey}` },
4078
- "X-Client-Version": "0.1.1-rc.1"
4078
+ "X-Client-Version": "0.2.0"
4079
4079
  },
4080
4080
  body: JSON.stringify(payload),
4081
4081
  signal: controller.signal
@@ -4131,7 +4131,7 @@ function buildTelemetry(result, options) {
4131
4131
  } : void 0,
4132
4132
  meta: {
4133
4133
  duration: result.duration,
4134
- clientVersion: "0.1.1-rc.1",
4134
+ clientVersion: "0.2.0",
4135
4135
  mode: options.mode ?? (ciProvider ? "ci" : "manual"),
4136
4136
  ciProvider
4137
4137
  },
@@ -4920,6 +4920,35 @@ function indent(text, spaces) {
4920
4920
  function escapeString(text) {
4921
4921
  return text.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
4922
4922
  }
4923
+ function getScoreGrade(score) {
4924
+ if (score >= 90) return "A";
4925
+ if (score >= 70) return "B";
4926
+ if (score >= 50) return "C";
4927
+ return "F";
4928
+ }
4929
+ function computeReadinessScore(summary) {
4930
+ const { total, passed, failed, byPriority } = summary;
4931
+ const hasP0 = byPriority.P0 > 0;
4932
+ if (total === 0) {
4933
+ return { score: 100, grade: "A", total: 0, passed: 0, failed: 0, hasP0: false };
4934
+ }
4935
+ let score = Math.round(100 * passed / total);
4936
+ if (hasP0 && score > 49) {
4937
+ score = 49;
4938
+ }
4939
+ return {
4940
+ score,
4941
+ grade: getScoreGrade(score),
4942
+ total,
4943
+ passed,
4944
+ failed,
4945
+ hasP0
4946
+ };
4947
+ }
4948
+ function formatScoreForCli(rs) {
4949
+ const gradeColor = rs.grade === "A" ? pc2.green : rs.grade === "B" ? pc2.yellow : rs.grade === "C" ? pc2.red : pc2.red;
4950
+ return gradeColor(`Score: ${rs.score}/100 (${rs.grade})`);
4951
+ }
4923
4952
 
4924
4953
  // src/commands/run.ts
4925
4954
  var DEFAULT_ARTIFACT_PATH = ".securitychecks/artifacts.json";
@@ -5084,14 +5113,14 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
5084
5113
  );
5085
5114
  }
5086
5115
  if (!options.quiet) {
5087
- console.log(pc.dim(`Mode: cloud evaluation (IP protected)`));
5116
+ console.log(pc2.dim(`Mode: cloud evaluation (IP protected)`));
5088
5117
  if (isLocalEndpoint(cloudBaseUrl)) {
5089
- console.log(pc.yellow("\u26A0 Using local cloud endpoint (dev/test)"));
5118
+ console.log(pc2.yellow("\u26A0 Using local cloud endpoint (dev/test)"));
5090
5119
  }
5091
5120
  }
5092
5121
  const auditArtifact = toArtifact(artifact);
5093
5122
  if (!options.quiet) {
5094
- console.log(pc.dim(`Evaluating via cloud API...`));
5123
+ console.log(pc2.dim(`Evaluating via cloud API...`));
5095
5124
  }
5096
5125
  const cloudEvalResult = await evaluateCloud(artifact, {
5097
5126
  apiKey: cloudApiKey,
@@ -5104,13 +5133,13 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
5104
5133
  let findings = cloudEvalResult.findings;
5105
5134
  if (!options.quiet) {
5106
5135
  console.log(
5107
- pc.dim(
5136
+ pc2.dim(
5108
5137
  `Cloud: ${cloudEvalResult.stats.invariantsRun} invariants, ${cloudEvalResult.stats.findingsCount} findings in ${cloudEvalResult.stats.executionMs}ms`
5109
5138
  )
5110
5139
  );
5111
5140
  if (cloudEvalResult.usage.scansRemaining !== Infinity) {
5112
5141
  console.log(
5113
- pc.dim(`Usage: ${cloudEvalResult.usage.scansUsed} scans used, ${cloudEvalResult.usage.scansRemaining} remaining`)
5142
+ pc2.dim(`Usage: ${cloudEvalResult.usage.scansUsed} scans used, ${cloudEvalResult.usage.scansRemaining} remaining`)
5114
5143
  );
5115
5144
  }
5116
5145
  }
@@ -5150,7 +5179,7 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
5150
5179
  };
5151
5180
  const frameworks = resolveFrameworks(artifact, artifactForPatterns);
5152
5181
  if (!options.quiet && frameworks.length > 0) {
5153
- console.log(pc.dim(`Frameworks detected: ${frameworks.join(", ")}`));
5182
+ console.log(pc2.dim(`Frameworks detected: ${frameworks.join(", ")}`));
5154
5183
  }
5155
5184
  try {
5156
5185
  const localScanStart = Date.now();
@@ -5158,13 +5187,13 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
5158
5187
  let patternSource = "bundled";
5159
5188
  if (options.patternsFile) {
5160
5189
  if (!options.quiet) {
5161
- console.log(pc.dim(`Loading patterns from ${options.patternsFile}...`));
5190
+ console.log(pc2.dim(`Loading patterns from ${options.patternsFile}...`));
5162
5191
  }
5163
5192
  patterns = await loadPatternsFromFile(options.patternsFile);
5164
5193
  patternSource = "file";
5165
5194
  } else if (shouldUseDevPatterns()) {
5166
5195
  if (!options.quiet) {
5167
- console.log(pc.yellow("\u26A0 Using dev test patterns (SECURITYCHECKS_DEV_PATTERNS=1)"));
5196
+ console.log(pc2.yellow("\u26A0 Using dev test patterns (SECURITYCHECKS_DEV_PATTERNS=1)"));
5168
5197
  }
5169
5198
  patterns = getTestPatternsForFrameworks(frameworks);
5170
5199
  patternSource = "dev";
@@ -5175,7 +5204,7 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
5175
5204
  if (patternConfig && !patternConfig.offlineMode) {
5176
5205
  try {
5177
5206
  if (!options.quiet) {
5178
- console.log(pc.dim(`Fetching Pro Patterns from ${patternConfig.endpoint}...`));
5207
+ console.log(pc2.dim(`Fetching Pro Patterns from ${patternConfig.endpoint}...`));
5179
5208
  }
5180
5209
  const apiPatterns = await fetchPatterns(
5181
5210
  artifactForPatterns,
@@ -5206,7 +5235,7 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
5206
5235
  const sourceLabel = patternSource === "bundled" ? "" : ` (${patternSource})`;
5207
5236
  const localScanMs = Date.now() - localScanStart;
5208
5237
  console.log(
5209
- pc.dim(`Local scan: ${stats.patternsLoaded} patterns${sourceLabel}, ${stats.matchesFound} match(es) found in ${localScanMs}ms`)
5238
+ pc2.dim(`Local scan: ${stats.patternsLoaded} patterns${sourceLabel}, ${stats.matchesFound} match(es) found in ${localScanMs}ms`)
5210
5239
  );
5211
5240
  }
5212
5241
  const existingKeys = new Set(
@@ -5220,11 +5249,11 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
5220
5249
  }
5221
5250
  }
5222
5251
  } else if (!options.quiet) {
5223
- console.log(pc.dim(`Local scan: No applicable patterns for detected frameworks`));
5252
+ console.log(pc2.dim(`Local scan: No applicable patterns for detected frameworks`));
5224
5253
  }
5225
5254
  } catch {
5226
5255
  if (!options.quiet) {
5227
- console.log(pc.dim(`Local scan: Pattern loading failed, using cloud findings only`));
5256
+ console.log(pc2.dim(`Local scan: Pattern loading failed, using cloud findings only`));
5228
5257
  }
5229
5258
  } finally {
5230
5259
  clearPatternCaches();
@@ -5249,13 +5278,15 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
5249
5278
  P1: findings.filter((f) => f.severity === "P1").length,
5250
5279
  P2: findings.filter((f) => f.severity === "P2").length
5251
5280
  };
5281
+ const summaryData = { total: updatedResults.length, passed, failed, waived, byPriority };
5282
+ const readiness = computeReadinessScore(summaryData);
5252
5283
  return {
5253
5284
  result: {
5254
5285
  version: "1.0",
5255
5286
  targetPath,
5256
5287
  runAt: (/* @__PURE__ */ new Date()).toISOString(),
5257
5288
  duration: Date.now() - startTime,
5258
- summary: { total: updatedResults.length, passed, failed, waived, byPriority },
5289
+ summary: { ...summaryData, score: readiness.score, grade: readiness.grade },
5259
5290
  results: updatedResults,
5260
5291
  artifact: auditArtifact
5261
5292
  },
@@ -5269,19 +5300,19 @@ For air-gapped environments, contact sales for an enterprise on-premise license.
5269
5300
  }
5270
5301
  async function runSingleScan(options) {
5271
5302
  if (!options.quiet) {
5272
- console.log(pc.bold("\n\u{1F50D} Scanning for invariants AI misses...\n"));
5303
+ console.log(pc2.bold("\n\u{1F50D} Scanning for invariants AI misses...\n"));
5273
5304
  }
5274
5305
  try {
5275
5306
  let artifact;
5276
5307
  if (options.artifact) {
5277
5308
  if (!options.quiet) {
5278
- console.log(pc.dim(`Loading artifact from ${options.artifact}...`));
5309
+ console.log(pc2.dim(`Loading artifact from ${options.artifact}...`));
5279
5310
  }
5280
5311
  artifact = await loadArtifact(options.artifact);
5281
5312
  } else {
5282
5313
  const targetPath2 = resolveTargetPath(options.path);
5283
5314
  if (!options.quiet) {
5284
- console.log(pc.dim(`Collecting artifacts from ${targetPath2}...`));
5315
+ console.log(pc2.dim(`Collecting artifacts from ${targetPath2}...`));
5285
5316
  }
5286
5317
  artifact = await collect({
5287
5318
  targetPath: targetPath2,
@@ -5292,16 +5323,16 @@ async function runSingleScan(options) {
5292
5323
  await mkdir(dirname(artifactPath), { recursive: true });
5293
5324
  await writeFile(artifactPath, JSON.stringify(artifact, null, 2), "utf-8");
5294
5325
  if (!options.quiet) {
5295
- console.log(pc.dim(`Artifact cached at ${DEFAULT_ARTIFACT_PATH}`));
5326
+ console.log(pc2.dim(`Artifact cached at ${DEFAULT_ARTIFACT_PATH}`));
5296
5327
  }
5297
5328
  } catch {
5298
5329
  }
5299
5330
  }
5300
5331
  if (!options.quiet) {
5301
5332
  const schemaVer = artifact.schemaVersion ?? "1.0.0";
5302
- console.log(pc.dim(`Artifact: v${artifact.version}, schema=${schemaVer}, profile=${artifact.profile}`));
5303
- console.log(pc.dim(`Codebase: ${artifact.codebase.root}`));
5304
- console.log(pc.dim(`Files: ${artifact.codebase.filesScanned}, Services: ${artifact.services.length}`));
5333
+ console.log(pc2.dim(`Artifact: v${artifact.version}, schema=${schemaVer}, profile=${artifact.profile}`));
5334
+ console.log(pc2.dim(`Codebase: ${artifact.codebase.root}`));
5335
+ console.log(pc2.dim(`Files: ${artifact.codebase.filesScanned}, Services: ${artifact.services.length}`));
5305
5336
  console.log("");
5306
5337
  }
5307
5338
  const {
@@ -5322,7 +5353,7 @@ async function runSingleScan(options) {
5322
5353
  const categorization = categorizeFindings(allFindings, baseline, waivers);
5323
5354
  const correlationResult = correlateFindings(result.results, result.artifact);
5324
5355
  if (!options.quiet && correlationResult.correlations.length > 0) {
5325
- console.log(pc.dim(`Correlation: ${correlationResult.stats.correlationGroups} compounding risk group(s) detected`));
5356
+ console.log(pc2.dim(`Correlation: ${correlationResult.stats.correlationGroups} compounding risk group(s) detected`));
5326
5357
  }
5327
5358
  const artifactForFrameworks = {
5328
5359
  version: "1.0",
@@ -5361,18 +5392,18 @@ async function runSingleScan(options) {
5361
5392
  if (calibrationResult.data) {
5362
5393
  aggregateCalibration = calibrationResult.data;
5363
5394
  if (!options.quiet && calibrationResult.data.meta.totalScansAnalyzed > 0) {
5364
- console.log(pc.dim(`Calibration: Loaded baseline from ${calibrationResult.data.meta.totalScansAnalyzed} scans${calibrationResult.fromCache ? " (cached)" : ""}`));
5395
+ console.log(pc2.dim(`Calibration: Loaded baseline from ${calibrationResult.data.meta.totalScansAnalyzed} scans${calibrationResult.fromCache ? " (cached)" : ""}`));
5365
5396
  }
5366
5397
  }
5367
5398
  } catch {
5368
5399
  }
5369
5400
  }
5370
5401
  if (options.sarif) {
5371
- const cliVersion = "0.1.1-rc.1";
5402
+ const cliVersion = "0.2.0";
5372
5403
  const sarifOutput = toSarif(result, cliVersion);
5373
5404
  await writeFile(options.sarif, JSON.stringify(sarifOutput, null, 2), "utf-8");
5374
5405
  if (!options.quiet) {
5375
- console.log(pc.green(`\u2713 SARIF report written to ${options.sarif}`));
5406
+ console.log(pc2.green(`\u2713 SARIF report written to ${options.sarif}`));
5376
5407
  }
5377
5408
  }
5378
5409
  if (options.json) {
@@ -5415,12 +5446,12 @@ async function runSingleScan(options) {
5415
5446
  const isLow = scansRemaining / total <= 0.2;
5416
5447
  const isNthScan = scansUsed % 5 === 0;
5417
5448
  if (isLow || isNthScan) {
5418
- const color = isLow ? pc.red : pc.yellow;
5449
+ const color = isLow ? pc2.red : pc2.yellow;
5419
5450
  const label = isLow ? "Low quota" : "Quota";
5420
5451
  console.log(color(`\u26A0 ${label}: ${scansUsed} of ${total} scans used this month (${scansRemaining} remaining)`));
5421
- console.log(pc.dim(" Upgrade at https://securitychecks.ai/pricing"));
5452
+ console.log(pc2.dim(" Upgrade at https://securitychecks.ai/pricing"));
5422
5453
  if (!isLow) {
5423
- console.log(pc.dim(" Suppress with --no-usage-banner or SECURITYCHECKS_NO_USAGE_BANNER=1"));
5454
+ console.log(pc2.dim(" Suppress with --no-usage-banner or SECURITYCHECKS_NO_USAGE_BANNER=1"));
5424
5455
  }
5425
5456
  }
5426
5457
  }
@@ -5446,13 +5477,13 @@ async function runSingleScan(options) {
5446
5477
  if (options.ci) {
5447
5478
  const exitCode = getCIExitCode(categorization);
5448
5479
  if (exitCode !== 0) {
5449
- console.log(pc.red(`
5480
+ console.log(pc2.red(`
5450
5481
  ${getCISummary(categorization)}`));
5451
- console.log(pc.dim("Use `scheck baseline --update` to baseline known issues."));
5452
- console.log(pc.dim("Use `scheck waive <findingId>` to temporarily waive issues.\n"));
5482
+ console.log(pc2.dim("Use `scheck baseline --update` to baseline known issues."));
5483
+ console.log(pc2.dim("Use `scheck waive <findingId>` to temporarily waive issues.\n"));
5453
5484
  process.exit(exitCode);
5454
5485
  } else {
5455
- console.log(pc.green(`
5486
+ console.log(pc2.green(`
5456
5487
  ${getCISummary(categorization)}
5457
5488
  `));
5458
5489
  }
@@ -5462,22 +5493,22 @@ ${getCISummary(categorization)}
5462
5493
  if (options.json) {
5463
5494
  console.error(JSON.stringify(cliError.toJSON(), null, 2));
5464
5495
  } else {
5465
- console.error(pc.red(`
5496
+ console.error(pc2.red(`
5466
5497
  ${cliError.toUserString()}
5467
5498
  `));
5468
5499
  const remediation = cliError.getRemediation();
5469
5500
  if (remediation) {
5470
- console.error(pc.yellow("How to fix:"));
5501
+ console.error(pc2.yellow("How to fix:"));
5471
5502
  for (const line of remediation.split("\n")) {
5472
- console.error(pc.dim(` ${line}`));
5503
+ console.error(pc2.dim(` ${line}`));
5473
5504
  }
5474
5505
  console.error("");
5475
5506
  }
5476
5507
  if (cliError.cause) {
5477
- console.error(pc.dim(`Caused by: ${cliError.cause.message}`));
5508
+ console.error(pc2.dim(`Caused by: ${cliError.cause.message}`));
5478
5509
  console.error("");
5479
5510
  }
5480
- console.error(pc.dim("Need help? https://securitychecks.ai/docs/troubleshooting"));
5511
+ console.error(pc2.dim("Need help? https://securitychecks.ai/docs/troubleshooting"));
5481
5512
  console.error("");
5482
5513
  }
5483
5514
  process.exit(1);
@@ -5489,35 +5520,35 @@ function isCLIEnabled() {
5489
5520
  }
5490
5521
  async function runCommand(options) {
5491
5522
  if (!isCLIEnabled()) {
5492
- console.log(pc.bold("\nSecurityChecks CLI is currently in private beta.\n"));
5523
+ console.log(pc2.bold("\nSecurityChecks CLI is currently in private beta.\n"));
5493
5524
  console.log("For public access, use our GitHub App instead:");
5494
- console.log(pc.cyan(" https://securitychecks.ai\n"));
5525
+ console.log(pc2.cyan(" https://securitychecks.ai\n"));
5495
5526
  console.log("Benefits of the GitHub App:");
5496
5527
  console.log(" - No installation required");
5497
5528
  console.log(" - Automatic PR checks");
5498
5529
  console.log(" - Evidence directly in your PRs");
5499
5530
  console.log(" - Free tier available\n");
5500
- console.log(pc.dim("Enterprise or beta access? Contact sales@securitychecks.ai"));
5501
- console.log(pc.dim("Set SECURITYCHECKS_CLI_ENABLED=1 if you have approved access.\n"));
5531
+ console.log(pc2.dim("Enterprise or beta access? Contact sales@securitychecks.ai"));
5532
+ console.log(pc2.dim("Set SECURITYCHECKS_CLI_ENABLED=1 if you have approved access.\n"));
5502
5533
  process.exit(0);
5503
5534
  }
5504
5535
  if (options.watch) {
5505
5536
  if (options.ci) {
5506
- console.error(pc.red("Error: --watch cannot be used with --ci mode"));
5537
+ console.error(pc2.red("Error: --watch cannot be used with --ci mode"));
5507
5538
  process.exit(1);
5508
5539
  }
5509
5540
  if (options.artifact) {
5510
- console.error(pc.red("Error: --watch cannot be used with --artifact"));
5541
+ console.error(pc2.red("Error: --watch cannot be used with --artifact"));
5511
5542
  process.exit(1);
5512
5543
  }
5513
5544
  if (options.json) {
5514
- console.error(pc.red("Error: --watch cannot be used with --json output"));
5545
+ console.error(pc2.red("Error: --watch cannot be used with --json output"));
5515
5546
  process.exit(1);
5516
5547
  }
5517
5548
  const targetPath = resolveTargetPath(options.path);
5518
- console.log(pc.bold("\n\u{1F440} Watch mode enabled\n"));
5519
- console.log(pc.dim(`Watching: ${targetPath}`));
5520
- console.log(pc.dim("Press Ctrl+C to stop\n"));
5549
+ console.log(pc2.bold("\n\u{1F440} Watch mode enabled\n"));
5550
+ console.log(pc2.dim(`Watching: ${targetPath}`));
5551
+ console.log(pc2.dim("Press Ctrl+C to stop\n"));
5521
5552
  await runSingleScan(options);
5522
5553
  let runCount = 1;
5523
5554
  const watcher = new FileWatcher({
@@ -5526,28 +5557,28 @@ async function runCommand(options) {
5526
5557
  onChanged: async () => {
5527
5558
  runCount++;
5528
5559
  console.clear();
5529
- console.log(pc.bold(`
5560
+ console.log(pc2.bold(`
5530
5561
  \u{1F440} Watch mode (run #${runCount})
5531
5562
  `));
5532
- console.log(pc.dim(`Watching: ${targetPath}`));
5533
- console.log(pc.dim("Press Ctrl+C to stop\n"));
5563
+ console.log(pc2.dim(`Watching: ${targetPath}`));
5564
+ console.log(pc2.dim("Press Ctrl+C to stop\n"));
5534
5565
  try {
5535
5566
  await runSingleScan(options);
5536
5567
  } catch {
5537
- console.log(pc.dim("\nWaiting for changes...\n"));
5568
+ console.log(pc2.dim("\nWaiting for changes...\n"));
5538
5569
  }
5539
5570
  }
5540
5571
  });
5541
5572
  watcher.on("change", (filename) => {
5542
5573
  if (!options.quiet) {
5543
- console.log(pc.dim(`
5574
+ console.log(pc2.dim(`
5544
5575
  File changed: ${filename}`));
5545
- console.log(pc.dim("Re-running scan...\n"));
5576
+ console.log(pc2.dim("Re-running scan...\n"));
5546
5577
  }
5547
5578
  });
5548
5579
  watcher.start();
5549
5580
  process.on("SIGINT", () => {
5550
- console.log(pc.dim("\n\nStopping watch mode..."));
5581
+ console.log(pc2.dim("\n\nStopping watch mode..."));
5551
5582
  watcher.stop();
5552
5583
  process.exit(0);
5553
5584
  });
@@ -5591,19 +5622,19 @@ function displayResults(result, categorization, correlation, options, calibratio
5591
5622
  }
5592
5623
  for (const [invariantId, findings] of byInvariant) {
5593
5624
  const severity = findings[0]?.severity;
5594
- const severityColor = severity === "P0" ? pc.red : severity === "P1" ? pc.yellow : pc.blue;
5625
+ const severityColor = severity === "P0" ? pc2.red : severity === "P1" ? pc2.yellow : pc2.blue;
5595
5626
  console.log(severityColor(`[${severity}] ${invariantId}`));
5596
5627
  console.log("");
5597
5628
  const staffQuestion = getStaffQuestion(invariantId);
5598
5629
  if (staffQuestion) {
5599
5630
  console.log(` A staff engineer would ask:`);
5600
- console.log(` ${pc.cyan(`"${staffQuestion}"`)}`);
5631
+ console.log(` ${pc2.cyan(`"${staffQuestion}"`)}`);
5601
5632
  console.log("");
5602
5633
  }
5603
5634
  for (const finding of findings) {
5604
5635
  for (const evidence of finding.evidence) {
5605
- console.log(` \u2192 ${pc.white(`${evidence.file}:${evidence.line}`)}`);
5606
- console.log(` ${pc.dim(finding.message)}`);
5636
+ console.log(` \u2192 ${pc2.white(`${evidence.file}:${evidence.line}`)}`);
5637
+ console.log(` ${pc2.dim(finding.message)}`);
5607
5638
  }
5608
5639
  }
5609
5640
  console.log("");
@@ -5611,46 +5642,48 @@ function displayResults(result, categorization, correlation, options, calibratio
5611
5642
  }
5612
5643
  const duration = (result.duration / 1e3).toFixed(1);
5613
5644
  const filesScanned = result.artifact?.services?.length ?? 0;
5614
- console.log(pc.dim(`Scanned ${filesScanned} files in ${duration}s`));
5645
+ console.log(pc2.dim(`Scanned ${filesScanned} files in ${duration}s`));
5646
+ const readiness = computeReadinessScore(result.summary);
5647
+ console.log(formatScoreForCli(readiness));
5615
5648
  console.log("");
5616
5649
  if (stoppedEarly) {
5617
- console.log(pc.red(`\u2716 Found critical issue. Stopping early.`));
5650
+ console.log(pc2.red(`\u2716 Found critical issue. Stopping early.`));
5618
5651
  console.log("");
5619
- console.log(pc.dim(`Run \`scheck run --all\` to see all findings.`));
5620
- console.log(pc.dim(`Run \`scheck explain ${displayFindings[0]?.invariantId}\` for details.`));
5652
+ console.log(pc2.dim(`Run \`scheck run --all\` to see all findings.`));
5653
+ console.log(pc2.dim(`Run \`scheck explain ${displayFindings[0]?.invariantId}\` for details.`));
5621
5654
  } else if (totalVisible === 0) {
5622
- console.log(pc.green(`\u2713 No issues found. Ship it.`));
5655
+ console.log(pc2.green(`\u2713 No issues found. Ship it.`));
5623
5656
  } else {
5624
5657
  const issueWord = totalVisible === 1 ? "issue" : "issues";
5625
5658
  const criticalNote = criticalCount > 0 ? ` (${criticalCount} critical)` : "";
5626
- console.log(pc.red(`\u2716 ${totalVisible} ${issueWord} require attention${criticalNote}`));
5659
+ console.log(pc2.red(`\u2716 ${totalVisible} ${issueWord} require attention${criticalNote}`));
5627
5660
  console.log("");
5628
5661
  const firstInvariant = displayFindings[0]?.invariantId;
5629
5662
  if (firstInvariant) {
5630
- console.log(pc.dim(`Run \`scheck explain ${firstInvariant}\` for details.`));
5663
+ console.log(pc2.dim(`Run \`scheck explain ${firstInvariant}\` for details.`));
5631
5664
  }
5632
5665
  }
5633
5666
  if (!showP2 && p2Count > 0 && !stoppedEarly) {
5634
- console.log(pc.dim(`(${p2Count} P2 finding(s) hidden. Run with --all to see them.)`));
5667
+ console.log(pc2.dim(`(${p2Count} P2 finding(s) hidden. Run with --all to see them.)`));
5635
5668
  }
5636
5669
  if (!options.quiet) {
5637
5670
  if (counts.baselined > 0 || counts.waived > 0) {
5638
5671
  console.log("");
5639
5672
  if (counts.baselined > 0) {
5640
- console.log(pc.dim(`Baselined: ${counts.baselined} finding(s) in baseline`));
5673
+ console.log(pc2.dim(`Baselined: ${counts.baselined} finding(s) in baseline`));
5641
5674
  }
5642
5675
  if (counts.waived > 0) {
5643
- console.log(pc.dim(`Waived: ${counts.waived} finding(s) temporarily waived`));
5676
+ console.log(pc2.dim(`Waived: ${counts.waived} finding(s) temporarily waived`));
5644
5677
  }
5645
5678
  }
5646
5679
  }
5647
5680
  if (correlation.correlations.length > 0 && !options.quiet && options.all) {
5648
5681
  console.log("");
5649
- console.log(pc.bold(pc.magenta("Compounding Risks Detected:")));
5682
+ console.log(pc2.bold(pc2.magenta("Compounding Risks Detected:")));
5650
5683
  for (const corr of correlation.correlations) {
5651
5684
  console.log(formatCorrelatedFinding(corr));
5652
5685
  }
5653
- console.log(pc.dim(formatCorrelationStats(correlation)));
5686
+ console.log(pc2.dim(formatCorrelationStats(correlation)));
5654
5687
  }
5655
5688
  if (calibration?.aggregateCalibration && calibration.frameworks.length > 0 && options.all) {
5656
5689
  const findings = {
@@ -5666,9 +5699,9 @@ function displayResults(result, categorization, correlation, options, calibratio
5666
5699
  );
5667
5700
  if (comparisonSummary) {
5668
5701
  console.log("");
5669
- console.log(pc.bold("Framework Comparison:"));
5702
+ console.log(pc2.bold("Framework Comparison:"));
5670
5703
  for (const line of comparisonSummary.split("\n")) {
5671
- console.log(` ${pc.dim(line)}`);
5704
+ console.log(` ${pc2.dim(line)}`);
5672
5705
  }
5673
5706
  }
5674
5707
  }
@@ -5677,34 +5710,34 @@ function displayResults(result, categorization, correlation, options, calibratio
5677
5710
  async function explainCommand(invariantId) {
5678
5711
  const invariant = getInvariantById(invariantId);
5679
5712
  if (!invariant) {
5680
- console.log(pc.red(`
5713
+ console.log(pc2.red(`
5681
5714
  Unknown invariant: ${invariantId}
5682
5715
  `));
5683
- console.log(pc.bold("Available invariants:\n"));
5716
+ console.log(pc2.bold("Available invariants:\n"));
5684
5717
  for (const inv of ALL_INVARIANTS) {
5685
- const severityColor2 = inv.severity === "P0" ? pc.red : inv.severity === "P1" ? pc.yellow : pc.blue;
5718
+ const severityColor2 = inv.severity === "P0" ? pc2.red : inv.severity === "P1" ? pc2.yellow : pc2.blue;
5686
5719
  console.log(` ${severityColor2(`[${inv.severity}]`)} ${inv.id}`);
5687
- console.log(pc.dim(` ${inv.name}`));
5720
+ console.log(pc2.dim(` ${inv.name}`));
5688
5721
  }
5689
5722
  console.log("");
5690
5723
  return;
5691
5724
  }
5692
- const severityColor = invariant.severity === "P0" ? pc.red : invariant.severity === "P1" ? pc.yellow : pc.blue;
5725
+ const severityColor = invariant.severity === "P0" ? pc2.red : invariant.severity === "P1" ? pc2.yellow : pc2.blue;
5693
5726
  console.log("");
5694
- console.log(severityColor(pc.bold(`[${invariant.severity}] ${invariant.id}`)));
5695
- console.log(pc.bold(invariant.name));
5727
+ console.log(severityColor(pc2.bold(`[${invariant.severity}] ${invariant.id}`)));
5728
+ console.log(pc2.bold(invariant.name));
5696
5729
  console.log("");
5697
- console.log(pc.bold("Description:"));
5730
+ console.log(pc2.bold("Description:"));
5698
5731
  console.log(wrapText(invariant.description, 80, 2));
5699
5732
  console.log("");
5700
- console.log(pc.bold("Category:"));
5733
+ console.log(pc2.bold("Category:"));
5701
5734
  console.log(` ${invariant.category}`);
5702
5735
  console.log("");
5703
- console.log(pc.bold("Required Proof:"));
5736
+ console.log(pc2.bold("Required Proof:"));
5704
5737
  console.log(wrapText(invariant.requiredProof, 80, 2));
5705
5738
  console.log("");
5706
5739
  if (invariant.documentationUrl) {
5707
- console.log(pc.bold("Documentation:"));
5740
+ console.log(pc2.bold("Documentation:"));
5708
5741
  console.log(` ${invariant.documentationUrl}`);
5709
5742
  console.log("");
5710
5743
  }
@@ -5838,7 +5871,7 @@ jobs:
5838
5871
  async function installPreCommitHook(targetPath) {
5839
5872
  const gitDir = join(targetPath, ".git");
5840
5873
  if (!existsSync(gitDir)) {
5841
- console.log(pc.yellow("\u26A0") + " Not a git repository, skipping pre-commit hook");
5874
+ console.log(pc2.yellow("\u26A0") + " Not a git repository, skipping pre-commit hook");
5842
5875
  return;
5843
5876
  }
5844
5877
  const hooksDir = join(gitDir, "hooks");
@@ -5851,33 +5884,33 @@ async function installPreCommitHook(targetPath) {
5851
5884
  (fs) => fs.promises.readFile(hookPath, "utf-8")
5852
5885
  );
5853
5886
  if (existingHook.includes("SecurityChecks pre-commit hook")) {
5854
- console.log(pc.yellow("\u26A0") + " Pre-commit hook already installed");
5887
+ console.log(pc2.yellow("\u26A0") + " Pre-commit hook already installed");
5855
5888
  return;
5856
5889
  }
5857
- console.log(pc.yellow("\u26A0") + " Existing pre-commit hook found");
5858
- console.log(pc.dim(" To integrate SecurityChecks, add this to your hook:"));
5859
- console.log(pc.cyan(" scheck run --changed --ci"));
5890
+ console.log(pc2.yellow("\u26A0") + " Existing pre-commit hook found");
5891
+ console.log(pc2.dim(" To integrate SecurityChecks, add this to your hook:"));
5892
+ console.log(pc2.cyan(" scheck run --changed --ci"));
5860
5893
  return;
5861
5894
  }
5862
5895
  await writeFile(hookPath, PRE_COMMIT_HOOK);
5863
5896
  await chmod(hookPath, 493);
5864
- console.log(pc.green("\u2713") + " Installed pre-commit hook");
5897
+ console.log(pc2.green("\u2713") + " Installed pre-commit hook");
5865
5898
  }
5866
5899
  async function initCommand(options) {
5867
5900
  const targetPath = resolveTargetPath(options.path);
5868
- console.log(pc.bold("\n\u{1F527} Initializing SecurityChecks for your project\n"));
5901
+ console.log(pc2.bold("\n\u{1F527} Initializing SecurityChecks for your project\n"));
5869
5902
  try {
5870
5903
  const scheckDir = join(targetPath, ".scheck");
5871
5904
  if (!existsSync(scheckDir)) {
5872
5905
  await mkdir(scheckDir, { recursive: true });
5873
- console.log(pc.green("\u2713") + " Created .scheck directory");
5906
+ console.log(pc2.green("\u2713") + " Created .scheck directory");
5874
5907
  }
5875
5908
  const configPath = join(scheckDir, "config.yaml");
5876
5909
  if (!existsSync(configPath)) {
5877
5910
  await writeFile(configPath, DEFAULT_CONFIG);
5878
- console.log(pc.green("\u2713") + " Created .scheck/config.yaml");
5911
+ console.log(pc2.green("\u2713") + " Created .scheck/config.yaml");
5879
5912
  } else {
5880
- console.log(pc.yellow("\u26A0") + " Config already exists, skipping");
5913
+ console.log(pc2.yellow("\u26A0") + " Config already exists, skipping");
5881
5914
  }
5882
5915
  const baselinePath = join(scheckDir, "baseline.json");
5883
5916
  if (!existsSync(baselinePath)) {
@@ -5888,18 +5921,18 @@ async function initCommand(options) {
5888
5921
  entries: []
5889
5922
  };
5890
5923
  await writeFile(baselinePath, JSON.stringify(baseline, null, 2));
5891
- console.log(pc.green("\u2713") + " Created .scheck/baseline.json");
5924
+ console.log(pc2.green("\u2713") + " Created .scheck/baseline.json");
5892
5925
  } else {
5893
- console.log(pc.yellow("\u26A0") + " Baseline already exists, skipping");
5926
+ console.log(pc2.yellow("\u26A0") + " Baseline already exists, skipping");
5894
5927
  }
5895
5928
  const workflowDir = join(targetPath, ".github", "workflows");
5896
5929
  const workflowPath = join(workflowDir, "scheck.yml");
5897
5930
  if (!existsSync(workflowPath)) {
5898
5931
  await mkdir(workflowDir, { recursive: true });
5899
5932
  await writeFile(workflowPath, GITHUB_ACTION);
5900
- console.log(pc.green("\u2713") + " Created .github/workflows/scheck.yml");
5933
+ console.log(pc2.green("\u2713") + " Created .github/workflows/scheck.yml");
5901
5934
  } else {
5902
- console.log(pc.yellow("\u26A0") + " GitHub Action already exists, skipping");
5935
+ console.log(pc2.yellow("\u26A0") + " GitHub Action already exists, skipping");
5903
5936
  }
5904
5937
  if (options.hooks) {
5905
5938
  await installPreCommitHook(targetPath);
@@ -5913,25 +5946,25 @@ async function initCommand(options) {
5913
5946
  await import('fs').then(
5914
5947
  (fs) => fs.promises.appendFile(gitignorePath, "\n# SecurityChecks\n.scheck/cache/\n")
5915
5948
  );
5916
- console.log(pc.green("\u2713") + " Updated .gitignore");
5949
+ console.log(pc2.green("\u2713") + " Updated .gitignore");
5917
5950
  }
5918
5951
  }
5919
5952
  console.log("");
5920
- console.log(pc.green("\u2713 SecurityChecks is ready."));
5921
- console.log(pc.dim(" Catch what Copilot misses in your codebase.\n"));
5922
- console.log(pc.bold("Next steps:"));
5953
+ console.log(pc2.green("\u2713 SecurityChecks is ready."));
5954
+ console.log(pc2.dim(" Catch what Copilot misses in your codebase.\n"));
5955
+ console.log(pc2.bold("Next steps:"));
5923
5956
  console.log("");
5924
5957
  console.log(" 1. Review the config at .scheck/config.yaml");
5925
5958
  console.log(" 2. Run your first scan:");
5926
- console.log(pc.cyan(" scheck run"));
5959
+ console.log(pc2.cyan(" scheck run"));
5927
5960
  console.log("");
5928
5961
  console.log(" 3. Fix any P0/P1 violations or add waivers");
5929
5962
  console.log(" 4. Commit the .scheck directory");
5930
5963
  console.log("");
5931
- console.log(pc.dim("Learn more: https://securitychecks.ai/docs"));
5964
+ console.log(pc2.dim("Learn more: https://securitychecks.ai/docs"));
5932
5965
  console.log("");
5933
5966
  } catch (error2) {
5934
- console.error(pc.red("\nError initializing:"), error2);
5967
+ console.error(pc2.red("\nError initializing:"), error2);
5935
5968
  process.exit(1);
5936
5969
  }
5937
5970
  }
@@ -6034,14 +6067,14 @@ async function showBaseline(targetPath) {
6034
6067
  const baseline = await loadBaseline(targetPath);
6035
6068
  const entries = Object.values(baseline.entries);
6036
6069
  if (entries.length === 0) {
6037
- console.log(pc.dim("No baseline entries."));
6038
- console.log(pc.dim(`Run ${pc.bold("scheck baseline --update")} to add current findings.`));
6070
+ console.log(pc2.dim("No baseline entries."));
6071
+ console.log(pc2.dim(`Run ${pc2.bold("scheck baseline --update")} to add current findings.`));
6039
6072
  return;
6040
6073
  }
6041
- console.log(pc.bold(`Baseline: ${entries.length} entries`));
6042
- console.log(pc.dim(`Schema version: ${baseline.schemaVersion}`));
6043
- console.log(pc.dim(`Last updated: ${baseline.updatedAt}`));
6044
- console.log(pc.dim(`Path: ${getBaselinePath(targetPath)}`));
6074
+ console.log(pc2.bold(`Baseline: ${entries.length} entries`));
6075
+ console.log(pc2.dim(`Schema version: ${baseline.schemaVersion}`));
6076
+ console.log(pc2.dim(`Last updated: ${baseline.updatedAt}`));
6077
+ console.log(pc2.dim(`Path: ${getBaselinePath(targetPath)}`));
6045
6078
  console.log();
6046
6079
  const byInvariant = /* @__PURE__ */ new Map();
6047
6080
  for (const entry of entries) {
@@ -6050,28 +6083,28 @@ async function showBaseline(targetPath) {
6050
6083
  byInvariant.set(entry.invariantId, list);
6051
6084
  }
6052
6085
  for (const [invariantId, invariantEntries] of byInvariant) {
6053
- console.log(pc.cyan(`${invariantId} (${invariantEntries.length})`));
6086
+ console.log(pc2.cyan(`${invariantId} (${invariantEntries.length})`));
6054
6087
  for (const entry of invariantEntries.slice(0, 5)) {
6055
6088
  const symbol = entry.symbol ? `:${entry.symbol}` : "";
6056
- console.log(` ${pc.dim("\u2022")} ${entry.file}${symbol}`);
6089
+ console.log(` ${pc2.dim("\u2022")} ${entry.file}${symbol}`);
6057
6090
  if (entry.notes) {
6058
- console.log(` ${pc.dim(entry.notes)}`);
6091
+ console.log(` ${pc2.dim(entry.notes)}`);
6059
6092
  }
6060
6093
  }
6061
6094
  if (invariantEntries.length > 5) {
6062
- console.log(pc.dim(` ... and ${invariantEntries.length - 5} more`));
6095
+ console.log(pc2.dim(` ... and ${invariantEntries.length - 5} more`));
6063
6096
  }
6064
6097
  }
6065
6098
  }
6066
6099
  async function updateBaseline(targetPath, notes, skipConfirmation = false) {
6067
- console.log(pc.dim("Running audit to collect current findings..."));
6100
+ console.log(pc2.dim("Running audit to collect current findings..."));
6068
6101
  const result = await audit({
6069
6102
  targetPath
6070
6103
  });
6071
6104
  const baseline = await loadBaseline(targetPath);
6072
6105
  const findings = result.results.flatMap((r) => r.findings);
6073
6106
  if (findings.length === 0) {
6074
- console.log(pc.green("No findings to baseline."));
6107
+ console.log(pc2.green("No findings to baseline."));
6075
6108
  return;
6076
6109
  }
6077
6110
  const newFindings = findings.filter((f) => {
@@ -6079,11 +6112,11 @@ async function updateBaseline(targetPath, notes, skipConfirmation = false) {
6079
6112
  return !(findingId2 in baseline.entries);
6080
6113
  });
6081
6114
  if (newFindings.length === 0) {
6082
- console.log(pc.green("All findings are already in the baseline."));
6115
+ console.log(pc2.green("All findings are already in the baseline."));
6083
6116
  return;
6084
6117
  }
6085
6118
  console.log();
6086
- console.log(pc.yellow(`About to add ${newFindings.length} finding(s) to the baseline:`));
6119
+ console.log(pc2.yellow(`About to add ${newFindings.length} finding(s) to the baseline:`));
6087
6120
  console.log();
6088
6121
  const byInvariant = /* @__PURE__ */ new Map();
6089
6122
  for (const finding of newFindings) {
@@ -6092,14 +6125,14 @@ async function updateBaseline(targetPath, notes, skipConfirmation = false) {
6092
6125
  byInvariant.set(finding.invariantId, list);
6093
6126
  }
6094
6127
  for (const [invariantId, invariantFindings] of byInvariant) {
6095
- console.log(pc.cyan(` ${invariantId} (${invariantFindings.length})`));
6128
+ console.log(pc2.cyan(` ${invariantId} (${invariantFindings.length})`));
6096
6129
  for (const finding of invariantFindings.slice(0, 3)) {
6097
6130
  const evidence = finding.evidence[0];
6098
6131
  const location = evidence ? `${evidence.file}:${evidence.line}` : "unknown";
6099
- console.log(pc.dim(` \u2022 ${location}`));
6132
+ console.log(pc2.dim(` \u2022 ${location}`));
6100
6133
  }
6101
6134
  if (invariantFindings.length > 3) {
6102
- console.log(pc.dim(` ... and ${invariantFindings.length - 3} more`));
6135
+ console.log(pc2.dim(` ... and ${invariantFindings.length - 3} more`));
6103
6136
  }
6104
6137
  }
6105
6138
  console.log();
@@ -6108,16 +6141,16 @@ async function updateBaseline(targetPath, notes, skipConfirmation = false) {
6108
6141
  `Add ${newFindings.length} finding(s) to baseline? This will suppress them in CI. [y/N] `
6109
6142
  );
6110
6143
  if (!confirmed) {
6111
- console.log(pc.dim("Cancelled."));
6144
+ console.log(pc2.dim("Cancelled."));
6112
6145
  return;
6113
6146
  }
6114
6147
  }
6115
6148
  const added = addToBaseline(baseline, findings, notes);
6116
6149
  await saveBaseline(targetPath, baseline);
6117
- console.log(pc.green(`\u2713 Baseline updated`));
6118
- console.log(` ${pc.bold(added.toString())} new entries added`);
6119
- console.log(` ${pc.dim(Object.keys(baseline.entries).length.toString())} total entries`);
6120
- console.log(` ${pc.dim(`Path: ${getBaselinePath(targetPath)}`)}`);
6150
+ console.log(pc2.green(`\u2713 Baseline updated`));
6151
+ console.log(` ${pc2.bold(added.toString())} new entries added`);
6152
+ console.log(` ${pc2.dim(Object.keys(baseline.entries).length.toString())} total entries`);
6153
+ console.log(` ${pc2.dim(`Path: ${getBaselinePath(targetPath)}`)}`);
6121
6154
  }
6122
6155
  function isNonInteractive() {
6123
6156
  return !process.stdin.isTTY || !!process.env["CI"];
@@ -6139,12 +6172,12 @@ async function pruneBaselineEntries(targetPath, staleDays) {
6139
6172
  const beforeCount = Object.keys(baseline.entries).length;
6140
6173
  const removed = pruneBaseline(baseline, staleDays);
6141
6174
  if (removed === 0) {
6142
- console.log(pc.dim(`No entries older than ${staleDays} days.`));
6175
+ console.log(pc2.dim(`No entries older than ${staleDays} days.`));
6143
6176
  return;
6144
6177
  }
6145
6178
  await saveBaseline(targetPath, baseline);
6146
- console.log(pc.green(`\u2713 Pruned ${removed} stale entries`));
6147
- console.log(` ${pc.dim(`${beforeCount} \u2192 ${Object.keys(baseline.entries).length} entries`)}`);
6179
+ console.log(pc2.green(`\u2713 Pruned ${removed} stale entries`));
6180
+ console.log(` ${pc2.dim(`${beforeCount} \u2192 ${Object.keys(baseline.entries).length} entries`)}`);
6148
6181
  }
6149
6182
  async function waiverCommand(options) {
6150
6183
  const targetPath = resolve(options.path ?? process.cwd());
@@ -6159,24 +6192,24 @@ async function waiverCommand(options) {
6159
6192
  async function waiveCommand(findingIdOrInvariant, options) {
6160
6193
  const targetPath = resolve(options.path ?? process.cwd());
6161
6194
  if (!options.reason) {
6162
- console.error(pc.red("Error: --reason is required"));
6195
+ console.error(pc2.red("Error: --reason is required"));
6163
6196
  console.log(
6164
- pc.dim('Example: scheck waive WEBHOOK.IDEMPOTENT:abc123 --reason-key will_fix_later --reason "Fixing in sprint 42"')
6197
+ pc2.dim('Example: scheck waive WEBHOOK.IDEMPOTENT:abc123 --reason-key will_fix_later --reason "Fixing in sprint 42"')
6165
6198
  );
6166
6199
  process.exit(1);
6167
6200
  }
6168
6201
  let reasonKey;
6169
6202
  if (options.reasonKey) {
6170
6203
  if (!isValidWaiverReasonKey(options.reasonKey)) {
6171
- console.error(pc.red(`Error: Invalid --reason-key "${options.reasonKey}"`));
6172
- console.log(pc.dim(`Valid reason keys: ${WAIVER_REASON_KEYS.join(", ")}`));
6204
+ console.error(pc2.red(`Error: Invalid --reason-key "${options.reasonKey}"`));
6205
+ console.log(pc2.dim(`Valid reason keys: ${WAIVER_REASON_KEYS.join(", ")}`));
6173
6206
  process.exit(1);
6174
6207
  }
6175
6208
  reasonKey = options.reasonKey;
6176
6209
  }
6177
6210
  const expiresInDays = parseExpiration(options.expires ?? "30d");
6178
6211
  if (expiresInDays === null) {
6179
- console.error(pc.red("Error: Invalid --expires format. Use: 7d, 30d, 90d, etc."));
6212
+ console.error(pc2.red("Error: Invalid --expires format. Use: 7d, 30d, 90d, etc."));
6180
6213
  process.exit(1);
6181
6214
  }
6182
6215
  const owner = options.owner ?? process.env["USER"] ?? process.env["USERNAME"] ?? "unknown";
@@ -6200,25 +6233,25 @@ async function showWaivers(targetPath) {
6200
6233
  const waivers = await loadWaivers(targetPath);
6201
6234
  const entries = Object.values(waivers.entries);
6202
6235
  if (entries.length === 0) {
6203
- console.log(pc.dim("No active waivers."));
6236
+ console.log(pc2.dim("No active waivers."));
6204
6237
  return;
6205
6238
  }
6206
- console.log(pc.bold(`Waivers: ${entries.length} active`));
6207
- console.log(pc.dim(`Schema version: ${waivers.schemaVersion}`));
6208
- console.log(pc.dim(`Path: ${getWaiverPath(targetPath)}`));
6239
+ console.log(pc2.bold(`Waivers: ${entries.length} active`));
6240
+ console.log(pc2.dim(`Schema version: ${waivers.schemaVersion}`));
6241
+ console.log(pc2.dim(`Path: ${getWaiverPath(targetPath)}`));
6209
6242
  console.log();
6210
6243
  entries.sort((a, b) => a.expiresAt.localeCompare(b.expiresAt));
6211
6244
  for (const entry of entries) {
6212
6245
  const expiresIn = getExpiresInLabel(entry.expiresAt);
6213
6246
  const isExpiringSoon = new Date(entry.expiresAt) < new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
6214
- console.log(pc.cyan(entry.findingId));
6215
- console.log(` ${pc.dim("File:")} ${entry.file}${entry.symbol ? `:${entry.symbol}` : ""}`);
6216
- console.log(` ${pc.dim("Reason:")} ${entry.reason}`);
6247
+ console.log(pc2.cyan(entry.findingId));
6248
+ console.log(` ${pc2.dim("File:")} ${entry.file}${entry.symbol ? `:${entry.symbol}` : ""}`);
6249
+ console.log(` ${pc2.dim("Reason:")} ${entry.reason}`);
6217
6250
  if (entry.reasonKey) {
6218
- console.log(` ${pc.dim("Reason Key:")} ${entry.reasonKey}`);
6251
+ console.log(` ${pc2.dim("Reason Key:")} ${entry.reasonKey}`);
6219
6252
  }
6220
- console.log(` ${pc.dim("Owner:")} ${entry.owner}`);
6221
- console.log(` ${pc.dim("Expires:")} ${isExpiringSoon ? pc.yellow(expiresIn) : expiresIn}`);
6253
+ console.log(` ${pc2.dim("Owner:")} ${entry.owner}`);
6254
+ console.log(` ${pc2.dim("Expires:")} ${isExpiringSoon ? pc2.yellow(expiresIn) : expiresIn}`);
6222
6255
  console.log();
6223
6256
  }
6224
6257
  }
@@ -6226,17 +6259,17 @@ async function showExpiringWaivers(targetPath, withinDays) {
6226
6259
  const waivers = await loadWaivers(targetPath);
6227
6260
  const expiring = getExpiringWaivers(waivers, withinDays);
6228
6261
  if (expiring.length === 0) {
6229
- console.log(pc.dim(`No waivers expiring within ${withinDays} days.`));
6262
+ console.log(pc2.dim(`No waivers expiring within ${withinDays} days.`));
6230
6263
  return;
6231
6264
  }
6232
- console.log(pc.yellow(`\u26A0 ${expiring.length} waiver(s) expiring within ${withinDays} days:`));
6265
+ console.log(pc2.yellow(`\u26A0 ${expiring.length} waiver(s) expiring within ${withinDays} days:`));
6233
6266
  console.log();
6234
6267
  for (const entry of expiring) {
6235
6268
  const expiresIn = getExpiresInLabel(entry.expiresAt);
6236
- console.log(pc.yellow(entry.findingId));
6237
- console.log(` ${pc.dim("File:")} ${entry.file}`);
6238
- console.log(` ${pc.dim("Owner:")} ${entry.owner}`);
6239
- console.log(` ${pc.dim("Expires:")} ${expiresIn}`);
6269
+ console.log(pc2.yellow(entry.findingId));
6270
+ console.log(` ${pc2.dim("File:")} ${entry.file}`);
6271
+ console.log(` ${pc2.dim("Owner:")} ${entry.owner}`);
6272
+ console.log(` ${pc2.dim("Expires:")} ${expiresIn}`);
6240
6273
  console.log();
6241
6274
  }
6242
6275
  }
@@ -6245,43 +6278,43 @@ async function pruneWaivers(targetPath) {
6245
6278
  const beforeCount = Object.keys(waivers.entries).length;
6246
6279
  const removed = pruneExpiredWaivers(waivers);
6247
6280
  if (removed === 0) {
6248
- console.log(pc.dim("No expired waivers to remove."));
6281
+ console.log(pc2.dim("No expired waivers to remove."));
6249
6282
  return;
6250
6283
  }
6251
6284
  await saveWaivers(targetPath, waivers);
6252
- console.log(pc.green(`\u2713 Removed ${removed} expired waivers`));
6253
- console.log(` ${pc.dim(`${beforeCount} \u2192 ${Object.keys(waivers.entries).length} waivers`)}`);
6285
+ console.log(pc2.green(`\u2713 Removed ${removed} expired waivers`));
6286
+ console.log(` ${pc2.dim(`${beforeCount} \u2192 ${Object.keys(waivers.entries).length} waivers`)}`);
6254
6287
  }
6255
6288
  async function waiveByFindingId(targetPath, findingId2, options) {
6256
- console.log(pc.dim("Finding matching issue..."));
6289
+ console.log(pc2.dim("Finding matching issue..."));
6257
6290
  const result = await audit({ targetPath });
6258
6291
  const findings = result.results.flatMap((r) => r.findings);
6259
6292
  const normalizedId = findingId2.split(":").slice(0, 2).join(":");
6260
6293
  const matching = findings.find((f) => generateFindingId(f) === normalizedId);
6261
6294
  if (!matching) {
6262
- console.error(pc.red(`Error: No finding found matching ${findingId2}`));
6263
- console.log(pc.dim("Run `scheck run` to see current findings."));
6295
+ console.error(pc2.red(`Error: No finding found matching ${findingId2}`));
6296
+ console.log(pc2.dim("Run `scheck run` to see current findings."));
6264
6297
  process.exit(1);
6265
6298
  }
6266
6299
  const waivers = await loadWaivers(targetPath);
6267
6300
  const entry = addWaiver(waivers, matching, options);
6268
6301
  await saveWaivers(targetPath, waivers);
6269
- console.log(pc.green(`\u2713 Waiver created`));
6270
- console.log(` ${pc.dim("Finding:")} ${entry.findingId}`);
6271
- console.log(` ${pc.dim("Reason:")} ${entry.reason}`);
6302
+ console.log(pc2.green(`\u2713 Waiver created`));
6303
+ console.log(` ${pc2.dim("Finding:")} ${entry.findingId}`);
6304
+ console.log(` ${pc2.dim("Reason:")} ${entry.reason}`);
6272
6305
  if (entry.reasonKey) {
6273
- console.log(` ${pc.dim("Reason Key:")} ${entry.reasonKey}`);
6306
+ console.log(` ${pc2.dim("Reason Key:")} ${entry.reasonKey}`);
6274
6307
  }
6275
- console.log(` ${pc.dim("Expires:")} ${getExpiresInLabel(entry.expiresAt)}`);
6308
+ console.log(` ${pc2.dim("Expires:")} ${getExpiresInLabel(entry.expiresAt)}`);
6276
6309
  }
6277
6310
  async function waiveByInvariant(targetPath, invariantId, options) {
6278
- console.log(pc.dim("Finding matching issues..."));
6311
+ console.log(pc2.dim("Finding matching issues..."));
6279
6312
  const result = await audit({ targetPath });
6280
6313
  const findings = result.results.flatMap((r) => r.findings);
6281
6314
  const matching = findings.filter((f) => f.invariantId === invariantId);
6282
6315
  if (matching.length === 0) {
6283
- console.error(pc.red(`Error: No findings found for ${invariantId}`));
6284
- console.log(pc.dim("Run `scheck run` to see current findings."));
6316
+ console.error(pc2.red(`Error: No findings found for ${invariantId}`));
6317
+ console.log(pc2.dim("Run `scheck run` to see current findings."));
6285
6318
  process.exit(1);
6286
6319
  }
6287
6320
  const waivers = await loadWaivers(targetPath);
@@ -6289,12 +6322,12 @@ async function waiveByInvariant(targetPath, invariantId, options) {
6289
6322
  addWaiver(waivers, finding, options);
6290
6323
  }
6291
6324
  await saveWaivers(targetPath, waivers);
6292
- console.log(pc.green(`\u2713 Created ${matching.length} waiver(s) for ${invariantId}`));
6293
- console.log(` ${pc.dim("Reason:")} ${options.reason}`);
6325
+ console.log(pc2.green(`\u2713 Created ${matching.length} waiver(s) for ${invariantId}`));
6326
+ console.log(` ${pc2.dim("Reason:")} ${options.reason}`);
6294
6327
  if (options.reasonKey) {
6295
- console.log(` ${pc.dim("Reason Key:")} ${options.reasonKey}`);
6328
+ console.log(` ${pc2.dim("Reason Key:")} ${options.reasonKey}`);
6296
6329
  }
6297
- console.log(` ${pc.dim("Expires:")} in ${options.expiresInDays} days`);
6330
+ console.log(` ${pc2.dim("Expires:")} in ${options.expiresInDays} days`);
6298
6331
  }
6299
6332
  function parseExpiration(value) {
6300
6333
  const match = value.match(/^(\d+)d$/i);
@@ -6336,7 +6369,7 @@ var CloudApiClient = class {
6336
6369
  const headers = {
6337
6370
  Authorization: `Bearer ${this.apiKey}`,
6338
6371
  "Content-Type": "application/json",
6339
- "User-Agent": `scheck-cli/${"0.1.1-rc.1"}`
6372
+ "User-Agent": `scheck-cli/${"0.2.0"}`
6340
6373
  };
6341
6374
  const bypassSecret = process.env["VERCEL_AUTOMATION_BYPASS_SECRET"];
6342
6375
  if (bypassSecret) {
@@ -6481,8 +6514,8 @@ function createCloudClient(apiUrl, apiKey) {
6481
6514
  // src/commands/login.ts
6482
6515
  async function promptForApiKey() {
6483
6516
  const rl = readline.createInterface({ input: stdin, output: stdout });
6484
- console.log(pc.bold("\nSecurityChecks Cloud Login\n"));
6485
- console.log(pc.dim("Get your API key at: https://securitychecks.ai/dashboard/settings/api-keys\n"));
6517
+ console.log(pc2.bold("\nSecurityChecks Cloud Login\n"));
6518
+ console.log(pc2.dim("Get your API key at: https://securitychecks.ai/dashboard/settings/api-keys\n"));
6486
6519
  try {
6487
6520
  const apiKey = await rl.question("API Key: ");
6488
6521
  return apiKey.trim();
@@ -6495,7 +6528,7 @@ async function loginCommand(options) {
6495
6528
  const config = await loadCloudConfig();
6496
6529
  if (options.check) {
6497
6530
  if (!config.apiKey) {
6498
- console.log(pc.yellow("Not logged in to SecurityChecks cloud.\n"));
6531
+ console.log(pc2.yellow("Not logged in to SecurityChecks cloud.\n"));
6499
6532
  console.log("Run `scheck login` to authenticate.");
6500
6533
  return;
6501
6534
  }
@@ -6504,18 +6537,18 @@ async function loginCommand(options) {
6504
6537
  try {
6505
6538
  const result2 = await client2.validateKey();
6506
6539
  if (result2.valid) {
6507
- console.log(pc.green("\u2713 Logged in to SecurityChecks cloud\n"));
6540
+ console.log(pc2.green("\u2713 Logged in to SecurityChecks cloud\n"));
6508
6541
  console.log(formatConfig(config));
6509
6542
  } else {
6510
- console.log(pc.yellow("API key is invalid or expired.\n"));
6543
+ console.log(pc2.yellow("API key is invalid or expired.\n"));
6511
6544
  console.log("Run `scheck login` to re-authenticate.");
6512
6545
  }
6513
6546
  } catch (error2) {
6514
6547
  if (error2 instanceof CLIError) {
6515
- console.log(pc.red(`\u2717 ${error2.message}
6548
+ console.log(pc2.red(`\u2717 ${error2.message}
6516
6549
  `));
6517
6550
  } else {
6518
- console.log(pc.red("\u2717 Failed to validate API key\n"));
6551
+ console.log(pc2.red("\u2717 Failed to validate API key\n"));
6519
6552
  }
6520
6553
  }
6521
6554
  return;
@@ -6525,7 +6558,7 @@ async function loginCommand(options) {
6525
6558
  apiKey = await promptForApiKey();
6526
6559
  }
6527
6560
  if (!apiKey) {
6528
- console.log(pc.red("\n\u2717 No API key provided.\n"));
6561
+ console.log(pc2.red("\n\u2717 No API key provided.\n"));
6529
6562
  process.exit(1);
6530
6563
  }
6531
6564
  if (!isValidApiKey(apiKey)) {
@@ -6535,7 +6568,7 @@ async function loginCommand(options) {
6535
6568
  );
6536
6569
  }
6537
6570
  const apiUrl = options.apiUrl || getApiUrl(config);
6538
- console.log(pc.dim(`
6571
+ console.log(pc2.dim(`
6539
6572
  Connecting to ${apiUrl}...`));
6540
6573
  const client = createCloudClient(apiUrl, apiKey);
6541
6574
  const result = await client.validateKey();
@@ -6552,7 +6585,7 @@ Connecting to ${apiUrl}...`));
6552
6585
  cloudEnabled: true,
6553
6586
  lastSync: (/* @__PURE__ */ new Date()).toISOString()
6554
6587
  });
6555
- console.log(pc.green("\n\u2713 Successfully logged in!\n"));
6588
+ console.log(pc2.green("\n\u2713 Successfully logged in!\n"));
6556
6589
  console.log(` Email: ${userInfo.email}`);
6557
6590
  if (userInfo.name) {
6558
6591
  console.log(` Name: ${userInfo.name}`);
@@ -6561,24 +6594,24 @@ Connecting to ${apiUrl}...`));
6561
6594
  console.log(` Organization: ${userInfo.organizations[0]?.name}`);
6562
6595
  }
6563
6596
  console.log("");
6564
- console.log(pc.dim("Cloud mode is now enabled by default."));
6565
- console.log(pc.dim("Run `scheck run --cloud` to sync findings."));
6597
+ console.log(pc2.dim("Cloud mode is now enabled by default."));
6598
+ console.log(pc2.dim("Run `scheck run --cloud` to sync findings."));
6566
6599
  console.log("");
6567
6600
  } catch (error2) {
6568
6601
  if (error2 instanceof CLIError) {
6569
- console.error(pc.red(`
6602
+ console.error(pc2.red(`
6570
6603
  \u2717 ${error2.message}
6571
6604
  `));
6572
6605
  const remediation = error2.getRemediation();
6573
6606
  if (remediation) {
6574
- console.error(pc.yellow("How to fix:"));
6607
+ console.error(pc2.yellow("How to fix:"));
6575
6608
  for (const line of remediation.split("\n")) {
6576
- console.error(pc.dim(` ${line}`));
6609
+ console.error(pc2.dim(` ${line}`));
6577
6610
  }
6578
6611
  console.error("");
6579
6612
  }
6580
6613
  } else {
6581
- console.error(pc.red(`
6614
+ console.error(pc2.red(`
6582
6615
  \u2717 Login failed: ${error2 instanceof Error ? error2.message : "Unknown error"}
6583
6616
  `));
6584
6617
  }
@@ -6588,7 +6621,7 @@ Connecting to ${apiUrl}...`));
6588
6621
  async function logoutCommand() {
6589
6622
  const config = await loadCloudConfig();
6590
6623
  if (!config.apiKey) {
6591
- console.log(pc.yellow("Not logged in to SecurityChecks cloud.\n"));
6624
+ console.log(pc2.yellow("Not logged in to SecurityChecks cloud.\n"));
6592
6625
  return;
6593
6626
  }
6594
6627
  await updateCloudConfig({
@@ -6598,8 +6631,8 @@ async function logoutCommand() {
6598
6631
  cloudEnabled: false,
6599
6632
  lastSync: void 0
6600
6633
  });
6601
- console.log(pc.green("\u2713 Successfully logged out.\n"));
6602
- console.log(pc.dim("Cloud mode has been disabled."));
6634
+ console.log(pc2.green("\u2713 Successfully logged out.\n"));
6635
+ console.log(pc2.dim("Cloud mode has been disabled."));
6603
6636
  console.log("");
6604
6637
  }
6605
6638
  var ALLOWED_KEYS = ["project", "apiUrl", "cloudEnabled"];
@@ -6613,13 +6646,13 @@ async function configCommand(options) {
6613
6646
  console.log("");
6614
6647
  console.log(formatConfig(config));
6615
6648
  console.log("");
6616
- console.log(pc.dim(`Config file: ${CONFIG_FILE}`));
6649
+ console.log(pc2.dim(`Config file: ${CONFIG_FILE}`));
6617
6650
  console.log("");
6618
6651
  return;
6619
6652
  }
6620
6653
  if (options.clear) {
6621
6654
  await clearCloudConfig();
6622
- console.log(pc.green("\u2713 Configuration cleared.\n"));
6655
+ console.log(pc2.green("\u2713 Configuration cleared.\n"));
6623
6656
  return;
6624
6657
  }
6625
6658
  if (options.set) {
@@ -6642,7 +6675,7 @@ async function configCommand(options) {
6642
6675
  parsedValue = value === "true" || value === "1";
6643
6676
  }
6644
6677
  await updateCloudConfig({ [key]: parsedValue });
6645
- console.log(pc.green(`\u2713 Set ${key} = ${parsedValue}
6678
+ console.log(pc2.green(`\u2713 Set ${key} = ${parsedValue}
6646
6679
  `));
6647
6680
  return;
6648
6681
  }
@@ -6655,40 +6688,40 @@ async function configCommand(options) {
6655
6688
  );
6656
6689
  }
6657
6690
  await updateCloudConfig({ [key]: void 0 });
6658
- console.log(pc.green(`\u2713 Unset ${key}
6691
+ console.log(pc2.green(`\u2713 Unset ${key}
6659
6692
  `));
6660
6693
  return;
6661
6694
  }
6662
6695
  if (options.project !== void 0) {
6663
6696
  await updateCloudConfig({ project: options.project });
6664
- console.log(pc.green(`\u2713 Set project = ${options.project}
6697
+ console.log(pc2.green(`\u2713 Set project = ${options.project}
6665
6698
  `));
6666
6699
  }
6667
6700
  if (options.apiUrl !== void 0) {
6668
6701
  await updateCloudConfig({ apiUrl: options.apiUrl });
6669
- console.log(pc.green(`\u2713 Set apiUrl = ${options.apiUrl}
6702
+ console.log(pc2.green(`\u2713 Set apiUrl = ${options.apiUrl}
6670
6703
  `));
6671
6704
  }
6672
6705
  if (options.cloudEnabled !== void 0) {
6673
6706
  await updateCloudConfig({ cloudEnabled: options.cloudEnabled });
6674
- console.log(pc.green(`\u2713 Set cloudEnabled = ${options.cloudEnabled}
6707
+ console.log(pc2.green(`\u2713 Set cloudEnabled = ${options.cloudEnabled}
6675
6708
  `));
6676
6709
  }
6677
6710
  } catch (error2) {
6678
6711
  if (error2 instanceof CLIError) {
6679
- console.error(pc.red(`
6712
+ console.error(pc2.red(`
6680
6713
  \u2717 ${error2.message}
6681
6714
  `));
6682
6715
  const remediation = error2.getRemediation();
6683
6716
  if (remediation) {
6684
- console.error(pc.yellow("How to fix:"));
6717
+ console.error(pc2.yellow("How to fix:"));
6685
6718
  for (const line of remediation.split("\n")) {
6686
- console.error(pc.dim(` ${line}`));
6719
+ console.error(pc2.dim(` ${line}`));
6687
6720
  }
6688
6721
  console.error("");
6689
6722
  }
6690
6723
  } else {
6691
- console.error(pc.red(`
6724
+ console.error(pc2.red(`
6692
6725
  \u2717 Config failed: ${error2 instanceof Error ? error2.message : "Unknown error"}
6693
6726
  `));
6694
6727
  }
@@ -6750,14 +6783,14 @@ async function syncCommand(options) {
6750
6783
  );
6751
6784
  }
6752
6785
  const findingsPath = options.findings || DEFAULT_FINDINGS_PATH;
6753
- console.log(pc.dim(`Loading results from ${findingsPath}...`));
6786
+ console.log(pc2.dim(`Loading results from ${findingsPath}...`));
6754
6787
  const results = await loadResults(findingsPath);
6755
6788
  const allFindings = results.results.flatMap((r) => r.findings);
6756
6789
  const gitInfo = await detectGitInfo(results.targetPath);
6757
6790
  const branch = options.branch || gitInfo.branch;
6758
6791
  const commitSha = options.commit || gitInfo.commit;
6759
6792
  console.log("");
6760
- console.log(pc.bold("Sync Summary:"));
6793
+ console.log(pc2.bold("Sync Summary:"));
6761
6794
  console.log(` Project: ${projectSlug}`);
6762
6795
  console.log(` Branch: ${branch || "(none)"}`);
6763
6796
  console.log(` Commit: ${commitSha ? commitSha.substring(0, 8) : "(none)"}`);
@@ -6765,24 +6798,24 @@ async function syncCommand(options) {
6765
6798
  console.log(` P0: ${results.summary.byPriority.P0}, P1: ${results.summary.byPriority.P1}, P2: ${results.summary.byPriority.P2}`);
6766
6799
  console.log("");
6767
6800
  if (options.dryRun) {
6768
- console.log(pc.yellow("Dry run - no changes made.\n"));
6801
+ console.log(pc2.yellow("Dry run - no changes made.\n"));
6769
6802
  return;
6770
6803
  }
6771
6804
  const apiUrl = getApiUrl(config);
6772
6805
  const client = createCloudClient(apiUrl, apiKey);
6773
- console.log(pc.dim("Creating scan..."));
6806
+ console.log(pc2.dim("Creating scan..."));
6774
6807
  const scan = await client.createScan({
6775
6808
  projectSlug,
6776
6809
  branch,
6777
6810
  commitSha
6778
6811
  });
6779
- console.log(pc.dim(`Scan created: ${scan.id}`));
6812
+ console.log(pc2.dim(`Scan created: ${scan.id}`));
6780
6813
  if (allFindings.length > 0) {
6781
- console.log(pc.dim("Submitting findings..."));
6814
+ console.log(pc2.dim("Submitting findings..."));
6782
6815
  const submitResult = await client.submitFindings(scan.id, allFindings);
6783
- console.log(pc.dim(`Created: ${submitResult.created}, Updated: ${submitResult.updated}`));
6816
+ console.log(pc2.dim(`Created: ${submitResult.created}, Updated: ${submitResult.updated}`));
6784
6817
  }
6785
- console.log(pc.dim("Completing scan..."));
6818
+ console.log(pc2.dim("Completing scan..."));
6786
6819
  await client.updateScan(scan.id, {
6787
6820
  status: "COMPLETED",
6788
6821
  summary: {
@@ -6793,24 +6826,24 @@ async function syncCommand(options) {
6793
6826
  duration: results.duration
6794
6827
  });
6795
6828
  await updateCloudConfig({ lastSync: (/* @__PURE__ */ new Date()).toISOString() });
6796
- console.log(pc.green("\n\u2713 Sync complete!\n"));
6797
- console.log(pc.dim(`View at: https://securitychecks.ai/dashboard/scans/${scan.id}`));
6829
+ console.log(pc2.green("\n\u2713 Sync complete!\n"));
6830
+ console.log(pc2.dim(`View at: https://securitychecks.ai/dashboard/scans/${scan.id}`));
6798
6831
  console.log("");
6799
6832
  } catch (error2) {
6800
6833
  if (error2 instanceof CLIError) {
6801
- console.error(pc.red(`
6834
+ console.error(pc2.red(`
6802
6835
  \u2717 ${error2.message}
6803
6836
  `));
6804
6837
  const remediation = error2.getRemediation();
6805
6838
  if (remediation) {
6806
- console.error(pc.yellow("How to fix:"));
6839
+ console.error(pc2.yellow("How to fix:"));
6807
6840
  for (const line of remediation.split("\n")) {
6808
- console.error(pc.dim(` ${line}`));
6841
+ console.error(pc2.dim(` ${line}`));
6809
6842
  }
6810
6843
  console.error("");
6811
6844
  }
6812
6845
  } else {
6813
- console.error(pc.red(`
6846
+ console.error(pc2.red(`
6814
6847
  \u2717 Sync failed: ${error2 instanceof Error ? error2.message : "Unknown error"}
6815
6848
  `));
6816
6849
  }
@@ -6857,7 +6890,7 @@ async function hooksCommand(options) {
6857
6890
  const targetPath = resolveTargetPath(options.path);
6858
6891
  const gitDir = join(targetPath, ".git");
6859
6892
  if (!existsSync(gitDir)) {
6860
- console.error(pc.red("Error:") + " Not a git repository");
6893
+ console.error(pc2.red("Error:") + " Not a git repository");
6861
6894
  process.exit(1);
6862
6895
  }
6863
6896
  const hooksDir = join(gitDir, "hooks");
@@ -6866,19 +6899,19 @@ async function hooksCommand(options) {
6866
6899
  options.show = true;
6867
6900
  }
6868
6901
  if (options.show) {
6869
- console.log(pc.bold("\nSecurityChecks Git Hooks\n"));
6902
+ console.log(pc2.bold("\nSecurityChecks Git Hooks\n"));
6870
6903
  if (existsSync(hookPath)) {
6871
6904
  const hookContent = await readFile(hookPath, "utf-8");
6872
6905
  if (hookContent.includes("SecurityChecks pre-commit hook")) {
6873
- console.log(pc.green("\u2713") + " Pre-commit hook is installed");
6874
- console.log(pc.dim(` Location: ${hookPath}`));
6906
+ console.log(pc2.green("\u2713") + " Pre-commit hook is installed");
6907
+ console.log(pc2.dim(` Location: ${hookPath}`));
6875
6908
  } else {
6876
- console.log(pc.yellow("\u26A0") + " Pre-commit hook exists but is not SecurityChecks");
6877
- console.log(pc.dim(" Use --install to add SecurityChecks to your workflow"));
6909
+ console.log(pc2.yellow("\u26A0") + " Pre-commit hook exists but is not SecurityChecks");
6910
+ console.log(pc2.dim(" Use --install to add SecurityChecks to your workflow"));
6878
6911
  }
6879
6912
  } else {
6880
- console.log(pc.dim("\u25CB") + " Pre-commit hook is not installed");
6881
- console.log(pc.dim(" Use --install to install it"));
6913
+ console.log(pc2.dim("\u25CB") + " Pre-commit hook is not installed");
6914
+ console.log(pc2.dim(" Use --install to install it"));
6882
6915
  }
6883
6916
  console.log("");
6884
6917
  return;
@@ -6890,36 +6923,36 @@ async function hooksCommand(options) {
6890
6923
  if (existsSync(hookPath)) {
6891
6924
  const existingHook = await readFile(hookPath, "utf-8");
6892
6925
  if (existingHook.includes("SecurityChecks pre-commit hook")) {
6893
- console.log(pc.yellow("\u26A0") + " Pre-commit hook already installed");
6926
+ console.log(pc2.yellow("\u26A0") + " Pre-commit hook already installed");
6894
6927
  return;
6895
6928
  }
6896
- console.log(pc.yellow("\u26A0") + " Existing pre-commit hook found");
6897
- console.log(pc.dim(" To manually integrate, add this to your hook:"));
6898
- console.log(pc.cyan(" scheck run --changed --ci"));
6929
+ console.log(pc2.yellow("\u26A0") + " Existing pre-commit hook found");
6930
+ console.log(pc2.dim(" To manually integrate, add this to your hook:"));
6931
+ console.log(pc2.cyan(" scheck run --changed --ci"));
6899
6932
  console.log("");
6900
- console.log(pc.dim(" Or use --uninstall first to replace the existing hook"));
6933
+ console.log(pc2.dim(" Or use --uninstall first to replace the existing hook"));
6901
6934
  return;
6902
6935
  }
6903
6936
  await writeFile(hookPath, PRE_COMMIT_HOOK2);
6904
6937
  await chmod(hookPath, 493);
6905
- console.log(pc.green("\u2713") + " Installed pre-commit hook");
6906
- console.log(pc.dim(" SecurityChecks will run on staged files before each commit"));
6907
- console.log(pc.dim(" To skip: git commit --no-verify"));
6938
+ console.log(pc2.green("\u2713") + " Installed pre-commit hook");
6939
+ console.log(pc2.dim(" SecurityChecks will run on staged files before each commit"));
6940
+ console.log(pc2.dim(" To skip: git commit --no-verify"));
6908
6941
  return;
6909
6942
  }
6910
6943
  if (options.uninstall) {
6911
6944
  if (!existsSync(hookPath)) {
6912
- console.log(pc.yellow("\u26A0") + " No pre-commit hook found");
6945
+ console.log(pc2.yellow("\u26A0") + " No pre-commit hook found");
6913
6946
  return;
6914
6947
  }
6915
6948
  const hookContent = await readFile(hookPath, "utf-8");
6916
6949
  if (!hookContent.includes("SecurityChecks pre-commit hook")) {
6917
- console.log(pc.yellow("\u26A0") + " Pre-commit hook is not a SecurityChecks hook");
6918
- console.log(pc.dim(" Not removing to avoid breaking your existing hook"));
6950
+ console.log(pc2.yellow("\u26A0") + " Pre-commit hook is not a SecurityChecks hook");
6951
+ console.log(pc2.dim(" Not removing to avoid breaking your existing hook"));
6919
6952
  return;
6920
6953
  }
6921
6954
  await unlink(hookPath);
6922
- console.log(pc.green("\u2713") + " Uninstalled pre-commit hook");
6955
+ console.log(pc2.green("\u2713") + " Uninstalled pre-commit hook");
6923
6956
  return;
6924
6957
  }
6925
6958
  }
@@ -6991,20 +7024,20 @@ var VERDICT_MAP = {
6991
7024
  async function feedbackCommand(invariantIdOrFindingId, options) {
6992
7025
  const verdict = VERDICT_MAP[options.verdict];
6993
7026
  if (!verdict) {
6994
- console.error(pc.red(`Error: Invalid verdict "${options.verdict}"`));
6995
- console.log(pc.dim("Valid values: tp, fp, true_positive, false_positive"));
7027
+ console.error(pc2.red(`Error: Invalid verdict "${options.verdict}"`));
7028
+ console.log(pc2.dim("Valid values: tp, fp, true_positive, false_positive"));
6996
7029
  process.exit(1);
6997
7030
  }
6998
7031
  if (options.reason && !isValidReason(options.reason)) {
6999
- console.error(pc.red(`Error: Invalid reason "${options.reason}"`));
7000
- console.log(pc.dim(`Valid reasons: ${VALID_REASONS.join(", ")}`));
7032
+ console.error(pc2.red(`Error: Invalid reason "${options.reason}"`));
7033
+ console.log(pc2.dim(`Valid reasons: ${VALID_REASONS.join(", ")}`));
7001
7034
  process.exit(1);
7002
7035
  }
7003
7036
  const reason = options.reason;
7004
7037
  const invariantId = invariantIdOrFindingId.includes(":") ? invariantIdOrFindingId.split(":").slice(0, -1).join(":") : invariantIdOrFindingId;
7005
7038
  if (isTelemetryDisabled()) {
7006
- console.log(pc.yellow("Telemetry is disabled. Feedback will not be sent."));
7007
- console.log(pc.dim("Unset SECURITYCHECKS_TELEMETRY=false or DO_NOT_TRACK=1 to enable."));
7039
+ console.log(pc2.yellow("Telemetry is disabled. Feedback will not be sent."));
7040
+ console.log(pc2.dim("Unset SECURITYCHECKS_TELEMETRY=false or DO_NOT_TRACK=1 to enable."));
7008
7041
  return;
7009
7042
  }
7010
7043
  const cloudConfig = await loadCloudConfig();
@@ -7023,8 +7056,8 @@ async function feedbackCommand(invariantIdOrFindingId, options) {
7023
7056
  endpoint,
7024
7057
  timeout: 5e3
7025
7058
  };
7026
- const clientVersion = "0.1.1-rc.1";
7027
- console.log(pc.dim("Sending feedback..."));
7059
+ const clientVersion = "0.2.0";
7060
+ console.log(pc2.dim("Sending feedback..."));
7028
7061
  const success2 = await reportFeedback(
7029
7062
  {
7030
7063
  invariantId,
@@ -7035,15 +7068,15 @@ async function feedbackCommand(invariantIdOrFindingId, options) {
7035
7068
  feedbackConfig
7036
7069
  );
7037
7070
  if (success2) {
7038
- const verdictLabel = verdict === "true_positive" ? pc.green("true positive") : pc.red("false positive");
7039
- console.log(pc.green("\u2713 Feedback recorded"));
7040
- console.log(` ${pc.dim("Invariant:")} ${invariantId}`);
7041
- console.log(` ${pc.dim("Verdict:")} ${verdictLabel}`);
7071
+ const verdictLabel = verdict === "true_positive" ? pc2.green("true positive") : pc2.red("false positive");
7072
+ console.log(pc2.green("\u2713 Feedback recorded"));
7073
+ console.log(` ${pc2.dim("Invariant:")} ${invariantId}`);
7074
+ console.log(` ${pc2.dim("Verdict:")} ${verdictLabel}`);
7042
7075
  if (reason) {
7043
- console.log(` ${pc.dim("Reason:")} ${reason}`);
7076
+ console.log(` ${pc2.dim("Reason:")} ${reason}`);
7044
7077
  }
7045
7078
  } else {
7046
- console.error(pc.red("Failed to send feedback. The API may be unreachable."));
7079
+ console.error(pc2.red("Failed to send feedback. The API may be unreachable."));
7047
7080
  process.exit(1);
7048
7081
  }
7049
7082
  }
@@ -7070,16 +7103,16 @@ async function listInvariantsCommand(options) {
7070
7103
  return;
7071
7104
  }
7072
7105
  if (category || severity) {
7073
- console.log(pc.dim(`Filters: ${category ? `category=${category}` : ""}${category && severity ? ", " : ""}${severity ? `severity=${severity}` : ""}`));
7106
+ console.log(pc2.dim(`Filters: ${category ? `category=${category}` : ""}${category && severity ? ", " : ""}${severity ? `severity=${severity}` : ""}`));
7074
7107
  console.log("");
7075
7108
  }
7076
7109
  for (const inv of invariants) {
7077
- const severityColor = inv.severity === "P0" ? pc.red : inv.severity === "P1" ? pc.yellow : pc.blue;
7110
+ const severityColor = inv.severity === "P0" ? pc2.red : inv.severity === "P1" ? pc2.yellow : pc2.blue;
7078
7111
  console.log(` ${severityColor(`[${inv.severity}]`)} ${inv.id}`);
7079
- console.log(pc.dim(` ${inv.name}`));
7112
+ console.log(pc2.dim(` ${inv.name}`));
7080
7113
  const question = getStaffQuestion(inv.id);
7081
7114
  if (question) {
7082
- console.log(pc.dim(` Staff asks: ${question}`));
7115
+ console.log(pc2.dim(` Staff asks: ${question}`));
7083
7116
  }
7084
7117
  }
7085
7118
  console.log("");
@@ -7087,13 +7120,117 @@ async function listInvariantsCommand(options) {
7087
7120
  async function generateTestCommand(invariantId, options) {
7088
7121
  const invariant = getInvariantById(invariantId);
7089
7122
  if (!invariant) {
7090
- console.error(pc.red(`Unknown invariant: ${invariantId}`));
7123
+ console.error(pc2.red(`Unknown invariant: ${invariantId}`));
7091
7124
  process.exit(1);
7092
7125
  }
7093
7126
  const framework = options.framework ?? "vitest";
7094
7127
  const test = generateTestSkeleton(invariant, framework, options.context);
7095
7128
  console.log(test);
7096
7129
  }
7130
+ var PREFLIGHT_INVARIANTS = [
7131
+ "AUTHZ.SERVICE_LAYER.ENFORCED",
7132
+ "WEBHOOK.IDEMPOTENT",
7133
+ "WEBHOOK.SIGNATURE.VERIFIED",
7134
+ "TRANSACTION.POST_COMMIT.SIDE_EFFECTS",
7135
+ "CONFIG.ENV_HARDCODED",
7136
+ "CONFIG.HEALTH_CHECK_MISSING",
7137
+ "CONFIG.ERROR_STACK_LEAK",
7138
+ "CONFIG.GRACEFUL_SHUTDOWN_MISSING",
7139
+ "SECRETS.HARDCODED",
7140
+ "DATAFLOW.UNTRUSTED.SQL_QUERY"
7141
+ ];
7142
+ var INVARIANT_LABELS = {
7143
+ "AUTHZ.SERVICE_LAYER.ENFORCED": "Auth at service layer",
7144
+ "WEBHOOK.IDEMPOTENT": "Webhook idempotency",
7145
+ "WEBHOOK.SIGNATURE.VERIFIED": "Webhook signature verification",
7146
+ "TRANSACTION.POST_COMMIT.SIDE_EFFECTS": "No side effects in transactions",
7147
+ "CONFIG.ENV_HARDCODED": "No hardcoded env values",
7148
+ "CONFIG.HEALTH_CHECK_MISSING": "Health endpoint exists",
7149
+ "CONFIG.ERROR_STACK_LEAK": "No stack traces leaked",
7150
+ "CONFIG.GRACEFUL_SHUTDOWN_MISSING": "Graceful shutdown",
7151
+ "SECRETS.HARDCODED": "No hardcoded secrets",
7152
+ "DATAFLOW.UNTRUSTED.SQL_QUERY": "No SQL injection"
7153
+ };
7154
+ async function preflightCommand(options) {
7155
+ if (!options.quiet) {
7156
+ console.log(pc2.bold("\nPreflight Check\n"));
7157
+ }
7158
+ try {
7159
+ const result = await audit({
7160
+ targetPath: options.path,
7161
+ only: PREFLIGHT_INVARIANTS
7162
+ });
7163
+ const readiness = computeReadinessScore(result.summary);
7164
+ const failedInvariants = /* @__PURE__ */ new Map();
7165
+ for (const r of result.results) {
7166
+ if (!r.passed) {
7167
+ const evidence = r.findings.map((f) => ({
7168
+ file: f.evidence?.[0]?.file ?? "unknown",
7169
+ line: f.evidence?.[0]?.line ?? 0,
7170
+ message: f.message
7171
+ }));
7172
+ failedInvariants.set(r.invariantId, evidence);
7173
+ }
7174
+ }
7175
+ if (options.json) {
7176
+ const jsonOutput = {
7177
+ passed: readiness.passed,
7178
+ failed: readiness.failed,
7179
+ total: readiness.total,
7180
+ score: readiness.score,
7181
+ grade: readiness.grade,
7182
+ hasP0: readiness.hasP0,
7183
+ checks: PREFLIGHT_INVARIANTS.map((id) => ({
7184
+ invariantId: id,
7185
+ label: INVARIANT_LABELS[id] ?? id,
7186
+ passed: !failedInvariants.has(id),
7187
+ findings: failedInvariants.get(id) ?? []
7188
+ }))
7189
+ };
7190
+ console.log(JSON.stringify(jsonOutput, null, 2));
7191
+ return;
7192
+ }
7193
+ if (options.quiet) {
7194
+ if (readiness.failed > 0) {
7195
+ process.exit(1);
7196
+ }
7197
+ return;
7198
+ }
7199
+ console.log(`Preflight Check \u2014 ${readiness.passed}/${readiness.total} passed
7200
+ `);
7201
+ for (const id of PREFLIGHT_INVARIANTS) {
7202
+ const label = INVARIANT_LABELS[id] ?? id;
7203
+ const evidence = failedInvariants.get(id);
7204
+ if (evidence) {
7205
+ console.log(pc2.red(` \u2717 ${label}`));
7206
+ for (const e of evidence) {
7207
+ console.log(pc2.dim(` \u2192 ${e.file}:${e.line} \u2014 ${e.message}`));
7208
+ }
7209
+ } else {
7210
+ console.log(pc2.green(` \u2713 ${label}`));
7211
+ }
7212
+ }
7213
+ console.log("");
7214
+ console.log(` ${formatScoreForCli(readiness)}`);
7215
+ if (readiness.failed > 0) {
7216
+ const issueWord = readiness.failed === 1 ? "issue" : "issues";
7217
+ console.log(` ${readiness.failed} ${issueWord} to fix before deploy.`);
7218
+ } else {
7219
+ console.log(pc2.green(" Ready to deploy."));
7220
+ }
7221
+ console.log("");
7222
+ } catch (error2) {
7223
+ const message = error2 instanceof Error ? error2.message : String(error2);
7224
+ if (options.json) {
7225
+ console.error(JSON.stringify({ error: message }, null, 2));
7226
+ } else {
7227
+ console.error(pc2.red(`
7228
+ Preflight failed: ${message}
7229
+ `));
7230
+ }
7231
+ process.exit(1);
7232
+ }
7233
+ }
7097
7234
  var SENSITIVE_PATTERNS = [
7098
7235
  /api[_-]?key/i,
7099
7236
  /secret/i,
@@ -7132,20 +7269,20 @@ var Logger = class {
7132
7269
  */
7133
7270
  step(step2, total, message) {
7134
7271
  if (this.silent) return;
7135
- console.log(pc.dim(`[${step2}/${total}]`), message);
7272
+ console.log(pc2.dim(`[${step2}/${total}]`), message);
7136
7273
  }
7137
7274
  /**
7138
7275
  * Log a success message
7139
7276
  */
7140
7277
  success(message) {
7141
7278
  if (this.silent) return;
7142
- console.log(pc.green("\u2713"), message);
7279
+ console.log(pc2.green("\u2713"), message);
7143
7280
  }
7144
7281
  /**
7145
7282
  * Log a failure message
7146
7283
  */
7147
7284
  fail(message) {
7148
- console.log(pc.red("\u2717"), message);
7285
+ console.log(pc2.red("\u2717"), message);
7149
7286
  }
7150
7287
  log(level, message, data) {
7151
7288
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
@@ -7169,19 +7306,19 @@ var Logger = class {
7169
7306
  console.log(formattedMessage);
7170
7307
  }
7171
7308
  if (this.verbose && redactedData) {
7172
- console.log(pc.dim(JSON.stringify(redactedData, null, 2)));
7309
+ console.log(pc2.dim(JSON.stringify(redactedData, null, 2)));
7173
7310
  }
7174
7311
  }
7175
7312
  getPrefix(level) {
7176
7313
  switch (level) {
7177
7314
  case "debug":
7178
- return pc.dim("[DEBUG]");
7315
+ return pc2.dim("[DEBUG]");
7179
7316
  case "info":
7180
- return pc.blue("[INFO]");
7317
+ return pc2.blue("[INFO]");
7181
7318
  case "warn":
7182
- return pc.yellow("[WARN]");
7319
+ return pc2.yellow("[WARN]");
7183
7320
  case "error":
7184
- return pc.red("[ERROR]");
7321
+ return pc2.red("[ERROR]");
7185
7322
  }
7186
7323
  }
7187
7324
  /**
@@ -7225,7 +7362,7 @@ logger.success.bind(logger);
7225
7362
  logger.fail.bind(logger);
7226
7363
 
7227
7364
  // src/index.ts
7228
- var version = "0.1.1-rc.1";
7365
+ var version = "0.2.0";
7229
7366
  var program = new Command();
7230
7367
  program.name("scheck").description("Enforce backend invariants in your codebase").version(version);
7231
7368
  program.command("run").description("Scan for security invariants").option("-p, --path <path>", "Target path to audit (default: current directory)").option("-a, --artifact <path>", "Use pre-collected artifact from scc (skips collection)").option("--changed", "Only check changed files (requires git)").option("--ci", "CI mode - fail on new violations").option("--all", "Show all findings (don't stop early, include P2)").option("--include-p2", "Include P2 (medium) findings").option("--only <invariants...>", "Only run specific invariant checks").option("--skip <invariants...>", "Skip specific invariant checks").option("--json", "Output results as JSON").option("--sarif <path>", "Write SARIF report to file (for GitHub Code Scanning)").option("--quiet", "Suppress output except errors").option("-v, --verbose", "Enable verbose output").option("--calibrate", "Enable calibration API (default: enabled)").option("--offline", "Disable all API calls (not supported - shows error)").option("--calibration-endpoint <url>", "Override calibration API endpoint").option("--patterns", "Enable Pro Patterns fetching (default: enabled)").option("--no-patterns", "Disable Pro Patterns fetching").option("--pattern-endpoint <url>", "Override patterns API endpoint").option("--patterns-file <path>", "Load patterns from local JSON file (dev/testing)").option("-w, --watch", "Watch for file changes and re-run").option("--no-local-scan", "Skip local source-level pattern scanning").option("--no-usage-banner", "Suppress periodic API usage reminders").action((options) => {
@@ -7239,6 +7376,10 @@ program.command("run").description("Scan for security invariants").option("-p, -
7239
7376
  program.command("explain <invariant>").description("Deep-dive on any invariant (why it matters, what good looks like)").action(explainCommand);
7240
7377
  program.command("list-invariants").description("List all invariants (optionally filtered)").option("--category <category>", "Filter by category (authz, webhooks, transactions, etc.)").option("--severity <severity>", "Filter by severity (P0, P1, P2)").option("--json", "Output as JSON").action((options) => listInvariantsCommand(options));
7241
7378
  program.command("generate-test <invariantId>").description("Generate a test skeleton proving an invariant is enforced").option("--framework <framework>", "Test framework: vitest, jest, playwright", "vitest").option("--context <context>", "Extra context to make the test more targeted").action((invariantId, options) => generateTestCommand(invariantId, options));
7379
+ program.command("preflight").description("Quick deployment readiness check (curated invariant subset)").option("-p, --path <path>", "Target path to audit (default: current directory)").option("--json", "Output results as JSON").option("--quiet", "Suppress output except errors (exit code only)").action((options) => {
7380
+ logger.configure({ silent: options.quiet, json: options.json });
7381
+ return preflightCommand(options);
7382
+ });
7242
7383
  program.command("init").description("Initialize SecurityChecks in a project").option("-p, --path <path>", "Target path (default: current directory)").option("--hooks", "Install git pre-commit hook to run checks before commits").action(initCommand);
7243
7384
  program.command("hooks").description("Manage git pre-commit hooks").option("-p, --path <path>", "Target path (default: current directory)").option("--install", "Install pre-commit hook").option("--uninstall", "Uninstall pre-commit hook").option("--show", "Show current hook status (default)").action(hooksCommand);
7244
7385
  program.command("baseline").description("Manage the baseline of known issues").option("-p, --path <path>", "Target path (default: current directory)").option("--update", "Update baseline with current findings").option("--show", "Show current baseline").option("--prune", "Remove stale entries not seen recently").option("--prune-days <days>", "Days before considering stale (default: 90)").option("--notes <notes>", "Notes to attach to new baseline entries").option("-y, --yes", "Skip confirmation prompt (for CI/automation)").action(baselineCommand);