eslint-plugin-ai-guard 1.2.0 → 1.2.2

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
@@ -30,6 +30,154 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  mod
31
31
  ));
32
32
 
33
+ // cli/utils/debug.ts
34
+ var debug_exports = {};
35
+ __export(debug_exports, {
36
+ debugLog: () => debugLog,
37
+ printCIDebug: () => printCIDebug,
38
+ printGitDebug: () => printGitDebug
39
+ });
40
+ function debugLog(message) {
41
+ process.stderr.write(`${DIM("[debug]")} ${message}
42
+ `);
43
+ }
44
+ function printGitDebug(result) {
45
+ const d = result.debugInfo;
46
+ process.stderr.write("\n");
47
+ process.stderr.write(`${LABEL(" Git Resolution Trace")}
48
+ `);
49
+ process.stderr.write(`${DIM(" \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
50
+ `);
51
+ process.stderr.write(` ${DIM("Mode:")} ${import_chalk4.default.white(result.mode)}
52
+ `);
53
+ if (d.resolvedBase) {
54
+ process.stderr.write(` ${DIM("Base branch:")} ${import_chalk4.default.white(d.resolvedBase)}
55
+ `);
56
+ } else {
57
+ process.stderr.write(` ${DIM("Base branch:")} ${import_chalk4.default.dim("(none \u2014 uncommitted mode)")}
58
+ `);
59
+ }
60
+ if (d.mergeBase) {
61
+ process.stderr.write(` ${DIM("Merge base:")} ${import_chalk4.default.white(d.mergeBase.slice(0, 12))}\u2026
62
+ `);
63
+ }
64
+ if (d.isShallowClone) {
65
+ process.stderr.write(` ${WARN} ${import_chalk4.default.yellow("Shallow clone detected")} \u2014 use fetch-depth: 0 in workflow
66
+ `);
67
+ }
68
+ if (d.isDetachedHead) {
69
+ process.stderr.write(` ${WARN} ${import_chalk4.default.yellow("Detached HEAD")} \u2014 branch context unavailable
70
+ `);
71
+ }
72
+ process.stderr.write("\n");
73
+ process.stderr.write(` ${DIM("Strategies attempted:")}
74
+ `);
75
+ for (const strategy of d.strategiesAttempted) {
76
+ const succeeded = strategy === d.strategySucceeded;
77
+ const icon = succeeded ? OK : ERR;
78
+ const label = succeeded ? import_chalk4.default.green(strategy) : import_chalk4.default.dim(strategy);
79
+ process.stderr.write(` ${icon} ${label}
80
+ `);
81
+ }
82
+ if (d.strategySucceeded) {
83
+ process.stderr.write(`
84
+ ${OK} ${import_chalk4.default.green("Succeeded:")} ${d.strategySucceeded}
85
+ `);
86
+ } else {
87
+ process.stderr.write(`
88
+ ${ERR} ${import_chalk4.default.red("All strategies failed")}
89
+ `);
90
+ }
91
+ process.stderr.write("\n");
92
+ process.stderr.write(` ${DIM("Files:")}
93
+ `);
94
+ process.stderr.write(` ${ARROW} Detected raw: ${import_chalk4.default.white(d.rawFileCount)}
95
+ `);
96
+ process.stderr.write(` ${ARROW} Kept: ${import_chalk4.default.green(result.files.length)}
97
+ `);
98
+ if (d.droppedByExtension > 0) {
99
+ process.stderr.write(` ${ARROW} Dropped (ext): ${import_chalk4.default.dim(d.droppedByExtension)}
100
+ `);
101
+ }
102
+ if (d.droppedByIgnore > 0) {
103
+ process.stderr.write(` ${ARROW} Dropped (ignored): ${import_chalk4.default.dim(d.droppedByIgnore)}
104
+ `);
105
+ }
106
+ if (d.droppedByWorkingDir > 0) {
107
+ process.stderr.write(` ${ARROW} Dropped (dir): ${import_chalk4.default.dim(d.droppedByWorkingDir)}
108
+ `);
109
+ }
110
+ if (d.droppedMissing > 0) {
111
+ process.stderr.write(` ${ARROW} Dropped (missing): ${import_chalk4.default.dim(d.droppedMissing)}
112
+ `);
113
+ }
114
+ if (result.zeroFilesReason) {
115
+ process.stderr.write("\n");
116
+ process.stderr.write(` ${WARN} ${import_chalk4.default.yellow("Zero files reason:")}
117
+ `);
118
+ process.stderr.write(` ${import_chalk4.default.dim(result.zeroFilesReason)}
119
+ `);
120
+ }
121
+ if (result.files.length > 0) {
122
+ process.stderr.write("\n");
123
+ process.stderr.write(` ${DIM("Changed files (first 20):")}
124
+ `);
125
+ for (const f of result.files.slice(0, 20)) {
126
+ process.stderr.write(` ${import_chalk4.default.dim("\xB7")} ${import_chalk4.default.white(f)}
127
+ `);
128
+ }
129
+ if (result.files.length > 20) {
130
+ process.stderr.write(` ${import_chalk4.default.dim(`\u2026 and ${result.files.length - 20} more`)}
131
+ `);
132
+ }
133
+ }
134
+ process.stderr.write("\n");
135
+ }
136
+ function printCIDebug(info) {
137
+ process.stderr.write("\n");
138
+ process.stderr.write(`${LABEL(" CI Environment")}
139
+ `);
140
+ process.stderr.write(`${DIM(" \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
141
+ `);
142
+ const env = (name, value) => {
143
+ const val = value ? import_chalk4.default.white(value) : import_chalk4.default.dim("(not set)");
144
+ process.stderr.write(` ${DIM(name.padEnd(26))} ${val}
145
+ `);
146
+ };
147
+ env("GITHUB_ACTIONS:", info.isGitHubActions ? "true" : "false");
148
+ env("GITHUB_BASE_REF:", info.githubBaseRef);
149
+ env("GITHUB_SHA:", info.githubSha);
150
+ env("GITHUB_REF:", info.githubRef);
151
+ env("GITHUB_REPOSITORY:", info.githubRepository);
152
+ env("GITHUB_WORKFLOW:", info.githubWorkflow);
153
+ env("GITHUB_RUN_ID:", info.githubRunId);
154
+ env("GITHUB_EVENT_NAME:", info.githubEventName);
155
+ process.stderr.write("\n");
156
+ env("GITHUB_STEP_SUMMARY:", info.githubStepSummary);
157
+ env("GITHUB_OUTPUT:", info.githubOutput);
158
+ process.stderr.write("\n");
159
+ env("Scan preset:", info.preset);
160
+ env("Fail on:", info.failOn);
161
+ env("Scan mode:", info.scanMode);
162
+ if (info.sarifOutputPath) {
163
+ env("SARIF output:", info.sarifOutputPath);
164
+ }
165
+ process.stderr.write("\n");
166
+ }
167
+ var import_chalk4, DIM, LABEL, OK, WARN, ERR, ARROW;
168
+ var init_debug = __esm({
169
+ "cli/utils/debug.ts"() {
170
+ "use strict";
171
+ import_chalk4 = __toESM(require("chalk"));
172
+ DIM = import_chalk4.default.dim;
173
+ LABEL = import_chalk4.default.cyan.bold;
174
+ OK = import_chalk4.default.green("\u2714");
175
+ WARN = import_chalk4.default.yellow("\u26A0");
176
+ ERR = import_chalk4.default.red("\u2716");
177
+ ARROW = import_chalk4.default.dim("\u2192");
178
+ }
179
+ });
180
+
33
181
  // cli/utils/config-manager.ts
34
182
  var config_manager_exports = {};
35
183
  __export(config_manager_exports, {
@@ -53,14 +201,14 @@ __export(config_manager_exports, {
53
201
  });
54
202
  function backupConfig(configPath) {
55
203
  const backupPath = `${configPath}.bak`;
56
- import_fs3.default.copyFileSync(configPath, backupPath);
204
+ import_fs6.default.copyFileSync(configPath, backupPath);
57
205
  return backupPath;
58
206
  }
59
207
  function readConfig(configPath) {
60
- return import_fs3.default.readFileSync(configPath, "utf-8");
208
+ return import_fs6.default.readFileSync(configPath, "utf-8");
61
209
  }
62
210
  function writeConfig(configPath, content) {
63
- import_fs3.default.writeFileSync(configPath, content, "utf-8");
211
+ import_fs6.default.writeFileSync(configPath, content, "utf-8");
64
212
  }
65
213
  function generateFlatConfig(preset) {
66
214
  return `import aiGuard from 'eslint-plugin-ai-guard';
@@ -266,19 +414,19 @@ function addIgnoresToLegacyConfig(existing) {
266
414
  function getConfigFilePath(configType, cwd = process.cwd()) {
267
415
  switch (configType) {
268
416
  case "flat-js":
269
- return import_path3.default.join(cwd, "eslint.config.js");
417
+ return import_path6.default.join(cwd, "eslint.config.js");
270
418
  case "flat-mjs":
271
- return import_path3.default.join(cwd, "eslint.config.mjs");
419
+ return import_path6.default.join(cwd, "eslint.config.mjs");
272
420
  case "flat-cjs":
273
- return import_path3.default.join(cwd, "eslint.config.cjs");
421
+ return import_path6.default.join(cwd, "eslint.config.cjs");
274
422
  case "eslintrc-js":
275
- return import_path3.default.join(cwd, ".eslintrc.js");
423
+ return import_path6.default.join(cwd, ".eslintrc.js");
276
424
  case "eslintrc-cjs":
277
- return import_path3.default.join(cwd, ".eslintrc.cjs");
425
+ return import_path6.default.join(cwd, ".eslintrc.cjs");
278
426
  case "eslintrc-json":
279
- return import_path3.default.join(cwd, ".eslintrc.json");
427
+ return import_path6.default.join(cwd, ".eslintrc.json");
280
428
  default:
281
- return import_path3.default.join(cwd, ".eslintrc.js");
429
+ return import_path6.default.join(cwd, ".eslintrc.js");
282
430
  }
283
431
  }
284
432
  function validateFlatConfigText(content) {
@@ -313,12 +461,12 @@ function validateLegacyConfigText(content) {
313
461
  }
314
462
  return problems;
315
463
  }
316
- var import_fs3, import_path3, AI_GUARD_MARKER, DEFAULT_FLAT_IGNORES, DEFAULT_LEGACY_IGNORES;
464
+ var import_fs6, import_path6, AI_GUARD_MARKER, DEFAULT_FLAT_IGNORES, DEFAULT_LEGACY_IGNORES;
317
465
  var init_config_manager = __esm({
318
466
  "cli/utils/config-manager.ts"() {
319
467
  "use strict";
320
- import_fs3 = __toESM(require("fs"));
321
- import_path3 = __toESM(require("path"));
468
+ import_fs6 = __toESM(require("fs"));
469
+ import_path6 = __toESM(require("path"));
322
470
  AI_GUARD_MARKER = "eslint-plugin-ai-guard";
323
471
  DEFAULT_FLAT_IGNORES = [
324
472
  "node_modules/**",
@@ -341,17 +489,18 @@ var init_config_manager = __esm({
341
489
 
342
490
  // cli/index.ts
343
491
  var import_commander = require("commander");
344
- var import_fs8 = __toESM(require("fs"));
345
- var import_path11 = __toESM(require("path"));
492
+ var import_fs11 = __toESM(require("fs"));
493
+ var import_path14 = __toESM(require("path"));
346
494
 
347
495
  // cli/commands/run.ts
348
496
  var import_ora = __toESM(require("ora"));
349
- var import_chalk2 = __toESM(require("chalk"));
497
+ var import_chalk3 = __toESM(require("chalk"));
350
498
 
351
499
  // cli/utils/eslint-runner.ts
352
500
  var import_fs = __toESM(require("fs"));
353
501
  var import_path = __toESM(require("path"));
354
502
  var import_url = require("url");
503
+ var import_chalk2 = __toESM(require("chalk"));
355
504
 
356
505
  // cli/utils/logger.ts
357
506
  var import_chalk = __toESM(require("chalk"));
@@ -396,14 +545,15 @@ var CONFIDENCE_TIER = {
396
545
  "ai-guard/no-catch-log-rethrow": "medium",
397
546
  "ai-guard/no-catch-without-use": "medium",
398
547
  "ai-guard/no-unsafe-deserialize": "medium",
399
- // Low confidence — suggestions, may be intentional
400
- "ai-guard/no-async-without-await": "low",
548
+ // Low confidence — review case-by-case
401
549
  "ai-guard/no-async-array-callback": "low",
402
550
  "ai-guard/no-dead-branch": "low",
403
551
  "ai-guard/no-broad-exception": "low",
404
552
  "ai-guard/no-console-in-handler": "low",
405
553
  "ai-guard/no-duplicate-logic-block": "low",
406
- "ai-guard/no-redundant-await": "low"
554
+ // Informational — mostly stylistic, high framework FP rate, collapsed by default
555
+ "ai-guard/no-async-without-await": "informational",
556
+ "ai-guard/no-redundant-await": "informational"
407
557
  };
408
558
  var ECOSYSTEM_FIX_HINTS = {
409
559
  "react-hooks/exhaustive-deps": "npm install --save-dev eslint-plugin-react-hooks",
@@ -541,6 +691,96 @@ var log = {
541
691
  };
542
692
 
543
693
  // cli/utils/eslint-runner.ts
694
+ var ISSUE_CONFIDENCE = {
695
+ // HIGH — near-certain bugs: false positive rate <2% in real-world codebases
696
+ "ai-guard/no-hardcoded-secret": "high",
697
+ // Literal credential string in source: almost always a mistake
698
+ "ai-guard/no-eval-dynamic": "high",
699
+ // Dynamic eval: no legitimate modern use case
700
+ "ai-guard/no-floating-promise": "high",
701
+ // Unhandled promise: async failures silently swallowed
702
+ // MEDIUM — likely bugs, but legitimate patterns exist
703
+ "ai-guard/no-empty-catch": "medium",
704
+ // CHANGED from high: empty catch is valid for optional ops (JSON.parse, feature detection)
705
+ "ai-guard/no-sql-string-concat": "medium",
706
+ // SQL injection risk, but ORM template literals can FP
707
+ "ai-guard/no-await-in-loop": "medium",
708
+ // Sequential await is often intentional (rate limiting, ordering)
709
+ "ai-guard/require-auth-middleware": "medium",
710
+ // Context-blind: public routes are valid
711
+ "ai-guard/require-authz-check": "medium",
712
+ // Context-blind: some resources are public
713
+ "ai-guard/no-catch-log-rethrow": "medium",
714
+ // Log-then-rethrow is a known antipattern but sometimes intentional
715
+ "ai-guard/no-catch-without-use": "medium",
716
+ // Unused catch param common in TS
717
+ "ai-guard/no-unsafe-deserialize": "medium",
718
+ // JSON.parse is sometimes safe with trusted input
719
+ // LOW — possible improvement, high false-positive rate
720
+ "ai-guard/no-async-array-callback": "low",
721
+ // Promise.all is better, but forEach async is not always a bug
722
+ "ai-guard/no-dead-branch": "low",
723
+ // Static analysis of conditions is inherently imprecise
724
+ "ai-guard/no-broad-exception": "low",
725
+ // Catching Error is common and sometimes correct
726
+ "ai-guard/no-console-in-handler": "low",
727
+ // Stylistic: debug logs left in vs intentional logging
728
+ "ai-guard/no-duplicate-logic-block": "low",
729
+ // May be coincidental similarity, not true duplication
730
+ // INFORMATIONAL — hints only, never fail CI
731
+ "ai-guard/no-async-without-await": "informational",
732
+ // Framework patterns (Next.js RSC, etc.) make this very noisy
733
+ "ai-guard/no-redundant-await": "informational"
734
+ // Explicit await in return is idiomatic in many codebases
735
+ };
736
+ var ISSUE_CATEGORY = {
737
+ "ai-guard/no-floating-promise": "Async Reliability",
738
+ "ai-guard/no-await-in-loop": "Async Reliability",
739
+ "ai-guard/no-async-without-await": "Async Reliability",
740
+ "ai-guard/no-async-array-callback": "Async Reliability",
741
+ "ai-guard/no-redundant-await": "Async Reliability",
742
+ "ai-guard/no-empty-catch": "Reliability",
743
+ "ai-guard/no-broad-exception": "Reliability",
744
+ "ai-guard/no-catch-log-rethrow": "Reliability",
745
+ "ai-guard/no-catch-without-use": "Reliability",
746
+ "ai-guard/no-hardcoded-secret": "Security",
747
+ "ai-guard/no-eval-dynamic": "Security",
748
+ "ai-guard/no-sql-string-concat": "Security",
749
+ "ai-guard/no-unsafe-deserialize": "Security",
750
+ "ai-guard/require-auth-middleware": "Security",
751
+ "ai-guard/require-authz-check": "Security",
752
+ "ai-guard/no-console-in-handler": "AI Patterns",
753
+ "ai-guard/no-duplicate-logic-block": "AI Patterns",
754
+ "ai-guard/no-dead-branch": "AI Patterns"
755
+ };
756
+ var ISSUE_ASYNC_RISK_TYPE = {
757
+ "ai-guard/no-floating-promise": "floating-promise",
758
+ "ai-guard/no-await-in-loop": "sequential-await",
759
+ "ai-guard/no-async-without-await": "unnecessary-async",
760
+ "ai-guard/no-async-array-callback": "async-array-callback",
761
+ "ai-guard/no-redundant-await": "redundant-await"
762
+ };
763
+ var ISSUE_REMEDIATION = {
764
+ "ai-guard/no-floating-promise": "Add `await` before the call, assign to a variable, or add `.catch()` to handle rejection.",
765
+ "ai-guard/no-await-in-loop": "Collect promises in an array and use `await Promise.all(...)` outside the loop.",
766
+ "ai-guard/no-async-without-await": "Remove `async` if no async operations are needed, or add an `await` expression.",
767
+ "ai-guard/no-async-array-callback": "Use `Promise.all(array.map(async item => ...))` to ensure all async callbacks are awaited.",
768
+ "ai-guard/no-redundant-await": "Remove the `await` \u2014 the value is already resolved.",
769
+ "ai-guard/no-empty-catch": "Add error handling in the catch block, or at minimum log the error.",
770
+ "ai-guard/no-broad-exception": "Catch specific error types or re-throw after handling.",
771
+ "ai-guard/no-catch-log-rethrow": "Either log the error OR rethrow it \u2014 doing both duplicates the error in logs.",
772
+ "ai-guard/no-catch-without-use": "Use the caught error variable or rename to `_` to signal intentional suppression.",
773
+ "ai-guard/no-hardcoded-secret": "Move credentials to environment variables: `process.env.SECRET_KEY`.",
774
+ "ai-guard/no-eval-dynamic": "Avoid eval and Function constructors. Use safer alternatives like JSON.parse.",
775
+ "ai-guard/no-sql-string-concat": "Use parameterized queries or an ORM to prevent SQL injection.",
776
+ "ai-guard/no-unsafe-deserialize": "Validate input with a schema library (zod, joi, yup) before parsing.",
777
+ "ai-guard/require-auth-middleware": "Add an authentication middleware before the route handler.",
778
+ "ai-guard/require-authz-check": "Verify the requesting user has permission to access this resource.",
779
+ "ai-guard/no-console-in-handler": "Replace console.log with a structured logger (pino, winston, etc.).",
780
+ "ai-guard/no-duplicate-logic-block": "Extract the duplicated logic into a shared function.",
781
+ "ai-guard/no-dead-branch": "Remove the unreachable branch or fix the condition."
782
+ };
783
+ var _runnerCache = /* @__PURE__ */ new Map();
544
784
  function isRecord(value) {
545
785
  return typeof value === "object" && value !== null;
546
786
  }
@@ -612,13 +852,40 @@ function getRules(preset) {
612
852
  return RECOMMENDED_RULES;
613
853
  }
614
854
  var DEFAULT_IGNORE_PATTERNS = [
855
+ // Core — always skip
615
856
  "**/node_modules/**",
616
857
  "**/.next/**",
617
858
  "**/dist/**",
618
859
  "**/build/**",
619
860
  "**/coverage/**",
620
861
  "**/out/**",
621
- "**/.git/**"
862
+ "**/.git/**",
863
+ // Build artifacts and caches
864
+ "**/.cache/**",
865
+ "**/.turbo/**",
866
+ "**/.nx/**",
867
+ "**/.parcel-cache/**",
868
+ "**/.webpack/**",
869
+ "**/tmp/**",
870
+ "**/temp/**",
871
+ // Package manager artifacts
872
+ "**/.yarn/**",
873
+ "**/.pnp.*",
874
+ // Framework generated
875
+ "**/.expo/**",
876
+ "**/.svelte-kit/**",
877
+ "**/storybook-static/**",
878
+ "**/.storybook/generated/**",
879
+ // Generated code
880
+ "**/generated/**",
881
+ "**/__generated__/**",
882
+ "**/vendor/**",
883
+ // Test outputs
884
+ "**/test-results/**",
885
+ "**/playwright-report/**",
886
+ // Misc
887
+ "**/.docusaurus/**",
888
+ "**/.vitepress/cache/**"
622
889
  ];
623
890
  function isSkippablePatternError(error) {
624
891
  const msg = error.message.toLowerCase();
@@ -645,8 +912,8 @@ function classifyEcosystemType(msg) {
645
912
  return "foreign-rule";
646
913
  }
647
914
  async function loadPluginModuleFromCwd(cwd) {
648
- const { createRequire: createRequire2 } = await import("module");
649
- const requireFromCwd = createRequire2(import_path.default.join(cwd, "package.json"));
915
+ const { createRequire: createRequire3 } = await import("module");
916
+ const requireFromCwd = createRequire3(import_path.default.join(cwd, "package.json"));
650
917
  try {
651
918
  const resolved = requireFromCwd.resolve("eslint-plugin-ai-guard");
652
919
  return import((0, import_url.pathToFileURL)(resolved).href);
@@ -669,50 +936,44 @@ async function loadPluginModuleFromCwd(cwd) {
669
936
  "eslint-plugin-ai-guard is not installed. Run: npm install --save-dev eslint-plugin-ai-guard"
670
937
  );
671
938
  }
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}`);
939
+ async function getOrBuildRunner(cwd, preset, eslintCwd) {
940
+ const cacheKey = `${eslintCwd}::${preset}`;
941
+ const cached = _runnerCache.get(cacheKey);
942
+ if (cached) {
943
+ return { runner: cached, fromCache: true };
693
944
  }
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");
714
- }
715
- timing.parserLoadMs = Date.now() - parserStart;
945
+ const [{ ESLint }, rawPlugin, tsParserResult] = await Promise.all([
946
+ // 1. Load ESLint
947
+ import("eslint").catch(() => {
948
+ throw new Error("ESLint is not installed. Run: npm install --save-dev eslint");
949
+ }),
950
+ // 2. Load plugin
951
+ loadPluginModuleFromCwd(cwd),
952
+ // 3. Load TS parser (optional — errors are caught)
953
+ Promise.resolve().then(() => {
954
+ const parserStart = Date.now();
955
+ try {
956
+ let tsParser2;
957
+ try {
958
+ tsParser2 = require(import_path.default.join(
959
+ cwd,
960
+ "node_modules",
961
+ "@typescript-eslint",
962
+ "parser"
963
+ ));
964
+ } catch {
965
+ tsParser2 = require("@typescript-eslint/parser");
966
+ }
967
+ return { tsParser: tsParser2, available: true, loadMs: Date.now() - parserStart };
968
+ } catch {
969
+ log.debug("@typescript-eslint/parser not found \u2014 TypeScript files will use espree fallback");
970
+ return { tsParser: null, available: false, loadMs: Date.now() - parserStart };
971
+ }
972
+ })
973
+ ]);
974
+ const plugin = normalizePlugin(rawPlugin);
975
+ const rules = getRules(preset);
976
+ const { tsParser, available: tsParserAvailable } = tsParserResult;
716
977
  const JS_TS_FILES = [
717
978
  "**/*.js",
718
979
  "**/*.jsx",
@@ -724,7 +985,9 @@ async function runEslint(options) {
724
985
  "**/*.cjs"
725
986
  ];
726
987
  const configBlocks = [
727
- // JS/JSX files — default espree parser with JSX support
988
+ // Ignore block first — ESLint can skip excluded files without processing
989
+ { ignores: DEFAULT_IGNORE_PATTERNS },
990
+ // JS/JSX — espree parser with JSX support
728
991
  {
729
992
  files: ["**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs"],
730
993
  plugins: { "ai-guard": plugin },
@@ -736,12 +999,10 @@ async function runEslint(options) {
736
999
  }
737
1000
  },
738
1001
  rules
739
- },
740
- // Ignore generated directories
741
- { ignores: DEFAULT_IGNORE_PATTERNS }
1002
+ }
742
1003
  ];
743
1004
  if (tsParser) {
744
- configBlocks.splice(1, 0, {
1005
+ configBlocks.push({
745
1006
  files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
746
1007
  plugins: { "ai-guard": plugin },
747
1008
  languageOptions: {
@@ -755,7 +1016,7 @@ async function runEslint(options) {
755
1016
  rules
756
1017
  });
757
1018
  } else {
758
- configBlocks.splice(1, 0, {
1019
+ configBlocks.push({
759
1020
  files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
760
1021
  plugins: { "ai-guard": plugin },
761
1022
  languageOptions: {
@@ -773,16 +1034,58 @@ async function runEslint(options) {
773
1034
  overrideConfigFile: true,
774
1035
  overrideConfig: configBlocks
775
1036
  });
776
- const patterns = isSingleFileTarget ? [import_path.default.basename(resolvedTargetPath)] : JS_TS_FILES;
1037
+ const runner = {
1038
+ eslint,
1039
+ tsParserAvailable,
1040
+ pluginLoadMs: 0,
1041
+ // accounted in parallel Promise.all
1042
+ parserLoadMs: tsParserResult.loadMs
1043
+ };
1044
+ _runnerCache.set(cacheKey, runner);
1045
+ return { runner, fromCache: false };
1046
+ }
1047
+ var JS_TS_FILE_PATTERNS = [
1048
+ "**/*.js",
1049
+ "**/*.jsx",
1050
+ "**/*.ts",
1051
+ "**/*.tsx",
1052
+ "**/*.mts",
1053
+ "**/*.cts",
1054
+ "**/*.mjs",
1055
+ "**/*.cjs"
1056
+ ];
1057
+ async function runEslint(options) {
1058
+ const overallStart = Date.now();
1059
+ const resolvedTargetPath = import_path.default.resolve(options.targetPath);
1060
+ if (!import_fs.default.existsSync(resolvedTargetPath)) {
1061
+ throw new Error(`Path not found: ${options.targetPath}`);
1062
+ }
1063
+ const targetStat = import_fs.default.statSync(resolvedTargetPath);
1064
+ const isSingleFileTarget = targetStat.isFile();
1065
+ const eslintCwd = isSingleFileTarget ? import_path.default.dirname(resolvedTargetPath) : resolvedTargetPath;
1066
+ const cwd = process.cwd();
1067
+ const initStart = Date.now();
1068
+ const { runner, fromCache } = await getOrBuildRunner(cwd, options.preset, eslintCwd);
1069
+ const initMs = Date.now() - initStart;
1070
+ const timing = {
1071
+ pluginLoadMs: fromCache ? 0 : runner.pluginLoadMs,
1072
+ parserLoadMs: fromCache ? 0 : runner.parserLoadMs,
1073
+ lintMs: 0,
1074
+ processMs: 0,
1075
+ fromCache
1076
+ };
1077
+ const hasExplicitFiles = options.files && options.files.length > 0;
1078
+ const patterns = hasExplicitFiles ? options.files : isSingleFileTarget ? [import_path.default.basename(resolvedTargetPath)] : JS_TS_FILE_PATTERNS;
777
1079
  const lintStart = Date.now();
778
1080
  let rawResults;
1081
+ const eslint = runner.eslint;
779
1082
  try {
780
1083
  rawResults = await eslint.lintFiles(patterns);
781
1084
  } catch (err) {
782
1085
  const error = err instanceof Error ? err : new Error(String(err));
783
1086
  if (isSkippablePatternError(error)) {
784
- log.debug("Single-call lintFiles failed with no-files, falling back to per-pattern");
785
- const perPatternResults = await Promise.all(
1087
+ log.debug("Single-call lintFiles failed, falling back to per-pattern");
1088
+ const perPatternResults = await Promise.allSettled(
786
1089
  patterns.map(async (pattern) => {
787
1090
  try {
788
1091
  return await eslint.lintFiles([pattern]);
@@ -796,8 +1099,10 @@ async function runEslint(options) {
796
1099
  }
797
1100
  })
798
1101
  );
799
- rawResults = perPatternResults.flat();
1102
+ rawResults = perPatternResults.filter((r) => r.status === "fulfilled").flatMap((r) => r.value);
800
1103
  } else {
1104
+ const cacheKey = `${eslintCwd}::${options.preset}`;
1105
+ _runnerCache.delete(cacheKey);
801
1106
  throw error;
802
1107
  }
803
1108
  }
@@ -812,7 +1117,7 @@ async function runEslint(options) {
812
1117
  const filesScanned = rawResults.length;
813
1118
  for (const result of rawResults) {
814
1119
  if (result.messages.length === 0) continue;
815
- const relPath = import_path.default.relative(process.cwd(), result.filePath);
1120
+ const relPath = import_path.default.relative(cwd, result.filePath);
816
1121
  const aiGuardIssues = [];
817
1122
  let fileErrors = 0;
818
1123
  let fileWarnings = 0;
@@ -837,12 +1142,19 @@ async function runEslint(options) {
837
1142
  });
838
1143
  continue;
839
1144
  }
1145
+ const ruleId = m.ruleId ?? "unknown";
840
1146
  const issue = {
841
- ruleId: m.ruleId ?? "unknown",
1147
+ ruleId,
842
1148
  severity: m.severity,
843
1149
  message: m.message,
844
1150
  line: m.line,
845
- column: m.column
1151
+ column: m.column,
1152
+ endLine: m.endLine,
1153
+ endColumn: m.endColumn,
1154
+ confidence: ISSUE_CONFIDENCE[ruleId],
1155
+ category: ISSUE_CATEGORY[ruleId],
1156
+ asyncRiskType: ISSUE_ASYNC_RISK_TYPE[ruleId],
1157
+ remediation: ISSUE_REMEDIATION[ruleId]
846
1158
  };
847
1159
  aiGuardIssues.push(issue);
848
1160
  if (m.severity === 2) {
@@ -869,11 +1181,12 @@ async function runEslint(options) {
869
1181
  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
1182
  timing.processMs = Date.now() - processStart;
871
1183
  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`);
1184
+ const totalMs = Date.now() - overallStart;
1185
+ const cacheLabel = fromCache ? import_chalk2.default.green("warm cache") : import_chalk2.default.yellow("cold start");
1186
+ log.debug(`Init: ${initMs}ms (${cacheLabel})`);
1187
+ log.debug(`Lint: ${timing.lintMs}ms`);
875
1188
  log.debug(`Process: ${timing.processMs}ms`);
876
- log.debug(`Total: ${Date.now() - overallStart}ms`);
1189
+ log.debug(`Total: ${totalMs}ms | Files: ${filesScanned} | ${filesScanned > 0 ? Math.round(totalMs / filesScanned) : 0}ms/file`);
877
1190
  }
878
1191
  return {
879
1192
  files,
@@ -887,17 +1200,698 @@ async function runEslint(options) {
887
1200
  timing,
888
1201
  ecosystemIssues,
889
1202
  parserErrors,
890
- tsParserAvailable
1203
+ tsParserAvailable: runner.tsParserAvailable
1204
+ };
1205
+ }
1206
+
1207
+ // cli/utils/version.ts
1208
+ var import_module = require("module");
1209
+ var import_url2 = require("url");
1210
+ var import_path2 = __toESM(require("path"));
1211
+ var import_meta = {};
1212
+ var _version;
1213
+ function readVersion() {
1214
+ try {
1215
+ const require_ = (0, import_module.createRequire)(import_meta.url);
1216
+ const pkgPath = import_path2.default.resolve(
1217
+ import_path2.default.dirname((0, import_url2.fileURLToPath)(import_meta.url)),
1218
+ "../../package.json"
1219
+ );
1220
+ const pkg = require_(pkgPath);
1221
+ if (pkg.version) return pkg.version;
1222
+ } catch {
1223
+ }
1224
+ if (typeof __PKG_VERSION__ !== "undefined") {
1225
+ return __PKG_VERSION__;
1226
+ }
1227
+ return "0.0.0";
1228
+ }
1229
+ var PKG_VERSION = _version ?? (_version = readVersion());
1230
+
1231
+ // cli/utils/sarif.ts
1232
+ var RULE_DOCS = {
1233
+ "ai-guard/no-empty-catch": { shortDesc: "Empty catch block silently swallows errors", tags: ["reliability"] },
1234
+ "ai-guard/no-broad-exception": { shortDesc: "Catching broad Exception/Error masks specific failures", tags: ["reliability"] },
1235
+ "ai-guard/no-catch-log-rethrow": { shortDesc: "Catch block logs and rethrows \u2014 use one or the other", tags: ["reliability"] },
1236
+ "ai-guard/no-catch-without-use": { shortDesc: "Caught error variable is not used", tags: ["reliability"] },
1237
+ "ai-guard/no-floating-promise": { shortDesc: "Promise not awaited or error-handled \u2014 async failures will be silently swallowed", tags: ["async", "async-reliability"] },
1238
+ "ai-guard/no-await-in-loop": { shortDesc: "Sequential await in loop \u2014 consider Promise.all for parallel execution", tags: ["async", "async-reliability", "performance"] },
1239
+ "ai-guard/no-async-without-await": { shortDesc: "Async function contains no await expressions", tags: ["async", "async-reliability"] },
1240
+ "ai-guard/no-async-array-callback": { shortDesc: "Async callback in array method may not behave as expected", tags: ["async", "async-reliability"] },
1241
+ "ai-guard/no-redundant-await": { shortDesc: "Redundant await on already-resolved value", tags: ["async"] },
1242
+ "ai-guard/no-hardcoded-secret": { shortDesc: "Hardcoded secret or credential detected", tags: ["security", "secrets"] },
1243
+ "ai-guard/no-eval-dynamic": { shortDesc: "Dynamic eval or Function constructor is dangerous", tags: ["security"] },
1244
+ "ai-guard/no-sql-string-concat": { shortDesc: "SQL query built via string concatenation \u2014 SQL injection risk", tags: ["security", "injection"] },
1245
+ "ai-guard/no-unsafe-deserialize": { shortDesc: "Unsafe deserialization of untrusted data", tags: ["security"] },
1246
+ "ai-guard/require-auth-middleware": { shortDesc: "Route handler appears to lack authentication middleware", tags: ["security", "auth"] },
1247
+ "ai-guard/require-authz-check": { shortDesc: "Handler lacks authorization check", tags: ["security", "auth"] },
1248
+ "ai-guard/no-console-in-handler": { shortDesc: "console.log in request handler \u2014 use structured logging", tags: ["ai-patterns"] },
1249
+ "ai-guard/no-duplicate-logic-block": { shortDesc: "Duplicate logic block detected", tags: ["ai-patterns"] },
1250
+ "ai-guard/no-dead-branch": { shortDesc: "Dead code branch that can never execute", tags: ["ai-patterns"] }
1251
+ };
1252
+ var BASE_DOCS_URL = "https://github.com/YashJadhav21/eslint-plugin-ai-guard/blob/main/docs/rules";
1253
+ function confidenceToSarifLevel(confidence) {
1254
+ switch (confidence) {
1255
+ case "high":
1256
+ return "error";
1257
+ case "medium":
1258
+ return "warning";
1259
+ case "low":
1260
+ return "warning";
1261
+ case "informational":
1262
+ return "note";
1263
+ default:
1264
+ return "warning";
1265
+ }
1266
+ }
1267
+ function confidenceToRank(confidence) {
1268
+ switch (confidence) {
1269
+ case "high":
1270
+ return 90;
1271
+ case "medium":
1272
+ return 60;
1273
+ case "low":
1274
+ return 30;
1275
+ case "informational":
1276
+ return 10;
1277
+ default:
1278
+ return 30;
1279
+ }
1280
+ }
1281
+ function confidenceToGitHubSeverity(confidence) {
1282
+ switch (confidence) {
1283
+ case "high":
1284
+ return "high";
1285
+ case "medium":
1286
+ return "medium";
1287
+ case "low":
1288
+ return "low";
1289
+ case "informational":
1290
+ return "recommendation";
1291
+ default:
1292
+ return "low";
1293
+ }
1294
+ }
1295
+ function filePathToUri(filePath) {
1296
+ return filePath.replace(/\\/g, "/");
1297
+ }
1298
+ function collectUsedRuleIds(result) {
1299
+ const ids = /* @__PURE__ */ new Set();
1300
+ for (const file of result.files) {
1301
+ for (const issue of file.issues) {
1302
+ ids.add(issue.ruleId);
1303
+ }
1304
+ }
1305
+ return [...ids].sort();
1306
+ }
1307
+ function buildRuleDescriptors(ruleIds) {
1308
+ return ruleIds.map((id) => {
1309
+ const meta = RULE_DOCS[id];
1310
+ const shortName = id.replace("ai-guard/", "");
1311
+ const confidence = ISSUE_CONFIDENCE[id];
1312
+ const category = ISSUE_CATEGORY[id] ?? "AI Patterns";
1313
+ const remediation = ISSUE_REMEDIATION[id];
1314
+ const helpMarkdown = [
1315
+ `**${meta?.shortDesc ?? shortName}**`,
1316
+ "",
1317
+ remediation ? `**Fix:** ${remediation}` : "",
1318
+ "",
1319
+ `[View rule documentation](${BASE_DOCS_URL}/${shortName}.md)`
1320
+ ].filter(Boolean).join("\n");
1321
+ return {
1322
+ id,
1323
+ shortDescription: {
1324
+ text: meta?.shortDesc ?? `ai-guard rule: ${shortName}`
1325
+ },
1326
+ fullDescription: {
1327
+ text: meta?.shortDesc ?? `ai-guard rule: ${shortName}`
1328
+ },
1329
+ help: {
1330
+ text: remediation ?? meta?.shortDesc ?? shortName,
1331
+ markdown: helpMarkdown
1332
+ },
1333
+ helpUri: `${BASE_DOCS_URL}/${shortName}.md`,
1334
+ defaultConfiguration: {
1335
+ level: confidenceToSarifLevel(confidence)
1336
+ },
1337
+ properties: {
1338
+ tags: [...meta?.tags ?? ["ai-guard"], category.toLowerCase().replace(/ /g, "-")],
1339
+ confidence: confidence ?? "low",
1340
+ category,
1341
+ "problem.severity": confidenceToGitHubSeverity(confidence)
1342
+ }
1343
+ };
1344
+ });
1345
+ }
1346
+ function buildSarifResult(issue, file) {
1347
+ const confidence = ISSUE_CONFIDENCE[issue.ruleId];
1348
+ const asyncRiskType = ISSUE_ASYNC_RISK_TYPE[issue.ruleId];
1349
+ const category = ISSUE_CATEGORY[issue.ruleId];
1350
+ return {
1351
+ ruleId: issue.ruleId,
1352
+ level: confidenceToSarifLevel(confidence),
1353
+ kind: confidence === "informational" ? "informational" : "fail",
1354
+ rank: confidenceToRank(confidence),
1355
+ message: { text: issue.message },
1356
+ locations: [
1357
+ {
1358
+ physicalLocation: {
1359
+ artifactLocation: {
1360
+ uri: filePathToUri(file.filePath),
1361
+ uriBaseId: "%SRCROOT%"
1362
+ },
1363
+ region: {
1364
+ startLine: Math.max(1, issue.line),
1365
+ startColumn: issue.column > 0 ? issue.column : 1,
1366
+ ...issue.endLine !== void 0 && { endLine: issue.endLine },
1367
+ ...issue.endColumn !== void 0 && { endColumn: issue.endColumn }
1368
+ }
1369
+ },
1370
+ message: { text: issue.message }
1371
+ }
1372
+ ],
1373
+ properties: {
1374
+ confidence: confidence ?? "low",
1375
+ ...asyncRiskType && { asyncRiskType },
1376
+ ...category && { category }
1377
+ }
1378
+ };
1379
+ }
1380
+ function buildSarifLog(result, version = PKG_VERSION) {
1381
+ const usedRuleIds = collectUsedRuleIds(result);
1382
+ const rules = buildRuleDescriptors(usedRuleIds);
1383
+ const sarifResults = [];
1384
+ for (const file of result.files) {
1385
+ for (const issue of file.issues) {
1386
+ sarifResults.push(buildSarifResult(issue, file));
1387
+ }
1388
+ }
1389
+ return {
1390
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
1391
+ version: "2.1.0",
1392
+ runs: [
1393
+ {
1394
+ tool: {
1395
+ driver: {
1396
+ name: "ai-guard",
1397
+ version,
1398
+ informationUri: "https://github.com/YashJadhav21/eslint-plugin-ai-guard",
1399
+ rules
1400
+ }
1401
+ },
1402
+ results: sarifResults,
1403
+ properties: {
1404
+ preset: "recommended",
1405
+ filesScanned: result.filesScanned,
1406
+ durationMs: result.durationMs,
1407
+ ecosystemIssuesCount: result.ecosystemIssues.length,
1408
+ parserErrorsCount: result.parserErrors.length
1409
+ }
1410
+ }
1411
+ ]
1412
+ };
1413
+ }
1414
+ function sarifToJson(log2) {
1415
+ return JSON.stringify(log2, null, 2);
1416
+ }
1417
+
1418
+ // cli/utils/github-summary.ts
1419
+ var import_fs2 = __toESM(require("fs"));
1420
+ function warnEnvIssue(name, filePath, err) {
1421
+ const msg = err instanceof Error ? err.message : String(err);
1422
+ process.stderr.write(
1423
+ `[ai-guard] Warning: Could not write to ${name}=${filePath}: ${msg}
1424
+ `
1425
+ );
1426
+ }
1427
+ function escapeMarkdown(text) {
1428
+ return text.replace(/[|`*_[\]<>]/g, "\\$&");
1429
+ }
1430
+ function tierEmoji(tier) {
1431
+ switch (tier) {
1432
+ case "high":
1433
+ return "\u{1F534}";
1434
+ case "medium":
1435
+ return "\u{1F7E1}";
1436
+ case "low":
1437
+ return "\u{1F535}";
1438
+ case "informational":
1439
+ return "\u2B1C";
1440
+ default:
1441
+ return "\u2B1C";
1442
+ }
1443
+ }
1444
+ function tierLabel(tier) {
1445
+ switch (tier) {
1446
+ case "high":
1447
+ return "High";
1448
+ case "medium":
1449
+ return "Medium";
1450
+ case "low":
1451
+ return "Low";
1452
+ case "informational":
1453
+ return "Info";
1454
+ default:
1455
+ return "Low";
1456
+ }
1457
+ }
1458
+ function emitGitHubAnnotations(result) {
1459
+ if (process.env.GITHUB_ACTIONS !== "true") return;
1460
+ for (const file of result.files) {
1461
+ for (const issue of file.issues) {
1462
+ const tier = CONFIDENCE_TIER[issue.ruleId];
1463
+ if (tier === "informational") continue;
1464
+ const level = issue.severity === 2 ? "error" : "warning";
1465
+ const filePath = file.filePath.replace(/\\/g, "/");
1466
+ const shortRule = issue.ruleId.replace("ai-guard/", "");
1467
+ const parts = [`file=${filePath}`, `line=${issue.line}`];
1468
+ if (issue.endLine) parts.push(`endLine=${issue.endLine}`);
1469
+ if (issue.column > 0) parts.push(`col=${issue.column}`);
1470
+ if (issue.endColumn) parts.push(`endColumn=${issue.endColumn}`);
1471
+ parts.push(`title=ai-guard/${shortRule}`);
1472
+ const msg = issue.message.replace(/\n/g, " ");
1473
+ process.stdout.write(`::${level} ${parts.join(",")}::${msg}
1474
+ `);
1475
+ }
1476
+ }
1477
+ }
1478
+ function buildGitHubSummaryMarkdown(result, options) {
1479
+ const lines = [];
1480
+ lines.push("## AI Guard \u2014 Async Reliability Report");
1481
+ lines.push("");
1482
+ lines.push(`> **Preset:** \`${options.preset}\` &nbsp;|&nbsp; **Mode:** ${options.scanMode === "changed" ? "Changed files only" : options.scanMode === "staged" ? "Staged files" : "Full scan"}`);
1483
+ if (options.changedFilesCount !== void 0) {
1484
+ lines.push(`> **Files scanned:** ${result.filesScanned} (${options.changedFilesCount} changed)`);
1485
+ } else {
1486
+ lines.push(`> **Files scanned:** ${result.filesScanned}`);
1487
+ }
1488
+ lines.push(`> **Duration:** ${result.durationMs}ms`);
1489
+ lines.push("");
1490
+ if (result.totalIssues === 0) {
1491
+ lines.push("### \u2705 No issues found");
1492
+ lines.push("");
1493
+ lines.push("All scanned files passed AI Guard reliability checks.");
1494
+ lines.push("");
1495
+ return lines.join("\n");
1496
+ }
1497
+ const { high = 0, medium = 0, low = 0, informational = 0 } = buildSignalCounts(result);
1498
+ if (high > 0) {
1499
+ lines.push(`### \u26A0\uFE0F ${high} high-confidence issue${high !== 1 ? "s" : ""} found`);
1500
+ } else if (medium > 0) {
1501
+ lines.push(`### \u{1F535} ${medium} medium-confidence issue${medium !== 1 ? "s" : ""} found`);
1502
+ } else {
1503
+ lines.push("### \u2139\uFE0F Informational hints only");
1504
+ }
1505
+ lines.push("");
1506
+ lines.push("| Category | \u{1F534} High | \u{1F7E1} Medium | \u{1F535} Low | \u2B1C Info |");
1507
+ lines.push("|----------|---------|----------|-------|--------|");
1508
+ const categoryBreakdown = buildCategoryBreakdown(result);
1509
+ for (const [cat, counts] of Object.entries(categoryBreakdown)) {
1510
+ if (counts.high + counts.medium + counts.low + counts.informational === 0) continue;
1511
+ lines.push(`| ${cat} | ${counts.high} | ${counts.medium} | ${counts.low} | ${counts.informational} |`);
1512
+ }
1513
+ lines.push("");
1514
+ const allIssues = result.files.flatMap(
1515
+ (f) => f.issues.map((i) => ({ ...i, filePath: f.filePath }))
1516
+ );
1517
+ const actionableIssues = allIssues.filter((i) => {
1518
+ const tier = CONFIDENCE_TIER[i.ruleId];
1519
+ return tier !== "informational";
1520
+ }).sort((a, b) => {
1521
+ const tierOrder = { high: 0, medium: 1, low: 2, informational: 3 };
1522
+ const ta = CONFIDENCE_TIER[a.ruleId];
1523
+ const tb = CONFIDENCE_TIER[b.ruleId];
1524
+ return (tierOrder[ta ?? "low"] ?? 2) - (tierOrder[tb ?? "low"] ?? 2);
1525
+ }).slice(0, 10);
1526
+ if (actionableIssues.length > 0) {
1527
+ lines.push("### Top Findings");
1528
+ lines.push("");
1529
+ lines.push("| File | Line | Rule | Confidence |");
1530
+ lines.push("|------|------|------|------------|");
1531
+ for (const issue of actionableIssues) {
1532
+ const tier = CONFIDENCE_TIER[issue.ruleId];
1533
+ const shortFile = issue.filePath.replace(/\\/g, "/").split("/").slice(-2).join("/");
1534
+ const shortRule = issue.ruleId.replace("ai-guard/", "");
1535
+ lines.push(
1536
+ `| \`${escapeMarkdown(shortFile)}\` | ${issue.line} | \`${shortRule}\` | ${tierEmoji(tier)} ${tierLabel(tier)} |`
1537
+ );
1538
+ }
1539
+ lines.push("");
1540
+ }
1541
+ if (informational > 0) {
1542
+ lines.push("<details>");
1543
+ lines.push(`<summary>\u{1F4A1} ${informational} informational hint${informational !== 1 ? "s" : ""} (click to expand)</summary>`);
1544
+ lines.push("");
1545
+ lines.push("These are stylistic hints with higher false-positive rates in frameworks like Next.js and React. Review manually before acting.");
1546
+ lines.push("");
1547
+ const infoIssues = allIssues.filter((i) => {
1548
+ const tier = CONFIDENCE_TIER[i.ruleId];
1549
+ return tier === "informational";
1550
+ }).slice(0, 20);
1551
+ lines.push("| File | Line | Rule |");
1552
+ lines.push("|------|------|------|");
1553
+ for (const issue of infoIssues) {
1554
+ const shortFile = issue.filePath.replace(/\\/g, "/").split("/").slice(-2).join("/");
1555
+ const shortRule = issue.ruleId.replace("ai-guard/", "");
1556
+ lines.push(`| \`${escapeMarkdown(shortFile)}\` | ${issue.line} | \`${shortRule}\` |`);
1557
+ }
1558
+ lines.push("</details>");
1559
+ lines.push("");
1560
+ }
1561
+ if (result.ecosystemIssues.length > 0) {
1562
+ lines.push("<details>");
1563
+ lines.push(`<summary>\u{1F527} ${result.ecosystemIssues.length} ESLint config issue${result.ecosystemIssues.length !== 1 ? "s" : ""} (not ai-guard findings)</summary>`);
1564
+ lines.push("");
1565
+ lines.push("These are from your project's ESLint configuration, not from ai-guard rules. Run `ai-guard doctor` to diagnose.");
1566
+ lines.push("</details>");
1567
+ lines.push("");
1568
+ }
1569
+ lines.push("---");
1570
+ lines.push("");
1571
+ lines.push("**Next steps:**");
1572
+ if (high > 0 || medium > 0) {
1573
+ lines.push("- Review high/medium confidence findings in the **Files changed** tab");
1574
+ lines.push("- Run `ai-guard run --verbose` locally to see full details");
1575
+ }
1576
+ if (informational > 0) {
1577
+ lines.push("- Run `ai-guard run --verbose` to expand informational hints");
1578
+ }
1579
+ lines.push("- Run `ai-guard baseline` to suppress known issues and track only new ones");
1580
+ lines.push("");
1581
+ lines.push("*Powered by [eslint-plugin-ai-guard](https://github.com/YashJadhav21/eslint-plugin-ai-guard)*");
1582
+ return lines.join("\n");
1583
+ }
1584
+ function buildSignalCounts(result) {
1585
+ let high = 0, medium = 0, low = 0, informational = 0;
1586
+ for (const file of result.files) {
1587
+ for (const issue of file.issues) {
1588
+ const tier = CONFIDENCE_TIER[issue.ruleId];
1589
+ if (tier === "high") high++;
1590
+ else if (tier === "medium") medium++;
1591
+ else if (tier === "informational") informational++;
1592
+ else low++;
1593
+ }
1594
+ }
1595
+ return { high, medium, low, informational };
1596
+ }
1597
+ function buildCategoryBreakdown(result) {
1598
+ const breakdown = {};
1599
+ for (const file of result.files) {
1600
+ for (const issue of file.issues) {
1601
+ const cat = RULE_CATEGORY[issue.ruleId] ?? "Other";
1602
+ const tier = CONFIDENCE_TIER[issue.ruleId];
1603
+ if (!breakdown[cat]) breakdown[cat] = { high: 0, medium: 0, low: 0, informational: 0 };
1604
+ if (tier === "high") breakdown[cat].high++;
1605
+ else if (tier === "medium") breakdown[cat].medium++;
1606
+ else if (tier === "informational") breakdown[cat].informational++;
1607
+ else breakdown[cat].low++;
1608
+ }
1609
+ }
1610
+ return breakdown;
1611
+ }
1612
+ function writeGitHubSummary(result, options) {
1613
+ const summaryPath = process.env.GITHUB_STEP_SUMMARY;
1614
+ if (!summaryPath) return;
1615
+ try {
1616
+ const markdown = buildGitHubSummaryMarkdown(result, options);
1617
+ import_fs2.default.appendFileSync(summaryPath, markdown + "\n", { encoding: "utf-8" });
1618
+ } catch (err) {
1619
+ warnEnvIssue("GITHUB_STEP_SUMMARY", summaryPath, err);
1620
+ }
1621
+ }
1622
+ function writeGitHubOutputs(result, sarifPath) {
1623
+ const outputPath = process.env.GITHUB_OUTPUT;
1624
+ if (!outputPath) return;
1625
+ const { high, medium, low, informational } = buildSignalCounts(result);
1626
+ const outputs = [
1627
+ `issues-found=${result.totalIssues}`,
1628
+ `high-confidence-count=${high}`,
1629
+ `medium-confidence-count=${medium}`,
1630
+ `low-confidence-count=${low}`,
1631
+ `informational-count=${informational}`,
1632
+ `files-scanned=${result.filesScanned}`,
1633
+ `duration-ms=${result.durationMs}`,
1634
+ ...sarifPath ? [`sarif-file=${sarifPath}`] : []
1635
+ ];
1636
+ try {
1637
+ import_fs2.default.appendFileSync(outputPath, outputs.join("\n") + "\n", { encoding: "utf-8" });
1638
+ } catch (err) {
1639
+ warnEnvIssue("GITHUB_OUTPUT", outputPath, err);
1640
+ }
1641
+ }
1642
+
1643
+ // cli/utils/git-diff.ts
1644
+ var import_child_process = require("child_process");
1645
+ var import_path3 = __toESM(require("path"));
1646
+ var import_fs3 = __toESM(require("fs"));
1647
+ var DEFAULT_EXTENSIONS = [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".mts", ".cts"];
1648
+ var IGNORE_PREFIXES = [
1649
+ "node_modules/",
1650
+ "dist/",
1651
+ "build/",
1652
+ ".next/",
1653
+ "out/",
1654
+ "coverage/",
1655
+ ".cache/",
1656
+ ".turbo/",
1657
+ ".nx/",
1658
+ "vendor/",
1659
+ "tmp/",
1660
+ "temp/",
1661
+ "__generated__/",
1662
+ "generated/",
1663
+ "storybook-static/",
1664
+ ".expo/",
1665
+ ".svelte-kit/"
1666
+ ];
1667
+ function runGit(args, cwd) {
1668
+ try {
1669
+ return (0, import_child_process.execSync)(`git ${args}`, {
1670
+ cwd,
1671
+ encoding: "utf-8",
1672
+ stdio: ["pipe", "pipe", "pipe"],
1673
+ timeout: 15e3
1674
+ }).trim();
1675
+ } catch {
1676
+ return null;
1677
+ }
1678
+ }
1679
+ function isGitAvailable(cwd) {
1680
+ return runGit("rev-parse --is-inside-work-tree", cwd) === "true";
1681
+ }
1682
+ function isShallowClone(cwd) {
1683
+ return runGit("rev-parse --is-shallow-repository", cwd) === "true";
1684
+ }
1685
+ function isDetachedHead(cwd) {
1686
+ const result = runGit("symbolic-ref --quiet HEAD", cwd);
1687
+ return result === null;
1688
+ }
1689
+ function normalizeBranchName(ref) {
1690
+ return ref.replace(/^origin\//, "").trim();
1691
+ }
1692
+ function tryPrDiff(branch, cwd, debugInfo) {
1693
+ const rawBranch = normalizeBranchName(branch);
1694
+ {
1695
+ const strategy = `merge-base HEAD origin/${rawBranch}`;
1696
+ debugInfo.strategiesAttempted.push(strategy);
1697
+ const mergeBase = runGit(`merge-base HEAD origin/${rawBranch}`, cwd);
1698
+ if (mergeBase) {
1699
+ debugInfo.mergeBase = mergeBase;
1700
+ const out = runGit(`diff --name-only --diff-filter=ACMRT ${mergeBase}`, cwd);
1701
+ if (out !== null) {
1702
+ debugInfo.strategySucceeded = strategy;
1703
+ return { output: out, strategy, mergeBase };
1704
+ }
1705
+ }
1706
+ }
1707
+ {
1708
+ const strategy = `merge-base HEAD ${rawBranch}`;
1709
+ debugInfo.strategiesAttempted.push(strategy);
1710
+ const mergeBase = runGit(`merge-base HEAD ${rawBranch}`, cwd);
1711
+ if (mergeBase) {
1712
+ if (!debugInfo.mergeBase) debugInfo.mergeBase = mergeBase;
1713
+ const out = runGit(`diff --name-only --diff-filter=ACMRT ${mergeBase}`, cwd);
1714
+ if (out !== null) {
1715
+ debugInfo.strategySucceeded = strategy;
1716
+ return { output: out, strategy, mergeBase };
1717
+ }
1718
+ }
1719
+ }
1720
+ {
1721
+ const strategy = `diff origin/${rawBranch}...HEAD`;
1722
+ debugInfo.strategiesAttempted.push(strategy);
1723
+ const out = runGit(`diff --name-only --diff-filter=ACMRT origin/${rawBranch}...HEAD`, cwd);
1724
+ if (out !== null) {
1725
+ debugInfo.strategySucceeded = strategy;
1726
+ return { output: out, strategy };
1727
+ }
1728
+ }
1729
+ {
1730
+ const strategy = `diff ${rawBranch}...HEAD`;
1731
+ debugInfo.strategiesAttempted.push(strategy);
1732
+ const out = runGit(`diff --name-only --diff-filter=ACMRT ${rawBranch}...HEAD`, cwd);
1733
+ if (out !== null) {
1734
+ debugInfo.strategySucceeded = strategy;
1735
+ return { output: out, strategy };
1736
+ }
1737
+ }
1738
+ {
1739
+ const strategy = "diff HEAD~1 (shallow fallback)";
1740
+ debugInfo.strategiesAttempted.push(strategy);
1741
+ const out = runGit("diff --name-only --diff-filter=ACMRT HEAD~1", cwd);
1742
+ if (out !== null) {
1743
+ debugInfo.strategySucceeded = strategy;
1744
+ return { output: out, strategy };
1745
+ }
1746
+ }
1747
+ debugInfo.strategySucceeded = null;
1748
+ return null;
1749
+ }
1750
+ function filterFiles(rawFiles, extensions, workingDirectory, cwd, debugInfo) {
1751
+ const kept = [];
1752
+ for (const rel of rawFiles) {
1753
+ const normalized = rel.replace(/\\/g, "/").trim();
1754
+ if (!normalized) continue;
1755
+ const ext = import_path3.default.extname(normalized).toLowerCase();
1756
+ if (!extensions.includes(ext)) {
1757
+ debugInfo.droppedByExtension++;
1758
+ continue;
1759
+ }
1760
+ if (IGNORE_PREFIXES.some((prefix) => normalized.startsWith(prefix))) {
1761
+ debugInfo.droppedByIgnore++;
1762
+ continue;
1763
+ }
1764
+ if (workingDirectory) {
1765
+ const wdNorm = workingDirectory.replace(/\\/g, "/").replace(/\/$/, "");
1766
+ if (!normalized.startsWith(wdNorm + "/") && normalized !== wdNorm) {
1767
+ debugInfo.droppedByWorkingDir++;
1768
+ continue;
1769
+ }
1770
+ }
1771
+ const abs = import_path3.default.resolve(cwd, normalized);
1772
+ if (!import_fs3.default.existsSync(abs)) {
1773
+ debugInfo.droppedMissing++;
1774
+ continue;
1775
+ }
1776
+ kept.push(abs);
1777
+ }
1778
+ return kept;
1779
+ }
1780
+ function parseFileList(output) {
1781
+ return output.split("\n").map((f) => f.trim()).filter(Boolean);
1782
+ }
1783
+ function getChangedFiles(options = {}) {
1784
+ const {
1785
+ staged = false,
1786
+ base,
1787
+ cwd = process.cwd(),
1788
+ extensions = DEFAULT_EXTENSIONS,
1789
+ workingDirectory,
1790
+ debug = false
1791
+ } = options;
1792
+ const debugInfo = {
1793
+ strategiesAttempted: [],
1794
+ strategySucceeded: null,
1795
+ isShallowClone: false,
1796
+ isDetachedHead: false,
1797
+ rawFileCount: 0,
1798
+ droppedByExtension: 0,
1799
+ droppedByIgnore: 0,
1800
+ droppedMissing: 0,
1801
+ droppedByWorkingDir: 0
1802
+ };
1803
+ if (!isGitAvailable(cwd)) {
1804
+ return {
1805
+ files: [],
1806
+ mode: "fallback",
1807
+ totalDetected: 0,
1808
+ filteredOut: 0,
1809
+ debugInfo,
1810
+ zeroFilesReason: "Git is not available in this directory. Run inside a git repository."
1811
+ };
1812
+ }
1813
+ debugInfo.isShallowClone = isShallowClone(cwd);
1814
+ debugInfo.isDetachedHead = isDetachedHead(cwd);
1815
+ const envBase = process.env.GITHUB_BASE_REF ? normalizeBranchName(process.env.GITHUB_BASE_REF) : void 0;
1816
+ const resolvedBranch = base ? normalizeBranchName(base) : envBase;
1817
+ debugInfo.resolvedBase = resolvedBranch;
1818
+ let rawOutput = null;
1819
+ let mode;
1820
+ let usedBase;
1821
+ let zeroFilesReason;
1822
+ if (staged) {
1823
+ rawOutput = runGit("diff --name-only --cached --diff-filter=ACMRT", cwd);
1824
+ mode = "staged";
1825
+ debugInfo.strategiesAttempted.push("git diff --cached");
1826
+ if (rawOutput !== null) debugInfo.strategySucceeded = "git diff --cached";
1827
+ if (!rawOutput) {
1828
+ zeroFilesReason = "No staged files found. Run `git add <files>` before using --staged.";
1829
+ }
1830
+ } else if (resolvedBranch) {
1831
+ mode = "pr-diff";
1832
+ usedBase = resolvedBranch;
1833
+ const diffResult = tryPrDiff(resolvedBranch, cwd, debugInfo);
1834
+ if (diffResult) {
1835
+ rawOutput = diffResult.output;
1836
+ } else {
1837
+ const shallow = debugInfo.isShallowClone ? " Shallow clone detected \u2014 try fetch-depth: 0 in your workflow." : "";
1838
+ const detached = debugInfo.isDetachedHead ? " HEAD is detached \u2014 ensure checkout fetches the branch." : "";
1839
+ zeroFilesReason = `PR diff failed against "${resolvedBranch}". Tried: ${debugInfo.strategiesAttempted.join(", ")}.${shallow}${detached} Run with --debug-git for full diagnosis.`;
1840
+ }
1841
+ } else {
1842
+ mode = "uncommitted";
1843
+ const staged_ = runGit("diff --name-only --cached --diff-filter=ACMRT", cwd) ?? "";
1844
+ const unstaged = runGit("diff --name-only --diff-filter=ACMRT", cwd) ?? "";
1845
+ debugInfo.strategiesAttempted.push("git diff --cached", "git diff (unstaged)");
1846
+ const combined = /* @__PURE__ */ new Set([...parseFileList(staged_), ...parseFileList(unstaged)]);
1847
+ rawOutput = [...combined].join("\n");
1848
+ if (rawOutput) debugInfo.strategySucceeded = "uncommitted changes";
1849
+ if (!rawOutput) {
1850
+ zeroFilesReason = "No uncommitted changes found. Use --pr to scan PR changes, or --staged for staged files.";
1851
+ }
1852
+ }
1853
+ if (!rawOutput) {
1854
+ return {
1855
+ files: [],
1856
+ mode,
1857
+ base: usedBase,
1858
+ totalDetected: 0,
1859
+ filteredOut: 0,
1860
+ debugInfo,
1861
+ zeroFilesReason
1862
+ };
1863
+ }
1864
+ const rawFiles = parseFileList(rawOutput);
1865
+ debugInfo.rawFileCount = rawFiles.length;
1866
+ const kept = filterFiles(rawFiles, extensions, workingDirectory, cwd, debugInfo);
1867
+ const dropped = debugInfo.droppedByExtension + debugInfo.droppedByIgnore + debugInfo.droppedByWorkingDir + debugInfo.droppedMissing;
1868
+ if (kept.length === 0 && rawFiles.length > 0) {
1869
+ zeroFilesReason = `${rawFiles.length} changed file${rawFiles.length !== 1 ? "s" : ""} detected but all filtered out. Extension filter: ${debugInfo.droppedByExtension}, ignored paths: ${debugInfo.droppedByIgnore}, missing from disk: ${debugInfo.droppedMissing}.`;
1870
+ }
1871
+ return {
1872
+ files: kept,
1873
+ mode,
1874
+ base: usedBase,
1875
+ totalDetected: rawFiles.length,
1876
+ filteredOut: dropped,
1877
+ debugInfo,
1878
+ zeroFilesReason: kept.length === 0 ? zeroFilesReason : void 0
891
1879
  };
892
1880
  }
1881
+ function isGitHubActions() {
1882
+ return process.env.GITHUB_ACTIONS === "true";
1883
+ }
1884
+ function getGitHubBaseRef() {
1885
+ return process.env.GITHUB_BASE_REF || void 0;
1886
+ }
893
1887
 
894
1888
  // cli/commands/run.ts
895
1889
  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(
1890
+ 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
1891
  "--max-warnings <n>",
898
1892
  "Fail with exit code 1 if warnings exceed this count",
899
1893
  (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) => {
1894
+ ).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
1895
  if (opts.maxWarnings !== void 0 && (!Number.isInteger(opts.maxWarnings) || opts.maxWarnings < 0)) {
902
1896
  log.blank();
903
1897
  log.error("--max-warnings must be a non-negative integer.");
@@ -930,21 +1924,33 @@ function registerRunCommand(program2) {
930
1924
  log.blank();
931
1925
  log.error("TypeScript detected but parser not found.");
932
1926
  log.blank();
933
- log.print(` ${import_chalk2.default.bold("Install the TypeScript parser:")}`);
1927
+ log.print(` ${import_chalk3.default.bold("Install the TypeScript parser:")}`);
934
1928
  log.blank();
935
- log.print(` ${import_chalk2.default.cyan("npm install --save-dev @typescript-eslint/parser")}`);
1929
+ log.print(` ${import_chalk3.default.cyan("npm install --save-dev @typescript-eslint/parser")}`);
936
1930
  log.blank();
937
1931
  } else {
938
1932
  log.blank();
939
1933
  log.error(msg);
940
1934
  log.blank();
941
- log.print(` ${import_chalk2.default.bold("Fix:")} Run ${import_chalk2.default.cyan("ai-guard doctor")} to diagnose your setup.`);
1935
+ log.print(` ${import_chalk3.default.bold("Fix:")} Run ${import_chalk3.default.cyan("ai-guard doctor")} to diagnose your setup.`);
942
1936
  log.blank();
943
1937
  }
944
1938
  process.exit(1);
945
1939
  return;
946
1940
  }
1941
+ if (isGitHubActions()) {
1942
+ emitGitHubAnnotations(result);
1943
+ writeGitHubSummary(result, { preset, scanMode: "full" });
1944
+ writeGitHubOutputs(result);
1945
+ }
1946
+ if (opts.sarif) {
1947
+ const sarifLog = buildSarifLog(result);
1948
+ console.log(sarifToJson(sarifLog));
1949
+ process.exit(resolveExitCode(result, opts.failOn ?? "errors", opts.maxWarnings));
1950
+ return;
1951
+ }
947
1952
  if (opts.json) {
1953
+ const signalSummary = buildSignalSummary(result);
948
1954
  const jsonOutput = {
949
1955
  preset,
950
1956
  scannedPath: opts.path,
@@ -954,6 +1960,7 @@ function registerRunCommand(program2) {
954
1960
  totalIssues: result.totalIssues,
955
1961
  durationMs: result.durationMs,
956
1962
  timing: result.timing,
1963
+ signalSummary,
957
1964
  ruleBreakdown: Object.fromEntries(result.ruleBreakdown),
958
1965
  topFiles: result.topFiles,
959
1966
  files: result.files,
@@ -962,19 +1969,19 @@ function registerRunCommand(program2) {
962
1969
  tsParserAvailable: result.tsParserAvailable
963
1970
  };
964
1971
  console.log(JSON.stringify(jsonOutput, null, 2));
965
- process.exit(getRunExitCode(result, opts.maxWarnings));
1972
+ process.exit(resolveExitCode(result, opts.failOn ?? "errors", opts.maxWarnings));
966
1973
  return;
967
1974
  }
968
1975
  log.scanStats(result.filesScanned, result.files.length, result.durationMs, preset);
969
1976
  if (!result.tsParserAvailable) {
970
1977
  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)`)
1978
+ 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
1979
  );
973
1980
  }
974
1981
  log.blank();
975
1982
  if (result.totalIssues === 0 && result.ecosystemIssues.length === 0 && result.parserErrors.length === 0) {
976
1983
  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`)}`
1984
+ ` ${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
1985
  );
979
1986
  log.blank();
980
1987
  process.exit(0);
@@ -982,15 +1989,18 @@ function registerRunCommand(program2) {
982
1989
  }
983
1990
  if (result.totalIssues === 0) {
984
1991
  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`)}`
1992
+ ` ${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
1993
  );
987
1994
  log.blank();
988
1995
  }
989
1996
  if (result.totalIssues > 0) {
1997
+ const verbose = opts.verbose ?? false;
1998
+ const quiet = opts.quiet ?? false;
1999
+ const { actionableFiles, informationalFiles } = partitionByTier(result, quiet);
990
2000
  log.section("Summary by Category");
991
2001
  const categoryErrors = {};
992
2002
  const categoryWarnings = {};
993
- for (const file of result.files) {
2003
+ for (const file of actionableFiles) {
994
2004
  for (const issue of file.issues) {
995
2005
  const cat = RULE_CATEGORY[issue.ruleId] ?? "Other";
996
2006
  if (issue.severity === 2) {
@@ -1022,7 +2032,7 @@ function registerRunCommand(program2) {
1022
2032
  }
1023
2033
  log.blank();
1024
2034
  log.print(
1025
- ` ${import_chalk2.default.bold("Total:")} ${formatIssueCount(result.totalErrors, result.totalWarnings)}`
2035
+ ` ${import_chalk3.default.bold("Total:")} ${formatIssueCount(result.totalErrors, result.totalWarnings)}`
1026
2036
  );
1027
2037
  log.blank();
1028
2038
  if (result.ruleBreakdown.size > 0) {
@@ -1033,9 +2043,10 @@ function registerRunCommand(program2) {
1033
2043
  for (const [rule, count] of sorted) {
1034
2044
  const shortRule = rule.replace(/^ai-guard\//, "");
1035
2045
  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]");
2046
+ 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]");
2047
+ if (quiet && tier === "informational") continue;
1037
2048
  log.print(
1038
- ` ${import_chalk2.default.gray("\u2022")} ${import_chalk2.default.yellow(shortRule)} ${tierLabel}${import_chalk2.default.gray(":")} ${import_chalk2.default.white(String(count))}`
2049
+ ` ${import_chalk3.default.gray("\u2022")} ${import_chalk3.default.yellow(shortRule)} ${tierLabel2}${import_chalk3.default.gray(":")} ${import_chalk3.default.white(String(count))}`
1039
2050
  );
1040
2051
  }
1041
2052
  log.blank();
@@ -1044,20 +2055,35 @@ function registerRunCommand(program2) {
1044
2055
  log.section("Top Files");
1045
2056
  for (const { path: fp, count } of result.topFiles) {
1046
2057
  log.print(
1047
- ` ${import_chalk2.default.gray("\u2022")} ${import_chalk2.default.white(fp)} ${import_chalk2.default.gray(`(${count})`)}`
2058
+ ` ${import_chalk3.default.gray("\u2022")} ${import_chalk3.default.white(fp)} ${import_chalk3.default.gray(`(${count})`)}`
1048
2059
  );
1049
2060
  }
1050
2061
  log.blank();
1051
2062
  }
1052
- log.section("Issues by File");
1053
- log.blank();
1054
- const verbose = opts.verbose ?? false;
1055
- renderIssuesByFile(result, verbose);
2063
+ if (actionableFiles.length > 0) {
2064
+ log.section("Issues by File");
2065
+ log.blank();
2066
+ renderIssuesByFile({ ...result, files: actionableFiles }, verbose, quiet);
2067
+ }
2068
+ const totalInfoCount = informationalFiles.reduce((n, f) => n + f.issues.length, 0);
2069
+ if (totalInfoCount > 0 && !quiet) {
2070
+ if (verbose) {
2071
+ log.section("Informational Hints");
2072
+ log.print(import_chalk3.default.dim(` Stylistic hints with higher false-positive rate \u2014 review before acting.`));
2073
+ log.blank();
2074
+ renderIssuesByFile({ ...result, files: informationalFiles }, true, false);
2075
+ } else {
2076
+ log.blank();
2077
+ log.print(
2078
+ ` ${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`)}`
2079
+ );
2080
+ }
2081
+ }
1056
2082
  }
1057
2083
  if (result.ecosystemIssues.length > 0) {
1058
2084
  log.ecosystemSection("ESLint Config Issues");
1059
2085
  log.print(
1060
- import_chalk2.default.dim(` These issues come from your project's ESLint config, not from ai-guard.`)
2086
+ import_chalk3.default.dim(` These issues come from your project's ESLint config, not from ai-guard.`)
1061
2087
  );
1062
2088
  log.blank();
1063
2089
  const seen = /* @__PURE__ */ new Map();
@@ -1078,7 +2104,7 @@ function registerRunCommand(program2) {
1078
2104
  if (result.parserErrors.length > 0) {
1079
2105
  log.ecosystemSection("Parser Errors");
1080
2106
  log.print(
1081
- import_chalk2.default.dim(` These files could not be parsed \u2014 not ai-guard findings.`)
2107
+ import_chalk3.default.dim(` These files could not be parsed \u2014 not ai-guard findings.`)
1082
2108
  );
1083
2109
  log.blank();
1084
2110
  for (const pe of result.parserErrors) {
@@ -1087,10 +2113,10 @@ function registerRunCommand(program2) {
1087
2113
  if (!result.tsParserAvailable) {
1088
2114
  log.blank();
1089
2115
  log.print(
1090
- ` ${import_chalk2.default.cyan("\u2192 Fix:")} ${import_chalk2.default.yellow("npm install --save-dev @typescript-eslint/parser")}`
2116
+ ` ${import_chalk3.default.cyan("\u2192 Fix:")} ${import_chalk3.default.yellow("npm install --save-dev @typescript-eslint/parser")}`
1091
2117
  );
1092
2118
  log.print(
1093
- import_chalk2.default.dim(` TypeScript parser resolves most .ts/.tsx parse failures.`)
2119
+ import_chalk3.default.dim(` TypeScript parser resolves most .ts/.tsx parse failures.`)
1094
2120
  );
1095
2121
  }
1096
2122
  log.blank();
@@ -1099,21 +2125,63 @@ function registerRunCommand(program2) {
1099
2125
  log.blank();
1100
2126
  log.section("Next Steps");
1101
2127
  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`);
2128
+ log.info(`Run ${import_chalk3.default.cyan("ai-guard baseline")} to save these issues and track only new ones`);
2129
+ log.info(`Run ${import_chalk3.default.cyan("ai-guard report")} to generate a shareable HTML report`);
2130
+ log.info(`Run ${import_chalk3.default.cyan("ai-guard ignore")} to suppress dist/build noise`);
1105
2131
  }
1106
2132
  if (result.ecosystemIssues.length > 0) {
1107
- log.info(`Run ${import_chalk2.default.cyan("ai-guard doctor")} to diagnose ESLint config issues`);
2133
+ log.info(`Run ${import_chalk3.default.cyan("ai-guard doctor")} to diagnose ESLint config issues`);
1108
2134
  }
1109
2135
  if (!opts.verbose && hasGroupedWarnings(result)) {
1110
- log.info(`Run with ${import_chalk2.default.cyan("--verbose")} to expand grouped warnings`);
2136
+ log.info(`Run with ${import_chalk3.default.cyan("--verbose")} to expand grouped warnings`);
1111
2137
  }
1112
2138
  log.blank();
1113
2139
  process.exit(getRunExitCode(result, opts.maxWarnings));
1114
2140
  });
1115
2141
  }
1116
2142
  var GROUP_THRESHOLD = 4;
2143
+ function partitionByTier(result, quiet) {
2144
+ const actionableFiles = [];
2145
+ const informationalFiles = [];
2146
+ for (const file of result.files) {
2147
+ const actionableIssues = file.issues.filter((i) => {
2148
+ const tier = CONFIDENCE_TIER[i.ruleId];
2149
+ if (quiet && (tier === "informational" || i.severity === 1)) return false;
2150
+ return tier !== "informational";
2151
+ });
2152
+ const infoIssues = file.issues.filter((i) => {
2153
+ const tier = CONFIDENCE_TIER[i.ruleId];
2154
+ return tier === "informational";
2155
+ });
2156
+ if (actionableIssues.length > 0) {
2157
+ actionableFiles.push({ ...file, issues: actionableIssues });
2158
+ }
2159
+ if (infoIssues.length > 0) {
2160
+ informationalFiles.push({ ...file, issues: infoIssues });
2161
+ }
2162
+ }
2163
+ return { actionableFiles, informationalFiles };
2164
+ }
2165
+ function buildSignalSummary(result) {
2166
+ let high = 0, medium = 0, low = 0, informational = 0;
2167
+ for (const file of result.files) {
2168
+ for (const issue of file.issues) {
2169
+ const tier = CONFIDENCE_TIER[issue.ruleId];
2170
+ if (tier === "high") high++;
2171
+ else if (tier === "medium") medium++;
2172
+ else if (tier === "informational") informational++;
2173
+ else low++;
2174
+ }
2175
+ }
2176
+ return {
2177
+ high,
2178
+ medium,
2179
+ low,
2180
+ informational,
2181
+ ecosystemIssues: result.ecosystemIssues.length,
2182
+ parserErrors: result.parserErrors.length
2183
+ };
2184
+ }
1117
2185
  function hasGroupedWarnings(result) {
1118
2186
  for (const file of result.files) {
1119
2187
  const ruleCounts = /* @__PURE__ */ new Map();
@@ -1126,10 +2194,10 @@ function hasGroupedWarnings(result) {
1126
2194
  }
1127
2195
  return false;
1128
2196
  }
1129
- function renderIssuesByFile(result, verbose) {
2197
+ function renderIssuesByFile(result, verbose, quiet = false) {
1130
2198
  for (const file of result.files) {
1131
2199
  log.print(
1132
- ` ${import_chalk2.default.bold.white(file.filePath)} ` + import_chalk2.default.gray(
2200
+ ` ${import_chalk3.default.bold.white(file.filePath)} ` + import_chalk3.default.gray(
1133
2201
  `(${file.errorCount} error${file.errorCount !== 1 ? "s" : ""}, ${file.warningCount} warning${file.warningCount !== 1 ? "s" : ""})`
1134
2202
  )
1135
2203
  );
@@ -1143,13 +2211,13 @@ function renderIssuesByFile(result, verbose) {
1143
2211
  for (const issues of ruleCounts.values()) {
1144
2212
  const tier = CONFIDENCE_TIER[issues[0].ruleId] ?? "low";
1145
2213
  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\//, ""));
2214
+ const sev = issues[0].severity === 2 ? import_chalk3.default.red("error") : import_chalk3.default.yellow(" warn");
2215
+ const shortRule = import_chalk3.default.gray(issues[0].ruleId.replace(/^ai-guard\//, ""));
1148
2216
  log.print(
1149
- ` ${sev} ${import_chalk2.default.white(issues[0].message)} ${shortRule}`
2217
+ ` ${sev} ${import_chalk3.default.white(issues[0].message)} ${shortRule}`
1150
2218
  );
1151
2219
  log.print(
1152
- import_chalk2.default.dim(` \u21B3 ${issues.length} occurrences \u2014 run with --verbose to expand`)
2220
+ import_chalk3.default.dim(` \u21B3 ${issues.length} occurrences \u2014 run with --verbose to expand`)
1153
2221
  );
1154
2222
  } else {
1155
2223
  for (const issue of issues) {
@@ -1166,13 +2234,13 @@ function renderIssuesByFile(result, verbose) {
1166
2234
  }
1167
2235
  }
1168
2236
  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\//, ""));
2237
+ const sev = issue.severity === 2 ? import_chalk3.default.red("error") : import_chalk3.default.yellow(" warn");
2238
+ const loc = import_chalk3.default.gray(`${String(issue.line)}:${String(issue.column)}`).padEnd(12);
2239
+ const ruleShort = import_chalk3.default.gray(issue.ruleId.replace(/^ai-guard\//, ""));
1172
2240
  const tier = CONFIDENCE_TIER[issue.ruleId];
1173
- const tierTag = tier === "high" ? "" : tier === "medium" ? "" : import_chalk2.default.dim(" [low]");
2241
+ const tierTag = tier === "informational" ? import_chalk3.default.dim(" [info]") : "";
1174
2242
  log.print(
1175
- ` ${loc} ${sev} ${import_chalk2.default.white(issue.message)}${tierTag} ${ruleShort}`
2243
+ ` ${loc} ${sev} ${import_chalk3.default.white(issue.message)}${tierTag} ${ruleShort}`
1176
2244
  );
1177
2245
  }
1178
2246
  function getRunExitCode(result, maxWarnings) {
@@ -1182,20 +2250,294 @@ function getRunExitCode(result, maxWarnings) {
1182
2250
  }
1183
2251
  function formatIssueCount(errors, warnings) {
1184
2252
  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 "));
2253
+ if (errors > 0) parts.push(import_chalk3.default.red.bold(`${errors} error${errors !== 1 ? "s" : ""}`));
2254
+ if (warnings > 0) parts.push(import_chalk3.default.yellow.bold(`${warnings} warning${warnings !== 1 ? "s" : ""}`));
2255
+ return parts.join(import_chalk3.default.gray(" \xB7 "));
2256
+ }
2257
+ function resolveExitCode(result, failOn, maxWarnings) {
2258
+ if (maxWarnings !== void 0 && result.totalWarnings > maxWarnings) return 1;
2259
+ switch (failOn) {
2260
+ case "none":
2261
+ return 0;
2262
+ case "errors":
2263
+ return result.totalErrors > 0 ? 1 : 0;
2264
+ case "high": {
2265
+ const high = result.files.flatMap((f) => f.issues).filter((i) => CONFIDENCE_TIER[i.ruleId] === "high").length;
2266
+ return high > 0 ? 1 : 0;
2267
+ }
2268
+ case "medium": {
2269
+ const highMed = result.files.flatMap((f) => f.issues).filter((i) => CONFIDENCE_TIER[i.ruleId] === "high" || CONFIDENCE_TIER[i.ruleId] === "medium").length;
2270
+ return highMed > 0 ? 1 : 0;
2271
+ }
2272
+ case "any": {
2273
+ const actionable = result.files.flatMap((f) => f.issues).filter((i) => CONFIDENCE_TIER[i.ruleId] !== "informational").length;
2274
+ return actionable > 0 ? 1 : 0;
2275
+ }
2276
+ default:
2277
+ return getRunExitCode(result, maxWarnings);
2278
+ }
1188
2279
  }
1189
2280
 
1190
- // cli/commands/init.ts
1191
- var import_chalk3 = __toESM(require("chalk"));
2281
+ // cli/commands/changed.ts
1192
2282
  var import_path4 = __toESM(require("path"));
1193
2283
  var import_fs4 = __toESM(require("fs"));
2284
+ var import_ora2 = __toESM(require("ora"));
2285
+ var import_chalk5 = __toESM(require("chalk"));
2286
+ function registerChangedCommand(program2) {
2287
+ 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 (raw name: main, develop \u2014 not 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 | errors | 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 this file path").option("--sarif-stdout", "Force SARIF to stdout even in CI (overrides auto-file behavior)").option("--github-summary", "Write GitHub step summary (auto-enabled in GitHub Actions)").option(
2288
+ "--max-warnings <n>",
2289
+ "Fail if warnings exceed this count",
2290
+ (v) => Number.parseInt(v, 10)
2291
+ ).option("--verbose", "Expand all findings (disable grouping)").option("--quiet", "Show errors only").option("--debug-timing", "Print per-phase timing diagnostics").option("--debug-git", "Print full git resolution trace (useful for diagnosing zero-file issues)").option("--debug-ci", "Print CI environment state (GITHUB_* vars, SARIF path, summary path)").action(async (opts) => {
2292
+ const preset = opts.strict ? "strict" : opts.security ? "security" : "recommended";
2293
+ const inCI = isGitHubActions();
2294
+ const baseRef = opts.base ?? (opts.pr || inCI ? getGitHubBaseRef() : void 0);
2295
+ const sarifToFile = opts.sarif && !opts.sarifStdout && (opts.sarifOutput || inCI);
2296
+ const resolvedSarifPath = opts.sarifOutput ?? (sarifToFile ? "ai-guard-results.sarif" : void 0);
2297
+ if (!opts.json && !opts.sarif) {
2298
+ log.banner("AI GUARD");
2299
+ }
2300
+ if (opts.debugCi) {
2301
+ const { printCIDebug: printCIDebug2 } = await Promise.resolve().then(() => (init_debug(), debug_exports));
2302
+ printCIDebug2({
2303
+ isGitHubActions: inCI,
2304
+ githubBaseRef: process.env.GITHUB_BASE_REF,
2305
+ githubSha: process.env.GITHUB_SHA,
2306
+ githubRef: process.env.GITHUB_REF,
2307
+ githubRepository: process.env.GITHUB_REPOSITORY,
2308
+ githubWorkflow: process.env.GITHUB_WORKFLOW,
2309
+ githubRunId: process.env.GITHUB_RUN_ID,
2310
+ githubEventName: process.env.GITHUB_EVENT_NAME,
2311
+ githubStepSummary: process.env.GITHUB_STEP_SUMMARY,
2312
+ githubOutput: process.env.GITHUB_OUTPUT,
2313
+ sarifOutputPath: resolvedSarifPath,
2314
+ preset,
2315
+ failOn: opts.failOn,
2316
+ scanMode: opts.staged ? "staged" : baseRef ? "pr-diff" : "uncommitted"
2317
+ });
2318
+ }
2319
+ const cwd = process.cwd();
2320
+ const resolvedPath = import_path4.default.resolve(opts.path);
2321
+ const workingDirectory = opts.path !== "." ? opts.path : void 0;
2322
+ const spinner = opts.json || opts.sarif ? null : (0, import_ora2.default)({ text: import_chalk5.default.dim("Detecting changed files\u2026"), color: "cyan" }).start();
2323
+ const changedResult = getChangedFiles({
2324
+ staged: opts.staged ?? false,
2325
+ base: baseRef,
2326
+ cwd,
2327
+ workingDirectory,
2328
+ debug: opts.debugGit
2329
+ });
2330
+ if (opts.debugGit) {
2331
+ const { printGitDebug: printGitDebug2 } = await Promise.resolve().then(() => (init_debug(), debug_exports));
2332
+ printGitDebug2(changedResult);
2333
+ }
2334
+ if (changedResult.files.length === 0) {
2335
+ spinner?.stop();
2336
+ if (changedResult.mode === "fallback") {
2337
+ if (!opts.json && !opts.sarif) {
2338
+ log.warn("Git not available. Falling back to full scan.");
2339
+ log.blank();
2340
+ }
2341
+ const result2 = await runEslint({
2342
+ preset,
2343
+ targetPath: resolvedPath.toString(),
2344
+ debugTiming: opts.debugTiming
2345
+ });
2346
+ handleResults(result2, opts, preset, "full", 0, inCI, resolvedSarifPath);
2347
+ return;
2348
+ }
2349
+ if (!opts.json && !opts.sarif) {
2350
+ log.blank();
2351
+ log.print(` ${import_chalk5.default.yellow("\u26A0")} No changed JS/TS files detected \u2014 nothing to scan`);
2352
+ if (changedResult.zeroFilesReason) {
2353
+ log.print(import_chalk5.default.dim(` ${changedResult.zeroFilesReason}`));
2354
+ }
2355
+ if (changedResult.filteredOut > 0) {
2356
+ log.print(import_chalk5.default.dim(` (${changedResult.filteredOut} file${changedResult.filteredOut !== 1 ? "s" : ""} filtered out)`));
2357
+ }
2358
+ if (!opts.debugGit) {
2359
+ log.print(import_chalk5.default.dim(" Run with --debug-git for full git resolution trace."));
2360
+ }
2361
+ log.blank();
2362
+ } else if (opts.json) {
2363
+ console.log(JSON.stringify({
2364
+ version: PKG_VERSION,
2365
+ preset,
2366
+ scanMode: changedResult.mode,
2367
+ changedFiles: 0,
2368
+ filesScanned: 0,
2369
+ totalIssues: 0,
2370
+ totalErrors: 0,
2371
+ totalWarnings: 0,
2372
+ zeroFilesReason: changedResult.zeroFilesReason,
2373
+ debugInfo: opts.debugGit ? changedResult.debugInfo : void 0,
2374
+ signalSummary: { high: 0, medium: 0, low: 0, informational: 0, ecosystemIssues: 0, parserErrors: 0 }
2375
+ }, null, 2));
2376
+ }
2377
+ process.exit(0);
2378
+ return;
2379
+ }
2380
+ if (spinner) {
2381
+ spinner.text = import_chalk5.default.dim(`Scanning ${changedResult.files.length} changed file${changedResult.files.length !== 1 ? "s" : ""}\u2026`);
2382
+ }
2383
+ const result = await runEslint({
2384
+ preset,
2385
+ targetPath: resolvedPath.toString(),
2386
+ files: changedResult.files,
2387
+ debugTiming: opts.debugTiming
2388
+ });
2389
+ spinner?.stop();
2390
+ const scanMode = changedResult.mode === "staged" ? "staged" : "changed";
2391
+ handleResults(result, opts, preset, scanMode, changedResult.files.length, inCI, resolvedSarifPath);
2392
+ });
2393
+ }
2394
+ function handleResults(result, opts, preset, scanMode, changedFilesCount, inCI, resolvedSarifPath) {
2395
+ if (inCI || opts.githubSummary) {
2396
+ writeGitHubSummary(result, { preset, scanMode, changedFilesCount });
2397
+ writeGitHubOutputs(result, opts.sarifOutput);
2398
+ emitGitHubAnnotations(result);
2399
+ }
2400
+ if (opts.sarif) {
2401
+ const sarifLog = buildSarifLog(result);
2402
+ const sarifJson = sarifToJson(sarifLog);
2403
+ if (resolvedSarifPath) {
2404
+ import_fs4.default.writeFileSync(resolvedSarifPath, sarifJson, "utf-8");
2405
+ } else {
2406
+ console.log(sarifJson);
2407
+ }
2408
+ process.exit(getFailOnExitCode(result, opts.failOn, opts.maxWarnings));
2409
+ return;
2410
+ }
2411
+ if (opts.json) {
2412
+ const signalSummary = buildSignalSummary(result);
2413
+ const jsonOutput = {
2414
+ version: PKG_VERSION,
2415
+ preset,
2416
+ scanMode,
2417
+ scannedPath: opts.path,
2418
+ filesScanned: result.filesScanned,
2419
+ changedFilesCount,
2420
+ totalErrors: result.totalErrors,
2421
+ totalWarnings: result.totalWarnings,
2422
+ totalIssues: result.totalIssues,
2423
+ durationMs: result.durationMs,
2424
+ signalSummary,
2425
+ ruleBreakdown: Object.fromEntries(result.ruleBreakdown),
2426
+ topFiles: result.topFiles,
2427
+ files: result.files,
2428
+ ecosystemIssues: result.ecosystemIssues,
2429
+ parserErrors: result.parserErrors,
2430
+ tsParserAvailable: result.tsParserAvailable
2431
+ };
2432
+ console.log(JSON.stringify(jsonOutput, null, 2));
2433
+ process.exit(getFailOnExitCode(result, opts.failOn, opts.maxWarnings));
2434
+ return;
2435
+ }
2436
+ const scanModeLabel = scanMode === "changed" ? "Changed files" : scanMode === "staged" ? "Staged files" : "Full scan";
2437
+ const modeTag = changedFilesCount > 0 ? `${changedFilesCount} changed` : "";
2438
+ log.summary(
2439
+ result.filesScanned,
2440
+ result.totalIssues,
2441
+ result.files.filter((f) => f.issues.length > 0).length,
2442
+ result.durationMs,
2443
+ `${preset} \xB7 ${scanModeLabel}${modeTag ? ` (${modeTag})` : ""}`
2444
+ );
2445
+ if (result.totalIssues === 0) {
2446
+ log.blank();
2447
+ log.success(`No AI issues found \u2014 ${result.filesScanned} file${result.filesScanned !== 1 ? "s" : ""} scanned, all clean`);
2448
+ log.blank();
2449
+ } else {
2450
+ const verbose = opts.verbose ?? false;
2451
+ const quiet = opts.quiet ?? false;
2452
+ const { actionableFiles, informationalFiles } = partitionByTier(result, quiet);
2453
+ log.section("Summary by Category");
2454
+ const categoryErrors = {};
2455
+ const categoryWarnings = {};
2456
+ for (const file of actionableFiles) {
2457
+ for (const issue of file.issues) {
2458
+ const cat = RULE_CATEGORY[issue.ruleId] ?? "Other";
2459
+ if (issue.severity === 2) categoryErrors[cat] = (categoryErrors[cat] ?? 0) + 1;
2460
+ else categoryWarnings[cat] = (categoryWarnings[cat] ?? 0) + 1;
2461
+ }
2462
+ }
2463
+ const allCategories = /* @__PURE__ */ new Set([...CATEGORY_ORDER, ...Object.keys(categoryErrors), ...Object.keys(categoryWarnings)]);
2464
+ const sortedCats = [...allCategories].filter((c) => (categoryErrors[c] ?? 0) + (categoryWarnings[c] ?? 0) > 0).sort((a, b) => {
2465
+ const ai = CATEGORY_ORDER.indexOf(a), bi = CATEGORY_ORDER.indexOf(b);
2466
+ if (ai !== -1 && bi !== -1) return ai - bi;
2467
+ if (ai !== -1) return -1;
2468
+ if (bi !== -1) return 1;
2469
+ return a.localeCompare(b);
2470
+ });
2471
+ for (const cat of sortedCats) {
2472
+ const icon = CATEGORY_ICONS[cat] ?? "\u26AA";
2473
+ log.category(icon, cat, categoryErrors[cat] ?? 0, categoryWarnings[cat] ?? 0);
2474
+ }
2475
+ log.blank();
2476
+ log.print(` ${import_chalk5.default.bold("Total:")} ${formatIssueCount(result.totalErrors, result.totalWarnings)}`);
2477
+ log.blank();
2478
+ if (actionableFiles.length > 0) {
2479
+ log.section("Issues by File");
2480
+ log.blank();
2481
+ renderIssuesByFile({ ...result, files: actionableFiles }, verbose, quiet);
2482
+ }
2483
+ const totalInfoCount = informationalFiles.reduce((n, f) => n + f.issues.length, 0);
2484
+ if (totalInfoCount > 0 && !quiet) {
2485
+ if (verbose) {
2486
+ log.section("Informational Hints");
2487
+ log.print(import_chalk5.default.dim(" Stylistic hints with higher false-positive rate \u2014 review before acting."));
2488
+ log.blank();
2489
+ renderIssuesByFile({ ...result, files: informationalFiles }, true, false);
2490
+ } else {
2491
+ log.blank();
2492
+ log.print(` ${import_chalk5.default.gray("\u25B8")} ${import_chalk5.default.gray(`${totalInfoCount} informational hint${totalInfoCount !== 1 ? "s" : ""} \u2014 run with ${import_chalk5.default.cyan("--verbose")} to expand`)}`);
2493
+ }
2494
+ }
2495
+ }
2496
+ if (result.ecosystemIssues.length > 0) {
2497
+ log.ecosystemSection("ESLint Config Issues");
2498
+ log.print(import_chalk5.default.dim(" These issues come from your project's ESLint config, not from ai-guard."));
2499
+ log.blank();
2500
+ const seen = /* @__PURE__ */ new Map();
2501
+ for (const issue of result.ecosystemIssues) {
2502
+ const key = `${issue.ruleId ?? "null"}::${issue.message}`;
2503
+ const existing = seen.get(key);
2504
+ if (existing) {
2505
+ existing.count++;
2506
+ } else {
2507
+ seen.set(key, { issue, count: 1 });
2508
+ }
2509
+ }
2510
+ for (const { issue, count } of seen.values()) {
2511
+ log.ecosystemIssue(issue, count);
2512
+ }
2513
+ log.blank();
2514
+ }
2515
+ log.divider();
2516
+ log.blank();
2517
+ log.section("Next Steps");
2518
+ if (result.totalIssues > 0) {
2519
+ log.info(`Run ${import_chalk5.default.cyan("ai-guard baseline")} to save these issues and track only new ones`);
2520
+ log.info(`Run ${import_chalk5.default.cyan("ai-guard report")} to generate a shareable HTML report`);
2521
+ }
2522
+ if (result.ecosystemIssues.length > 0) {
2523
+ log.info(`Run ${import_chalk5.default.cyan("ai-guard doctor")} to diagnose ESLint config issues`);
2524
+ }
2525
+ log.blank();
2526
+ process.exit(getFailOnExitCode(result, opts.failOn, opts.maxWarnings));
2527
+ }
2528
+ function getFailOnExitCode(result, failOn, maxWarnings) {
2529
+ return resolveExitCode(result, failOn, maxWarnings);
2530
+ }
2531
+
2532
+ // cli/commands/init.ts
2533
+ var import_chalk6 = __toESM(require("chalk"));
2534
+ var import_path7 = __toESM(require("path"));
2535
+ var import_fs7 = __toESM(require("fs"));
1194
2536
 
1195
2537
  // cli/utils/detector.ts
1196
- var import_fs2 = __toESM(require("fs"));
1197
- var import_path2 = __toESM(require("path"));
1198
- var import_module = require("module");
2538
+ var import_fs5 = __toESM(require("fs"));
2539
+ var import_path5 = __toESM(require("path"));
2540
+ var import_module2 = require("module");
1199
2541
  var FLAT_CONFIG_FILES = [
1200
2542
  { file: "eslint.config.js", type: "flat-js" },
1201
2543
  { file: "eslint.config.mjs", type: "flat-mjs" },
@@ -1211,11 +2553,11 @@ var LEGACY_CONFIG_FILES = [
1211
2553
  var ALL_CONFIG_FILES = [...FLAT_CONFIG_FILES, ...LEGACY_CONFIG_FILES];
1212
2554
  function isPackageInstalled(pkgName, cwd = process.cwd()) {
1213
2555
  try {
1214
- const pkgPath = import_path2.default.join(cwd, "node_modules", pkgName, "package.json");
1215
- if (import_fs2.default.existsSync(pkgPath)) {
2556
+ const pkgPath = import_path5.default.join(cwd, "node_modules", pkgName, "package.json");
2557
+ if (import_fs5.default.existsSync(pkgPath)) {
1216
2558
  return true;
1217
2559
  }
1218
- const req = (0, import_module.createRequire)(import_path2.default.join(cwd, "package.json"));
2560
+ const req = (0, import_module2.createRequire)(import_path5.default.join(cwd, "package.json"));
1219
2561
  req.resolve(`${pkgName}/package.json`);
1220
2562
  return true;
1221
2563
  } catch {
@@ -1224,9 +2566,9 @@ function isPackageInstalled(pkgName, cwd = process.cwd()) {
1224
2566
  }
1225
2567
  function getPackageVersion(pkgName, cwd = process.cwd()) {
1226
2568
  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");
2569
+ const pkgPath = import_path5.default.join(cwd, "node_modules", pkgName, "package.json");
2570
+ const resolvedPath = import_fs5.default.existsSync(pkgPath) ? pkgPath : (0, import_module2.createRequire)(import_path5.default.join(cwd, "package.json")).resolve(`${pkgName}/package.json`);
2571
+ const raw = import_fs5.default.readFileSync(resolvedPath, "utf-8");
1230
2572
  const json = JSON.parse(raw);
1231
2573
  return json.version ?? null;
1232
2574
  } catch {
@@ -1242,8 +2584,8 @@ function detectConfigType(cwd = process.cwd()) {
1242
2584
  const found = [];
1243
2585
  let first = null;
1244
2586
  for (const { file, type } of ALL_CONFIG_FILES) {
1245
- const fullPath = import_path2.default.join(cwd, file);
1246
- if (import_fs2.default.existsSync(fullPath)) {
2587
+ const fullPath = import_path5.default.join(cwd, file);
2588
+ if (import_fs5.default.existsSync(fullPath)) {
1247
2589
  found.push(fullPath);
1248
2590
  if (!first) first = { type, path: fullPath };
1249
2591
  }
@@ -1266,7 +2608,7 @@ function requiresFlatConfig(eslintMajor) {
1266
2608
  function detectNukeIgnore(configPath) {
1267
2609
  if (!configPath) return false;
1268
2610
  try {
1269
- const content = import_fs2.default.readFileSync(configPath, "utf-8");
2611
+ const content = import_fs5.default.readFileSync(configPath, "utf-8");
1270
2612
  return content.includes('"**/*"') || content.includes("'**/*'");
1271
2613
  } catch {
1272
2614
  return false;
@@ -1322,7 +2664,7 @@ async function verifyConfigLoads(configPath, cwd) {
1322
2664
  overrideConfigFile: configPath
1323
2665
  });
1324
2666
  await eslint.lintText("", {
1325
- filePath: import_path4.default.join(cwd, "_ai_guard_probe_.js")
2667
+ filePath: import_path7.default.join(cwd, "_ai_guard_probe_.js")
1326
2668
  });
1327
2669
  return null;
1328
2670
  } catch (err) {
@@ -1340,7 +2682,7 @@ function registerInitCommand(program2) {
1340
2682
  const isDryRun = opts.dryRun === true;
1341
2683
  log.banner("AI GUARD INIT");
1342
2684
  if (isDryRun) {
1343
- log.print(` ${import_chalk3.default.yellow("\u2691 DRY RUN \u2014 no files will be written")}`);
2685
+ log.print(` ${import_chalk6.default.yellow("\u2691 DRY RUN \u2014 no files will be written")}`);
1344
2686
  log.blank();
1345
2687
  }
1346
2688
  log.info("Detecting your project setup\u2026");
@@ -1358,7 +2700,7 @@ function registerInitCommand(program2) {
1358
2700
  log.warn("eslint-plugin-ai-guard not found");
1359
2701
  }
1360
2702
  if (env.configType !== "none") {
1361
- log.success(`ESLint config found: ${import_chalk3.default.white(import_path4.default.relative(cwd, env.configPath ?? env.configType))}`);
2703
+ log.success(`ESLint config found: ${import_chalk6.default.white(import_path7.default.relative(cwd, env.configPath ?? env.configType))}`);
1362
2704
  } else {
1363
2705
  log.info("No ESLint config found \u2014 will generate one");
1364
2706
  }
@@ -1367,9 +2709,9 @@ function registerInitCommand(program2) {
1367
2709
  log.blank();
1368
2710
  log.warn("Conflicting configs detected:");
1369
2711
  for (const p of env.allConfigPaths) {
1370
- log.print(` ${import_chalk3.default.yellow("\u2192")} ${import_chalk3.default.white(import_path4.default.relative(cwd, p))}`);
2712
+ log.print(` ${import_chalk6.default.yellow("\u2192")} ${import_chalk6.default.white(import_path7.default.relative(cwd, p))}`);
1371
2713
  }
1372
- log.print(` ${import_chalk3.default.gray("Multiple config formats found \u2014 this can cause unpredictable behavior.")}`);
2714
+ log.print(` ${import_chalk6.default.gray("Multiple config formats found \u2014 this can cause unpredictable behavior.")}`);
1373
2715
  }
1374
2716
  log.blank();
1375
2717
  const toInstall = [];
@@ -1380,22 +2722,22 @@ function registerInitCommand(program2) {
1380
2722
  log.warn("The following packages are not installed:");
1381
2723
  log.blank();
1382
2724
  for (const pkg of toInstall) {
1383
- log.print(` ${import_chalk3.default.yellow("\u2192")} ${import_chalk3.default.white(pkg)}`);
2725
+ log.print(` ${import_chalk6.default.yellow("\u2192")} ${import_chalk6.default.white(pkg)}`);
1384
2726
  }
1385
2727
  log.blank();
1386
- log.print(` ${import_chalk3.default.bold("Run this first:")}`);
2728
+ log.print(` ${import_chalk6.default.bold("Run this first:")}`);
1387
2729
  log.blank();
1388
- log.print(` ${import_chalk3.default.cyan(`npm install --save-dev ${toInstall.join(" ")}`)}`);
2730
+ log.print(` ${import_chalk6.default.cyan(`npm install --save-dev ${toInstall.join(" ")}`)}`);
1389
2731
  log.blank();
1390
- log.print(` Then re-run ${import_chalk3.default.cyan("ai-guard init")} to complete setup.`);
2732
+ log.print(` Then re-run ${import_chalk6.default.cyan("ai-guard init")} to complete setup.`);
1391
2733
  log.blank();
1392
2734
  process.exit(1);
1393
2735
  return;
1394
2736
  }
1395
2737
  try {
1396
- const { createRequire: createRequire2 } = await import("module");
2738
+ const { createRequire: createRequire3 } = await import("module");
1397
2739
  const { pathToFileURL: pathToFileURL2 } = await import("url");
1398
- const req = createRequire2(import_path4.default.join(cwd, "_probe.js"));
2740
+ const req = createRequire3(import_path7.default.join(cwd, "_probe.js"));
1399
2741
  const resolvedPath = req.resolve("eslint-plugin-ai-guard");
1400
2742
  const aiGuard = await import(pathToFileURL2(resolvedPath).href);
1401
2743
  const plugin = aiGuard.default || aiGuard;
@@ -1414,9 +2756,9 @@ function registerInitCommand(program2) {
1414
2756
  if (eslintNeedsFlat && existingIsLegacy) {
1415
2757
  log.section("Config Migration Required");
1416
2758
  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))}`);
2759
+ log.print(` ${import_chalk6.default.white(import_path7.default.relative(cwd, env.configPath))}`);
1418
2760
  log.blank();
1419
- log.print(` ${import_chalk3.default.bold("ai-guard will create a new flat config.")}`);
2761
+ log.print(` ${import_chalk6.default.bold("ai-guard will create a new flat config.")}`);
1420
2762
  log.blank();
1421
2763
  }
1422
2764
  log.section(`Configuring ESLint${isDryRun ? " (dry run)" : ""}`);
@@ -1426,34 +2768,34 @@ function registerInitCommand(program2) {
1426
2768
  const raw = readConfig(env.configPath);
1427
2769
  const { content: fixed, changed } = removeNukeIgnore(raw);
1428
2770
  if (changed) {
1429
- log.warn(`Removed "**/*" ignore pattern from ${import_path4.default.relative(cwd, env.configPath)}`);
2771
+ log.warn(`Removed "**/*" ignore pattern from ${import_path7.default.relative(cwd, env.configPath)}`);
1430
2772
  if (!isDryRun) {
1431
2773
  backupConfig(env.configPath);
1432
2774
  writeConfig(env.configPath, fixed);
1433
2775
  log.success("Ignore pattern fixed");
1434
2776
  } else {
1435
- log.print(` ${import_chalk3.default.gray("[dry-run] would remove nuke-ignore from existing config")}`);
2777
+ log.print(` ${import_chalk6.default.gray("[dry-run] would remove nuke-ignore from existing config")}`);
1436
2778
  }
1437
2779
  }
1438
2780
  }
1439
2781
  if (env.configType === "none" || eslintNeedsFlat && existingIsLegacy) {
1440
2782
  const targetName = useFlat ? "eslint.config.mjs" : ".eslintrc.js";
1441
- finalConfigPath = import_path4.default.join(cwd, targetName);
2783
+ finalConfigPath = import_path7.default.join(cwd, targetName);
1442
2784
  const content2 = useFlat ? generateFlatConfig(preset) : generateLegacyConfig(preset);
1443
2785
  if (isDryRun) {
1444
- log.print(` ${import_chalk3.default.gray(`[dry-run] would create: ${targetName}`)}`);
2786
+ log.print(` ${import_chalk6.default.gray(`[dry-run] would create: ${targetName}`)}`);
1445
2787
  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"));
2788
+ log.print(import_chalk6.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
2789
  log.blank();
1448
2790
  for (const line of content2.split("\n")) {
1449
- log.print(import_chalk3.default.gray(` ${line}`));
2791
+ log.print(import_chalk6.default.gray(` ${line}`));
1450
2792
  }
1451
2793
  log.blank();
1452
2794
  } else {
1453
2795
  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)")}`);
2796
+ log.success(`Created ${import_chalk6.default.white(targetName)}`);
2797
+ log.info(` Preset: ${import_chalk6.default.cyan(preset)}`);
2798
+ log.info(` Format: ${import_chalk6.default.cyan(useFlat ? "ESLint v9 flat config (eslint.config.mjs)" : "ESLint v8 legacy config (.eslintrc.js)")}`);
1457
2799
  configWritten = true;
1458
2800
  }
1459
2801
  } else if (useFlat && existingIsFlat) {
@@ -1466,15 +2808,15 @@ function registerInitCommand(program2) {
1466
2808
  finalConfigPath = env.configPath;
1467
2809
  } else if (isDryRun) {
1468
2810
  finalConfigPath = env.configPath;
1469
- log.print(` ${import_chalk3.default.gray(`[dry-run] would patch: ${import_path4.default.relative(cwd, env.configPath)}`)}`);
2811
+ log.print(` ${import_chalk6.default.gray(`[dry-run] would patch: ${import_path7.default.relative(cwd, env.configPath)}`)}`);
1470
2812
  } else {
1471
2813
  const bak = backupConfig(env.configPath);
1472
- log.info(`Backed up \u2192 ${import_chalk3.default.gray(import_path4.default.relative(cwd, bak))}`);
2814
+ log.info(`Backed up \u2192 ${import_chalk6.default.gray(import_path7.default.relative(cwd, bak))}`);
1473
2815
  writeConfig(env.configPath, patched);
1474
2816
  if (isInvalid) {
1475
- log.success(`Detected invalid ai-guard config \u2014 repairing ${import_chalk3.default.white(import_path4.default.relative(cwd, env.configPath))}`);
2817
+ log.success(`Detected invalid ai-guard config \u2014 repairing ${import_chalk6.default.white(import_path7.default.relative(cwd, env.configPath))}`);
1476
2818
  } else {
1477
- log.success(`Patched ${import_chalk3.default.white(import_path4.default.relative(cwd, env.configPath))}`);
2819
+ log.success(`Patched ${import_chalk6.default.white(import_path7.default.relative(cwd, env.configPath))}`);
1478
2820
  }
1479
2821
  finalConfigPath = env.configPath;
1480
2822
  configWritten = true;
@@ -1487,12 +2829,12 @@ function registerInitCommand(program2) {
1487
2829
  finalConfigPath = env.configPath;
1488
2830
  } else if (isDryRun) {
1489
2831
  finalConfigPath = env.configPath;
1490
- log.print(` ${import_chalk3.default.gray(`[dry-run] would patch: ${import_path4.default.relative(cwd, env.configPath)}`)}`);
2832
+ log.print(` ${import_chalk6.default.gray(`[dry-run] would patch: ${import_path7.default.relative(cwd, env.configPath)}`)}`);
1491
2833
  } else {
1492
2834
  const bak = backupConfig(env.configPath);
1493
- log.info(`Backed up \u2192 ${import_chalk3.default.gray(import_path4.default.relative(cwd, bak))}`);
2835
+ log.info(`Backed up \u2192 ${import_chalk6.default.gray(import_path7.default.relative(cwd, bak))}`);
1494
2836
  writeConfig(env.configPath, patched);
1495
- log.success(`Patched ${import_chalk3.default.white(import_path4.default.relative(cwd, env.configPath))}`);
2837
+ log.success(`Patched ${import_chalk6.default.white(import_path7.default.relative(cwd, env.configPath))}`);
1496
2838
  finalConfigPath = env.configPath;
1497
2839
  configWritten = true;
1498
2840
  }
@@ -1501,18 +2843,18 @@ function registerInitCommand(program2) {
1501
2843
  const legacyConfigs = env.allConfigPaths.filter((p) => !p.includes("eslint.config."));
1502
2844
  let cleanedUp = false;
1503
2845
  for (const p of legacyConfigs) {
1504
- if (import_fs4.default.existsSync(p)) {
2846
+ if (import_fs7.default.existsSync(p)) {
1505
2847
  if (!isDryRun) {
1506
2848
  try {
1507
- import_fs4.default.renameSync(p, `${p}.bak`);
2849
+ import_fs7.default.renameSync(p, `${p}.bak`);
1508
2850
  cleanedUp = true;
1509
2851
  } catch (err) {
1510
2852
  const reason = err instanceof Error ? err.message : String(err);
1511
- log.warn(`Could not back up ${import_path4.default.relative(cwd, p)}: ${reason}`);
2853
+ log.warn(`Could not back up ${import_path7.default.relative(cwd, p)}: ${reason}`);
1512
2854
  }
1513
2855
  } else {
1514
2856
  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`)}`);
2857
+ log.print(` ${import_chalk6.default.gray(`[dry-run] would rename ${import_path7.default.relative(cwd, p)} to ${import_path7.default.basename(p)}.bak`)}`);
1516
2858
  }
1517
2859
  }
1518
2860
  }
@@ -1522,19 +2864,19 @@ function registerInitCommand(program2) {
1522
2864
  }
1523
2865
  if (isDryRun) {
1524
2866
  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.`);
2867
+ log.print(` ${import_chalk6.default.yellow("Dry run complete \u2014 no files written.")}`);
2868
+ log.print(` Re-run without ${import_chalk6.default.cyan("--dry-run")} to apply.`);
1527
2869
  log.blank();
1528
2870
  process.exit(0);
1529
2871
  return;
1530
2872
  }
1531
2873
  log.blank();
1532
2874
  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)}`);
2875
+ if (!import_fs7.default.existsSync(finalConfigPath)) {
2876
+ log.error(`Config file not found after write: ${import_path7.default.relative(cwd, finalConfigPath)}`);
1535
2877
  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.`);
2878
+ log.print(` ${import_chalk6.default.bold("Problem:")} File write may have failed.`);
2879
+ log.print(` ${import_chalk6.default.bold("Fix:")} Check disk space and file permissions, then re-run.`);
1538
2880
  log.blank();
1539
2881
  process.exit(1);
1540
2882
  return;
@@ -1544,36 +2886,36 @@ function registerInitCommand(program2) {
1544
2886
  if (validationErrors.length > 0) {
1545
2887
  log.warn("Config validation found issues:");
1546
2888
  for (const e of validationErrors) {
1547
- log.print(` ${import_chalk3.default.red("\u2716")} ${e}`);
2889
+ log.print(` ${import_chalk6.default.red("\u2716")} ${e}`);
1548
2890
  }
1549
2891
  } else {
1550
- log.success(`Config structure valid: ${import_chalk3.default.white(import_path4.default.relative(cwd, finalConfigPath))}`);
2892
+ log.success(`Config structure valid: ${import_chalk6.default.white(import_path7.default.relative(cwd, finalConfigPath))}`);
1551
2893
  }
1552
2894
  log.info("Verifying ESLint can load the config\u2026");
1553
2895
  const loadError = await verifyConfigLoads(finalConfigPath, cwd);
1554
2896
  if (loadError !== null) {
1555
2897
  log.blank();
1556
2898
  if (loadError.kind === "module-not-found") {
1557
- log.warn(`Could not verify: module not found: ${import_chalk3.default.yellow(loadError.pkg)}`);
2899
+ log.warn(`Could not verify: module not found: ${import_chalk6.default.yellow(loadError.pkg)}`);
1558
2900
  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.`);
2901
+ log.print(` ${import_chalk6.default.bold("Install the missing package:")}`);
2902
+ log.print(` ${import_chalk6.default.cyan(`npm install --save-dev ${loadError.pkg}`)}`);
2903
+ log.print(` Then run ${import_chalk6.default.cyan("ai-guard doctor")} to confirm.`);
1562
2904
  log.blank();
1563
2905
  } else {
1564
2906
  log.error("ESLint failed to load the generated config.");
1565
2907
  log.blank();
1566
- log.print(` ${import_chalk3.default.bold("Problem:")}`);
1567
- log.print(` ${import_chalk3.default.red(loadError.message)}`);
2908
+ log.print(` ${import_chalk6.default.bold("Problem:")}`);
2909
+ log.print(` ${import_chalk6.default.red(loadError.message)}`);
1568
2910
  if (loadError.kind === "unknown") {
1569
2911
  log.blank();
1570
- log.print(` ${import_chalk3.default.yellow("Generated config may be using incorrect plugin structure.")}`);
2912
+ log.print(` ${import_chalk6.default.yellow("Generated config may be using incorrect plugin structure.")}`);
1571
2913
  }
1572
2914
  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`);
2915
+ log.print(` ${import_chalk6.default.bold("Fix:")}`);
2916
+ log.print(` 1. Check the syntax in ${import_chalk6.default.cyan(import_path7.default.relative(cwd, finalConfigPath))}`);
2917
+ log.print(` 2. Make sure ${import_chalk6.default.cyan("eslint-plugin-ai-guard")} is installed`);
2918
+ log.print(` 3. Run ${import_chalk6.default.cyan("ai-guard doctor")} for full diagnostics`);
1577
2919
  log.blank();
1578
2920
  process.exit(1);
1579
2921
  return;
@@ -1587,34 +2929,34 @@ function registerInitCommand(program2) {
1587
2929
  let cleanedUpInfo = false;
1588
2930
  if (eslintNeedsFlat) {
1589
2931
  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));
2932
+ cleanedUpInfo = legacyConfigs.some((p) => import_fs7.default.existsSync(`${p}.bak`) && !import_fs7.default.existsSync(p));
1591
2933
  }
1592
2934
  const lingeringConflicts = env.hasConflictingConfigs && !eslintNeedsFlat && !cleanedUpInfo;
1593
2935
  if (loadError === null && !lingeringConflicts) {
1594
- log.print(` ${import_chalk3.default.green("\u2714")} ${import_chalk3.default.bold.green("Configuration validated successfully")}`);
2936
+ log.print(` ${import_chalk6.default.green("\u2714")} ${import_chalk6.default.bold.green("Configuration validated successfully")}`);
1595
2937
  }
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)}`);
2938
+ log.print(` ${import_chalk6.default.green("\u2714")} ${import_chalk6.default.bold("ESLint config:")} ${import_chalk6.default.cyan(import_path7.default.relative(cwd, finalConfigPath))}`);
2939
+ log.print(` ${import_chalk6.default.green("\u2714")} ${import_chalk6.default.bold("Mode:")} ${import_chalk6.default.cyan(useFlat ? `Flat config (ESLint v${env.eslintMajor ?? 9})` : `Legacy config (ESLint v${env.eslintMajor ?? 8})`)}`);
2940
+ log.print(` ${import_chalk6.default.green("\u2714")} ${import_chalk6.default.bold("Preset:")} ${import_chalk6.default.cyan(preset)}`);
1599
2941
  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`);
2942
+ log.info(`Run ${import_chalk6.default.cyan("npx eslint .")} \u2192 lint with your editor integration`);
2943
+ log.info(`Run ${import_chalk6.default.cyan("ai-guard run")} \u2192 zero-config scan`);
2944
+ log.info(`Run ${import_chalk6.default.cyan("ai-guard doctor")} \u2192 verify the full setup`);
2945
+ log.info(`Run ${import_chalk6.default.cyan("ai-guard baseline")} \u2192 save baseline, track only new issues`);
1604
2946
  log.blank();
1605
2947
  });
1606
2948
  }
1607
2949
 
1608
2950
  // cli/commands/doctor.ts
1609
- var import_chalk4 = __toESM(require("chalk"));
1610
- var import_path5 = __toESM(require("path"));
2951
+ var import_chalk7 = __toESM(require("chalk"));
2952
+ var import_path8 = __toESM(require("path"));
1611
2953
  init_config_manager();
1612
- var import_meta = {};
2954
+ var import_meta2 = {};
1613
2955
  async function probeConfigLoad(configPath, cwd) {
1614
2956
  try {
1615
2957
  const { ESLint } = await import("eslint");
1616
2958
  const eslint = new ESLint({ cwd, overrideConfigFile: configPath });
1617
- await eslint.lintText("", { filePath: import_path5.default.join(cwd, "_probe_.js") });
2959
+ await eslint.lintText("", { filePath: import_path8.default.join(cwd, "_probe_.js") });
1618
2960
  return { ok: true, error: null };
1619
2961
  } catch (err) {
1620
2962
  const msg = err instanceof Error ? err.message : String(err);
@@ -1649,7 +2991,7 @@ function registerDoctorCommand(program2) {
1649
2991
  checks.push({
1650
2992
  label: "ESLint config present",
1651
2993
  pass: hasConfig,
1652
- detail: hasConfig ? `${import_path5.default.relative(cwd, env.configPath ?? "")} (${env.configType})` : "No eslint.config.* or .eslintrc.* found",
2994
+ detail: hasConfig ? `${import_path8.default.relative(cwd, env.configPath ?? "")} (${env.configType})` : "No eslint.config.* or .eslintrc.* found",
1653
2995
  fix: hasConfig ? void 0 : "ai-guard init",
1654
2996
  note: hasConfig ? void 0 : "This will generate the correct config format for your ESLint version"
1655
2997
  });
@@ -1681,7 +3023,7 @@ function registerDoctorCommand(program2) {
1681
3023
  checks.push({
1682
3024
  label: "Plugin wired in config",
1683
3025
  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)}`,
3026
+ detail: readError ? `Could not read config: ${readError}` : isInvalidConfig ? "Invalid ai-guard config detected (wrong export usage)" : pluginWired ? "eslint-plugin-ai-guard referenced in config" : `Config exists but ai-guard plugin not found in ${import_path8.default.relative(cwd, env.configPath)}`,
1685
3027
  fix: isInvalidConfig || !pluginWired && !readError ? "ai-guard init (will patch/repair your existing config)" : void 0
1686
3028
  });
1687
3029
  }
@@ -1689,7 +3031,7 @@ function registerDoctorCommand(program2) {
1689
3031
  label: "No conflicting config files",
1690
3032
  pass: !env.hasConflictingConfigs,
1691
3033
  detail: env.hasConflictingConfigs ? `Conflicting config detected:
1692
- ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}`).join("\n")}` : "Only one config format detected",
3034
+ ${env.allConfigPaths.map((p) => ` ${import_path8.default.relative(cwd, p)}`).join("\n")}` : "Only one config format detected",
1693
3035
  fix: env.hasConflictingConfigs ? "Remove or backup legacy config" : void 0,
1694
3036
  note: env.hasConflictingConfigs ? "ESLint v9 silently ignores .eslintrc.* when a flat config exists. This can cause confusion." : void 0
1695
3037
  });
@@ -1716,7 +3058,7 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1716
3058
  label: "ESLint config loads without errors",
1717
3059
  pass: probe.ok,
1718
3060
  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)}`,
3061
+ fix: probe.ok ? void 0 : `Check syntax in ${import_path8.default.relative(cwd, env.configPath)}`,
1720
3062
  note: probe.ok ? void 0 : "Run: ai-guard init --dry-run to preview a fresh config"
1721
3063
  });
1722
3064
  }
@@ -1724,8 +3066,8 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1724
3066
  let utilsOk = false;
1725
3067
  let utilsVersion = null;
1726
3068
  try {
1727
- const { createRequire: createRequire2 } = await import("module");
1728
- const req = createRequire2(import_path5.default.join(cwd, "package.json"));
3069
+ const { createRequire: createRequire3 } = await import("module");
3070
+ const req = createRequire3(import_path8.default.join(cwd, "package.json"));
1729
3071
  const pkgPath = req.resolve("@typescript-eslint/utils/package.json");
1730
3072
  const pkg = JSON.parse(
1731
3073
  (await import("fs")).readFileSync(pkgPath, "utf-8")
@@ -1736,10 +3078,10 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1736
3078
  }
1737
3079
  if (!utilsOk) {
1738
3080
  try {
1739
- const selfDir = import_path5.default.dirname(
1740
- new URL(import_meta.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")
3081
+ const selfDir = import_path8.default.dirname(
3082
+ new URL(import_meta2.url).pathname.replace(/^\/([A-Za-z]:)/, "$1")
1741
3083
  );
1742
- const pkgPath = import_path5.default.join(selfDir, "../../node_modules/@typescript-eslint/utils/package.json");
3084
+ const pkgPath = import_path8.default.join(selfDir, "../../node_modules/@typescript-eslint/utils/package.json");
1743
3085
  if ((await import("fs")).existsSync(pkgPath)) {
1744
3086
  utilsOk = true;
1745
3087
  }
@@ -1758,8 +3100,8 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1758
3100
  let parserOk = false;
1759
3101
  let parserVersion = null;
1760
3102
  try {
1761
- const { createRequire: createRequire2 } = await import("module");
1762
- const req = createRequire2(import_path5.default.join(cwd, "package.json"));
3103
+ const { createRequire: createRequire3 } = await import("module");
3104
+ const req = createRequire3(import_path8.default.join(cwd, "package.json"));
1763
3105
  const pkgPath = req.resolve("@typescript-eslint/parser/package.json");
1764
3106
  const pkg = JSON.parse(
1765
3107
  (await import("fs")).readFileSync(pkgPath, "utf-8")
@@ -1790,17 +3132,17 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1790
3132
  log.section("Diagnostics");
1791
3133
  let allPassed = true;
1792
3134
  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);
3135
+ const icon = check.pass ? import_chalk7.default.green("\u2714") : import_chalk7.default.red("\u2716");
3136
+ const label = check.pass ? import_chalk7.default.white(check.label) : import_chalk7.default.red.bold(check.label);
1795
3137
  log.print(` ${icon} ${label}`);
1796
- log.print(` ${import_chalk4.default.gray(check.detail)}`);
3138
+ log.print(` ${import_chalk7.default.gray(check.detail)}`);
1797
3139
  if (!check.pass) {
1798
3140
  allPassed = false;
1799
3141
  if (check.fix) {
1800
- log.print(` ${import_chalk4.default.cyan("\u2192 Fix:")} ${import_chalk4.default.yellow(check.fix)}`);
3142
+ log.print(` ${import_chalk7.default.cyan("\u2192 Fix:")} ${import_chalk7.default.yellow(check.fix)}`);
1801
3143
  }
1802
3144
  if (check.note) {
1803
- log.print(` ${import_chalk4.default.gray(check.note)}`);
3145
+ log.print(` ${import_chalk7.default.gray(check.note)}`);
1804
3146
  }
1805
3147
  }
1806
3148
  log.blank();
@@ -1808,10 +3150,10 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1808
3150
  log.divider();
1809
3151
  log.blank();
1810
3152
  if (allPassed) {
1811
- log.print(` ${import_chalk4.default.green("\u2714")} ${import_chalk4.default.bold.green("All checks passed! Your setup is correct.")}`);
3153
+ log.print(` ${import_chalk7.default.green("\u2714")} ${import_chalk7.default.bold.green("All checks passed! Your setup is correct.")}`);
1812
3154
  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.`);
3155
+ log.info(`Run ${import_chalk7.default.cyan("ai-guard run")} to start scanning.`);
3156
+ log.info(`Run ${import_chalk7.default.cyan("npx eslint .")} to lint with ESLint directly.`);
1815
3157
  } else {
1816
3158
  log.error("Some checks failed. Follow the fixes above.");
1817
3159
  log.blank();
@@ -1826,15 +3168,15 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1826
3168
  );
1827
3169
  if (pluginMissing) {
1828
3170
  log.print(
1829
- ` Install missing packages first, then re-run ${import_chalk4.default.cyan("ai-guard doctor")}.`
3171
+ ` Install missing packages first, then re-run ${import_chalk7.default.cyan("ai-guard doctor")}.`
1830
3172
  );
1831
3173
  } else if (configMissing || configFormatMismatch) {
1832
3174
  log.print(
1833
- ` Run ${import_chalk4.default.cyan("ai-guard init")} to generate the correct config for your ESLint version.`
3175
+ ` Run ${import_chalk7.default.cyan("ai-guard init")} to generate the correct config for your ESLint version.`
1834
3176
  );
1835
3177
  } else {
1836
3178
  log.print(
1837
- ` Run ${import_chalk4.default.cyan("ai-guard init --dry-run")} to preview what would change.`
3179
+ ` Run ${import_chalk7.default.cyan("ai-guard init --dry-run")} to preview what would change.`
1838
3180
  );
1839
3181
  }
1840
3182
  }
@@ -1845,9 +3187,9 @@ ${env.allConfigPaths.map((p) => ` ${import_path5.default.relative(cwd, p)}
1845
3187
 
1846
3188
  // cli/commands/preset.ts
1847
3189
  var import_prompts = require("@inquirer/prompts");
1848
- var import_chalk5 = __toESM(require("chalk"));
3190
+ var import_chalk8 = __toESM(require("chalk"));
1849
3191
  init_config_manager();
1850
- var import_path6 = __toESM(require("path"));
3192
+ var import_path9 = __toESM(require("path"));
1851
3193
  function registerPresetCommand(program2) {
1852
3194
  program2.command("preset").description("Interactively select and apply an ai-guard preset to your ESLint config").action(async () => {
1853
3195
  const cwd = process.cwd();
@@ -1857,32 +3199,32 @@ function registerPresetCommand(program2) {
1857
3199
  message: "Choose a preset:",
1858
3200
  choices: [
1859
3201
  {
1860
- name: `${import_chalk5.default.green("recommended")} \u2014 balanced defaults, low noise. Best starting point.`,
3202
+ name: `${import_chalk8.default.green("recommended")} \u2014 balanced defaults, low noise. Best starting point.`,
1861
3203
  value: "recommended"
1862
3204
  },
1863
3205
  {
1864
- name: `${import_chalk5.default.yellow("strict")} \u2014 all rules at error. For mature codebases.`,
3206
+ name: `${import_chalk8.default.yellow("strict")} \u2014 all rules at error. For mature codebases.`,
1865
3207
  value: "strict"
1866
3208
  },
1867
3209
  {
1868
- name: `${import_chalk5.default.red("security")} \u2014 security rules only. For AppSec teams.`,
3210
+ name: `${import_chalk8.default.red("security")} \u2014 security rules only. For AppSec teams.`,
1869
3211
  value: "security"
1870
3212
  }
1871
3213
  ]
1872
3214
  });
1873
3215
  log.blank();
1874
- log.info(`Applying preset: ${import_chalk5.default.cyan(preset)}`);
3216
+ log.info(`Applying preset: ${import_chalk8.default.cyan(preset)}`);
1875
3217
  const env = detect(cwd);
1876
3218
  const useFlat = env.eslintMajor !== null && env.eslintMajor >= 9 || isFlat(env.configType);
1877
3219
  if (env.configType === "none") {
1878
- const configPath = useFlat ? import_path6.default.join(cwd, "eslint.config.mjs") : import_path6.default.join(cwd, ".eslintrc.js");
3220
+ const configPath = useFlat ? import_path9.default.join(cwd, "eslint.config.mjs") : import_path9.default.join(cwd, ".eslintrc.js");
1879
3221
  const content = useFlat ? generateFlatConfig(preset) : generateLegacyConfig(preset);
1880
3222
  writeConfig(configPath, content);
1881
- log.success(`Created ${import_chalk5.default.white(import_path6.default.relative(cwd, configPath))} with ${import_chalk5.default.cyan(preset)} preset`);
3223
+ log.success(`Created ${import_chalk8.default.white(import_path9.default.relative(cwd, configPath))} with ${import_chalk8.default.cyan(preset)} preset`);
1882
3224
  } else {
1883
3225
  const configPath = env.configPath;
1884
3226
  const backupPath = backupConfig(configPath);
1885
- log.info(`Backed up \u2192 ${import_chalk5.default.gray(import_path6.default.relative(cwd, backupPath))}`);
3227
+ log.info(`Backed up \u2192 ${import_chalk8.default.gray(import_path9.default.relative(cwd, backupPath))}`);
1886
3228
  const existing = readConfig(configPath);
1887
3229
  const withPlugin = isFlat(env.configType) ? patchFlatConfig(existing, preset) : patchLegacyConfig(existing, preset);
1888
3230
  const patched = isFlat(env.configType) ? switchFlatPreset(withPlugin, preset) : switchLegacyPreset(withPlugin, preset);
@@ -1890,47 +3232,47 @@ function registerPresetCommand(program2) {
1890
3232
  log.warn("No preset changes needed. Config already matches selected preset.");
1891
3233
  } else {
1892
3234
  writeConfig(configPath, patched);
1893
- log.success(`Updated ${import_chalk5.default.white(import_path6.default.relative(cwd, configPath))} to ${import_chalk5.default.cyan(preset)} preset`);
3235
+ log.success(`Updated ${import_chalk8.default.white(import_path9.default.relative(cwd, configPath))} to ${import_chalk8.default.cyan(preset)} preset`);
1894
3236
  }
1895
3237
  }
1896
3238
  log.blank();
1897
3239
  log.section("Preset Details");
1898
3240
  const presetDetails = {
1899
3241
  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") }
3242
+ { rule: "no-empty-catch", level: import_chalk8.default.red("error") },
3243
+ { rule: "no-floating-promise", level: import_chalk8.default.red("error") },
3244
+ { rule: "no-hardcoded-secret", level: import_chalk8.default.red("error") },
3245
+ { rule: "no-eval-dynamic", level: import_chalk8.default.red("error") },
3246
+ { rule: "no-broad-exception", level: import_chalk8.default.yellow("warn") },
3247
+ { rule: "no-async-array-callback", level: import_chalk8.default.yellow("warn") },
3248
+ { rule: "no-await-in-loop", level: import_chalk8.default.yellow("warn") },
3249
+ { rule: "+ 5 more\u2026", level: import_chalk8.default.gray("warn") }
1908
3250
  ],
1909
3251
  strict: [
1910
- { rule: "All 17 rules", level: import_chalk5.default.red("error") }
3252
+ { rule: "All 17 rules", level: import_chalk8.default.red("error") }
1911
3253
  ],
1912
3254
  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") }
3255
+ { rule: "no-hardcoded-secret", level: import_chalk8.default.red("error") },
3256
+ { rule: "no-eval-dynamic", level: import_chalk8.default.red("error") },
3257
+ { rule: "no-sql-string-concat", level: import_chalk8.default.red("error") },
3258
+ { rule: "no-unsafe-deserialize", level: import_chalk8.default.yellow("warn") },
3259
+ { rule: "require-auth-middleware", level: import_chalk8.default.yellow("warn") },
3260
+ { rule: "require-authz-check", level: import_chalk8.default.yellow("warn") }
1919
3261
  ]
1920
3262
  };
1921
3263
  for (const { rule, level } of presetDetails[preset]) {
1922
- log.print(` ${import_chalk5.default.gray("\u2022")} ${import_chalk5.default.white(rule).padEnd(32)}${level}`);
3264
+ log.print(` ${import_chalk8.default.gray("\u2022")} ${import_chalk8.default.white(rule).padEnd(32)}${level}`);
1923
3265
  }
1924
3266
  log.blank();
1925
- log.info(`Run ${import_chalk5.default.cyan("ai-guard run")} to see results with the new preset.`);
3267
+ log.info(`Run ${import_chalk8.default.cyan("ai-guard run")} to see results with the new preset.`);
1926
3268
  log.blank();
1927
3269
  });
1928
3270
  }
1929
3271
 
1930
3272
  // cli/commands/ignore.ts
1931
- var import_chalk6 = __toESM(require("chalk"));
3273
+ var import_chalk9 = __toESM(require("chalk"));
1932
3274
  init_config_manager();
1933
- var import_path7 = __toESM(require("path"));
3275
+ var import_path10 = __toESM(require("path"));
1934
3276
  var DEFAULT_IGNORES = [
1935
3277
  ".next/",
1936
3278
  "dist/",
@@ -1946,41 +3288,41 @@ function registerIgnoreCommand(program2) {
1946
3288
  log.blank();
1947
3289
  log.section("Default Ignores");
1948
3290
  for (const p of DEFAULT_IGNORES) {
1949
- log.print(` ${import_chalk6.default.gray("\u2022")} ${import_chalk6.default.yellow(p)}`);
3291
+ log.print(` ${import_chalk9.default.gray("\u2022")} ${import_chalk9.default.yellow(p)}`);
1950
3292
  }
1951
3293
  log.blank();
1952
3294
  const env = detect(cwd);
1953
3295
  if (env.configType === "none") {
1954
3296
  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");
3297
+ const configPath = useFlat ? import_path10.default.join(cwd, "eslint.config.mjs") : import_path10.default.join(cwd, ".eslintrc.js");
1956
3298
  const content = useFlat ? generateFlatConfig("recommended") : generateLegacyConfig("recommended");
1957
3299
  writeConfig(configPath, content);
1958
- log.success(`No config found \u2014 generated ${import_chalk6.default.white(import_path7.default.relative(cwd, configPath))} with ignores included`);
3300
+ log.success(`No config found \u2014 generated ${import_chalk9.default.white(import_path10.default.relative(cwd, configPath))} with ignores included`);
1959
3301
  } else {
1960
3302
  const configPath = env.configPath;
1961
3303
  const backupPath = backupConfig(configPath);
1962
- log.info(`Backed up \u2192 ${import_chalk6.default.gray(import_path7.default.relative(cwd, backupPath))}`);
3304
+ log.info(`Backed up \u2192 ${import_chalk9.default.gray(import_path10.default.relative(cwd, backupPath))}`);
1963
3305
  const existing = readConfig(configPath);
1964
3306
  const patched = isFlat(env.configType) ? addIgnoresToFlatConfig(existing) : addIgnoresToLegacyConfig(existing);
1965
3307
  if (patched === existing) {
1966
3308
  log.warn("Ignore patterns already present in config \u2014 no changes made.");
1967
3309
  } else {
1968
3310
  writeConfig(configPath, patched);
1969
- log.success(`Patched ${import_chalk6.default.white(import_path7.default.relative(cwd, configPath))} with ignore patterns`);
3311
+ log.success(`Patched ${import_chalk9.default.white(import_path10.default.relative(cwd, configPath))} with ignore patterns`);
1970
3312
  }
1971
3313
  }
1972
3314
  log.blank();
1973
3315
  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.`);
3316
+ log.info(`Run ${import_chalk9.default.cyan("ai-guard run")} to verify cleaner results.`);
1975
3317
  log.blank();
1976
3318
  });
1977
3319
  }
1978
3320
 
1979
3321
  // 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"));
3322
+ var import_fs8 = __toESM(require("fs"));
3323
+ var import_path11 = __toESM(require("path"));
3324
+ var import_chalk10 = __toESM(require("chalk"));
3325
+ var import_ora3 = __toESM(require("ora"));
1984
3326
  var BASELINE_FILE = ".ai-guard-baseline.json";
1985
3327
  function saveBaseline(result, preset, mode, cwd) {
1986
3328
  const entries = result.files.map((f) => ({
@@ -1999,15 +3341,15 @@ function saveBaseline(result, preset, mode, cwd) {
1999
3341
  totalIssues: result.totalIssues,
2000
3342
  entries
2001
3343
  };
2002
- const baselinePath = import_path8.default.join(cwd, BASELINE_FILE);
2003
- import_fs5.default.writeFileSync(baselinePath, JSON.stringify(baseline, null, 2), "utf-8");
3344
+ const baselinePath = import_path11.default.join(cwd, BASELINE_FILE);
3345
+ import_fs8.default.writeFileSync(baselinePath, JSON.stringify(baseline, null, 2), "utf-8");
2004
3346
  return baselinePath;
2005
3347
  }
2006
3348
  function loadBaseline(cwd) {
2007
- const baselinePath = import_path8.default.join(cwd, BASELINE_FILE);
2008
- if (!import_fs5.default.existsSync(baselinePath)) return null;
3349
+ const baselinePath = import_path11.default.join(cwd, BASELINE_FILE);
3350
+ if (!import_fs8.default.existsSync(baselinePath)) return null;
2009
3351
  try {
2010
- const raw = import_fs5.default.readFileSync(baselinePath, "utf-8");
3352
+ const raw = import_fs8.default.readFileSync(baselinePath, "utf-8");
2011
3353
  return JSON.parse(raw);
2012
3354
  } catch {
2013
3355
  return null;
@@ -2079,7 +3421,7 @@ function registerBaselineCommand(program2) {
2079
3421
  const doCheck = opts.check ?? (existingBaseline !== null && !opts.save);
2080
3422
  if (doSave) {
2081
3423
  log.info("Scanning project to save baseline\u2026");
2082
- const spinner = (0, import_ora2.default)({ text: "Running analysis\u2026", color: "cyan" }).start();
3424
+ const spinner = (0, import_ora3.default)({ text: "Running analysis\u2026", color: "cyan" }).start();
2083
3425
  let result;
2084
3426
  try {
2085
3427
  result = await runEslint({ preset, targetPath: opts.path });
@@ -2091,30 +3433,30 @@ function registerBaselineCommand(program2) {
2091
3433
  }
2092
3434
  const baselinePath = saveBaseline(result, preset, mode, cwd);
2093
3435
  log.success(
2094
- `Baseline saved \u2192 ${import_chalk7.default.white(import_path8.default.relative(cwd, baselinePath))}`
3436
+ `Baseline saved \u2192 ${import_chalk10.default.white(import_path11.default.relative(cwd, baselinePath))}`
2095
3437
  );
2096
3438
  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)}`);
3439
+ log.info(` Preset: ${import_chalk10.default.cyan(preset)}`);
3440
+ log.info(` Mode: ${import_chalk10.default.cyan(mode)}`);
2099
3441
  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.");
3442
+ log.info("Future runs of " + import_chalk10.default.cyan("ai-guard baseline --check") + " will show only NEW issues.");
3443
+ log.info("Add " + import_chalk10.default.white(BASELINE_FILE) + " to your git repository to share baseline with your team.");
2102
3444
  log.blank();
2103
3445
  return;
2104
3446
  }
2105
3447
  if (doCheck) {
2106
3448
  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.`);
3449
+ log.error(`No baseline found at ${import_chalk10.default.white(BASELINE_FILE)}`);
3450
+ log.info(`Run ${import_chalk10.default.cyan("ai-guard baseline --save")} first to create one.`);
2109
3451
  process.exit(1);
2110
3452
  }
2111
3453
  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)}`);
3454
+ log.info(`Baseline from: ${import_chalk10.default.gray(baselineDate)}`);
3455
+ log.info(`Baseline preset: ${import_chalk10.default.cyan(existingBaseline.preset)}`);
3456
+ log.info(`Baseline mode: ${import_chalk10.default.cyan(existingBaseline.mode ?? "strict")}`);
3457
+ log.info(`Baseline issues: ${import_chalk10.default.gray(existingBaseline.totalIssues)}`);
2116
3458
  log.blank();
2117
- const spinner = (0, import_ora2.default)({ text: "Scanning for new issues\u2026", color: "cyan" }).start();
3459
+ const spinner = (0, import_ora3.default)({ text: "Scanning for new issues\u2026", color: "cyan" }).start();
2118
3460
  let result;
2119
3461
  try {
2120
3462
  result = await runEslint({ preset, targetPath: opts.path });
@@ -2137,7 +3479,7 @@ function registerBaselineCommand(program2) {
2137
3479
  return;
2138
3480
  }
2139
3481
  log.print(
2140
- import_chalk7.default.bold.red(` \u2716 ${newResult.totalIssues} new issue${newResult.totalIssues !== 1 ? "s" : ""} since baseline`)
3482
+ import_chalk10.default.bold.red(` \u2716 ${newResult.totalIssues} new issue${newResult.totalIssues !== 1 ? "s" : ""} since baseline`)
2141
3483
  );
2142
3484
  log.blank();
2143
3485
  if (newResult.ruleBreakdown.size > 0) {
@@ -2153,7 +3495,7 @@ function registerBaselineCommand(program2) {
2153
3495
  log.blank();
2154
3496
  for (const file of newResult.files) {
2155
3497
  log.print(
2156
- ` ${import_chalk7.default.bold.white(file.filePath)} ` + import_chalk7.default.gray(`(${file.issues.length} new)`)
3498
+ ` ${import_chalk10.default.bold.white(file.filePath)} ` + import_chalk10.default.gray(`(${file.issues.length} new)`)
2157
3499
  );
2158
3500
  for (const issue of file.issues) {
2159
3501
  log.issue(issue.message, issue.severity, issue.line, issue.column);
@@ -2166,7 +3508,7 @@ function registerBaselineCommand(program2) {
2166
3508
  log.error(
2167
3509
  `${newResult.totalIssues} new issue${newResult.totalIssues !== 1 ? "s" : ""} introduced since baseline.`
2168
3510
  );
2169
- log.info(`Run ${import_chalk7.default.cyan("ai-guard baseline --save")} to update the baseline after fixing them.`);
3511
+ log.info(`Run ${import_chalk10.default.cyan("ai-guard baseline --save")} to update the baseline after fixing them.`);
2170
3512
  log.blank();
2171
3513
  process.exit(1);
2172
3514
  }
@@ -2174,9 +3516,9 @@ function registerBaselineCommand(program2) {
2174
3516
  }
2175
3517
 
2176
3518
  // 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"));
3519
+ var import_fs9 = __toESM(require("fs"));
3520
+ var import_path12 = __toESM(require("path"));
3521
+ var import_chalk11 = __toESM(require("chalk"));
2180
3522
  var import_prompts2 = require("@inquirer/prompts");
2181
3523
  var RULE_CATEGORIES = {
2182
3524
  "error-handling": [
@@ -2539,23 +3881,23 @@ var AGENTS = [
2539
3881
  {
2540
3882
  id: "copilot",
2541
3883
  label: "GitHub Copilot",
2542
- filePath: import_path9.default.join(".github", "copilot-instructions.md"),
3884
+ filePath: import_path12.default.join(".github", "copilot-instructions.md"),
2543
3885
  generate: generateCopilotFile
2544
3886
  }
2545
3887
  ];
2546
3888
  function getPackageVersion2(cwd) {
2547
3889
  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"));
3890
+ const pkgPath = import_path12.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
3891
+ if (import_fs9.default.existsSync(pkgPath)) {
3892
+ const pkg = JSON.parse(import_fs9.default.readFileSync(pkgPath, "utf-8"));
2551
3893
  if (pkg.version) return pkg.version;
2552
3894
  }
2553
3895
  } catch {
2554
3896
  }
2555
3897
  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"));
3898
+ const pkgPath = import_path12.default.join(cwd, "package.json");
3899
+ if (import_fs9.default.existsSync(pkgPath)) {
3900
+ const pkg = JSON.parse(import_fs9.default.readFileSync(pkgPath, "utf-8"));
2559
3901
  if (pkg.version) return pkg.version;
2560
3902
  }
2561
3903
  } catch {
@@ -2584,7 +3926,7 @@ function registerInitContextCommand(program2) {
2584
3926
  const isForce = opts.force === true;
2585
3927
  log.banner("AI GUARD INIT-CONTEXT");
2586
3928
  if (isDryRun) {
2587
- log.print(` ${import_chalk8.default.yellow("\u2691 DRY RUN \u2014 no files will be written")}`);
3929
+ log.print(` ${import_chalk11.default.yellow("\u2691 DRY RUN \u2014 no files will be written")}`);
2588
3930
  log.blank();
2589
3931
  }
2590
3932
  let selectedAgents;
@@ -2617,13 +3959,13 @@ function registerInitContextCommand(program2) {
2617
3959
  selectedAgents = AGENTS.filter((a) => selected.includes(a.id));
2618
3960
  }
2619
3961
  }
2620
- log.info(`Categories: ${import_chalk8.default.cyan(categories.join(", "))}`);
2621
- log.info(`Version: ${import_chalk8.default.cyan(version)}`);
3962
+ log.info(`Categories: ${import_chalk11.default.cyan(categories.join(", "))}`);
3963
+ log.info(`Version: ${import_chalk11.default.cyan(version)}`);
2622
3964
  log.blank();
2623
3965
  const results = [];
2624
3966
  for (const agent of selectedAgents) {
2625
- const fullPath = import_path9.default.join(cwd, agent.filePath);
2626
- const exists = import_fs6.default.existsSync(fullPath);
3967
+ const fullPath = import_path12.default.join(cwd, agent.filePath);
3968
+ const exists = import_fs9.default.existsSync(fullPath);
2627
3969
  if (exists && !isForce && !isDryRun) {
2628
3970
  const overwrite = await (0, import_prompts2.confirm)({
2629
3971
  message: `${agent.filePath} already exists. Overwrite?`,
@@ -2637,41 +3979,41 @@ function registerInitContextCommand(program2) {
2637
3979
  const content = agent.generate(version, categories);
2638
3980
  if (isDryRun) {
2639
3981
  log.section(`Preview: ${agent.filePath}`);
2640
- log.print(import_chalk8.default.gray("\u2500".repeat(60)));
3982
+ log.print(import_chalk11.default.gray("\u2500".repeat(60)));
2641
3983
  for (const line of content.split("\n").slice(0, 20)) {
2642
- log.print(import_chalk8.default.gray(` ${line}`));
3984
+ log.print(import_chalk11.default.gray(` ${line}`));
2643
3985
  }
2644
- log.print(import_chalk8.default.gray(" ... (truncated)"));
2645
- log.print(import_chalk8.default.gray("\u2500".repeat(60)));
3986
+ log.print(import_chalk11.default.gray(" ... (truncated)"));
3987
+ log.print(import_chalk11.default.gray("\u2500".repeat(60)));
2646
3988
  log.blank();
2647
3989
  results.push({ file: agent.filePath, status: "dry-run" });
2648
3990
  continue;
2649
3991
  }
2650
- const parentDir = import_path9.default.dirname(fullPath);
2651
- if (!import_fs6.default.existsSync(parentDir)) {
2652
- import_fs6.default.mkdirSync(parentDir, { recursive: true });
3992
+ const parentDir = import_path12.default.dirname(fullPath);
3993
+ if (!import_fs9.default.existsSync(parentDir)) {
3994
+ import_fs9.default.mkdirSync(parentDir, { recursive: true });
2653
3995
  }
2654
- import_fs6.default.writeFileSync(fullPath, content, "utf-8");
3996
+ import_fs9.default.writeFileSync(fullPath, content, "utf-8");
2655
3997
  results.push({ file: agent.filePath, status: "created" });
2656
3998
  }
2657
3999
  log.section("Summary");
2658
4000
  for (const result of results) {
2659
4001
  switch (result.status) {
2660
4002
  case "created":
2661
- log.print(` ${import_chalk8.default.green("\u2705")} Created ${import_chalk8.default.white(result.file)}`);
4003
+ log.print(` ${import_chalk11.default.green("\u2705")} Created ${import_chalk11.default.white(result.file)}`);
2662
4004
  break;
2663
4005
  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)")}`);
4006
+ log.print(` ${import_chalk11.default.yellow("\u23ED")} Skipped ${import_chalk11.default.white(result.file)} ${import_chalk11.default.gray("(already exists \u2014 use --force to overwrite)")}`);
2665
4007
  break;
2666
4008
  case "dry-run":
2667
- log.print(` ${import_chalk8.default.cyan("\u{1F4DD}")} Would create ${import_chalk8.default.white(result.file)}`);
4009
+ log.print(` ${import_chalk11.default.cyan("\u{1F4DD}")} Would create ${import_chalk11.default.white(result.file)}`);
2668
4010
  break;
2669
4011
  }
2670
4012
  }
2671
4013
  log.blank();
2672
4014
  if (isDryRun) {
2673
4015
  log.info("Dry run complete \u2014 no files were written.");
2674
- log.info(`Re-run without ${import_chalk8.default.cyan("--dry-run")} to apply.`);
4016
+ log.info(`Re-run without ${import_chalk11.default.cyan("--dry-run")} to apply.`);
2675
4017
  } else {
2676
4018
  const createdCount = results.filter((r) => r.status === "created").length;
2677
4019
  if (createdCount > 0) {
@@ -2685,9 +4027,9 @@ function registerInitContextCommand(program2) {
2685
4027
  }
2686
4028
 
2687
4029
  // 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"));
4030
+ var import_fs10 = __toESM(require("fs"));
4031
+ var import_path13 = __toESM(require("path"));
4032
+ var import_ora4 = __toESM(require("ora"));
2691
4033
  var RULE_META = {
2692
4034
  "ai-guard/no-empty-catch": { description: "Empty catch blocks silently swallow errors", category: "Reliability" },
2693
4035
  "ai-guard/no-broad-exception": { description: "Catching all exceptions hides bugs", category: "Reliability" },
@@ -2710,28 +4052,28 @@ var RULE_META = {
2710
4052
  };
2711
4053
  function getProjectName(cwd) {
2712
4054
  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"));
4055
+ const pkgPath = import_path13.default.join(cwd, "package.json");
4056
+ if (import_fs10.default.existsSync(pkgPath)) {
4057
+ const pkg = JSON.parse(import_fs10.default.readFileSync(pkgPath, "utf-8"));
2716
4058
  if (pkg.name) return pkg.name;
2717
4059
  }
2718
4060
  } catch {
2719
4061
  }
2720
- return import_path10.default.basename(cwd);
4062
+ return import_path13.default.basename(cwd);
2721
4063
  }
2722
4064
  function getPluginVersion(cwd) {
2723
4065
  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"));
4066
+ const pkgPath = import_path13.default.resolve(__dirname, "../../package.json");
4067
+ if (import_fs10.default.existsSync(pkgPath)) {
4068
+ const pkg = JSON.parse(import_fs10.default.readFileSync(pkgPath, "utf-8"));
2727
4069
  if (pkg.version) return pkg.version;
2728
4070
  }
2729
4071
  } catch {
2730
4072
  }
2731
4073
  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"));
4074
+ const pkgPath = import_path13.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
4075
+ if (import_fs10.default.existsSync(pkgPath)) {
4076
+ const pkg = JSON.parse(import_fs10.default.readFileSync(pkgPath, "utf-8"));
2735
4077
  if (pkg.version) return pkg.version;
2736
4078
  }
2737
4079
  } catch {
@@ -3086,7 +4428,7 @@ function registerReportCommand(program2) {
3086
4428
  "Generate a beautiful HTML report of AI Guard scan results\nPerfect for sharing with your team or in Slack."
3087
4429
  ).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
4430
  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();
4431
+ const spinner = opts.json ? null : (0, import_ora4.default)({ text: "Scanning\u2026", color: "cyan" }).start();
3090
4432
  let result;
3091
4433
  try {
3092
4434
  result = await runEslint({ preset, targetPath: opts.path });
@@ -3108,8 +4450,8 @@ function registerReportCommand(program2) {
3108
4450
  log.banner("AI GUARD REPORT");
3109
4451
  log.blank();
3110
4452
  const html = generateHtml(reportData);
3111
- const outputPath = import_path10.default.resolve(cwd, opts.output);
3112
- import_fs7.default.writeFileSync(outputPath, html, "utf-8");
4453
+ const outputPath = import_path13.default.resolve(cwd, opts.output);
4454
+ import_fs10.default.writeFileSync(outputPath, html, "utf-8");
3113
4455
  log.success(`Report saved: ${opts.output}`);
3114
4456
  log.info(
3115
4457
  `Score: ${reportData.score}/100 \xB7 ${reportData.totalErrors} errors \xB7 ${reportData.totalWarnings} warnings`
@@ -3124,9 +4466,9 @@ function registerReportCommand(program2) {
3124
4466
  await open(outputPath);
3125
4467
  log.info("Opened in your browser.");
3126
4468
  } else {
3127
- const { execSync } = await import("child_process");
4469
+ const { execSync: execSync2 } = await import("child_process");
3128
4470
  const cmd = process.platform === "win32" ? `start "" "${outputPath}"` : process.platform === "darwin" ? `open "${outputPath}"` : `xdg-open "${outputPath}"`;
3129
- execSync(cmd);
4471
+ execSync2(cmd);
3130
4472
  log.info("Opened in your browser.");
3131
4473
  }
3132
4474
  } catch {
@@ -3142,8 +4484,8 @@ function registerReportCommand(program2) {
3142
4484
  // cli/index.ts
3143
4485
  function getVersion() {
3144
4486
  try {
3145
- const pkgPath = import_path11.default.resolve(__dirname, "../../package.json");
3146
- const raw = import_fs8.default.readFileSync(pkgPath, "utf-8");
4487
+ const pkgPath = import_path14.default.resolve(__dirname, "../../package.json");
4488
+ const raw = import_fs11.default.readFileSync(pkgPath, "utf-8");
3147
4489
  const pkg = JSON.parse(raw);
3148
4490
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
3149
4491
  } catch {
@@ -3152,9 +4494,10 @@ function getVersion() {
3152
4494
  }
3153
4495
  var program = new import_commander.Command();
3154
4496
  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"
4497
+ "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
4498
  ).version(getVersion(), "-v, --version", "Print version number").helpOption("-h, --help", "Show help");
3157
4499
  registerRunCommand(program);
4500
+ registerChangedCommand(program);
3158
4501
  registerInitCommand(program);
3159
4502
  registerDoctorCommand(program);
3160
4503
  registerPresetCommand(program);