eslint-plugin-ai-guard 1.1.12 โ 1.1.13
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/README.md +18 -29
- package/dist/cli/index.js +84 -59
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +219 -28
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +219 -28
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -6
package/README.md
CHANGED
|
@@ -57,29 +57,6 @@ This writes:
|
|
|
57
57
|
Your AI tools will now avoid these patterns before you even run the linter.
|
|
58
58
|
Use `--force` to regenerate after upgrading to a new version with new rules.
|
|
59
59
|
|
|
60
|
-
## ๐ค Set Up AI Agent Rules
|
|
61
|
-
|
|
62
|
-
Generate instruction files so Claude Code, Cursor, and GitHub Copilot
|
|
63
|
-
automatically avoid the 17 most common AI-generated anti-patterns:
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
npx ai-guard init-context
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
Follow the prompts to select your agent(s). Or generate all at once:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
npx ai-guard init-context --all
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
This writes:
|
|
76
|
-
- `CLAUDE.md` โ read automatically by Claude Code
|
|
77
|
-
- `.cursorrules` โ read automatically by Cursor
|
|
78
|
-
- `.github/copilot-instructions.md` โ read automatically by GitHub Copilot
|
|
79
|
-
|
|
80
|
-
Your AI tools will now avoid these patterns before you even run the linter.
|
|
81
|
-
Use `--force` to regenerate after upgrading to a new version with new rules.
|
|
82
|
-
|
|
83
60
|
## ๐งช Real-World Usage Philosophy
|
|
84
61
|
|
|
85
62
|
`ai-guard` is designed for production adoption in existing codebases:
|
|
@@ -88,6 +65,18 @@ Use `--force` to regenerate after upgrading to a new version with new rules.
|
|
|
88
65
|
2. **Strict preset enables full enforcement** for mature teams that want maximum coverage.
|
|
89
66
|
3. **Security preset focuses only on security rules** with critical issues as errors.
|
|
90
67
|
|
|
68
|
+
## ๐ ๏ธ Safe Autofix Support
|
|
69
|
+
|
|
70
|
+
`ai-guard` now includes safe autofixers for selected high-confidence rules:
|
|
71
|
+
|
|
72
|
+
- `ai-guard/no-empty-catch` โ inserts `{ /* TODO: handle error */ }`
|
|
73
|
+
- `ai-guard/no-await-in-loop` โ rewrites simple independent loops to `await Promise.all(...)`
|
|
74
|
+
- `ai-guard/no-hardcoded-secret` โ replaces hardcoded literals with `process.env.*`
|
|
75
|
+
- `ai-guard/no-floating-promise` โ marks intentional fire-and-forget with `void`
|
|
76
|
+
- `ai-guard/no-async-without-await` โ inserts `await (...)` for simple function bodies
|
|
77
|
+
|
|
78
|
+
These fixes are intentionally conservative and avoid complex transformations when confidence is low.
|
|
79
|
+
|
|
91
80
|
## ๐ฌ Real Workspace Demo
|
|
92
81
|
|
|
93
82
|
See how `ai-guard` catches a common AI-generated async bug that silent failures in production:
|
|
@@ -119,7 +108,7 @@ const users = await Promise.all(userIds.map(async (id) => {
|
|
|
119
108
|
### ๐ฏ Error Handling
|
|
120
109
|
|
|
121
110
|
- **`ai-guard/no-empty-catch`** (Error)
|
|
122
|
-
Disallow empty catch blocks.
|
|
111
|
+
Disallow empty catch blocks. Includes safe autofix that inserts an explicit placeholder handler comment.
|
|
123
112
|
- **`ai-guard/no-broad-exception`** (Warn)
|
|
124
113
|
Disallow catching `any` or `unknown` without instance narrowing. AI tools default to `catch (e: any)` which obscures the underlying failure.
|
|
125
114
|
- **`ai-guard/no-catch-log-rethrow`** (Off in `recommended`, Error in `strict`)
|
|
@@ -134,22 +123,22 @@ const users = await Promise.all(userIds.map(async (id) => {
|
|
|
134
123
|
- **`ai-guard/no-async-array-callback`** (Warn)
|
|
135
124
|
Disallow async functions in `.map()`, `.filter()`, etc. AI tools frequently suggest `array.map(async ...)` expecting resolved values, creating silent bugs.
|
|
136
125
|
- **`ai-guard/no-floating-promise`** (Error)
|
|
137
|
-
Require awaiting or handling promises.
|
|
126
|
+
Require awaiting or handling promises. Includes safe autofix that marks floating calls with `void`.
|
|
138
127
|
- **`ai-guard/no-await-in-loop`** (Warn)
|
|
139
|
-
Disallow sequential `await` inside loops.
|
|
128
|
+
Disallow independent sequential `await` inside loops. Intent-aware suppression protects retry/fallback loops, and safe autofix is available for simple independent cases.
|
|
140
129
|
- **`ai-guard/no-async-without-await`** (Warn)
|
|
141
|
-
Disallow async functions that do not use `await`.
|
|
130
|
+
Disallow async functions that do not use `await`. Includes safe autofix for simple bodies by inserting explicit await.
|
|
142
131
|
- **`ai-guard/no-redundant-await`** (Off in `recommended`, Error in `strict`)
|
|
143
132
|
Disallow redundant `return await` outside try/catch/finally. AI tools often emit this pattern even when returning the Promise directly is equivalent.
|
|
144
133
|
|
|
145
134
|
### ๐ก๏ธ Security
|
|
146
135
|
|
|
147
136
|
- **`ai-guard/no-hardcoded-secret`** (Error)
|
|
148
|
-
Disallow hardcoded keys/passwords.
|
|
137
|
+
Disallow hardcoded keys/passwords. Includes safe autofix that rewrites values to `process.env.*`.
|
|
149
138
|
- **`ai-guard/no-eval-dynamic`** (Error)
|
|
150
139
|
Disallow dynamic `eval()` or `new Function()`.
|
|
151
140
|
- **`ai-guard/no-sql-string-concat`** (Warn in `recommended`, Error in `security`/`strict`)
|
|
152
|
-
Disallow variable concatenation/interpolation in SQL queries.
|
|
141
|
+
Disallow variable concatenation/interpolation in SQL queries. Now context-aware for known query builders (Knex, Drizzle, Prisma, Kysely, Sequelize, TypeORM, Mikro-ORM) to reduce false positives while staying strict for non-builder sinks.
|
|
153
142
|
- **`ai-guard/no-unsafe-deserialize`** (Warn in `recommended`/`security`, Error in `strict`)
|
|
154
143
|
Disallow `JSON.parse()` on likely untrusted inputs (like `req.body`) without visible validation.
|
|
155
144
|
- **`ai-guard/require-auth-middleware`** (Warn)
|
package/dist/cli/index.js
CHANGED
|
@@ -45,6 +45,8 @@ __export(config_manager_exports, {
|
|
|
45
45
|
readConfig: () => readConfig,
|
|
46
46
|
removeNukeIgnore: () => removeNukeIgnore,
|
|
47
47
|
repairInvalidFlatConfig: () => repairInvalidFlatConfig,
|
|
48
|
+
switchFlatPreset: () => switchFlatPreset,
|
|
49
|
+
switchLegacyPreset: () => switchLegacyPreset,
|
|
48
50
|
validateFlatConfigText: () => validateFlatConfigText,
|
|
49
51
|
validateLegacyConfigText: () => validateLegacyConfigText,
|
|
50
52
|
writeConfig: () => writeConfig
|
|
@@ -199,6 +201,30 @@ function patchLegacyConfig(existing, preset) {
|
|
|
199
201
|
}
|
|
200
202
|
return patched;
|
|
201
203
|
}
|
|
204
|
+
function switchFlatPreset(existing, preset) {
|
|
205
|
+
let patched = existing;
|
|
206
|
+
patched = patched.replace(
|
|
207
|
+
/\.\.\.aiGuard\.configs\.(recommended|strict|security)\.rules/g,
|
|
208
|
+
`...aiGuard.configs.${preset}.rules`
|
|
209
|
+
);
|
|
210
|
+
patched = patched.replace(
|
|
211
|
+
/['"]ai-guard\/(recommended|strict|security)['"]/g,
|
|
212
|
+
`'ai-guard/${preset}'`
|
|
213
|
+
);
|
|
214
|
+
return patched;
|
|
215
|
+
}
|
|
216
|
+
function switchLegacyPreset(existing, preset) {
|
|
217
|
+
let patched = existing;
|
|
218
|
+
patched = patched.replace(
|
|
219
|
+
/['"]plugin:ai-guard\/(recommended|strict|security)['"]/g,
|
|
220
|
+
`'plugin:ai-guard/${preset}'`
|
|
221
|
+
);
|
|
222
|
+
patched = patched.replace(
|
|
223
|
+
/['"]ai-guard\/(recommended|strict|security)['"]/g,
|
|
224
|
+
`'ai-guard/${preset}'`
|
|
225
|
+
);
|
|
226
|
+
return patched;
|
|
227
|
+
}
|
|
202
228
|
function removeNukeIgnore(existing) {
|
|
203
229
|
const nukePatterns = [/"?\*\*\/\*"?/g];
|
|
204
230
|
const hasNuke = existing.includes('"**/*"') || existing.includes("'**/*'");
|
|
@@ -469,8 +495,8 @@ function isSkippablePatternError(error) {
|
|
|
469
495
|
return hasNoFilesSignal || hasIgnoredSignal;
|
|
470
496
|
}
|
|
471
497
|
async function loadPluginModuleFromCwd(cwd) {
|
|
472
|
-
const { createRequire } = await import("module");
|
|
473
|
-
const requireFromCwd =
|
|
498
|
+
const { createRequire: createRequire2 } = await import("module");
|
|
499
|
+
const requireFromCwd = createRequire2(import_path.default.join(cwd, "package.json"));
|
|
474
500
|
try {
|
|
475
501
|
const resolved = requireFromCwd.resolve("eslint-plugin-ai-guard");
|
|
476
502
|
return import((0, import_url.pathToFileURL)(resolved).href);
|
|
@@ -515,6 +541,8 @@ async function runEslint(options) {
|
|
|
515
541
|
"**/*.jsx",
|
|
516
542
|
"**/*.ts",
|
|
517
543
|
"**/*.tsx",
|
|
544
|
+
"**/*.mts",
|
|
545
|
+
"**/*.cts",
|
|
518
546
|
"**/*.mjs",
|
|
519
547
|
"**/*.cjs"
|
|
520
548
|
];
|
|
@@ -551,7 +579,7 @@ async function runEslint(options) {
|
|
|
551
579
|
];
|
|
552
580
|
if (tsParser) {
|
|
553
581
|
configBlocks.splice(1, 0, {
|
|
554
|
-
files: ["**/*.ts", "**/*.tsx"],
|
|
582
|
+
files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
|
|
555
583
|
plugins: { "ai-guard": plugin },
|
|
556
584
|
languageOptions: {
|
|
557
585
|
parser: tsParser,
|
|
@@ -565,7 +593,7 @@ async function runEslint(options) {
|
|
|
565
593
|
});
|
|
566
594
|
} else {
|
|
567
595
|
configBlocks.splice(1, 0, {
|
|
568
|
-
files: ["**/*.ts", "**/*.tsx"],
|
|
596
|
+
files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
|
|
569
597
|
plugins: { "ai-guard": plugin },
|
|
570
598
|
rules
|
|
571
599
|
});
|
|
@@ -782,6 +810,7 @@ var import_fs4 = __toESM(require("fs"));
|
|
|
782
810
|
// cli/utils/detector.ts
|
|
783
811
|
var import_fs2 = __toESM(require("fs"));
|
|
784
812
|
var import_path2 = __toESM(require("path"));
|
|
813
|
+
var import_module = require("module");
|
|
785
814
|
var FLAT_CONFIG_FILES = [
|
|
786
815
|
{ file: "eslint.config.js", type: "flat-js" },
|
|
787
816
|
{ file: "eslint.config.mjs", type: "flat-mjs" },
|
|
@@ -798,7 +827,12 @@ var ALL_CONFIG_FILES = [...FLAT_CONFIG_FILES, ...LEGACY_CONFIG_FILES];
|
|
|
798
827
|
function isPackageInstalled(pkgName, cwd = process.cwd()) {
|
|
799
828
|
try {
|
|
800
829
|
const pkgPath = import_path2.default.join(cwd, "node_modules", pkgName, "package.json");
|
|
801
|
-
|
|
830
|
+
if (import_fs2.default.existsSync(pkgPath)) {
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
const req = (0, import_module.createRequire)(import_path2.default.join(cwd, "package.json"));
|
|
834
|
+
req.resolve(`${pkgName}/package.json`);
|
|
835
|
+
return true;
|
|
802
836
|
} catch {
|
|
803
837
|
return false;
|
|
804
838
|
}
|
|
@@ -806,8 +840,8 @@ function isPackageInstalled(pkgName, cwd = process.cwd()) {
|
|
|
806
840
|
function getPackageVersion(pkgName, cwd = process.cwd()) {
|
|
807
841
|
try {
|
|
808
842
|
const pkgPath = import_path2.default.join(cwd, "node_modules", pkgName, "package.json");
|
|
809
|
-
|
|
810
|
-
const raw = import_fs2.default.readFileSync(
|
|
843
|
+
const resolvedPath = import_fs2.default.existsSync(pkgPath) ? pkgPath : (0, import_module.createRequire)(import_path2.default.join(cwd, "package.json")).resolve(`${pkgName}/package.json`);
|
|
844
|
+
const raw = import_fs2.default.readFileSync(resolvedPath, "utf-8");
|
|
811
845
|
const json = JSON.parse(raw);
|
|
812
846
|
return json.version ?? null;
|
|
813
847
|
} catch {
|
|
@@ -974,9 +1008,9 @@ function registerInitCommand(program2) {
|
|
|
974
1008
|
return;
|
|
975
1009
|
}
|
|
976
1010
|
try {
|
|
977
|
-
const { createRequire } = await import("module");
|
|
1011
|
+
const { createRequire: createRequire2 } = await import("module");
|
|
978
1012
|
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
979
|
-
const req =
|
|
1013
|
+
const req = createRequire2(import_path4.default.join(cwd, "_probe.js"));
|
|
980
1014
|
const resolvedPath = req.resolve("eslint-plugin-ai-guard");
|
|
981
1015
|
const aiGuard = await import(pathToFileURL2(resolvedPath).href);
|
|
982
1016
|
const plugin = aiGuard.default || aiGuard;
|
|
@@ -1397,12 +1431,13 @@ function registerPresetCommand(program2) {
|
|
|
1397
1431
|
const backupPath = backupConfig(configPath);
|
|
1398
1432
|
log.info(`Backed up \u2192 ${import_chalk5.default.gray(import_path6.default.relative(cwd, backupPath))}`);
|
|
1399
1433
|
const existing = readConfig(configPath);
|
|
1400
|
-
const
|
|
1434
|
+
const withPlugin = isFlat(env.configType) ? patchFlatConfig(existing, preset) : patchLegacyConfig(existing, preset);
|
|
1435
|
+
const patched = isFlat(env.configType) ? switchFlatPreset(withPlugin, preset) : switchLegacyPreset(withPlugin, preset);
|
|
1401
1436
|
if (patched === existing) {
|
|
1402
|
-
log.warn("
|
|
1437
|
+
log.warn("No preset changes needed. Config already matches selected preset.");
|
|
1403
1438
|
} else {
|
|
1404
1439
|
writeConfig(configPath, patched);
|
|
1405
|
-
log.success(`
|
|
1440
|
+
log.success(`Updated ${import_chalk5.default.white(import_path6.default.relative(cwd, configPath))} to ${import_chalk5.default.cyan(preset)} preset`);
|
|
1406
1441
|
}
|
|
1407
1442
|
}
|
|
1408
1443
|
log.blank();
|
|
@@ -1494,7 +1529,7 @@ var import_path8 = __toESM(require("path"));
|
|
|
1494
1529
|
var import_chalk7 = __toESM(require("chalk"));
|
|
1495
1530
|
var import_ora2 = __toESM(require("ora"));
|
|
1496
1531
|
var BASELINE_FILE = ".ai-guard-baseline.json";
|
|
1497
|
-
function saveBaseline(result, preset, cwd) {
|
|
1532
|
+
function saveBaseline(result, preset, mode, cwd) {
|
|
1498
1533
|
const entries = result.files.map((f) => ({
|
|
1499
1534
|
filePath: f.filePath,
|
|
1500
1535
|
issues: f.issues.map((i) => ({
|
|
@@ -1507,6 +1542,7 @@ function saveBaseline(result, preset, cwd) {
|
|
|
1507
1542
|
const baseline = {
|
|
1508
1543
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1509
1544
|
preset,
|
|
1545
|
+
mode,
|
|
1510
1546
|
totalIssues: result.totalIssues,
|
|
1511
1547
|
entries
|
|
1512
1548
|
};
|
|
@@ -1527,23 +1563,28 @@ function loadBaseline(cwd) {
|
|
|
1527
1563
|
function issueKey(filePath, issue) {
|
|
1528
1564
|
return `${filePath}::${issue.ruleId}::${issue.line}::${issue.column}::${issue.message}`;
|
|
1529
1565
|
}
|
|
1530
|
-
function
|
|
1566
|
+
function stableIssueKey(filePath, issue) {
|
|
1567
|
+
return `${filePath}::${issue.ruleId}::${issue.message}`;
|
|
1568
|
+
}
|
|
1569
|
+
function buildBaselineSet(baseline, mode) {
|
|
1570
|
+
const toKey = mode === "stable" ? stableIssueKey : issueKey;
|
|
1531
1571
|
const set = /* @__PURE__ */ new Set();
|
|
1532
1572
|
for (const entry of baseline.entries) {
|
|
1533
1573
|
for (const issue of entry.issues) {
|
|
1534
|
-
set.add(
|
|
1574
|
+
set.add(toKey(entry.filePath, issue));
|
|
1535
1575
|
}
|
|
1536
1576
|
}
|
|
1537
1577
|
return set;
|
|
1538
1578
|
}
|
|
1539
|
-
function computeNewIssues(result, baselineSet) {
|
|
1579
|
+
function computeNewIssues(result, baselineSet, mode) {
|
|
1580
|
+
const toKey = mode === "stable" ? stableIssueKey : issueKey;
|
|
1540
1581
|
const newFiles = [];
|
|
1541
1582
|
let totalErrors = 0;
|
|
1542
1583
|
let totalWarnings = 0;
|
|
1543
1584
|
const ruleBreakdown = /* @__PURE__ */ new Map();
|
|
1544
1585
|
for (const file of result.files) {
|
|
1545
1586
|
const newIssues = file.issues.filter(
|
|
1546
|
-
(i) => !baselineSet.has(
|
|
1587
|
+
(i) => !baselineSet.has(toKey(file.filePath, i))
|
|
1547
1588
|
);
|
|
1548
1589
|
if (newIssues.length === 0) continue;
|
|
1549
1590
|
const errorCount = newIssues.filter((i) => i.severity === 2).length;
|
|
@@ -1570,9 +1611,14 @@ function computeNewIssues(result, baselineSet) {
|
|
|
1570
1611
|
};
|
|
1571
1612
|
}
|
|
1572
1613
|
function registerBaselineCommand(program2) {
|
|
1573
|
-
program2.command("baseline").description("Save current issues as baseline; future runs show only new issues").option("--save", "Save current state as the new baseline").option("--check", "Show only issues introduced since the last baseline").option("--path <dir>", "Directory to scan", ".").option("--preset <name>", "Preset: recommended | strict | security", "recommended").action(async (opts) => {
|
|
1614
|
+
program2.command("baseline").description("Save current issues as baseline; future runs show only new issues").option("--save", "Save current state as the new baseline").option("--check", "Show only issues introduced since the last baseline").option("--mode <name>", "Baseline match mode: strict | stable", "stable").option("--path <dir>", "Directory to scan", ".").option("--preset <name>", "Preset: recommended | strict | security", "recommended").action(async (opts) => {
|
|
1574
1615
|
const cwd = process.cwd();
|
|
1575
1616
|
const preset = opts.preset ?? "recommended";
|
|
1617
|
+
const mode = opts.mode ?? "stable";
|
|
1618
|
+
if (mode !== "strict" && mode !== "stable") {
|
|
1619
|
+
log.error("Invalid --mode. Use strict or stable.");
|
|
1620
|
+
process.exit(1);
|
|
1621
|
+
}
|
|
1576
1622
|
log.banner("AI GUARD BASELINE");
|
|
1577
1623
|
log.blank();
|
|
1578
1624
|
const existingBaseline = loadBaseline(cwd);
|
|
@@ -1590,12 +1636,13 @@ function registerBaselineCommand(program2) {
|
|
|
1590
1636
|
log.error(err instanceof Error ? err.message : String(err));
|
|
1591
1637
|
process.exit(1);
|
|
1592
1638
|
}
|
|
1593
|
-
const baselinePath = saveBaseline(result, preset, cwd);
|
|
1639
|
+
const baselinePath = saveBaseline(result, preset, mode, cwd);
|
|
1594
1640
|
log.success(
|
|
1595
1641
|
`Baseline saved \u2192 ${import_chalk7.default.white(import_path8.default.relative(cwd, baselinePath))}`
|
|
1596
1642
|
);
|
|
1597
1643
|
log.info(` ${result.totalIssues} issues recorded (${result.totalErrors} errors, ${result.totalWarnings} warnings)`);
|
|
1598
1644
|
log.info(` Preset: ${import_chalk7.default.cyan(preset)}`);
|
|
1645
|
+
log.info(` Mode: ${import_chalk7.default.cyan(mode)}`);
|
|
1599
1646
|
log.blank();
|
|
1600
1647
|
log.info("Future runs of " + import_chalk7.default.cyan("ai-guard baseline --check") + " will show only NEW issues.");
|
|
1601
1648
|
log.info("Add " + import_chalk7.default.white(BASELINE_FILE) + " to your git repository to share baseline with your team.");
|
|
@@ -1611,6 +1658,7 @@ function registerBaselineCommand(program2) {
|
|
|
1611
1658
|
const baselineDate = new Date(existingBaseline.createdAt).toLocaleString();
|
|
1612
1659
|
log.info(`Baseline from: ${import_chalk7.default.gray(baselineDate)}`);
|
|
1613
1660
|
log.info(`Baseline preset: ${import_chalk7.default.cyan(existingBaseline.preset)}`);
|
|
1661
|
+
log.info(`Baseline mode: ${import_chalk7.default.cyan(existingBaseline.mode ?? "strict")}`);
|
|
1614
1662
|
log.info(`Baseline issues: ${import_chalk7.default.gray(existingBaseline.totalIssues)}`);
|
|
1615
1663
|
log.blank();
|
|
1616
1664
|
const spinner = (0, import_ora2.default)({ text: "Scanning for new issues\u2026", color: "cyan" }).start();
|
|
@@ -1623,8 +1671,9 @@ function registerBaselineCommand(program2) {
|
|
|
1623
1671
|
log.error(err instanceof Error ? err.message : String(err));
|
|
1624
1672
|
process.exit(1);
|
|
1625
1673
|
}
|
|
1626
|
-
const
|
|
1627
|
-
const
|
|
1674
|
+
const compareMode = existingBaseline.mode ?? "strict";
|
|
1675
|
+
const baselineSet = buildBaselineSet(existingBaseline, compareMode);
|
|
1676
|
+
const newResult = computeNewIssues(result, baselineSet, compareMode);
|
|
1628
1677
|
if (newResult.totalIssues === 0) {
|
|
1629
1678
|
log.success("No new issues since baseline! \u2728");
|
|
1630
1679
|
log.info(
|
|
@@ -1675,7 +1724,7 @@ function registerBaselineCommand(program2) {
|
|
|
1675
1724
|
var import_fs6 = __toESM(require("fs"));
|
|
1676
1725
|
var import_path9 = __toESM(require("path"));
|
|
1677
1726
|
var import_chalk8 = __toESM(require("chalk"));
|
|
1678
|
-
var import_prompts2 =
|
|
1727
|
+
var import_prompts2 = require("@inquirer/prompts");
|
|
1679
1728
|
var RULE_CATEGORIES = {
|
|
1680
1729
|
"error-handling": [
|
|
1681
1730
|
"no-empty-catch",
|
|
@@ -2071,15 +2120,6 @@ function parseCategories(raw) {
|
|
|
2071
2120
|
}
|
|
2072
2121
|
return valid.length > 0 ? valid : ALL_CATEGORIES;
|
|
2073
2122
|
}
|
|
2074
|
-
function normalizeSelectedAgents(raw) {
|
|
2075
|
-
if (Array.isArray(raw)) {
|
|
2076
|
-
return raw.filter((value) => typeof value === "string");
|
|
2077
|
-
}
|
|
2078
|
-
if (typeof raw === "string") {
|
|
2079
|
-
return [raw];
|
|
2080
|
-
}
|
|
2081
|
-
return [];
|
|
2082
|
-
}
|
|
2083
2123
|
function registerInitContextCommand(program2) {
|
|
2084
2124
|
program2.command("init-context").description(
|
|
2085
2125
|
"Generate AI agent instruction files (CLAUDE.md, .cursorrules, copilot-instructions.md)\nso Claude Code, Cursor, and GitHub Copilot avoid the 17 most common AI anti-patterns."
|
|
@@ -2098,35 +2138,22 @@ function registerInitContextCommand(program2) {
|
|
|
2098
2138
|
if (opts.all) {
|
|
2099
2139
|
selectedAgents = [...AGENTS];
|
|
2100
2140
|
} else {
|
|
2101
|
-
let
|
|
2102
|
-
|
|
2103
|
-
{
|
|
2104
|
-
type: "multiselect",
|
|
2105
|
-
name: "agents",
|
|
2141
|
+
let selected;
|
|
2142
|
+
try {
|
|
2143
|
+
selected = await (0, import_prompts2.checkbox)({
|
|
2106
2144
|
message: "Which AI agents do you use?",
|
|
2107
2145
|
choices: [
|
|
2108
|
-
{
|
|
2109
|
-
{
|
|
2110
|
-
{
|
|
2111
|
-
{
|
|
2146
|
+
{ name: "Claude Code -> writes CLAUDE.md", value: "claude" },
|
|
2147
|
+
{ name: "Cursor -> writes .cursorrules", value: "cursor" },
|
|
2148
|
+
{ name: "GitHub Copilot -> writes .github/copilot-instructions.md", value: "copilot" },
|
|
2149
|
+
{ name: "All three (recommended) -> writes all files", value: "all" }
|
|
2112
2150
|
],
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
warn: "Select at least one agent."
|
|
2117
|
-
},
|
|
2118
|
-
{
|
|
2119
|
-
onCancel: () => {
|
|
2120
|
-
wasCancelled = true;
|
|
2121
|
-
return false;
|
|
2122
|
-
}
|
|
2123
|
-
}
|
|
2124
|
-
);
|
|
2125
|
-
if (wasCancelled) {
|
|
2151
|
+
required: true
|
|
2152
|
+
});
|
|
2153
|
+
} catch {
|
|
2126
2154
|
log.info("Selection cancelled. Nothing to do.");
|
|
2127
2155
|
return;
|
|
2128
2156
|
}
|
|
2129
|
-
const selected = normalizeSelectedAgents(response.agents);
|
|
2130
2157
|
if (selected.length === 0) {
|
|
2131
2158
|
log.info("No valid agents selected. Use --all to generate all files.");
|
|
2132
2159
|
return;
|
|
@@ -2145,13 +2172,11 @@ function registerInitContextCommand(program2) {
|
|
|
2145
2172
|
const fullPath = import_path9.default.join(cwd, agent.filePath);
|
|
2146
2173
|
const exists = import_fs6.default.existsSync(fullPath);
|
|
2147
2174
|
if (exists && !isForce && !isDryRun) {
|
|
2148
|
-
const
|
|
2149
|
-
type: "confirm",
|
|
2150
|
-
name: "overwrite",
|
|
2175
|
+
const overwrite = await (0, import_prompts2.confirm)({
|
|
2151
2176
|
message: `${agent.filePath} already exists. Overwrite?`,
|
|
2152
|
-
|
|
2177
|
+
default: false
|
|
2153
2178
|
});
|
|
2154
|
-
if (!
|
|
2179
|
+
if (!overwrite) {
|
|
2155
2180
|
results.push({ file: agent.filePath, status: "skipped" });
|
|
2156
2181
|
continue;
|
|
2157
2182
|
}
|