@stupify/cli 0.0.2 → 0.0.3
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 +60 -0
- package/dist/analysis.d.ts +14 -0
- package/dist/analysis.js +276 -0
- package/dist/batcher.d.ts +3 -0
- package/dist/batcher.js +142 -0
- package/dist/cache.d.ts +2 -0
- package/dist/cache.js +59 -0
- package/dist/candidate-context.d.ts +2 -0
- package/dist/candidate-context.js +40 -0
- package/dist/checks.d.ts +3 -0
- package/dist/checks.js +131 -0
- package/dist/command.d.ts +2 -0
- package/dist/command.js +183 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +53 -0
- package/dist/counter-scout.d.ts +14 -0
- package/dist/counter-scout.js +97 -0
- package/dist/diff.d.ts +1 -0
- package/dist/diff.js +10 -0
- package/dist/experiment.d.ts +1 -0
- package/dist/experiment.js +225 -0
- package/dist/git.d.ts +8 -0
- package/dist/git.js +219 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/model.d.ts +24 -0
- package/dist/model.js +281 -0
- package/dist/prompts.d.ts +5 -0
- package/dist/prompts.js +197 -0
- package/dist/render.d.ts +3 -0
- package/dist/render.js +101 -0
- package/dist/repomix-provider.d.ts +4 -0
- package/dist/repomix-provider.js +145 -0
- package/dist/sem-provider.d.ts +2 -0
- package/dist/sem-provider.js +221 -0
- package/dist/stupify.d.ts +2 -0
- package/dist/stupify.js +387 -0
- package/dist/trace.d.ts +29 -0
- package/dist/trace.js +64 -0
- package/dist/types.d.ts +236 -0
- package/dist/types.js +6 -0
- package/package.json +42 -5
- package/src/analysis.ts +408 -0
- package/src/batcher.ts +198 -0
- package/src/cache.ts +65 -0
- package/src/candidate-context.ts +43 -0
- package/src/checks.ts +132 -0
- package/src/command.ts +218 -0
- package/src/constants.ts +56 -0
- package/src/counter-scout.ts +119 -0
- package/src/diff.ts +9 -0
- package/src/experiment.ts +317 -0
- package/src/git.ts +228 -0
- package/src/index.ts +1 -0
- package/src/model.ts +360 -0
- package/src/prompts.ts +234 -0
- package/src/render.ts +107 -0
- package/src/repomix-provider.ts +163 -0
- package/src/sem-provider.ts +255 -0
- package/src/stupify.ts +598 -0
- package/src/trace.ts +103 -0
- package/src/types.ts +264 -0
- package/bin/stupify.mjs +0 -3
package/dist/checks.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { checkId } from "./types.js";
|
|
2
|
+
export const defaultChecks = [
|
|
3
|
+
{
|
|
4
|
+
id: checkId("duplicated_schema"),
|
|
5
|
+
name: "Duplicated schema",
|
|
6
|
+
question: "Did the change duplicate an existing type, schema, payload, or DTO shape?",
|
|
7
|
+
lookFor: [
|
|
8
|
+
"local shape mirrors existing fields and maps them one-for-one",
|
|
9
|
+
"new response, payload, schema, or DTO adds no filtering, renaming, validation, or versioning",
|
|
10
|
+
],
|
|
11
|
+
ignoreWhen: [
|
|
12
|
+
"test fixture, mock, or intentional external contract",
|
|
13
|
+
],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: checkId("unnecessary_complexity"),
|
|
17
|
+
name: "Unnecessary complexity",
|
|
18
|
+
question: "Did the change add structure without buying clarity?",
|
|
19
|
+
lookFor: [
|
|
20
|
+
"helper, wrapper, service, layer, or extra file around simple logic without reuse",
|
|
21
|
+
],
|
|
22
|
+
ignoreWhen: [
|
|
23
|
+
"isolates dependency, removes duplication, or improves testability",
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: checkId("fake_precision_windowing"),
|
|
28
|
+
name: "Fake precision windowing",
|
|
29
|
+
question: "Did the change add fake precision around model context?",
|
|
30
|
+
lookFor: [
|
|
31
|
+
"precise-looking counts, budgets, ratios, reports, or batching fields without useful behavior",
|
|
32
|
+
],
|
|
33
|
+
ignoreWhen: [
|
|
34
|
+
"simple fixed cap or chunking",
|
|
35
|
+
"external API requirement",
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: checkId("coauthored_slop"),
|
|
40
|
+
name: "Coauthored slop",
|
|
41
|
+
question: "Does author metadata contain co-author text?",
|
|
42
|
+
lookFor: [
|
|
43
|
+
"author signal contains coauhtoried, coauthored, or co-authored text",
|
|
44
|
+
],
|
|
45
|
+
ignoreWhen: [
|
|
46
|
+
"normal Co-authored-by trailer in the commit body",
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: checkId("mega_file"),
|
|
51
|
+
name: "Mega file",
|
|
52
|
+
question: "Is a touched non-config file over 1000 LOC?",
|
|
53
|
+
lookFor: [
|
|
54
|
+
"touched non-config source file over 1000 LOC",
|
|
55
|
+
],
|
|
56
|
+
ignoreWhen: [
|
|
57
|
+
"config, lock, generated, fixture, or vendored file",
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: checkId("over_commenting"),
|
|
62
|
+
name: "Over commenting",
|
|
63
|
+
question: "Did the change add noisy comments?",
|
|
64
|
+
lookFor: [
|
|
65
|
+
"comments restate obvious code or narrate simple logic",
|
|
66
|
+
],
|
|
67
|
+
ignoreWhen: [
|
|
68
|
+
"comment explains intent, constraint, workaround, or public API behavior",
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: checkId("lint_bypass"),
|
|
73
|
+
name: "Lint bypass",
|
|
74
|
+
question: "Did the change bypass lint or type rules?",
|
|
75
|
+
lookFor: [
|
|
76
|
+
"adds suppressions, any, broad casts, or weakens lint/typecheck config",
|
|
77
|
+
],
|
|
78
|
+
ignoreWhen: [
|
|
79
|
+
"narrow suppression with a reason",
|
|
80
|
+
"type-level test",
|
|
81
|
+
"generated file convention",
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: checkId("inconsistent_patterns"),
|
|
86
|
+
name: "Inconsistent patterns",
|
|
87
|
+
question: "Does the change clash with nearby patterns?",
|
|
88
|
+
lookFor: [
|
|
89
|
+
"same job uses different naming, errors, state, imports, or layout than nearby files",
|
|
90
|
+
],
|
|
91
|
+
ignoreWhen: [
|
|
92
|
+
"external API requires it",
|
|
93
|
+
"change follows a newer local convention",
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: checkId("reinvented_utils"),
|
|
98
|
+
name: "Reinvented utils",
|
|
99
|
+
question: "Did the change recreate an existing utility?",
|
|
100
|
+
lookFor: [
|
|
101
|
+
"new helper duplicates local utility or standard library behavior",
|
|
102
|
+
],
|
|
103
|
+
ignoreWhen: [
|
|
104
|
+
"existing utility has wrong contract",
|
|
105
|
+
"new helper is clearer as a tiny private expression",
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: checkId("operator_style_mismatch"),
|
|
110
|
+
name: "Operator style mismatch",
|
|
111
|
+
question: "Does the change read unlike the surrounding code?",
|
|
112
|
+
lookFor: [
|
|
113
|
+
"generic or template-like names, abstractions, comments, or control flow clash with local style",
|
|
114
|
+
],
|
|
115
|
+
ignoreWhen: [
|
|
116
|
+
"generated, vendored, framework-required, or newer established local style",
|
|
117
|
+
],
|
|
118
|
+
enabledByDefault: false,
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
export function enabledChecks(checkIds) {
|
|
122
|
+
if (!checkIds)
|
|
123
|
+
return defaultChecks.filter((check) => check.enabledByDefault !== false);
|
|
124
|
+
const checksById = new Map(defaultChecks.map((check) => [check.id, check]));
|
|
125
|
+
return checkIds.map((id) => {
|
|
126
|
+
const check = checksById.get(id);
|
|
127
|
+
if (!check)
|
|
128
|
+
throw new Error(`Unknown check: ${id}`);
|
|
129
|
+
return check;
|
|
130
|
+
});
|
|
131
|
+
}
|
package/dist/command.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { DEFAULT_MODEL_ID, MODEL_REGISTRY } from "./constants.js";
|
|
2
|
+
const DEFAULT_SINCE = "2 weeks ago";
|
|
3
|
+
const DEFAULT_MAX_CANDIDATES = 10;
|
|
4
|
+
const DEFAULT_SCOUT_MODE = "counter";
|
|
5
|
+
const DEFAULT_AUDIT_CONTEXT = "repomix";
|
|
6
|
+
const DEFAULT_AUDIT_PROMPT = "high_bar";
|
|
7
|
+
const DEFAULT_AUDIT_BATCH_SIZE = 25;
|
|
8
|
+
const DEFAULT_MAX_AUDIT_INPUT_TOKENS = 20_000;
|
|
9
|
+
const DEFAULT_AUDIT_CONCURRENCY = 2;
|
|
10
|
+
export function parseCommand(argv) {
|
|
11
|
+
if (argv.length === 1 && isHelp(argv[0]))
|
|
12
|
+
return { kind: "help" };
|
|
13
|
+
if (argv[0] === "experiment") {
|
|
14
|
+
const configPath = argv[1];
|
|
15
|
+
if (!configPath || argv.length > 2)
|
|
16
|
+
throw new Error("Usage: stupify experiment <config.json>");
|
|
17
|
+
return { kind: "experiment", configPath };
|
|
18
|
+
}
|
|
19
|
+
const initialState = {
|
|
20
|
+
inputMode: { kind: "since", since: DEFAULT_SINCE },
|
|
21
|
+
explicitInputMode: false,
|
|
22
|
+
checkIds: null,
|
|
23
|
+
json: false,
|
|
24
|
+
model: DEFAULT_MODEL_ID,
|
|
25
|
+
engine: "raw-diff",
|
|
26
|
+
scout: DEFAULT_SCOUT_MODE,
|
|
27
|
+
auditContext: DEFAULT_AUDIT_CONTEXT,
|
|
28
|
+
auditPrompt: DEFAULT_AUDIT_PROMPT,
|
|
29
|
+
debugSem: false,
|
|
30
|
+
debugTargets: false,
|
|
31
|
+
maxCandidates: DEFAULT_MAX_CANDIDATES,
|
|
32
|
+
auditBatchSize: DEFAULT_AUDIT_BATCH_SIZE,
|
|
33
|
+
maxAuditInputTokens: DEFAULT_MAX_AUDIT_INPUT_TOKENS,
|
|
34
|
+
auditConcurrency: DEFAULT_AUDIT_CONCURRENCY,
|
|
35
|
+
};
|
|
36
|
+
const finalState = parseFrom(0, initialState);
|
|
37
|
+
return {
|
|
38
|
+
...finalState.inputMode,
|
|
39
|
+
checkIds: finalState.checkIds,
|
|
40
|
+
json: finalState.json,
|
|
41
|
+
model: finalState.model,
|
|
42
|
+
engine: finalState.engine,
|
|
43
|
+
scout: finalState.scout,
|
|
44
|
+
auditContext: finalState.auditContext,
|
|
45
|
+
auditPrompt: finalState.auditPrompt,
|
|
46
|
+
debugSem: finalState.debugSem,
|
|
47
|
+
debugTargets: finalState.debugTargets,
|
|
48
|
+
maxCandidates: finalState.maxCandidates,
|
|
49
|
+
auditBatchSize: finalState.auditBatchSize,
|
|
50
|
+
maxAuditInputTokens: finalState.maxAuditInputTokens,
|
|
51
|
+
auditConcurrency: finalState.auditConcurrency,
|
|
52
|
+
};
|
|
53
|
+
function parseFrom(index, state) {
|
|
54
|
+
if (index >= argv.length)
|
|
55
|
+
return state;
|
|
56
|
+
const arg = argv[index];
|
|
57
|
+
if (arg === "--stdin")
|
|
58
|
+
return parseFrom(index + 1, setInputMode(state, { kind: "stdin" }));
|
|
59
|
+
if (arg === "--json")
|
|
60
|
+
return parseFrom(index + 1, { ...state, json: true });
|
|
61
|
+
if (arg === "--debug-sem")
|
|
62
|
+
return parseFrom(index + 1, { ...state, debugSem: true });
|
|
63
|
+
if (arg === "--debug-targets")
|
|
64
|
+
return parseFrom(index + 1, { ...state, debugTargets: true });
|
|
65
|
+
if (arg === "--since") {
|
|
66
|
+
const value = argv[index + 1];
|
|
67
|
+
if (!value || value.startsWith("-"))
|
|
68
|
+
throw new Error("--since requires a git date, such as \"2 weeks ago\".");
|
|
69
|
+
return parseFrom(index + 2, setInputMode(state, { kind: "since", since: value }));
|
|
70
|
+
}
|
|
71
|
+
if (arg === "--commit") {
|
|
72
|
+
const value = argv[index + 1];
|
|
73
|
+
if (!value || !isSafeCommitArg(value))
|
|
74
|
+
throw new Error("Invalid commit.");
|
|
75
|
+
return parseFrom(index + 2, setInputMode(state, { kind: "commit", commit: value }));
|
|
76
|
+
}
|
|
77
|
+
if (arg === "--commits") {
|
|
78
|
+
const value = argv[index + 1];
|
|
79
|
+
const count = Number(value);
|
|
80
|
+
if (!Number.isInteger(count) || count < 1)
|
|
81
|
+
throw new Error("--commits requires a positive integer.");
|
|
82
|
+
return parseFrom(index + 2, setInputMode(state, { kind: "commits", count }));
|
|
83
|
+
}
|
|
84
|
+
if (arg === "--checks") {
|
|
85
|
+
const value = argv[index + 1];
|
|
86
|
+
if (!value || value.startsWith("-"))
|
|
87
|
+
throw new Error("--checks requires a comma-separated list.");
|
|
88
|
+
const checkIds = value.split(",").map((id) => id.trim()).filter(Boolean);
|
|
89
|
+
if (checkIds.length === 0)
|
|
90
|
+
throw new Error("--checks requires at least one check id.");
|
|
91
|
+
return parseFrom(index + 2, { ...state, checkIds });
|
|
92
|
+
}
|
|
93
|
+
if (arg === "--model") {
|
|
94
|
+
const value = argv[index + 1];
|
|
95
|
+
if (!value || !isModelId(value))
|
|
96
|
+
throw new Error(`--model must be one of: ${Object.keys(MODEL_REGISTRY).join(", ")}`);
|
|
97
|
+
return parseFrom(index + 2, { ...state, model: value });
|
|
98
|
+
}
|
|
99
|
+
if (arg === "--engine") {
|
|
100
|
+
const value = argv[index + 1];
|
|
101
|
+
if (!value || !isEngine(value))
|
|
102
|
+
throw new Error("--engine must be raw-diff or sem.");
|
|
103
|
+
return parseFrom(index + 2, { ...state, engine: value });
|
|
104
|
+
}
|
|
105
|
+
if (arg === "--scout") {
|
|
106
|
+
const value = argv[index + 1];
|
|
107
|
+
if (!value || !isScoutMode(value))
|
|
108
|
+
throw new Error("--scout must be counter or llm.");
|
|
109
|
+
return parseFrom(index + 2, { ...state, scout: value });
|
|
110
|
+
}
|
|
111
|
+
if (arg === "--audit-context") {
|
|
112
|
+
const value = argv[index + 1];
|
|
113
|
+
if (!value || !isAuditContextMode(value))
|
|
114
|
+
throw new Error("--audit-context must be none or repomix.");
|
|
115
|
+
return parseFrom(index + 2, { ...state, auditContext: value });
|
|
116
|
+
}
|
|
117
|
+
if (arg === "--audit-prompt") {
|
|
118
|
+
const value = argv[index + 1];
|
|
119
|
+
if (!value || !isAuditPromptName(value))
|
|
120
|
+
throw new Error("--audit-prompt must be strict or high_bar.");
|
|
121
|
+
return parseFrom(index + 2, { ...state, auditPrompt: value });
|
|
122
|
+
}
|
|
123
|
+
if (arg === "--max-candidates") {
|
|
124
|
+
const value = argv[index + 1];
|
|
125
|
+
const maxCandidates = Number(value);
|
|
126
|
+
if (!Number.isInteger(maxCandidates) || maxCandidates < 1) {
|
|
127
|
+
throw new Error("--max-candidates requires a positive integer.");
|
|
128
|
+
}
|
|
129
|
+
return parseFrom(index + 2, { ...state, maxCandidates });
|
|
130
|
+
}
|
|
131
|
+
if (arg === "--audit-batch-size") {
|
|
132
|
+
const value = argv[index + 1];
|
|
133
|
+
const auditBatchSize = Number(value);
|
|
134
|
+
if (!Number.isInteger(auditBatchSize) || auditBatchSize < 1) {
|
|
135
|
+
throw new Error("--audit-batch-size requires a positive integer.");
|
|
136
|
+
}
|
|
137
|
+
return parseFrom(index + 2, { ...state, auditBatchSize });
|
|
138
|
+
}
|
|
139
|
+
if (arg === "--max-audit-input-tokens") {
|
|
140
|
+
const value = argv[index + 1];
|
|
141
|
+
const maxAuditInputTokens = Number(value);
|
|
142
|
+
if (!Number.isInteger(maxAuditInputTokens) || maxAuditInputTokens < 1) {
|
|
143
|
+
throw new Error("--max-audit-input-tokens requires a positive integer.");
|
|
144
|
+
}
|
|
145
|
+
return parseFrom(index + 2, { ...state, maxAuditInputTokens });
|
|
146
|
+
}
|
|
147
|
+
if (arg === "--audit-concurrency") {
|
|
148
|
+
const value = argv[index + 1];
|
|
149
|
+
const auditConcurrency = Number(value);
|
|
150
|
+
if (!Number.isInteger(auditConcurrency) || auditConcurrency < 1) {
|
|
151
|
+
throw new Error("--audit-concurrency requires a positive integer.");
|
|
152
|
+
}
|
|
153
|
+
return parseFrom(index + 2, { ...state, auditConcurrency });
|
|
154
|
+
}
|
|
155
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
156
|
+
}
|
|
157
|
+
function setInputMode(state, next) {
|
|
158
|
+
if (state.explicitInputMode)
|
|
159
|
+
throw new Error("Choose only one input mode: --since, --stdin, --commit, or --commits.");
|
|
160
|
+
return { ...state, inputMode: next, explicitInputMode: true };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function isSafeCommitArg(value) {
|
|
164
|
+
return value.length > 0 && !value.startsWith("-") && /^[A-Za-z0-9._/@~^:+-]+$/.test(value);
|
|
165
|
+
}
|
|
166
|
+
function isHelp(value) {
|
|
167
|
+
return value === "--help" || value === "-h";
|
|
168
|
+
}
|
|
169
|
+
function isModelId(value) {
|
|
170
|
+
return value in MODEL_REGISTRY;
|
|
171
|
+
}
|
|
172
|
+
function isEngine(value) {
|
|
173
|
+
return value === "raw-diff" || value === "sem";
|
|
174
|
+
}
|
|
175
|
+
function isScoutMode(value) {
|
|
176
|
+
return value === "counter" || value === "llm";
|
|
177
|
+
}
|
|
178
|
+
function isAuditContextMode(value) {
|
|
179
|
+
return value === "none" || value === "repomix";
|
|
180
|
+
}
|
|
181
|
+
function isAuditPromptName(value) {
|
|
182
|
+
return value === "strict" || value === "high_bar";
|
|
183
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const VERSION = "0.0.3";
|
|
2
|
+
export const DEFAULT_MODEL_ID = "gemma-4-e2b";
|
|
3
|
+
export const MODEL_REGISTRY = {
|
|
4
|
+
"gemma-4-e2b": {
|
|
5
|
+
id: "gemma-4-e2b",
|
|
6
|
+
name: "Gemma 4 E2B Instruct Q4_K_M",
|
|
7
|
+
size: "about 3.1 GB",
|
|
8
|
+
file: "gemma-4-e2b-it-q4_k_m.gguf",
|
|
9
|
+
url: "https://huggingface.co/unsloth/gemma-4-E2B-it-GGUF/resolve/main/gemma-4-E2B-it-Q4_K_M.gguf?download=true",
|
|
10
|
+
},
|
|
11
|
+
"gemma-4-e4b": {
|
|
12
|
+
id: "gemma-4-e4b",
|
|
13
|
+
name: "Gemma 4 E4B Instruct Q4_K_M",
|
|
14
|
+
size: "about 5.0 GB",
|
|
15
|
+
file: "gemma-4-e4b-it-q4_k_m.gguf",
|
|
16
|
+
url: "https://huggingface.co/unsloth/gemma-4-E4B-it-GGUF/resolve/main/gemma-4-E4B-it-Q4_K_M.gguf?download=true",
|
|
17
|
+
},
|
|
18
|
+
"gemma-4-26b-a4b": {
|
|
19
|
+
id: "gemma-4-26b-a4b",
|
|
20
|
+
name: "Gemma 4 26B A4B Instruct UD-IQ2_XXS",
|
|
21
|
+
size: "about 9.9 GB",
|
|
22
|
+
file: "gemma-4-26b-a4b-it-ud-iq2_xxs.gguf",
|
|
23
|
+
url: "https://huggingface.co/unsloth/gemma-4-26B-A4B-it-GGUF/resolve/main/gemma-4-26B-A4B-it-UD-IQ2_XXS.gguf?download=true",
|
|
24
|
+
},
|
|
25
|
+
"qwen3-4b-magicquant": {
|
|
26
|
+
id: "qwen3-4b-magicquant",
|
|
27
|
+
name: "Qwen3-4B-Instruct-2507 MagicQuant Q4_K_M",
|
|
28
|
+
size: "about 2.4 GB",
|
|
29
|
+
file: "qwen3-4b-instruct-2507-magicquant-q4_k_m.gguf",
|
|
30
|
+
url: "https://huggingface.co/magiccodingman/Qwen3-4B-Instruct-2507-Unsloth-MagicQuant-v2-GGUF/resolve/main/Model-MQ-Q4_K_M_1.gguf?download=true",
|
|
31
|
+
},
|
|
32
|
+
"qwen2.5-coder-1.5b": {
|
|
33
|
+
id: "qwen2.5-coder-1.5b",
|
|
34
|
+
name: "Qwen2.5-Coder-1.5B-Instruct Q4_K_M",
|
|
35
|
+
size: "about 1.1 GB",
|
|
36
|
+
file: "qwen2.5-coder-1.5b-instruct-q4_k_m.gguf",
|
|
37
|
+
url: "https://huggingface.co/Qwen/Qwen2.5-Coder-1.5B-Instruct-GGUF/resolve/main/qwen2.5-coder-1.5b-instruct-q4_k_m.gguf?download=true",
|
|
38
|
+
},
|
|
39
|
+
"qwen2.5-coder-7b": {
|
|
40
|
+
id: "qwen2.5-coder-7b",
|
|
41
|
+
name: "Qwen2.5-Coder-7B-Instruct Q4_K_M",
|
|
42
|
+
size: "about 4.7 GB",
|
|
43
|
+
file: "qwen2.5-coder-7b-instruct-q4_k_m.gguf",
|
|
44
|
+
url: "https://huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct-GGUF/resolve/main/qwen2.5-coder-7b-instruct-q4_k_m.gguf?download=true",
|
|
45
|
+
},
|
|
46
|
+
"qwen2.5-coder-32b": {
|
|
47
|
+
id: "qwen2.5-coder-32b",
|
|
48
|
+
name: "Qwen2.5-Coder-32B-Instruct Q4_K_M",
|
|
49
|
+
size: "about 19 GB",
|
|
50
|
+
file: "qwen2.5-coder-32b-instruct-q4_k_m.gguf",
|
|
51
|
+
url: "https://huggingface.co/Qwen/Qwen2.5-Coder-32B-Instruct-GGUF/resolve/main/qwen2.5-coder-32b-instruct-q4_k_m.gguf?download=true",
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CheckId, SemCandidate, SemChangeSet, StupifyCheck } from "./types.ts";
|
|
2
|
+
type Signal = Readonly<{
|
|
3
|
+
checkId: CheckId;
|
|
4
|
+
entityId: string;
|
|
5
|
+
reasonCode: string;
|
|
6
|
+
}>;
|
|
7
|
+
type SignalBucket = Readonly<{
|
|
8
|
+
checkId: CheckId;
|
|
9
|
+
total: number;
|
|
10
|
+
examples: readonly Signal[];
|
|
11
|
+
}>;
|
|
12
|
+
export declare function counterScoutTargets(changeSet: SemChangeSet, checks: readonly StupifyCheck[], maxTargets: number): readonly SemCandidate[];
|
|
13
|
+
export declare function runSignalCounters(changeSet: SemChangeSet, checks: readonly StupifyCheck[]): readonly SignalBucket[];
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const MAX_COUNTER_EXAMPLES_PER_CHECK = 4;
|
|
2
|
+
export function counterScoutTargets(changeSet, checks, maxTargets) {
|
|
3
|
+
const buckets = runSignalCounters(changeSet, checks);
|
|
4
|
+
const targets = [];
|
|
5
|
+
let cursor = 0;
|
|
6
|
+
while (targets.length < maxTargets && buckets.some((bucket) => cursor < bucket.examples.length)) {
|
|
7
|
+
for (const bucket of buckets) {
|
|
8
|
+
const signal = bucket.examples[cursor];
|
|
9
|
+
if (!signal)
|
|
10
|
+
continue;
|
|
11
|
+
targets.push({
|
|
12
|
+
sourceId: changeSet.id,
|
|
13
|
+
targetId: `t${String(targets.length + 1).padStart(3, "0")}`,
|
|
14
|
+
entityId: signal.entityId,
|
|
15
|
+
checkId: signal.checkId,
|
|
16
|
+
reason: signal.reasonCode,
|
|
17
|
+
});
|
|
18
|
+
if (targets.length >= maxTargets)
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
cursor += 1;
|
|
22
|
+
}
|
|
23
|
+
return targets;
|
|
24
|
+
}
|
|
25
|
+
export function runSignalCounters(changeSet, checks) {
|
|
26
|
+
return checks
|
|
27
|
+
.map((check) => {
|
|
28
|
+
const signals = changeSet.changes.flatMap((change) => {
|
|
29
|
+
const reasonCode = reasonForCheck(check.id, change);
|
|
30
|
+
return reasonCode ? [{ checkId: check.id, entityId: change.entityId, reasonCode }] : [];
|
|
31
|
+
});
|
|
32
|
+
return {
|
|
33
|
+
checkId: check.id,
|
|
34
|
+
total: signals.length,
|
|
35
|
+
examples: signals.slice(0, MAX_COUNTER_EXAMPLES_PER_CHECK),
|
|
36
|
+
};
|
|
37
|
+
})
|
|
38
|
+
.filter((bucket) => bucket.total > 0);
|
|
39
|
+
}
|
|
40
|
+
function reasonForCheck(checkId, change) {
|
|
41
|
+
const haystack = `${change.entityName}\n${change.entityType}\n${change.filePath}\n${change.afterContent ?? ""}`.toLowerCase();
|
|
42
|
+
const changed = change.changeType === "added" || change.changeType === "modified";
|
|
43
|
+
if (!changed)
|
|
44
|
+
return null;
|
|
45
|
+
switch (checkId) {
|
|
46
|
+
case "duplicated_schema":
|
|
47
|
+
return isSchemaish(change, haystack) ? "schemaish_type_or_payload" : null;
|
|
48
|
+
case "unnecessary_complexity":
|
|
49
|
+
return /\b(helper|wrapper|service|provider|manager|factory|adapter|resolver|coordinator)\b/i.test(change.entityName)
|
|
50
|
+
? "new_abstraction_name"
|
|
51
|
+
: null;
|
|
52
|
+
case "fake_precision_windowing":
|
|
53
|
+
return /\b(token|budget|window|batch|ratio|estimate|counter|count|limit)\b/i.test(haystack)
|
|
54
|
+
? "precision_accounting_terms"
|
|
55
|
+
: null;
|
|
56
|
+
case "coauthored_slop":
|
|
57
|
+
return /\b(coauhtoried|coauthored|co-authored|co-authored-by)\b/i.test(haystack)
|
|
58
|
+
? "coauthor_text"
|
|
59
|
+
: null;
|
|
60
|
+
case "mega_file":
|
|
61
|
+
return change.entityType === "chunk" && /lines\s+\d+-\d+/i.test(change.entityName)
|
|
62
|
+
? "large_changed_chunk"
|
|
63
|
+
: null;
|
|
64
|
+
case "over_commenting":
|
|
65
|
+
return commentLines(change.afterContent) > commentLines(change.beforeContent) + 3
|
|
66
|
+
? "comment_lines_increased"
|
|
67
|
+
: null;
|
|
68
|
+
case "lint_bypass":
|
|
69
|
+
return /(eslint-disable|biome-ignore|@ts-ignore|@ts-expect-error|\bas unknown as\b|\bany\b)/i.test(change.afterContent ?? "")
|
|
70
|
+
? "lint_or_type_bypass_text"
|
|
71
|
+
: null;
|
|
72
|
+
case "inconsistent_patterns":
|
|
73
|
+
return /\b(manager|factory|provider|adapter|orchestrator|coordinator)\b/i.test(change.entityName)
|
|
74
|
+
? "pattern_abstraction_name"
|
|
75
|
+
: null;
|
|
76
|
+
case "reinvented_utils":
|
|
77
|
+
return /^(format|parse|normalize|group|sort|filter|find|has|get|set|is|resolve|clamp|slug)/i.test(change.entityName)
|
|
78
|
+
? "generic_utility_name"
|
|
79
|
+
: null;
|
|
80
|
+
case "operator_style_mismatch":
|
|
81
|
+
return /\b(manager|factory|provider|enterprise|orchestrator)\b/i.test(haystack)
|
|
82
|
+
? "style_smell_terms"
|
|
83
|
+
: null;
|
|
84
|
+
default:
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function isSchemaish(change, haystack) {
|
|
89
|
+
if (/^(interface|type|class)$/i.test(change.entityType))
|
|
90
|
+
return true;
|
|
91
|
+
return /\b(payload|dto|schema|response|request|input|output|result|context|asset|job|node|edge|generation)\b/i.test(haystack);
|
|
92
|
+
}
|
|
93
|
+
function commentLines(value) {
|
|
94
|
+
if (!value)
|
|
95
|
+
return 0;
|
|
96
|
+
return value.split(/\r?\n/).filter((line) => /^\s*(\/\/|\/\*|\*|#)/.test(line)).length;
|
|
97
|
+
}
|
package/dist/diff.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function readDiffFromStdin(): Promise<string>;
|
package/dist/diff.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { stdin as input } from "node:process";
|
|
2
|
+
export async function readDiffFromStdin() {
|
|
3
|
+
const chunks = [];
|
|
4
|
+
for await (const chunk of input)
|
|
5
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
6
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
7
|
+
if (!text.trim())
|
|
8
|
+
throw new Error("No diff received on stdin.");
|
|
9
|
+
return text;
|
|
10
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runExperiment(configPath: string): Promise<string>;
|