eslint-plugin-ai-guard 1.1.10 โ†’ 1.1.11

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 CHANGED
@@ -28,11 +28,35 @@ npx ai-guard run # recommended preset (lowest noise)
28
28
  npx ai-guard run --strict
29
29
  npx ai-guard run --security
30
30
  npx ai-guard init # auto-creates ESLint config for you
31
+ npx ai-guard init --dry-run
31
32
  npx ai-guard doctor # diagnoses setup issues
32
33
  npx ai-guard baseline # track only *new* issues going forward
33
34
  ```
34
35
  That's it. **Zero configuration required.**
35
36
 
37
+ ## ๐Ÿค– Set Up AI Agent Rules
38
+
39
+ Generate instruction files so Claude Code, Cursor, and GitHub Copilot
40
+ automatically avoid the 17 most common AI-generated anti-patterns:
41
+
42
+ ```bash
43
+ npx ai-guard init-context
44
+ ```
45
+
46
+ Follow the prompts to select your agent(s). Or generate all at once:
47
+
48
+ ```bash
49
+ npx ai-guard init-context --all
50
+ ```
51
+
52
+ This writes:
53
+ - `CLAUDE.md` โ€” read automatically by Claude Code
54
+ - `.cursorrules` โ€” read automatically by Cursor
55
+ - `.github/copilot-instructions.md` โ€” read automatically by GitHub Copilot
56
+
57
+ Your AI tools will now avoid these patterns before you even run the linter.
58
+ Use `--force` to regenerate after upgrading to a new version with new rules.
59
+
36
60
  ## ๐Ÿงช Real-World Usage Philosophy
37
61
 
38
62
  `ai-guard` is designed for production adoption in existing codebases:
@@ -62,7 +86,8 @@ const users = await Promise.all(userIds.map(async (id) => {
62
86
 
63
87
  ### Terminal Output
64
88
 
65
- ![ai-guard linting demo](./assets/demo.png)
89
+ ![ai-guard linting demo](./assets/example_1.png)
90
+ ![ai-guard linting demo](./assets/example_2.png)
66
91
 
67
92
  *The terminal output above shows `ai-guard` catching multiple AI-generated anti-patterns in a single run.*
68
93
 
@@ -102,7 +127,7 @@ const users = await Promise.all(userIds.map(async (id) => {
102
127
  Disallow dynamic `eval()` or `new Function()`.
103
128
  - **`ai-guard/no-sql-string-concat`** (Warn in `recommended`, Error in `security`/`strict`)
104
129
  Disallow variable concatenation/interpolation in SQL queries. AI tools frequently generate dangerous code enabling SQL injection.
105
- - **`ai-guard/no-unsafe-deserialize`** (Warn)
130
+ - **`ai-guard/no-unsafe-deserialize`** (Warn in `recommended`/`security`, Error in `strict`)
106
131
  Disallow `JSON.parse()` on likely untrusted inputs (like `req.body`) without visible validation.
107
132
  - **`ai-guard/require-auth-middleware`** (Warn)
108
133
  Enforce authentication middleware on Express/Fastify routes. AI tools frequently generate unprotected endpoints exposing sensitive data.
package/dist/cli/index.js CHANGED
@@ -315,8 +315,8 @@ var init_config_manager = __esm({
315
315
 
316
316
  // cli/index.ts
317
317
  var import_commander = require("commander");
318
- var import_fs6 = __toESM(require("fs"));
319
- var import_path9 = __toESM(require("path"));
318
+ var import_fs7 = __toESM(require("fs"));
319
+ var import_path10 = __toESM(require("path"));
320
320
 
321
321
  // cli/commands/run.ts
322
322
  var import_ora = __toESM(require("ora"));
@@ -473,12 +473,21 @@ async function loadPluginModuleFromCwd(cwd) {
473
473
  const requireFromCwd = createRequire(import_path.default.join(cwd, "package.json"));
474
474
  try {
475
475
  const resolved = requireFromCwd.resolve("eslint-plugin-ai-guard");
476
- return await import((0, import_url.pathToFileURL)(resolved).href);
476
+ return import((0, import_url.pathToFileURL)(resolved).href);
477
477
  } catch {
478
478
  }
479
479
  const localDistEntry = import_path.default.join(cwd, "dist", "index.js");
480
480
  if (import_fs.default.existsSync(localDistEntry)) {
481
- return await import((0, import_url.pathToFileURL)(localDistEntry).href);
481
+ return import((0, import_url.pathToFileURL)(localDistEntry).href);
482
+ }
483
+ const localSrcEntry = import_path.default.join(cwd, "src", "index.ts");
484
+ if (import_fs.default.existsSync(localSrcEntry)) {
485
+ try {
486
+ return import((0, import_url.pathToFileURL)(localSrcEntry).href);
487
+ } catch (err) {
488
+ const reason = err instanceof Error ? err.message : String(err);
489
+ log.debug(`Local src plugin import failed: ${reason}`);
490
+ }
482
491
  }
483
492
  throw new Error(
484
493
  "eslint-plugin-ai-guard is not installed. Run: npm install --save-dev eslint-plugin-ai-guard"
@@ -493,7 +502,13 @@ async function runEslint(options) {
493
502
  const rawPlugin = await loadPluginModuleFromCwd(process.cwd());
494
503
  const plugin = normalizePlugin(rawPlugin);
495
504
  const rules = getRules(options.preset);
496
- const targetPath = import_path.default.resolve(options.targetPath);
505
+ const resolvedTargetPath = import_path.default.resolve(options.targetPath);
506
+ if (!import_fs.default.existsSync(resolvedTargetPath)) {
507
+ throw new Error(`Path not found: ${options.targetPath}`);
508
+ }
509
+ const targetStat = import_fs.default.statSync(resolvedTargetPath);
510
+ const isSingleFileTarget = targetStat.isFile();
511
+ const eslintCwd = isSingleFileTarget ? import_path.default.dirname(resolvedTargetPath) : resolvedTargetPath;
497
512
  const startMs = Date.now();
498
513
  const JS_TS_FILES = [
499
514
  "**/*.js",
@@ -556,11 +571,11 @@ async function runEslint(options) {
556
571
  });
557
572
  }
558
573
  const eslint = new ESLint({
559
- cwd: targetPath,
574
+ cwd: eslintCwd,
560
575
  overrideConfigFile: true,
561
576
  overrideConfig: configBlocks
562
577
  });
563
- const patterns = JS_TS_FILES;
578
+ const patterns = isSingleFileTarget ? [import_path.default.basename(resolvedTargetPath)] : JS_TS_FILES;
564
579
  const perPatternResults = await Promise.all(
565
580
  patterns.map(async (pattern) => {
566
581
  try {
@@ -617,8 +632,23 @@ async function runEslint(options) {
617
632
 
618
633
  // cli/commands/run.ts
619
634
  function registerRunCommand(program2) {
620
- 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").option("--max-warnings <n>", "Fail if warnings exceed this count", parseInt).action(async (opts) => {
635
+ 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").option(
636
+ "--max-warnings <n>",
637
+ "Fail if warnings exceed this count",
638
+ (value) => Number.parseInt(value, 10)
639
+ ).action(async (opts) => {
640
+ if (opts.maxWarnings !== void 0 && (!Number.isInteger(opts.maxWarnings) || opts.maxWarnings < 0)) {
641
+ log.blank();
642
+ log.error("--max-warnings must be a non-negative integer.");
643
+ log.blank();
644
+ process.exit(1);
645
+ return;
646
+ }
621
647
  const preset = opts.strict ? "strict" : opts.security ? "security" : "recommended";
648
+ if (opts.strict && opts.security && !opts.json) {
649
+ log.warn("Both --strict and --security were provided. Using --strict.");
650
+ log.blank();
651
+ }
622
652
  if (!opts.json) {
623
653
  log.banner("AI GUARD RESULTS");
624
654
  log.blank();
@@ -662,7 +692,7 @@ function registerRunCommand(program2) {
662
692
  files: result.files
663
693
  };
664
694
  console.log(JSON.stringify(jsonOutput, null, 2));
665
- process.exit(exitCode(result, opts.maxWarnings));
695
+ process.exit(getRunExitCode(result, opts.maxWarnings));
666
696
  return;
667
697
  }
668
698
  log.success(`Scanned: ${import_chalk2.default.white(opts.path)}`);
@@ -729,10 +759,10 @@ function registerRunCommand(program2) {
729
759
  log.info(`Run ${import_chalk2.default.cyan("ai-guard baseline")} to save these issues and track only new ones`);
730
760
  log.info(`Run ${import_chalk2.default.cyan("ai-guard ignore")} to suppress dist/build noise`);
731
761
  log.blank();
732
- process.exit(exitCode(result, opts.maxWarnings));
762
+ process.exit(getRunExitCode(result, opts.maxWarnings));
733
763
  });
734
764
  }
735
- function exitCode(result, maxWarnings) {
765
+ function getRunExitCode(result, maxWarnings) {
736
766
  if (result.totalErrors > 0) return 1;
737
767
  if (maxWarnings !== void 0 && result.totalWarnings > maxWarnings) return 1;
738
768
  return 0;
@@ -1641,11 +1671,491 @@ function registerBaselineCommand(program2) {
1641
1671
  });
1642
1672
  }
1643
1673
 
1674
+ // cli/commands/init-context.ts
1675
+ var import_fs6 = __toESM(require("fs"));
1676
+ var import_path9 = __toESM(require("path"));
1677
+ var import_chalk8 = __toESM(require("chalk"));
1678
+ var import_prompts2 = __toESM(require("prompts"));
1679
+ var RULE_CATEGORIES = {
1680
+ "error-handling": [
1681
+ "no-empty-catch",
1682
+ "no-broad-exception",
1683
+ "no-catch-log-rethrow",
1684
+ "no-catch-without-use"
1685
+ ],
1686
+ async: [
1687
+ "no-async-array-callback",
1688
+ "no-floating-promise",
1689
+ "no-await-in-loop",
1690
+ "no-async-without-await",
1691
+ "no-redundant-await"
1692
+ ],
1693
+ security: [
1694
+ "no-hardcoded-secret",
1695
+ "no-eval-dynamic",
1696
+ "no-sql-string-concat",
1697
+ "no-unsafe-deserialize",
1698
+ "require-auth-middleware",
1699
+ "require-authz-check"
1700
+ ],
1701
+ quality: [
1702
+ "no-console-in-handler",
1703
+ "no-duplicate-logic-block"
1704
+ ]
1705
+ };
1706
+ var ALL_CATEGORIES = ["error-handling", "async", "security", "quality"];
1707
+ function errorHandlingSection() {
1708
+ return `## Error Handling
1709
+ **Never** generate empty catch blocks:
1710
+ \u274C try { await fetchUser() } catch (e) {}
1711
+ \u2705 try { await fetchUser() } catch (e) { logger.error(e); throw e; }
1712
+
1713
+ **Never** catch (e: any) \u2014 use specific types or narrow with instanceof.
1714
+
1715
+ **Never** catch, log, and immediately rethrow without adding context:
1716
+ \u274C catch (e) { console.error(e); throw e; }
1717
+ \u2705 catch (e) { throw new Error('fetchUser failed', { cause: e }); }`;
1718
+ }
1719
+ function asyncSection() {
1720
+ return `## Async Correctness
1721
+ **Never** pass async callbacks to array.map(), filter(), forEach(), or reduce():
1722
+ \u274C const results = ids.map(async (id) => await fetchUser(id));
1723
+ \u2705 const results = await Promise.all(ids.map((id) => fetchUser(id)));
1724
+
1725
+ **Never** use await inside a for...of or while loop when parallel execution
1726
+ is possible:
1727
+ \u274C for (const id of ids) { const user = await fetchUser(id); }
1728
+ \u2705 const users = await Promise.all(ids.map(fetchUser));
1729
+
1730
+ **Never** call an async function without await or .catch():
1731
+ \u274C sendEmail(user);
1732
+ \u2705 await sendEmail(user);
1733
+
1734
+ **Never** declare a function async if it never uses await.`;
1735
+ }
1736
+ function securitySection() {
1737
+ return `## Security
1738
+ **Never** hardcode secrets, API keys, tokens, or passwords inline:
1739
+ \u274C const apiKey = 'sk-prod-abc123';
1740
+ \u2705 const apiKey = process.env.API_KEY;
1741
+
1742
+ **Never** concatenate user input into SQL strings:
1743
+ \u274C db.query('SELECT * FROM users WHERE id = ' + req.params.id);
1744
+ \u2705 db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
1745
+
1746
+ **Never** use eval() or new Function() with dynamic/user-supplied strings.
1747
+
1748
+ **Never** call JSON.parse() on req.body, req.query, or req.params directly
1749
+ without schema validation.
1750
+
1751
+ **Always** add authentication middleware before route handlers:
1752
+ \u274C router.get('/admin/users', getUsers);
1753
+ \u2705 router.get('/admin/users', requireAuth, getUsers);
1754
+
1755
+ **Always** verify that the authenticated user owns or has access to the
1756
+ resource being accessed when using req.params.id or similar identifiers.`;
1757
+ }
1758
+ function qualitySection() {
1759
+ return `## Code Quality
1760
+ **Never** leave console.log or console.debug statements inside HTTP route
1761
+ handlers or event listeners in production code.
1762
+
1763
+ **Never** copy-paste the same logic block consecutively \u2014 extract shared
1764
+ logic into a helper function or consolidate duplicated code.`;
1765
+ }
1766
+ var SECTION_MAP = {
1767
+ "error-handling": errorHandlingSection,
1768
+ async: asyncSection,
1769
+ security: securitySection,
1770
+ quality: qualitySection
1771
+ };
1772
+ function getSections(categories) {
1773
+ return categories.map((cat) => SECTION_MAP[cat]()).join("\n\n");
1774
+ }
1775
+ function generateClaudeFile(version, categories) {
1776
+ return `<!-- DO NOT EDIT MANUALLY \u2014 generated by eslint-plugin-ai-guard -->
1777
+ <!-- Regenerate: npx ai-guard init-context --force -->
1778
+
1779
+ # AI Guard Rules \u2014 Claude Code Instructions
1780
+
1781
+ You are coding in a project that uses eslint-plugin-ai-guard.
1782
+ Avoid generating the following patterns. They will be flagged as lint errors.
1783
+
1784
+ ${getSections(categories)}
1785
+
1786
+ ---
1787
+ Generated by eslint-plugin-ai-guard v${version}.
1788
+ Run \`npx ai-guard init-context --force\` to regenerate.
1789
+ `;
1790
+ }
1791
+ function cursorErrorHandling() {
1792
+ return `## Error Handling
1793
+
1794
+ Rule: no-empty-catch
1795
+ Bad: try { await fetchUser() } catch (e) {}
1796
+ Good: try { await fetchUser() } catch (e) { logger.error(e); throw e; }
1797
+
1798
+ Rule: no-broad-exception
1799
+ Bad: catch (e: any) { handle(e); }
1800
+ Good: catch (e) { if (e instanceof NetworkError) { retry(); } throw e; }
1801
+
1802
+ Rule: no-catch-log-rethrow
1803
+ Bad: catch (e) { console.error(e); throw e; }
1804
+ Good: catch (e) { throw new Error('fetchUser failed', { cause: e }); }`;
1805
+ }
1806
+ function cursorAsyncSection() {
1807
+ return `## Async Correctness
1808
+
1809
+ Rule: no-async-array-callback
1810
+ Bad: const results = ids.map(async (id) => await fetchUser(id));
1811
+ Good: const results = await Promise.all(ids.map((id) => fetchUser(id)));
1812
+
1813
+ Rule: no-await-in-loop
1814
+ Bad: for (const id of ids) { const user = await fetchUser(id); }
1815
+ Good: const users = await Promise.all(ids.map(fetchUser));
1816
+
1817
+ Rule: no-floating-promise
1818
+ Bad: sendEmail(user);
1819
+ Good: await sendEmail(user);
1820
+
1821
+ Rule: no-async-without-await
1822
+ Bad: async function getUser() { return db.findOne(id); }
1823
+ Good: function getUser() { return db.findOne(id); }`;
1824
+ }
1825
+ function cursorSecuritySection() {
1826
+ return `## Security
1827
+
1828
+ Rule: no-hardcoded-secret
1829
+ Bad: const apiKey = 'sk-prod-abc123';
1830
+ Good: const apiKey = process.env.API_KEY;
1831
+
1832
+ Rule: no-sql-string-concat
1833
+ Bad: db.query('SELECT * FROM users WHERE id = ' + req.params.id);
1834
+ Good: db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
1835
+
1836
+ Rule: no-eval-dynamic
1837
+ Bad: eval(userInput);
1838
+ Good: Use a safe parser or allowlist approach instead.
1839
+
1840
+ Rule: no-unsafe-deserialize
1841
+ Bad: const data = JSON.parse(req.body);
1842
+ Good: const data = schema.parse(req.body);
1843
+
1844
+ Rule: require-auth-middleware
1845
+ Bad: router.get('/admin/users', getUsers);
1846
+ Good: router.get('/admin/users', requireAuth, getUsers);
1847
+
1848
+ Rule: require-authz-check
1849
+ Bad: const user = await db.findById(req.params.id); res.json(user);
1850
+ Good: const user = await db.findById(req.params.id); if (user.ownerId !== req.user.id) throw new ForbiddenError(); res.json(user);`;
1851
+ }
1852
+ function cursorQualitySection() {
1853
+ return `## Code Quality
1854
+
1855
+ Rule: no-console-in-handler
1856
+ Bad: router.get('/users', (req, res) => { console.log('hit'); ... });
1857
+ Good: router.get('/users', (req, res) => { logger.info('hit'); ... });
1858
+
1859
+ Rule: no-duplicate-logic-block
1860
+ Bad: Two identical consecutive code blocks copy-pasted.
1861
+ Good: Extract the duplicated logic into a shared helper function.`;
1862
+ }
1863
+ var CURSOR_SECTION_MAP = {
1864
+ "error-handling": cursorErrorHandling,
1865
+ async: cursorAsyncSection,
1866
+ security: cursorSecuritySection,
1867
+ quality: cursorQualitySection
1868
+ };
1869
+ function generateCursorFile(version, categories) {
1870
+ const sections = categories.map((cat) => CURSOR_SECTION_MAP[cat]()).join("\n\n");
1871
+ return `# AI Guard Rules \u2014 Cursor Instructions
1872
+ # DO NOT EDIT MANUALLY \u2014 generated by eslint-plugin-ai-guard
1873
+ # Regenerate: npx ai-guard init-context --force
1874
+
1875
+ This project uses eslint-plugin-ai-guard to catch the 17 most common
1876
+ anti-patterns generated by AI coding tools. Follow these rules when
1877
+ generating or editing code in this project. Violations will be flagged
1878
+ as lint errors.
1879
+
1880
+ ${sections}
1881
+
1882
+ ---
1883
+ These rules mirror eslint-plugin-ai-guard v${version}.
1884
+ Run \`npx ai-guard init-context --force\` to regenerate.
1885
+ `;
1886
+ }
1887
+ function copilotQuickRef(categories) {
1888
+ const lines = [];
1889
+ if (categories.includes("error-handling")) {
1890
+ lines.push(
1891
+ "Never: generate empty catch blocks",
1892
+ "Never: catch (e: any) without narrowing",
1893
+ "Never: catch, log, and rethrow without adding context"
1894
+ );
1895
+ }
1896
+ if (categories.includes("async")) {
1897
+ lines.push(
1898
+ "Never: pass async callbacks to .map(), .filter(), .forEach()",
1899
+ "Never: use await inside loops when Promise.all() works",
1900
+ "Never: call async functions without await or .catch()",
1901
+ "Never: mark functions async if they never await"
1902
+ );
1903
+ }
1904
+ if (categories.includes("security")) {
1905
+ lines.push(
1906
+ "Never: hardcode API keys, secrets, or passwords",
1907
+ "Never: concatenate user input into SQL strings",
1908
+ "Never: use eval() or new Function() with dynamic input",
1909
+ "Never: JSON.parse() untrusted input without validation",
1910
+ "Always: add auth middleware before route handlers",
1911
+ "Always: verify resource ownership in handlers"
1912
+ );
1913
+ }
1914
+ if (categories.includes("quality")) {
1915
+ lines.push(
1916
+ "Never: leave console.log in route handlers",
1917
+ "Never: copy-paste identical consecutive code blocks"
1918
+ );
1919
+ }
1920
+ return lines.map((l) => `- ${l}`).join("\n");
1921
+ }
1922
+ function copilotDetails(categories) {
1923
+ const details = [];
1924
+ if (categories.includes("async")) {
1925
+ details.push(`### no-floating-promise
1926
+ \`\`\`typescript
1927
+ // \u274C Bad \u2014 promise rejected silently
1928
+ sendEmail(user);
1929
+
1930
+ // \u2705 Good
1931
+ await sendEmail(user);
1932
+ \`\`\``);
1933
+ details.push(`### no-async-array-callback
1934
+ \`\`\`typescript
1935
+ // \u274C Bad \u2014 returns Promise[], not values
1936
+ ids.map(async (id) => await fetchUser(id));
1937
+
1938
+ // \u2705 Good
1939
+ await Promise.all(ids.map((id) => fetchUser(id)));
1940
+ \`\`\``);
1941
+ }
1942
+ if (categories.includes("error-handling")) {
1943
+ details.push(`### no-empty-catch
1944
+ \`\`\`typescript
1945
+ // \u274C Bad \u2014 error swallowed silently
1946
+ try { await fetchUser() } catch (e) {}
1947
+
1948
+ // \u2705 Good
1949
+ try { await fetchUser() } catch (e) { logger.error(e); throw e; }
1950
+ \`\`\``);
1951
+ }
1952
+ if (categories.includes("security")) {
1953
+ details.push(`### no-hardcoded-secret
1954
+ \`\`\`typescript
1955
+ // \u274C Bad
1956
+ const apiKey = 'sk-prod-abc123';
1957
+
1958
+ // \u2705 Good
1959
+ const apiKey = process.env.API_KEY;
1960
+ \`\`\``);
1961
+ details.push(`### no-sql-string-concat
1962
+ \`\`\`typescript
1963
+ // \u274C Bad \u2014 SQL injection
1964
+ db.query('SELECT * FROM users WHERE id = ' + req.params.id);
1965
+
1966
+ // \u2705 Good
1967
+ db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
1968
+ \`\`\``);
1969
+ }
1970
+ return details.join("\n\n");
1971
+ }
1972
+ function generateCopilotFile(version, categories) {
1973
+ return `<!-- DO NOT EDIT MANUALLY \u2014 generated by eslint-plugin-ai-guard -->
1974
+ <!-- Regenerate: npx ai-guard init-context --force -->
1975
+
1976
+ # AI Guard \u2014 GitHub Copilot Instructions
1977
+
1978
+ This project uses \`eslint-plugin-ai-guard\`. Follow these rules to avoid
1979
+ generating patterns that will be flagged as lint errors.
1980
+
1981
+ ## Quick Reference
1982
+
1983
+ ${copilotQuickRef(categories)}
1984
+
1985
+ ## Details
1986
+
1987
+ ${copilotDetails(categories)}
1988
+
1989
+ ---
1990
+ Generated by eslint-plugin-ai-guard v${version}.
1991
+ Run \`npx ai-guard init-context --force\` to regenerate.
1992
+ `;
1993
+ }
1994
+ var AGENTS = [
1995
+ {
1996
+ id: "claude",
1997
+ label: "Claude Code",
1998
+ filePath: "CLAUDE.md",
1999
+ generate: generateClaudeFile
2000
+ },
2001
+ {
2002
+ id: "cursor",
2003
+ label: "Cursor",
2004
+ filePath: ".cursorrules",
2005
+ generate: generateCursorFile
2006
+ },
2007
+ {
2008
+ id: "copilot",
2009
+ label: "GitHub Copilot",
2010
+ filePath: import_path9.default.join(".github", "copilot-instructions.md"),
2011
+ generate: generateCopilotFile
2012
+ }
2013
+ ];
2014
+ function getPackageVersion2(cwd) {
2015
+ try {
2016
+ const pkgPath = import_path9.default.join(cwd, "node_modules", "eslint-plugin-ai-guard", "package.json");
2017
+ if (import_fs6.default.existsSync(pkgPath)) {
2018
+ const pkg = JSON.parse(import_fs6.default.readFileSync(pkgPath, "utf-8"));
2019
+ if (pkg.version) return pkg.version;
2020
+ }
2021
+ } catch {
2022
+ }
2023
+ try {
2024
+ const pkgPath = import_path9.default.join(cwd, "package.json");
2025
+ if (import_fs6.default.existsSync(pkgPath)) {
2026
+ const pkg = JSON.parse(import_fs6.default.readFileSync(pkgPath, "utf-8"));
2027
+ if (pkg.version) return pkg.version;
2028
+ }
2029
+ } catch {
2030
+ }
2031
+ return "0.0.0";
2032
+ }
2033
+ function parseCategories(raw) {
2034
+ if (!raw) return ALL_CATEGORIES;
2035
+ const parts = raw.split(",").map((s) => s.trim().toLowerCase());
2036
+ const valid = [];
2037
+ for (const part of parts) {
2038
+ if (part in RULE_CATEGORIES) {
2039
+ valid.push(part);
2040
+ }
2041
+ }
2042
+ return valid.length > 0 ? valid : ALL_CATEGORIES;
2043
+ }
2044
+ function registerInitContextCommand(program2) {
2045
+ program2.command("init-context").description(
2046
+ "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."
2047
+ ).option("--all", "Generate files for all agents (skip prompt)").option("--force", "Overwrite existing files without asking").option("--dry-run", "Print what would be generated without writing files").option("--rules <categories>", "Comma-separated categories: async,security,error-handling,quality").action(async (opts) => {
2048
+ const cwd = process.cwd();
2049
+ const version = getPackageVersion2(cwd);
2050
+ const categories = parseCategories(opts.rules);
2051
+ const isDryRun = opts.dryRun === true;
2052
+ const isForce = opts.force === true;
2053
+ log.banner("AI GUARD INIT-CONTEXT");
2054
+ if (isDryRun) {
2055
+ log.print(` ${import_chalk8.default.yellow("\u2691 DRY RUN \u2014 no files will be written")}`);
2056
+ log.blank();
2057
+ }
2058
+ let selectedAgents;
2059
+ if (opts.all) {
2060
+ selectedAgents = [...AGENTS];
2061
+ } else {
2062
+ const response = await (0, import_prompts2.default)({
2063
+ type: "multiselect",
2064
+ name: "agents",
2065
+ message: "Which AI agents do you use?",
2066
+ choices: [
2067
+ { title: "Claude Code", value: "claude", description: "\u2192 writes CLAUDE.md" },
2068
+ { title: "Cursor", value: "cursor", description: "\u2192 writes .cursorrules" },
2069
+ { title: "GitHub Copilot", value: "copilot", description: "\u2192 writes .github/copilot-instructions.md" },
2070
+ { title: "All three (recommended)", value: "all", description: "\u2192 writes all files" }
2071
+ ],
2072
+ hint: "- space to select, enter to confirm"
2073
+ });
2074
+ if (!response.agents || response.agents.length === 0) {
2075
+ log.info("No agents selected. Nothing to do.");
2076
+ return;
2077
+ }
2078
+ const selected = response.agents;
2079
+ if (selected.includes("all")) {
2080
+ selectedAgents = [...AGENTS];
2081
+ } else {
2082
+ selectedAgents = AGENTS.filter((a) => selected.includes(a.id));
2083
+ }
2084
+ }
2085
+ log.info(`Categories: ${import_chalk8.default.cyan(categories.join(", "))}`);
2086
+ log.info(`Version: ${import_chalk8.default.cyan(version)}`);
2087
+ log.blank();
2088
+ const results = [];
2089
+ for (const agent of selectedAgents) {
2090
+ const fullPath = import_path9.default.join(cwd, agent.filePath);
2091
+ const exists = import_fs6.default.existsSync(fullPath);
2092
+ if (exists && !isForce && !isDryRun) {
2093
+ const confirm = await (0, import_prompts2.default)({
2094
+ type: "confirm",
2095
+ name: "overwrite",
2096
+ message: `${agent.filePath} already exists. Overwrite?`,
2097
+ initial: false
2098
+ });
2099
+ if (!confirm.overwrite) {
2100
+ results.push({ file: agent.filePath, status: "skipped" });
2101
+ continue;
2102
+ }
2103
+ }
2104
+ const content = agent.generate(version, categories);
2105
+ if (isDryRun) {
2106
+ log.section(`Preview: ${agent.filePath}`);
2107
+ log.print(import_chalk8.default.gray("\u2500".repeat(60)));
2108
+ for (const line of content.split("\n").slice(0, 20)) {
2109
+ log.print(import_chalk8.default.gray(` ${line}`));
2110
+ }
2111
+ log.print(import_chalk8.default.gray(" ... (truncated)"));
2112
+ log.print(import_chalk8.default.gray("\u2500".repeat(60)));
2113
+ log.blank();
2114
+ results.push({ file: agent.filePath, status: "dry-run" });
2115
+ continue;
2116
+ }
2117
+ const parentDir = import_path9.default.dirname(fullPath);
2118
+ if (!import_fs6.default.existsSync(parentDir)) {
2119
+ import_fs6.default.mkdirSync(parentDir, { recursive: true });
2120
+ }
2121
+ import_fs6.default.writeFileSync(fullPath, content, "utf-8");
2122
+ results.push({ file: agent.filePath, status: "created" });
2123
+ }
2124
+ log.section("Summary");
2125
+ for (const result of results) {
2126
+ switch (result.status) {
2127
+ case "created":
2128
+ log.print(` ${import_chalk8.default.green("\u2705")} Created ${import_chalk8.default.white(result.file)}`);
2129
+ break;
2130
+ case "skipped":
2131
+ log.print(` ${import_chalk8.default.yellow("\u23ED")} Skipped ${import_chalk8.default.white(result.file)} ${import_chalk8.default.gray("(already exists \u2014 use --force to overwrite)")}`);
2132
+ break;
2133
+ case "dry-run":
2134
+ log.print(` ${import_chalk8.default.cyan("\u{1F4DD}")} Would create ${import_chalk8.default.white(result.file)}`);
2135
+ break;
2136
+ }
2137
+ }
2138
+ log.blank();
2139
+ if (isDryRun) {
2140
+ log.info("Dry run complete \u2014 no files were written.");
2141
+ log.info(`Re-run without ${import_chalk8.default.cyan("--dry-run")} to apply.`);
2142
+ } else {
2143
+ const createdCount = results.filter((r) => r.status === "created").length;
2144
+ if (createdCount > 0) {
2145
+ log.success(
2146
+ "Done. Your AI agents will now automatically avoid the 17 most common AI-generated code anti-patterns."
2147
+ );
2148
+ }
2149
+ }
2150
+ log.blank();
2151
+ });
2152
+ }
2153
+
1644
2154
  // cli/index.ts
1645
2155
  function getVersion() {
1646
2156
  try {
1647
- const pkgPath = import_path9.default.resolve(__dirname, "../../package.json");
1648
- const raw = import_fs6.default.readFileSync(pkgPath, "utf-8");
2157
+ const pkgPath = import_path10.default.resolve(__dirname, "../../package.json");
2158
+ const raw = import_fs7.default.readFileSync(pkgPath, "utf-8");
1649
2159
  const pkg = JSON.parse(raw);
1650
2160
  return typeof pkg.version === "string" ? pkg.version : "0.0.0";
1651
2161
  } catch {
@@ -1654,7 +2164,7 @@ function getVersion() {
1654
2164
  }
1655
2165
  var program = new import_commander.Command();
1656
2166
  program.name("ai-guard").description(
1657
- "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 doctor Check your setup\n npx ai-guard baseline Save baseline, track new issues only"
2167
+ "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 doctor Check your setup\n npx ai-guard baseline Save baseline, track new issues only"
1658
2168
  ).version(getVersion(), "-v, --version", "Print version number").helpOption("-h, --help", "Show help");
1659
2169
  registerRunCommand(program);
1660
2170
  registerInitCommand(program);
@@ -1662,6 +2172,7 @@ registerDoctorCommand(program);
1662
2172
  registerPresetCommand(program);
1663
2173
  registerIgnoreCommand(program);
1664
2174
  registerBaselineCommand(program);
2175
+ registerInitContextCommand(program);
1665
2176
  program.configureOutput({
1666
2177
  writeErr(str) {
1667
2178
  const cleaned = str.replace(/^error:\s*/i, "").trim();