eslint-plugin-ai-guard 1.2.0 → 1.2.1

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
@@ -53,14 +53,14 @@ __export(config_manager_exports, {
53
53
  });
54
54
  function backupConfig(configPath) {
55
55
  const backupPath = `${configPath}.bak`;
56
- import_fs3.default.copyFileSync(configPath, backupPath);
56
+ import_fs6.default.copyFileSync(configPath, backupPath);
57
57
  return backupPath;
58
58
  }
59
59
  function readConfig(configPath) {
60
- return import_fs3.default.readFileSync(configPath, "utf-8");
60
+ return import_fs6.default.readFileSync(configPath, "utf-8");
61
61
  }
62
62
  function writeConfig(configPath, content) {
63
- import_fs3.default.writeFileSync(configPath, content, "utf-8");
63
+ import_fs6.default.writeFileSync(configPath, content, "utf-8");
64
64
  }
65
65
  function generateFlatConfig(preset) {
66
66
  return `import aiGuard from 'eslint-plugin-ai-guard';
@@ -266,19 +266,19 @@ function addIgnoresToLegacyConfig(existing) {
266
266
  function getConfigFilePath(configType, cwd = process.cwd()) {
267
267
  switch (configType) {
268
268
  case "flat-js":
269
- return import_path3.default.join(cwd, "eslint.config.js");
269
+ return import_path5.default.join(cwd, "eslint.config.js");
270
270
  case "flat-mjs":
271
- return import_path3.default.join(cwd, "eslint.config.mjs");
271
+ return import_path5.default.join(cwd, "eslint.config.mjs");
272
272
  case "flat-cjs":
273
- return import_path3.default.join(cwd, "eslint.config.cjs");
273
+ return import_path5.default.join(cwd, "eslint.config.cjs");
274
274
  case "eslintrc-js":
275
- return import_path3.default.join(cwd, ".eslintrc.js");
275
+ return import_path5.default.join(cwd, ".eslintrc.js");
276
276
  case "eslintrc-cjs":
277
- return import_path3.default.join(cwd, ".eslintrc.cjs");
277
+ return import_path5.default.join(cwd, ".eslintrc.cjs");
278
278
  case "eslintrc-json":
279
- return import_path3.default.join(cwd, ".eslintrc.json");
279
+ return import_path5.default.join(cwd, ".eslintrc.json");
280
280
  default:
281
- return import_path3.default.join(cwd, ".eslintrc.js");
281
+ return import_path5.default.join(cwd, ".eslintrc.js");
282
282
  }
283
283
  }
284
284
  function validateFlatConfigText(content) {
@@ -313,12 +313,12 @@ function validateLegacyConfigText(content) {
313
313
  }
314
314
  return problems;
315
315
  }
316
- var import_fs3, import_path3, AI_GUARD_MARKER, DEFAULT_FLAT_IGNORES, DEFAULT_LEGACY_IGNORES;
316
+ var import_fs6, import_path5, AI_GUARD_MARKER, DEFAULT_FLAT_IGNORES, DEFAULT_LEGACY_IGNORES;
317
317
  var init_config_manager = __esm({
318
318
  "cli/utils/config-manager.ts"() {
319
319
  "use strict";
320
- import_fs3 = __toESM(require("fs"));
321
- import_path3 = __toESM(require("path"));
320
+ import_fs6 = __toESM(require("fs"));
321
+ import_path5 = __toESM(require("path"));
322
322
  AI_GUARD_MARKER = "eslint-plugin-ai-guard";
323
323
  DEFAULT_FLAT_IGNORES = [
324
324
  "node_modules/**",
@@ -341,17 +341,18 @@ var init_config_manager = __esm({
341
341
 
342
342
  // cli/index.ts
343
343
  var import_commander = require("commander");
344
- var import_fs8 = __toESM(require("fs"));
345
- var import_path11 = __toESM(require("path"));
344
+ var import_fs11 = __toESM(require("fs"));
345
+ var import_path13 = __toESM(require("path"));
346
346
 
347
347
  // cli/commands/run.ts
348
348
  var import_ora = __toESM(require("ora"));
349
- var import_chalk2 = __toESM(require("chalk"));
349
+ var import_chalk3 = __toESM(require("chalk"));
350
350
 
351
351
  // cli/utils/eslint-runner.ts
352
352
  var import_fs = __toESM(require("fs"));
353
353
  var import_path = __toESM(require("path"));
354
354
  var import_url = require("url");
355
+ var import_chalk2 = __toESM(require("chalk"));
355
356
 
356
357
  // cli/utils/logger.ts
357
358
  var import_chalk = __toESM(require("chalk"));
@@ -396,14 +397,15 @@ var CONFIDENCE_TIER = {
396
397
  "ai-guard/no-catch-log-rethrow": "medium",
397
398
  "ai-guard/no-catch-without-use": "medium",
398
399
  "ai-guard/no-unsafe-deserialize": "medium",
399
- // Low confidence — suggestions, may be intentional
400
- "ai-guard/no-async-without-await": "low",
400
+ // Low confidence — review case-by-case
401
401
  "ai-guard/no-async-array-callback": "low",
402
402
  "ai-guard/no-dead-branch": "low",
403
403
  "ai-guard/no-broad-exception": "low",
404
404
  "ai-guard/no-console-in-handler": "low",
405
405
  "ai-guard/no-duplicate-logic-block": "low",
406
- "ai-guard/no-redundant-await": "low"
406
+ // Informational — mostly stylistic, high framework FP rate, collapsed by default
407
+ "ai-guard/no-async-without-await": "informational",
408
+ "ai-guard/no-redundant-await": "informational"
407
409
  };
408
410
  var ECOSYSTEM_FIX_HINTS = {
409
411
  "react-hooks/exhaustive-deps": "npm install --save-dev eslint-plugin-react-hooks",
@@ -541,6 +543,74 @@ var log = {
541
543
  };
542
544
 
543
545
  // cli/utils/eslint-runner.ts
546
+ var ISSUE_CONFIDENCE = {
547
+ "ai-guard/no-hardcoded-secret": "high",
548
+ "ai-guard/no-eval-dynamic": "high",
549
+ "ai-guard/no-floating-promise": "high",
550
+ "ai-guard/no-empty-catch": "high",
551
+ "ai-guard/no-sql-string-concat": "medium",
552
+ "ai-guard/no-await-in-loop": "medium",
553
+ "ai-guard/require-auth-middleware": "medium",
554
+ "ai-guard/require-authz-check": "medium",
555
+ "ai-guard/no-catch-log-rethrow": "medium",
556
+ "ai-guard/no-catch-without-use": "medium",
557
+ "ai-guard/no-unsafe-deserialize": "medium",
558
+ "ai-guard/no-async-array-callback": "low",
559
+ "ai-guard/no-dead-branch": "low",
560
+ "ai-guard/no-broad-exception": "low",
561
+ "ai-guard/no-console-in-handler": "low",
562
+ "ai-guard/no-duplicate-logic-block": "low",
563
+ "ai-guard/no-async-without-await": "informational",
564
+ "ai-guard/no-redundant-await": "informational"
565
+ };
566
+ var ISSUE_CATEGORY = {
567
+ "ai-guard/no-floating-promise": "Async Reliability",
568
+ "ai-guard/no-await-in-loop": "Async Reliability",
569
+ "ai-guard/no-async-without-await": "Async Reliability",
570
+ "ai-guard/no-async-array-callback": "Async Reliability",
571
+ "ai-guard/no-redundant-await": "Async Reliability",
572
+ "ai-guard/no-empty-catch": "Reliability",
573
+ "ai-guard/no-broad-exception": "Reliability",
574
+ "ai-guard/no-catch-log-rethrow": "Reliability",
575
+ "ai-guard/no-catch-without-use": "Reliability",
576
+ "ai-guard/no-hardcoded-secret": "Security",
577
+ "ai-guard/no-eval-dynamic": "Security",
578
+ "ai-guard/no-sql-string-concat": "Security",
579
+ "ai-guard/no-unsafe-deserialize": "Security",
580
+ "ai-guard/require-auth-middleware": "Security",
581
+ "ai-guard/require-authz-check": "Security",
582
+ "ai-guard/no-console-in-handler": "AI Patterns",
583
+ "ai-guard/no-duplicate-logic-block": "AI Patterns",
584
+ "ai-guard/no-dead-branch": "AI Patterns"
585
+ };
586
+ var ISSUE_ASYNC_RISK_TYPE = {
587
+ "ai-guard/no-floating-promise": "floating-promise",
588
+ "ai-guard/no-await-in-loop": "sequential-await",
589
+ "ai-guard/no-async-without-await": "unnecessary-async",
590
+ "ai-guard/no-async-array-callback": "async-array-callback",
591
+ "ai-guard/no-redundant-await": "redundant-await"
592
+ };
593
+ var ISSUE_REMEDIATION = {
594
+ "ai-guard/no-floating-promise": "Add `await` before the call, assign to a variable, or add `.catch()` to handle rejection.",
595
+ "ai-guard/no-await-in-loop": "Collect promises in an array and use `await Promise.all(...)` outside the loop.",
596
+ "ai-guard/no-async-without-await": "Remove `async` if no async operations are needed, or add an `await` expression.",
597
+ "ai-guard/no-async-array-callback": "Use `Promise.all(array.map(async item => ...))` to ensure all async callbacks are awaited.",
598
+ "ai-guard/no-redundant-await": "Remove the `await` \u2014 the value is already resolved.",
599
+ "ai-guard/no-empty-catch": "Add error handling in the catch block, or at minimum log the error.",
600
+ "ai-guard/no-broad-exception": "Catch specific error types or re-throw after handling.",
601
+ "ai-guard/no-catch-log-rethrow": "Either log the error OR rethrow it \u2014 doing both duplicates the error in logs.",
602
+ "ai-guard/no-catch-without-use": "Use the caught error variable or rename to `_` to signal intentional suppression.",
603
+ "ai-guard/no-hardcoded-secret": "Move credentials to environment variables: `process.env.SECRET_KEY`.",
604
+ "ai-guard/no-eval-dynamic": "Avoid eval and Function constructors. Use safer alternatives like JSON.parse.",
605
+ "ai-guard/no-sql-string-concat": "Use parameterized queries or an ORM to prevent SQL injection.",
606
+ "ai-guard/no-unsafe-deserialize": "Validate input with a schema library (zod, joi, yup) before parsing.",
607
+ "ai-guard/require-auth-middleware": "Add an authentication middleware before the route handler.",
608
+ "ai-guard/require-authz-check": "Verify the requesting user has permission to access this resource.",
609
+ "ai-guard/no-console-in-handler": "Replace console.log with a structured logger (pino, winston, etc.).",
610
+ "ai-guard/no-duplicate-logic-block": "Extract the duplicated logic into a shared function.",
611
+ "ai-guard/no-dead-branch": "Remove the unreachable branch or fix the condition."
612
+ };
613
+ var _runnerCache = /* @__PURE__ */ new Map();
544
614
  function isRecord(value) {
545
615
  return typeof value === "object" && value !== null;
546
616
  }
@@ -612,13 +682,40 @@ function getRules(preset) {
612
682
  return RECOMMENDED_RULES;
613
683
  }
614
684
  var DEFAULT_IGNORE_PATTERNS = [
685
+ // Core — always skip
615
686
  "**/node_modules/**",
616
687
  "**/.next/**",
617
688
  "**/dist/**",
618
689
  "**/build/**",
619
690
  "**/coverage/**",
620
691
  "**/out/**",
621
- "**/.git/**"
692
+ "**/.git/**",
693
+ // Build artifacts and caches
694
+ "**/.cache/**",
695
+ "**/.turbo/**",
696
+ "**/.nx/**",
697
+ "**/.parcel-cache/**",
698
+ "**/.webpack/**",
699
+ "**/tmp/**",
700
+ "**/temp/**",
701
+ // Package manager artifacts
702
+ "**/.yarn/**",
703
+ "**/.pnp.*",
704
+ // Framework generated
705
+ "**/.expo/**",
706
+ "**/.svelte-kit/**",
707
+ "**/storybook-static/**",
708
+ "**/.storybook/generated/**",
709
+ // Generated code
710
+ "**/generated/**",
711
+ "**/__generated__/**",
712
+ "**/vendor/**",
713
+ // Test outputs
714
+ "**/test-results/**",
715
+ "**/playwright-report/**",
716
+ // Misc
717
+ "**/.docusaurus/**",
718
+ "**/.vitepress/cache/**"
622
719
  ];
623
720
  function isSkippablePatternError(error) {
624
721
  const msg = error.message.toLowerCase();
@@ -669,50 +766,44 @@ async function loadPluginModuleFromCwd(cwd) {
669
766
  "eslint-plugin-ai-guard is not installed. Run: npm install --save-dev eslint-plugin-ai-guard"
670
767
  );
671
768
  }
672
- async function runEslint(options) {
673
- const overallStart = Date.now();
674
- const timing = {
675
- pluginLoadMs: 0,
676
- parserLoadMs: 0,
677
- lintMs: 0,
678
- processMs: 0
679
- };
680
- const { ESLint } = await import("eslint").catch(() => {
681
- throw new Error(
682
- "ESLint is not installed. Run: npm install --save-dev eslint"
683
- );
684
- });
685
- const pluginStart = Date.now();
686
- const rawPlugin = await loadPluginModuleFromCwd(process.cwd());
687
- const plugin = normalizePlugin(rawPlugin);
688
- timing.pluginLoadMs = Date.now() - pluginStart;
689
- const rules = getRules(options.preset);
690
- const resolvedTargetPath = import_path.default.resolve(options.targetPath);
691
- if (!import_fs.default.existsSync(resolvedTargetPath)) {
692
- throw new Error(`Path not found: ${options.targetPath}`);
693
- }
694
- const targetStat = import_fs.default.statSync(resolvedTargetPath);
695
- const isSingleFileTarget = targetStat.isFile();
696
- const eslintCwd = isSingleFileTarget ? import_path.default.dirname(resolvedTargetPath) : resolvedTargetPath;
697
- const parserStart = Date.now();
698
- let tsParser = null;
699
- let tsParserAvailable = false;
700
- try {
701
- try {
702
- tsParser = require(import_path.default.join(
703
- process.cwd(),
704
- "node_modules",
705
- "@typescript-eslint",
706
- "parser"
707
- ));
708
- } catch {
709
- tsParser = require("@typescript-eslint/parser");
710
- }
711
- tsParserAvailable = true;
712
- } catch {
713
- log.debug("@typescript-eslint/parser not found \u2014 TypeScript files will use espree fallback");
769
+ async function getOrBuildRunner(cwd, preset, eslintCwd) {
770
+ const cacheKey = `${eslintCwd}::${preset}`;
771
+ const cached = _runnerCache.get(cacheKey);
772
+ if (cached) {
773
+ return { runner: cached, fromCache: true };
714
774
  }
715
- timing.parserLoadMs = Date.now() - parserStart;
775
+ const [{ ESLint }, rawPlugin, tsParserResult] = await Promise.all([
776
+ // 1. Load ESLint
777
+ import("eslint").catch(() => {
778
+ throw new Error("ESLint is not installed. Run: npm install --save-dev eslint");
779
+ }),
780
+ // 2. Load plugin
781
+ loadPluginModuleFromCwd(cwd),
782
+ // 3. Load TS parser (optional — errors are caught)
783
+ Promise.resolve().then(() => {
784
+ const parserStart = Date.now();
785
+ try {
786
+ let tsParser2;
787
+ try {
788
+ tsParser2 = require(import_path.default.join(
789
+ cwd,
790
+ "node_modules",
791
+ "@typescript-eslint",
792
+ "parser"
793
+ ));
794
+ } catch {
795
+ tsParser2 = require("@typescript-eslint/parser");
796
+ }
797
+ return { tsParser: tsParser2, available: true, loadMs: Date.now() - parserStart };
798
+ } catch {
799
+ log.debug("@typescript-eslint/parser not found \u2014 TypeScript files will use espree fallback");
800
+ return { tsParser: null, available: false, loadMs: Date.now() - parserStart };
801
+ }
802
+ })
803
+ ]);
804
+ const plugin = normalizePlugin(rawPlugin);
805
+ const rules = getRules(preset);
806
+ const { tsParser, available: tsParserAvailable } = tsParserResult;
716
807
  const JS_TS_FILES = [
717
808
  "**/*.js",
718
809
  "**/*.jsx",
@@ -724,7 +815,9 @@ async function runEslint(options) {
724
815
  "**/*.cjs"
725
816
  ];
726
817
  const configBlocks = [
727
- // JS/JSX files — default espree parser with JSX support
818
+ // Ignore block first — ESLint can skip excluded files without processing
819
+ { ignores: DEFAULT_IGNORE_PATTERNS },
820
+ // JS/JSX — espree parser with JSX support
728
821
  {
729
822
  files: ["**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs"],
730
823
  plugins: { "ai-guard": plugin },
@@ -736,12 +829,10 @@ async function runEslint(options) {
736
829
  }
737
830
  },
738
831
  rules
739
- },
740
- // Ignore generated directories
741
- { ignores: DEFAULT_IGNORE_PATTERNS }
832
+ }
742
833
  ];
743
834
  if (tsParser) {
744
- configBlocks.splice(1, 0, {
835
+ configBlocks.push({
745
836
  files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
746
837
  plugins: { "ai-guard": plugin },
747
838
  languageOptions: {
@@ -755,7 +846,7 @@ async function runEslint(options) {
755
846
  rules
756
847
  });
757
848
  } else {
758
- configBlocks.splice(1, 0, {
849
+ configBlocks.push({
759
850
  files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
760
851
  plugins: { "ai-guard": plugin },
761
852
  languageOptions: {
@@ -773,16 +864,58 @@ async function runEslint(options) {
773
864
  overrideConfigFile: true,
774
865
  overrideConfig: configBlocks
775
866
  });
776
- const patterns = isSingleFileTarget ? [import_path.default.basename(resolvedTargetPath)] : JS_TS_FILES;
867
+ const runner = {
868
+ eslint,
869
+ tsParserAvailable,
870
+ pluginLoadMs: 0,
871
+ // accounted in parallel Promise.all
872
+ parserLoadMs: tsParserResult.loadMs
873
+ };
874
+ _runnerCache.set(cacheKey, runner);
875
+ return { runner, fromCache: false };
876
+ }
877
+ var JS_TS_FILE_PATTERNS = [
878
+ "**/*.js",
879
+ "**/*.jsx",
880
+ "**/*.ts",
881
+ "**/*.tsx",
882
+ "**/*.mts",
883
+ "**/*.cts",
884
+ "**/*.mjs",
885
+ "**/*.cjs"
886
+ ];
887
+ async function runEslint(options) {
888
+ const overallStart = Date.now();
889
+ const resolvedTargetPath = import_path.default.resolve(options.targetPath);
890
+ if (!import_fs.default.existsSync(resolvedTargetPath)) {
891
+ throw new Error(`Path not found: ${options.targetPath}`);
892
+ }
893
+ const targetStat = import_fs.default.statSync(resolvedTargetPath);
894
+ const isSingleFileTarget = targetStat.isFile();
895
+ const eslintCwd = isSingleFileTarget ? import_path.default.dirname(resolvedTargetPath) : resolvedTargetPath;
896
+ const cwd = process.cwd();
897
+ const initStart = Date.now();
898
+ const { runner, fromCache } = await getOrBuildRunner(cwd, options.preset, eslintCwd);
899
+ const initMs = Date.now() - initStart;
900
+ const timing = {
901
+ pluginLoadMs: fromCache ? 0 : runner.pluginLoadMs,
902
+ parserLoadMs: fromCache ? 0 : runner.parserLoadMs,
903
+ lintMs: 0,
904
+ processMs: 0,
905
+ fromCache
906
+ };
907
+ const hasExplicitFiles = options.files && options.files.length > 0;
908
+ const patterns = hasExplicitFiles ? options.files : isSingleFileTarget ? [import_path.default.basename(resolvedTargetPath)] : JS_TS_FILE_PATTERNS;
777
909
  const lintStart = Date.now();
778
910
  let rawResults;
911
+ const eslint = runner.eslint;
779
912
  try {
780
913
  rawResults = await eslint.lintFiles(patterns);
781
914
  } catch (err) {
782
915
  const error = err instanceof Error ? err : new Error(String(err));
783
916
  if (isSkippablePatternError(error)) {
784
- log.debug("Single-call lintFiles failed with no-files, falling back to per-pattern");
785
- const perPatternResults = await Promise.all(
917
+ log.debug("Single-call lintFiles failed, falling back to per-pattern");
918
+ const perPatternResults = await Promise.allSettled(
786
919
  patterns.map(async (pattern) => {
787
920
  try {
788
921
  return await eslint.lintFiles([pattern]);
@@ -796,8 +929,10 @@ async function runEslint(options) {
796
929
  }
797
930
  })
798
931
  );
799
- rawResults = perPatternResults.flat();
932
+ rawResults = perPatternResults.filter((r) => r.status === "fulfilled").flatMap((r) => r.value);
800
933
  } else {
934
+ const cacheKey = `${eslintCwd}::${options.preset}`;
935
+ _runnerCache.delete(cacheKey);
801
936
  throw error;
802
937
  }
803
938
  }
@@ -812,7 +947,7 @@ async function runEslint(options) {
812
947
  const filesScanned = rawResults.length;
813
948
  for (const result of rawResults) {
814
949
  if (result.messages.length === 0) continue;
815
- const relPath = import_path.default.relative(process.cwd(), result.filePath);
950
+ const relPath = import_path.default.relative(cwd, result.filePath);
816
951
  const aiGuardIssues = [];
817
952
  let fileErrors = 0;
818
953
  let fileWarnings = 0;
@@ -837,12 +972,19 @@ async function runEslint(options) {
837
972
  });
838
973
  continue;
839
974
  }
975
+ const ruleId = m.ruleId ?? "unknown";
840
976
  const issue = {
841
- ruleId: m.ruleId ?? "unknown",
977
+ ruleId,
842
978
  severity: m.severity,
843
979
  message: m.message,
844
980
  line: m.line,
845
- column: m.column
981
+ column: m.column,
982
+ endLine: m.endLine,
983
+ endColumn: m.endColumn,
984
+ confidence: ISSUE_CONFIDENCE[ruleId],
985
+ category: ISSUE_CATEGORY[ruleId],
986
+ asyncRiskType: ISSUE_ASYNC_RISK_TYPE[ruleId],
987
+ remediation: ISSUE_REMEDIATION[ruleId]
846
988
  };
847
989
  aiGuardIssues.push(issue);
848
990
  if (m.severity === 2) {
@@ -869,11 +1011,12 @@ async function runEslint(options) {
869
1011
  const topFiles = [...files].sort((a, b) => b.issues.length - a.issues.length).slice(0, 10).map((f) => ({ path: f.filePath, count: f.issues.length }));
870
1012
  timing.processMs = Date.now() - processStart;
871
1013
  if (options.debugTiming) {
872
- log.debug(`Plugin load: ${timing.pluginLoadMs}ms`);
873
- log.debug(`Parser load: ${timing.parserLoadMs}ms`);
874
- log.debug(`Lint: ${timing.lintMs}ms`);
1014
+ const totalMs = Date.now() - overallStart;
1015
+ const cacheLabel = fromCache ? import_chalk2.default.green("warm cache") : import_chalk2.default.yellow("cold start");
1016
+ log.debug(`Init: ${initMs}ms (${cacheLabel})`);
1017
+ log.debug(`Lint: ${timing.lintMs}ms`);
875
1018
  log.debug(`Process: ${timing.processMs}ms`);
876
- log.debug(`Total: ${Date.now() - overallStart}ms`);
1019
+ log.debug(`Total: ${totalMs}ms | Files: ${filesScanned} | ${filesScanned > 0 ? Math.round(totalMs / filesScanned) : 0}ms/file`);
877
1020
  }
878
1021
  return {
879
1022
  files,
@@ -887,17 +1030,546 @@ async function runEslint(options) {
887
1030
  timing,
888
1031
  ecosystemIssues,
889
1032
  parserErrors,
890
- tsParserAvailable
1033
+ tsParserAvailable: runner.tsParserAvailable
1034
+ };
1035
+ }
1036
+
1037
+ // cli/utils/sarif.ts
1038
+ var RULE_DOCS = {
1039
+ "ai-guard/no-empty-catch": { shortDesc: "Empty catch block silently swallows errors", tags: ["reliability"] },
1040
+ "ai-guard/no-broad-exception": { shortDesc: "Catching broad Exception/Error masks specific failures", tags: ["reliability"] },
1041
+ "ai-guard/no-catch-log-rethrow": { shortDesc: "Catch block logs and rethrows \u2014 use one or the other", tags: ["reliability"] },
1042
+ "ai-guard/no-catch-without-use": { shortDesc: "Caught error variable is not used", tags: ["reliability"] },
1043
+ "ai-guard/no-floating-promise": { shortDesc: "Promise not awaited or error-handled \u2014 async failures will be silently swallowed", tags: ["async", "async-reliability"] },
1044
+ "ai-guard/no-await-in-loop": { shortDesc: "Sequential await in loop \u2014 consider Promise.all for parallel execution", tags: ["async", "async-reliability", "performance"] },
1045
+ "ai-guard/no-async-without-await": { shortDesc: "Async function contains no await expressions", tags: ["async", "async-reliability"] },
1046
+ "ai-guard/no-async-array-callback": { shortDesc: "Async callback in array method may not behave as expected", tags: ["async", "async-reliability"] },
1047
+ "ai-guard/no-redundant-await": { shortDesc: "Redundant await on already-resolved value", tags: ["async"] },
1048
+ "ai-guard/no-hardcoded-secret": { shortDesc: "Hardcoded secret or credential detected", tags: ["security", "secrets"] },
1049
+ "ai-guard/no-eval-dynamic": { shortDesc: "Dynamic eval or Function constructor is dangerous", tags: ["security"] },
1050
+ "ai-guard/no-sql-string-concat": { shortDesc: "SQL query built via string concatenation \u2014 SQL injection risk", tags: ["security", "injection"] },
1051
+ "ai-guard/no-unsafe-deserialize": { shortDesc: "Unsafe deserialization of untrusted data", tags: ["security"] },
1052
+ "ai-guard/require-auth-middleware": { shortDesc: "Route handler appears to lack authentication middleware", tags: ["security", "auth"] },
1053
+ "ai-guard/require-authz-check": { shortDesc: "Handler lacks authorization check", tags: ["security", "auth"] },
1054
+ "ai-guard/no-console-in-handler": { shortDesc: "console.log in request handler \u2014 use structured logging", tags: ["ai-patterns"] },
1055
+ "ai-guard/no-duplicate-logic-block": { shortDesc: "Duplicate logic block detected", tags: ["ai-patterns"] },
1056
+ "ai-guard/no-dead-branch": { shortDesc: "Dead code branch that can never execute", tags: ["ai-patterns"] }
1057
+ };
1058
+ var BASE_DOCS_URL = "https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules";
1059
+ function confidenceToSarifLevel(confidence) {
1060
+ switch (confidence) {
1061
+ case "high":
1062
+ return "error";
1063
+ case "medium":
1064
+ return "warning";
1065
+ case "low":
1066
+ return "warning";
1067
+ case "informational":
1068
+ return "note";
1069
+ default:
1070
+ return "warning";
1071
+ }
1072
+ }
1073
+ function confidenceToRank(confidence) {
1074
+ switch (confidence) {
1075
+ case "high":
1076
+ return 90;
1077
+ case "medium":
1078
+ return 60;
1079
+ case "low":
1080
+ return 30;
1081
+ case "informational":
1082
+ return 10;
1083
+ default:
1084
+ return 30;
1085
+ }
1086
+ }
1087
+ function confidenceToGitHubSeverity(confidence) {
1088
+ switch (confidence) {
1089
+ case "high":
1090
+ return "high";
1091
+ case "medium":
1092
+ return "medium";
1093
+ case "low":
1094
+ return "low";
1095
+ case "informational":
1096
+ return "recommendation";
1097
+ default:
1098
+ return "low";
1099
+ }
1100
+ }
1101
+ function filePathToUri(filePath) {
1102
+ return filePath.replace(/\\/g, "/");
1103
+ }
1104
+ function collectUsedRuleIds(result) {
1105
+ const ids = /* @__PURE__ */ new Set();
1106
+ for (const file of result.files) {
1107
+ for (const issue of file.issues) {
1108
+ ids.add(issue.ruleId);
1109
+ }
1110
+ }
1111
+ return [...ids].sort();
1112
+ }
1113
+ function buildRuleDescriptors(ruleIds) {
1114
+ return ruleIds.map((id) => {
1115
+ const meta = RULE_DOCS[id];
1116
+ const shortName = id.replace("ai-guard/", "");
1117
+ const confidence = ISSUE_CONFIDENCE[id];
1118
+ const category = ISSUE_CATEGORY[id] ?? "AI Patterns";
1119
+ const remediation = ISSUE_REMEDIATION[id];
1120
+ const helpMarkdown = [
1121
+ `**${meta?.shortDesc ?? shortName}**`,
1122
+ "",
1123
+ remediation ? `**Fix:** ${remediation}` : "",
1124
+ "",
1125
+ `[View rule documentation](${BASE_DOCS_URL}/${shortName}.md)`
1126
+ ].filter(Boolean).join("\n");
1127
+ return {
1128
+ id,
1129
+ shortDescription: {
1130
+ text: meta?.shortDesc ?? `ai-guard rule: ${shortName}`
1131
+ },
1132
+ fullDescription: {
1133
+ text: meta?.shortDesc ?? `ai-guard rule: ${shortName}`
1134
+ },
1135
+ help: {
1136
+ text: remediation ?? meta?.shortDesc ?? shortName,
1137
+ markdown: helpMarkdown
1138
+ },
1139
+ helpUri: `${BASE_DOCS_URL}/${shortName}.md`,
1140
+ defaultConfiguration: {
1141
+ level: confidenceToSarifLevel(confidence)
1142
+ },
1143
+ properties: {
1144
+ tags: [...meta?.tags ?? ["ai-guard"], category.toLowerCase().replace(/ /g, "-")],
1145
+ confidence: confidence ?? "low",
1146
+ category,
1147
+ "problem.severity": confidenceToGitHubSeverity(confidence)
1148
+ }
1149
+ };
1150
+ });
1151
+ }
1152
+ function buildSarifResult(issue, file) {
1153
+ const confidence = ISSUE_CONFIDENCE[issue.ruleId];
1154
+ const asyncRiskType = ISSUE_ASYNC_RISK_TYPE[issue.ruleId];
1155
+ const category = ISSUE_CATEGORY[issue.ruleId];
1156
+ return {
1157
+ ruleId: issue.ruleId,
1158
+ level: confidenceToSarifLevel(confidence),
1159
+ kind: confidence === "informational" ? "informational" : "fail",
1160
+ rank: confidenceToRank(confidence),
1161
+ message: { text: issue.message },
1162
+ locations: [
1163
+ {
1164
+ physicalLocation: {
1165
+ artifactLocation: {
1166
+ uri: filePathToUri(file.filePath),
1167
+ uriBaseId: "%SRCROOT%"
1168
+ },
1169
+ region: {
1170
+ startLine: Math.max(1, issue.line),
1171
+ startColumn: issue.column > 0 ? issue.column : 1,
1172
+ ...issue.endLine !== void 0 && { endLine: issue.endLine },
1173
+ ...issue.endColumn !== void 0 && { endColumn: issue.endColumn }
1174
+ }
1175
+ },
1176
+ message: { text: issue.message }
1177
+ }
1178
+ ],
1179
+ properties: {
1180
+ confidence: confidence ?? "low",
1181
+ ...asyncRiskType && { asyncRiskType },
1182
+ ...category && { category }
1183
+ }
1184
+ };
1185
+ }
1186
+ function buildSarifLog(result, version = "1.3.0") {
1187
+ const usedRuleIds = collectUsedRuleIds(result);
1188
+ const rules = buildRuleDescriptors(usedRuleIds);
1189
+ const sarifResults = [];
1190
+ for (const file of result.files) {
1191
+ for (const issue of file.issues) {
1192
+ sarifResults.push(buildSarifResult(issue, file));
1193
+ }
1194
+ }
1195
+ return {
1196
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
1197
+ version: "2.1.0",
1198
+ runs: [
1199
+ {
1200
+ tool: {
1201
+ driver: {
1202
+ name: "ai-guard",
1203
+ version,
1204
+ informationUri: "https://github.com/YashJadhav21/eslint-plugin-ai-guard",
1205
+ rules
1206
+ }
1207
+ },
1208
+ results: sarifResults,
1209
+ properties: {
1210
+ preset: "recommended",
1211
+ filesScanned: result.filesScanned,
1212
+ durationMs: result.durationMs,
1213
+ ecosystemIssuesCount: result.ecosystemIssues.length,
1214
+ parserErrorsCount: result.parserErrors.length
1215
+ }
1216
+ }
1217
+ ]
1218
+ };
1219
+ }
1220
+ function sarifToJson(log2) {
1221
+ return JSON.stringify(log2, null, 2);
1222
+ }
1223
+
1224
+ // cli/utils/github-summary.ts
1225
+ var import_fs2 = __toESM(require("fs"));
1226
+ function escapeMarkdown(text) {
1227
+ return text.replace(/[|`*_[\]<>]/g, "\\$&");
1228
+ }
1229
+ function tierEmoji(tier) {
1230
+ switch (tier) {
1231
+ case "high":
1232
+ return "\u{1F534}";
1233
+ case "medium":
1234
+ return "\u{1F7E1}";
1235
+ case "low":
1236
+ return "\u{1F535}";
1237
+ case "informational":
1238
+ return "\u2B1C";
1239
+ default:
1240
+ return "\u2B1C";
1241
+ }
1242
+ }
1243
+ function tierLabel(tier) {
1244
+ switch (tier) {
1245
+ case "high":
1246
+ return "High";
1247
+ case "medium":
1248
+ return "Medium";
1249
+ case "low":
1250
+ return "Low";
1251
+ case "informational":
1252
+ return "Info";
1253
+ default:
1254
+ return "Low";
1255
+ }
1256
+ }
1257
+ function emitGitHubAnnotations(result) {
1258
+ if (process.env.GITHUB_ACTIONS !== "true") return;
1259
+ for (const file of result.files) {
1260
+ for (const issue of file.issues) {
1261
+ const tier = CONFIDENCE_TIER[issue.ruleId];
1262
+ if (tier === "informational") continue;
1263
+ const level = issue.severity === 2 ? "error" : "warning";
1264
+ const filePath = file.filePath.replace(/\\/g, "/");
1265
+ const shortRule = issue.ruleId.replace("ai-guard/", "");
1266
+ const parts = [`file=${filePath}`, `line=${issue.line}`];
1267
+ if (issue.endLine) parts.push(`endLine=${issue.endLine}`);
1268
+ if (issue.column > 0) parts.push(`col=${issue.column}`);
1269
+ if (issue.endColumn) parts.push(`endColumn=${issue.endColumn}`);
1270
+ parts.push(`title=ai-guard/${shortRule}`);
1271
+ const msg = issue.message.replace(/\n/g, " ");
1272
+ process.stdout.write(`::${level} ${parts.join(",")}::${msg}
1273
+ `);
1274
+ }
1275
+ }
1276
+ }
1277
+ function buildGitHubSummaryMarkdown(result, options) {
1278
+ const lines = [];
1279
+ lines.push("## AI Guard \u2014 Async Reliability Report");
1280
+ lines.push("");
1281
+ lines.push(`> **Preset:** \`${options.preset}\` &nbsp;|&nbsp; **Mode:** ${options.scanMode === "changed" ? "Changed files only" : options.scanMode === "staged" ? "Staged files" : "Full scan"}`);
1282
+ if (options.changedFilesCount !== void 0) {
1283
+ lines.push(`> **Files scanned:** ${result.filesScanned} (${options.changedFilesCount} changed)`);
1284
+ } else {
1285
+ lines.push(`> **Files scanned:** ${result.filesScanned}`);
1286
+ }
1287
+ lines.push(`> **Duration:** ${result.durationMs}ms`);
1288
+ lines.push("");
1289
+ if (result.totalIssues === 0) {
1290
+ lines.push("### \u2705 No issues found");
1291
+ lines.push("");
1292
+ lines.push("All scanned files passed AI Guard reliability checks.");
1293
+ lines.push("");
1294
+ return lines.join("\n");
1295
+ }
1296
+ const { high = 0, medium = 0, low = 0, informational = 0 } = buildSignalCounts(result);
1297
+ if (high > 0) {
1298
+ lines.push(`### \u26A0\uFE0F ${high} high-confidence issue${high !== 1 ? "s" : ""} found`);
1299
+ } else if (medium > 0) {
1300
+ lines.push(`### \u{1F535} ${medium} medium-confidence issue${medium !== 1 ? "s" : ""} found`);
1301
+ } else {
1302
+ lines.push("### \u2139\uFE0F Informational hints only");
1303
+ }
1304
+ lines.push("");
1305
+ lines.push("| Category | \u{1F534} High | \u{1F7E1} Medium | \u{1F535} Low | \u2B1C Info |");
1306
+ lines.push("|----------|---------|----------|-------|--------|");
1307
+ const categoryBreakdown = buildCategoryBreakdown(result);
1308
+ for (const [cat, counts] of Object.entries(categoryBreakdown)) {
1309
+ if (counts.high + counts.medium + counts.low + counts.informational === 0) continue;
1310
+ lines.push(`| ${cat} | ${counts.high} | ${counts.medium} | ${counts.low} | ${counts.informational} |`);
1311
+ }
1312
+ lines.push("");
1313
+ const allIssues = result.files.flatMap(
1314
+ (f) => f.issues.map((i) => ({ ...i, filePath: f.filePath }))
1315
+ );
1316
+ const actionableIssues = allIssues.filter((i) => {
1317
+ const tier = CONFIDENCE_TIER[i.ruleId];
1318
+ return tier !== "informational";
1319
+ }).sort((a, b) => {
1320
+ const tierOrder = { high: 0, medium: 1, low: 2, informational: 3 };
1321
+ const ta = CONFIDENCE_TIER[a.ruleId];
1322
+ const tb = CONFIDENCE_TIER[b.ruleId];
1323
+ return (tierOrder[ta ?? "low"] ?? 2) - (tierOrder[tb ?? "low"] ?? 2);
1324
+ }).slice(0, 10);
1325
+ if (actionableIssues.length > 0) {
1326
+ lines.push("### Top Findings");
1327
+ lines.push("");
1328
+ lines.push("| File | Line | Rule | Confidence |");
1329
+ lines.push("|------|------|------|------------|");
1330
+ for (const issue of actionableIssues) {
1331
+ const tier = CONFIDENCE_TIER[issue.ruleId];
1332
+ const shortFile = issue.filePath.replace(/\\/g, "/").split("/").slice(-2).join("/");
1333
+ const shortRule = issue.ruleId.replace("ai-guard/", "");
1334
+ lines.push(
1335
+ `| \`${escapeMarkdown(shortFile)}\` | ${issue.line} | \`${shortRule}\` | ${tierEmoji(tier)} ${tierLabel(tier)} |`
1336
+ );
1337
+ }
1338
+ lines.push("");
1339
+ }
1340
+ if (informational > 0) {
1341
+ lines.push("<details>");
1342
+ lines.push(`<summary>\u{1F4A1} ${informational} informational hint${informational !== 1 ? "s" : ""} (click to expand)</summary>`);
1343
+ lines.push("");
1344
+ lines.push("These are stylistic hints with higher false-positive rates in frameworks like Next.js and React. Review manually before acting.");
1345
+ lines.push("");
1346
+ const infoIssues = allIssues.filter((i) => {
1347
+ const tier = CONFIDENCE_TIER[i.ruleId];
1348
+ return tier === "informational";
1349
+ }).slice(0, 20);
1350
+ lines.push("| File | Line | Rule |");
1351
+ lines.push("|------|------|------|");
1352
+ for (const issue of infoIssues) {
1353
+ const shortFile = issue.filePath.replace(/\\/g, "/").split("/").slice(-2).join("/");
1354
+ const shortRule = issue.ruleId.replace("ai-guard/", "");
1355
+ lines.push(`| \`${escapeMarkdown(shortFile)}\` | ${issue.line} | \`${shortRule}\` |`);
1356
+ }
1357
+ lines.push("</details>");
1358
+ lines.push("");
1359
+ }
1360
+ if (result.ecosystemIssues.length > 0) {
1361
+ lines.push("<details>");
1362
+ lines.push(`<summary>\u{1F527} ${result.ecosystemIssues.length} ESLint config issue${result.ecosystemIssues.length !== 1 ? "s" : ""} (not ai-guard findings)</summary>`);
1363
+ lines.push("");
1364
+ lines.push("These are from your project's ESLint configuration, not from ai-guard rules. Run `ai-guard doctor` to diagnose.");
1365
+ lines.push("</details>");
1366
+ lines.push("");
1367
+ }
1368
+ lines.push("---");
1369
+ lines.push("");
1370
+ lines.push("**Next steps:**");
1371
+ if (high > 0 || medium > 0) {
1372
+ lines.push("- Review high/medium confidence findings in the **Files changed** tab");
1373
+ lines.push("- Run `ai-guard run --verbose` locally to see full details");
1374
+ }
1375
+ if (informational > 0) {
1376
+ lines.push("- Run `ai-guard run --verbose` to expand informational hints");
1377
+ }
1378
+ lines.push("- Run `ai-guard baseline` to suppress known issues and track only new ones");
1379
+ lines.push("");
1380
+ lines.push("*Powered by [eslint-plugin-ai-guard](https://github.com/YashJadhav21/eslint-plugin-ai-guard)*");
1381
+ return lines.join("\n");
1382
+ }
1383
+ function buildSignalCounts(result) {
1384
+ let high = 0, medium = 0, low = 0, informational = 0;
1385
+ for (const file of result.files) {
1386
+ for (const issue of file.issues) {
1387
+ const tier = CONFIDENCE_TIER[issue.ruleId];
1388
+ if (tier === "high") high++;
1389
+ else if (tier === "medium") medium++;
1390
+ else if (tier === "informational") informational++;
1391
+ else low++;
1392
+ }
1393
+ }
1394
+ return { high, medium, low, informational };
1395
+ }
1396
+ function buildCategoryBreakdown(result) {
1397
+ const breakdown = {};
1398
+ for (const file of result.files) {
1399
+ for (const issue of file.issues) {
1400
+ const cat = RULE_CATEGORY[issue.ruleId] ?? "Other";
1401
+ const tier = CONFIDENCE_TIER[issue.ruleId];
1402
+ if (!breakdown[cat]) breakdown[cat] = { high: 0, medium: 0, low: 0, informational: 0 };
1403
+ if (tier === "high") breakdown[cat].high++;
1404
+ else if (tier === "medium") breakdown[cat].medium++;
1405
+ else if (tier === "informational") breakdown[cat].informational++;
1406
+ else breakdown[cat].low++;
1407
+ }
1408
+ }
1409
+ return breakdown;
1410
+ }
1411
+ function writeGitHubSummary(result, options) {
1412
+ const summaryPath = process.env.GITHUB_STEP_SUMMARY;
1413
+ if (!summaryPath) return;
1414
+ try {
1415
+ const markdown = buildGitHubSummaryMarkdown(result, options);
1416
+ import_fs2.default.appendFileSync(summaryPath, markdown + "\n");
1417
+ } catch {
1418
+ }
1419
+ }
1420
+ function writeGitHubOutputs(result, sarifPath) {
1421
+ const outputPath = process.env.GITHUB_OUTPUT;
1422
+ if (!outputPath) return;
1423
+ const { high, medium, low, informational } = buildSignalCounts(result);
1424
+ const outputs = [
1425
+ `issues-found=${result.totalIssues}`,
1426
+ `high-confidence-count=${high}`,
1427
+ `medium-confidence-count=${medium}`,
1428
+ `low-confidence-count=${low}`,
1429
+ `informational-count=${informational}`,
1430
+ `files-scanned=${result.filesScanned}`,
1431
+ `duration-ms=${result.durationMs}`,
1432
+ ...sarifPath ? [`sarif-file=${sarifPath}`] : []
1433
+ ];
1434
+ try {
1435
+ import_fs2.default.appendFileSync(outputPath, outputs.join("\n") + "\n");
1436
+ } catch {
1437
+ }
1438
+ }
1439
+
1440
+ // cli/utils/git-diff.ts
1441
+ var import_child_process = require("child_process");
1442
+ var import_path2 = __toESM(require("path"));
1443
+ var import_fs3 = __toESM(require("fs"));
1444
+ var DEFAULT_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"];
1445
+ var IGNORE_PREFIXES = [
1446
+ "node_modules/",
1447
+ "dist/",
1448
+ "build/",
1449
+ ".next/",
1450
+ "out/",
1451
+ "coverage/",
1452
+ ".cache/",
1453
+ ".turbo/",
1454
+ ".nx/",
1455
+ "vendor/",
1456
+ "tmp/",
1457
+ "temp/",
1458
+ "__generated__/",
1459
+ "generated/",
1460
+ "storybook-static/",
1461
+ ".expo/",
1462
+ ".svelte-kit/"
1463
+ ];
1464
+ function runGit(args, cwd) {
1465
+ try {
1466
+ return (0, import_child_process.execSync)(`git ${args}`, {
1467
+ cwd,
1468
+ encoding: "utf-8",
1469
+ stdio: ["pipe", "pipe", "pipe"]
1470
+ }).trim();
1471
+ } catch {
1472
+ return null;
1473
+ }
1474
+ }
1475
+ function isGitAvailable(cwd) {
1476
+ return runGit("rev-parse --is-inside-work-tree", cwd) === "true";
1477
+ }
1478
+ function filterFiles(rawFiles, extensions, workingDirectory, cwd) {
1479
+ let dropped = 0;
1480
+ const kept = [];
1481
+ for (const rel of rawFiles) {
1482
+ const ext = import_path2.default.extname(rel).toLowerCase();
1483
+ if (!extensions.includes(ext)) {
1484
+ dropped++;
1485
+ continue;
1486
+ }
1487
+ const normalized = rel.replace(/\\/g, "/");
1488
+ if (IGNORE_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
1489
+ dropped++;
1490
+ continue;
1491
+ }
1492
+ if (workingDirectory) {
1493
+ const wdNorm = workingDirectory.replace(/\\/g, "/").replace(/\/$/, "");
1494
+ if (!normalized.startsWith(wdNorm + "/") && normalized !== wdNorm) {
1495
+ dropped++;
1496
+ continue;
1497
+ }
1498
+ }
1499
+ const abs = import_path2.default.resolve(cwd, rel);
1500
+ if (!import_fs3.default.existsSync(abs)) {
1501
+ dropped++;
1502
+ continue;
1503
+ }
1504
+ kept.push(abs);
1505
+ }
1506
+ return { kept, dropped };
1507
+ }
1508
+ function parseFileList(output) {
1509
+ return output.split("\n").map((f) => f.trim()).filter(Boolean);
1510
+ }
1511
+ function getChangedFiles(options = {}) {
1512
+ const {
1513
+ staged = false,
1514
+ base,
1515
+ cwd = process.cwd(),
1516
+ extensions = DEFAULT_EXTENSIONS,
1517
+ workingDirectory
1518
+ } = options;
1519
+ const effectiveBase = base ?? process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF ?? ""}` : void 0;
1520
+ if (!isGitAvailable(cwd)) {
1521
+ return { files: [], mode: "fallback", totalDetected: 0, filteredOut: 0 };
1522
+ }
1523
+ let rawOutput = null;
1524
+ let mode;
1525
+ let usedBase;
1526
+ if (effectiveBase && !staged) {
1527
+ const mergeBase = runGit(`merge-base HEAD ${effectiveBase}`, cwd);
1528
+ if (mergeBase) {
1529
+ rawOutput = runGit(`diff --name-only --diff-filter=ACMRT ${mergeBase}...HEAD`, cwd);
1530
+ }
1531
+ if (!rawOutput) {
1532
+ rawOutput = runGit(`diff --name-only --diff-filter=ACMRT ${effectiveBase}`, cwd);
1533
+ }
1534
+ mode = "pr-diff";
1535
+ usedBase = effectiveBase;
1536
+ } else if (staged) {
1537
+ rawOutput = runGit("diff --name-only --cached --diff-filter=ACMRT", cwd);
1538
+ mode = "staged";
1539
+ } else {
1540
+ const staged_ = runGit("diff --name-only --cached --diff-filter=ACMRT", cwd) ?? "";
1541
+ const unstaged = runGit("diff --name-only --diff-filter=ACMRT", cwd) ?? "";
1542
+ const combined = /* @__PURE__ */ new Set([...parseFileList(staged_), ...parseFileList(unstaged)]);
1543
+ rawOutput = [...combined].join("\n");
1544
+ mode = "uncommitted";
1545
+ }
1546
+ if (!rawOutput) {
1547
+ return { files: [], mode, base: usedBase, totalDetected: 0, filteredOut: 0 };
1548
+ }
1549
+ const rawFiles = parseFileList(rawOutput);
1550
+ const { kept, dropped } = filterFiles(rawFiles, extensions, workingDirectory, cwd);
1551
+ return {
1552
+ files: kept,
1553
+ mode,
1554
+ base: usedBase,
1555
+ totalDetected: rawFiles.length,
1556
+ filteredOut: dropped
891
1557
  };
892
1558
  }
1559
+ function isGitHubActions() {
1560
+ return process.env.GITHUB_ACTIONS === "true";
1561
+ }
1562
+ function getGitHubBaseRef() {
1563
+ return process.env.GITHUB_BASE_REF || void 0;
1564
+ }
893
1565
 
894
1566
  // cli/commands/run.ts
895
1567
  function registerRunCommand(program2) {
896
- program2.command("run").description("Run ai-guard rules on your project (zero ESLint config required)").option("--path <dir>", "Directory or file to scan", ".").option("--strict", "Use the strict rule preset (all rules at error)").option("--security", "Use the security-only rule preset").option("--json", "Output results as JSON (CI-friendly)").option(
1568
+ program2.command("run").description("Run ai-guard rules on your project (zero ESLint config required)").option("--path <dir>", "Directory or file to scan", ".").option("--strict", "Use the strict rule preset (all rules at error)").option("--security", "Use the security-only rule preset").option("--json", "Output results as JSON (CI-friendly)").option("--sarif", "Output results as SARIF 2.1.0 (for GitHub Code Scanning)").option("--fail-on <level>", "Fail CI on: errors (default) | high | medium | any | none", "errors").option(
897
1569
  "--max-warnings <n>",
898
1570
  "Fail with exit code 1 if warnings exceed this count",
899
1571
  (value) => Number.parseInt(value, 10)
900
- ).option("--verbose", "Show all issues \u2014 disable grouping of repeated warnings").option("--debug-timing", "Print per-phase timing diagnostics").action(async (opts) => {
1572
+ ).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").action(async (opts) => {
901
1573
  if (opts.maxWarnings !== void 0 && (!Number.isInteger(opts.maxWarnings) || opts.maxWarnings < 0)) {
902
1574
  log.blank();
903
1575
  log.error("--max-warnings must be a non-negative integer.");
@@ -930,21 +1602,33 @@ function registerRunCommand(program2) {
930
1602
  log.blank();
931
1603
  log.error("TypeScript detected but parser not found.");
932
1604
  log.blank();
933
- log.print(` ${import_chalk2.default.bold("Install the TypeScript parser:")}`);
1605
+ log.print(` ${import_chalk3.default.bold("Install the TypeScript parser:")}`);
934
1606
  log.blank();
935
- log.print(` ${import_chalk2.default.cyan("npm install --save-dev @typescript-eslint/parser")}`);
1607
+ log.print(` ${import_chalk3.default.cyan("npm install --save-dev @typescript-eslint/parser")}`);
936
1608
  log.blank();
937
1609
  } else {
938
1610
  log.blank();
939
1611
  log.error(msg);
940
1612
  log.blank();
941
- log.print(` ${import_chalk2.default.bold("Fix:")} Run ${import_chalk2.default.cyan("ai-guard doctor")} to diagnose your setup.`);
1613
+ log.print(` ${import_chalk3.default.bold("Fix:")} Run ${import_chalk3.default.cyan("ai-guard doctor")} to diagnose your setup.`);
942
1614
  log.blank();
943
1615
  }
944
1616
  process.exit(1);
945
1617
  return;
946
1618
  }
1619
+ if (isGitHubActions()) {
1620
+ emitGitHubAnnotations(result);
1621
+ writeGitHubSummary(result, { preset, scanMode: "full" });
1622
+ writeGitHubOutputs(result);
1623
+ }
1624
+ if (opts.sarif) {
1625
+ const sarifLog = buildSarifLog(result);
1626
+ console.log(sarifToJson(sarifLog));
1627
+ process.exit(resolveExitCode(result, opts.failOn ?? "errors", opts.maxWarnings));
1628
+ return;
1629
+ }
947
1630
  if (opts.json) {
1631
+ const signalSummary = buildSignalSummary(result);
948
1632
  const jsonOutput = {
949
1633
  preset,
950
1634
  scannedPath: opts.path,
@@ -954,6 +1638,7 @@ function registerRunCommand(program2) {
954
1638
  totalIssues: result.totalIssues,
955
1639
  durationMs: result.durationMs,
956
1640
  timing: result.timing,
1641
+ signalSummary,
957
1642
  ruleBreakdown: Object.fromEntries(result.ruleBreakdown),
958
1643
  topFiles: result.topFiles,
959
1644
  files: result.files,
@@ -962,19 +1647,19 @@ function registerRunCommand(program2) {
962
1647
  tsParserAvailable: result.tsParserAvailable
963
1648
  };
964
1649
  console.log(JSON.stringify(jsonOutput, null, 2));
965
- process.exit(getRunExitCode(result, opts.maxWarnings));
1650
+ process.exit(resolveExitCode(result, opts.failOn ?? "errors", opts.maxWarnings));
966
1651
  return;
967
1652
  }
968
1653
  log.scanStats(result.filesScanned, result.files.length, result.durationMs, preset);
969
1654
  if (!result.tsParserAvailable) {
970
1655
  log.print(
971
- import_chalk2.default.dim(` \u2139 TypeScript parser not found \u2014 .ts/.tsx files use espree fallback`) + import_chalk2.default.dim(` (install @typescript-eslint/parser for full TS support)`)
1656
+ import_chalk3.default.dim(` \u2139 TypeScript parser not found \u2014 .ts/.tsx files use espree fallback`) + import_chalk3.default.dim(` (install @typescript-eslint/parser for full TS support)`)
972
1657
  );
973
1658
  }
974
1659
  log.blank();
975
1660
  if (result.totalIssues === 0 && result.ecosystemIssues.length === 0 && result.parserErrors.length === 0) {
976
1661
  log.print(
977
- ` ${import_chalk2.default.green("\u2714")} ${import_chalk2.default.bold.green("No AI issues found")} ${import_chalk2.default.gray(`\u2014 ${result.filesScanned} file${result.filesScanned !== 1 ? "s" : ""} scanned, all clean`)}`
1662
+ ` ${import_chalk3.default.green("\u2714")} ${import_chalk3.default.bold.green("No AI issues found")} ${import_chalk3.default.gray(`\u2014 ${result.filesScanned} file${result.filesScanned !== 1 ? "s" : ""} scanned, all clean`)}`
978
1663
  );
979
1664
  log.blank();
980
1665
  process.exit(0);
@@ -982,15 +1667,18 @@ function registerRunCommand(program2) {
982
1667
  }
983
1668
  if (result.totalIssues === 0) {
984
1669
  log.print(
985
- ` ${import_chalk2.default.green("\u2714")} ${import_chalk2.default.bold.green("No AI issues found")} ${import_chalk2.default.gray(`\u2014 ${result.filesScanned} file${result.filesScanned !== 1 ? "s" : ""} scanned, all clean`)}`
1670
+ ` ${import_chalk3.default.green("\u2714")} ${import_chalk3.default.bold.green("No AI issues found")} ${import_chalk3.default.gray(`\u2014 ${result.filesScanned} file${result.filesScanned !== 1 ? "s" : ""} scanned, all clean`)}`
986
1671
  );
987
1672
  log.blank();
988
1673
  }
989
1674
  if (result.totalIssues > 0) {
1675
+ const verbose = opts.verbose ?? false;
1676
+ const quiet = opts.quiet ?? false;
1677
+ const { actionableFiles, informationalFiles } = partitionByTier(result, quiet);
990
1678
  log.section("Summary by Category");
991
1679
  const categoryErrors = {};
992
1680
  const categoryWarnings = {};
993
- for (const file of result.files) {
1681
+ for (const file of actionableFiles) {
994
1682
  for (const issue of file.issues) {
995
1683
  const cat = RULE_CATEGORY[issue.ruleId] ?? "Other";
996
1684
  if (issue.severity === 2) {
@@ -1022,7 +1710,7 @@ function registerRunCommand(program2) {
1022
1710
  }
1023
1711
  log.blank();
1024
1712
  log.print(
1025
- ` ${import_chalk2.default.bold("Total:")} ${formatIssueCount(result.totalErrors, result.totalWarnings)}`
1713
+ ` ${import_chalk3.default.bold("Total:")} ${formatIssueCount(result.totalErrors, result.totalWarnings)}`
1026
1714
  );
1027
1715
  log.blank();
1028
1716
  if (result.ruleBreakdown.size > 0) {
@@ -1033,9 +1721,10 @@ function registerRunCommand(program2) {
1033
1721
  for (const [rule, count] of sorted) {
1034
1722
  const shortRule = rule.replace(/^ai-guard\//, "");
1035
1723
  const tier = CONFIDENCE_TIER[rule];
1036
- const tierLabel = tier === "high" ? import_chalk2.default.red.dim("[high]") : tier === "medium" ? import_chalk2.default.yellow.dim("[medium]") : import_chalk2.default.gray.dim("[low]");
1724
+ const tierLabel2 = tier === "high" ? import_chalk3.default.red.dim("[high]") : tier === "medium" ? import_chalk3.default.yellow.dim("[medium]") : tier === "informational" ? import_chalk3.default.gray.dim("[info]") : import_chalk3.default.gray.dim("[low]");
1725
+ if (quiet && tier === "informational") continue;
1037
1726
  log.print(
1038
- ` ${import_chalk2.default.gray("\u2022")} ${import_chalk2.default.yellow(shortRule)} ${tierLabel}${import_chalk2.default.gray(":")} ${import_chalk2.default.white(String(count))}`
1727
+ ` ${import_chalk3.default.gray("\u2022")} ${import_chalk3.default.yellow(shortRule)} ${tierLabel2}${import_chalk3.default.gray(":")} ${import_chalk3.default.white(String(count))}`
1039
1728
  );
1040
1729
  }
1041
1730
  log.blank();
@@ -1044,20 +1733,35 @@ function registerRunCommand(program2) {
1044
1733
  log.section("Top Files");
1045
1734
  for (const { path: fp, count } of result.topFiles) {
1046
1735
  log.print(
1047
- ` ${import_chalk2.default.gray("\u2022")} ${import_chalk2.default.white(fp)} ${import_chalk2.default.gray(`(${count})`)}`
1736
+ ` ${import_chalk3.default.gray("\u2022")} ${import_chalk3.default.white(fp)} ${import_chalk3.default.gray(`(${count})`)}`
1048
1737
  );
1049
1738
  }
1050
1739
  log.blank();
1051
1740
  }
1052
- log.section("Issues by File");
1053
- log.blank();
1054
- const verbose = opts.verbose ?? false;
1055
- renderIssuesByFile(result, verbose);
1741
+ if (actionableFiles.length > 0) {
1742
+ log.section("Issues by File");
1743
+ log.blank();
1744
+ renderIssuesByFile({ ...result, files: actionableFiles }, verbose, quiet);
1745
+ }
1746
+ const totalInfoCount = informationalFiles.reduce((n, f) => n + f.issues.length, 0);
1747
+ if (totalInfoCount > 0 && !quiet) {
1748
+ if (verbose) {
1749
+ log.section("Informational Hints");
1750
+ log.print(import_chalk3.default.dim(` Stylistic hints with higher false-positive rate \u2014 review before acting.`));
1751
+ log.blank();
1752
+ renderIssuesByFile({ ...result, files: informationalFiles }, true, false);
1753
+ } else {
1754
+ log.blank();
1755
+ log.print(
1756
+ ` ${import_chalk3.default.gray("\u25B8")} ${import_chalk3.default.gray(`${totalInfoCount} informational hint${totalInfoCount !== 1 ? "s" : ""} \u2014 run with ${import_chalk3.default.cyan("--verbose")} to expand`)}`
1757
+ );
1758
+ }
1759
+ }
1056
1760
  }
1057
1761
  if (result.ecosystemIssues.length > 0) {
1058
1762
  log.ecosystemSection("ESLint Config Issues");
1059
1763
  log.print(
1060
- import_chalk2.default.dim(` These issues come from your project's ESLint config, not from ai-guard.`)
1764
+ import_chalk3.default.dim(` These issues come from your project's ESLint config, not from ai-guard.`)
1061
1765
  );
1062
1766
  log.blank();
1063
1767
  const seen = /* @__PURE__ */ new Map();
@@ -1078,7 +1782,7 @@ function registerRunCommand(program2) {
1078
1782
  if (result.parserErrors.length > 0) {
1079
1783
  log.ecosystemSection("Parser Errors");
1080
1784
  log.print(
1081
- import_chalk2.default.dim(` These files could not be parsed \u2014 not ai-guard findings.`)
1785
+ import_chalk3.default.dim(` These files could not be parsed \u2014 not ai-guard findings.`)
1082
1786
  );
1083
1787
  log.blank();
1084
1788
  for (const pe of result.parserErrors) {
@@ -1087,10 +1791,10 @@ function registerRunCommand(program2) {
1087
1791
  if (!result.tsParserAvailable) {
1088
1792
  log.blank();
1089
1793
  log.print(
1090
- ` ${import_chalk2.default.cyan("\u2192 Fix:")} ${import_chalk2.default.yellow("npm install --save-dev @typescript-eslint/parser")}`
1794
+ ` ${import_chalk3.default.cyan("\u2192 Fix:")} ${import_chalk3.default.yellow("npm install --save-dev @typescript-eslint/parser")}`
1091
1795
  );
1092
1796
  log.print(
1093
- import_chalk2.default.dim(` TypeScript parser resolves most .ts/.tsx parse failures.`)
1797
+ import_chalk3.default.dim(` TypeScript parser resolves most .ts/.tsx parse failures.`)
1094
1798
  );
1095
1799
  }
1096
1800
  log.blank();
@@ -1099,21 +1803,63 @@ function registerRunCommand(program2) {
1099
1803
  log.blank();
1100
1804
  log.section("Next Steps");
1101
1805
  if (result.totalIssues > 0) {
1102
- log.info(`Run ${import_chalk2.default.cyan("ai-guard baseline")} to save these issues and track only new ones`);
1103
- log.info(`Run ${import_chalk2.default.cyan("ai-guard report")} to generate a shareable HTML report`);
1104
- log.info(`Run ${import_chalk2.default.cyan("ai-guard ignore")} to suppress dist/build noise`);
1806
+ log.info(`Run ${import_chalk3.default.cyan("ai-guard baseline")} to save these issues and track only new ones`);
1807
+ log.info(`Run ${import_chalk3.default.cyan("ai-guard report")} to generate a shareable HTML report`);
1808
+ log.info(`Run ${import_chalk3.default.cyan("ai-guard ignore")} to suppress dist/build noise`);
1105
1809
  }
1106
1810
  if (result.ecosystemIssues.length > 0) {
1107
- log.info(`Run ${import_chalk2.default.cyan("ai-guard doctor")} to diagnose ESLint config issues`);
1811
+ log.info(`Run ${import_chalk3.default.cyan("ai-guard doctor")} to diagnose ESLint config issues`);
1108
1812
  }
1109
1813
  if (!opts.verbose && hasGroupedWarnings(result)) {
1110
- log.info(`Run with ${import_chalk2.default.cyan("--verbose")} to expand grouped warnings`);
1814
+ log.info(`Run with ${import_chalk3.default.cyan("--verbose")} to expand grouped warnings`);
1111
1815
  }
1112
1816
  log.blank();
1113
1817
  process.exit(getRunExitCode(result, opts.maxWarnings));
1114
1818
  });
1115
1819
  }
1116
1820
  var GROUP_THRESHOLD = 4;
1821
+ function partitionByTier(result, quiet) {
1822
+ const actionableFiles = [];
1823
+ const informationalFiles = [];
1824
+ for (const file of result.files) {
1825
+ const actionableIssues = file.issues.filter((i) => {
1826
+ const tier = CONFIDENCE_TIER[i.ruleId];
1827
+ if (quiet && (tier === "informational" || i.severity === 1)) return false;
1828
+ return tier !== "informational";
1829
+ });
1830
+ const infoIssues = file.issues.filter((i) => {
1831
+ const tier = CONFIDENCE_TIER[i.ruleId];
1832
+ return tier === "informational";
1833
+ });
1834
+ if (actionableIssues.length > 0) {
1835
+ actionableFiles.push({ ...file, issues: actionableIssues });
1836
+ }
1837
+ if (infoIssues.length > 0) {
1838
+ informationalFiles.push({ ...file, issues: infoIssues });
1839
+ }
1840
+ }
1841
+ return { actionableFiles, informationalFiles };
1842
+ }
1843
+ function buildSignalSummary(result) {
1844
+ let high = 0, medium = 0, low = 0, informational = 0;
1845
+ for (const file of result.files) {
1846
+ for (const issue of file.issues) {
1847
+ const tier = CONFIDENCE_TIER[issue.ruleId];
1848
+ if (tier === "high") high++;
1849
+ else if (tier === "medium") medium++;
1850
+ else if (tier === "informational") informational++;
1851
+ else low++;
1852
+ }
1853
+ }
1854
+ return {
1855
+ high,
1856
+ medium,
1857
+ low,
1858
+ informational,
1859
+ ecosystemIssues: result.ecosystemIssues.length,
1860
+ parserErrors: result.parserErrors.length
1861
+ };
1862
+ }
1117
1863
  function hasGroupedWarnings(result) {
1118
1864
  for (const file of result.files) {
1119
1865
  const ruleCounts = /* @__PURE__ */ new Map();
@@ -1126,10 +1872,10 @@ function hasGroupedWarnings(result) {
1126
1872
  }
1127
1873
  return false;
1128
1874
  }
1129
- function renderIssuesByFile(result, verbose) {
1875
+ function renderIssuesByFile(result, verbose, quiet = false) {
1130
1876
  for (const file of result.files) {
1131
1877
  log.print(
1132
- ` ${import_chalk2.default.bold.white(file.filePath)} ` + import_chalk2.default.gray(
1878
+ ` ${import_chalk3.default.bold.white(file.filePath)} ` + import_chalk3.default.gray(
1133
1879
  `(${file.errorCount} error${file.errorCount !== 1 ? "s" : ""}, ${file.warningCount} warning${file.warningCount !== 1 ? "s" : ""})`
1134
1880
  )
1135
1881
  );
@@ -1143,13 +1889,13 @@ function renderIssuesByFile(result, verbose) {
1143
1889
  for (const issues of ruleCounts.values()) {
1144
1890
  const tier = CONFIDENCE_TIER[issues[0].ruleId] ?? "low";
1145
1891
  if (tier === "low" && issues.length >= GROUP_THRESHOLD) {
1146
- const sev = issues[0].severity === 2 ? import_chalk2.default.red("error") : import_chalk2.default.yellow(" warn");
1147
- const shortRule = import_chalk2.default.gray(issues[0].ruleId.replace(/^ai-guard\//, ""));
1892
+ const sev = issues[0].severity === 2 ? import_chalk3.default.red("error") : import_chalk3.default.yellow(" warn");
1893
+ const shortRule = import_chalk3.default.gray(issues[0].ruleId.replace(/^ai-guard\//, ""));
1148
1894
  log.print(
1149
- ` ${sev} ${import_chalk2.default.white(issues[0].message)} ${shortRule}`
1895
+ ` ${sev} ${import_chalk3.default.white(issues[0].message)} ${shortRule}`
1150
1896
  );
1151
1897
  log.print(
1152
- import_chalk2.default.dim(` \u21B3 ${issues.length} occurrences \u2014 run with --verbose to expand`)
1898
+ import_chalk3.default.dim(` \u21B3 ${issues.length} occurrences \u2014 run with --verbose to expand`)
1153
1899
  );
1154
1900
  } else {
1155
1901
  for (const issue of issues) {
@@ -1166,13 +1912,13 @@ function renderIssuesByFile(result, verbose) {
1166
1912
  }
1167
1913
  }
1168
1914
  function renderSingleIssue(issue) {
1169
- const sev = issue.severity === 2 ? import_chalk2.default.red("error") : import_chalk2.default.yellow(" warn");
1170
- const loc = import_chalk2.default.gray(`${String(issue.line)}:${String(issue.column)}`).padEnd(12);
1171
- const ruleShort = import_chalk2.default.gray(issue.ruleId.replace(/^ai-guard\//, ""));
1915
+ const sev = issue.severity === 2 ? import_chalk3.default.red("error") : import_chalk3.default.yellow(" warn");
1916
+ const loc = import_chalk3.default.gray(`${String(issue.line)}:${String(issue.column)}`).padEnd(12);
1917
+ const ruleShort = import_chalk3.default.gray(issue.ruleId.replace(/^ai-guard\//, ""));
1172
1918
  const tier = CONFIDENCE_TIER[issue.ruleId];
1173
- const tierTag = tier === "high" ? "" : tier === "medium" ? "" : import_chalk2.default.dim(" [low]");
1919
+ const tierTag = tier === "informational" ? import_chalk3.default.dim(" [info]") : "";
1174
1920
  log.print(
1175
- ` ${loc} ${sev} ${import_chalk2.default.white(issue.message)}${tierTag} ${ruleShort}`
1921
+ ` ${loc} ${sev} ${import_chalk3.default.white(issue.message)}${tierTag} ${ruleShort}`
1176
1922
  );
1177
1923
  }
1178
1924
  function getRunExitCode(result, maxWarnings) {
@@ -1182,19 +1928,257 @@ function getRunExitCode(result, maxWarnings) {
1182
1928
  }
1183
1929
  function formatIssueCount(errors, warnings) {
1184
1930
  const parts = [];
1185
- if (errors > 0) parts.push(import_chalk2.default.red.bold(`${errors} error${errors !== 1 ? "s" : ""}`));
1186
- if (warnings > 0) parts.push(import_chalk2.default.yellow.bold(`${warnings} warning${warnings !== 1 ? "s" : ""}`));
1187
- return parts.join(import_chalk2.default.gray(" \xB7 "));
1931
+ if (errors > 0) parts.push(import_chalk3.default.red.bold(`${errors} error${errors !== 1 ? "s" : ""}`));
1932
+ if (warnings > 0) parts.push(import_chalk3.default.yellow.bold(`${warnings} warning${warnings !== 1 ? "s" : ""}`));
1933
+ return parts.join(import_chalk3.default.gray(" \xB7 "));
1934
+ }
1935
+ function resolveExitCode(result, failOn, maxWarnings) {
1936
+ if (maxWarnings !== void 0 && result.totalWarnings > maxWarnings) return 1;
1937
+ switch (failOn) {
1938
+ case "none":
1939
+ return 0;
1940
+ case "errors":
1941
+ return result.totalErrors > 0 ? 1 : 0;
1942
+ case "high": {
1943
+ const high = result.files.flatMap((f) => f.issues).filter((i) => CONFIDENCE_TIER[i.ruleId] === "high").length;
1944
+ return high > 0 ? 1 : 0;
1945
+ }
1946
+ case "medium": {
1947
+ const highMed = result.files.flatMap((f) => f.issues).filter((i) => CONFIDENCE_TIER[i.ruleId] === "high" || CONFIDENCE_TIER[i.ruleId] === "medium").length;
1948
+ return highMed > 0 ? 1 : 0;
1949
+ }
1950
+ case "any": {
1951
+ const actionable = result.files.flatMap((f) => f.issues).filter((i) => CONFIDENCE_TIER[i.ruleId] !== "informational").length;
1952
+ return actionable > 0 ? 1 : 0;
1953
+ }
1954
+ default:
1955
+ return getRunExitCode(result, maxWarnings);
1956
+ }
1188
1957
  }
1189
1958
 
1190
- // cli/commands/init.ts
1191
- var import_chalk3 = __toESM(require("chalk"));
1192
- var import_path4 = __toESM(require("path"));
1959
+ // cli/commands/changed.ts
1960
+ var import_path3 = __toESM(require("path"));
1193
1961
  var import_fs4 = __toESM(require("fs"));
1962
+ var import_ora2 = __toESM(require("ora"));
1963
+ var import_chalk4 = __toESM(require("chalk"));
1964
+ function registerChangedCommand(program2) {
1965
+ program2.command("changed").description("Scan only changed files in the current PR or working tree (fastest CI mode)").option("--pr", "Scan files changed vs base branch (auto-detects GITHUB_BASE_REF in CI)").option("--staged", "Scan only staged (git add'd) files").option("--base <branch>", "Base branch to diff against (e.g. main, origin/main)").option("--path <dir>", "Root path / working directory for scanning", ".").option("--strict", "Use the strict rule preset (all rules at error)").option("--security", "Use the security-only rule preset").option("--fail-on <level>", "Fail CI on: high | medium | any | none", "high").option("--json", "Output results as JSON").option("--sarif", "Output results as SARIF 2.1.0 (for GitHub Code Scanning)").option("--sarif-output <file>", "Write SARIF to file instead of stdout").option("--github-summary", "Write GitHub step summary (auto-enabled in GitHub Actions)").option(
1966
+ "--max-warnings <n>",
1967
+ "Fail if warnings exceed this count",
1968
+ (v) => Number.parseInt(v, 10)
1969
+ ).option("--verbose", "Expand all findings (disable grouping)").option("--quiet", "Show errors only").option("--debug-timing", "Print per-phase timing diagnostics").action(async (opts) => {
1970
+ const preset = opts.strict ? "strict" : opts.security ? "security" : "recommended";
1971
+ const inCI = isGitHubActions();
1972
+ const baseRef = opts.base ?? (opts.pr || inCI ? getGitHubBaseRef() : void 0);
1973
+ if (!opts.json && !opts.sarif) {
1974
+ log.banner("AI GUARD");
1975
+ }
1976
+ const cwd = process.cwd();
1977
+ const resolvedPath = import_path3.default.resolve(opts.path);
1978
+ const workingDirectory = opts.path !== "." ? opts.path : void 0;
1979
+ const spinner = opts.json || opts.sarif ? null : (0, import_ora2.default)({ text: import_chalk4.default.dim("Detecting changed files\u2026"), color: "cyan" }).start();
1980
+ const changedResult = getChangedFiles({
1981
+ staged: opts.staged ?? false,
1982
+ base: baseRef ? `origin/${baseRef}` : opts.base,
1983
+ cwd,
1984
+ workingDirectory
1985
+ });
1986
+ if (changedResult.files.length === 0) {
1987
+ spinner?.stop();
1988
+ if (changedResult.mode === "fallback") {
1989
+ if (!opts.json && !opts.sarif) {
1990
+ log.warn("Git not available. Falling back to full scan.");
1991
+ log.blank();
1992
+ }
1993
+ const result2 = await runEslint({
1994
+ preset,
1995
+ targetPath: resolvedPath.toString(),
1996
+ debugTiming: opts.debugTiming
1997
+ });
1998
+ handleResults(result2, opts, preset, "full", 0, inCI);
1999
+ return;
2000
+ }
2001
+ if (!opts.json && !opts.sarif) {
2002
+ log.blank();
2003
+ log.print(` ${import_chalk4.default.green("\u2714")} No changed JS/TS files detected \u2014 nothing to scan`);
2004
+ if (changedResult.filteredOut > 0) {
2005
+ log.print(import_chalk4.default.dim(` (${changedResult.filteredOut} changed file${changedResult.filteredOut !== 1 ? "s" : ""} skipped \u2014 not JS/TS)`));
2006
+ }
2007
+ log.blank();
2008
+ } else if (opts.json) {
2009
+ console.log(JSON.stringify({
2010
+ preset,
2011
+ scanMode: changedResult.mode,
2012
+ changedFiles: 0,
2013
+ filesScanned: 0,
2014
+ totalIssues: 0,
2015
+ totalErrors: 0,
2016
+ totalWarnings: 0,
2017
+ signalSummary: { high: 0, medium: 0, low: 0, informational: 0, ecosystemIssues: 0, parserErrors: 0 }
2018
+ }, null, 2));
2019
+ }
2020
+ process.exit(0);
2021
+ return;
2022
+ }
2023
+ if (spinner) {
2024
+ spinner.text = import_chalk4.default.dim(`Scanning ${changedResult.files.length} changed file${changedResult.files.length !== 1 ? "s" : ""}\u2026`);
2025
+ }
2026
+ const result = await runEslint({
2027
+ preset,
2028
+ targetPath: resolvedPath.toString(),
2029
+ files: changedResult.files,
2030
+ debugTiming: opts.debugTiming
2031
+ });
2032
+ spinner?.stop();
2033
+ handleResults(result, opts, preset, changedResult.mode === "pr-diff" ? "changed" : changedResult.mode === "staged" ? "staged" : "changed", changedResult.files.length, inCI);
2034
+ });
2035
+ }
2036
+ function handleResults(result, opts, preset, scanMode, changedFilesCount, inCI) {
2037
+ if (inCI || opts.githubSummary) {
2038
+ writeGitHubSummary(result, { preset, scanMode, changedFilesCount });
2039
+ writeGitHubOutputs(result, opts.sarifOutput);
2040
+ emitGitHubAnnotations(result);
2041
+ }
2042
+ if (opts.sarif) {
2043
+ const sarifLog = buildSarifLog(result);
2044
+ const sarifJson = sarifToJson(sarifLog);
2045
+ if (opts.sarifOutput) {
2046
+ import_fs4.default.writeFileSync(opts.sarifOutput, sarifJson, "utf-8");
2047
+ } else {
2048
+ console.log(sarifJson);
2049
+ }
2050
+ process.exit(getFailOnExitCode(result, opts.failOn, opts.maxWarnings));
2051
+ return;
2052
+ }
2053
+ if (opts.json) {
2054
+ const signalSummary = buildSignalSummary(result);
2055
+ const jsonOutput = {
2056
+ version: "1.3.0",
2057
+ preset,
2058
+ scanMode,
2059
+ scannedPath: opts.path,
2060
+ filesScanned: result.filesScanned,
2061
+ changedFilesCount,
2062
+ totalErrors: result.totalErrors,
2063
+ totalWarnings: result.totalWarnings,
2064
+ totalIssues: result.totalIssues,
2065
+ durationMs: result.durationMs,
2066
+ signalSummary,
2067
+ ruleBreakdown: Object.fromEntries(result.ruleBreakdown),
2068
+ topFiles: result.topFiles,
2069
+ files: result.files,
2070
+ ecosystemIssues: result.ecosystemIssues,
2071
+ parserErrors: result.parserErrors,
2072
+ tsParserAvailable: result.tsParserAvailable
2073
+ };
2074
+ console.log(JSON.stringify(jsonOutput, null, 2));
2075
+ process.exit(getFailOnExitCode(result, opts.failOn, opts.maxWarnings));
2076
+ return;
2077
+ }
2078
+ const scanModeLabel = scanMode === "changed" ? "Changed files" : scanMode === "staged" ? "Staged files" : "Full scan";
2079
+ const modeTag = changedFilesCount > 0 ? `${changedFilesCount} changed` : "";
2080
+ log.summary(
2081
+ result.filesScanned,
2082
+ result.totalIssues,
2083
+ result.files.filter((f) => f.issues.length > 0).length,
2084
+ result.durationMs,
2085
+ `${preset} \xB7 ${scanModeLabel}${modeTag ? ` (${modeTag})` : ""}`
2086
+ );
2087
+ if (result.totalIssues === 0) {
2088
+ log.blank();
2089
+ log.success(`No AI issues found \u2014 ${result.filesScanned} file${result.filesScanned !== 1 ? "s" : ""} scanned, all clean`);
2090
+ log.blank();
2091
+ } else {
2092
+ const verbose = opts.verbose ?? false;
2093
+ const quiet = opts.quiet ?? false;
2094
+ const { actionableFiles, informationalFiles } = partitionByTier(result, quiet);
2095
+ log.section("Summary by Category");
2096
+ const categoryErrors = {};
2097
+ const categoryWarnings = {};
2098
+ for (const file of actionableFiles) {
2099
+ for (const issue of file.issues) {
2100
+ const cat = RULE_CATEGORY[issue.ruleId] ?? "Other";
2101
+ if (issue.severity === 2) categoryErrors[cat] = (categoryErrors[cat] ?? 0) + 1;
2102
+ else categoryWarnings[cat] = (categoryWarnings[cat] ?? 0) + 1;
2103
+ }
2104
+ }
2105
+ const allCategories = /* @__PURE__ */ new Set([...CATEGORY_ORDER, ...Object.keys(categoryErrors), ...Object.keys(categoryWarnings)]);
2106
+ const sortedCats = [...allCategories].filter((c) => (categoryErrors[c] ?? 0) + (categoryWarnings[c] ?? 0) > 0).sort((a, b) => {
2107
+ const ai = CATEGORY_ORDER.indexOf(a), bi = CATEGORY_ORDER.indexOf(b);
2108
+ if (ai !== -1 && bi !== -1) return ai - bi;
2109
+ if (ai !== -1) return -1;
2110
+ if (bi !== -1) return 1;
2111
+ return a.localeCompare(b);
2112
+ });
2113
+ for (const cat of sortedCats) {
2114
+ const icon = CATEGORY_ICONS[cat] ?? "\u26AA";
2115
+ log.category(icon, cat, categoryErrors[cat] ?? 0, categoryWarnings[cat] ?? 0);
2116
+ }
2117
+ log.blank();
2118
+ log.print(` ${import_chalk4.default.bold("Total:")} ${formatIssueCount(result.totalErrors, result.totalWarnings)}`);
2119
+ log.blank();
2120
+ if (actionableFiles.length > 0) {
2121
+ log.section("Issues by File");
2122
+ log.blank();
2123
+ renderIssuesByFile({ ...result, files: actionableFiles }, verbose, quiet);
2124
+ }
2125
+ const totalInfoCount = informationalFiles.reduce((n, f) => n + f.issues.length, 0);
2126
+ if (totalInfoCount > 0 && !quiet) {
2127
+ if (verbose) {
2128
+ log.section("Informational Hints");
2129
+ log.print(import_chalk4.default.dim(" Stylistic hints with higher false-positive rate \u2014 review before acting."));
2130
+ log.blank();
2131
+ renderIssuesByFile({ ...result, files: informationalFiles }, true, false);
2132
+ } else {
2133
+ log.blank();
2134
+ log.print(` ${import_chalk4.default.gray("\u25B8")} ${import_chalk4.default.gray(`${totalInfoCount} informational hint${totalInfoCount !== 1 ? "s" : ""} \u2014 run with ${import_chalk4.default.cyan("--verbose")} to expand`)}`);
2135
+ }
2136
+ }
2137
+ }
2138
+ if (result.ecosystemIssues.length > 0) {
2139
+ log.ecosystemSection("ESLint Config Issues");
2140
+ log.print(import_chalk4.default.dim(" These issues come from your project's ESLint config, not from ai-guard."));
2141
+ log.blank();
2142
+ const seen = /* @__PURE__ */ new Map();
2143
+ for (const issue of result.ecosystemIssues) {
2144
+ const key = `${issue.ruleId ?? "null"}::${issue.message}`;
2145
+ const existing = seen.get(key);
2146
+ if (existing) {
2147
+ existing.count++;
2148
+ } else {
2149
+ seen.set(key, { issue, count: 1 });
2150
+ }
2151
+ }
2152
+ for (const { issue, count } of seen.values()) {
2153
+ log.ecosystemIssue(issue, count);
2154
+ }
2155
+ log.blank();
2156
+ }
2157
+ log.divider();
2158
+ log.blank();
2159
+ log.section("Next Steps");
2160
+ if (result.totalIssues > 0) {
2161
+ log.info(`Run ${import_chalk4.default.cyan("ai-guard baseline")} to save these issues and track only new ones`);
2162
+ log.info(`Run ${import_chalk4.default.cyan("ai-guard report")} to generate a shareable HTML report`);
2163
+ }
2164
+ if (result.ecosystemIssues.length > 0) {
2165
+ log.info(`Run ${import_chalk4.default.cyan("ai-guard doctor")} to diagnose ESLint config issues`);
2166
+ }
2167
+ log.blank();
2168
+ process.exit(getFailOnExitCode(result, opts.failOn, opts.maxWarnings));
2169
+ }
2170
+ function getFailOnExitCode(result, failOn, maxWarnings) {
2171
+ return resolveExitCode(result, failOn, maxWarnings);
2172
+ }
2173
+
2174
+ // cli/commands/init.ts
2175
+ var import_chalk5 = __toESM(require("chalk"));
2176
+ var import_path6 = __toESM(require("path"));
2177
+ var import_fs7 = __toESM(require("fs"));
1194
2178
 
1195
2179
  // cli/utils/detector.ts
1196
- var import_fs2 = __toESM(require("fs"));
1197
- var import_path2 = __toESM(require("path"));
2180
+ var import_fs5 = __toESM(require("fs"));
2181
+ var import_path4 = __toESM(require("path"));
1198
2182
  var import_module = require("module");
1199
2183
  var FLAT_CONFIG_FILES = [
1200
2184
  { file: "eslint.config.js", type: "flat-js" },
@@ -1211,11 +2195,11 @@ var LEGACY_CONFIG_FILES = [
1211
2195
  var ALL_CONFIG_FILES = [...FLAT_CONFIG_FILES, ...LEGACY_CONFIG_FILES];
1212
2196
  function isPackageInstalled(pkgName, cwd = process.cwd()) {
1213
2197
  try {
1214
- const pkgPath = import_path2.default.join(cwd, "node_modules", pkgName, "package.json");
1215
- if (import_fs2.default.existsSync(pkgPath)) {
2198
+ const pkgPath = import_path4.default.join(cwd, "node_modules", pkgName, "package.json");
2199
+ if (import_fs5.default.existsSync(pkgPath)) {
1216
2200
  return true;
1217
2201
  }
1218
- const req = (0, import_module.createRequire)(import_path2.default.join(cwd, "package.json"));
2202
+ const req = (0, import_module.createRequire)(import_path4.default.join(cwd, "package.json"));
1219
2203
  req.resolve(`${pkgName}/package.json`);
1220
2204
  return true;
1221
2205
  } catch {
@@ -1224,9 +2208,9 @@ function isPackageInstalled(pkgName, cwd = process.cwd()) {
1224
2208
  }
1225
2209
  function getPackageVersion(pkgName, cwd = process.cwd()) {
1226
2210
  try {
1227
- const pkgPath = import_path2.default.join(cwd, "node_modules", pkgName, "package.json");
1228
- const resolvedPath = import_fs2.default.existsSync(pkgPath) ? pkgPath : (0, import_module.createRequire)(import_path2.default.join(cwd, "package.json")).resolve(`${pkgName}/package.json`);
1229
- const raw = import_fs2.default.readFileSync(resolvedPath, "utf-8");
2211
+ const pkgPath = import_path4.default.join(cwd, "node_modules", pkgName, "package.json");
2212
+ const resolvedPath = import_fs5.default.existsSync(pkgPath) ? pkgPath : (0, import_module.createRequire)(import_path4.default.join(cwd, "package.json")).resolve(`${pkgName}/package.json`);
2213
+ const raw = import_fs5.default.readFileSync(resolvedPath, "utf-8");
1230
2214
  const json = JSON.parse(raw);
1231
2215
  return json.version ?? null;
1232
2216
  } catch {
@@ -1242,8 +2226,8 @@ function detectConfigType(cwd = process.cwd()) {
1242
2226
  const found = [];
1243
2227
  let first = null;
1244
2228
  for (const { file, type } of ALL_CONFIG_FILES) {
1245
- const fullPath = import_path2.default.join(cwd, file);
1246
- if (import_fs2.default.existsSync(fullPath)) {
2229
+ const fullPath = import_path4.default.join(cwd, file);
2230
+ if (import_fs5.default.existsSync(fullPath)) {
1247
2231
  found.push(fullPath);
1248
2232
  if (!first) first = { type, path: fullPath };
1249
2233
  }
@@ -1266,7 +2250,7 @@ function requiresFlatConfig(eslintMajor) {
1266
2250
  function detectNukeIgnore(configPath) {
1267
2251
  if (!configPath) return false;
1268
2252
  try {
1269
- const content = import_fs2.default.readFileSync(configPath, "utf-8");
2253
+ const content = import_fs5.default.readFileSync(configPath, "utf-8");
1270
2254
  return content.includes('"**/*"') || content.includes("'**/*'");
1271
2255
  } catch {
1272
2256
  return false;
@@ -1322,7 +2306,7 @@ async function verifyConfigLoads(configPath, cwd) {
1322
2306
  overrideConfigFile: configPath
1323
2307
  });
1324
2308
  await eslint.lintText("", {
1325
- filePath: import_path4.default.join(cwd, "_ai_guard_probe_.js")
2309
+ filePath: import_path6.default.join(cwd, "_ai_guard_probe_.js")
1326
2310
  });
1327
2311
  return null;
1328
2312
  } catch (err) {
@@ -1340,7 +2324,7 @@ function registerInitCommand(program2) {
1340
2324
  const isDryRun = opts.dryRun === true;
1341
2325
  log.banner("AI GUARD INIT");
1342
2326
  if (isDryRun) {
1343
- log.print(` ${import_chalk3.default.yellow("\u2691 DRY RUN \u2014 no files will be written")}`);
2327
+ log.print(` ${import_chalk5.default.yellow("\u2691 DRY RUN \u2014 no files will be written")}`);
1344
2328
  log.blank();
1345
2329
  }
1346
2330
  log.info("Detecting your project setup\u2026");
@@ -1358,7 +2342,7 @@ function registerInitCommand(program2) {
1358
2342
  log.warn("eslint-plugin-ai-guard not found");
1359
2343
  }
1360
2344
  if (env.configType !== "none") {
1361
- log.success(`ESLint config found: ${import_chalk3.default.white(import_path4.default.relative(cwd, env.configPath ?? env.configType))}`);
2345
+ log.success(`ESLint config found: ${import_chalk5.default.white(import_path6.default.relative(cwd, env.configPath ?? env.configType))}`);
1362
2346
  } else {
1363
2347
  log.info("No ESLint config found \u2014 will generate one");
1364
2348
  }
@@ -1367,9 +2351,9 @@ function registerInitCommand(program2) {
1367
2351
  log.blank();
1368
2352
  log.warn("Conflicting configs detected:");
1369
2353
  for (const p of env.allConfigPaths) {
1370
- log.print(` ${import_chalk3.default.yellow("\u2192")} ${import_chalk3.default.white(import_path4.default.relative(cwd, p))}`);
2354
+ log.print(` ${import_chalk5.default.yellow("\u2192")} ${import_chalk5.default.white(import_path6.default.relative(cwd, p))}`);
1371
2355
  }
1372
- log.print(` ${import_chalk3.default.gray("Multiple config formats found \u2014 this can cause unpredictable behavior.")}`);
2356
+ log.print(` ${import_chalk5.default.gray("Multiple config formats found \u2014 this can cause unpredictable behavior.")}`);
1373
2357
  }
1374
2358
  log.blank();
1375
2359
  const toInstall = [];
@@ -1380,14 +2364,14 @@ function registerInitCommand(program2) {
1380
2364
  log.warn("The following packages are not installed:");
1381
2365
  log.blank();
1382
2366
  for (const pkg of toInstall) {
1383
- log.print(` ${import_chalk3.default.yellow("\u2192")} ${import_chalk3.default.white(pkg)}`);
2367
+ log.print(` ${import_chalk5.default.yellow("\u2192")} ${import_chalk5.default.white(pkg)}`);
1384
2368
  }
1385
2369
  log.blank();
1386
- log.print(` ${import_chalk3.default.bold("Run this first:")}`);
2370
+ log.print(` ${import_chalk5.default.bold("Run this first:")}`);
1387
2371
  log.blank();
1388
- log.print(` ${import_chalk3.default.cyan(`npm install --save-dev ${toInstall.join(" ")}`)}`);
2372
+ log.print(` ${import_chalk5.default.cyan(`npm install --save-dev ${toInstall.join(" ")}`)}`);
1389
2373
  log.blank();
1390
- log.print(` Then re-run ${import_chalk3.default.cyan("ai-guard init")} to complete setup.`);
2374
+ log.print(` Then re-run ${import_chalk5.default.cyan("ai-guard init")} to complete setup.`);
1391
2375
  log.blank();
1392
2376
  process.exit(1);
1393
2377
  return;
@@ -1395,7 +2379,7 @@ function registerInitCommand(program2) {
1395
2379
  try {
1396
2380
  const { createRequire: createRequire2 } = await import("module");
1397
2381
  const { pathToFileURL: pathToFileURL2 } = await import("url");
1398
- const req = createRequire2(import_path4.default.join(cwd, "_probe.js"));
2382
+ const req = createRequire2(import_path6.default.join(cwd, "_probe.js"));
1399
2383
  const resolvedPath = req.resolve("eslint-plugin-ai-guard");
1400
2384
  const aiGuard = await import(pathToFileURL2(resolvedPath).href);
1401
2385
  const plugin = aiGuard.default || aiGuard;
@@ -1414,9 +2398,9 @@ function registerInitCommand(program2) {
1414
2398
  if (eslintNeedsFlat && existingIsLegacy) {
1415
2399
  log.section("Config Migration Required");
1416
2400
  log.warn(`ESLint v${env.eslintMajor} requires flat config, but a legacy config was found:`);
1417
- log.print(` ${import_chalk3.default.white(import_path4.default.relative(cwd, env.configPath))}`);
2401
+ log.print(` ${import_chalk5.default.white(import_path6.default.relative(cwd, env.configPath))}`);
1418
2402
  log.blank();
1419
- log.print(` ${import_chalk3.default.bold("ai-guard will create a new flat config.")}`);
2403
+ log.print(` ${import_chalk5.default.bold("ai-guard will create a new flat config.")}`);
1420
2404
  log.blank();
1421
2405
  }
1422
2406
  log.section(`Configuring ESLint${isDryRun ? " (dry run)" : ""}`);
@@ -1426,34 +2410,34 @@ function registerInitCommand(program2) {
1426
2410
  const raw = readConfig(env.configPath);
1427
2411
  const { content: fixed, changed } = removeNukeIgnore(raw);
1428
2412
  if (changed) {
1429
- log.warn(`Removed "**/*" ignore pattern from ${import_path4.default.relative(cwd, env.configPath)}`);
2413
+ log.warn(`Removed "**/*" ignore pattern from ${import_path6.default.relative(cwd, env.configPath)}`);
1430
2414
  if (!isDryRun) {
1431
2415
  backupConfig(env.configPath);
1432
2416
  writeConfig(env.configPath, fixed);
1433
2417
  log.success("Ignore pattern fixed");
1434
2418
  } else {
1435
- log.print(` ${import_chalk3.default.gray("[dry-run] would remove nuke-ignore from existing config")}`);
2419
+ log.print(` ${import_chalk5.default.gray("[dry-run] would remove nuke-ignore from existing config")}`);
1436
2420
  }
1437
2421
  }
1438
2422
  }
1439
2423
  if (env.configType === "none" || eslintNeedsFlat && existingIsLegacy) {
1440
2424
  const targetName = useFlat ? "eslint.config.mjs" : ".eslintrc.js";
1441
- finalConfigPath = import_path4.default.join(cwd, targetName);
2425
+ finalConfigPath = import_path6.default.join(cwd, targetName);
1442
2426
  const content2 = useFlat ? generateFlatConfig(preset) : generateLegacyConfig(preset);
1443
2427
  if (isDryRun) {
1444
- log.print(` ${import_chalk3.default.gray(`[dry-run] would create: ${targetName}`)}`);
2428
+ log.print(` ${import_chalk5.default.gray(`[dry-run] would create: ${targetName}`)}`);
1445
2429
  log.blank();
1446
- log.print(import_chalk3.default.gray("\u2500\u2500\u2500 Preview \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\u2500\u2500\u2500\u2500"));
2430
+ log.print(import_chalk5.default.gray("\u2500\u2500\u2500 Preview \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\u2500\u2500\u2500\u2500"));
1447
2431
  log.blank();
1448
2432
  for (const line of content2.split("\n")) {
1449
- log.print(import_chalk3.default.gray(` ${line}`));
2433
+ log.print(import_chalk5.default.gray(` ${line}`));
1450
2434
  }
1451
2435
  log.blank();
1452
2436
  } else {
1453
2437
  writeConfig(finalConfigPath, content2);
1454
- log.success(`Created ${import_chalk3.default.white(targetName)}`);
1455
- log.info(` Preset: ${import_chalk3.default.cyan(preset)}`);
1456
- log.info(` Format: ${import_chalk3.default.cyan(useFlat ? "ESLint v9 flat config (eslint.config.mjs)" : "ESLint v8 legacy config (.eslintrc.js)")}`);
2438
+ log.success(`Created ${import_chalk5.default.white(targetName)}`);
2439
+ log.info(` Preset: ${import_chalk5.default.cyan(preset)}`);
2440
+ log.info(` Format: ${import_chalk5.default.cyan(useFlat ? "ESLint v9 flat config (eslint.config.mjs)" : "ESLint v8 legacy config (.eslintrc.js)")}`);
1457
2441
  configWritten = true;
1458
2442
  }
1459
2443
  } else if (useFlat && existingIsFlat) {
@@ -1466,15 +2450,15 @@ function registerInitCommand(program2) {
1466
2450
  finalConfigPath = env.configPath;
1467
2451
  } else if (isDryRun) {
1468
2452
  finalConfigPath = env.configPath;
1469
- log.print(` ${import_chalk3.default.gray(`[dry-run] would patch: ${import_path4.default.relative(cwd, env.configPath)}`)}`);
2453
+ log.print(` ${import_chalk5.default.gray(`[dry-run] would patch: ${import_path6.default.relative(cwd, env.configPath)}`)}`);
1470
2454
  } else {
1471
2455
  const bak = backupConfig(env.configPath);
1472
- log.info(`Backed up \u2192 ${import_chalk3.default.gray(import_path4.default.relative(cwd, bak))}`);
2456
+ log.info(`Backed up \u2192 ${import_chalk5.default.gray(import_path6.default.relative(cwd, bak))}`);
1473
2457
  writeConfig(env.configPath, patched);
1474
2458
  if (isInvalid) {
1475
- log.success(`Detected invalid ai-guard config \u2014 repairing ${import_chalk3.default.white(import_path4.default.relative(cwd, env.configPath))}`);
2459
+ log.success(`Detected invalid ai-guard config \u2014 repairing ${import_chalk5.default.white(import_path6.default.relative(cwd, env.configPath))}`);
1476
2460
  } else {
1477
- log.success(`Patched ${import_chalk3.default.white(import_path4.default.relative(cwd, env.configPath))}`);
2461
+ log.success(`Patched ${import_chalk5.default.white(import_path6.default.relative(cwd, env.configPath))}`);
1478
2462
  }
1479
2463
  finalConfigPath = env.configPath;
1480
2464
  configWritten = true;
@@ -1487,12 +2471,12 @@ function registerInitCommand(program2) {
1487
2471
  finalConfigPath = env.configPath;
1488
2472
  } else if (isDryRun) {
1489
2473
  finalConfigPath = env.configPath;
1490
- log.print(` ${import_chalk3.default.gray(`[dry-run] would patch: ${import_path4.default.relative(cwd, env.configPath)}`)}`);
2474
+ log.print(` ${import_chalk5.default.gray(`[dry-run] would patch: ${import_path6.default.relative(cwd, env.configPath)}`)}`);
1491
2475
  } else {
1492
2476
  const bak = backupConfig(env.configPath);
1493
- log.info(`Backed up \u2192 ${import_chalk3.default.gray(import_path4.default.relative(cwd, bak))}`);
2477
+ log.info(`Backed up \u2192 ${import_chalk5.default.gray(import_path6.default.relative(cwd, bak))}`);
1494
2478
  writeConfig(env.configPath, patched);
1495
- log.success(`Patched ${import_chalk3.default.white(import_path4.default.relative(cwd, env.configPath))}`);
2479
+ log.success(`Patched ${import_chalk5.default.white(import_path6.default.relative(cwd, env.configPath))}`);
1496
2480
  finalConfigPath = env.configPath;
1497
2481
  configWritten = true;
1498
2482
  }
@@ -1501,18 +2485,18 @@ function registerInitCommand(program2) {
1501
2485
  const legacyConfigs = env.allConfigPaths.filter((p) => !p.includes("eslint.config."));
1502
2486
  let cleanedUp = false;
1503
2487
  for (const p of legacyConfigs) {
1504
- if (import_fs4.default.existsSync(p)) {
2488
+ if (import_fs7.default.existsSync(p)) {
1505
2489
  if (!isDryRun) {
1506
2490
  try {
1507
- import_fs4.default.renameSync(p, `${p}.bak`);
2491
+ import_fs7.default.renameSync(p, `${p}.bak`);
1508
2492
  cleanedUp = true;
1509
2493
  } catch (err) {
1510
2494
  const reason = err instanceof Error ? err.message : String(err);
1511
- log.warn(`Could not back up ${import_path4.default.relative(cwd, p)}: ${reason}`);
2495
+ log.warn(`Could not back up ${import_path6.default.relative(cwd, p)}: ${reason}`);
1512
2496
  }
1513
2497
  } else {
1514
2498
  cleanedUp = true;
1515
- log.print(` ${import_chalk3.default.gray(`[dry-run] would rename ${import_path4.default.relative(cwd, p)} to ${import_path4.default.basename(p)}.bak`)}`);
2499
+ log.print(` ${import_chalk5.default.gray(`[dry-run] would rename ${import_path6.default.relative(cwd, p)} to ${import_path6.default.basename(p)}.bak`)}`);
1516
2500
  }
1517
2501
  }
1518
2502
  }
@@ -1522,19 +2506,19 @@ function registerInitCommand(program2) {
1522
2506
  }
1523
2507
  if (isDryRun) {
1524
2508
  log.blank();
1525
- log.print(` ${import_chalk3.default.yellow("Dry run complete \u2014 no files written.")}`);
1526
- log.print(` Re-run without ${import_chalk3.default.cyan("--dry-run")} to apply.`);
2509
+ log.print(` ${import_chalk5.default.yellow("Dry run complete \u2014 no files written.")}`);
2510
+ log.print(` Re-run without ${import_chalk5.default.cyan("--dry-run")} to apply.`);
1527
2511
  log.blank();
1528
2512
  process.exit(0);
1529
2513
  return;
1530
2514
  }
1531
2515
  log.blank();
1532
2516
  log.section("Validation");
1533
- if (!import_fs4.default.existsSync(finalConfigPath)) {
1534
- log.error(`Config file not found after write: ${import_path4.default.relative(cwd, finalConfigPath)}`);
2517
+ if (!import_fs7.default.existsSync(finalConfigPath)) {
2518
+ log.error(`Config file not found after write: ${import_path6.default.relative(cwd, finalConfigPath)}`);
1535
2519
  log.blank();
1536
- log.print(` ${import_chalk3.default.bold("Problem:")} File write may have failed.`);
1537
- log.print(` ${import_chalk3.default.bold("Fix:")} Check disk space and file permissions, then re-run.`);
2520
+ log.print(` ${import_chalk5.default.bold("Problem:")} File write may have failed.`);
2521
+ log.print(` ${import_chalk5.default.bold("Fix:")} Check disk space and file permissions, then re-run.`);
1538
2522
  log.blank();
1539
2523
  process.exit(1);
1540
2524
  return;
@@ -1544,36 +2528,36 @@ function registerInitCommand(program2) {
1544
2528
  if (validationErrors.length > 0) {
1545
2529
  log.warn("Config validation found issues:");
1546
2530
  for (const e of validationErrors) {
1547
- log.print(` ${import_chalk3.default.red("\u2716")} ${e}`);
2531
+ log.print(` ${import_chalk5.default.red("\u2716")} ${e}`);
1548
2532
  }
1549
2533
  } else {
1550
- log.success(`Config structure valid: ${import_chalk3.default.white(import_path4.default.relative(cwd, finalConfigPath))}`);
2534
+ log.success(`Config structure valid: ${import_chalk5.default.white(import_path6.default.relative(cwd, finalConfigPath))}`);
1551
2535
  }
1552
2536
  log.info("Verifying ESLint can load the config\u2026");
1553
2537
  const loadError = await verifyConfigLoads(finalConfigPath, cwd);
1554
2538
  if (loadError !== null) {
1555
2539
  log.blank();
1556
2540
  if (loadError.kind === "module-not-found") {
1557
- log.warn(`Could not verify: module not found: ${import_chalk3.default.yellow(loadError.pkg)}`);
2541
+ log.warn(`Could not verify: module not found: ${import_chalk5.default.yellow(loadError.pkg)}`);
1558
2542
  log.blank();
1559
- log.print(` ${import_chalk3.default.bold("Install the missing package:")}`);
1560
- log.print(` ${import_chalk3.default.cyan(`npm install --save-dev ${loadError.pkg}`)}`);
1561
- log.print(` Then run ${import_chalk3.default.cyan("ai-guard doctor")} to confirm.`);
2543
+ log.print(` ${import_chalk5.default.bold("Install the missing package:")}`);
2544
+ log.print(` ${import_chalk5.default.cyan(`npm install --save-dev ${loadError.pkg}`)}`);
2545
+ log.print(` Then run ${import_chalk5.default.cyan("ai-guard doctor")} to confirm.`);
1562
2546
  log.blank();
1563
2547
  } else {
1564
2548
  log.error("ESLint failed to load the generated config.");
1565
2549
  log.blank();
1566
- log.print(` ${import_chalk3.default.bold("Problem:")}`);
1567
- log.print(` ${import_chalk3.default.red(loadError.message)}`);
2550
+ log.print(` ${import_chalk5.default.bold("Problem:")}`);
2551
+ log.print(` ${import_chalk5.default.red(loadError.message)}`);
1568
2552
  if (loadError.kind === "unknown") {
1569
2553
  log.blank();
1570
- log.print(` ${import_chalk3.default.yellow("Generated config may be using incorrect plugin structure.")}`);
2554
+ log.print(` ${import_chalk5.default.yellow("Generated config may be using incorrect plugin structure.")}`);
1571
2555
  }
1572
2556
  log.blank();
1573
- log.print(` ${import_chalk3.default.bold("Fix:")}`);
1574
- log.print(` 1. Check the syntax in ${import_chalk3.default.cyan(import_path4.default.relative(cwd, finalConfigPath))}`);
1575
- log.print(` 2. Make sure ${import_chalk3.default.cyan("eslint-plugin-ai-guard")} is installed`);
1576
- log.print(` 3. Run ${import_chalk3.default.cyan("ai-guard doctor")} for full diagnostics`);
2557
+ log.print(` ${import_chalk5.default.bold("Fix:")}`);
2558
+ log.print(` 1. Check the syntax in ${import_chalk5.default.cyan(import_path6.default.relative(cwd, finalConfigPath))}`);
2559
+ log.print(` 2. Make sure ${import_chalk5.default.cyan("eslint-plugin-ai-guard")} is installed`);
2560
+ log.print(` 3. Run ${import_chalk5.default.cyan("ai-guard doctor")} for full diagnostics`);
1577
2561
  log.blank();
1578
2562
  process.exit(1);
1579
2563
  return;
@@ -1587,34 +2571,34 @@ function registerInitCommand(program2) {
1587
2571
  let cleanedUpInfo = false;
1588
2572
  if (eslintNeedsFlat) {
1589
2573
  const legacyConfigs = env.allConfigPaths.filter((p) => !p.includes("eslint.config."));
1590
- cleanedUpInfo = legacyConfigs.some((p) => import_fs4.default.existsSync(`${p}.bak`) && !import_fs4.default.existsSync(p));
2574
+ cleanedUpInfo = legacyConfigs.some((p) => import_fs7.default.existsSync(`${p}.bak`) && !import_fs7.default.existsSync(p));
1591
2575
  }
1592
2576
  const lingeringConflicts = env.hasConflictingConfigs && !eslintNeedsFlat && !cleanedUpInfo;
1593
2577
  if (loadError === null && !lingeringConflicts) {
1594
- log.print(` ${import_chalk3.default.green("\u2714")} ${import_chalk3.default.bold.green("Configuration validated successfully")}`);
2578
+ log.print(` ${import_chalk5.default.green("\u2714")} ${import_chalk5.default.bold.green("Configuration validated successfully")}`);
1595
2579
  }
1596
- log.print(` ${import_chalk3.default.green("\u2714")} ${import_chalk3.default.bold("ESLint config:")} ${import_chalk3.default.cyan(import_path4.default.relative(cwd, finalConfigPath))}`);
1597
- log.print(` ${import_chalk3.default.green("\u2714")} ${import_chalk3.default.bold("Mode:")} ${import_chalk3.default.cyan(useFlat ? `Flat config (ESLint v${env.eslintMajor ?? 9})` : `Legacy config (ESLint v${env.eslintMajor ?? 8})`)}`);
1598
- log.print(` ${import_chalk3.default.green("\u2714")} ${import_chalk3.default.bold("Preset:")} ${import_chalk3.default.cyan(preset)}`);
2580
+ log.print(` ${import_chalk5.default.green("\u2714")} ${import_chalk5.default.bold("ESLint config:")} ${import_chalk5.default.cyan(import_path6.default.relative(cwd, finalConfigPath))}`);
2581
+ log.print(` ${import_chalk5.default.green("\u2714")} ${import_chalk5.default.bold("Mode:")} ${import_chalk5.default.cyan(useFlat ? `Flat config (ESLint v${env.eslintMajor ?? 9})` : `Legacy config (ESLint v${env.eslintMajor ?? 8})`)}`);
2582
+ log.print(` ${import_chalk5.default.green("\u2714")} ${import_chalk5.default.bold("Preset:")} ${import_chalk5.default.cyan(preset)}`);
1599
2583
  log.blank();
1600
- log.info(`Run ${import_chalk3.default.cyan("npx eslint .")} \u2192 lint with your editor integration`);
1601
- log.info(`Run ${import_chalk3.default.cyan("ai-guard run")} \u2192 zero-config scan`);
1602
- log.info(`Run ${import_chalk3.default.cyan("ai-guard doctor")} \u2192 verify the full setup`);
1603
- log.info(`Run ${import_chalk3.default.cyan("ai-guard baseline")} \u2192 save baseline, track only new issues`);
2584
+ log.info(`Run ${import_chalk5.default.cyan("npx eslint .")} \u2192 lint with your editor integration`);
2585
+ log.info(`Run ${import_chalk5.default.cyan("ai-guard run")} \u2192 zero-config scan`);
2586
+ log.info(`Run ${import_chalk5.default.cyan("ai-guard doctor")} \u2192 verify the full setup`);
2587
+ log.info(`Run ${import_chalk5.default.cyan("ai-guard baseline")} \u2192 save baseline, track only new issues`);
1604
2588
  log.blank();
1605
2589
  });
1606
2590
  }
1607
2591
 
1608
2592
  // cli/commands/doctor.ts
1609
- var import_chalk4 = __toESM(require("chalk"));
1610
- var import_path5 = __toESM(require("path"));
2593
+ var import_chalk6 = __toESM(require("chalk"));
2594
+ var import_path7 = __toESM(require("path"));
1611
2595
  init_config_manager();
1612
2596
  var import_meta = {};
1613
2597
  async function probeConfigLoad(configPath, cwd) {
1614
2598
  try {
1615
2599
  const { ESLint } = await import("eslint");
1616
2600
  const eslint = new ESLint({ cwd, overrideConfigFile: configPath });
1617
- await eslint.lintText("", { filePath: import_path5.default.join(cwd, "_probe_.js") });
2601
+ await eslint.lintText("", { filePath: import_path7.default.join(cwd, "_probe_.js") });
1618
2602
  return { ok: true, error: null };
1619
2603
  } catch (err) {
1620
2604
  const msg = err instanceof Error ? err.message : String(err);
@@ -1649,7 +2633,7 @@ function registerDoctorCommand(program2) {
1649
2633
  checks.push({
1650
2634
  label: "ESLint config present",
1651
2635
  pass: hasConfig,
1652
- detail: hasConfig ? `${import_path5.default.relative(cwd, env.configPath ?? "")} (${env.configType})` : "No eslint.config.* or .eslintrc.* found",
2636
+ detail: hasConfig ? `${import_path7.default.relative(cwd, env.configPath ?? "")} (${env.configType})` : "No eslint.config.* or .eslintrc.* found",
1653
2637
  fix: hasConfig ? void 0 : "ai-guard init",
1654
2638
  note: hasConfig ? void 0 : "This will generate the correct config format for your ESLint version"
1655
2639
  });
@@ -1681,7 +2665,7 @@ function registerDoctorCommand(program2) {
1681
2665
  checks.push({
1682
2666
  label: "Plugin wired in config",
1683
2667
  pass: pluginWired && !isInvalidConfig,
1684
- 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_path5.default.relative(cwd, env.configPath)}`,
2668
+ 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_path7.default.relative(cwd, env.configPath)}`,
1685
2669
  fix: isInvalidConfig || !pluginWired && !readError ? "ai-guard init (will patch/repair your existing config)" : void 0
1686
2670
  });
1687
2671
  }
@@ -1689,7 +2673,7 @@ function registerDoctorCommand(program2) {
1689
2673
  label: "No conflicting config files",
1690
2674
  pass: !env.hasConflictingConfigs,
1691
2675
  detail: env.hasConflictingConfigs ? `Conflicting config detected:
1692
- ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}`).join("\n")}` : "Only one config format detected",
2676
+ ${env.allConfigPaths.map((p) => ` ${import_path7.default.relative(cwd, p)}`).join("\n")}` : "Only one config format detected",
1693
2677
  fix: env.hasConflictingConfigs ? "Remove or backup legacy config" : void 0,
1694
2678
  note: env.hasConflictingConfigs ? "ESLint v9 silently ignores .eslintrc.* when a flat config exists. This can cause confusion." : void 0
1695
2679
  });
@@ -1716,7 +2700,7 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1716
2700
  label: "ESLint config loads without errors",
1717
2701
  pass: probe.ok,
1718
2702
  detail: probe.ok ? "ESLint loaded config successfully" : probe.error ?? "Unknown error",
1719
- fix: probe.ok ? void 0 : `Check syntax in ${import_path5.default.relative(cwd, env.configPath)}`,
2703
+ fix: probe.ok ? void 0 : `Check syntax in ${import_path7.default.relative(cwd, env.configPath)}`,
1720
2704
  note: probe.ok ? void 0 : "Run: ai-guard init --dry-run to preview a fresh config"
1721
2705
  });
1722
2706
  }
@@ -1725,7 +2709,7 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1725
2709
  let utilsVersion = null;
1726
2710
  try {
1727
2711
  const { createRequire: createRequire2 } = await import("module");
1728
- const req = createRequire2(import_path5.default.join(cwd, "package.json"));
2712
+ const req = createRequire2(import_path7.default.join(cwd, "package.json"));
1729
2713
  const pkgPath = req.resolve("@typescript-eslint/utils/package.json");
1730
2714
  const pkg = JSON.parse(
1731
2715
  (await import("fs")).readFileSync(pkgPath, "utf-8")
@@ -1736,10 +2720,10 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1736
2720
  }
1737
2721
  if (!utilsOk) {
1738
2722
  try {
1739
- const selfDir = import_path5.default.dirname(
2723
+ const selfDir = import_path7.default.dirname(
1740
2724
  new URL(import_meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")
1741
2725
  );
1742
- const pkgPath = import_path5.default.join(selfDir, "../../node_modules/@typescript-eslint/utils/package.json");
2726
+ const pkgPath = import_path7.default.join(selfDir, "../../node_modules/@typescript-eslint/utils/package.json");
1743
2727
  if ((await import("fs")).existsSync(pkgPath)) {
1744
2728
  utilsOk = true;
1745
2729
  }
@@ -1759,7 +2743,7 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1759
2743
  let parserVersion = null;
1760
2744
  try {
1761
2745
  const { createRequire: createRequire2 } = await import("module");
1762
- const req = createRequire2(import_path5.default.join(cwd, "package.json"));
2746
+ const req = createRequire2(import_path7.default.join(cwd, "package.json"));
1763
2747
  const pkgPath = req.resolve("@typescript-eslint/parser/package.json");
1764
2748
  const pkg = JSON.parse(
1765
2749
  (await import("fs")).readFileSync(pkgPath, "utf-8")
@@ -1790,17 +2774,17 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1790
2774
  log.section("Diagnostics");
1791
2775
  let allPassed = true;
1792
2776
  for (const check of checks) {
1793
- const icon = check.pass ? import_chalk4.default.green("\u2714") : import_chalk4.default.red("\u2716");
1794
- const label = check.pass ? import_chalk4.default.white(check.label) : import_chalk4.default.red.bold(check.label);
2777
+ const icon = check.pass ? import_chalk6.default.green("\u2714") : import_chalk6.default.red("\u2716");
2778
+ const label = check.pass ? import_chalk6.default.white(check.label) : import_chalk6.default.red.bold(check.label);
1795
2779
  log.print(` ${icon} ${label}`);
1796
- log.print(` ${import_chalk4.default.gray(check.detail)}`);
2780
+ log.print(` ${import_chalk6.default.gray(check.detail)}`);
1797
2781
  if (!check.pass) {
1798
2782
  allPassed = false;
1799
2783
  if (check.fix) {
1800
- log.print(` ${import_chalk4.default.cyan("\u2192 Fix:")} ${import_chalk4.default.yellow(check.fix)}`);
2784
+ log.print(` ${import_chalk6.default.cyan("\u2192 Fix:")} ${import_chalk6.default.yellow(check.fix)}`);
1801
2785
  }
1802
2786
  if (check.note) {
1803
- log.print(` ${import_chalk4.default.gray(check.note)}`);
2787
+ log.print(` ${import_chalk6.default.gray(check.note)}`);
1804
2788
  }
1805
2789
  }
1806
2790
  log.blank();
@@ -1808,10 +2792,10 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1808
2792
  log.divider();
1809
2793
  log.blank();
1810
2794
  if (allPassed) {
1811
- log.print(` ${import_chalk4.default.green("\u2714")} ${import_chalk4.default.bold.green("All checks passed! Your setup is correct.")}`);
2795
+ log.print(` ${import_chalk6.default.green("\u2714")} ${import_chalk6.default.bold.green("All checks passed! Your setup is correct.")}`);
1812
2796
  log.blank();
1813
- log.info(`Run ${import_chalk4.default.cyan("ai-guard run")} to start scanning.`);
1814
- log.info(`Run ${import_chalk4.default.cyan("npx eslint .")} to lint with ESLint directly.`);
2797
+ log.info(`Run ${import_chalk6.default.cyan("ai-guard run")} to start scanning.`);
2798
+ log.info(`Run ${import_chalk6.default.cyan("npx eslint .")} to lint with ESLint directly.`);
1815
2799
  } else {
1816
2800
  log.error("Some checks failed. Follow the fixes above.");
1817
2801
  log.blank();
@@ -1826,15 +2810,15 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1826
2810
  );
1827
2811
  if (pluginMissing) {
1828
2812
  log.print(
1829
- ` Install missing packages first, then re-run ${import_chalk4.default.cyan("ai-guard doctor")}.`
2813
+ ` Install missing packages first, then re-run ${import_chalk6.default.cyan("ai-guard doctor")}.`
1830
2814
  );
1831
2815
  } else if (configMissing || configFormatMismatch) {
1832
2816
  log.print(
1833
- ` Run ${import_chalk4.default.cyan("ai-guard init")} to generate the correct config for your ESLint version.`
2817
+ ` Run ${import_chalk6.default.cyan("ai-guard init")} to generate the correct config for your ESLint version.`
1834
2818
  );
1835
2819
  } else {
1836
2820
  log.print(
1837
- ` Run ${import_chalk4.default.cyan("ai-guard init --dry-run")} to preview what would change.`
2821
+ ` Run ${import_chalk6.default.cyan("ai-guard init --dry-run")} to preview what would change.`
1838
2822
  );
1839
2823
  }
1840
2824
  }
@@ -1845,9 +2829,9 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1845
2829
 
1846
2830
  // cli/commands/preset.ts
1847
2831
  var import_prompts = require("@inquirer/prompts");
1848
- var import_chalk5 = __toESM(require("chalk"));
2832
+ var import_chalk7 = __toESM(require("chalk"));
1849
2833
  init_config_manager();
1850
- var import_path6 = __toESM(require("path"));
2834
+ var import_path8 = __toESM(require("path"));
1851
2835
  function registerPresetCommand(program2) {
1852
2836
  program2.command("preset").description("Interactively select and apply an ai-guard preset to your ESLint config").action(async () => {
1853
2837
  const cwd = process.cwd();
@@ -1857,32 +2841,32 @@ function registerPresetCommand(program2) {
1857
2841
  message: "Choose a preset:",
1858
2842
  choices: [
1859
2843
  {
1860
- name: `${import_chalk5.default.green("recommended")} \u2014 balanced defaults, low noise. Best starting point.`,
2844
+ name: `${import_chalk7.default.green("recommended")} \u2014 balanced defaults, low noise. Best starting point.`,
1861
2845
  value: "recommended"
1862
2846
  },
1863
2847
  {
1864
- name: `${import_chalk5.default.yellow("strict")} \u2014 all rules at error. For mature codebases.`,
2848
+ name: `${import_chalk7.default.yellow("strict")} \u2014 all rules at error. For mature codebases.`,
1865
2849
  value: "strict"
1866
2850
  },
1867
2851
  {
1868
- name: `${import_chalk5.default.red("security")} \u2014 security rules only. For AppSec teams.`,
2852
+ name: `${import_chalk7.default.red("security")} \u2014 security rules only. For AppSec teams.`,
1869
2853
  value: "security"
1870
2854
  }
1871
2855
  ]
1872
2856
  });
1873
2857
  log.blank();
1874
- log.info(`Applying preset: ${import_chalk5.default.cyan(preset)}`);
2858
+ log.info(`Applying preset: ${import_chalk7.default.cyan(preset)}`);
1875
2859
  const env = detect(cwd);
1876
2860
  const useFlat = env.eslintMajor !== null && env.eslintMajor >= 9 || isFlat(env.configType);
1877
2861
  if (env.configType === "none") {
1878
- const configPath = useFlat ? import_path6.default.join(cwd, "eslint.config.mjs") : import_path6.default.join(cwd, ".eslintrc.js");
2862
+ const configPath = useFlat ? import_path8.default.join(cwd, "eslint.config.mjs") : import_path8.default.join(cwd, ".eslintrc.js");
1879
2863
  const content = useFlat ? generateFlatConfig(preset) : generateLegacyConfig(preset);
1880
2864
  writeConfig(configPath, content);
1881
- log.success(`Created ${import_chalk5.default.white(import_path6.default.relative(cwd, configPath))} with ${import_chalk5.default.cyan(preset)} preset`);
2865
+ log.success(`Created ${import_chalk7.default.white(import_path8.default.relative(cwd, configPath))} with ${import_chalk7.default.cyan(preset)} preset`);
1882
2866
  } else {
1883
2867
  const configPath = env.configPath;
1884
2868
  const backupPath = backupConfig(configPath);
1885
- log.info(`Backed up \u2192 ${import_chalk5.default.gray(import_path6.default.relative(cwd, backupPath))}`);
2869
+ log.info(`Backed up \u2192 ${import_chalk7.default.gray(import_path8.default.relative(cwd, backupPath))}`);
1886
2870
  const existing = readConfig(configPath);
1887
2871
  const withPlugin = isFlat(env.configType) ? patchFlatConfig(existing, preset) : patchLegacyConfig(existing, preset);
1888
2872
  const patched = isFlat(env.configType) ? switchFlatPreset(withPlugin, preset) : switchLegacyPreset(withPlugin, preset);
@@ -1890,47 +2874,47 @@ function registerPresetCommand(program2) {
1890
2874
  log.warn("No preset changes needed. Config already matches selected preset.");
1891
2875
  } else {
1892
2876
  writeConfig(configPath, patched);
1893
- log.success(`Updated ${import_chalk5.default.white(import_path6.default.relative(cwd, configPath))} to ${import_chalk5.default.cyan(preset)} preset`);
2877
+ log.success(`Updated ${import_chalk7.default.white(import_path8.default.relative(cwd, configPath))} to ${import_chalk7.default.cyan(preset)} preset`);
1894
2878
  }
1895
2879
  }
1896
2880
  log.blank();
1897
2881
  log.section("Preset Details");
1898
2882
  const presetDetails = {
1899
2883
  recommended: [
1900
- { rule: "no-empty-catch", level: import_chalk5.default.red("error") },
1901
- { rule: "no-floating-promise", level: import_chalk5.default.red("error") },
1902
- { rule: "no-hardcoded-secret", level: import_chalk5.default.red("error") },
1903
- { rule: "no-eval-dynamic", level: import_chalk5.default.red("error") },
1904
- { rule: "no-broad-exception", level: import_chalk5.default.yellow("warn") },
1905
- { rule: "no-async-array-callback", level: import_chalk5.default.yellow("warn") },
1906
- { rule: "no-await-in-loop", level: import_chalk5.default.yellow("warn") },
1907
- { rule: "+ 5 more\u2026", level: import_chalk5.default.gray("warn") }
2884
+ { rule: "no-empty-catch", level: import_chalk7.default.red("error") },
2885
+ { rule: "no-floating-promise", level: import_chalk7.default.red("error") },
2886
+ { rule: "no-hardcoded-secret", level: import_chalk7.default.red("error") },
2887
+ { rule: "no-eval-dynamic", level: import_chalk7.default.red("error") },
2888
+ { rule: "no-broad-exception", level: import_chalk7.default.yellow("warn") },
2889
+ { rule: "no-async-array-callback", level: import_chalk7.default.yellow("warn") },
2890
+ { rule: "no-await-in-loop", level: import_chalk7.default.yellow("warn") },
2891
+ { rule: "+ 5 more\u2026", level: import_chalk7.default.gray("warn") }
1908
2892
  ],
1909
2893
  strict: [
1910
- { rule: "All 17 rules", level: import_chalk5.default.red("error") }
2894
+ { rule: "All 17 rules", level: import_chalk7.default.red("error") }
1911
2895
  ],
1912
2896
  security: [
1913
- { rule: "no-hardcoded-secret", level: import_chalk5.default.red("error") },
1914
- { rule: "no-eval-dynamic", level: import_chalk5.default.red("error") },
1915
- { rule: "no-sql-string-concat", level: import_chalk5.default.red("error") },
1916
- { rule: "no-unsafe-deserialize", level: import_chalk5.default.yellow("warn") },
1917
- { rule: "require-auth-middleware", level: import_chalk5.default.yellow("warn") },
1918
- { rule: "require-authz-check", level: import_chalk5.default.yellow("warn") }
2897
+ { rule: "no-hardcoded-secret", level: import_chalk7.default.red("error") },
2898
+ { rule: "no-eval-dynamic", level: import_chalk7.default.red("error") },
2899
+ { rule: "no-sql-string-concat", level: import_chalk7.default.red("error") },
2900
+ { rule: "no-unsafe-deserialize", level: import_chalk7.default.yellow("warn") },
2901
+ { rule: "require-auth-middleware", level: import_chalk7.default.yellow("warn") },
2902
+ { rule: "require-authz-check", level: import_chalk7.default.yellow("warn") }
1919
2903
  ]
1920
2904
  };
1921
2905
  for (const { rule, level } of presetDetails[preset]) {
1922
- log.print(` ${import_chalk5.default.gray("\u2022")} ${import_chalk5.default.white(rule).padEnd(32)}${level}`);
2906
+ log.print(` ${import_chalk7.default.gray("\u2022")} ${import_chalk7.default.white(rule).padEnd(32)}${level}`);
1923
2907
  }
1924
2908
  log.blank();
1925
- log.info(`Run ${import_chalk5.default.cyan("ai-guard run")} to see results with the new preset.`);
2909
+ log.info(`Run ${import_chalk7.default.cyan("ai-guard run")} to see results with the new preset.`);
1926
2910
  log.blank();
1927
2911
  });
1928
2912
  }
1929
2913
 
1930
2914
  // cli/commands/ignore.ts
1931
- var import_chalk6 = __toESM(require("chalk"));
2915
+ var import_chalk8 = __toESM(require("chalk"));
1932
2916
  init_config_manager();
1933
- var import_path7 = __toESM(require("path"));
2917
+ var import_path9 = __toESM(require("path"));
1934
2918
  var DEFAULT_IGNORES = [
1935
2919
  ".next/",
1936
2920
  "dist/",
@@ -1946,41 +2930,41 @@ function registerIgnoreCommand(program2) {
1946
2930
  log.blank();
1947
2931
  log.section("Default Ignores");
1948
2932
  for (const p of DEFAULT_IGNORES) {
1949
- log.print(` ${import_chalk6.default.gray("\u2022")} ${import_chalk6.default.yellow(p)}`);
2933
+ log.print(` ${import_chalk8.default.gray("\u2022")} ${import_chalk8.default.yellow(p)}`);
1950
2934
  }
1951
2935
  log.blank();
1952
2936
  const env = detect(cwd);
1953
2937
  if (env.configType === "none") {
1954
2938
  const useFlat = env.eslintMajor !== null && env.eslintMajor >= 9;
1955
- const configPath = useFlat ? import_path7.default.join(cwd, "eslint.config.mjs") : import_path7.default.join(cwd, ".eslintrc.js");
2939
+ const configPath = useFlat ? import_path9.default.join(cwd, "eslint.config.mjs") : import_path9.default.join(cwd, ".eslintrc.js");
1956
2940
  const content = useFlat ? generateFlatConfig("recommended") : generateLegacyConfig("recommended");
1957
2941
  writeConfig(configPath, content);
1958
- log.success(`No config found \u2014 generated ${import_chalk6.default.white(import_path7.default.relative(cwd, configPath))} with ignores included`);
2942
+ log.success(`No config found \u2014 generated ${import_chalk8.default.white(import_path9.default.relative(cwd, configPath))} with ignores included`);
1959
2943
  } else {
1960
2944
  const configPath = env.configPath;
1961
2945
  const backupPath = backupConfig(configPath);
1962
- log.info(`Backed up \u2192 ${import_chalk6.default.gray(import_path7.default.relative(cwd, backupPath))}`);
2946
+ log.info(`Backed up \u2192 ${import_chalk8.default.gray(import_path9.default.relative(cwd, backupPath))}`);
1963
2947
  const existing = readConfig(configPath);
1964
2948
  const patched = isFlat(env.configType) ? addIgnoresToFlatConfig(existing) : addIgnoresToLegacyConfig(existing);
1965
2949
  if (patched === existing) {
1966
2950
  log.warn("Ignore patterns already present in config \u2014 no changes made.");
1967
2951
  } else {
1968
2952
  writeConfig(configPath, patched);
1969
- log.success(`Patched ${import_chalk6.default.white(import_path7.default.relative(cwd, configPath))} with ignore patterns`);
2953
+ log.success(`Patched ${import_chalk8.default.white(import_path9.default.relative(cwd, configPath))} with ignore patterns`);
1970
2954
  }
1971
2955
  }
1972
2956
  log.blank();
1973
2957
  log.info("Noisy directories will now be skipped in all future ESLint runs.");
1974
- log.info(`Run ${import_chalk6.default.cyan("ai-guard run")} to verify cleaner results.`);
2958
+ log.info(`Run ${import_chalk8.default.cyan("ai-guard run")} to verify cleaner results.`);
1975
2959
  log.blank();
1976
2960
  });
1977
2961
  }
1978
2962
 
1979
2963
  // cli/commands/baseline.ts
1980
- var import_fs5 = __toESM(require("fs"));
1981
- var import_path8 = __toESM(require("path"));
1982
- var import_chalk7 = __toESM(require("chalk"));
1983
- var import_ora2 = __toESM(require("ora"));
2964
+ var import_fs8 = __toESM(require("fs"));
2965
+ var import_path10 = __toESM(require("path"));
2966
+ var import_chalk9 = __toESM(require("chalk"));
2967
+ var import_ora3 = __toESM(require("ora"));
1984
2968
  var BASELINE_FILE = ".ai-guard-baseline.json";
1985
2969
  function saveBaseline(result, preset, mode, cwd) {
1986
2970
  const entries = result.files.map((f) => ({
@@ -1999,15 +2983,15 @@ function saveBaseline(result, preset, mode, cwd) {
1999
2983
  totalIssues: result.totalIssues,
2000
2984
  entries
2001
2985
  };
2002
- const baselinePath = import_path8.default.join(cwd, BASELINE_FILE);
2003
- import_fs5.default.writeFileSync(baselinePath, JSON.stringify(baseline, null, 2), "utf-8");
2986
+ const baselinePath = import_path10.default.join(cwd, BASELINE_FILE);
2987
+ import_fs8.default.writeFileSync(baselinePath, JSON.stringify(baseline, null, 2), "utf-8");
2004
2988
  return baselinePath;
2005
2989
  }
2006
2990
  function loadBaseline(cwd) {
2007
- const baselinePath = import_path8.default.join(cwd, BASELINE_FILE);
2008
- if (!import_fs5.default.existsSync(baselinePath)) return null;
2991
+ const baselinePath = import_path10.default.join(cwd, BASELINE_FILE);
2992
+ if (!import_fs8.default.existsSync(baselinePath)) return null;
2009
2993
  try {
2010
- const raw = import_fs5.default.readFileSync(baselinePath, "utf-8");
2994
+ const raw = import_fs8.default.readFileSync(baselinePath, "utf-8");
2011
2995
  return JSON.parse(raw);
2012
2996
  } catch {
2013
2997
  return null;
@@ -2079,7 +3063,7 @@ function registerBaselineCommand(program2) {
2079
3063
  const doCheck = opts.check ?? (existingBaseline !== null && !opts.save);
2080
3064
  if (doSave) {
2081
3065
  log.info("Scanning project to save baseline\u2026");
2082
- const spinner = (0, import_ora2.default)({ text: "Running analysis\u2026", color: "cyan" }).start();
3066
+ const spinner = (0, import_ora3.default)({ text: "Running analysis\u2026", color: "cyan" }).start();
2083
3067
  let result;
2084
3068
  try {
2085
3069
  result = await runEslint({ preset, targetPath: opts.path });
@@ -2091,30 +3075,30 @@ function registerBaselineCommand(program2) {
2091
3075
  }
2092
3076
  const baselinePath = saveBaseline(result, preset, mode, cwd);
2093
3077
  log.success(
2094
- `Baseline saved \u2192 ${import_chalk7.default.white(import_path8.default.relative(cwd, baselinePath))}`
3078
+ `Baseline saved \u2192 ${import_chalk9.default.white(import_path10.default.relative(cwd, baselinePath))}`
2095
3079
  );
2096
3080
  log.info(` ${result.totalIssues} issues recorded (${result.totalErrors} errors, ${result.totalWarnings} warnings)`);
2097
- log.info(` Preset: ${import_chalk7.default.cyan(preset)}`);
2098
- log.info(` Mode: ${import_chalk7.default.cyan(mode)}`);
3081
+ log.info(` Preset: ${import_chalk9.default.cyan(preset)}`);
3082
+ log.info(` Mode: ${import_chalk9.default.cyan(mode)}`);
2099
3083
  log.blank();
2100
- log.info("Future runs of " + import_chalk7.default.cyan("ai-guard baseline --check") + " will show only NEW issues.");
2101
- log.info("Add " + import_chalk7.default.white(BASELINE_FILE) + " to your git repository to share baseline with your team.");
3084
+ log.info("Future runs of " + import_chalk9.default.cyan("ai-guard baseline --check") + " will show only NEW issues.");
3085
+ log.info("Add " + import_chalk9.default.white(BASELINE_FILE) + " to your git repository to share baseline with your team.");
2102
3086
  log.blank();
2103
3087
  return;
2104
3088
  }
2105
3089
  if (doCheck) {
2106
3090
  if (!existingBaseline) {
2107
- log.error(`No baseline found at ${import_chalk7.default.white(BASELINE_FILE)}`);
2108
- log.info(`Run ${import_chalk7.default.cyan("ai-guard baseline --save")} first to create one.`);
3091
+ log.error(`No baseline found at ${import_chalk9.default.white(BASELINE_FILE)}`);
3092
+ log.info(`Run ${import_chalk9.default.cyan("ai-guard baseline --save")} first to create one.`);
2109
3093
  process.exit(1);
2110
3094
  }
2111
3095
  const baselineDate = new Date(existingBaseline.createdAt).toLocaleString();
2112
- log.info(`Baseline from: ${import_chalk7.default.gray(baselineDate)}`);
2113
- log.info(`Baseline preset: ${import_chalk7.default.cyan(existingBaseline.preset)}`);
2114
- log.info(`Baseline mode: ${import_chalk7.default.cyan(existingBaseline.mode ?? "strict")}`);
2115
- log.info(`Baseline issues: ${import_chalk7.default.gray(existingBaseline.totalIssues)}`);
3096
+ log.info(`Baseline from: ${import_chalk9.default.gray(baselineDate)}`);
3097
+ log.info(`Baseline preset: ${import_chalk9.default.cyan(existingBaseline.preset)}`);
3098
+ log.info(`Baseline mode: ${import_chalk9.default.cyan(existingBaseline.mode ?? "strict")}`);
3099
+ log.info(`Baseline issues: ${import_chalk9.default.gray(existingBaseline.totalIssues)}`);
2116
3100
  log.blank();
2117
- const spinner = (0, import_ora2.default)({ text: "Scanning for new issues\u2026", color: "cyan" }).start();
3101
+ const spinner = (0, import_ora3.default)({ text: "Scanning for new issues\u2026", color: "cyan" }).start();
2118
3102
  let result;
2119
3103
  try {
2120
3104
  result = await runEslint({ preset, targetPath: opts.path });
@@ -2137,7 +3121,7 @@ function registerBaselineCommand(program2) {
2137
3121
  return;
2138
3122
  }
2139
3123
  log.print(
2140
- import_chalk7.default.bold.red(` \u2716 ${newResult.totalIssues} new issue${newResult.totalIssues !== 1 ? "s" : ""} since baseline`)
3124
+ import_chalk9.default.bold.red(` \u2716 ${newResult.totalIssues} new issue${newResult.totalIssues !== 1 ? "s" : ""} since baseline`)
2141
3125
  );
2142
3126
  log.blank();
2143
3127
  if (newResult.ruleBreakdown.size > 0) {
@@ -2153,7 +3137,7 @@ function registerBaselineCommand(program2) {
2153
3137
  log.blank();
2154
3138
  for (const file of newResult.files) {
2155
3139
  log.print(
2156
- ` ${import_chalk7.default.bold.white(file.filePath)} ` + import_chalk7.default.gray(`(${file.issues.length} new)`)
3140
+ ` ${import_chalk9.default.bold.white(file.filePath)} ` + import_chalk9.default.gray(`(${file.issues.length} new)`)
2157
3141
  );
2158
3142
  for (const issue of file.issues) {
2159
3143
  log.issue(issue.message, issue.severity, issue.line, issue.column);
@@ -2166,7 +3150,7 @@ function registerBaselineCommand(program2) {
2166
3150
  log.error(
2167
3151
  `${newResult.totalIssues} new issue${newResult.totalIssues !== 1 ? "s" : ""} introduced since baseline.`
2168
3152
  );
2169
- log.info(`Run ${import_chalk7.default.cyan("ai-guard baseline --save")} to update the baseline after fixing them.`);
3153
+ log.info(`Run ${import_chalk9.default.cyan("ai-guard baseline --save")} to update the baseline after fixing them.`);
2170
3154
  log.blank();
2171
3155
  process.exit(1);
2172
3156
  }
@@ -2174,9 +3158,9 @@ function registerBaselineCommand(program2) {
2174
3158
  }
2175
3159
 
2176
3160
  // cli/commands/init-context.ts
2177
- var import_fs6 = __toESM(require("fs"));
2178
- var import_path9 = __toESM(require("path"));
2179
- var import_chalk8 = __toESM(require("chalk"));
3161
+ var import_fs9 = __toESM(require("fs"));
3162
+ var import_path11 = __toESM(require("path"));
3163
+ var import_chalk10 = __toESM(require("chalk"));
2180
3164
  var import_prompts2 = require("@inquirer/prompts");
2181
3165
  var RULE_CATEGORIES = {
2182
3166
  "error-handling": [
@@ -2539,23 +3523,23 @@ var AGENTS = [
2539
3523
  {
2540
3524
  id: "copilot",
2541
3525
  label: "GitHub Copilot",
2542
- filePath: import_path9.default.join(".github", "copilot-instructions.md"),
3526
+ filePath: import_path11.default.join(".github", "copilot-instructions.md"),
2543
3527
  generate: generateCopilotFile
2544
3528
  }
2545
3529
  ];
2546
3530
  function getPackageVersion2(cwd) {
2547
3531
  try {
2548
- const pkgPath = import_path9.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
2549
- if (import_fs6.default.existsSync(pkgPath)) {
2550
- const pkg = JSON.parse(import_fs6.default.readFileSync(pkgPath, "utf-8"));
3532
+ const pkgPath = import_path11.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
3533
+ if (import_fs9.default.existsSync(pkgPath)) {
3534
+ const pkg = JSON.parse(import_fs9.default.readFileSync(pkgPath, "utf-8"));
2551
3535
  if (pkg.version) return pkg.version;
2552
3536
  }
2553
3537
  } catch {
2554
3538
  }
2555
3539
  try {
2556
- const pkgPath = import_path9.default.join(cwd, "package.json");
2557
- if (import_fs6.default.existsSync(pkgPath)) {
2558
- const pkg = JSON.parse(import_fs6.default.readFileSync(pkgPath, "utf-8"));
3540
+ const pkgPath = import_path11.default.join(cwd, "package.json");
3541
+ if (import_fs9.default.existsSync(pkgPath)) {
3542
+ const pkg = JSON.parse(import_fs9.default.readFileSync(pkgPath, "utf-8"));
2559
3543
  if (pkg.version) return pkg.version;
2560
3544
  }
2561
3545
  } catch {
@@ -2584,7 +3568,7 @@ function registerInitContextCommand(program2) {
2584
3568
  const isForce = opts.force === true;
2585
3569
  log.banner("AI GUARD INIT-CONTEXT");
2586
3570
  if (isDryRun) {
2587
- log.print(` ${import_chalk8.default.yellow("\u2691 DRY RUN \u2014 no files will be written")}`);
3571
+ log.print(` ${import_chalk10.default.yellow("\u2691 DRY RUN \u2014 no files will be written")}`);
2588
3572
  log.blank();
2589
3573
  }
2590
3574
  let selectedAgents;
@@ -2617,13 +3601,13 @@ function registerInitContextCommand(program2) {
2617
3601
  selectedAgents = AGENTS.filter((a) => selected.includes(a.id));
2618
3602
  }
2619
3603
  }
2620
- log.info(`Categories: ${import_chalk8.default.cyan(categories.join(", "))}`);
2621
- log.info(`Version: ${import_chalk8.default.cyan(version)}`);
3604
+ log.info(`Categories: ${import_chalk10.default.cyan(categories.join(", "))}`);
3605
+ log.info(`Version: ${import_chalk10.default.cyan(version)}`);
2622
3606
  log.blank();
2623
3607
  const results = [];
2624
3608
  for (const agent of selectedAgents) {
2625
- const fullPath = import_path9.default.join(cwd, agent.filePath);
2626
- const exists = import_fs6.default.existsSync(fullPath);
3609
+ const fullPath = import_path11.default.join(cwd, agent.filePath);
3610
+ const exists = import_fs9.default.existsSync(fullPath);
2627
3611
  if (exists && !isForce && !isDryRun) {
2628
3612
  const overwrite = await (0, import_prompts2.confirm)({
2629
3613
  message: `${agent.filePath} already exists. Overwrite?`,
@@ -2637,41 +3621,41 @@ function registerInitContextCommand(program2) {
2637
3621
  const content = agent.generate(version, categories);
2638
3622
  if (isDryRun) {
2639
3623
  log.section(`Preview: ${agent.filePath}`);
2640
- log.print(import_chalk8.default.gray("\u2500".repeat(60)));
3624
+ log.print(import_chalk10.default.gray("\u2500".repeat(60)));
2641
3625
  for (const line of content.split("\n").slice(0, 20)) {
2642
- log.print(import_chalk8.default.gray(` ${line}`));
3626
+ log.print(import_chalk10.default.gray(` ${line}`));
2643
3627
  }
2644
- log.print(import_chalk8.default.gray(" ... (truncated)"));
2645
- log.print(import_chalk8.default.gray("\u2500".repeat(60)));
3628
+ log.print(import_chalk10.default.gray(" ... (truncated)"));
3629
+ log.print(import_chalk10.default.gray("\u2500".repeat(60)));
2646
3630
  log.blank();
2647
3631
  results.push({ file: agent.filePath, status: "dry-run" });
2648
3632
  continue;
2649
3633
  }
2650
- const parentDir = import_path9.default.dirname(fullPath);
2651
- if (!import_fs6.default.existsSync(parentDir)) {
2652
- import_fs6.default.mkdirSync(parentDir, { recursive: true });
3634
+ const parentDir = import_path11.default.dirname(fullPath);
3635
+ if (!import_fs9.default.existsSync(parentDir)) {
3636
+ import_fs9.default.mkdirSync(parentDir, { recursive: true });
2653
3637
  }
2654
- import_fs6.default.writeFileSync(fullPath, content, "utf-8");
3638
+ import_fs9.default.writeFileSync(fullPath, content, "utf-8");
2655
3639
  results.push({ file: agent.filePath, status: "created" });
2656
3640
  }
2657
3641
  log.section("Summary");
2658
3642
  for (const result of results) {
2659
3643
  switch (result.status) {
2660
3644
  case "created":
2661
- log.print(` ${import_chalk8.default.green("\u2705")} Created ${import_chalk8.default.white(result.file)}`);
3645
+ log.print(` ${import_chalk10.default.green("\u2705")} Created ${import_chalk10.default.white(result.file)}`);
2662
3646
  break;
2663
3647
  case "skipped":
2664
- log.print(` ${import_chalk8.default.yellow("\u23ED")} Skipped ${import_chalk8.default.white(result.file)} ${import_chalk8.default.gray("(already exists \u2014 use --force to overwrite)")}`);
3648
+ log.print(` ${import_chalk10.default.yellow("\u23ED")} Skipped ${import_chalk10.default.white(result.file)} ${import_chalk10.default.gray("(already exists \u2014 use --force to overwrite)")}`);
2665
3649
  break;
2666
3650
  case "dry-run":
2667
- log.print(` ${import_chalk8.default.cyan("\u{1F4DD}")} Would create ${import_chalk8.default.white(result.file)}`);
3651
+ log.print(` ${import_chalk10.default.cyan("\u{1F4DD}")} Would create ${import_chalk10.default.white(result.file)}`);
2668
3652
  break;
2669
3653
  }
2670
3654
  }
2671
3655
  log.blank();
2672
3656
  if (isDryRun) {
2673
3657
  log.info("Dry run complete \u2014 no files were written.");
2674
- log.info(`Re-run without ${import_chalk8.default.cyan("--dry-run")} to apply.`);
3658
+ log.info(`Re-run without ${import_chalk10.default.cyan("--dry-run")} to apply.`);
2675
3659
  } else {
2676
3660
  const createdCount = results.filter((r) => r.status === "created").length;
2677
3661
  if (createdCount > 0) {
@@ -2685,9 +3669,9 @@ function registerInitContextCommand(program2) {
2685
3669
  }
2686
3670
 
2687
3671
  // cli/commands/report.ts
2688
- var import_fs7 = __toESM(require("fs"));
2689
- var import_path10 = __toESM(require("path"));
2690
- var import_ora3 = __toESM(require("ora"));
3672
+ var import_fs10 = __toESM(require("fs"));
3673
+ var import_path12 = __toESM(require("path"));
3674
+ var import_ora4 = __toESM(require("ora"));
2691
3675
  var RULE_META = {
2692
3676
  "ai-guard/no-empty-catch": { description: "Empty catch blocks silently swallow errors", category: "Reliability" },
2693
3677
  "ai-guard/no-broad-exception": { description: "Catching all exceptions hides bugs", category: "Reliability" },
@@ -2710,28 +3694,28 @@ var RULE_META = {
2710
3694
  };
2711
3695
  function getProjectName(cwd) {
2712
3696
  try {
2713
- const pkgPath = import_path10.default.join(cwd, "package.json");
2714
- if (import_fs7.default.existsSync(pkgPath)) {
2715
- const pkg = JSON.parse(import_fs7.default.readFileSync(pkgPath, "utf-8"));
3697
+ const pkgPath = import_path12.default.join(cwd, "package.json");
3698
+ if (import_fs10.default.existsSync(pkgPath)) {
3699
+ const pkg = JSON.parse(import_fs10.default.readFileSync(pkgPath, "utf-8"));
2716
3700
  if (pkg.name) return pkg.name;
2717
3701
  }
2718
3702
  } catch {
2719
3703
  }
2720
- return import_path10.default.basename(cwd);
3704
+ return import_path12.default.basename(cwd);
2721
3705
  }
2722
3706
  function getPluginVersion(cwd) {
2723
3707
  try {
2724
- const pkgPath = import_path10.default.resolve(__dirname, "../../package.json");
2725
- if (import_fs7.default.existsSync(pkgPath)) {
2726
- const pkg = JSON.parse(import_fs7.default.readFileSync(pkgPath, "utf-8"));
3708
+ const pkgPath = import_path12.default.resolve(__dirname, "../../package.json");
3709
+ if (import_fs10.default.existsSync(pkgPath)) {
3710
+ const pkg = JSON.parse(import_fs10.default.readFileSync(pkgPath, "utf-8"));
2727
3711
  if (pkg.version) return pkg.version;
2728
3712
  }
2729
3713
  } catch {
2730
3714
  }
2731
3715
  try {
2732
- const pkgPath = import_path10.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
2733
- if (import_fs7.default.existsSync(pkgPath)) {
2734
- const pkg = JSON.parse(import_fs7.default.readFileSync(pkgPath, "utf-8"));
3716
+ const pkgPath = import_path12.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
3717
+ if (import_fs10.default.existsSync(pkgPath)) {
3718
+ const pkg = JSON.parse(import_fs10.default.readFileSync(pkgPath, "utf-8"));
2735
3719
  if (pkg.version) return pkg.version;
2736
3720
  }
2737
3721
  } catch {
@@ -3086,7 +4070,7 @@ function registerReportCommand(program2) {
3086
4070
  "Generate a beautiful HTML report of AI Guard scan results\nPerfect for sharing with your team or in Slack."
3087
4071
  ).option("--path <dir>", "Directory or file to scan", ".").option("--preset <preset>", "Rule preset: recommended | strict | security", "recommended").option("--html", "Generate HTML report (default behavior)").option("--json", "Output raw JSON to stdout instead of writing HTML").option("--output <filename>", "Output file name", "ai-guard-report.html").option("--no-open", "Do not open the report in the browser after generating").action(async (opts) => {
3088
4072
  const preset = ["recommended", "strict", "security"].includes(opts.preset) ? opts.preset : "recommended";
3089
- const spinner = opts.json ? null : (0, import_ora3.default)({ text: "Scanning\u2026", color: "cyan" }).start();
4073
+ const spinner = opts.json ? null : (0, import_ora4.default)({ text: "Scanning\u2026", color: "cyan" }).start();
3090
4074
  let result;
3091
4075
  try {
3092
4076
  result = await runEslint({ preset, targetPath: opts.path });
@@ -3108,8 +4092,8 @@ function registerReportCommand(program2) {
3108
4092
  log.banner("AI GUARD REPORT");
3109
4093
  log.blank();
3110
4094
  const html = generateHtml(reportData);
3111
- const outputPath = import_path10.default.resolve(cwd, opts.output);
3112
- import_fs7.default.writeFileSync(outputPath, html, "utf-8");
4095
+ const outputPath = import_path12.default.resolve(cwd, opts.output);
4096
+ import_fs10.default.writeFileSync(outputPath, html, "utf-8");
3113
4097
  log.success(`Report saved: ${opts.output}`);
3114
4098
  log.info(
3115
4099
  `Score: ${reportData.score}/100 \xB7 ${reportData.totalErrors} errors \xB7 ${reportData.totalWarnings} warnings`
@@ -3124,9 +4108,9 @@ function registerReportCommand(program2) {
3124
4108
  await open(outputPath);
3125
4109
  log.info("Opened in your browser.");
3126
4110
  } else {
3127
- const { execSync } = await import("child_process");
4111
+ const { execSync: execSync2 } = await import("child_process");
3128
4112
  const cmd = process.platform === "win32" ? `start "" "${outputPath}"` : process.platform === "darwin" ? `open "${outputPath}"` : `xdg-open "${outputPath}"`;
3129
- execSync(cmd);
4113
+ execSync2(cmd);
3130
4114
  log.info("Opened in your browser.");
3131
4115
  }
3132
4116
  } catch {
@@ -3142,8 +4126,8 @@ function registerReportCommand(program2) {
3142
4126
  // cli/index.ts
3143
4127
  function getVersion() {
3144
4128
  try {
3145
- const pkgPath = import_path11.default.resolve(__dirname, "../../package.json");
3146
- const raw = import_fs8.default.readFileSync(pkgPath, "utf-8");
4129
+ const pkgPath = import_path13.default.resolve(__dirname, "../../package.json");
4130
+ const raw = import_fs11.default.readFileSync(pkgPath, "utf-8");
3147
4131
  const pkg = JSON.parse(raw);
3148
4132
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
3149
4133
  } catch {
@@ -3152,9 +4136,10 @@ function getVersion() {
3152
4136
  }
3153
4137
  var program = new import_commander.Command();
3154
4138
  program.name("ai-guard").description(
3155
- "Production-grade CLI for eslint-plugin-ai-guard\nCatch AI-generated code issues instantly \u2014 no ESLint config required.\n\nQuick start:\n npx ai-guard run Scan current project\n npx ai-guard init Auto-configure ESLint\n npx ai-guard init-context Generate AI agent rules\n npx ai-guard report Generate shareable HTML report\n npx ai-guard doctor Check your setup\n npx ai-guard baseline Save baseline, track new issues only"
4139
+ "Production-grade CLI for eslint-plugin-ai-guard\nCatch AI-generated code issues instantly \u2014 no ESLint config required.\n\nQuick start:\n npx ai-guard run Scan current project\n npx ai-guard changed Scan only changed files (CI mode)\n npx ai-guard init Auto-configure ESLint\n npx ai-guard init-context Generate AI agent rules\n npx ai-guard report Generate shareable HTML report\n npx ai-guard doctor Check your setup\n npx ai-guard baseline Save baseline, track new issues only"
3156
4140
  ).version(getVersion(), "-v, --version", "Print version number").helpOption("-h, --help", "Show help");
3157
4141
  registerRunCommand(program);
4142
+ registerChangedCommand(program);
3158
4143
  registerInitCommand(program);
3159
4144
  registerDoctorCommand(program);
3160
4145
  registerPresetCommand(program);