@alint-js/plugin-simplicity 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,301 @@
1
+ # `@alint-js/plugin-simplicity`
2
+
3
+ Tell your code to **DRY — Don't Repeat Yourself**.
4
+ Finds small helper functions that are defined twice, or that should not exist at all.
5
+
6
+ Both rules work on **helpers**: anything with a name and a body. A function declaration,
7
+ a method, and, in TypeScript, TSX and JavaScript, an arrow function or function
8
+ expression bound to a name (`const parse = (text: string) => JSON.parse(text)`). That
9
+ last shape is how most tiny helpers are actually written, so leaving it out would hide
10
+ exactly the code these rules look for.
11
+
12
+ Languages: TypeScript, TSX, JavaScript, Rust, Go and Python.
13
+
14
+ ## How it works
15
+
16
+ One index of the whole workspace, built once per run and shared by both rules. Everything
17
+ a hash can settle is settled by code. Only what is left reaches a model.
18
+
19
+ ```mermaid
20
+ flowchart TB
21
+ subgraph idx ["Repo index — Once per run, no token cost"]
22
+ direction TB
23
+ files["Every parseable file in the workspace"]
24
+ files --> parse["Parse and query by tree-sitter"]
25
+ parse --> keep{"Small helper?<br/>within maxLines, at least minTokens"}
26
+ keep -->|no| skip["Not indexed"]
27
+ keep -->|yes| helpers["Indexed with its facts:<br/>exact fingerprint, alpha fingerprint,<br/>usage count, whether exported"]
28
+ end
29
+
30
+ helpers --> lint["Files to lint"]
31
+ lint --> dup
32
+ lint --> needless
33
+
34
+ subgraph dup ["no-duplicated-helper"]
35
+ direction TB
36
+ d1{"Identical body<br/>somewhere else?"}
37
+ d1 -->|yes| dr1["Report: also defined at ..."]
38
+ d1 -->|no| d2{"Same body, renamed?<br/>alpha-equivalent"}
39
+ d2 -->|yes| dr2["Report: renamed copy of ..."]
40
+ d2 -->|no| d3["Agent, holding the index as tools:<br/>search bodies, rank similar, read in full"]
41
+ d3 --> dr3["Report: duplicates ..., and why"]
42
+ end
43
+
44
+ subgraph needless ["no-needless-helper"]
45
+ direction TB
46
+ n1{"Whole body is<br/>one expression?"}
47
+ n1 -->|no| nx["Never asked about"]
48
+ n1 -->|yes| n2["One model call per file.<br/>Usage count and exported go in<br/>as facts, not filters"]
49
+ n2 --> nr["Report: does not earn its existence"]
50
+ end
51
+
52
+ cache[("Cache in .alint/simplicity/<br/>a run that changed nothing costs nothing")]
53
+ d3 <--> cache
54
+ n2 <--> cache
55
+
56
+ classDef free fill:#e8f5e9,stroke:#43a047,color:#1b5e20
57
+ classDef paid fill:#fff3e0,stroke:#fb8c00,color:#e65100
58
+ class files,parse,keep,skip,helpers,d1,d2,dr1,dr2,n1,nx free
59
+ class d3,dr3,n2,nr paid
60
+ ```
61
+
62
+ Green spends nothing. Orange spends model tokens, and only ever sees the helpers the
63
+ green half could not settle.
64
+
65
+ ### The two fingerprints
66
+
67
+ Every indexed helper is hashed twice. Both hashes drop comments and collapse whitespace
68
+ first, so reformatting a copy or rewording its documentation changes neither.
69
+
70
+ **The exact fingerprint** hashes what is left, verbatim. Two helpers that share it are the
71
+ same code, character for character, once layout and comments are set aside.
72
+
73
+ **The alpha fingerprint** goes one step further: every name the helper *declares* — its own
74
+ name, its parameters, its locals — is replaced by a placeholder, in order of first
75
+ appearance. Everything the helper merely *refers to* stays as written: the functions it
76
+ calls, the types it names, the properties it reads.
77
+
78
+ That split is the whole design. A copy can rename what it declares; it cannot rename what
79
+ it refers to and still be a copy. So these two match:
80
+
81
+ ```ts
82
+ function hasErrorCode(failure: unknown): failure is NodeJS.ErrnoException {
83
+ return failure instanceof Error && 'code' in failure
84
+ }
85
+
86
+ function isNodeError(error: unknown): error is NodeJS.ErrnoException {
87
+ return error instanceof Error && 'code' in error
88
+ }
89
+ ```
90
+
91
+ Both normalize to the same thing, so the second is reported as a renamed copy of the first:
92
+
93
+ ```
94
+ function $0($1: unknown): $1 is NodeJS.ErrnoException { return $1 instanceof Error && 'code' in $1 }
95
+ ```
96
+
97
+ `Error` and `NodeJS.ErrnoException` survive, because the helper only refers to them. And
98
+ these two do **not** match:
99
+
100
+ ```ts
101
+ export function readName(entry: Entry): string {
102
+ return entry.name
103
+ }
104
+
105
+ export function readSize(entry: Entry): number {
106
+ return entry.size
107
+ }
108
+ ```
109
+
110
+ `entry` is declared, so it is blinded; `name` and `size` are properties the helpers read,
111
+ so they survive the hash and tell the two apart. A detector that blinded every identifier
112
+ instead — NiCad's blind renaming, PMD's `--ignore-identifiers` — collapses this pair and
113
+ reports two unrelated accessors as copies.
114
+
115
+ Matching alpha fingerprints mean the helpers are **alpha-equivalent**: identical up to
116
+ renaming. It is an equivalence, not a similarity score, so there is no threshold to tune
117
+ and no false-positive rate to trade off. Everything it cannot decide is the agent's
118
+ question.
119
+
120
+ ## Rules
121
+
122
+ - **`simplicity/no-duplicated-helper`**: a small helper is defined in two or more places.
123
+ - **`simplicity/no-needless-helper`**: a one-line helper that does not earn its existence.
124
+
125
+ Rule ids carry the prefix you give the plugin in `plugins`, which is `simplicity/` in
126
+ every example here.
127
+
128
+ ## `no-duplicated-helper`
129
+
130
+ Two approaches, and the AST one spends nothing.
131
+
132
+ The **AST approach** hashes. An **identical** helper is reported by code alone, and so is a
133
+ **renamed** one: a copy that changed its own name, its parameters and its locals, but not the
134
+ functions it calls, the types it names or the properties it reads. Those are the names a copy
135
+ cannot change and stay a copy, so they are left in the hash, and they are also what keeps
136
+ `return this.name` and `return this.size` from being mistaken for each other. This is
137
+ alpha-equivalence, and because it is an equivalence rather than a score there is no
138
+ threshold to tune.
139
+
140
+ Everything a hash cannot settle goes to the **agentic approach**. The agent is not handed a
141
+ shortlist: it gets the index as tools and searches the way a reviewer would, saying what a
142
+ helper does, searching other bodies for that behaviour (`search_helper_bodies` looks for
143
+ `instanceof Error`, not for names), reading the candidates in full, and deciding.
144
+
145
+ A finding names the twin and the responsibility they share, and stops there:
146
+
147
+ ```
148
+ Helper "isNodeErrorCode" duplicates "isNodeError" at ts/store.ts:16:
149
+ Both ask whether an error has a specific Node error code.
150
+ ```
151
+
152
+ It does not suggest where the shared copy should live. That decision depends on
153
+ ownership, layering and dependency direction, none of which this rule looks at; see
154
+ `docs/simplicity-architecture.md` for the version that tried and what it produced.
155
+
156
+ ## `no-needless-helper`
157
+
158
+ A helper earns its existence when its interface is simpler than its implementation, when
159
+ the name tells a reader something the body would not. This rule reports the ones that do
160
+ not: a body of a single expression that says what the name says, so a reader jumps to the
161
+ declaration to learn nothing.
162
+
163
+ ```
164
+ Helper "parse" does not earn its existence: Forwards to JSON.parse unchanged.
165
+ ```
166
+
167
+ **It has no AST approach, and cannot have one.** A hash can prove a duplicate, because two
168
+ identical bodies are a fact. Nothing can prove a helper *should not exist*, because that
169
+ is a judgement about a reader. So the deterministic half finds the helpers worth asking
170
+ about (one expression, no more) and gathers the facts a judgement needs: how often the
171
+ helper is called, and whether it is somebody's public API. Those go to the model as
172
+ **facts, not filters**. A helper called forty times is not disqualified by code; the model
173
+ is told the number and left to weigh it.
174
+
175
+ What it must *not* report matters as much as what it must:
176
+
177
+ - **`clamp(value, low, high)`** is short, and earns it. The name is the documentation;
178
+ `Math.min(Math.max(x, 0), 1)` is not.
179
+ - **`isNodeError(error): error is ErrnoException`** is a one-line type guard. Inlined, the
180
+ check still runs but the call site loses the type it narrowed to.
181
+
182
+ That second case is the sharpest in the plugin: `no-duplicated-helper` must report
183
+ `isNodeError`, because it is copied, and `no-needless-helper` must not, because it earns
184
+ its keep. The same three lines, two rules, opposite decisions.
185
+
186
+ It costs one model call per file rather than an agent loop, because everything the
187
+ judgement needs is already in hand and there is nothing to search for.
188
+
189
+ ## Usage
190
+
191
+ The rules read whole files and parse them internally, so they take plain-text targets:
192
+
193
+ ```js
194
+ import simplicityPlugin from '@alint-js/plugin-simplicity'
195
+
196
+ import { createApeiraAdapter } from '@alint-js/agent-apeira'
197
+ import { defineConfig } from '@alint-js/cli'
198
+
199
+ export default defineConfig([
200
+ {
201
+ // `no-duplicated-helper`'s agentic approach needs this. Without it, the AST approach
202
+ // still runs.
203
+ agent: createApeiraAdapter(),
204
+ files: ['**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts,rs,go,py}'],
205
+ language: 'text/plain',
206
+ plugins: {
207
+ simplicity: simplicityPlugin,
208
+ },
209
+ rules: {
210
+ 'simplicity/no-duplicated-helper': 'warn',
211
+ 'simplicity/no-needless-helper': 'warn',
212
+ },
213
+ settings: {
214
+ simplicity: {
215
+ // Test and fixture globs to leave alone.
216
+ ignores: ['**/*.test.ts'],
217
+ // False keeps the AST approach and spends no tokens at all.
218
+ judge: true,
219
+ // The small-helper threshold, in lines. Note that it *selects* small functions;
220
+ // every copy-paste detector uses its threshold to exclude them.
221
+ maxLines: 10,
222
+ // Content tokens a helper needs before it is worth a word. An empty function is
223
+ // 3 in any language; the smallest real helper is 6.
224
+ minTokens: 5,
225
+ },
226
+ },
227
+ },
228
+ ])
229
+ ```
230
+
231
+ `simplicityPlugin.configs.recommended` ships the same rules and file globs, if you would
232
+ rather not spell them out.
233
+
234
+ alint rule entries carry a severity and nothing else, which is why the options live under
235
+ `settings.simplicity` rather than beside the rule.
236
+
237
+ ### Cost
238
+
239
+ The agentic approach is **on by default and spends model tokens**. Identical and renamed
240
+ copies never reach it, and a file whose helpers were all settled by a hash costs nothing at
241
+ all, because no agent is started for it. `settings.simplicity.judge: false` turns the plugin
242
+ into a deterministic, zero-token duplicate detector for small helpers.
243
+
244
+ Two things keep the agentic approach affordable.
245
+
246
+ **A review is cached under a fingerprint of every helper in the workspace**, so a run that
247
+ changed nothing costs nothing. A helper added, edited, moved or deleted anywhere throws
248
+ the whole cache away, because a helper added anywhere could be the twin of a helper here,
249
+ and a decision decided against one workspace means nothing against another. The
250
+ invalidation is coarse on purpose and cheap in practice: the index holds only helpers of
251
+ ten lines or fewer, and most commits do not touch one. Turn it off with
252
+ `settings.simplicity.cache: false`.
253
+
254
+ The cache is written to `.alint/simplicity/`. Add it to your `.gitignore`: what a model
255
+ decided is not project configuration.
256
+
257
+ **Only the files you lint are reviewed.** The index always covers the whole workspace, so
258
+ a twin is found wherever it lives, but only the files you pass are handed to an agent.
259
+ That makes the cost proportional to your diff rather than to the repository:
260
+
261
+ ```sh
262
+ alint $(git diff --name-only origin/main...)
263
+ ```
264
+
265
+ Agent tokens are metered and reported, so a run says what it cost.
266
+
267
+ ### Evaluating a change to a prompt
268
+
269
+ `fixtures/` is a graded corpus: every helper in it is either a finding a run must produce
270
+ or one it must not. The unit tests assert the parts a fingerprint decides, which cost
271
+ nothing. The parts a model decides are measured by a harness that spends real tokens, so
272
+ it is kept outside this repository and CI never runs it. See
273
+ [the architecture notes](./docs/simplicity-architecture.md) for what it measures and why a
274
+ prompt cannot be changed without it.
275
+
276
+ ## When to use
277
+
278
+ - A codebase where several people, or several agents, open branches in parallel and the
279
+ same tiny helper keeps being re-invented in each of them. This is the case the plugin
280
+ was built from: the duplication in #28 and #31 entered through parallel branches and was
281
+ found by a human afterwards.
282
+ - Reviews where "search for an existing implementation first" is written down as guidance
283
+ and is not, on its own, working.
284
+ - Codebases that already run a copy-paste detector and want the band it cannot see:
285
+ helpers below its minimum-token floor.
286
+
287
+ ## When not to use
288
+
289
+ - **Not a general code-quality plugin.** The scope is function-level simplification smells
290
+ and nothing more. If a check does not answer "does this helper exist twice" or "should
291
+ this helper exist at all", it belongs elsewhere.
292
+ - **Not a cross-language dedupe tool.** Helpers are compared within one language only. A
293
+ Go twin of a TypeScript helper is not shareable code, so reporting it would be advice
294
+ nobody could take, and the tool that records findings refuses a cross-language pair
295
+ outright.
296
+ - **Not a replacement for token-based copy-paste detectors.** jscpd, PMD's CPD and their
297
+ peers cover large duplicated blocks (roughly 50 tokens and up) well, cheaply, and across
298
+ 150+ languages. Keep using them, and run this plugin for the small helpers they are
299
+ configured to ignore.
300
+ - **Not free, unless you ask it to be.** The judge spends tokens. `judge: false` keeps the
301
+ AST approach and costs nothing.
@@ -0,0 +1,193 @@
1
+ import { RuleContext, SourceLocation, SourceRange } from "@alint-js/core";
2
+ import { AgentTool } from "@alint-js/core/agent";
3
+ import { InferOutput } from "valibot";
4
+
5
+ //#region src/extract/types.d.ts
6
+ interface CallSite {
7
+ /** The last segment only: `a.b.helper()` and `helper()` both yield `helper`. */
8
+ name: string;
9
+ range: SourceRange;
10
+ }
11
+ interface ExtractedFunction {
12
+ /** The names this function declares. Everything else stays verbatim in the alpha fingerprint; see `queries.ts`. */
13
+ binderNames: string[];
14
+ /** Stricter than one statement: a body that is one `if` and its two returns is one statement and not one expression. */
15
+ bodyIsSingleExpression: boolean;
16
+ bodyStatements: number;
17
+ /** Relative to `text`, not to the source. */
18
+ commentRanges: SourceRange[];
19
+ exported: boolean;
20
+ /** Relative to `text`. Property, field and type names are not here: replacing them would collapse `entry.name` and `entry.size`. */
21
+ identifierRanges: SourceRange[];
22
+ /** Absolute. Lines are 1-based, columns 0-based. */
23
+ loc: SourceLocation;
24
+ name: string;
25
+ /** Absolute offsets into the source. */
26
+ range: SourceRange;
27
+ text: string;
28
+ }
29
+ type ExtractLanguage = 'go' | 'javascript' | 'python' | 'rust' | 'tsx' | 'typescript';
30
+ interface SourceExtract {
31
+ /** Every call in the source, including calls made outside any function. */
32
+ calls: CallSite[];
33
+ functions: ExtractedFunction[];
34
+ }
35
+ //#endregion
36
+ //#region src/extract/extract.d.ts
37
+ declare function extractSource(source: string, language: ExtractLanguage): Promise<SourceExtract>;
38
+ //#endregion
39
+ //#region src/extract/language.d.ts
40
+ declare function resolveExtractLanguage(filePath: string): ExtractLanguage | undefined;
41
+ //#endregion
42
+ //#region src/fingerprint/fingerprint.d.ts
43
+ /**
44
+ * Hashes a function with the names it declares (own name, parameters, locals) replaced by
45
+ * placeholders. Same alpha fingerprint means the same function, renamed.
46
+ *
47
+ * `identifierRanges` must hold renameable identifiers only. Replacing property, field or type
48
+ * names is the mistake this fingerprint exists to avoid.
49
+ */
50
+ declare function alphaFingerprint(text: string, commentRanges: readonly SourceRange[], identifierRanges: readonly SourceRange[], binderNames: readonly string[]): string;
51
+ /** Hashes a function with comments and formatting removed, so layout and docs do not count. */
52
+ declare function exactFingerprint(text: string, commentRanges: readonly SourceRange[]): string;
53
+ /** Comments and formatting removed, names left alone, so `search_helper_bodies` can search real code. */
54
+ declare function normalizedBody(text: string, commentRanges: readonly SourceRange[]): string;
55
+ /** Content tokens, alpha-normalized. Punctuation is dropped: every function has braces. */
56
+ declare function tokenize(text: string, commentRanges: readonly SourceRange[], identifierRanges: readonly SourceRange[], binderNames: readonly string[]): string[];
57
+ /** Shared token fraction of the larger bag. Ranks candidates, decides nothing. */
58
+ declare function tokenOverlap(left: readonly string[], right: readonly string[]): number;
59
+ //#endregion
60
+ //#region src/rules/no-duplicated-helper/tools.d.ts
61
+ interface AgentFinding {
62
+ helperId: string;
63
+ reason: string;
64
+ twinId: string;
65
+ }
66
+ interface DuplicateToolsOptions {
67
+ /** Mutated by `report_duplicate`; the caller reads it once the agent stops. */
68
+ findings: AgentFinding[];
69
+ index: RepoIndex;
70
+ /** Helpers of the file under review: the only ones the agent may report on. */
71
+ reviewing: readonly IndexedHelper[];
72
+ }
73
+ declare function createDuplicateTools(options: DuplicateToolsOptions): AgentTool[];
74
+ //#endregion
75
+ //#region src/repo/cache.d.ts
76
+ interface ReviewCache {
77
+ /** A review is returned only to a run whose `fingerprint` matches the one it was decided against. */
78
+ get: (filePath: string) => AgentFinding[] | undefined;
79
+ set: (filePath: string, findings: AgentFinding[]) => Promise<void>;
80
+ }
81
+ declare function reviewCacheFor(ctx: RuleContext, options: {
82
+ cwd: string;
83
+ enabled: boolean;
84
+ fingerprint: string;
85
+ }): Promise<ReviewCache>;
86
+ //#endregion
87
+ //#region src/repo/index.d.ts
88
+ interface IndexedHelper {
89
+ alphaFingerprint: string;
90
+ /** Comments and formatting removed, names left alone, so `search_helper_bodies` searches real code. */
91
+ body: string;
92
+ bodyIsSingleExpression: boolean;
93
+ /** Statements in the body, not counting comments. */
94
+ bodyStatements: number;
95
+ exactFingerprint: string;
96
+ exported: boolean;
97
+ filePath: string;
98
+ /** `packages/cli/src/lint.ts:57`. Unique, and a model can quote it back. */
99
+ id: string;
100
+ language: ExtractLanguage;
101
+ line: number;
102
+ lines: number;
103
+ name: string;
104
+ text: string;
105
+ tokens: string[];
106
+ /**
107
+ * How often a function of this name is called across the workspace.
108
+ *
109
+ * Counted by NAME, not by binding, so two `isEmpty` helpers share a count and `x.isEmpty()` counts too.
110
+ * Only ever handed to the judge as an approximate fact.
111
+ */
112
+ usageCount: number;
113
+ }
114
+ interface RepoIndex {
115
+ byAlpha: Map<string, IndexedHelper[]>;
116
+ byExact: Map<string, IndexedHelper[]>;
117
+ byId: Map<string, IndexedHelper>;
118
+ /** Every helper in the workspace, in one hash. What a cached review is stamped with. */
119
+ fingerprint: string;
120
+ helpers: IndexedHelper[];
121
+ }
122
+ interface RepoIndexOptions {
123
+ cwd: string;
124
+ ignores: readonly string[];
125
+ maxLines: number;
126
+ minTokens: number;
127
+ }
128
+ /** Helpers of one file, in source order. */
129
+ declare function helpersIn(index: RepoIndex, filePath: string): IndexedHelper[];
130
+ declare function repoIndexFor(ctx: RuleContext, options: RepoIndexOptions): Promise<RepoIndex>;
131
+ /** Every other helper sharing a fingerprint. A helper is never its own twin. */
132
+ declare function twinsOf(index: RepoIndex, helper: IndexedHelper, kind: 'alpha' | 'exact'): IndexedHelper[];
133
+ //#endregion
134
+ //#region src/rules/no-duplicated-helper/prompt.d.ts
135
+ declare const duplicatedHelperInstructions: string;
136
+ /**
137
+ * The nearest helpers are pasted in rather than fetched: measured, the agent spent three of its four steps discovering what to read,
138
+ * and every step re-sends the system prompt and every tool schema.
139
+ */
140
+ declare function buildDuplicatedHelperPrompt(options: {
141
+ candidates: readonly IndexedHelper[];
142
+ filePath: string;
143
+ helpers: readonly IndexedHelper[];
144
+ }): string;
145
+ //#endregion
146
+ //#region src/rules/no-duplicated-helper/rule.d.ts
147
+ /**
148
+ * Reports a small helper already implemented somewhere else in the workspace.
149
+ *
150
+ * Identical bodies and renamed-only copies are settled by AST fingerprints, with no model. What
151
+ * a fingerprint cannot settle goes to an agent holding the whole index as tools.
152
+ */
153
+ declare const duplicatedHelperRule: import("@alint-js/core").RuleDefinition;
154
+ //#endregion
155
+ //#region src/rules/no-needless-helper/prompt.d.ts
156
+ declare const needlessHelperPrompt: string;
157
+ declare function buildNeedlessHelperPrompt(helpers: readonly IndexedHelper[]): string;
158
+ //#endregion
159
+ //#region src/rules/no-needless-helper/rule.d.ts
160
+ declare const needlessHelperResponseSchema: import("valibot").ObjectSchema<{
161
+ readonly findings: import("valibot").ArraySchema<import("valibot").ObjectSchema<{
162
+ readonly helper: import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<undefined>, import("valibot").DescriptionAction<number, "The number shown beside the helper, exactly as given.">]>;
163
+ readonly name: import("valibot").SchemaWithPipe<readonly [import("valibot").StringSchema<undefined>, import("valibot").DescriptionAction<string, "The helper's name, exactly as shown.">]>;
164
+ readonly reason: import("valibot").SchemaWithPipe<readonly [import("valibot").StringSchema<undefined>, import("valibot").DescriptionAction<string, "At most twelve words, naming what the body already says.">]>;
165
+ }, undefined>, undefined>;
166
+ }, undefined>;
167
+ /**
168
+ * Reports a helper whose interface is no simpler than its implementation.
169
+ *
170
+ * No AST approach is possible: a hash can prove a duplicate, but nothing can prove a helper
171
+ * should not exist. The deterministic half only finds candidates and gathers facts (usage
172
+ * count, exported), which are given to the model rather than applied as filters.
173
+ */
174
+ declare const needlessHelperRule: import("@alint-js/core").RuleDefinition;
175
+ //#endregion
176
+ //#region src/rules/shared/settings.d.ts
177
+ declare const settingsSchema: import("valibot").ObjectSchema<{
178
+ readonly cache: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, true>; /** Globs matched against the repository-relative file path. */
179
+ readonly ignores: import("valibot").OptionalSchema<import("valibot").ArraySchema<import("valibot").StringSchema<undefined>, undefined>, readonly []>;
180
+ /**
181
+ * When false, no model is called: `no-duplicated-helper` still reports what a fingerprint settles,
182
+ * `no-needless-helper` reports nothing.
183
+ */
184
+ readonly judge: import("valibot").OptionalSchema<import("valibot").BooleanSchema<undefined>, true>;
185
+ readonly maxLines: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<undefined>, import("valibot").IntegerAction<number, undefined>, import("valibot").MinValueAction<number, 1, undefined>]>, 10>;
186
+ readonly minTokens: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<undefined>, import("valibot").IntegerAction<number, undefined>, import("valibot").MinValueAction<number, 1, undefined>]>, 5>;
187
+ }, undefined>;
188
+ type SimplicitySettings = InferOutput<typeof settingsSchema>;
189
+ //#endregion
190
+ //#region src/index.d.ts
191
+ declare const simplicityPlugin: import("@alint-js/core").PluginDefinition;
192
+ //#endregion
193
+ export { type AgentFinding, type CallSite, type DuplicateToolsOptions, type ExtractLanguage, type ExtractedFunction, type IndexedHelper, type RepoIndex, type RepoIndexOptions, type ReviewCache, type SimplicitySettings, type SourceExtract, alphaFingerprint, buildDuplicatedHelperPrompt, buildNeedlessHelperPrompt, createDuplicateTools, simplicityPlugin as default, simplicityPlugin, duplicatedHelperInstructions, duplicatedHelperRule, exactFingerprint, extractSource, helpersIn, needlessHelperPrompt, needlessHelperResponseSchema, needlessHelperRule, normalizedBody, repoIndexFor, resolveExtractLanguage, reviewCacheFor, tokenOverlap, tokenize, twinsOf };