distyll 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 (243) hide show
  1. package/CONTRIBUTING.md +159 -0
  2. package/POSTMORTEM.json +60 -0
  3. package/README.md +218 -0
  4. package/SETUP.md +79 -0
  5. package/action.yml +37 -0
  6. package/dist/cache.d.ts +26 -0
  7. package/dist/cache.d.ts.map +1 -0
  8. package/dist/cache.js +115 -0
  9. package/dist/cache.js.map +1 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.d.ts.map +1 -0
  12. package/dist/cli.js +153 -0
  13. package/dist/cli.js.map +1 -0
  14. package/dist/commands/ci.d.ts +7 -0
  15. package/dist/commands/ci.d.ts.map +1 -0
  16. package/dist/commands/ci.js +101 -0
  17. package/dist/commands/ci.js.map +1 -0
  18. package/dist/commands/diff.d.ts +10 -0
  19. package/dist/commands/diff.d.ts.map +1 -0
  20. package/dist/commands/diff.js +95 -0
  21. package/dist/commands/diff.js.map +1 -0
  22. package/dist/commands/fingerprint.d.ts +2 -0
  23. package/dist/commands/fingerprint.d.ts.map +1 -0
  24. package/dist/commands/fingerprint.js +77 -0
  25. package/dist/commands/fingerprint.js.map +1 -0
  26. package/dist/commands/hook.d.ts +3 -0
  27. package/dist/commands/hook.d.ts.map +1 -0
  28. package/dist/commands/hook.js +110 -0
  29. package/dist/commands/hook.js.map +1 -0
  30. package/dist/commands/init.d.ts +2 -0
  31. package/dist/commands/init.d.ts.map +1 -0
  32. package/dist/commands/init.js +75 -0
  33. package/dist/commands/init.js.map +1 -0
  34. package/dist/config.d.ts +7 -0
  35. package/dist/config.d.ts.map +1 -0
  36. package/dist/config.js +100 -0
  37. package/dist/config.js.map +1 -0
  38. package/dist/errors.d.ts +30 -0
  39. package/dist/errors.d.ts.map +1 -0
  40. package/dist/errors.js +133 -0
  41. package/dist/errors.js.map +1 -0
  42. package/dist/fingerprint/analyzer.d.ts +3 -0
  43. package/dist/fingerprint/analyzer.d.ts.map +1 -0
  44. package/dist/fingerprint/analyzer.js +230 -0
  45. package/dist/fingerprint/analyzer.js.map +1 -0
  46. package/dist/fingerprint/comparator.d.ts +4 -0
  47. package/dist/fingerprint/comparator.d.ts.map +1 -0
  48. package/dist/fingerprint/comparator.js +78 -0
  49. package/dist/fingerprint/comparator.js.map +1 -0
  50. package/dist/fingerprint/profile.d.ts +5 -0
  51. package/dist/fingerprint/profile.d.ts.map +1 -0
  52. package/dist/fingerprint/profile.js +68 -0
  53. package/dist/fingerprint/profile.js.map +1 -0
  54. package/dist/fixes/index.d.ts +12 -0
  55. package/dist/fixes/index.d.ts.map +1 -0
  56. package/dist/fixes/index.js +42 -0
  57. package/dist/fixes/index.js.map +1 -0
  58. package/dist/fixes/single-use-wrapper.d.ts +8 -0
  59. package/dist/fixes/single-use-wrapper.d.ts.map +1 -0
  60. package/dist/fixes/single-use-wrapper.js +54 -0
  61. package/dist/fixes/single-use-wrapper.js.map +1 -0
  62. package/dist/fixes/unnecessary-try-catch.d.ts +8 -0
  63. package/dist/fixes/unnecessary-try-catch.d.ts.map +1 -0
  64. package/dist/fixes/unnecessary-try-catch.js +37 -0
  65. package/dist/fixes/unnecessary-try-catch.js.map +1 -0
  66. package/dist/fixes/unused-imports.d.ts +7 -0
  67. package/dist/fixes/unused-imports.d.ts.map +1 -0
  68. package/dist/fixes/unused-imports.js +41 -0
  69. package/dist/fixes/unused-imports.js.map +1 -0
  70. package/dist/fixes/verbose-comments.d.ts +7 -0
  71. package/dist/fixes/verbose-comments.d.ts.map +1 -0
  72. package/dist/fixes/verbose-comments.js +29 -0
  73. package/dist/fixes/verbose-comments.js.map +1 -0
  74. package/dist/formatter.d.ts +4 -0
  75. package/dist/formatter.d.ts.map +1 -0
  76. package/dist/formatter.js +72 -0
  77. package/dist/formatter.js.map +1 -0
  78. package/dist/git.d.ts +22 -0
  79. package/dist/git.d.ts.map +1 -0
  80. package/dist/git.js +130 -0
  81. package/dist/git.js.map +1 -0
  82. package/dist/index.d.ts +16 -0
  83. package/dist/index.d.ts.map +1 -0
  84. package/dist/index.js +40 -0
  85. package/dist/index.js.map +1 -0
  86. package/dist/languages/index.d.ts +8 -0
  87. package/dist/languages/index.d.ts.map +1 -0
  88. package/dist/languages/index.js +50 -0
  89. package/dist/languages/index.js.map +1 -0
  90. package/dist/languages/javascript.d.ts +6 -0
  91. package/dist/languages/javascript.d.ts.map +1 -0
  92. package/dist/languages/javascript.js +39 -0
  93. package/dist/languages/javascript.js.map +1 -0
  94. package/dist/languages/python.d.ts +6 -0
  95. package/dist/languages/python.d.ts.map +1 -0
  96. package/dist/languages/python.js +50 -0
  97. package/dist/languages/python.js.map +1 -0
  98. package/dist/parser.d.ts +8 -0
  99. package/dist/parser.d.ts.map +1 -0
  100. package/dist/parser.js +55 -0
  101. package/dist/parser.js.map +1 -0
  102. package/dist/reporters/github.d.ts +4 -0
  103. package/dist/reporters/github.d.ts.map +1 -0
  104. package/dist/reporters/github.js +70 -0
  105. package/dist/reporters/github.js.map +1 -0
  106. package/dist/reporters/terminal.d.ts +4 -0
  107. package/dist/reporters/terminal.d.ts.map +1 -0
  108. package/dist/reporters/terminal.js +59 -0
  109. package/dist/reporters/terminal.js.map +1 -0
  110. package/dist/rules/dead-code-paths.d.ts +3 -0
  111. package/dist/rules/dead-code-paths.d.ts.map +1 -0
  112. package/dist/rules/dead-code-paths.js +57 -0
  113. package/dist/rules/dead-code-paths.js.map +1 -0
  114. package/dist/rules/excessive-comments.d.ts +3 -0
  115. package/dist/rules/excessive-comments.d.ts.map +1 -0
  116. package/dist/rules/excessive-comments.js +86 -0
  117. package/dist/rules/excessive-comments.js.map +1 -0
  118. package/dist/rules/hallucinated-imports.d.ts +3 -0
  119. package/dist/rules/hallucinated-imports.d.ts.map +1 -0
  120. package/dist/rules/hallucinated-imports.js +228 -0
  121. package/dist/rules/hallucinated-imports.js.map +1 -0
  122. package/dist/rules/index.d.ts +4 -0
  123. package/dist/rules/index.d.ts.map +1 -0
  124. package/dist/rules/index.js +34 -0
  125. package/dist/rules/index.js.map +1 -0
  126. package/dist/rules/magic-values.d.ts +3 -0
  127. package/dist/rules/magic-values.d.ts.map +1 -0
  128. package/dist/rules/magic-values.js +168 -0
  129. package/dist/rules/magic-values.js.map +1 -0
  130. package/dist/rules/near-duplicate-functions.d.ts +3 -0
  131. package/dist/rules/near-duplicate-functions.d.ts.map +1 -0
  132. package/dist/rules/near-duplicate-functions.js +78 -0
  133. package/dist/rules/near-duplicate-functions.js.map +1 -0
  134. package/dist/rules/over-defensive-nulls.d.ts +3 -0
  135. package/dist/rules/over-defensive-nulls.d.ts.map +1 -0
  136. package/dist/rules/over-defensive-nulls.js +129 -0
  137. package/dist/rules/over-defensive-nulls.js.map +1 -0
  138. package/dist/rules/redundant-else-return.d.ts +3 -0
  139. package/dist/rules/redundant-else-return.d.ts.map +1 -0
  140. package/dist/rules/redundant-else-return.js +57 -0
  141. package/dist/rules/redundant-else-return.js.map +1 -0
  142. package/dist/rules/single-option-object.d.ts +3 -0
  143. package/dist/rules/single-option-object.d.ts.map +1 -0
  144. package/dist/rules/single-option-object.js +88 -0
  145. package/dist/rules/single-option-object.js.map +1 -0
  146. package/dist/rules/single-use-wrapper.d.ts +3 -0
  147. package/dist/rules/single-use-wrapper.d.ts.map +1 -0
  148. package/dist/rules/single-use-wrapper.js +172 -0
  149. package/dist/rules/single-use-wrapper.js.map +1 -0
  150. package/dist/rules/unnecessary-try-catch.d.ts +3 -0
  151. package/dist/rules/unnecessary-try-catch.d.ts.map +1 -0
  152. package/dist/rules/unnecessary-try-catch.js +116 -0
  153. package/dist/rules/unnecessary-try-catch.js.map +1 -0
  154. package/dist/rules/unused-imports.d.ts +3 -0
  155. package/dist/rules/unused-imports.d.ts.map +1 -0
  156. package/dist/rules/unused-imports.js +103 -0
  157. package/dist/rules/unused-imports.js.map +1 -0
  158. package/dist/rules/verbose-comments.d.ts +3 -0
  159. package/dist/rules/verbose-comments.d.ts.map +1 -0
  160. package/dist/rules/verbose-comments.js +100 -0
  161. package/dist/rules/verbose-comments.js.map +1 -0
  162. package/dist/scanner.d.ts +11 -0
  163. package/dist/scanner.d.ts.map +1 -0
  164. package/dist/scanner.js +196 -0
  165. package/dist/scanner.js.map +1 -0
  166. package/dist/scorer.d.ts +3 -0
  167. package/dist/scorer.d.ts.map +1 -0
  168. package/dist/scorer.js +23 -0
  169. package/dist/scorer.js.map +1 -0
  170. package/dist/types.d.ts +62 -0
  171. package/dist/types.d.ts.map +1 -0
  172. package/dist/types.js +3 -0
  173. package/dist/types.js.map +1 -0
  174. package/hn_post.md +13 -0
  175. package/marketing/COMPETITIVE_ANALYSIS.md +62 -0
  176. package/marketing/EMAIL_ANNOUNCEMENT.md +91 -0
  177. package/marketing/LANDING_PAGE_COPY.md +123 -0
  178. package/marketing/LAUNCH_POST.md +68 -0
  179. package/marketing/PRODUCT_HUNT.md +39 -0
  180. package/marketing/TWITTER_THREAD.md +70 -0
  181. package/package.json +44 -0
  182. package/producthunt.md +52 -0
  183. package/reddit_post.md +39 -0
  184. package/site/favicon.svg +10 -0
  185. package/site/index.html +281 -0
  186. package/site/script.js +82 -0
  187. package/site/style.css +516 -0
  188. package/src/cache.ts +114 -0
  189. package/src/cli.ts +169 -0
  190. package/src/commands/ci.ts +111 -0
  191. package/src/commands/diff.ts +108 -0
  192. package/src/commands/fingerprint.ts +47 -0
  193. package/src/commands/hook.ts +85 -0
  194. package/src/commands/init.ts +42 -0
  195. package/src/config.ts +75 -0
  196. package/src/errors.ts +105 -0
  197. package/src/fingerprint/analyzer.ts +214 -0
  198. package/src/fingerprint/comparator.ts +93 -0
  199. package/src/fingerprint/profile.ts +32 -0
  200. package/src/fixes/index.ts +58 -0
  201. package/src/fixes/single-use-wrapper.ts +60 -0
  202. package/src/fixes/unnecessary-try-catch.ts +43 -0
  203. package/src/fixes/unused-imports.ts +53 -0
  204. package/src/fixes/verbose-comments.ts +35 -0
  205. package/src/formatter.ts +79 -0
  206. package/src/git.ts +115 -0
  207. package/src/index.ts +15 -0
  208. package/src/languages/index.ts +50 -0
  209. package/src/languages/javascript.ts +36 -0
  210. package/src/languages/python.ts +47 -0
  211. package/src/parser.ts +52 -0
  212. package/src/reporters/github.ts +75 -0
  213. package/src/reporters/terminal.ts +67 -0
  214. package/src/rules/dead-code-paths.ts +62 -0
  215. package/src/rules/excessive-comments.ts +94 -0
  216. package/src/rules/hallucinated-imports.ts +195 -0
  217. package/src/rules/index.ts +32 -0
  218. package/src/rules/magic-values.ts +167 -0
  219. package/src/rules/near-duplicate-functions.ts +89 -0
  220. package/src/rules/over-defensive-nulls.ts +137 -0
  221. package/src/rules/redundant-else-return.ts +61 -0
  222. package/src/rules/single-option-object.ts +97 -0
  223. package/src/rules/single-use-wrapper.ts +184 -0
  224. package/src/rules/unnecessary-try-catch.ts +121 -0
  225. package/src/rules/unused-imports.ts +115 -0
  226. package/src/rules/verbose-comments.ts +105 -0
  227. package/src/scanner.ts +184 -0
  228. package/src/scorer.ts +26 -0
  229. package/src/types.ts +70 -0
  230. package/tests/commands/diff.test.ts +107 -0
  231. package/tests/config.test.ts +69 -0
  232. package/tests/e2e.test.ts +163 -0
  233. package/tests/edge-cases.test.ts +167 -0
  234. package/tests/fingerprint/analyzer.test.ts +131 -0
  235. package/tests/fixes/unnecessary-try-catch.test.ts +62 -0
  236. package/tests/git.test.ts +79 -0
  237. package/tests/rules/hallucinated-imports.test.ts +59 -0
  238. package/tests/rules/near-duplicate-functions.test.ts +90 -0
  239. package/tests/rules/unnecessary-try-catch.test.ts +81 -0
  240. package/tests/scanner.test.ts +88 -0
  241. package/tsconfig.json +20 -0
  242. package/twitter_thread.md +46 -0
  243. package/vitest.config.ts +7 -0
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { nearDuplicateFunctions } from '../../src/rules/near-duplicate-functions';
3
+ import { parse } from '../../src/parser';
4
+
5
+ function checkJS(code: string) {
6
+ const tree = parse(code, 'javascript');
7
+ return nearDuplicateFunctions.check(tree, code, 'test.js');
8
+ }
9
+
10
+ function checkPython(code: string) {
11
+ const tree = parse(code, 'python');
12
+ return nearDuplicateFunctions.check(tree, code, 'test.py');
13
+ }
14
+
15
+ describe('near-duplicate-functions', () => {
16
+ it('flags near-duplicate JS functions', () => {
17
+ const code = `
18
+ function processUserData(user) {
19
+ const result = {};
20
+ result.name = user.name.trim().toLowerCase();
21
+ result.email = user.email.trim().toLowerCase();
22
+ result.age = parseInt(user.age, 10);
23
+ result.active = Boolean(user.active);
24
+ return result;
25
+ }
26
+
27
+ function processAdminData(admin) {
28
+ const result = {};
29
+ result.name = admin.name.trim().toLowerCase();
30
+ result.email = admin.email.trim().toLowerCase();
31
+ result.age = parseInt(admin.age, 10);
32
+ result.active = Boolean(admin.active);
33
+ return result;
34
+ }
35
+ `;
36
+ const findings = checkJS(code);
37
+ expect(findings.length).toBeGreaterThan(0);
38
+ expect(findings[0].message).toContain('similar');
39
+ });
40
+
41
+ it('does NOT flag functions with different logic', () => {
42
+ const code = `
43
+ function add(a, b) {
44
+ const result = a + b;
45
+ console.log('Adding:', a, b);
46
+ return result;
47
+ }
48
+
49
+ function multiply(a, b) {
50
+ const result = a * b;
51
+ console.log('Multiplying:', a, b);
52
+ return Math.floor(result);
53
+ }
54
+ `;
55
+ const findings = checkJS(code);
56
+ expect(findings).toHaveLength(0);
57
+ });
58
+
59
+ it('ignores short functions', () => {
60
+ const code = `
61
+ function a() { return 1; }
62
+ function b() { return 1; }
63
+ `;
64
+ const findings = checkJS(code);
65
+ expect(findings).toHaveLength(0);
66
+ });
67
+
68
+ it('flags near-duplicate Python functions', () => {
69
+ const code = `
70
+ def process_user_data(user):
71
+ result = {}
72
+ result['name'] = user['name'].strip().lower()
73
+ result['email'] = user['email'].strip().lower()
74
+ result['age'] = int(user['age'])
75
+ result['active'] = bool(user['active'])
76
+ return result
77
+
78
+ def process_admin_data(admin):
79
+ result = {}
80
+ result['name'] = admin['name'].strip().lower()
81
+ result['email'] = admin['email'].strip().lower()
82
+ result['age'] = int(admin['age'])
83
+ result['active'] = bool(admin['active'])
84
+ return result
85
+ `;
86
+ const findings = checkPython(code);
87
+ expect(findings.length).toBeGreaterThan(0);
88
+ expect(findings[0].message).toContain('similar');
89
+ });
90
+ });
@@ -0,0 +1,81 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { unnecessaryTryCatch } from '../../src/rules/unnecessary-try-catch';
3
+ import { parse } from '../../src/parser';
4
+
5
+ function check(code: string, lang: 'javascript' | 'typescript' = 'javascript') {
6
+ const tree = parse(code, lang);
7
+ return unnecessaryTryCatch.check(tree, code, 'test.js');
8
+ }
9
+
10
+ describe('unnecessary-try-catch', () => {
11
+ it('flags try-catch that only rethrows', () => {
12
+ const findings = check(`
13
+ try {
14
+ doSomething();
15
+ } catch (e) {
16
+ throw e;
17
+ }
18
+ `);
19
+ expect(findings).toHaveLength(1);
20
+ expect(findings[0].message).toContain('rethrow');
21
+ });
22
+
23
+ it('flags try-catch around pure assignments', () => {
24
+ const findings = check(`
25
+ try {
26
+ const x = 1;
27
+ const y = 2;
28
+ } catch (e) {
29
+ console.log(e);
30
+ }
31
+ `);
32
+ expect(findings).toHaveLength(1);
33
+ expect(findings[0].message).toContain('unlikely to throw');
34
+ });
35
+
36
+ it('does NOT flag try-catch around function calls', () => {
37
+ const findings = check(`
38
+ try {
39
+ const data = JSON.parse(input);
40
+ } catch (e) {
41
+ console.log('Parse error:', e);
42
+ }
43
+ `);
44
+ expect(findings).toHaveLength(0);
45
+ });
46
+
47
+ it('does NOT flag try-catch around await', () => {
48
+ const findings = check(`
49
+ async function load() {
50
+ try {
51
+ const data = await fetchData();
52
+ } catch (e) {
53
+ handleError(e);
54
+ }
55
+ }
56
+ `);
57
+ expect(findings).toHaveLength(0);
58
+ });
59
+
60
+ it('does NOT flag try-catch around property access', () => {
61
+ const findings = check(`
62
+ try {
63
+ const name = obj.deeply.nested.property;
64
+ } catch (e) {
65
+ console.log('Access error:', e);
66
+ }
67
+ `);
68
+ expect(findings).toHaveLength(0);
69
+ });
70
+
71
+ it('does NOT flag try-catch that contains throw', () => {
72
+ const findings = check(`
73
+ try {
74
+ if (!valid) throw new Error('invalid');
75
+ } catch (e) {
76
+ log(e);
77
+ }
78
+ `);
79
+ expect(findings).toHaveLength(0);
80
+ });
81
+ });
@@ -0,0 +1,88 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ import { scanPaths, scanFile } from '../src/scanner';
6
+
7
+ function createTempFile(content: string, ext: string = '.js'): string {
8
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'distyll-test-'));
9
+ const filePath = path.join(dir, `test${ext}`);
10
+ fs.writeFileSync(filePath, content);
11
+ return filePath;
12
+ }
13
+
14
+ describe('scanFile', () => {
15
+ it('returns null for unsupported file types', () => {
16
+ const filePath = createTempFile('hello world', '.txt');
17
+ expect(scanFile(filePath)).toBeNull();
18
+ });
19
+
20
+ it('returns null for empty files', () => {
21
+ const filePath = createTempFile('', '.js');
22
+ expect(scanFile(filePath)).toBeNull();
23
+ });
24
+
25
+ it('scans a clean JS file with no findings', () => {
26
+ const filePath = createTempFile(`
27
+ function add(a, b) {
28
+ return a + b;
29
+ }
30
+
31
+ const result = add(1, 2);
32
+ console.log(result);
33
+ `, '.js');
34
+ const result = scanFile(filePath);
35
+ expect(result).not.toBeNull();
36
+ expect(result!.findings).toHaveLength(0);
37
+ });
38
+
39
+ it('detects a single-use wrapper function', () => {
40
+ const filePath = createTempFile(`
41
+ function wrapper(x) {
42
+ return doThing(x);
43
+ }
44
+ `, '.js');
45
+ const result = scanFile(filePath);
46
+ expect(result).not.toBeNull();
47
+ const wrapperFindings = result!.findings.filter(f => f.rule === 'single-use-wrapper');
48
+ expect(wrapperFindings.length).toBeGreaterThan(0);
49
+ });
50
+
51
+ it('scans TypeScript files', () => {
52
+ const filePath = createTempFile(`
53
+ interface Options {
54
+ name: string;
55
+ }
56
+
57
+ function greet(options: Options): string {
58
+ return 'Hello ' + options.name;
59
+ }
60
+ `, '.ts');
61
+ const result = scanFile(filePath);
62
+ expect(result).not.toBeNull();
63
+ expect(result!.loc).toBeGreaterThan(0);
64
+ });
65
+ });
66
+
67
+ describe('scanPaths', () => {
68
+ it('scans a directory of files', async () => {
69
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'distyll-test-'));
70
+ fs.writeFileSync(path.join(dir, 'a.js'), 'const x = 1;\n');
71
+ fs.writeFileSync(path.join(dir, 'b.ts'), 'const y: number = 2;\n');
72
+ fs.writeFileSync(path.join(dir, 'c.txt'), 'not scanned');
73
+
74
+ const summary = await scanPaths([dir]);
75
+ expect(summary.results.length).toBe(2);
76
+ expect(summary.score).toBeGreaterThanOrEqual(0);
77
+ });
78
+
79
+ it('returns score of 0 for clean code', async () => {
80
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'distyll-test-'));
81
+ fs.writeFileSync(
82
+ path.join(dir, 'clean.js'),
83
+ `function add(a, b) {\n return a + b;\n}\n`
84
+ );
85
+ const summary = await scanPaths([dir]);
86
+ expect(summary.score).toBe(0);
87
+ });
88
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "commonjs",
5
+ "moduleResolution": "node",
6
+ "lib": ["ES2022"],
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist", "tests", "site"]
20
+ }
@@ -0,0 +1,46 @@
1
+ 1/ AI coding tools write code that "works" but is quietly bloating your codebase.
2
+
3
+ Unnecessary try-catch blocks. Single-use abstractions. Comments restating the obvious. Imports that go nowhere.
4
+
5
+ I built a tool to catch it automatically.
6
+
7
+ 2/ The problem: linters check syntax. Formatters check style. But NOTHING catches the structural anti-patterns unique to AI-generated code.
8
+
9
+ Your codebase is accumulating "slop" and code review isn't catching it fast enough.
10
+
11
+ 3/ Meet Distyll — a CLI that scans JS/TS/Python for 12 AI code anti-patterns using AST analysis (tree-sitter).
12
+
13
+ No LLM needed. Runs in seconds. Assigns a slop score from 0-100 with specific fix suggestions.
14
+
15
+ 4/ What it catches:
16
+
17
+ - Try-catch wrapping pure synchronous functions
18
+ - Single-use wrapper abstractions
19
+ - Verbose comments restating code
20
+ - Hallucinated/unused imports
21
+ - Near-duplicate functions (>85% similarity)
22
+ - Dead code paths
23
+ - Over-engineered options objects
24
+
25
+ 5/ Multiple ways to run it:
26
+
27
+ `distyll scan .` — full project scan
28
+ `distyll diff` — only changed lines
29
+ `distyll hook install` — pre-commit gate
30
+ `distyll ci` — GitHub Actions with annotations
31
+
32
+ 6/ The `fingerprint` command profiles YOUR codebase's patterns — naming conventions, abstraction style, comment density.
33
+
34
+ Then it flags AI-generated code that doesn't match your team's conventions. Not generic rules. YOUR rules.
35
+
36
+ 7/ I ran it on my own repos that used Cursor and Claude Code heavily.
37
+
38
+ Found dozens of pointless abstractions and defensive code blocks I'd approved in review without thinking twice.
39
+
40
+ The "comfortable drift" is real.
41
+
42
+ 8/ Distyll is open source and free to use.
43
+
44
+ [GITHUB_URL]
45
+
46
+ Try it on your AI-assisted codebase. The slop score might surprise you.
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['tests/**/*.test.ts'],
6
+ },
7
+ });