@stupify/cli 0.0.3 → 0.0.4
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 +26 -31
- package/dist/analysis.d.ts +11 -9
- package/dist/analysis.js +30 -173
- package/dist/checks.d.ts +1 -0
- package/dist/checks.js +89 -2
- package/dist/command.js +55 -91
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/counter-scout.js +70 -8
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +131 -0
- package/dist/git.d.ts +4 -1
- package/dist/git.js +34 -0
- package/dist/hooks.d.ts +3 -0
- package/dist/hooks.js +117 -0
- package/dist/model.d.ts +1 -15
- package/dist/model.js +37 -21
- package/dist/prompts.d.ts +8 -5
- package/dist/prompts.js +58 -168
- package/dist/render.d.ts +2 -2
- package/dist/render.js +70 -78
- package/dist/repomix-provider.d.ts +10 -2
- package/dist/repomix-provider.js +62 -11
- package/dist/search-bench.d.ts +1 -0
- package/dist/search-bench.js +675 -0
- package/dist/search-profile.d.ts +6 -0
- package/dist/search-profile.js +73 -0
- package/dist/sem-provider.d.ts +2 -2
- package/dist/sem-provider.js +33 -7
- package/dist/stupify.d.ts +2 -0
- package/dist/stupify.js +183 -333
- package/dist/types.d.ts +193 -109
- package/package.json +1 -1
- package/src/analysis.ts +48 -268
- package/src/checks.ts +91 -2
- package/src/command.ts +62 -107
- package/src/constants.ts +1 -1
- package/src/counter-scout.ts +63 -7
- package/src/doctor.ts +140 -0
- package/src/git.ts +35 -1
- package/src/hooks.ts +134 -0
- package/src/model.ts +39 -26
- package/src/prompts.ts +66 -202
- package/src/render.ts +68 -79
- package/src/repomix-provider.ts +66 -10
- package/src/search-bench.ts +783 -0
- package/src/search-profile.ts +89 -0
- package/src/sem-provider.ts +36 -9
- package/src/stupify.ts +213 -526
- package/src/types.ts +195 -119
- package/dist/batcher.d.ts +0 -3
- package/dist/batcher.js +0 -142
- package/dist/candidate-context.d.ts +0 -2
- package/dist/candidate-context.js +0 -40
- package/dist/experiment.d.ts +0 -1
- package/dist/experiment.js +0 -225
- package/src/batcher.ts +0 -198
- package/src/candidate-context.ts +0 -43
- package/src/experiment.ts +0 -317
package/src/analysis.ts
CHANGED
|
@@ -1,95 +1,36 @@
|
|
|
1
|
-
import { auditPrompt, findingsAuditPrompt, scoutPrompt, semScoutPrompt } from "./prompts.ts";
|
|
2
1
|
import { cachedJson, fingerprint } from "./cache.ts";
|
|
3
2
|
import type { LocalModel } from "./model.ts";
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
CheckId,
|
|
7
|
-
DiffBatch,
|
|
8
|
-
AuditPromptName,
|
|
9
|
-
AuditReviewResult,
|
|
10
|
-
Finding,
|
|
11
|
-
FindingsResult,
|
|
12
|
-
NetDiff,
|
|
13
|
-
SemCandidate,
|
|
14
|
-
SemChangeSet,
|
|
15
|
-
SemContext,
|
|
16
|
-
SemContextPack,
|
|
17
|
-
SourceId,
|
|
18
|
-
StupifyCheck,
|
|
19
|
-
} from "./types.ts";
|
|
3
|
+
import { searchPrompt } from "./prompts.ts";
|
|
4
|
+
import type { SearchMatch, SemChangeSet, SemContext, SemContextPack, StupifyCheck } from "./types.ts";
|
|
20
5
|
|
|
21
|
-
export async function
|
|
6
|
+
export async function runSearch(
|
|
22
7
|
model: LocalModel,
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const raw = await runJsonPrompt(model, scoutPrompt(batch, checks, sourceLabel), scoutSchema(batch), 0);
|
|
28
|
-
return uncheckedCandidates(raw);
|
|
8
|
+
request: SearchRequest,
|
|
9
|
+
): Promise<readonly SearchMatch[]> {
|
|
10
|
+
const raw = await runJsonPrompt(model, request.prompt, request.schema, 0);
|
|
11
|
+
return uncheckedSearchMatches(raw, request.contexts);
|
|
29
12
|
}
|
|
30
13
|
|
|
31
|
-
export
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
contexts: readonly
|
|
35
|
-
|
|
36
|
-
): Promise<FindingsResult> {
|
|
37
|
-
if (contexts.length === 0) return { findings: [], summary: "No candidate regions found." };
|
|
38
|
-
|
|
39
|
-
const raw = await runJsonPrompt(model, auditPrompt(contexts, checks, diff.label), auditSchema(contexts), 0);
|
|
40
|
-
return uncheckedRawAuditResult(raw, diff.id);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function scoutSemChanges(
|
|
44
|
-
model: LocalModel,
|
|
45
|
-
changeSet: SemChangeSet,
|
|
46
|
-
checks: readonly StupifyCheck[],
|
|
47
|
-
maxCandidates: number,
|
|
48
|
-
): Promise<readonly SemCandidate[]> {
|
|
49
|
-
const raw = await runJsonPrompt(
|
|
50
|
-
model,
|
|
51
|
-
semScoutPrompt(changeSet, checks, maxCandidates),
|
|
52
|
-
semScoutSchema(changeSet, checks, maxCandidates),
|
|
53
|
-
0,
|
|
54
|
-
);
|
|
55
|
-
return uncheckedSemCandidates(raw, changeSet.id);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export async function runFindingsAudit(
|
|
59
|
-
model: LocalModel,
|
|
60
|
-
changeSet: SemChangeSet,
|
|
61
|
-
contexts: readonly SemContext[],
|
|
62
|
-
pack: SemContextPack,
|
|
63
|
-
checks: readonly StupifyCheck[],
|
|
64
|
-
request = findingsAuditRequest(changeSet, contexts, pack, checks),
|
|
65
|
-
): Promise<AuditReviewResult> {
|
|
66
|
-
if (contexts.length === 0) {
|
|
67
|
-
return {
|
|
68
|
-
findings: [],
|
|
69
|
-
summary: "No candidate entities found.",
|
|
70
|
-
stats: { totalTargets: 0, finding: 0, clean: 0, uncertain: 0, invalid: 0 },
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const raw = await runJsonPrompt(
|
|
75
|
-
model,
|
|
76
|
-
request.prompt,
|
|
77
|
-
request.schema,
|
|
78
|
-
0,
|
|
79
|
-
);
|
|
80
|
-
return uncheckedFindingsAuditResult(raw, changeSet.id, contexts);
|
|
81
|
-
}
|
|
14
|
+
export type SearchRequest = Readonly<{
|
|
15
|
+
prompt: string;
|
|
16
|
+
schema: unknown;
|
|
17
|
+
contexts: readonly SemContext[];
|
|
18
|
+
}>;
|
|
82
19
|
|
|
83
|
-
export function
|
|
84
|
-
changeSet: SemChangeSet
|
|
85
|
-
contexts: readonly SemContext[]
|
|
86
|
-
pack: SemContextPack
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
):
|
|
20
|
+
export function searchRequest(input: Readonly<{
|
|
21
|
+
changeSet: SemChangeSet;
|
|
22
|
+
contexts: readonly SemContext[];
|
|
23
|
+
pack: SemContextPack;
|
|
24
|
+
patterns: readonly StupifyCheck[];
|
|
25
|
+
includeCounterReasonInPrompt?: boolean;
|
|
26
|
+
}>): SearchRequest {
|
|
90
27
|
return {
|
|
91
|
-
prompt:
|
|
92
|
-
|
|
28
|
+
prompt: searchPrompt({
|
|
29
|
+
...input,
|
|
30
|
+
includeCounterReason: input.includeCounterReasonInPrompt ?? false,
|
|
31
|
+
}),
|
|
32
|
+
schema: searchSchema(input.contexts),
|
|
33
|
+
contexts: input.contexts,
|
|
93
34
|
};
|
|
94
35
|
}
|
|
95
36
|
|
|
@@ -119,214 +60,53 @@ export async function countPromptTokens(model: LocalModel, prompt: string): Prom
|
|
|
119
60
|
return cached.count;
|
|
120
61
|
}
|
|
121
62
|
|
|
122
|
-
function
|
|
123
|
-
const targetIds = contexts.map((context) => context.targetId);
|
|
124
|
-
const findingItem = {
|
|
125
|
-
type: "object",
|
|
126
|
-
properties: {
|
|
127
|
-
targetId: { type: "string", enum: targetIds },
|
|
128
|
-
why: { type: "string" },
|
|
129
|
-
proof: { type: "string" },
|
|
130
|
-
},
|
|
131
|
-
required: ["targetId", "why", "proof"],
|
|
132
|
-
additionalProperties: false,
|
|
133
|
-
};
|
|
134
|
-
const uncertainItem = {
|
|
135
|
-
type: "object",
|
|
136
|
-
properties: {
|
|
137
|
-
targetId: { type: "string", enum: targetIds },
|
|
138
|
-
why: { type: "string" },
|
|
139
|
-
},
|
|
140
|
-
required: ["targetId", "why"],
|
|
141
|
-
additionalProperties: false,
|
|
142
|
-
};
|
|
143
|
-
return {
|
|
144
|
-
type: "object",
|
|
145
|
-
properties: {
|
|
146
|
-
findings: {
|
|
147
|
-
type: "array",
|
|
148
|
-
items: findingItem,
|
|
149
|
-
},
|
|
150
|
-
uncertain: {
|
|
151
|
-
type: "array",
|
|
152
|
-
items: uncertainItem,
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
additionalProperties: false,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function auditSchema(contexts: readonly CandidateContext[]): unknown {
|
|
160
|
-
return auditSchemaFromProofs(contexts.map((context) => context.pointer));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function auditSchemaFromProofs(proofs: readonly string[]): unknown {
|
|
164
|
-
return {
|
|
165
|
-
type: "object",
|
|
166
|
-
properties: {
|
|
167
|
-
findings: {
|
|
168
|
-
type: "array",
|
|
169
|
-
items: {
|
|
170
|
-
type: "object",
|
|
171
|
-
properties: {
|
|
172
|
-
checkId: { type: "string" },
|
|
173
|
-
why: { type: "string" },
|
|
174
|
-
proof: { type: "string", enum: proofs },
|
|
175
|
-
},
|
|
176
|
-
required: ["checkId", "why", "proof"],
|
|
177
|
-
additionalProperties: false,
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
summary: { type: "string" },
|
|
181
|
-
},
|
|
182
|
-
required: ["findings", "summary"],
|
|
183
|
-
additionalProperties: false,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function semScoutSchema(
|
|
188
|
-
changeSet: SemChangeSet,
|
|
189
|
-
checks: readonly StupifyCheck[],
|
|
190
|
-
maxCandidates: number,
|
|
191
|
-
): unknown {
|
|
63
|
+
function searchSchema(contexts: readonly SemContext[]): unknown {
|
|
192
64
|
return {
|
|
193
65
|
type: "object",
|
|
194
66
|
properties: {
|
|
195
|
-
|
|
67
|
+
matches: {
|
|
196
68
|
type: "array",
|
|
197
|
-
maxItems:
|
|
69
|
+
maxItems: 5,
|
|
198
70
|
items: {
|
|
199
71
|
type: "object",
|
|
200
72
|
properties: {
|
|
201
|
-
|
|
202
|
-
checkId: { type: "string", enum: checks.map((check) => check.id) },
|
|
73
|
+
targetId: { type: "string", enum: contexts.map((context) => context.targetId) },
|
|
203
74
|
reason: { type: "string" },
|
|
75
|
+
proof: { type: "string" },
|
|
204
76
|
},
|
|
205
|
-
required: ["
|
|
77
|
+
required: ["targetId", "reason", "proof"],
|
|
206
78
|
additionalProperties: false,
|
|
207
79
|
},
|
|
208
80
|
},
|
|
209
81
|
},
|
|
210
|
-
required: ["
|
|
211
|
-
additionalProperties: false,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function scoutSchema(batch: DiffBatch): unknown {
|
|
216
|
-
return {
|
|
217
|
-
type: "object",
|
|
218
|
-
properties: {
|
|
219
|
-
candidates: {
|
|
220
|
-
type: "array",
|
|
221
|
-
maxItems: 3,
|
|
222
|
-
items: { type: "string", enum: batch.hunks.map((hunk) => hunk.pointer) },
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
required: ["candidates"],
|
|
82
|
+
required: ["matches"],
|
|
226
83
|
additionalProperties: false,
|
|
227
84
|
};
|
|
228
85
|
}
|
|
229
86
|
|
|
230
|
-
type
|
|
231
|
-
|
|
232
|
-
findings?: readonly RawFinding[];
|
|
233
|
-
summary?: string;
|
|
87
|
+
type RawSearchOutput = Readonly<{
|
|
88
|
+
matches?: readonly RawSearchMatch[];
|
|
234
89
|
}>;
|
|
235
|
-
type
|
|
236
|
-
targets?: readonly RawSemCandidate[];
|
|
237
|
-
candidates?: readonly RawSemCandidate[];
|
|
238
|
-
}>;
|
|
239
|
-
type RawSemCandidate = Readonly<{
|
|
90
|
+
type RawSearchMatch = Readonly<{
|
|
240
91
|
targetId?: string;
|
|
241
|
-
entityId?: string;
|
|
242
|
-
checkId?: string;
|
|
243
|
-
checkIds?: readonly string[];
|
|
244
92
|
reason?: string;
|
|
245
|
-
}>;
|
|
246
|
-
type RawFinding = Readonly<{
|
|
247
|
-
checkId?: string;
|
|
248
|
-
why?: string;
|
|
249
93
|
proof?: string;
|
|
250
94
|
}>;
|
|
251
|
-
type RawFindingReview = RawFinding & Readonly<{ targetId?: string }>;
|
|
252
|
-
type RawFindingsAuditOutput = Readonly<{
|
|
253
|
-
findings?: readonly RawFindingReview[];
|
|
254
|
-
uncertain?: readonly RawFindingReview[];
|
|
255
|
-
}>;
|
|
256
|
-
|
|
257
|
-
function uncheckedCandidates(value: unknown): readonly string[] {
|
|
258
|
-
return [...((value as RawScoutOutput).candidates ?? [])];
|
|
259
|
-
}
|
|
260
95
|
|
|
261
|
-
function
|
|
262
|
-
const output = value as
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
const rawTargets = output.targets ?? output.candidates ?? [];
|
|
275
|
-
return rawTargets.flatMap((candidate) => {
|
|
276
|
-
if (candidate.checkId) {
|
|
277
|
-
return [{
|
|
278
|
-
sourceId,
|
|
279
|
-
targetId: candidate.targetId ?? "",
|
|
280
|
-
entityId: candidate.entityId ?? "",
|
|
281
|
-
checkId: candidate.checkId as CheckId,
|
|
282
|
-
reason: candidate.reason ?? "",
|
|
283
|
-
}];
|
|
284
|
-
}
|
|
285
|
-
return (candidate.checkIds ?? []).map((checkId) => ({
|
|
286
|
-
sourceId,
|
|
287
|
-
targetId: candidate.targetId ?? "",
|
|
288
|
-
entityId: candidate.entityId ?? "",
|
|
289
|
-
checkId: checkId as CheckId,
|
|
290
|
-
reason: candidate.reason ?? "",
|
|
291
|
-
}));
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function uncheckedFindingsAuditResult(
|
|
296
|
-
value: unknown,
|
|
297
|
-
sourceId: SourceId,
|
|
298
|
-
contexts: readonly SemContext[],
|
|
299
|
-
): AuditReviewResult {
|
|
300
|
-
const output = value as RawFindingsAuditOutput;
|
|
301
|
-
const targetsById = new Map(contexts.map((context) => [context.targetId, context]));
|
|
302
|
-
const findings = (output.findings ?? []).map((finding): Finding => {
|
|
303
|
-
const target = targetsById.get(finding.targetId ?? "");
|
|
304
|
-
return {
|
|
305
|
-
sourceId,
|
|
306
|
-
checkId: (target?.checkId ?? "") as CheckId,
|
|
307
|
-
why: finding.why ?? "",
|
|
308
|
-
proof: finding.proof ?? "",
|
|
309
|
-
};
|
|
96
|
+
function uncheckedSearchMatches(value: unknown, contexts: readonly SemContext[]): readonly SearchMatch[] {
|
|
97
|
+
const output = value as RawSearchOutput;
|
|
98
|
+
const contextsByTargetId = new Map(contexts.map((context) => [context.targetId, context]));
|
|
99
|
+
return (output.matches ?? []).flatMap((match): readonly SearchMatch[] => {
|
|
100
|
+
const targetId = match.targetId ?? "";
|
|
101
|
+
const context = contextsByTargetId.get(targetId);
|
|
102
|
+
if (!context) return [];
|
|
103
|
+
return [{
|
|
104
|
+
targetId,
|
|
105
|
+
patternId: context.checkId,
|
|
106
|
+
reason: match.reason ?? "",
|
|
107
|
+
proof: match.proof ?? "",
|
|
108
|
+
}];
|
|
310
109
|
});
|
|
311
|
-
const uncertain = output.uncertain?.length ?? 0;
|
|
312
|
-
const totalTargets = contexts.length;
|
|
313
|
-
return {
|
|
314
|
-
findings,
|
|
315
|
-
summary: defaultSummary(findings.length),
|
|
316
|
-
stats: {
|
|
317
|
-
totalTargets,
|
|
318
|
-
finding: findings.length,
|
|
319
|
-
clean: Math.max(0, totalTargets - findings.length - uncertain),
|
|
320
|
-
uncertain,
|
|
321
|
-
invalid: 0,
|
|
322
|
-
},
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function defaultSummary(findingCount: number): string {
|
|
327
|
-
return findingCount === 0
|
|
328
|
-
? "No clear judgment-offload signal found."
|
|
329
|
-
: `${findingCount} finding review${findingCount === 1 ? "" : "s"} accepted.`;
|
|
330
110
|
}
|
|
331
111
|
|
|
332
112
|
async function runJsonPrompt(
|
package/src/checks.ts
CHANGED
|
@@ -11,7 +11,19 @@ export const defaultChecks: readonly StupifyCheck[] = [
|
|
|
11
11
|
],
|
|
12
12
|
ignoreWhen: [
|
|
13
13
|
"test fixture, mock, or intentional external contract",
|
|
14
|
-
|
|
14
|
+
"public API DTO filters, omits, protects, renames, or versions fields",
|
|
15
|
+
],
|
|
16
|
+
hookMode: "warn",
|
|
17
|
+
searchPrompt: "Find only local/private payload or schema shapes that clearly copy another local shape one-for-one without creating a boundary. Do not match ordinary Input/Output/Request/Response types by name alone, public DTOs, external contracts, client types, or types that omit/protect private fields.",
|
|
18
|
+
searchExamples: {
|
|
19
|
+
match: [
|
|
20
|
+
"LocalUserPayload repeats User fields and maps id/email/displayName one-for-one.",
|
|
21
|
+
],
|
|
22
|
+
nonMatch: [
|
|
23
|
+
"PublicWebhookDto omits privateNotes from InternalJob.",
|
|
24
|
+
"A client type describes an external dependency boundary.",
|
|
25
|
+
],
|
|
26
|
+
},
|
|
15
27
|
},
|
|
16
28
|
{
|
|
17
29
|
id: checkId("unnecessary_complexity"),
|
|
@@ -23,6 +35,35 @@ export const defaultChecks: readonly StupifyCheck[] = [
|
|
|
23
35
|
ignoreWhen: [
|
|
24
36
|
"isolates dependency, removes duplication, or improves testability",
|
|
25
37
|
],
|
|
38
|
+
hookMode: "warn",
|
|
39
|
+
searchPrompt: `Find staged changes where a locally simple decision is made harder to understand by new indirection.
|
|
40
|
+
Only match when the staged diff clearly shows:
|
|
41
|
+
- a new named helper, wrapper, service, adapter, boundary, or abstraction
|
|
42
|
+
- and the surrounding change still appears locally simple
|
|
43
|
+
- and the new structure makes the decision harder to see
|
|
44
|
+
Do not match:
|
|
45
|
+
- plain conditionals, guard clauses, skip paths, or error handling
|
|
46
|
+
- normal feature structure
|
|
47
|
+
- exported utilities that are part of a real feature
|
|
48
|
+
- command plumbing
|
|
49
|
+
- prompt/instruction files
|
|
50
|
+
- domain configuration
|
|
51
|
+
- refactors that make ownership clearer
|
|
52
|
+
- changes where the payoff is unclear from the diff
|
|
53
|
+
Prefer no match over a weak match.`,
|
|
54
|
+
searchExamples: {
|
|
55
|
+
match: [
|
|
56
|
+
"A small inline operation becomes a helper/service/wrapper with one obvious caller.",
|
|
57
|
+
"A straightforward flow is split across files in a way that hides the decision.",
|
|
58
|
+
"A new abstraction appears before there is evidence it buys clarity, correctness, reuse, or isolation.",
|
|
59
|
+
],
|
|
60
|
+
nonMatch: [
|
|
61
|
+
"A real external dependency boundary is isolated.",
|
|
62
|
+
"A security/auth boundary becomes clearer.",
|
|
63
|
+
"A refactor removes larger complexity elsewhere.",
|
|
64
|
+
"Framework-required structure is added.",
|
|
65
|
+
],
|
|
66
|
+
},
|
|
26
67
|
},
|
|
27
68
|
{
|
|
28
69
|
id: checkId("fake_precision_windowing"),
|
|
@@ -68,6 +109,22 @@ export const defaultChecks: readonly StupifyCheck[] = [
|
|
|
68
109
|
ignoreWhen: [
|
|
69
110
|
"comment explains intent, constraint, workaround, or public API behavior",
|
|
70
111
|
],
|
|
112
|
+
hookMode: "warn",
|
|
113
|
+
searchPrompt: "Find staged changes where comments appear to substitute for judgment rather than clarify it.",
|
|
114
|
+
searchExamples: {
|
|
115
|
+
match: [
|
|
116
|
+
"New comments narrate obvious code instead of explaining tradeoffs.",
|
|
117
|
+
"A simple change gains multiple generic comments that restate control flow.",
|
|
118
|
+
"Comments make the code look more deliberate without adding useful reasoning.",
|
|
119
|
+
],
|
|
120
|
+
nonMatch: [
|
|
121
|
+
"Comments explain a real domain constraint.",
|
|
122
|
+
"Comments document an external API quirk.",
|
|
123
|
+
"Comments clarify a surprising edge case.",
|
|
124
|
+
"Comments are sparse and specific.",
|
|
125
|
+
"Comments explain provider, finance, reconciliation, timezone, or ledger behavior.",
|
|
126
|
+
],
|
|
127
|
+
},
|
|
71
128
|
},
|
|
72
129
|
{
|
|
73
130
|
id: checkId("lint_bypass"),
|
|
@@ -81,6 +138,16 @@ export const defaultChecks: readonly StupifyCheck[] = [
|
|
|
81
138
|
"type-level test",
|
|
82
139
|
"generated file convention",
|
|
83
140
|
],
|
|
141
|
+
hookMode: "warn",
|
|
142
|
+
searchPrompt: "Find only broad lint/type bypasses that hide useful feedback. Match bare @ts-ignore, bare @ts-expect-error, broad casts, any, or eslint/biome suppressions without a concrete inline reason. Do not match targeted suppressions that include a reason for a known framework, test, mock, or external-library limitation.",
|
|
143
|
+
searchExamples: {
|
|
144
|
+
match: [
|
|
145
|
+
"A bare // @ts-ignore hides property access on unknown input.",
|
|
146
|
+
],
|
|
147
|
+
nonMatch: [
|
|
148
|
+
"// @ts-expect-error explains a known external library typing gap.",
|
|
149
|
+
],
|
|
150
|
+
},
|
|
84
151
|
},
|
|
85
152
|
{
|
|
86
153
|
id: checkId("inconsistent_patterns"),
|
|
@@ -104,7 +171,19 @@ export const defaultChecks: readonly StupifyCheck[] = [
|
|
|
104
171
|
ignoreWhen: [
|
|
105
172
|
"existing utility has wrong contract",
|
|
106
173
|
"new helper is clearer as a tiny private expression",
|
|
107
|
-
|
|
174
|
+
"helper is domain-specific or used by multiple local call sites",
|
|
175
|
+
],
|
|
176
|
+
hookMode: "warn",
|
|
177
|
+
searchPrompt: "Find only tiny generic utility functions that recreate common helpers such as clamp, debounce, throttle, slugify, group, sort, pick, omit, uniq, or shuffle without domain-specific behavior. Do not match resolve/parse/format helpers, domain formatting, feature constants, or helpers with multiple obvious call sites.",
|
|
178
|
+
searchExamples: {
|
|
179
|
+
match: [
|
|
180
|
+
"clampValue returns min, max, or value.",
|
|
181
|
+
],
|
|
182
|
+
nonMatch: [
|
|
183
|
+
"formatCurrencyHelper is used by invoice and refund labels.",
|
|
184
|
+
"Subscription tier constants encode domain configuration.",
|
|
185
|
+
],
|
|
186
|
+
},
|
|
108
187
|
},
|
|
109
188
|
{
|
|
110
189
|
id: checkId("operator_style_mismatch"),
|
|
@@ -123,6 +202,16 @@ export const defaultChecks: readonly StupifyCheck[] = [
|
|
|
123
202
|
export function enabledChecks(checkIds: readonly string[] | null): readonly StupifyCheck[] {
|
|
124
203
|
if (!checkIds) return defaultChecks.filter((check) => check.enabledByDefault !== false);
|
|
125
204
|
|
|
205
|
+
return checksById(checkIds);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function searchChecks(checkIds: readonly string[] | null): readonly StupifyCheck[] {
|
|
209
|
+
if (!checkIds) return defaultChecks.filter((check) => check.hookMode === "warn");
|
|
210
|
+
|
|
211
|
+
return checksById(checkIds);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function checksById(checkIds: readonly string[]): readonly StupifyCheck[] {
|
|
126
215
|
const checksById = new Map<string, StupifyCheck>(defaultChecks.map((check) => [check.id, check]));
|
|
127
216
|
return checkIds.map((id) => {
|
|
128
217
|
const check = checksById.get(id);
|