@promise-inc/ai-guard 0.1.0

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.
Files changed (54) hide show
  1. package/README.md +141 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +29 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/config.d.ts +3 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +103 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/engine.d.ts +3 -0
  11. package/dist/engine.d.ts.map +1 -0
  12. package/dist/engine.js +74 -0
  13. package/dist/engine.js.map +1 -0
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +12 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/rules/ai-patterns.d.ts +5 -0
  19. package/dist/rules/ai-patterns.d.ts.map +1 -0
  20. package/dist/rules/ai-patterns.js +94 -0
  21. package/dist/rules/ai-patterns.js.map +1 -0
  22. package/dist/rules/excessive-comments.d.ts +5 -0
  23. package/dist/rules/excessive-comments.d.ts.map +1 -0
  24. package/dist/rules/excessive-comments.js +41 -0
  25. package/dist/rules/excessive-comments.js.map +1 -0
  26. package/dist/rules/generic-names.d.ts +5 -0
  27. package/dist/rules/generic-names.d.ts.map +1 -0
  28. package/dist/rules/generic-names.js +61 -0
  29. package/dist/rules/generic-names.js.map +1 -0
  30. package/dist/rules/index.d.ts +8 -0
  31. package/dist/rules/index.d.ts.map +1 -0
  32. package/dist/rules/index.js +49 -0
  33. package/dist/rules/index.js.map +1 -0
  34. package/dist/rules/large-functions.d.ts +5 -0
  35. package/dist/rules/large-functions.d.ts.map +1 -0
  36. package/dist/rules/large-functions.js +58 -0
  37. package/dist/rules/large-functions.js.map +1 -0
  38. package/dist/rules/obvious-comments.d.ts +5 -0
  39. package/dist/rules/obvious-comments.d.ts.map +1 -0
  40. package/dist/rules/obvious-comments.js +111 -0
  41. package/dist/rules/obvious-comments.js.map +1 -0
  42. package/dist/types.d.ts +34 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +43 -0
  45. package/dist/types.js.map +1 -0
  46. package/dist/utils/files.d.ts +4 -0
  47. package/dist/utils/files.d.ts.map +1 -0
  48. package/dist/utils/files.js +85 -0
  49. package/dist/utils/files.js.map +1 -0
  50. package/dist/utils/output.d.ts +3 -0
  51. package/dist/utils/output.d.ts.map +1 -0
  52. package/dist/utils/output.js +43 -0
  53. package/dist/utils/output.js.map +1 -0
  54. package/package.json +46 -0
package/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # @promise-inc/ai-guard
2
+
3
+ Detect AI-generated code patterns before commit/push. Not a linter — a guard.
4
+
5
+ <p align="center">
6
+ <img src="https://raw.githubusercontent.com/promise-inc/ai-guard/main/assets/demo.svg" alt="ai-guard CLI output demo" width="680" />
7
+ </p>
8
+
9
+ <p align="center">
10
+ <img src="https://raw.githubusercontent.com/promise-inc/ai-guard/main/assets/usage.svg" alt="ai-guard config example" width="680" />
11
+ </p>
12
+
13
+ ## Why?
14
+
15
+ AI tools generate code that works but often carries patterns that degrade codebases over time:
16
+
17
+ - Excessive comments that restate the obvious
18
+ - Generic variable names (`data`, `result`, `item`)
19
+ - Functions that are way too large
20
+ - `// Step 1:` style comments
21
+ - Verbose JSDoc blocks with no real information
22
+
23
+ `ai-guard` catches these patterns **before they reach your codebase**.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install @promise-inc/ai-guard --save-dev
29
+ ```
30
+
31
+ ## Usage
32
+
33
+ ```bash
34
+ # Analyze the entire project
35
+ npx ai-guard
36
+
37
+ # Analyze only staged files (for pre-commit hooks)
38
+ npx ai-guard --staged
39
+ ```
40
+
41
+ ### As a Git Hook
42
+
43
+ ```json
44
+ {
45
+ "husky": {
46
+ "hooks": {
47
+ "pre-push": "ai-guard"
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Or with `lint-staged`:
54
+
55
+ ```json
56
+ {
57
+ "lint-staged": {
58
+ "*.{ts,tsx,js,jsx}": "ai-guard --staged"
59
+ }
60
+ }
61
+ ```
62
+
63
+ ## Rules
64
+
65
+ | Rule | What it detects | Severity |
66
+ |------|----------------|----------|
67
+ | `excessive-comments` | Files where comment ratio exceeds threshold | error |
68
+ | `obvious-comments` | Redundant comments like `// Initialize the result` | warning |
69
+ | `generic-names` | Variables/functions named `data`, `result`, `item`, etc. | warning |
70
+ | `large-functions` | Functions exceeding the line limit | error |
71
+ | `ai-patterns` | `// Step 1:`, `// Helper function`, verbose JSDoc, forbidden text patterns | warning/error |
72
+
73
+ ## Configuration
74
+
75
+ Create `ai-guard.config.ts`, `ai-guard.config.js`, or add an `ai-guard` field to `package.json`:
76
+
77
+ ```ts
78
+ export default {
79
+ maxCommentsRatio: 0.15,
80
+ maxFunctionLines: 60,
81
+ forbidGenericNames: ["data", "result", "item", "value", "temp"],
82
+ aiPatterns: {
83
+ forbid: [
84
+ "this function",
85
+ "the purpose of",
86
+ "we will",
87
+ "this method",
88
+ "note that",
89
+ ],
90
+ },
91
+ include: ["**/*.ts", "**/*.tsx"],
92
+ exclude: ["node_modules/**", "dist/**", "**/*.test.*"],
93
+ };
94
+ ```
95
+
96
+ ### Config Options
97
+
98
+ | Option | Type | Default | Description |
99
+ |--------|------|---------|-------------|
100
+ | `maxCommentsRatio` | `number` | `0.15` | Max ratio of comment lines to total lines |
101
+ | `maxFunctionLines` | `number` | `60` | Max lines per function/method |
102
+ | `forbidGenericNames` | `string[]` | `["data", "result", ...]` | Banned variable/function names |
103
+ | `aiPatterns.forbid` | `string[]` | `["this function", ...]` | Forbidden phrases in comments |
104
+ | `include` | `string[]` | `["**/*.ts", "**/*.tsx", ...]` | File patterns to analyze |
105
+ | `exclude` | `string[]` | `["node_modules/**", ...]` | File patterns to skip |
106
+
107
+ ## Programmatic API
108
+
109
+ ```ts
110
+ import { analyzeProject, loadConfig } from '@promise-inc/ai-guard';
111
+
112
+ const config = await loadConfig(process.cwd());
113
+ const analysis = await analyzeProject(process.cwd(), config);
114
+
115
+ console.log(analysis.passed); // true | false
116
+ console.log(analysis.totalViolations); // number
117
+ ```
118
+
119
+ ## Exit Codes
120
+
121
+ | Code | Meaning |
122
+ |------|---------|
123
+ | `0` | All files passed |
124
+ | `1` | Violations found |
125
+ | `2` | Internal error |
126
+
127
+ ## Design Principles
128
+
129
+ - **Fast** — AST-based analysis, no network calls
130
+ - **Deterministic** — Same input always produces same output
131
+ - **CI-friendly** — Exit codes, no interactive prompts
132
+ - **Zero external dependencies** — Only `ts-morph` for AST parsing
133
+ - **Not a linter** — Focused exclusively on AI-generated code patterns
134
+
135
+ ## License
136
+
137
+ MIT
138
+
139
+ ---
140
+
141
+ Developed by [Promise Inc.](https://promise.codes)
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const config_1 = require("./config");
5
+ const engine_1 = require("./engine");
6
+ const files_1 = require("./utils/files");
7
+ const output_1 = require("./utils/output");
8
+ async function main() {
9
+ const args = process.argv.slice(2);
10
+ const staged = args.includes('--staged');
11
+ const cwd = process.cwd();
12
+ const config = await (0, config_1.loadConfig)(cwd);
13
+ let stagedFiles;
14
+ if (staged) {
15
+ stagedFiles = await (0, files_1.getStagedFiles)();
16
+ if (stagedFiles.length === 0) {
17
+ console.log('\nai-guard: No staged files to analyze.\n');
18
+ process.exit(0);
19
+ }
20
+ }
21
+ const analysis = await (0, engine_1.analyzeProject)(cwd, config, stagedFiles);
22
+ console.log((0, output_1.formatResult)(analysis));
23
+ process.exit(analysis.passed ? 0 : 1);
24
+ }
25
+ main().catch((err) => {
26
+ console.error('ai-guard error:', err);
27
+ process.exit(2);
28
+ });
29
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;;AAEA,qCAAsC;AACtC,qCAA0C;AAC1C,yCAA+C;AAC/C,2CAA8C;AAE9C,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;IACzC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1B,MAAM,MAAM,GAAG,MAAM,IAAA,mBAAU,EAAC,GAAG,CAAC,CAAC;IAErC,IAAI,WAAiC,CAAC;IACtC,IAAI,MAAM,EAAE,CAAC;QACX,WAAW,GAAG,MAAM,IAAA,sBAAc,GAAE,CAAC;QACrC,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;YACzD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,IAAA,uBAAc,EAAC,GAAG,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;IAEhE,OAAO,CAAC,GAAG,CAAC,IAAA,qBAAY,EAAC,QAAQ,CAAC,CAAC,CAAC;IAEpC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACxC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IAC5B,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { AiGuardConfig } from './types';
2
+ export declare function loadConfig(cwd?: string): Promise<AiGuardConfig>;
3
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAkB,MAAM,SAAS,CAAC;AAgDxD,wBAAsB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,OAAO,CAAC,aAAa,CAAC,CAYpF"}
package/dist/config.js ADDED
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadConfig = loadConfig;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const path = __importStar(require("path"));
39
+ const types_1 = require("./types");
40
+ const CONFIG_FILES = [
41
+ 'ai-guard.config.ts',
42
+ 'ai-guard.config.js',
43
+ ];
44
+ async function fileExists(filePath) {
45
+ try {
46
+ await fs.access(filePath);
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ async function loadFromPackageJson(cwd) {
54
+ const pkgPath = path.join(cwd, 'package.json');
55
+ if (!(await fileExists(pkgPath)))
56
+ return null;
57
+ const content = await fs.readFile(pkgPath, 'utf-8');
58
+ const pkg = JSON.parse(content);
59
+ return pkg['ai-guard'] ?? null;
60
+ }
61
+ async function loadFromConfigFile(cwd) {
62
+ for (const configFile of CONFIG_FILES) {
63
+ const configPath = path.join(cwd, configFile);
64
+ if (await fileExists(configPath)) {
65
+ const absolutePath = path.resolve(configPath);
66
+ if (configFile.endsWith('.ts')) {
67
+ try {
68
+ require('ts-node/register/transpile-only');
69
+ }
70
+ catch {
71
+ // ts-node not available — skip .ts config
72
+ continue;
73
+ }
74
+ }
75
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
76
+ const mod = require(absolutePath);
77
+ return mod.default ?? mod;
78
+ }
79
+ }
80
+ return null;
81
+ }
82
+ async function loadConfig(cwd = process.cwd()) {
83
+ const fromFile = await loadFromConfigFile(cwd);
84
+ if (fromFile) {
85
+ return mergeConfig(fromFile);
86
+ }
87
+ const fromPkg = await loadFromPackageJson(cwd);
88
+ if (fromPkg) {
89
+ return mergeConfig(fromPkg);
90
+ }
91
+ return { ...types_1.DEFAULT_CONFIG };
92
+ }
93
+ function mergeConfig(partial) {
94
+ return {
95
+ ...types_1.DEFAULT_CONFIG,
96
+ ...partial,
97
+ aiPatterns: {
98
+ ...types_1.DEFAULT_CONFIG.aiPatterns,
99
+ ...partial.aiPatterns,
100
+ },
101
+ };
102
+ }
103
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,gCAYC;AA9DD,gDAAkC;AAClC,2CAA6B;AAC7B,mCAAwD;AAExD,MAAM,YAAY,GAAG;IACnB,oBAAoB;IACpB,oBAAoB;CACrB,CAAC;AAEF,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,mBAAmB,CAAC,GAAW;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IAC/C,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,OAAO,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAE9C,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IACpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChC,OAAO,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC;AACjC,CAAC;AAED,KAAK,UAAU,kBAAkB,CAAC,GAAW;IAC3C,KAAK,MAAM,UAAU,IAAI,YAAY,EAAE,CAAC;QACtC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;QAC9C,IAAI,MAAM,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YACjC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YAE9C,IAAI,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC/B,IAAI,CAAC;oBACH,OAAO,CAAC,iCAAiC,CAAC,CAAC;gBAC7C,CAAC;gBAAC,MAAM,CAAC;oBACP,0CAA0C;oBAC1C,SAAS;gBACX,CAAC;YACH,CAAC;YAED,iEAAiE;YACjE,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;YAClC,OAAO,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC;QAC5B,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAEM,KAAK,UAAU,UAAU,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAC1D,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,WAAW,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC;IAC/C,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC;IAC9B,CAAC;IAED,OAAO,EAAE,GAAG,sBAAc,EAAE,CAAC;AAC/B,CAAC;AAED,SAAS,WAAW,CAAC,OAA+B;IAClD,OAAO;QACL,GAAG,sBAAc;QACjB,GAAG,OAAO;QACV,UAAU,EAAE;YACV,GAAG,sBAAc,CAAC,UAAU;YAC5B,GAAG,OAAO,CAAC,UAAU;SACtB;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { AiGuardConfig, AnalysisResult } from './types';
2
+ export declare function analyzeProject(cwd: string, config: AiGuardConfig, stagedFiles?: string[]): Promise<AnalysisResult>;
3
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAgB,MAAM,SAAS,CAAC;AAItE,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,aAAa,EACrB,WAAW,CAAC,EAAE,MAAM,EAAE,GACrB,OAAO,CAAC,cAAc,CAAC,CAoCzB"}
package/dist/engine.js ADDED
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.analyzeProject = analyzeProject;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const path = __importStar(require("path"));
39
+ const rules_1 = require("./rules");
40
+ const files_1 = require("./utils/files");
41
+ async function analyzeProject(cwd, config, stagedFiles) {
42
+ const files = stagedFiles ?? await (0, files_1.resolveFiles)(cwd, config);
43
+ const results = [];
44
+ for (const file of files) {
45
+ const absolutePath = path.isAbsolute(file) ? file : path.join(cwd, file);
46
+ try {
47
+ await fs.access(absolutePath);
48
+ }
49
+ catch {
50
+ continue;
51
+ }
52
+ const violations = rules_1.ALL_RULES.flatMap((rule) => {
53
+ try {
54
+ return rule.analyze(absolutePath, config);
55
+ }
56
+ catch {
57
+ return [];
58
+ }
59
+ });
60
+ if (violations.length > 0) {
61
+ results.push({
62
+ filePath: path.relative(cwd, absolutePath),
63
+ violations,
64
+ });
65
+ }
66
+ }
67
+ const totalViolations = results.reduce((sum, r) => sum + r.violations.length, 0);
68
+ return {
69
+ files: results,
70
+ totalViolations,
71
+ passed: totalViolations === 0,
72
+ };
73
+ }
74
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAMA,wCAwCC;AA9CD,gDAAkC;AAClC,2CAA6B;AAE7B,mCAAoC;AACpC,yCAA6C;AAEtC,KAAK,UAAU,cAAc,CAClC,GAAW,EACX,MAAqB,EACrB,WAAsB;IAEtB,MAAM,KAAK,GAAG,WAAW,IAAI,MAAM,IAAA,oBAAY,EAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAmB,EAAE,CAAC;IAEnC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QAEzE,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QAED,MAAM,UAAU,GAAG,iBAAS,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;YAC5C,IAAI,CAAC;gBACH,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;YAC5C,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,EAAE,CAAC;YACZ,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,IAAI,CAAC;gBACX,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAC;gBAC1C,UAAU;aACX,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;IAEjF,OAAO;QACL,KAAK,EAAE,OAAO;QACd,eAAe;QACf,MAAM,EAAE,eAAe,KAAK,CAAC;KAC9B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ export { loadConfig } from './config';
2
+ export { analyzeProject } from './engine';
3
+ export { ALL_RULES } from './rules';
4
+ export type { AiGuardConfig, Violation, FileAnalysis, AnalysisResult, Rule, } from './types';
5
+ export { DEFAULT_CONFIG } from './types';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpC,YAAY,EACV,aAAa,EACb,SAAS,EACT,YAAY,EACZ,cAAc,EACd,IAAI,GACL,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONFIG = exports.ALL_RULES = exports.analyzeProject = exports.loadConfig = void 0;
4
+ var config_1 = require("./config");
5
+ Object.defineProperty(exports, "loadConfig", { enumerable: true, get: function () { return config_1.loadConfig; } });
6
+ var engine_1 = require("./engine");
7
+ Object.defineProperty(exports, "analyzeProject", { enumerable: true, get: function () { return engine_1.analyzeProject; } });
8
+ var rules_1 = require("./rules");
9
+ Object.defineProperty(exports, "ALL_RULES", { enumerable: true, get: function () { return rules_1.ALL_RULES; } });
10
+ var types_1 = require("./types");
11
+ Object.defineProperty(exports, "DEFAULT_CONFIG", { enumerable: true, get: function () { return types_1.DEFAULT_CONFIG; } });
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAAsC;AAA7B,oGAAA,UAAU,OAAA;AACnB,mCAA0C;AAAjC,wGAAA,cAAc,OAAA;AACvB,iCAAoC;AAA3B,kGAAA,SAAS,OAAA;AAQlB,iCAAyC;AAAhC,uGAAA,cAAc,OAAA"}
@@ -0,0 +1,5 @@
1
+ import { AiGuardConfig, Violation } from '../types';
2
+ export declare const name = "ai-patterns";
3
+ export declare const description = "Detects textual patterns commonly found in AI-generated code";
4
+ export declare function analyze(filePath: string, config: AiGuardConfig): Violation[];
5
+ //# sourceMappingURL=ai-patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-patterns.d.ts","sourceRoot":"","sources":["../../src/rules/ai-patterns.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEpD,eAAO,MAAM,IAAI,gBAAgB,CAAC;AAClC,eAAO,MAAM,WAAW,iEAAiE,CAAC;AAkD1F,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,SAAS,EAAE,CAQ5E"}
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.description = exports.name = void 0;
37
+ exports.analyze = analyze;
38
+ const fs = __importStar(require("fs"));
39
+ exports.name = 'ai-patterns';
40
+ exports.description = 'Detects textual patterns commonly found in AI-generated code';
41
+ const CODE_AI_PATTERNS = [
42
+ /\/\/\s*Step\s+\d+/i,
43
+ /\/\/\s*-{3,}/,
44
+ /\/\/\s*={3,}/,
45
+ /\/\/\s*#{3,}/,
46
+ /\/\*\*[\s\S]*?@description\s+This\s+(function|method|class)/i,
47
+ /\/\/\s*Helper\s+(function|method)\s+/i,
48
+ /\/\/\s*Utility\s+(function|method)\s+/i,
49
+ /\/\/\s*Main\s+(function|logic)\s+/i,
50
+ ];
51
+ function findCodePatterns(lines, filePath) {
52
+ const violations = [];
53
+ for (let i = 0; i < lines.length; i++) {
54
+ for (const pattern of CODE_AI_PATTERNS) {
55
+ if (pattern.test(lines[i])) {
56
+ violations.push({
57
+ rule: exports.name,
58
+ file: filePath,
59
+ message: `AI-generated pattern detected: "${lines[i].trim()}"`,
60
+ line: i + 1,
61
+ severity: 'warning',
62
+ });
63
+ break;
64
+ }
65
+ }
66
+ }
67
+ return violations;
68
+ }
69
+ function findVerboseJsDoc(content, filePath) {
70
+ const blocks = content.match(/\/\*\*[\s\S]*?\*\//g) ?? [];
71
+ return blocks
72
+ .filter((block) => {
73
+ const lineCount = block.split('\n').length;
74
+ const hasOnlyBasicTags = /\*\s*@(param|returns|description)\s/.test(block) &&
75
+ !/@(throws|example|deprecated|see|link)/.test(block);
76
+ return lineCount > 8 && hasOnlyBasicTags;
77
+ })
78
+ .map((block) => ({
79
+ rule: exports.name,
80
+ file: filePath,
81
+ message: `Overly verbose JSDoc (${block.split('\n').length} lines)`,
82
+ line: content.substring(0, content.indexOf(block)).split('\n').length,
83
+ severity: 'warning',
84
+ }));
85
+ }
86
+ function analyze(filePath, config) {
87
+ const content = fs.readFileSync(filePath, 'utf-8');
88
+ const lines = content.split('\n');
89
+ return [
90
+ ...findCodePatterns(lines, filePath),
91
+ ...findVerboseJsDoc(content, filePath),
92
+ ];
93
+ }
94
+ //# sourceMappingURL=ai-patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai-patterns.js","sourceRoot":"","sources":["../../src/rules/ai-patterns.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,0BAQC;AA9DD,uCAAyB;AAGZ,QAAA,IAAI,GAAG,aAAa,CAAC;AACrB,QAAA,WAAW,GAAG,8DAA8D,CAAC;AAE1F,MAAM,gBAAgB,GAAG;IACvB,oBAAoB;IACpB,cAAc;IACd,cAAc;IACd,cAAc;IACd,8DAA8D;IAC9D,uCAAuC;IACvC,wCAAwC;IACxC,oCAAoC;CACrC,CAAC;AAEF,SAAS,gBAAgB,CAAC,KAAe,EAAE,QAAgB;IACzD,MAAM,UAAU,GAAgB,EAAE,CAAC;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,KAAK,MAAM,OAAO,IAAI,gBAAgB,EAAE,CAAC;YACvC,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC3B,UAAU,CAAC,IAAI,CAAC;oBACd,IAAI,EAAE,YAAI;oBACV,IAAI,EAAE,QAAQ;oBACd,OAAO,EAAE,mCAAmC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG;oBAC9D,IAAI,EAAE,CAAC,GAAG,CAAC;oBACX,QAAQ,EAAE,SAAS;iBACpB,CAAC,CAAC;gBACH,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,SAAS,gBAAgB,CAAC,OAAe,EAAE,QAAgB;IACzD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,EAAE,CAAC;IAC1D,OAAO,MAAM;SACV,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAChB,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;QAC3C,MAAM,gBAAgB,GAAG,qCAAqC,CAAC,IAAI,CAAC,KAAK,CAAC;YACxE,CAAC,uCAAuC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACvD,OAAO,SAAS,GAAG,CAAC,IAAI,gBAAgB,CAAC;IAC3C,CAAC,CAAC;SACD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACf,IAAI,EAAE,YAAI;QACV,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,yBAAyB,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,SAAS;QACnE,IAAI,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM;QACrE,QAAQ,EAAE,SAAkB;KAC7B,CAAC,CAAC,CAAC;AACR,CAAC;AAED,SAAgB,OAAO,CAAC,QAAgB,EAAE,MAAqB;IAC7D,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAElC,OAAO;QACL,GAAG,gBAAgB,CAAC,KAAK,EAAE,QAAQ,CAAC;QACpC,GAAG,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC;KACvC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { AiGuardConfig, Violation } from '../types';
2
+ export declare const name = "excessive-comments";
3
+ export declare const description = "Detects files with too many comments relative to code";
4
+ export declare function analyze(filePath: string, config: AiGuardConfig): Violation[];
5
+ //# sourceMappingURL=excessive-comments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excessive-comments.d.ts","sourceRoot":"","sources":["../../src/rules/excessive-comments.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEpD,eAAO,MAAM,IAAI,uBAAuB,CAAC;AACzC,eAAO,MAAM,WAAW,0DAA0D,CAAC;AAEnF,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,SAAS,EAAE,CAuC5E"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.description = exports.name = void 0;
4
+ exports.analyze = analyze;
5
+ const ts_morph_1 = require("ts-morph");
6
+ exports.name = 'excessive-comments';
7
+ exports.description = 'Detects files with too many comments relative to code';
8
+ function analyze(filePath, config) {
9
+ const project = new ts_morph_1.Project({ skipAddingFilesFromTsConfig: true });
10
+ const sourceFile = project.addSourceFileAtPath(filePath);
11
+ const fullText = sourceFile.getFullText();
12
+ const lines = fullText.split('\n');
13
+ const totalLines = lines.filter((l) => l.trim().length > 0).length;
14
+ if (totalLines === 0)
15
+ return [];
16
+ let commentLines = 0;
17
+ // Count single-line comments
18
+ const leadingCommentRanges = sourceFile.getDescendantsOfKind(
19
+ // SyntaxKind values for comments aren't directly available via getDescendantsOfKind
20
+ // We'll use regex on the full text instead
21
+ 0);
22
+ void leadingCommentRanges;
23
+ // Regex-based counting (more reliable for comment ratio)
24
+ for (const line of lines) {
25
+ const trimmed = line.trim();
26
+ if (trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*') || trimmed.startsWith('*/')) {
27
+ commentLines++;
28
+ }
29
+ }
30
+ const ratio = commentLines / totalLines;
31
+ if (ratio > config.maxCommentsRatio) {
32
+ return [{
33
+ rule: exports.name,
34
+ file: filePath,
35
+ message: `Excessive comments (${Math.round(ratio * 100)}% — max ${Math.round(config.maxCommentsRatio * 100)}%)`,
36
+ severity: 'error',
37
+ }];
38
+ }
39
+ return [];
40
+ }
41
+ //# sourceMappingURL=excessive-comments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excessive-comments.js","sourceRoot":"","sources":["../../src/rules/excessive-comments.ts"],"names":[],"mappings":";;;AAMA,0BAuCC;AA7CD,uCAAmC;AAGtB,QAAA,IAAI,GAAG,oBAAoB,CAAC;AAC5B,QAAA,WAAW,GAAG,uDAAuD,CAAC;AAEnF,SAAgB,OAAO,CAAC,QAAgB,EAAE,MAAqB;IAC7D,MAAM,OAAO,GAAG,IAAI,kBAAO,CAAC,EAAE,2BAA2B,EAAE,IAAI,EAAE,CAAC,CAAC;IACnE,MAAM,UAAU,GAAG,OAAO,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACzD,MAAM,QAAQ,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;IAEnE,IAAI,UAAU,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEhC,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,6BAA6B;IAC7B,MAAM,oBAAoB,GAAG,UAAU,CAAC,oBAAoB;IAC1D,oFAAoF;IACpF,2CAA2C;IAC3C,CAAC,CACF,CAAC;IACF,KAAK,oBAAoB,CAAC;IAE1B,yDAAyD;IACzD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YAChH,YAAY,EAAE,CAAC;QACjB,CAAC;IACH,CAAC;IAED,MAAM,KAAK,GAAG,YAAY,GAAG,UAAU,CAAC;IAExC,IAAI,KAAK,GAAG,MAAM,CAAC,gBAAgB,EAAE,CAAC;QACpC,OAAO,CAAC;gBACN,IAAI,EAAE,YAAI;gBACV,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,uBAAuB,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,WAAW,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,gBAAgB,GAAG,GAAG,CAAC,IAAI;gBAC/G,QAAQ,EAAE,OAAO;aAClB,CAAC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { AiGuardConfig, Violation } from '../types';
2
+ export declare const name = "generic-names";
3
+ export declare const description = "Detects generic variable/function names typical of AI-generated code";
4
+ export declare function analyze(filePath: string, config: AiGuardConfig): Violation[];
5
+ //# sourceMappingURL=generic-names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generic-names.d.ts","sourceRoot":"","sources":["../../src/rules/generic-names.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEpD,eAAO,MAAM,IAAI,kBAAkB,CAAC;AACpC,eAAO,MAAM,WAAW,yEAAyE,CAAC;AAElG,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,SAAS,EAAE,CA0D5E"}