eslint-plugin-ai-guard 1.2.4 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -414,19 +414,19 @@ function addIgnoresToLegacyConfig(existing) {
414
414
  function getConfigFilePath(configType, cwd = process.cwd()) {
415
415
  switch (configType) {
416
416
  case "flat-js":
417
- return import_path6.default.join(cwd, "eslint.config.js");
417
+ return import_path7.default.join(cwd, "eslint.config.js");
418
418
  case "flat-mjs":
419
- return import_path6.default.join(cwd, "eslint.config.mjs");
419
+ return import_path7.default.join(cwd, "eslint.config.mjs");
420
420
  case "flat-cjs":
421
- return import_path6.default.join(cwd, "eslint.config.cjs");
421
+ return import_path7.default.join(cwd, "eslint.config.cjs");
422
422
  case "eslintrc-js":
423
- return import_path6.default.join(cwd, ".eslintrc.js");
423
+ return import_path7.default.join(cwd, ".eslintrc.js");
424
424
  case "eslintrc-cjs":
425
- return import_path6.default.join(cwd, ".eslintrc.cjs");
425
+ return import_path7.default.join(cwd, ".eslintrc.cjs");
426
426
  case "eslintrc-json":
427
- return import_path6.default.join(cwd, ".eslintrc.json");
427
+ return import_path7.default.join(cwd, ".eslintrc.json");
428
428
  default:
429
- return import_path6.default.join(cwd, ".eslintrc.js");
429
+ return import_path7.default.join(cwd, ".eslintrc.js");
430
430
  }
431
431
  }
432
432
  function validateFlatConfigText(content) {
@@ -461,12 +461,12 @@ function validateLegacyConfigText(content) {
461
461
  }
462
462
  return problems;
463
463
  }
464
- var import_fs7, import_path6, AI_GUARD_MARKER, DEFAULT_FLAT_IGNORES, DEFAULT_LEGACY_IGNORES;
464
+ var import_fs7, import_path7, AI_GUARD_MARKER, DEFAULT_FLAT_IGNORES, DEFAULT_LEGACY_IGNORES;
465
465
  var init_config_manager = __esm({
466
466
  "cli/utils/config-manager.ts"() {
467
467
  "use strict";
468
468
  import_fs7 = __toESM(require("fs"));
469
- import_path6 = __toESM(require("path"));
469
+ import_path7 = __toESM(require("path"));
470
470
  AI_GUARD_MARKER = "eslint-plugin-ai-guard";
471
471
  DEFAULT_FLAT_IGNORES = [
472
472
  "node_modules/**",
@@ -490,7 +490,7 @@ var init_config_manager = __esm({
490
490
  // cli/index.ts
491
491
  var import_commander = require("commander");
492
492
  var import_fs12 = __toESM(require("fs"));
493
- var import_path14 = __toESM(require("path"));
493
+ var import_path15 = __toESM(require("path"));
494
494
 
495
495
  // cli/commands/run.ts
496
496
  var import_fs4 = __toESM(require("fs"));
@@ -1205,6 +1205,10 @@ async function runEslint(options) {
1205
1205
  };
1206
1206
  }
1207
1207
 
1208
+ // cli/utils/sarif.ts
1209
+ var import_crypto = require("crypto");
1210
+ var import_path3 = __toESM(require("path"));
1211
+
1208
1212
  // cli/utils/version.ts
1209
1213
  var import_module = require("module");
1210
1214
  var import_url2 = require("url");
@@ -1230,6 +1234,8 @@ function readVersion() {
1230
1234
  var PKG_VERSION = _version ?? (_version = readVersion());
1231
1235
 
1232
1236
  // cli/utils/sarif.ts
1237
+ var SARIF_AUTOMATION_ID = "ai-guard";
1238
+ var FINGERPRINT_KEY = "ai-guard/v1";
1233
1239
  var RULE_DOCS = {
1234
1240
  "ai-guard/no-empty-catch": { shortDesc: "Empty catch block silently swallows errors", tags: ["reliability"] },
1235
1241
  "ai-guard/no-broad-exception": { shortDesc: "Catching broad Exception/Error masks specific failures", tags: ["reliability"] },
@@ -1353,8 +1359,34 @@ function confidenceToRank(confidence) {
1353
1359
  return 30;
1354
1360
  }
1355
1361
  }
1356
- function filePathToUri(filePath) {
1357
- return filePath.replace(/\\/g, "/");
1362
+ function normalizeSarifPath(filePath, repoRoot) {
1363
+ let normalized = filePath;
1364
+ normalized = normalized.replace(/\\/g, "/");
1365
+ normalized = normalized.replace(/^[A-Za-z]:\//, "/");
1366
+ if (repoRoot && import_path3.default.isAbsolute(filePath)) {
1367
+ const posixRoot = repoRoot.replace(/\\/g, "/").replace(/^[A-Za-z]:\//, "/").replace(/\/$/, "");
1368
+ if (normalized.startsWith(posixRoot + "/")) {
1369
+ normalized = normalized.slice(posixRoot.length + 1);
1370
+ } else if (normalized.startsWith(posixRoot)) {
1371
+ normalized = normalized.slice(posixRoot.length);
1372
+ }
1373
+ }
1374
+ normalized = normalized.replace(/^\/+/, "");
1375
+ normalized = normalized.replace(/^\.\//, "");
1376
+ if (!normalized || normalized === ".") {
1377
+ normalized = filePath.replace(/\\/g, "/");
1378
+ }
1379
+ return normalized;
1380
+ }
1381
+ function generateStableFingerprint(normalizedUri, ruleId, startLine, message) {
1382
+ const normalizedMessage = message.trim().toLowerCase().replace(/\s+/g, " ");
1383
+ const input = [
1384
+ ruleId,
1385
+ normalizedUri.toLowerCase(),
1386
+ String(startLine),
1387
+ normalizedMessage
1388
+ ].join(":");
1389
+ return (0, import_crypto.createHash)("sha256").update(input, "utf8").digest("hex");
1358
1390
  }
1359
1391
  function collectUsedRuleIds(result) {
1360
1392
  const ids = /* @__PURE__ */ new Set();
@@ -1405,22 +1437,34 @@ function buildRuleDescriptors(ruleIds) {
1405
1437
  });
1406
1438
  });
1407
1439
  }
1408
- function buildSarifResult(issue, file) {
1440
+ function buildSarifResult(issue, file, repoRoot) {
1409
1441
  const confidence = ISSUE_CONFIDENCE[issue.ruleId];
1410
1442
  const asyncRiskType = ISSUE_ASYNC_RISK_TYPE[issue.ruleId];
1411
1443
  const category = ISSUE_CATEGORY[issue.ruleId];
1444
+ const sarifUri = normalizeSarifPath(file.filePath, repoRoot);
1445
+ const fingerprint = generateStableFingerprint(
1446
+ sarifUri,
1447
+ issue.ruleId,
1448
+ Math.max(1, issue.line),
1449
+ issue.message
1450
+ );
1412
1451
  return {
1413
1452
  ruleId: issue.ruleId,
1414
1453
  level: confidenceToSarifLevel(confidence),
1415
1454
  kind: confidence === "informational" ? "informational" : "fail",
1416
1455
  rank: confidenceToRank(confidence),
1417
1456
  message: { text: issue.message },
1457
+ // partialFingerprints: per-result hash for GitHub alert deduplication.
1458
+ // Without this, GitHub closes and reopens every alert on every scan run.
1459
+ partialFingerprints: {
1460
+ [FINGERPRINT_KEY]: fingerprint
1461
+ },
1418
1462
  locations: [
1419
1463
  {
1420
1464
  physicalLocation: {
1421
1465
  artifactLocation: {
1422
- uri: filePathToUri(file.filePath),
1423
- uriBaseId: "%SRCROOT%"
1466
+ // Repository-relative POSIX path — no uriBaseId
1467
+ uri: sarifUri
1424
1468
  },
1425
1469
  region: {
1426
1470
  startLine: Math.max(1, issue.line),
@@ -1515,13 +1559,13 @@ function normalizeSarifForGitHub(log2) {
1515
1559
  runs: normalizedRuns
1516
1560
  };
1517
1561
  }
1518
- function buildSarifLog(result, version = PKG_VERSION) {
1562
+ function buildSarifLog(result, version = PKG_VERSION, repoRoot) {
1519
1563
  const usedRuleIds = collectUsedRuleIds(result);
1520
1564
  const rules = buildRuleDescriptors(usedRuleIds);
1521
1565
  const sarifResults = [];
1522
1566
  for (const file of result.files) {
1523
1567
  for (const issue of file.issues) {
1524
- sarifResults.push(buildSarifResult(issue, file));
1568
+ sarifResults.push(buildSarifResult(issue, file, repoRoot));
1525
1569
  }
1526
1570
  }
1527
1571
  const log2 = {
@@ -1537,6 +1581,12 @@ function buildSarifLog(result, version = PKG_VERSION) {
1537
1581
  rules
1538
1582
  }
1539
1583
  },
1584
+ // automationDetails.id: stable tool identifier for GitHub Code Scanning.
1585
+ // Groups all ai-guard scan runs into one persistent analysis slot.
1586
+ // MUST remain "ai-guard" forever — changing this orphans all existing alerts.
1587
+ automationDetails: {
1588
+ id: SARIF_AUTOMATION_ID
1589
+ },
1540
1590
  results: sarifResults,
1541
1591
  properties: {
1542
1592
  preset: "recommended",
@@ -1553,7 +1603,19 @@ function buildSarifLog(result, version = PKG_VERSION) {
1553
1603
  function sarifToJson(log2) {
1554
1604
  return JSON.stringify(log2, null, 2);
1555
1605
  }
1556
- function buildSarifDebugInfo(result) {
1606
+ function debugSarifPath(filePath, repoRoot) {
1607
+ const normalizedUri = normalizeSarifPath(filePath, repoRoot);
1608
+ return {
1609
+ originalPath: filePath,
1610
+ normalizedUri,
1611
+ isRepositoryRelative: !import_path3.default.isAbsolute(normalizedUri) && !normalizedUri.startsWith("/"),
1612
+ hasLeadingSlash: normalizedUri.startsWith("/"),
1613
+ hasDriveLetter: /^[A-Za-z]:/.test(normalizedUri),
1614
+ hasBackslashes: normalizedUri.includes("\\"),
1615
+ hasLeadingDotSlash: normalizedUri.startsWith("./")
1616
+ };
1617
+ }
1618
+ function buildSarifDebugInfo(result, repoRoot) {
1557
1619
  const usedRuleIds = collectUsedRuleIds(result);
1558
1620
  const rulesEmitted = usedRuleIds.map((id) => {
1559
1621
  const meta = RULE_DOCS[id];
@@ -1575,9 +1637,22 @@ function buildSarifDebugInfo(result) {
1575
1637
  };
1576
1638
  });
1577
1639
  const resultsEmitted = [];
1640
+ const pathsDebug = [];
1641
+ const seenPaths = /* @__PURE__ */ new Set();
1578
1642
  for (const file of result.files) {
1643
+ if (!seenPaths.has(file.filePath)) {
1644
+ seenPaths.add(file.filePath);
1645
+ pathsDebug.push(debugSarifPath(file.filePath, repoRoot));
1646
+ }
1579
1647
  for (const issue of file.issues) {
1580
1648
  const confidence = ISSUE_CONFIDENCE[issue.ruleId];
1649
+ const normalizedUri = normalizeSarifPath(file.filePath, repoRoot);
1650
+ const fingerprint = generateStableFingerprint(
1651
+ normalizedUri,
1652
+ issue.ruleId,
1653
+ Math.max(1, issue.line),
1654
+ issue.message
1655
+ );
1581
1656
  resultsEmitted.push({
1582
1657
  ruleId: issue.ruleId,
1583
1658
  level: confidenceToSarifLevel(confidence),
@@ -1585,13 +1660,18 @@ function buildSarifDebugInfo(result) {
1585
1660
  securitySeverity: confidenceToSecuritySeverity(confidence),
1586
1661
  precision: confidenceToPrecision(confidence),
1587
1662
  filePath: file.filePath,
1588
- line: issue.line
1663
+ normalizedUri,
1664
+ line: issue.line,
1665
+ fingerprint
1589
1666
  });
1590
1667
  }
1591
1668
  }
1592
1669
  return {
1670
+ automationId: SARIF_AUTOMATION_ID,
1671
+ fingerprintKey: FINGERPRINT_KEY,
1593
1672
  rulesEmitted,
1594
1673
  resultsEmitted,
1674
+ pathsDebug,
1595
1675
  totalResults: result.files.reduce((n, f) => n + f.issues.length, 0),
1596
1676
  version: PKG_VERSION
1597
1677
  };
@@ -1824,7 +1904,7 @@ function writeGitHubOutputs(result, sarifPath) {
1824
1904
 
1825
1905
  // cli/utils/git-diff.ts
1826
1906
  var import_child_process = require("child_process");
1827
- var import_path3 = __toESM(require("path"));
1907
+ var import_path4 = __toESM(require("path"));
1828
1908
  var import_fs3 = __toESM(require("fs"));
1829
1909
  var DEFAULT_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"];
1830
1910
  var IGNORE_PREFIXES = [
@@ -1934,7 +2014,7 @@ function filterFiles(rawFiles, extensions, workingDirectory, cwd, debugInfo) {
1934
2014
  for (const rel of rawFiles) {
1935
2015
  const normalized = rel.replace(/\\/g, "/").trim();
1936
2016
  if (!normalized) continue;
1937
- const ext = import_path3.default.extname(normalized).toLowerCase();
2017
+ const ext = import_path4.default.extname(normalized).toLowerCase();
1938
2018
  if (!extensions.includes(ext)) {
1939
2019
  debugInfo.droppedByExtension++;
1940
2020
  continue;
@@ -1950,7 +2030,7 @@ function filterFiles(rawFiles, extensions, workingDirectory, cwd, debugInfo) {
1950
2030
  continue;
1951
2031
  }
1952
2032
  }
1953
- const abs = import_path3.default.resolve(cwd, normalized);
2033
+ const abs = import_path4.default.resolve(cwd, normalized);
1954
2034
  if (!import_fs3.default.existsSync(abs)) {
1955
2035
  debugInfo.droppedMissing++;
1956
2036
  continue;
@@ -2073,7 +2153,7 @@ function registerRunCommand(program2) {
2073
2153
  "--max-warnings <n>",
2074
2154
  "Fail with exit code 1 if warnings exceed this count",
2075
2155
  (value) => Number.parseInt(value, 10)
2076
- ).option("--verbose", "Show all issues \u2014 disable grouping of repeated warnings").option("--quiet", "Only show errors \u2014 suppress warnings and informational hints").option("--debug-timing", "Print per-phase timing diagnostics").option("--debug-sarif", "Print SARIF tag normalization trace \u2014 diagnose duplicate-tag schema issues").option("--sarif-output <file>", "Write SARIF to this file instead of stdout").action(async (opts) => {
2156
+ ).option("--verbose", "Show all issues \u2014 disable grouping of repeated warnings").option("--quiet", "Only show errors \u2014 suppress warnings and informational hints").option("--debug-timing", "Print per-phase timing diagnostics").option("--debug-sarif", "Print SARIF tag/level normalization trace \u2014 diagnose schema issues").option("--debug-sarif-paths", "Print path normalization trace \u2014 diagnose GitHub Code Scanning path resolution").option("--debug-sarif-persistence", "Print persistence identity trace \u2014 fingerprints, automationDetails, category").option("--sarif-output <file>", "Write SARIF to this file instead of stdout").action(async (opts) => {
2077
2157
  if (opts.maxWarnings !== void 0 && (!Number.isInteger(opts.maxWarnings) || opts.maxWarnings < 0)) {
2078
2158
  log.blank();
2079
2159
  log.error("--max-warnings must be a non-negative integer.");
@@ -2126,10 +2206,11 @@ function registerRunCommand(program2) {
2126
2206
  writeGitHubOutputs(result);
2127
2207
  }
2128
2208
  if (opts.sarif || opts.sarifOutput) {
2129
- const sarifLog = buildSarifLog(result);
2209
+ const repoRoot = process.env.GITHUB_WORKSPACE ?? void 0;
2210
+ const sarifLog = buildSarifLog(result, void 0, repoRoot);
2130
2211
  const sarifJson = sarifToJson(sarifLog);
2131
2212
  if (opts.debugSarif) {
2132
- const debugInfo = buildSarifDebugInfo(result);
2213
+ const debugInfo = buildSarifDebugInfo(result, repoRoot);
2133
2214
  process.stderr.write("\n[debug-sarif] SARIF GitHub Compatibility Trace\n");
2134
2215
  process.stderr.write("[debug-sarif] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
2135
2216
  process.stderr.write("[debug-sarif] Rules Emitted:\n");
@@ -2167,11 +2248,67 @@ function registerRunCommand(program2) {
2167
2248
  process.stderr.write(`[debug-sarif] security-severity: ${res.securitySeverity}
2168
2249
  `);
2169
2250
  process.stderr.write(`[debug-sarif] precision: ${res.precision}
2251
+ `);
2252
+ process.stderr.write(`[debug-sarif] artifact uri: ${res.normalizedUri}
2170
2253
  `);
2171
2254
  }
2172
2255
  }
2173
2256
  process.stderr.write("[debug-sarif] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n");
2174
2257
  }
2258
+ if (opts.debugSarifPaths) {
2259
+ const debugInfo = buildSarifDebugInfo(result, repoRoot);
2260
+ process.stderr.write("\n[debug-sarif-paths] Path Normalization Trace\n");
2261
+ process.stderr.write("[debug-sarif-paths] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
2262
+ process.stderr.write(`[debug-sarif-paths] GITHUB_WORKSPACE: ${repoRoot ?? "(not set \u2014 local run)"}
2263
+
2264
+ `);
2265
+ if (debugInfo.pathsDebug.length === 0) {
2266
+ process.stderr.write("[debug-sarif-paths] (no files in results)\n");
2267
+ } else {
2268
+ for (const p of debugInfo.pathsDebug) {
2269
+ process.stderr.write(`[debug-sarif-paths] original path: ${p.originalPath}
2270
+ `);
2271
+ process.stderr.write(`[debug-sarif-paths] artifact uri: ${p.normalizedUri}
2272
+ `);
2273
+ process.stderr.write(`[debug-sarif-paths] repo-relative: ${p.isRepositoryRelative ? "\u2705 yes" : "\u274C NO (GitHub may miss this)"}
2274
+ `);
2275
+ if (p.hasBackslashes) process.stderr.write("[debug-sarif-paths] \u26A0 had backslashes \u2014 converted\n");
2276
+ if (p.hasDriveLetter) process.stderr.write("[debug-sarif-paths] \u26A0 had drive letter \u2014 stripped\n");
2277
+ if (p.hasLeadingSlash) process.stderr.write("[debug-sarif-paths] \u26A0 had leading slash \u2014 stripped\n");
2278
+ if (p.hasLeadingDotSlash) process.stderr.write("[debug-sarif-paths] \u26A0 had leading ./ \u2014 stripped\n");
2279
+ process.stderr.write("\n");
2280
+ }
2281
+ }
2282
+ process.stderr.write("[debug-sarif-paths] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n");
2283
+ }
2284
+ if (opts.debugSarifPersistence) {
2285
+ const debugInfo = buildSarifDebugInfo(result, repoRoot);
2286
+ process.stderr.write("\n[debug-sarif-persistence] GitHub Persistence Identity Trace\n");
2287
+ process.stderr.write("[debug-sarif-persistence] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
2288
+ process.stderr.write(`[debug-sarif-persistence] automationDetails.id: ${debugInfo.automationId}
2289
+ `);
2290
+ process.stderr.write(`[debug-sarif-persistence] fingerprintKey: ${debugInfo.fingerprintKey}
2291
+ `);
2292
+ process.stderr.write(`[debug-sarif-persistence] GITHUB_WORKSPACE: ${repoRoot ?? "(not set)"}
2293
+ `);
2294
+ process.stderr.write(`[debug-sarif-persistence] totalResults: ${debugInfo.totalResults}
2295
+
2296
+ `);
2297
+ if (debugInfo.resultsEmitted.length === 0) {
2298
+ process.stderr.write("[debug-sarif-persistence] (no findings \u2014 no fingerprints to show)\n");
2299
+ } else {
2300
+ process.stderr.write("[debug-sarif-persistence] Fingerprints (stable identity per finding):\n");
2301
+ for (const res of debugInfo.resultsEmitted) {
2302
+ process.stderr.write(`[debug-sarif-persistence] \u2500 ${res.ruleId}
2303
+ `);
2304
+ process.stderr.write(`[debug-sarif-persistence] file: ${res.normalizedUri}:${res.line}
2305
+ `);
2306
+ process.stderr.write(`[debug-sarif-persistence] fingerprint: ${res.fingerprint}
2307
+ `);
2308
+ }
2309
+ }
2310
+ process.stderr.write("[debug-sarif-persistence] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n");
2311
+ }
2175
2312
  if (opts.sarifOutput) {
2176
2313
  import_fs4.default.writeFileSync(opts.sarifOutput, sarifJson, "utf-8");
2177
2314
  if (opts.debugSarif) {
@@ -2514,7 +2651,7 @@ function resolveExitCode(result, failOn, maxWarnings) {
2514
2651
  }
2515
2652
 
2516
2653
  // cli/commands/changed.ts
2517
- var import_path4 = __toESM(require("path"));
2654
+ var import_path5 = __toESM(require("path"));
2518
2655
  var import_fs5 = __toESM(require("fs"));
2519
2656
  var import_ora2 = __toESM(require("ora"));
2520
2657
  var import_chalk5 = __toESM(require("chalk"));
@@ -2523,7 +2660,7 @@ function registerChangedCommand(program2) {
2523
2660
  "--max-warnings <n>",
2524
2661
  "Fail if warnings exceed this count",
2525
2662
  (v) => Number.parseInt(v, 10)
2526
- ).option("--verbose", "Expand all findings (disable grouping)").option("--quiet", "Show errors only").option("--debug-timing", "Print per-phase timing diagnostics").option("--debug-git", "Print full git resolution trace (useful for diagnosing zero-file issues)").option("--debug-ci", "Print CI environment state (GITHUB_* vars, SARIF path, summary path)").option("--debug-sarif", "Print SARIF tag normalization trace \u2014 diagnose duplicate-tag schema issues").action(async (opts) => {
2663
+ ).option("--verbose", "Expand all findings (disable grouping)").option("--quiet", "Show errors only").option("--debug-timing", "Print per-phase timing diagnostics").option("--debug-git", "Print full git resolution trace (useful for diagnosing zero-file issues)").option("--debug-ci", "Print CI environment state (GITHUB_* vars, SARIF path, summary path)").option("--debug-sarif", "Print SARIF tag/level normalization trace \u2014 diagnose schema issues").option("--debug-sarif-paths", "Print path normalization trace \u2014 diagnose GitHub Code Scanning path resolution").option("--debug-sarif-persistence", "Print persistence identity trace \u2014 fingerprints, automationDetails, category").action(async (opts) => {
2527
2664
  const preset = opts.strict ? "strict" : opts.security ? "security" : "recommended";
2528
2665
  const inCI = isGitHubActions();
2529
2666
  const baseRef = opts.base ?? (opts.pr || inCI ? getGitHubBaseRef() : void 0);
@@ -2552,7 +2689,7 @@ function registerChangedCommand(program2) {
2552
2689
  });
2553
2690
  }
2554
2691
  const cwd = process.cwd();
2555
- const resolvedPath = import_path4.default.resolve(opts.path);
2692
+ const resolvedPath = import_path5.default.resolve(opts.path);
2556
2693
  const workingDirectory = opts.path !== "." ? opts.path : void 0;
2557
2694
  const spinner = opts.json || opts.sarif ? null : (0, import_ora2.default)({ text: import_chalk5.default.dim("Detecting changed files\u2026"), color: "cyan" }).start();
2558
2695
  const changedResult = getChangedFiles({
@@ -2633,10 +2770,11 @@ function handleResults(result, opts, preset, scanMode, changedFilesCount, inCI,
2633
2770
  emitGitHubAnnotations(result);
2634
2771
  }
2635
2772
  if (opts.sarif) {
2636
- const sarifLog = buildSarifLog(result);
2773
+ const repoRoot = process.env.GITHUB_WORKSPACE ?? void 0;
2774
+ const sarifLog = buildSarifLog(result, void 0, repoRoot);
2637
2775
  const sarifJson = sarifToJson(sarifLog);
2638
2776
  if (opts.debugSarif) {
2639
- const debugInfo = buildSarifDebugInfo(result);
2777
+ const debugInfo = buildSarifDebugInfo(result, repoRoot);
2640
2778
  process.stderr.write("\n[debug-sarif] SARIF GitHub Compatibility Trace\n");
2641
2779
  process.stderr.write("[debug-sarif] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
2642
2780
  process.stderr.write("[debug-sarif] Rules Emitted:\n");
@@ -2674,11 +2812,67 @@ function handleResults(result, opts, preset, scanMode, changedFilesCount, inCI,
2674
2812
  process.stderr.write(`[debug-sarif] security-severity: ${res.securitySeverity}
2675
2813
  `);
2676
2814
  process.stderr.write(`[debug-sarif] precision: ${res.precision}
2815
+ `);
2816
+ process.stderr.write(`[debug-sarif] artifact uri: ${res.normalizedUri}
2677
2817
  `);
2678
2818
  }
2679
2819
  }
2680
2820
  process.stderr.write("[debug-sarif] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n");
2681
2821
  }
2822
+ if (opts.debugSarifPaths) {
2823
+ const debugInfo = buildSarifDebugInfo(result, repoRoot);
2824
+ process.stderr.write("\n[debug-sarif-paths] Path Normalization Trace\n");
2825
+ process.stderr.write("[debug-sarif-paths] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
2826
+ process.stderr.write(`[debug-sarif-paths] GITHUB_WORKSPACE: ${repoRoot ?? "(not set \u2014 local run)"}
2827
+
2828
+ `);
2829
+ if (debugInfo.pathsDebug.length === 0) {
2830
+ process.stderr.write("[debug-sarif-paths] (no files in results)\n");
2831
+ } else {
2832
+ for (const p of debugInfo.pathsDebug) {
2833
+ process.stderr.write(`[debug-sarif-paths] original path: ${p.originalPath}
2834
+ `);
2835
+ process.stderr.write(`[debug-sarif-paths] artifact uri: ${p.normalizedUri}
2836
+ `);
2837
+ process.stderr.write(`[debug-sarif-paths] repo-relative: ${p.isRepositoryRelative ? "\u2705 yes" : "\u274C NO (GitHub may miss this)"}
2838
+ `);
2839
+ if (p.hasBackslashes) process.stderr.write("[debug-sarif-paths] \u26A0 had backslashes \u2014 converted\n");
2840
+ if (p.hasDriveLetter) process.stderr.write("[debug-sarif-paths] \u26A0 had drive letter \u2014 stripped\n");
2841
+ if (p.hasLeadingSlash) process.stderr.write("[debug-sarif-paths] \u26A0 had leading slash \u2014 stripped\n");
2842
+ if (p.hasLeadingDotSlash) process.stderr.write("[debug-sarif-paths] \u26A0 had leading ./ \u2014 stripped\n");
2843
+ process.stderr.write("\n");
2844
+ }
2845
+ }
2846
+ process.stderr.write("[debug-sarif-paths] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n");
2847
+ }
2848
+ if (opts.debugSarifPersistence) {
2849
+ const debugInfo = buildSarifDebugInfo(result, repoRoot);
2850
+ process.stderr.write("\n[debug-sarif-persistence] GitHub Persistence Identity Trace\n");
2851
+ process.stderr.write("[debug-sarif-persistence] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n");
2852
+ process.stderr.write(`[debug-sarif-persistence] automationDetails.id: ${debugInfo.automationId}
2853
+ `);
2854
+ process.stderr.write(`[debug-sarif-persistence] fingerprintKey: ${debugInfo.fingerprintKey}
2855
+ `);
2856
+ process.stderr.write(`[debug-sarif-persistence] GITHUB_WORKSPACE: ${repoRoot ?? "(not set)"}
2857
+ `);
2858
+ process.stderr.write(`[debug-sarif-persistence] totalResults: ${debugInfo.totalResults}
2859
+
2860
+ `);
2861
+ if (debugInfo.resultsEmitted.length === 0) {
2862
+ process.stderr.write("[debug-sarif-persistence] (no findings \u2014 no fingerprints to show)\n");
2863
+ } else {
2864
+ process.stderr.write("[debug-sarif-persistence] Fingerprints (stable identity per finding):\n");
2865
+ for (const res of debugInfo.resultsEmitted) {
2866
+ process.stderr.write(`[debug-sarif-persistence] \u2500 ${res.ruleId}
2867
+ `);
2868
+ process.stderr.write(`[debug-sarif-persistence] file: ${res.normalizedUri}:${res.line}
2869
+ `);
2870
+ process.stderr.write(`[debug-sarif-persistence] fingerprint: ${res.fingerprint}
2871
+ `);
2872
+ }
2873
+ }
2874
+ process.stderr.write("[debug-sarif-persistence] \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n");
2875
+ }
2682
2876
  if (resolvedSarifPath) {
2683
2877
  import_fs5.default.writeFileSync(resolvedSarifPath, sarifJson, "utf-8");
2684
2878
  if (opts.debugSarif) {
@@ -2814,12 +3008,12 @@ function getFailOnExitCode(result, failOn, maxWarnings) {
2814
3008
 
2815
3009
  // cli/commands/init.ts
2816
3010
  var import_chalk6 = __toESM(require("chalk"));
2817
- var import_path7 = __toESM(require("path"));
3011
+ var import_path8 = __toESM(require("path"));
2818
3012
  var import_fs8 = __toESM(require("fs"));
2819
3013
 
2820
3014
  // cli/utils/detector.ts
2821
3015
  var import_fs6 = __toESM(require("fs"));
2822
- var import_path5 = __toESM(require("path"));
3016
+ var import_path6 = __toESM(require("path"));
2823
3017
  var import_module2 = require("module");
2824
3018
  var FLAT_CONFIG_FILES = [
2825
3019
  { file: "eslint.config.js", type: "flat-js" },
@@ -2836,11 +3030,11 @@ var LEGACY_CONFIG_FILES = [
2836
3030
  var ALL_CONFIG_FILES = [...FLAT_CONFIG_FILES, ...LEGACY_CONFIG_FILES];
2837
3031
  function isPackageInstalled(pkgName, cwd = process.cwd()) {
2838
3032
  try {
2839
- const pkgPath = import_path5.default.join(cwd, "node_modules", pkgName, "package.json");
3033
+ const pkgPath = import_path6.default.join(cwd, "node_modules", pkgName, "package.json");
2840
3034
  if (import_fs6.default.existsSync(pkgPath)) {
2841
3035
  return true;
2842
3036
  }
2843
- const req = (0, import_module2.createRequire)(import_path5.default.join(cwd, "package.json"));
3037
+ const req = (0, import_module2.createRequire)(import_path6.default.join(cwd, "package.json"));
2844
3038
  req.resolve(`${pkgName}/package.json`);
2845
3039
  return true;
2846
3040
  } catch {
@@ -2849,8 +3043,8 @@ function isPackageInstalled(pkgName, cwd = process.cwd()) {
2849
3043
  }
2850
3044
  function getPackageVersion(pkgName, cwd = process.cwd()) {
2851
3045
  try {
2852
- const pkgPath = import_path5.default.join(cwd, "node_modules", pkgName, "package.json");
2853
- const resolvedPath = import_fs6.default.existsSync(pkgPath) ? pkgPath : (0, import_module2.createRequire)(import_path5.default.join(cwd, "package.json")).resolve(`${pkgName}/package.json`);
3046
+ const pkgPath = import_path6.default.join(cwd, "node_modules", pkgName, "package.json");
3047
+ const resolvedPath = import_fs6.default.existsSync(pkgPath) ? pkgPath : (0, import_module2.createRequire)(import_path6.default.join(cwd, "package.json")).resolve(`${pkgName}/package.json`);
2854
3048
  const raw = import_fs6.default.readFileSync(resolvedPath, "utf-8");
2855
3049
  const json = JSON.parse(raw);
2856
3050
  return json.version ?? null;
@@ -2867,7 +3061,7 @@ function detectConfigType(cwd = process.cwd()) {
2867
3061
  const found = [];
2868
3062
  let first = null;
2869
3063
  for (const { file, type } of ALL_CONFIG_FILES) {
2870
- const fullPath = import_path5.default.join(cwd, file);
3064
+ const fullPath = import_path6.default.join(cwd, file);
2871
3065
  if (import_fs6.default.existsSync(fullPath)) {
2872
3066
  found.push(fullPath);
2873
3067
  if (!first) first = { type, path: fullPath };
@@ -2947,7 +3141,7 @@ async function verifyConfigLoads(configPath, cwd) {
2947
3141
  overrideConfigFile: configPath
2948
3142
  });
2949
3143
  await eslint.lintText("", {
2950
- filePath: import_path7.default.join(cwd, "_ai_guard_probe_.js")
3144
+ filePath: import_path8.default.join(cwd, "_ai_guard_probe_.js")
2951
3145
  });
2952
3146
  return null;
2953
3147
  } catch (err) {
@@ -2983,7 +3177,7 @@ function registerInitCommand(program2) {
2983
3177
  log.warn("eslint-plugin-ai-guard not found");
2984
3178
  }
2985
3179
  if (env.configType !== "none") {
2986
- log.success(`ESLint config found: ${import_chalk6.default.white(import_path7.default.relative(cwd, env.configPath ?? env.configType))}`);
3180
+ log.success(`ESLint config found: ${import_chalk6.default.white(import_path8.default.relative(cwd, env.configPath ?? env.configType))}`);
2987
3181
  } else {
2988
3182
  log.info("No ESLint config found \u2014 will generate one");
2989
3183
  }
@@ -2992,7 +3186,7 @@ function registerInitCommand(program2) {
2992
3186
  log.blank();
2993
3187
  log.warn("Conflicting configs detected:");
2994
3188
  for (const p of env.allConfigPaths) {
2995
- log.print(` ${import_chalk6.default.yellow("\u2192")} ${import_chalk6.default.white(import_path7.default.relative(cwd, p))}`);
3189
+ log.print(` ${import_chalk6.default.yellow("\u2192")} ${import_chalk6.default.white(import_path8.default.relative(cwd, p))}`);
2996
3190
  }
2997
3191
  log.print(` ${import_chalk6.default.gray("Multiple config formats found \u2014 this can cause unpredictable behavior.")}`);
2998
3192
  }
@@ -3020,7 +3214,7 @@ function registerInitCommand(program2) {
3020
3214
  try {
3021
3215
  const { createRequire: createRequire3 } = await import("module");
3022
3216
  const { pathToFileURL: pathToFileURL2 } = await import("url");
3023
- const req = createRequire3(import_path7.default.join(cwd, "_probe.js"));
3217
+ const req = createRequire3(import_path8.default.join(cwd, "_probe.js"));
3024
3218
  const resolvedPath = req.resolve("eslint-plugin-ai-guard");
3025
3219
  const aiGuard = await import(pathToFileURL2(resolvedPath).href);
3026
3220
  const plugin = aiGuard.default || aiGuard;
@@ -3039,7 +3233,7 @@ function registerInitCommand(program2) {
3039
3233
  if (eslintNeedsFlat && existingIsLegacy) {
3040
3234
  log.section("Config Migration Required");
3041
3235
  log.warn(`ESLint v${env.eslintMajor} requires flat config, but a legacy config was found:`);
3042
- log.print(` ${import_chalk6.default.white(import_path7.default.relative(cwd, env.configPath))}`);
3236
+ log.print(` ${import_chalk6.default.white(import_path8.default.relative(cwd, env.configPath))}`);
3043
3237
  log.blank();
3044
3238
  log.print(` ${import_chalk6.default.bold("ai-guard will create a new flat config.")}`);
3045
3239
  log.blank();
@@ -3051,7 +3245,7 @@ function registerInitCommand(program2) {
3051
3245
  const raw = readConfig(env.configPath);
3052
3246
  const { content: fixed, changed } = removeNukeIgnore(raw);
3053
3247
  if (changed) {
3054
- log.warn(`Removed "**/*" ignore pattern from ${import_path7.default.relative(cwd, env.configPath)}`);
3248
+ log.warn(`Removed "**/*" ignore pattern from ${import_path8.default.relative(cwd, env.configPath)}`);
3055
3249
  if (!isDryRun) {
3056
3250
  backupConfig(env.configPath);
3057
3251
  writeConfig(env.configPath, fixed);
@@ -3063,7 +3257,7 @@ function registerInitCommand(program2) {
3063
3257
  }
3064
3258
  if (env.configType === "none" || eslintNeedsFlat && existingIsLegacy) {
3065
3259
  const targetName = useFlat ? "eslint.config.mjs" : ".eslintrc.js";
3066
- finalConfigPath = import_path7.default.join(cwd, targetName);
3260
+ finalConfigPath = import_path8.default.join(cwd, targetName);
3067
3261
  const content2 = useFlat ? generateFlatConfig(preset) : generateLegacyConfig(preset);
3068
3262
  if (isDryRun) {
3069
3263
  log.print(` ${import_chalk6.default.gray(`[dry-run] would create: ${targetName}`)}`);
@@ -3091,15 +3285,15 @@ function registerInitCommand(program2) {
3091
3285
  finalConfigPath = env.configPath;
3092
3286
  } else if (isDryRun) {
3093
3287
  finalConfigPath = env.configPath;
3094
- log.print(` ${import_chalk6.default.gray(`[dry-run] would patch: ${import_path7.default.relative(cwd, env.configPath)}`)}`);
3288
+ log.print(` ${import_chalk6.default.gray(`[dry-run] would patch: ${import_path8.default.relative(cwd, env.configPath)}`)}`);
3095
3289
  } else {
3096
3290
  const bak = backupConfig(env.configPath);
3097
- log.info(`Backed up \u2192 ${import_chalk6.default.gray(import_path7.default.relative(cwd, bak))}`);
3291
+ log.info(`Backed up \u2192 ${import_chalk6.default.gray(import_path8.default.relative(cwd, bak))}`);
3098
3292
  writeConfig(env.configPath, patched);
3099
3293
  if (isInvalid) {
3100
- log.success(`Detected invalid ai-guard config \u2014 repairing ${import_chalk6.default.white(import_path7.default.relative(cwd, env.configPath))}`);
3294
+ log.success(`Detected invalid ai-guard config \u2014 repairing ${import_chalk6.default.white(import_path8.default.relative(cwd, env.configPath))}`);
3101
3295
  } else {
3102
- log.success(`Patched ${import_chalk6.default.white(import_path7.default.relative(cwd, env.configPath))}`);
3296
+ log.success(`Patched ${import_chalk6.default.white(import_path8.default.relative(cwd, env.configPath))}`);
3103
3297
  }
3104
3298
  finalConfigPath = env.configPath;
3105
3299
  configWritten = true;
@@ -3112,12 +3306,12 @@ function registerInitCommand(program2) {
3112
3306
  finalConfigPath = env.configPath;
3113
3307
  } else if (isDryRun) {
3114
3308
  finalConfigPath = env.configPath;
3115
- log.print(` ${import_chalk6.default.gray(`[dry-run] would patch: ${import_path7.default.relative(cwd, env.configPath)}`)}`);
3309
+ log.print(` ${import_chalk6.default.gray(`[dry-run] would patch: ${import_path8.default.relative(cwd, env.configPath)}`)}`);
3116
3310
  } else {
3117
3311
  const bak = backupConfig(env.configPath);
3118
- log.info(`Backed up \u2192 ${import_chalk6.default.gray(import_path7.default.relative(cwd, bak))}`);
3312
+ log.info(`Backed up \u2192 ${import_chalk6.default.gray(import_path8.default.relative(cwd, bak))}`);
3119
3313
  writeConfig(env.configPath, patched);
3120
- log.success(`Patched ${import_chalk6.default.white(import_path7.default.relative(cwd, env.configPath))}`);
3314
+ log.success(`Patched ${import_chalk6.default.white(import_path8.default.relative(cwd, env.configPath))}`);
3121
3315
  finalConfigPath = env.configPath;
3122
3316
  configWritten = true;
3123
3317
  }
@@ -3133,11 +3327,11 @@ function registerInitCommand(program2) {
3133
3327
  cleanedUp = true;
3134
3328
  } catch (err) {
3135
3329
  const reason = err instanceof Error ? err.message : String(err);
3136
- log.warn(`Could not back up ${import_path7.default.relative(cwd, p)}: ${reason}`);
3330
+ log.warn(`Could not back up ${import_path8.default.relative(cwd, p)}: ${reason}`);
3137
3331
  }
3138
3332
  } else {
3139
3333
  cleanedUp = true;
3140
- log.print(` ${import_chalk6.default.gray(`[dry-run] would rename ${import_path7.default.relative(cwd, p)} to ${import_path7.default.basename(p)}.bak`)}`);
3334
+ log.print(` ${import_chalk6.default.gray(`[dry-run] would rename ${import_path8.default.relative(cwd, p)} to ${import_path8.default.basename(p)}.bak`)}`);
3141
3335
  }
3142
3336
  }
3143
3337
  }
@@ -3156,7 +3350,7 @@ function registerInitCommand(program2) {
3156
3350
  log.blank();
3157
3351
  log.section("Validation");
3158
3352
  if (!import_fs8.default.existsSync(finalConfigPath)) {
3159
- log.error(`Config file not found after write: ${import_path7.default.relative(cwd, finalConfigPath)}`);
3353
+ log.error(`Config file not found after write: ${import_path8.default.relative(cwd, finalConfigPath)}`);
3160
3354
  log.blank();
3161
3355
  log.print(` ${import_chalk6.default.bold("Problem:")} File write may have failed.`);
3162
3356
  log.print(` ${import_chalk6.default.bold("Fix:")} Check disk space and file permissions, then re-run.`);
@@ -3172,7 +3366,7 @@ function registerInitCommand(program2) {
3172
3366
  log.print(` ${import_chalk6.default.red("\u2716")} ${e}`);
3173
3367
  }
3174
3368
  } else {
3175
- log.success(`Config structure valid: ${import_chalk6.default.white(import_path7.default.relative(cwd, finalConfigPath))}`);
3369
+ log.success(`Config structure valid: ${import_chalk6.default.white(import_path8.default.relative(cwd, finalConfigPath))}`);
3176
3370
  }
3177
3371
  log.info("Verifying ESLint can load the config\u2026");
3178
3372
  const loadError = await verifyConfigLoads(finalConfigPath, cwd);
@@ -3196,7 +3390,7 @@ function registerInitCommand(program2) {
3196
3390
  }
3197
3391
  log.blank();
3198
3392
  log.print(` ${import_chalk6.default.bold("Fix:")}`);
3199
- log.print(` 1. Check the syntax in ${import_chalk6.default.cyan(import_path7.default.relative(cwd, finalConfigPath))}`);
3393
+ log.print(` 1. Check the syntax in ${import_chalk6.default.cyan(import_path8.default.relative(cwd, finalConfigPath))}`);
3200
3394
  log.print(` 2. Make sure ${import_chalk6.default.cyan("eslint-plugin-ai-guard")} is installed`);
3201
3395
  log.print(` 3. Run ${import_chalk6.default.cyan("ai-guard doctor")} for full diagnostics`);
3202
3396
  log.blank();
@@ -3218,7 +3412,7 @@ function registerInitCommand(program2) {
3218
3412
  if (loadError === null && !lingeringConflicts) {
3219
3413
  log.print(` ${import_chalk6.default.green("\u2714")} ${import_chalk6.default.bold.green("Configuration validated successfully")}`);
3220
3414
  }
3221
- log.print(` ${import_chalk6.default.green("\u2714")} ${import_chalk6.default.bold("ESLint config:")} ${import_chalk6.default.cyan(import_path7.default.relative(cwd, finalConfigPath))}`);
3415
+ log.print(` ${import_chalk6.default.green("\u2714")} ${import_chalk6.default.bold("ESLint config:")} ${import_chalk6.default.cyan(import_path8.default.relative(cwd, finalConfigPath))}`);
3222
3416
  log.print(` ${import_chalk6.default.green("\u2714")} ${import_chalk6.default.bold("Mode:")} ${import_chalk6.default.cyan(useFlat ? `Flat config (ESLint v${env.eslintMajor ?? 9})` : `Legacy config (ESLint v${env.eslintMajor ?? 8})`)}`);
3223
3417
  log.print(` ${import_chalk6.default.green("\u2714")} ${import_chalk6.default.bold("Preset:")} ${import_chalk6.default.cyan(preset)}`);
3224
3418
  log.blank();
@@ -3232,14 +3426,14 @@ function registerInitCommand(program2) {
3232
3426
 
3233
3427
  // cli/commands/doctor.ts
3234
3428
  var import_chalk7 = __toESM(require("chalk"));
3235
- var import_path8 = __toESM(require("path"));
3429
+ var import_path9 = __toESM(require("path"));
3236
3430
  init_config_manager();
3237
3431
  var import_meta2 = {};
3238
3432
  async function probeConfigLoad(configPath, cwd) {
3239
3433
  try {
3240
3434
  const { ESLint } = await import("eslint");
3241
3435
  const eslint = new ESLint({ cwd, overrideConfigFile: configPath });
3242
- await eslint.lintText("", { filePath: import_path8.default.join(cwd, "_probe_.js") });
3436
+ await eslint.lintText("", { filePath: import_path9.default.join(cwd, "_probe_.js") });
3243
3437
  return { ok: true, error: null };
3244
3438
  } catch (err) {
3245
3439
  const msg = err instanceof Error ? err.message : String(err);
@@ -3274,7 +3468,7 @@ function registerDoctorCommand(program2) {
3274
3468
  checks.push({
3275
3469
  label: "ESLint config present",
3276
3470
  pass: hasConfig,
3277
- detail: hasConfig ? `${import_path8.default.relative(cwd, env.configPath ?? "")} (${env.configType})` : "No eslint.config.* or .eslintrc.* found",
3471
+ detail: hasConfig ? `${import_path9.default.relative(cwd, env.configPath ?? "")} (${env.configType})` : "No eslint.config.* or .eslintrc.* found",
3278
3472
  fix: hasConfig ? void 0 : "ai-guard init",
3279
3473
  note: hasConfig ? void 0 : "This will generate the correct config format for your ESLint version"
3280
3474
  });
@@ -3306,7 +3500,7 @@ function registerDoctorCommand(program2) {
3306
3500
  checks.push({
3307
3501
  label: "Plugin wired in config",
3308
3502
  pass: pluginWired && !isInvalidConfig,
3309
- detail: readError ? `Could not read config: ${readError}` : isInvalidConfig ? "Invalid ai-guard config detected (wrong export usage)" : pluginWired ? "eslint-plugin-ai-guard referenced in config" : `Config exists but ai-guard plugin not found in ${import_path8.default.relative(cwd, env.configPath)}`,
3503
+ detail: readError ? `Could not read config: ${readError}` : isInvalidConfig ? "Invalid ai-guard config detected (wrong export usage)" : pluginWired ? "eslint-plugin-ai-guard referenced in config" : `Config exists but ai-guard plugin not found in ${import_path9.default.relative(cwd, env.configPath)}`,
3310
3504
  fix: isInvalidConfig || !pluginWired && !readError ? "ai-guard init (will patch/repair your existing config)" : void 0
3311
3505
  });
3312
3506
  }
@@ -3314,7 +3508,7 @@ function registerDoctorCommand(program2) {
3314
3508
  label: "No conflicting config files",
3315
3509
  pass: !env.hasConflictingConfigs,
3316
3510
  detail: env.hasConflictingConfigs ? `Conflicting config detected:
3317
- ${env.allConfigPaths.map((p) => ` ${import_path8.default.relative(cwd, p)}`).join("\n")}` : "Only one config format detected",
3511
+ ${env.allConfigPaths.map((p) => ` ${import_path9.default.relative(cwd, p)}`).join("\n")}` : "Only one config format detected",
3318
3512
  fix: env.hasConflictingConfigs ? "Remove or backup legacy config" : void 0,
3319
3513
  note: env.hasConflictingConfigs ? "ESLint v9 silently ignores .eslintrc.* when a flat config exists. This can cause confusion." : void 0
3320
3514
  });
@@ -3341,7 +3535,7 @@ ${env.allConfigPaths.map((p) => ` ${import_path8.default.relative(cwd, p)}
3341
3535
  label: "ESLint config loads without errors",
3342
3536
  pass: probe.ok,
3343
3537
  detail: probe.ok ? "ESLint loaded config successfully" : probe.error ?? "Unknown error",
3344
- fix: probe.ok ? void 0 : `Check syntax in ${import_path8.default.relative(cwd, env.configPath)}`,
3538
+ fix: probe.ok ? void 0 : `Check syntax in ${import_path9.default.relative(cwd, env.configPath)}`,
3345
3539
  note: probe.ok ? void 0 : "Run: ai-guard init --dry-run to preview a fresh config"
3346
3540
  });
3347
3541
  }
@@ -3350,7 +3544,7 @@ ${env.allConfigPaths.map((p) => ` ${import_path8.default.relative(cwd, p)}
3350
3544
  let utilsVersion = null;
3351
3545
  try {
3352
3546
  const { createRequire: createRequire3 } = await import("module");
3353
- const req = createRequire3(import_path8.default.join(cwd, "package.json"));
3547
+ const req = createRequire3(import_path9.default.join(cwd, "package.json"));
3354
3548
  const pkgPath = req.resolve("@typescript-eslint/utils/package.json");
3355
3549
  const pkg = JSON.parse(
3356
3550
  (await import("fs")).readFileSync(pkgPath, "utf-8")
@@ -3361,10 +3555,10 @@ ${env.allConfigPaths.map((p) => ` ${import_path8.default.relative(cwd, p)}
3361
3555
  }
3362
3556
  if (!utilsOk) {
3363
3557
  try {
3364
- const selfDir = import_path8.default.dirname(
3558
+ const selfDir = import_path9.default.dirname(
3365
3559
  new URL(import_meta2.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")
3366
3560
  );
3367
- const pkgPath = import_path8.default.join(selfDir, "../../node_modules/@typescript-eslint/utils/package.json");
3561
+ const pkgPath = import_path9.default.join(selfDir, "../../node_modules/@typescript-eslint/utils/package.json");
3368
3562
  if ((await import("fs")).existsSync(pkgPath)) {
3369
3563
  utilsOk = true;
3370
3564
  }
@@ -3384,7 +3578,7 @@ ${env.allConfigPaths.map((p) => ` ${import_path8.default.relative(cwd, p)}
3384
3578
  let parserVersion = null;
3385
3579
  try {
3386
3580
  const { createRequire: createRequire3 } = await import("module");
3387
- const req = createRequire3(import_path8.default.join(cwd, "package.json"));
3581
+ const req = createRequire3(import_path9.default.join(cwd, "package.json"));
3388
3582
  const pkgPath = req.resolve("@typescript-eslint/parser/package.json");
3389
3583
  const pkg = JSON.parse(
3390
3584
  (await import("fs")).readFileSync(pkgPath, "utf-8")
@@ -3472,7 +3666,7 @@ ${env.allConfigPaths.map((p) => ` ${import_path8.default.relative(cwd, p)}
3472
3666
  var import_prompts = require("@inquirer/prompts");
3473
3667
  var import_chalk8 = __toESM(require("chalk"));
3474
3668
  init_config_manager();
3475
- var import_path9 = __toESM(require("path"));
3669
+ var import_path10 = __toESM(require("path"));
3476
3670
  function registerPresetCommand(program2) {
3477
3671
  program2.command("preset").description("Interactively select and apply an ai-guard preset to your ESLint config").action(async () => {
3478
3672
  const cwd = process.cwd();
@@ -3500,14 +3694,14 @@ function registerPresetCommand(program2) {
3500
3694
  const env = detect(cwd);
3501
3695
  const useFlat = env.eslintMajor !== null && env.eslintMajor >= 9 || isFlat(env.configType);
3502
3696
  if (env.configType === "none") {
3503
- const configPath = useFlat ? import_path9.default.join(cwd, "eslint.config.mjs") : import_path9.default.join(cwd, ".eslintrc.js");
3697
+ const configPath = useFlat ? import_path10.default.join(cwd, "eslint.config.mjs") : import_path10.default.join(cwd, ".eslintrc.js");
3504
3698
  const content = useFlat ? generateFlatConfig(preset) : generateLegacyConfig(preset);
3505
3699
  writeConfig(configPath, content);
3506
- log.success(`Created ${import_chalk8.default.white(import_path9.default.relative(cwd, configPath))} with ${import_chalk8.default.cyan(preset)} preset`);
3700
+ log.success(`Created ${import_chalk8.default.white(import_path10.default.relative(cwd, configPath))} with ${import_chalk8.default.cyan(preset)} preset`);
3507
3701
  } else {
3508
3702
  const configPath = env.configPath;
3509
3703
  const backupPath = backupConfig(configPath);
3510
- log.info(`Backed up \u2192 ${import_chalk8.default.gray(import_path9.default.relative(cwd, backupPath))}`);
3704
+ log.info(`Backed up \u2192 ${import_chalk8.default.gray(import_path10.default.relative(cwd, backupPath))}`);
3511
3705
  const existing = readConfig(configPath);
3512
3706
  const withPlugin = isFlat(env.configType) ? patchFlatConfig(existing, preset) : patchLegacyConfig(existing, preset);
3513
3707
  const patched = isFlat(env.configType) ? switchFlatPreset(withPlugin, preset) : switchLegacyPreset(withPlugin, preset);
@@ -3515,7 +3709,7 @@ function registerPresetCommand(program2) {
3515
3709
  log.warn("No preset changes needed. Config already matches selected preset.");
3516
3710
  } else {
3517
3711
  writeConfig(configPath, patched);
3518
- log.success(`Updated ${import_chalk8.default.white(import_path9.default.relative(cwd, configPath))} to ${import_chalk8.default.cyan(preset)} preset`);
3712
+ log.success(`Updated ${import_chalk8.default.white(import_path10.default.relative(cwd, configPath))} to ${import_chalk8.default.cyan(preset)} preset`);
3519
3713
  }
3520
3714
  }
3521
3715
  log.blank();
@@ -3555,7 +3749,7 @@ function registerPresetCommand(program2) {
3555
3749
  // cli/commands/ignore.ts
3556
3750
  var import_chalk9 = __toESM(require("chalk"));
3557
3751
  init_config_manager();
3558
- var import_path10 = __toESM(require("path"));
3752
+ var import_path11 = __toESM(require("path"));
3559
3753
  var DEFAULT_IGNORES = [
3560
3754
  ".next/",
3561
3755
  "dist/",
@@ -3577,21 +3771,21 @@ function registerIgnoreCommand(program2) {
3577
3771
  const env = detect(cwd);
3578
3772
  if (env.configType === "none") {
3579
3773
  const useFlat = env.eslintMajor !== null && env.eslintMajor >= 9;
3580
- const configPath = useFlat ? import_path10.default.join(cwd, "eslint.config.mjs") : import_path10.default.join(cwd, ".eslintrc.js");
3774
+ const configPath = useFlat ? import_path11.default.join(cwd, "eslint.config.mjs") : import_path11.default.join(cwd, ".eslintrc.js");
3581
3775
  const content = useFlat ? generateFlatConfig("recommended") : generateLegacyConfig("recommended");
3582
3776
  writeConfig(configPath, content);
3583
- log.success(`No config found \u2014 generated ${import_chalk9.default.white(import_path10.default.relative(cwd, configPath))} with ignores included`);
3777
+ log.success(`No config found \u2014 generated ${import_chalk9.default.white(import_path11.default.relative(cwd, configPath))} with ignores included`);
3584
3778
  } else {
3585
3779
  const configPath = env.configPath;
3586
3780
  const backupPath = backupConfig(configPath);
3587
- log.info(`Backed up \u2192 ${import_chalk9.default.gray(import_path10.default.relative(cwd, backupPath))}`);
3781
+ log.info(`Backed up \u2192 ${import_chalk9.default.gray(import_path11.default.relative(cwd, backupPath))}`);
3588
3782
  const existing = readConfig(configPath);
3589
3783
  const patched = isFlat(env.configType) ? addIgnoresToFlatConfig(existing) : addIgnoresToLegacyConfig(existing);
3590
3784
  if (patched === existing) {
3591
3785
  log.warn("Ignore patterns already present in config \u2014 no changes made.");
3592
3786
  } else {
3593
3787
  writeConfig(configPath, patched);
3594
- log.success(`Patched ${import_chalk9.default.white(import_path10.default.relative(cwd, configPath))} with ignore patterns`);
3788
+ log.success(`Patched ${import_chalk9.default.white(import_path11.default.relative(cwd, configPath))} with ignore patterns`);
3595
3789
  }
3596
3790
  }
3597
3791
  log.blank();
@@ -3603,7 +3797,7 @@ function registerIgnoreCommand(program2) {
3603
3797
 
3604
3798
  // cli/commands/baseline.ts
3605
3799
  var import_fs9 = __toESM(require("fs"));
3606
- var import_path11 = __toESM(require("path"));
3800
+ var import_path12 = __toESM(require("path"));
3607
3801
  var import_chalk10 = __toESM(require("chalk"));
3608
3802
  var import_ora3 = __toESM(require("ora"));
3609
3803
  var BASELINE_FILE = ".ai-guard-baseline.json";
@@ -3624,12 +3818,12 @@ function saveBaseline(result, preset, mode, cwd) {
3624
3818
  totalIssues: result.totalIssues,
3625
3819
  entries
3626
3820
  };
3627
- const baselinePath = import_path11.default.join(cwd, BASELINE_FILE);
3821
+ const baselinePath = import_path12.default.join(cwd, BASELINE_FILE);
3628
3822
  import_fs9.default.writeFileSync(baselinePath, JSON.stringify(baseline, null, 2), "utf-8");
3629
3823
  return baselinePath;
3630
3824
  }
3631
3825
  function loadBaseline(cwd) {
3632
- const baselinePath = import_path11.default.join(cwd, BASELINE_FILE);
3826
+ const baselinePath = import_path12.default.join(cwd, BASELINE_FILE);
3633
3827
  if (!import_fs9.default.existsSync(baselinePath)) return null;
3634
3828
  try {
3635
3829
  const raw = import_fs9.default.readFileSync(baselinePath, "utf-8");
@@ -3716,7 +3910,7 @@ function registerBaselineCommand(program2) {
3716
3910
  }
3717
3911
  const baselinePath = saveBaseline(result, preset, mode, cwd);
3718
3912
  log.success(
3719
- `Baseline saved \u2192 ${import_chalk10.default.white(import_path11.default.relative(cwd, baselinePath))}`
3913
+ `Baseline saved \u2192 ${import_chalk10.default.white(import_path12.default.relative(cwd, baselinePath))}`
3720
3914
  );
3721
3915
  log.info(` ${result.totalIssues} issues recorded (${result.totalErrors} errors, ${result.totalWarnings} warnings)`);
3722
3916
  log.info(` Preset: ${import_chalk10.default.cyan(preset)}`);
@@ -3800,7 +3994,7 @@ function registerBaselineCommand(program2) {
3800
3994
 
3801
3995
  // cli/commands/init-context.ts
3802
3996
  var import_fs10 = __toESM(require("fs"));
3803
- var import_path12 = __toESM(require("path"));
3997
+ var import_path13 = __toESM(require("path"));
3804
3998
  var import_chalk11 = __toESM(require("chalk"));
3805
3999
  var import_prompts2 = require("@inquirer/prompts");
3806
4000
  var RULE_CATEGORIES = {
@@ -4164,13 +4358,13 @@ var AGENTS = [
4164
4358
  {
4165
4359
  id: "copilot",
4166
4360
  label: "GitHub Copilot",
4167
- filePath: import_path12.default.join(".github", "copilot-instructions.md"),
4361
+ filePath: import_path13.default.join(".github", "copilot-instructions.md"),
4168
4362
  generate: generateCopilotFile
4169
4363
  }
4170
4364
  ];
4171
4365
  function getPackageVersion2(cwd) {
4172
4366
  try {
4173
- const pkgPath = import_path12.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
4367
+ const pkgPath = import_path13.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
4174
4368
  if (import_fs10.default.existsSync(pkgPath)) {
4175
4369
  const pkg = JSON.parse(import_fs10.default.readFileSync(pkgPath, "utf-8"));
4176
4370
  if (pkg.version) return pkg.version;
@@ -4178,7 +4372,7 @@ function getPackageVersion2(cwd) {
4178
4372
  } catch {
4179
4373
  }
4180
4374
  try {
4181
- const pkgPath = import_path12.default.join(cwd, "package.json");
4375
+ const pkgPath = import_path13.default.join(cwd, "package.json");
4182
4376
  if (import_fs10.default.existsSync(pkgPath)) {
4183
4377
  const pkg = JSON.parse(import_fs10.default.readFileSync(pkgPath, "utf-8"));
4184
4378
  if (pkg.version) return pkg.version;
@@ -4247,7 +4441,7 @@ function registerInitContextCommand(program2) {
4247
4441
  log.blank();
4248
4442
  const results = [];
4249
4443
  for (const agent of selectedAgents) {
4250
- const fullPath = import_path12.default.join(cwd, agent.filePath);
4444
+ const fullPath = import_path13.default.join(cwd, agent.filePath);
4251
4445
  const exists = import_fs10.default.existsSync(fullPath);
4252
4446
  if (exists && !isForce && !isDryRun) {
4253
4447
  const overwrite = await (0, import_prompts2.confirm)({
@@ -4272,7 +4466,7 @@ function registerInitContextCommand(program2) {
4272
4466
  results.push({ file: agent.filePath, status: "dry-run" });
4273
4467
  continue;
4274
4468
  }
4275
- const parentDir = import_path12.default.dirname(fullPath);
4469
+ const parentDir = import_path13.default.dirname(fullPath);
4276
4470
  if (!import_fs10.default.existsSync(parentDir)) {
4277
4471
  import_fs10.default.mkdirSync(parentDir, { recursive: true });
4278
4472
  }
@@ -4311,7 +4505,7 @@ function registerInitContextCommand(program2) {
4311
4505
 
4312
4506
  // cli/commands/report.ts
4313
4507
  var import_fs11 = __toESM(require("fs"));
4314
- var import_path13 = __toESM(require("path"));
4508
+ var import_path14 = __toESM(require("path"));
4315
4509
  var import_ora4 = __toESM(require("ora"));
4316
4510
  var RULE_META = {
4317
4511
  "ai-guard/no-empty-catch": { description: "Empty catch blocks silently swallow errors", category: "Reliability" },
@@ -4335,18 +4529,18 @@ var RULE_META = {
4335
4529
  };
4336
4530
  function getProjectName(cwd) {
4337
4531
  try {
4338
- const pkgPath = import_path13.default.join(cwd, "package.json");
4532
+ const pkgPath = import_path14.default.join(cwd, "package.json");
4339
4533
  if (import_fs11.default.existsSync(pkgPath)) {
4340
4534
  const pkg = JSON.parse(import_fs11.default.readFileSync(pkgPath, "utf-8"));
4341
4535
  if (pkg.name) return pkg.name;
4342
4536
  }
4343
4537
  } catch {
4344
4538
  }
4345
- return import_path13.default.basename(cwd);
4539
+ return import_path14.default.basename(cwd);
4346
4540
  }
4347
4541
  function getPluginVersion(cwd) {
4348
4542
  try {
4349
- const pkgPath = import_path13.default.resolve(__dirname, "../../package.json");
4543
+ const pkgPath = import_path14.default.resolve(__dirname, "../../package.json");
4350
4544
  if (import_fs11.default.existsSync(pkgPath)) {
4351
4545
  const pkg = JSON.parse(import_fs11.default.readFileSync(pkgPath, "utf-8"));
4352
4546
  if (pkg.version) return pkg.version;
@@ -4354,7 +4548,7 @@ function getPluginVersion(cwd) {
4354
4548
  } catch {
4355
4549
  }
4356
4550
  try {
4357
- const pkgPath = import_path13.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
4551
+ const pkgPath = import_path14.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
4358
4552
  if (import_fs11.default.existsSync(pkgPath)) {
4359
4553
  const pkg = JSON.parse(import_fs11.default.readFileSync(pkgPath, "utf-8"));
4360
4554
  if (pkg.version) return pkg.version;
@@ -4733,7 +4927,7 @@ function registerReportCommand(program2) {
4733
4927
  log.banner("AI GUARD REPORT");
4734
4928
  log.blank();
4735
4929
  const html = generateHtml(reportData);
4736
- const outputPath = import_path13.default.resolve(cwd, opts.output);
4930
+ const outputPath = import_path14.default.resolve(cwd, opts.output);
4737
4931
  import_fs11.default.writeFileSync(outputPath, html, "utf-8");
4738
4932
  log.success(`Report saved: ${opts.output}`);
4739
4933
  log.info(
@@ -4767,7 +4961,7 @@ function registerReportCommand(program2) {
4767
4961
  // cli/index.ts
4768
4962
  function getVersion() {
4769
4963
  try {
4770
- const pkgPath = import_path14.default.resolve(__dirname, "../../package.json");
4964
+ const pkgPath = import_path15.default.resolve(__dirname, "../../package.json");
4771
4965
  const raw = import_fs12.default.readFileSync(pkgPath, "utf-8");
4772
4966
  const pkg = JSON.parse(raw);
4773
4967
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";