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.
- package/CONTRIBUTING.md +159 -0
- package/POSTMORTEM.json +60 -0
- package/README.md +218 -0
- package/SETUP.md +79 -0
- package/action.yml +37 -0
- package/dist/cache.d.ts +26 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +115 -0
- package/dist/cache.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +153 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ci.d.ts +7 -0
- package/dist/commands/ci.d.ts.map +1 -0
- package/dist/commands/ci.js +101 -0
- package/dist/commands/ci.js.map +1 -0
- package/dist/commands/diff.d.ts +10 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +95 -0
- package/dist/commands/diff.js.map +1 -0
- package/dist/commands/fingerprint.d.ts +2 -0
- package/dist/commands/fingerprint.d.ts.map +1 -0
- package/dist/commands/fingerprint.js +77 -0
- package/dist/commands/fingerprint.js.map +1 -0
- package/dist/commands/hook.d.ts +3 -0
- package/dist/commands/hook.d.ts.map +1 -0
- package/dist/commands/hook.js +110 -0
- package/dist/commands/hook.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +75 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +100 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +30 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +133 -0
- package/dist/errors.js.map +1 -0
- package/dist/fingerprint/analyzer.d.ts +3 -0
- package/dist/fingerprint/analyzer.d.ts.map +1 -0
- package/dist/fingerprint/analyzer.js +230 -0
- package/dist/fingerprint/analyzer.js.map +1 -0
- package/dist/fingerprint/comparator.d.ts +4 -0
- package/dist/fingerprint/comparator.d.ts.map +1 -0
- package/dist/fingerprint/comparator.js +78 -0
- package/dist/fingerprint/comparator.js.map +1 -0
- package/dist/fingerprint/profile.d.ts +5 -0
- package/dist/fingerprint/profile.d.ts.map +1 -0
- package/dist/fingerprint/profile.js +68 -0
- package/dist/fingerprint/profile.js.map +1 -0
- package/dist/fixes/index.d.ts +12 -0
- package/dist/fixes/index.d.ts.map +1 -0
- package/dist/fixes/index.js +42 -0
- package/dist/fixes/index.js.map +1 -0
- package/dist/fixes/single-use-wrapper.d.ts +8 -0
- package/dist/fixes/single-use-wrapper.d.ts.map +1 -0
- package/dist/fixes/single-use-wrapper.js +54 -0
- package/dist/fixes/single-use-wrapper.js.map +1 -0
- package/dist/fixes/unnecessary-try-catch.d.ts +8 -0
- package/dist/fixes/unnecessary-try-catch.d.ts.map +1 -0
- package/dist/fixes/unnecessary-try-catch.js +37 -0
- package/dist/fixes/unnecessary-try-catch.js.map +1 -0
- package/dist/fixes/unused-imports.d.ts +7 -0
- package/dist/fixes/unused-imports.d.ts.map +1 -0
- package/dist/fixes/unused-imports.js +41 -0
- package/dist/fixes/unused-imports.js.map +1 -0
- package/dist/fixes/verbose-comments.d.ts +7 -0
- package/dist/fixes/verbose-comments.d.ts.map +1 -0
- package/dist/fixes/verbose-comments.js +29 -0
- package/dist/fixes/verbose-comments.js.map +1 -0
- package/dist/formatter.d.ts +4 -0
- package/dist/formatter.d.ts.map +1 -0
- package/dist/formatter.js +72 -0
- package/dist/formatter.js.map +1 -0
- package/dist/git.d.ts +22 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +130 -0
- package/dist/git.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +40 -0
- package/dist/index.js.map +1 -0
- package/dist/languages/index.d.ts +8 -0
- package/dist/languages/index.d.ts.map +1 -0
- package/dist/languages/index.js +50 -0
- package/dist/languages/index.js.map +1 -0
- package/dist/languages/javascript.d.ts +6 -0
- package/dist/languages/javascript.d.ts.map +1 -0
- package/dist/languages/javascript.js +39 -0
- package/dist/languages/javascript.js.map +1 -0
- package/dist/languages/python.d.ts +6 -0
- package/dist/languages/python.d.ts.map +1 -0
- package/dist/languages/python.js +50 -0
- package/dist/languages/python.js.map +1 -0
- package/dist/parser.d.ts +8 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +55 -0
- package/dist/parser.js.map +1 -0
- package/dist/reporters/github.d.ts +4 -0
- package/dist/reporters/github.d.ts.map +1 -0
- package/dist/reporters/github.js +70 -0
- package/dist/reporters/github.js.map +1 -0
- package/dist/reporters/terminal.d.ts +4 -0
- package/dist/reporters/terminal.d.ts.map +1 -0
- package/dist/reporters/terminal.js +59 -0
- package/dist/reporters/terminal.js.map +1 -0
- package/dist/rules/dead-code-paths.d.ts +3 -0
- package/dist/rules/dead-code-paths.d.ts.map +1 -0
- package/dist/rules/dead-code-paths.js +57 -0
- package/dist/rules/dead-code-paths.js.map +1 -0
- package/dist/rules/excessive-comments.d.ts +3 -0
- package/dist/rules/excessive-comments.d.ts.map +1 -0
- package/dist/rules/excessive-comments.js +86 -0
- package/dist/rules/excessive-comments.js.map +1 -0
- package/dist/rules/hallucinated-imports.d.ts +3 -0
- package/dist/rules/hallucinated-imports.d.ts.map +1 -0
- package/dist/rules/hallucinated-imports.js +228 -0
- package/dist/rules/hallucinated-imports.js.map +1 -0
- package/dist/rules/index.d.ts +4 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +34 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/magic-values.d.ts +3 -0
- package/dist/rules/magic-values.d.ts.map +1 -0
- package/dist/rules/magic-values.js +168 -0
- package/dist/rules/magic-values.js.map +1 -0
- package/dist/rules/near-duplicate-functions.d.ts +3 -0
- package/dist/rules/near-duplicate-functions.d.ts.map +1 -0
- package/dist/rules/near-duplicate-functions.js +78 -0
- package/dist/rules/near-duplicate-functions.js.map +1 -0
- package/dist/rules/over-defensive-nulls.d.ts +3 -0
- package/dist/rules/over-defensive-nulls.d.ts.map +1 -0
- package/dist/rules/over-defensive-nulls.js +129 -0
- package/dist/rules/over-defensive-nulls.js.map +1 -0
- package/dist/rules/redundant-else-return.d.ts +3 -0
- package/dist/rules/redundant-else-return.d.ts.map +1 -0
- package/dist/rules/redundant-else-return.js +57 -0
- package/dist/rules/redundant-else-return.js.map +1 -0
- package/dist/rules/single-option-object.d.ts +3 -0
- package/dist/rules/single-option-object.d.ts.map +1 -0
- package/dist/rules/single-option-object.js +88 -0
- package/dist/rules/single-option-object.js.map +1 -0
- package/dist/rules/single-use-wrapper.d.ts +3 -0
- package/dist/rules/single-use-wrapper.d.ts.map +1 -0
- package/dist/rules/single-use-wrapper.js +172 -0
- package/dist/rules/single-use-wrapper.js.map +1 -0
- package/dist/rules/unnecessary-try-catch.d.ts +3 -0
- package/dist/rules/unnecessary-try-catch.d.ts.map +1 -0
- package/dist/rules/unnecessary-try-catch.js +116 -0
- package/dist/rules/unnecessary-try-catch.js.map +1 -0
- package/dist/rules/unused-imports.d.ts +3 -0
- package/dist/rules/unused-imports.d.ts.map +1 -0
- package/dist/rules/unused-imports.js +103 -0
- package/dist/rules/unused-imports.js.map +1 -0
- package/dist/rules/verbose-comments.d.ts +3 -0
- package/dist/rules/verbose-comments.d.ts.map +1 -0
- package/dist/rules/verbose-comments.js +100 -0
- package/dist/rules/verbose-comments.js.map +1 -0
- package/dist/scanner.d.ts +11 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +196 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scorer.d.ts +3 -0
- package/dist/scorer.d.ts.map +1 -0
- package/dist/scorer.js +23 -0
- package/dist/scorer.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/hn_post.md +13 -0
- package/marketing/COMPETITIVE_ANALYSIS.md +62 -0
- package/marketing/EMAIL_ANNOUNCEMENT.md +91 -0
- package/marketing/LANDING_PAGE_COPY.md +123 -0
- package/marketing/LAUNCH_POST.md +68 -0
- package/marketing/PRODUCT_HUNT.md +39 -0
- package/marketing/TWITTER_THREAD.md +70 -0
- package/package.json +44 -0
- package/producthunt.md +52 -0
- package/reddit_post.md +39 -0
- package/site/favicon.svg +10 -0
- package/site/index.html +281 -0
- package/site/script.js +82 -0
- package/site/style.css +516 -0
- package/src/cache.ts +114 -0
- package/src/cli.ts +169 -0
- package/src/commands/ci.ts +111 -0
- package/src/commands/diff.ts +108 -0
- package/src/commands/fingerprint.ts +47 -0
- package/src/commands/hook.ts +85 -0
- package/src/commands/init.ts +42 -0
- package/src/config.ts +75 -0
- package/src/errors.ts +105 -0
- package/src/fingerprint/analyzer.ts +214 -0
- package/src/fingerprint/comparator.ts +93 -0
- package/src/fingerprint/profile.ts +32 -0
- package/src/fixes/index.ts +58 -0
- package/src/fixes/single-use-wrapper.ts +60 -0
- package/src/fixes/unnecessary-try-catch.ts +43 -0
- package/src/fixes/unused-imports.ts +53 -0
- package/src/fixes/verbose-comments.ts +35 -0
- package/src/formatter.ts +79 -0
- package/src/git.ts +115 -0
- package/src/index.ts +15 -0
- package/src/languages/index.ts +50 -0
- package/src/languages/javascript.ts +36 -0
- package/src/languages/python.ts +47 -0
- package/src/parser.ts +52 -0
- package/src/reporters/github.ts +75 -0
- package/src/reporters/terminal.ts +67 -0
- package/src/rules/dead-code-paths.ts +62 -0
- package/src/rules/excessive-comments.ts +94 -0
- package/src/rules/hallucinated-imports.ts +195 -0
- package/src/rules/index.ts +32 -0
- package/src/rules/magic-values.ts +167 -0
- package/src/rules/near-duplicate-functions.ts +89 -0
- package/src/rules/over-defensive-nulls.ts +137 -0
- package/src/rules/redundant-else-return.ts +61 -0
- package/src/rules/single-option-object.ts +97 -0
- package/src/rules/single-use-wrapper.ts +184 -0
- package/src/rules/unnecessary-try-catch.ts +121 -0
- package/src/rules/unused-imports.ts +115 -0
- package/src/rules/verbose-comments.ts +105 -0
- package/src/scanner.ts +184 -0
- package/src/scorer.ts +26 -0
- package/src/types.ts +70 -0
- package/tests/commands/diff.test.ts +107 -0
- package/tests/config.test.ts +69 -0
- package/tests/e2e.test.ts +163 -0
- package/tests/edge-cases.test.ts +167 -0
- package/tests/fingerprint/analyzer.test.ts +131 -0
- package/tests/fixes/unnecessary-try-catch.test.ts +62 -0
- package/tests/git.test.ts +79 -0
- package/tests/rules/hallucinated-imports.test.ts +59 -0
- package/tests/rules/near-duplicate-functions.test.ts +90 -0
- package/tests/rules/unnecessary-try-catch.test.ts +81 -0
- package/tests/scanner.test.ts +88 -0
- package/tsconfig.json +20 -0
- package/twitter_thread.md +46 -0
- package/vitest.config.ts +7 -0
package/CONTRIBUTING.md
ADDED
|
@@ -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
|
+
```
|
package/POSTMORTEM.json
ADDED
|
@@ -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'
|
package/dist/cache.d.ts
ADDED
|
@@ -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"}
|