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,159 @@
1
+ # Contributing to Distyll
2
+
3
+ ## Development Setup
4
+
5
+ ```bash
6
+ # Clone the repo
7
+ git clone https://github.com/your-org/distyll.git
8
+ cd distyll
9
+
10
+ # Install dependencies
11
+ npm install
12
+
13
+ # Build
14
+ npm run build
15
+
16
+ # Run tests
17
+ npm test
18
+
19
+ # Watch mode for development
20
+ npm run dev # TypeScript watch
21
+ npm run test:watch # Vitest watch
22
+ ```
23
+
24
+ ## Project Structure
25
+
26
+ ```
27
+ src/
28
+ cli.ts # CLI entry point (commander)
29
+ scanner.ts # File scanning orchestration
30
+ parser.ts # Tree-sitter AST parsing
31
+ scorer.ts # Slop score calculation
32
+ config.ts # .distyll.json config loading
33
+ formatter.ts # Text and JSON output formatting
34
+ errors.ts # Centralized error handling
35
+ types.ts # Shared TypeScript types
36
+ cache.ts # Score history caching
37
+ git.ts # Git diff integration
38
+ commands/ # CLI subcommands (scan, diff, hook, ci, init, fingerprint)
39
+ rules/ # Detection rules (one file per rule)
40
+ fixes/ # Fix suggestion generators
41
+ fingerprint/ # Style profile analysis
42
+ languages/ # Language-specific tree-sitter configs
43
+ reporters/ # Output formatters (terminal, GitHub)
44
+ tests/
45
+ *.test.ts # Test files matching src/ structure
46
+ ```
47
+
48
+ ## Adding a New Rule
49
+
50
+ 1. Create `src/rules/your-rule-name.ts`:
51
+
52
+ ```typescript
53
+ import type { Rule, Finding } from '../types';
54
+ import { findNodes } from '../parser';
55
+
56
+ export const yourRuleName: Rule = {
57
+ name: 'your-rule-name',
58
+ description: 'What this rule detects',
59
+ severity: 'warning',
60
+ check(tree, source, filePath): Finding[] {
61
+ const findings: Finding[] = [];
62
+
63
+ // Use tree-sitter AST to find patterns
64
+ const nodes = findNodes(tree.rootNode, 'some_node_type');
65
+
66
+ for (const node of nodes) {
67
+ // Your detection logic here
68
+ // Be conservative — only flag clear AI slop, not legitimate code
69
+ findings.push({
70
+ file: filePath,
71
+ line: node.startPosition.row + 1,
72
+ column: node.startPosition.column + 1,
73
+ endLine: node.endPosition.row + 1,
74
+ endColumn: node.endPosition.column + 1,
75
+ rule: 'your-rule-name',
76
+ severity: 'warning',
77
+ message: 'Clear, actionable description of the issue',
78
+ });
79
+ }
80
+
81
+ return findings;
82
+ },
83
+ };
84
+ ```
85
+
86
+ 2. Register the rule in `src/rules/index.ts`:
87
+
88
+ ```typescript
89
+ import { yourRuleName } from './your-rule-name';
90
+
91
+ export const allRules: Rule[] = [
92
+ // ... existing rules
93
+ yourRuleName,
94
+ ];
95
+ ```
96
+
97
+ 3. Write tests in `tests/rules/your-rule-name.test.ts`:
98
+
99
+ ```typescript
100
+ import { describe, it, expect } from 'vitest';
101
+ import { scanFile } from '../../src/scanner';
102
+ import * as fs from 'fs';
103
+ import * as path from 'path';
104
+ import * as os from 'os';
105
+
106
+ function createTempFile(content: string, ext = '.js'): string {
107
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'distyll-test-'));
108
+ const filePath = path.join(dir, `test${ext}`);
109
+ fs.writeFileSync(filePath, content);
110
+ return filePath;
111
+ }
112
+
113
+ describe('your-rule-name', () => {
114
+ it('flags the slop pattern', () => {
115
+ const file = createTempFile('/* code that triggers rule */');
116
+ const result = scanFile(file);
117
+ const findings = result!.findings.filter(f => f.rule === 'your-rule-name');
118
+ expect(findings.length).toBeGreaterThan(0);
119
+ });
120
+
121
+ it('does not flag legitimate code', () => {
122
+ const file = createTempFile('/* clean code */');
123
+ const result = scanFile(file);
124
+ const findings = result!.findings.filter(f => f.rule === 'your-rule-name');
125
+ expect(findings).toHaveLength(0);
126
+ });
127
+ });
128
+ ```
129
+
130
+ 4. Run `npm test` to verify.
131
+
132
+ ## Guidelines
133
+
134
+ ### False Positives Are Bugs
135
+
136
+ The #1 priority is avoiding false positives. Every rule should be conservative — it's better to miss some real slop than to flag legitimate code. Test with real-world codebases before submitting.
137
+
138
+ ### Rule Quality Checklist
139
+
140
+ - [ ] Only flags patterns that are clearly AI-generated slop
141
+ - [ ] Does not trigger on well-known idioms or framework patterns
142
+ - [ ] Has at least one test for a true positive and one for a true negative
143
+ - [ ] Includes a clear, actionable message explaining what's wrong
144
+ - [ ] Works for all supported languages where the pattern applies
145
+
146
+ ### Code Style
147
+
148
+ - TypeScript strict mode
149
+ - No `any` types except for tree-sitter grammar imports
150
+ - Prefer `const` over `let`
151
+ - Use early returns over nested conditionals
152
+
153
+ ### Testing
154
+
155
+ ```bash
156
+ npm test # Run all tests
157
+ npm run test:watch # Watch mode
158
+ npx vitest run tests/rules/your-rule.test.ts # Single file
159
+ ```
@@ -0,0 +1,60 @@
1
+ {
2
+ "weakest_link": {
3
+ "what": "Fix suggestions are only implemented for 1 of 12 rules (unnecessary-try-catch has a real fix; single-use-wrapper works but is simple; verbose-comments and unused-imports fix files exist but are stubs). The landing page and product idea promise 'Fix suggestions with one-click apply' as a key differentiator over free OSS tools, but 10 of 12 rules produce no fix.",
4
+ "where": "src/fixes/ directory \u2014 only unnecessary-try-catch.ts and single-use-wrapper.ts have real implementations; verbose-comments.ts and unused-imports.ts are stub files",
5
+ "impact": "Fix suggestions are positioned as THE paid-tier differentiator over free alternatives (AI-SLOP-Detector, KarpeSlop). Without them, the free tier and the pro tier deliver nearly identical value. Users who see 'Fix: ...' on one finding but not the other 11 will feel the tool is incomplete.",
6
+ "fix": "Implement fix generators for the 4 highest-signal rules: unused-imports (remove the import line), redundant-else-return (remove the else wrapper), verbose-comments (delete the comment), and dead-code-paths (remove unreachable lines). These are all straightforward AST-to-text transforms."
7
+ },
8
+ "one_and_done_risk": "The slop score of 100/100 on a single trivial wrapper function is immediately discrediting. A user runs `distyll scan` on a real project for the first time and sees scores of 60-80 on codebases they consider clean \u2014 because the scoring formula (weighted sum / LOC * 100) is hypersensitive on small files and doesn't calibrate against project size. If the first scan produces a number that feels wrong, the user stops trusting the tool entirely. The score needs to feel earned, not alarmist. Additionally, the core value proposition IS delivered \u2014 AST-based detection works, findings are real, and the rules are conservative \u2014 but the numeric score undermines credibility before users discover the quality underneath.",
9
+ "hn_skeptic_comment": "This is just ESLint with a marketing rebrand. Half of these 'AI slop patterns' are things ESLint already catches (unused imports, dead code paths, redundant else). The 'hallucinated imports' rule is just checking if a module exists \u2014 TypeScript's compiler already does this. The 'slop score' is a vanity metric with no calibration data \u2014 what does 42 mean? Is that good? Compared to what? And the 'style fingerprinting' is just computing averages of function length and naming conventions, which SonarQube has done for a decade. The only genuinely novel rule here is 'verbose comments restating code,' and even that could be an ESLint plugin in 50 lines. At $9/user/month you're charging for what should be a free ESLint config. Also, the 'Pro' tier promises LLM-powered features but they're not implemented \u2014 it's just heuristic analysis all the way down. Ship the ESLint plugin for free, get adoption, then maybe charge for the dashboard.",
10
+ "most_likely_failure_mode": {
11
+ "mode": "retention",
12
+ "explanation": "Users install it, run a scan, see findings, maybe fix a few things \u2014 then never run it again because it doesn't integrate into their existing workflow seamlessly enough. The pre-commit hook is opt-in, the CI integration requires manual setup, and the slop score doesn't connect to any feedback loop (no PR comments, no Slack notifications, no dashboard showing trends over time in the free tier). The tool produces output but doesn't create a habit. Code quality tools only stick when they're invisible and automatic \u2014 ESLint succeeds because it runs in your editor on every keystroke, not because you remember to invoke it.",
13
+ "mitigation": "Ship a VS Code extension that shows inline warnings as you type (like ESLint). Make the GitHub Action dead simple (one-line workflow file). Add a `distyll watch` mode for local development. The goal is zero-friction continuous feedback, not periodic scanning."
14
+ },
15
+ "two_hour_fix_list": [
16
+ {
17
+ "priority": 1,
18
+ "file": "src/scorer.ts",
19
+ "change": "Recalibrate the scoring formula. Currently `min(100, round(weightedSum / totalLOC * 100))` produces 100 on a 1-line file with 1 finding. Add a minimum LOC floor (e.g., max(totalLOC, 50)) and apply a logarithmic curve so scores feel proportional. Test against 3-4 real open-source repos to ensure scores land in a believable 10-60 range for typical codebases.",
20
+ "why": "The first number users see IS the product. A score of 100 on a trivial file destroys trust instantly. Calibrated scores that feel fair make users share their results ('we got our slop score down to 23!').",
21
+ "time": "30min"
22
+ },
23
+ {
24
+ "priority": 2,
25
+ "file": "src/fixes/unused-imports.ts, src/fixes/verbose-comments.ts",
26
+ "change": "Implement the two easiest fix generators: unused-imports (delete the import line from source) and verbose-comments (delete the comment line). Both are simple string operations given the line number from the finding.",
27
+ "why": "Fix suggestions are the #1 differentiator over free tools. Even 4/12 rules having fixes is better than 2/12.",
28
+ "time": "25min"
29
+ },
30
+ {
31
+ "priority": 3,
32
+ "file": "site/index.html",
33
+ "change": "Replace the hardcoded GitHub link (https://github.com/distyll-dev/distyll) with a real repo URL or a '#' placeholder. Add actual terminal demo output to the animated terminal instead of relying solely on JS animation (provide a static fallback). The terminal is empty if JS fails.",
34
+ "why": "A broken GitHub link and an empty terminal widget on the landing page signal 'vaporware' to the exact skeptical developer audience you're targeting.",
35
+ "time": "15min"
36
+ },
37
+ {
38
+ "priority": 4,
39
+ "file": "README.md",
40
+ "change": "Add a 'Sample Output' section showing real terminal output from running distyll on a known-sloppy file. Show the before (sloppy code) and after (distyll output with findings and score). This is the single most convincing thing a README can contain for a CLI tool.",
41
+ "why": "Developers evaluate CLI tools by reading the README on npm/GitHub. If they can't see what the output looks like in 5 seconds, they move on.",
42
+ "time": "15min"
43
+ },
44
+ {
45
+ "priority": 5,
46
+ "file": "package.json",
47
+ "change": "Verify `npm pack` and `npx distyll` work end-to-end. The bin entry points to dist/cli.js but tree-sitter native bindings may fail on install for users without node-gyp/build tools. Consider adding a postinstall check or documenting the native dependency requirement prominently.",
48
+ "why": "If `npx distyll scan .` fails on first try due to native build errors, you lose 80% of potential users at the install step. Tree-sitter's native bindings are the #1 adoption barrier for this tool.",
49
+ "time": "20min"
50
+ }
51
+ ],
52
+ "scores": {
53
+ "first_impression": 7,
54
+ "core_functionality": 7,
55
+ "code_quality": 8,
56
+ "production_readiness": 5,
57
+ "market_fit": 6
58
+ },
59
+ "overall_assessment": "Distyll is a genuinely well-engineered prototype \u2014 the tree-sitter AST analysis is real, the 12 rules are thoughtfully conservative, 85 tests pass, and the CLI works end-to-end. The code quality is notably high for a prototype. But it ships with two credibility-killing issues: a scoring formula that produces absurd numbers on real code, and fix suggestions that are mostly unimplemented despite being the headline differentiator. I would use this on a side project to audit AI-generated PRs, but I wouldn't pay $9/month for it \u2014 ESLint + a careful code review gets me 70% of the way there for free. The path to a real product is: nail the scoring calibration so the number means something, finish the fix suggestions, and ship a GitHub Action that 'just works' in one line."
60
+ }
package/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # Distyll
2
+
3
+ Catch AI-generated code slop before it ships — a quality gate for the vibe-coding era.
4
+
5
+ Distyll analyzes pull requests for AI-generated code anti-patterns and assigns a **slop score** (0-100) with specific, actionable fixes. It uses AST-based pattern detection via tree-sitter to catch structural issues that traditional linters miss.
6
+
7
+ ## Quick Start
8
+
9
+ ```bash
10
+ # Install globally
11
+ npm install -g distyll
12
+
13
+ # Scan a project
14
+ distyll scan .
15
+
16
+ # Or use npx without installing
17
+ npx distyll scan .
18
+ ```
19
+
20
+ ## What It Detects
21
+
22
+ Distyll ships with 12 detection rules targeting patterns unique to AI-generated code:
23
+
24
+ | Rule | What It Catches |
25
+ |------|----------------|
26
+ | `unnecessary-try-catch` | Try-catch blocks wrapping synchronous pure functions that cannot throw |
27
+ | `single-use-wrapper` | Functions that wrap a single call and are only used once — pointless abstraction |
28
+ | `verbose-comments` | Comments that restate the code verbatim (e.g., `// increment counter` above `counter++`) |
29
+ | `unused-imports` | Import statements for modules/symbols never referenced in the file |
30
+ | `single-option-object` | Options/config objects that only ever use one property — over-engineering |
31
+ | `redundant-else-return` | `else` blocks after a `return` statement — the else is unnecessary |
32
+ | `hallucinated-imports` | Imports of modules that don't exist in the project or standard library |
33
+ | `near-duplicate-functions` | Function bodies that are >85% similar — copy-paste with subtle variations |
34
+ | `over-defensive-nulls` | Null/undefined checks on values that are already guaranteed non-null |
35
+ | `magic-values` | Magic strings and numbers used inline instead of named constants |
36
+ | `dead-code-paths` | Unreachable code after unconditional return/throw/break/continue |
37
+ | `excessive-comments` | Files where >50% of lines have trailing comments — a hallmark of AI verbosity |
38
+
39
+ Every rule is **conservative by design**. The #1 complaint about existing slop detectors is false positives. Distyll only flags patterns that are clearly AI-generated bloat, not legitimate code.
40
+
41
+ ## CLI Reference
42
+
43
+ ### `distyll scan <paths...>`
44
+
45
+ Scan files or directories for slop patterns.
46
+
47
+ ```bash
48
+ distyll scan . # Scan current directory
49
+ distyll scan src/ lib/ # Scan specific directories
50
+ distyll scan src/utils.ts # Scan a single file
51
+ distyll scan . --format json # JSON output
52
+ distyll scan . --threshold 50 # Exit code 1 if score > 50
53
+ distyll scan . --style # Compare against style profile
54
+ distyll scan . --verbose # Show parsing details per file
55
+ distyll scan . --quiet # Output only the score number
56
+ ```
57
+
58
+ **Options:**
59
+ - `-f, --format <format>` — Output format: `text` (default) or `json`
60
+ - `-t, --threshold <number>` — Exit with code 1 if slop score exceeds this value
61
+ - `-s, --style` — Compare against project style profile (run `distyll fingerprint` first)
62
+ - `-v, --verbose` — Show AST parsing details and rule execution time per file
63
+ - `-q, --quiet` — Only output the slop score number (useful for scripting)
64
+
65
+ ### `distyll diff`
66
+
67
+ Scan only changed lines in a git diff. Only reports findings on new/modified code.
68
+
69
+ ```bash
70
+ distyll diff # Scan unstaged changes
71
+ distyll diff --staged # Scan staged changes only
72
+ distyll diff --ref main # Diff against a branch
73
+ distyll diff --ref HEAD~3 # Diff against 3 commits ago
74
+ ```
75
+
76
+ **Options:**
77
+ - `-s, --staged` — Scan staged changes only
78
+ - `-r, --ref <ref>` — Git ref to diff against
79
+ - `-f, --format <format>` — Output format: `text` or `json`
80
+ - `-t, --threshold <number>` — Exit with code 1 if score exceeds threshold
81
+ - `-v, --verbose` — Show detailed info
82
+ - `-q, --quiet` — Output only the score number
83
+
84
+ ### `distyll hook install`
85
+
86
+ Install a pre-commit hook that blocks commits above a slop threshold.
87
+
88
+ ```bash
89
+ distyll hook install # Default threshold: 70
90
+ distyll hook install --threshold 50 # Custom threshold
91
+ distyll hook uninstall # Remove the hook
92
+ ```
93
+
94
+ ### `distyll ci`
95
+
96
+ Run in CI mode with GitHub Actions-optimized output.
97
+
98
+ ```bash
99
+ distyll ci # Annotations format (default)
100
+ distyll ci --format summary # Markdown summary
101
+ distyll ci --format json # JSON output
102
+ distyll ci --threshold 60 # Fail CI if score > 60
103
+ distyll ci --ref main # Diff against main branch
104
+ ```
105
+
106
+ ### `distyll fingerprint [dir]`
107
+
108
+ Analyze your codebase to build a style profile. This captures your team's actual patterns (function length, naming conventions, comment density, etc.) so `--style` mode can flag deviations.
109
+
110
+ ```bash
111
+ distyll fingerprint # Analyze current directory
112
+ distyll fingerprint ./src # Analyze specific directory
113
+ ```
114
+
115
+ The profile is stored in `.distyll/profile.json`.
116
+
117
+ ### `distyll init [dir]`
118
+
119
+ Initialize Distyll in a project. Creates `.distyll.json` config and `.distyll/` cache directory.
120
+
121
+ ```bash
122
+ distyll init # Initialize in current directory
123
+ distyll init ./my-project # Initialize in specific directory
124
+ ```
125
+
126
+ ## Configuration
127
+
128
+ Create a `.distyll.json` file in your project root (or run `distyll init`):
129
+
130
+ ```json
131
+ {
132
+ "rules": {
133
+ "unnecessary-try-catch": "error",
134
+ "verbose-comments": "warn",
135
+ "magic-values": "off"
136
+ },
137
+ "threshold": 70,
138
+ "ignore": [
139
+ "**/generated/**",
140
+ "**/migrations/**"
141
+ ]
142
+ }
143
+ ```
144
+
145
+ ### Config Options
146
+
147
+ - **`rules`** — Override severity or disable rules. Values: `"off"`, `"warn"`, `"error"`
148
+ - **`threshold`** — Default slop score threshold for hooks and CI
149
+ - **`ignore`** — Additional glob patterns to exclude from scanning (node_modules, dist, build, and vendor are always excluded)
150
+
151
+ ## GitHub Actions
152
+
153
+ Add Distyll to your CI pipeline. Create `.github/workflows/distyll.yml`:
154
+
155
+ ```yaml
156
+ name: Distyll Slop Check
157
+ on: [pull_request]
158
+
159
+ jobs:
160
+ distyll:
161
+ runs-on: ubuntu-latest
162
+ steps:
163
+ - uses: actions/checkout@v4
164
+ with:
165
+ fetch-depth: 0
166
+ - uses: actions/setup-node@v4
167
+ with:
168
+ node-version: '18'
169
+ - run: npm install -g distyll
170
+ - run: distyll ci --ref origin/main --threshold 50
171
+ ```
172
+
173
+ Or use the bundled action:
174
+
175
+ ```yaml
176
+ - uses: ./action.yml
177
+ with:
178
+ threshold: 50
179
+ ```
180
+
181
+ ## Scoring
182
+
183
+ The slop score (0-100) is calculated from weighted findings normalized by lines of code:
184
+
185
+ - **Info** findings: weight 1
186
+ - **Warning** findings: weight 3
187
+ - **Error** findings: weight 5
188
+
189
+ Formula: `min(100, round(weightedSum / totalLOC * 100))`
190
+
191
+ | Score | Rating | Meaning |
192
+ |-------|--------|---------|
193
+ | 0-20 | Clean | Minimal AI slop detected |
194
+ | 21-50 | Moderate | Some patterns worth reviewing |
195
+ | 51-100 | High | Significant AI-generated bloat |
196
+
197
+ ## Supported Languages
198
+
199
+ - JavaScript (`.js`, `.mjs`, `.cjs`)
200
+ - TypeScript (`.ts`, `.tsx`)
201
+ - Python (`.py`)
202
+
203
+ ## How It Compares
204
+
205
+ | Feature | Distyll | ESLint | CodeRabbit | SonarQube |
206
+ |---------|---------|--------|------------|-----------|
207
+ | AI slop detection | 12 rules | No | Generic | No |
208
+ | Slop score | 0-100 | No | No | Complexity only |
209
+ | Fix suggestions | Yes | Some | Yes | Some |
210
+ | Style fingerprinting | Yes | No | No | No |
211
+ | Git hook | Built-in | Via plugin | No | No |
212
+ | CI integration | Native | Native | Native | Native |
213
+ | Offline/local-first | Yes | Yes | No | Self-host |
214
+
215
+ ## Requirements
216
+
217
+ - Node.js >= 18.0.0
218
+ - Git (for `diff`, `hook`, and `ci` commands)
package/SETUP.md ADDED
@@ -0,0 +1,79 @@
1
+ # Distyll — Setup Guide
2
+
3
+ ## Prerequisites
4
+
5
+ - **Node.js 18+** — [Download here](https://nodejs.org/)
6
+ - **npm** (comes with Node.js)
7
+ - A C++ compiler for native modules (tree-sitter):
8
+ - **macOS**: `xcode-select --install`
9
+ - **Ubuntu/Debian**: `sudo apt install build-essential`
10
+ - **Windows**: Install Visual Studio Build Tools
11
+
12
+ ## Quick Start
13
+
14
+ ```bash
15
+ # Clone and install
16
+ cd distyll
17
+ npm install
18
+
19
+ # Build the TypeScript source
20
+ npm run build
21
+
22
+ # Scan a file
23
+ node dist/cli.js scan path/to/your/file.js
24
+
25
+ # Scan a directory
26
+ node dist/cli.js scan src/
27
+
28
+ # JSON output
29
+ node dist/cli.js scan src/ --format json
30
+
31
+ # Fail if slop score exceeds threshold (for CI)
32
+ node dist/cli.js scan src/ --threshold 50
33
+ ```
34
+
35
+ ## Global Installation (optional)
36
+
37
+ ```bash
38
+ npm install -g .
39
+ distyll scan src/
40
+ ```
41
+
42
+ ## What It Does
43
+
44
+ Distyll scans JavaScript and TypeScript files for AI-generated code anti-patterns and gives you a **slop score** from 0-100.
45
+
46
+ ### Detection Rules
47
+
48
+ | Rule | Severity | What it catches |
49
+ |------|----------|----------------|
50
+ | `unnecessary-try-catch` | warning | try-catch blocks wrapping code that can't throw |
51
+ | `single-use-wrapper` | warning | Functions that only delegate to another function |
52
+ | `verbose-comments` | info | Comments that restate the code they describe |
53
+ | `unused-imports` | warning | Imported names never used in the file |
54
+ | `single-option-object` | info | Destructured options objects with only 1 property |
55
+ | `redundant-else-return` | info | Else blocks after if blocks that return/throw |
56
+
57
+ ## Running Tests
58
+
59
+ ```bash
60
+ npm test
61
+ ```
62
+
63
+ ## Troubleshooting
64
+
65
+ ### `node-gyp` build errors during `npm install`
66
+
67
+ tree-sitter requires native compilation. Make sure you have:
68
+ - Python 3 installed
69
+ - A C++ compiler (see Prerequisites above)
70
+
71
+ On macOS, run: `xcode-select --install`
72
+
73
+ ### "Unsupported language" warnings
74
+
75
+ Distyll currently supports `.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs` files. Other file types are silently skipped.
76
+
77
+ ### No findings on a file
78
+
79
+ The rules are intentionally conservative to avoid false positives. A clean score means the code doesn't exhibit the specific AI slop patterns Distyll checks for.
package/action.yml ADDED
@@ -0,0 +1,37 @@
1
+ name: 'Distyll Slop Detector'
2
+ description: 'Detect AI-generated code anti-patterns in pull requests'
3
+ author: 'Distyll'
4
+
5
+ inputs:
6
+ threshold:
7
+ description: 'Maximum allowed slop score (0-100). Fails the check if exceeded.'
8
+ required: false
9
+ default: '70'
10
+ format:
11
+ description: 'Output format: annotations, json, or summary'
12
+ required: false
13
+ default: 'annotations'
14
+ ref:
15
+ description: 'Git ref to diff against (e.g., origin/main). Defaults to HEAD~1.'
16
+ required: false
17
+ default: 'HEAD~1'
18
+
19
+ runs:
20
+ using: 'composite'
21
+ steps:
22
+ - name: Setup Node.js
23
+ uses: actions/setup-node@v4
24
+ with:
25
+ node-version: '18'
26
+
27
+ - name: Install Distyll
28
+ shell: bash
29
+ run: npm install -g distyll
30
+
31
+ - name: Run Distyll
32
+ shell: bash
33
+ run: distyll ci --ref "${{ inputs.ref }}" --format "${{ inputs.format }}" --threshold "${{ inputs.threshold }}"
34
+
35
+ branding:
36
+ icon: 'filter'
37
+ color: 'purple'
@@ -0,0 +1,26 @@
1
+ interface CacheEntry {
2
+ timestamp: string;
3
+ ref?: string;
4
+ branch?: string;
5
+ score: number;
6
+ totalFindings: number;
7
+ fileCount: number;
8
+ }
9
+ interface CacheData {
10
+ entries: CacheEntry[];
11
+ }
12
+ export declare function loadCache(repoRoot: string): CacheData;
13
+ export declare function saveScore(repoRoot: string, score: number, totalFindings: number, fileCount: number, ref?: string, branch?: string): void;
14
+ export interface TrendSummary {
15
+ current: number;
16
+ previous: number | null;
17
+ delta: number | null;
18
+ direction: 'improving' | 'worsening' | 'stable' | 'first-scan';
19
+ history: {
20
+ timestamp: string;
21
+ score: number;
22
+ }[];
23
+ }
24
+ export declare function getTrend(repoRoot: string, currentScore: number): TrendSummary;
25
+ export {};
26
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAGA,UAAU,UAAU;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,SAAS;IACjB,OAAO,EAAE,UAAU,EAAE,CAAC;CACvB;AAiBD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,CAYrD;AAED,wBAAgB,SAAS,CACvB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,EACb,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,MAAM,GACd,IAAI,CAsBN;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,WAAW,GAAG,WAAW,GAAG,QAAQ,GAAG,YAAY,CAAC;IAC/D,OAAO,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACjD;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,YAAY,CA6B7E"}