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