@stupify/cli 0.0.16 → 0.2.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/.review/CORPUS.md +44 -0
- package/.review/CORPUS.template.md +73 -0
- package/.review/REVIEW-PROMPT.md +52 -0
- package/.review/RUBRIC.md +46 -0
- package/LICENSE +1 -1
- package/README.md +95 -37
- package/package.json +27 -26
- package/packs/antirez.md +10 -0
- package/packs/anton-kropp.md +10 -0
- package/packs/dhh.md +10 -0
- package/packs/dtolnay.md +10 -0
- package/packs/jarred-sumner.md +9 -0
- package/packs/mitchell-hashimoto.md +10 -0
- package/packs/rich-harris.md +10 -0
- package/packs/simon-willison.md +10 -0
- package/packs/sindre-sorhus.md +10 -0
- package/packs/tanner-linsley.md +10 -0
- package/packs/zod.md +10 -0
- package/src/cli.ts +626 -0
- package/src/prime-install.test.ts +109 -0
- package/src/prime.ts +50 -0
- package/src/review-sweep.test.ts +101 -0
- package/src/review-sweep.ts +526 -0
- package/dist/analysis.d.ts +0 -16
- package/dist/analysis.js +0 -168
- package/dist/cache.d.ts +0 -2
- package/dist/cache.js +0 -57
- package/dist/checks.d.ts +0 -4
- package/dist/checks.js +0 -228
- package/dist/command.d.ts +0 -2
- package/dist/command.js +0 -147
- package/dist/constants.d.ts +0 -4
- package/dist/constants.js +0 -53
- package/dist/counter-scout.d.ts +0 -21
- package/dist/counter-scout.js +0 -167
- package/dist/diff.d.ts +0 -1
- package/dist/diff.js +0 -10
- package/dist/doctor.d.ts +0 -16
- package/dist/doctor.js +0 -143
- package/dist/git.d.ts +0 -17
- package/dist/git.js +0 -368
- package/dist/hooks.d.ts +0 -5
- package/dist/hooks.js +0 -135
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/model.d.ts +0 -11
- package/dist/model.js +0 -296
- package/dist/prompts.d.ts +0 -8
- package/dist/prompts.js +0 -89
- package/dist/render.d.ts +0 -6
- package/dist/render.js +0 -295
- package/dist/repomix-provider.d.ts +0 -12
- package/dist/repomix-provider.js +0 -196
- package/dist/search-bench.d.ts +0 -1
- package/dist/search-bench.js +0 -677
- package/dist/search-profile.d.ts +0 -6
- package/dist/search-profile.js +0 -73
- package/dist/sem-provider.d.ts +0 -2
- package/dist/sem-provider.js +0 -255
- package/dist/stupify.d.ts +0 -38
- package/dist/stupify.js +0 -505
- package/dist/trace.d.ts +0 -31
- package/dist/trace.js +0 -86
- package/dist/types.d.ts +0 -341
- package/dist/types.js +0 -6
- package/dist/ui.d.ts +0 -34
- package/dist/ui.js +0 -143
- package/src/analysis.ts +0 -223
- package/src/cache.ts +0 -63
- package/src/checks.ts +0 -231
- package/src/command.ts +0 -173
- package/src/constants.ts +0 -56
- package/src/counter-scout.ts +0 -195
- package/src/diff.ts +0 -9
- package/src/doctor.ts +0 -166
- package/src/git.ts +0 -380
- package/src/hooks.ts +0 -151
- package/src/index.ts +0 -1
- package/src/model.ts +0 -367
- package/src/prompts.ts +0 -100
- package/src/render.ts +0 -328
- package/src/repomix-provider.ts +0 -219
- package/src/search-bench.ts +0 -783
- package/src/search-profile.ts +0 -89
- package/src/sem-provider.ts +0 -300
- package/src/stupify.ts +0 -604
- package/src/trace.ts +0 -126
- package/src/types.ts +0 -362
- package/src/ui.ts +0 -187
package/dist/stupify.js
DELETED
|
@@ -1,505 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { realpathSync } from "node:fs";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { countPromptTokens, runSearch, searchRequest } from "./analysis.js";
|
|
5
|
-
import { searchChecks } from "./checks.js";
|
|
6
|
-
import { parseCommand } from "./command.js";
|
|
7
|
-
import { counterScoutPlan } from "./counter-scout.js";
|
|
8
|
-
import { renderDoctorToUi, runDoctor } from "./doctor.js";
|
|
9
|
-
import { blameEntity } from "./git.js";
|
|
10
|
-
import { renderHookResultToUi, runHookCommand } from "./hooks.js";
|
|
11
|
-
import { firstRunModelBootstrap, loadLocalModel } from "./model.js";
|
|
12
|
-
import { entityContextsFromChanges, emptyContextPack, repomixContextPack, repomixSearchConfig } from "./repomix-provider.js";
|
|
13
|
-
import { helpText, renderSearchRun, renderSearchRunToUi } from "./render.js";
|
|
14
|
-
import { effectiveMaxCandidates, effectiveMaxSearchInputTokens, effectiveRepomixConfig, effectiveSearchChecks, loadSearchProfile, } from "./search-profile.js";
|
|
15
|
-
import { semChangeSetForCommand } from "./sem-provider.js";
|
|
16
|
-
import { createTracer } from "./trace.js";
|
|
17
|
-
import { createCliUi } from "./ui.js";
|
|
18
|
-
export async function main(argv = process.argv.slice(2)) {
|
|
19
|
-
const startedAt = Date.now();
|
|
20
|
-
let ui = createCliUi();
|
|
21
|
-
try {
|
|
22
|
-
const command = parseCommand(argv);
|
|
23
|
-
if (command.kind === "help") {
|
|
24
|
-
ui.intro("stupify");
|
|
25
|
-
ui.note(helpText().trim(), "Help");
|
|
26
|
-
ui.outro("Local-only. Warn-only.");
|
|
27
|
-
return 0;
|
|
28
|
-
}
|
|
29
|
-
if (command.kind === "hook") {
|
|
30
|
-
ui.intro("stupify");
|
|
31
|
-
renderHookResultToUi(await runHookCommand(command.action), ui);
|
|
32
|
-
ui.outro("Hook mode is warn-only. Commits are not blocked.");
|
|
33
|
-
return 0;
|
|
34
|
-
}
|
|
35
|
-
if (command.kind === "doctor") {
|
|
36
|
-
const result = await runDoctor();
|
|
37
|
-
ui.intro("stupify");
|
|
38
|
-
renderDoctorToUi(result, ui);
|
|
39
|
-
ui.outro(result.exitCode === 0 ? "Ready." : "Fix missing required dependencies, then rerun doctor.");
|
|
40
|
-
return result.exitCode;
|
|
41
|
-
}
|
|
42
|
-
if (command.kind === "bench-search") {
|
|
43
|
-
const { runSearchBench } = await import("./search-bench.js");
|
|
44
|
-
ui.intro("stupify");
|
|
45
|
-
ui.note(await runSearchBench(command.configPath), "Search bench");
|
|
46
|
-
ui.outro("Bench complete.");
|
|
47
|
-
return 0;
|
|
48
|
-
}
|
|
49
|
-
ui = createCliUi({ quiet: command.json });
|
|
50
|
-
const run = await runSearchCommand(command, startedAt, ui);
|
|
51
|
-
if (command.json)
|
|
52
|
-
ui.writeStdout(renderSearchRun(run, command));
|
|
53
|
-
else
|
|
54
|
-
renderSearchRunToUi(run, command, ui);
|
|
55
|
-
return 0;
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
ui.error(error instanceof Error ? error.message : String(error), { force: true });
|
|
59
|
-
return 1;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
export async function runSearchCommand(command, startedAt, ui = createCliUi({ quiet: command.json })) {
|
|
63
|
-
const activeSpans = new Map();
|
|
64
|
-
const t = createTracer({
|
|
65
|
-
writeLine: () => undefined,
|
|
66
|
-
onEvent: (event) => {
|
|
67
|
-
if (command.json)
|
|
68
|
-
return;
|
|
69
|
-
if (event.phase === "start") {
|
|
70
|
-
activeSpans.set(event.name, ui.spinner(formatStartStep(event.name, event.detail)));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
const active = activeSpans.get(event.name);
|
|
74
|
-
activeSpans.delete(event.name);
|
|
75
|
-
const message = event.phase === "error"
|
|
76
|
-
? formatErrorStep(event.name, event.ms)
|
|
77
|
-
: formatStep(event.name, event.ms, event.count, event.detail);
|
|
78
|
-
if (!active) {
|
|
79
|
-
if (event.phase === "error")
|
|
80
|
-
ui.error(message);
|
|
81
|
-
else
|
|
82
|
-
ui.step(message);
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
if (event.phase === "error")
|
|
86
|
-
active.error(message);
|
|
87
|
-
else
|
|
88
|
-
active.stop(message);
|
|
89
|
-
},
|
|
90
|
-
});
|
|
91
|
-
const profile = await loadSearchProfile(command.searchProfilePath);
|
|
92
|
-
const checks = profile ? effectiveSearchChecks(command.checkIds, profile) : searchChecks(command.checkIds);
|
|
93
|
-
const patternIds = checks.map((check) => check.id);
|
|
94
|
-
const maxCandidates = effectiveMaxCandidates(command.maxCandidates, profile);
|
|
95
|
-
const maxSearchInputTokens = effectiveMaxSearchInputTokens(command.maxSearchInputTokens, profile);
|
|
96
|
-
printRunPlan(command, patternIds, ui);
|
|
97
|
-
const { value: changeSet } = await t.trace("entity.diff", () => semChangeSetForCommand(command), {
|
|
98
|
-
count: (v) => v.summary.total,
|
|
99
|
-
detail: (v) => `${v.summary.fileCount} files`,
|
|
100
|
-
});
|
|
101
|
-
try {
|
|
102
|
-
const scoutPlan = counterScoutPlan(changeSet, checks, maxCandidates);
|
|
103
|
-
if (!command.json)
|
|
104
|
-
ui.step(scoutPlanLine(scoutPlan, changeSet.summary.total));
|
|
105
|
-
const candidates = scoutPlan.targets;
|
|
106
|
-
const contexts = entityContextsFromChanges(candidates, changeSet.changes);
|
|
107
|
-
const targetsByPattern = countTargetsByPattern(contexts);
|
|
108
|
-
const targetsPreview = previewTargets(contexts);
|
|
109
|
-
if (contexts.length === 0) {
|
|
110
|
-
return {
|
|
111
|
-
schemaVersion: "search.v1",
|
|
112
|
-
mode: "search",
|
|
113
|
-
source: command.source,
|
|
114
|
-
model: { id: command.model },
|
|
115
|
-
patterns: patternIds,
|
|
116
|
-
stats: {
|
|
117
|
-
elapsedMs: Date.now() - startedAt,
|
|
118
|
-
modelCalls: 0,
|
|
119
|
-
committers: changeSet.committers,
|
|
120
|
-
commitSubjects: changeSet.commitSubjects,
|
|
121
|
-
skipped: true,
|
|
122
|
-
skipReason: "no_candidates",
|
|
123
|
-
filesChanged: changeSet.summary.fileCount,
|
|
124
|
-
entitiesScanned: changeSet.summary.total,
|
|
125
|
-
candidates: 0,
|
|
126
|
-
searchTargets: 0,
|
|
127
|
-
repomixFiles: 0,
|
|
128
|
-
repomixTokens: 0,
|
|
129
|
-
profileId: profile?.id,
|
|
130
|
-
targetsByPattern,
|
|
131
|
-
targetsPreview,
|
|
132
|
-
},
|
|
133
|
-
matches: [],
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
const baseRepomixConfig = effectiveRepomixConfig(repomixSearchConfig(), profile);
|
|
137
|
-
const initialPack = profile?.context === "sem"
|
|
138
|
-
? emptyContextPack()
|
|
139
|
-
: await t.trace("context.pack", () => repomixContextPack(changeSet.contextCwd, contexts, changeSet.changes, baseRepomixConfig), {
|
|
140
|
-
count: (v) => v.filePaths.length,
|
|
141
|
-
detail: (v) => `${v.totalTokens} tokens`,
|
|
142
|
-
}).then((result) => result.value);
|
|
143
|
-
const packedFiles = new Set(initialPack.filePaths);
|
|
144
|
-
const searchContexts = profile?.context === "sem"
|
|
145
|
-
? contexts
|
|
146
|
-
: contexts.filter((context) => context.filePath && packedFiles.has(context.filePath));
|
|
147
|
-
if (!command.json)
|
|
148
|
-
ui.step(targetPlanLine(searchContexts, contexts.length, countTargetsByPattern(searchContexts)));
|
|
149
|
-
if (searchContexts.length === 0) {
|
|
150
|
-
return {
|
|
151
|
-
schemaVersion: "search.v1",
|
|
152
|
-
mode: "search",
|
|
153
|
-
source: command.source,
|
|
154
|
-
model: { id: command.model },
|
|
155
|
-
patterns: patternIds,
|
|
156
|
-
stats: {
|
|
157
|
-
elapsedMs: Date.now() - startedAt,
|
|
158
|
-
modelCalls: 0,
|
|
159
|
-
committers: changeSet.committers,
|
|
160
|
-
commitSubjects: changeSet.commitSubjects,
|
|
161
|
-
skipped: true,
|
|
162
|
-
skipReason: "no_candidates",
|
|
163
|
-
filesChanged: changeSet.summary.fileCount,
|
|
164
|
-
entitiesScanned: changeSet.summary.total,
|
|
165
|
-
candidates: contexts.length,
|
|
166
|
-
searchTargets: 0,
|
|
167
|
-
repomixFiles: initialPack.filePaths.length,
|
|
168
|
-
repomixTokens: initialPack.totalTokens,
|
|
169
|
-
repomixConfig: initialPack.config,
|
|
170
|
-
profileId: profile?.id,
|
|
171
|
-
targetsByPattern,
|
|
172
|
-
targetsPreview,
|
|
173
|
-
},
|
|
174
|
-
matches: [],
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
const pack = profile?.context === "sem" || searchContexts.length === contexts.length
|
|
178
|
-
? initialPack
|
|
179
|
-
: await repomixContextPack(changeSet.contextCwd, searchContexts, changeSet.changes, baseRepomixConfig);
|
|
180
|
-
const { value: batches } = await t.trace("search.batches", () => buildSearchBatches({
|
|
181
|
-
command,
|
|
182
|
-
changeSet,
|
|
183
|
-
contexts: searchContexts,
|
|
184
|
-
initialPack: pack,
|
|
185
|
-
checks,
|
|
186
|
-
profile,
|
|
187
|
-
includeCounterReasonInPrompt: command.includeCounterReasonInPrompt,
|
|
188
|
-
maxSearchInputTokens,
|
|
189
|
-
baseRepomixConfig,
|
|
190
|
-
}), {
|
|
191
|
-
startDetail: `${searchContexts.length} targets`,
|
|
192
|
-
count: (result) => result.batches.length,
|
|
193
|
-
detail: (result) => result.wasSplit
|
|
194
|
-
? `${result.skippedTargets} oversized targets skipped`
|
|
195
|
-
: `${result.estimatedInputTokens} estimated tokens`,
|
|
196
|
-
});
|
|
197
|
-
if (batches.batches.length === 0) {
|
|
198
|
-
return {
|
|
199
|
-
schemaVersion: "search.v1",
|
|
200
|
-
mode: "search",
|
|
201
|
-
source: command.source,
|
|
202
|
-
model: { id: command.model },
|
|
203
|
-
patterns: patternIds,
|
|
204
|
-
stats: {
|
|
205
|
-
elapsedMs: Date.now() - startedAt,
|
|
206
|
-
modelCalls: 0,
|
|
207
|
-
inputTokens: batches.estimatedInputTokens,
|
|
208
|
-
inputTokenCap: maxSearchInputTokens,
|
|
209
|
-
committers: changeSet.committers,
|
|
210
|
-
commitSubjects: changeSet.commitSubjects,
|
|
211
|
-
skipped: true,
|
|
212
|
-
skipReason: "input_too_large",
|
|
213
|
-
filesChanged: changeSet.summary.fileCount,
|
|
214
|
-
entitiesScanned: changeSet.summary.total,
|
|
215
|
-
candidates: contexts.length,
|
|
216
|
-
searchTargets: searchContexts.length,
|
|
217
|
-
repomixFiles: pack.filePaths.length,
|
|
218
|
-
repomixTokens: pack.totalTokens,
|
|
219
|
-
repomixConfig: pack.config,
|
|
220
|
-
searchBatches: 0,
|
|
221
|
-
skippedTargets: batches.skippedTargets,
|
|
222
|
-
profileId: profile?.id,
|
|
223
|
-
targetsByPattern: countTargetsByPattern(searchContexts),
|
|
224
|
-
targetsPreview: previewTargets(searchContexts),
|
|
225
|
-
},
|
|
226
|
-
matches: [],
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
if (batches.wasSplit && !command.json) {
|
|
230
|
-
ui.warn(`Search input is large; queued ${batches.batches.length} smaller batches for ${searchContexts.length} targets (${maxSearchInputTokens} token cap).`);
|
|
231
|
-
if (batches.skippedTargets > 0) {
|
|
232
|
-
ui.warn(`Skipped ${batches.skippedTargets} oversized targets that could not fit alone.`);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
else if (!command.json) {
|
|
236
|
-
ui.step(`Search: ${searchContexts.length} targets in ${batches.batches.length} model batch (${maxSearchInputTokens} token cap)`);
|
|
237
|
-
}
|
|
238
|
-
const modelPath = await firstRunModelBootstrap(command.model, ui);
|
|
239
|
-
const model = await loadLocalModel(modelPath, command.model, "scout", ui);
|
|
240
|
-
const matches = [];
|
|
241
|
-
let modelCalls = 0;
|
|
242
|
-
let inputTokens = 0;
|
|
243
|
-
let exactSkippedTargets = batches.skippedTargets;
|
|
244
|
-
for (const batch of batches.batches) {
|
|
245
|
-
const { value: batchInputTokens } = await t.trace("prompt.tokens", () => countPromptTokens(model, batch.request.prompt), {
|
|
246
|
-
startDetail: `${batch.contexts.length} targets`,
|
|
247
|
-
count: (tokens) => tokens,
|
|
248
|
-
});
|
|
249
|
-
inputTokens += batchInputTokens;
|
|
250
|
-
if (batchInputTokens > maxSearchInputTokens) {
|
|
251
|
-
exactSkippedTargets += batch.contexts.length;
|
|
252
|
-
if (!command.json) {
|
|
253
|
-
ui.warn(`Skipped ${batch.contexts.length} targets after exact token count exceeded the limit.`);
|
|
254
|
-
}
|
|
255
|
-
continue;
|
|
256
|
-
}
|
|
257
|
-
const { value } = await t.trace("search.model", () => runSearch(model, batch.request), {
|
|
258
|
-
startDetail: `${batch.contexts.length} targets`,
|
|
259
|
-
count: (v) => v.length,
|
|
260
|
-
});
|
|
261
|
-
modelCalls += 1;
|
|
262
|
-
matches.push(...withCheckWhy(value, checks));
|
|
263
|
-
}
|
|
264
|
-
const uniqueMatches = await withEntityBlame(dedupeMatches(matches), changeSet.target, command);
|
|
265
|
-
return {
|
|
266
|
-
schemaVersion: "search.v1",
|
|
267
|
-
mode: "search",
|
|
268
|
-
source: command.source,
|
|
269
|
-
model: { id: command.model },
|
|
270
|
-
patterns: patternIds,
|
|
271
|
-
stats: {
|
|
272
|
-
elapsedMs: Date.now() - startedAt,
|
|
273
|
-
modelCalls,
|
|
274
|
-
inputTokens,
|
|
275
|
-
inputTokenCap: maxSearchInputTokens,
|
|
276
|
-
committers: changeSet.committers,
|
|
277
|
-
commitSubjects: changeSet.commitSubjects,
|
|
278
|
-
filesChanged: changeSet.summary.fileCount,
|
|
279
|
-
entitiesScanned: changeSet.summary.total,
|
|
280
|
-
candidates: contexts.length,
|
|
281
|
-
searchTargets: searchContexts.length,
|
|
282
|
-
repomixFiles: pack.filePaths.length,
|
|
283
|
-
repomixTokens: pack.totalTokens,
|
|
284
|
-
repomixConfig: pack.config,
|
|
285
|
-
searchBatches: batches.batches.length,
|
|
286
|
-
skippedTargets: exactSkippedTargets,
|
|
287
|
-
profileId: profile?.id,
|
|
288
|
-
targetsByPattern: countTargetsByPattern(searchContexts),
|
|
289
|
-
targetsPreview: previewTargets(searchContexts),
|
|
290
|
-
},
|
|
291
|
-
matches: uniqueMatches,
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
finally {
|
|
295
|
-
await changeSet.cleanup();
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
function dedupeMatches(matches) {
|
|
299
|
-
const seen = new Set();
|
|
300
|
-
return matches.filter((match) => {
|
|
301
|
-
const key = `${match.patternId}\n${match.proof.trim()}`;
|
|
302
|
-
if (seen.has(key))
|
|
303
|
-
return false;
|
|
304
|
-
seen.add(key);
|
|
305
|
-
return true;
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
function withCheckWhy(matches, checks) {
|
|
309
|
-
const checksById = new Map(checks.map((check) => [check.id, check]));
|
|
310
|
-
return matches.map((match) => ({
|
|
311
|
-
...match,
|
|
312
|
-
patternName: checksById.get(match.patternId)?.name,
|
|
313
|
-
checkWhy: checksById.get(match.patternId)?.why,
|
|
314
|
-
}));
|
|
315
|
-
}
|
|
316
|
-
async function withEntityBlame(matches, targetRev, command) {
|
|
317
|
-
if (command.kind === "staged" || command.kind === "stdin")
|
|
318
|
-
return matches;
|
|
319
|
-
return Promise.all(matches.map(async (match) => {
|
|
320
|
-
if (!match.filePath || !match.entityName)
|
|
321
|
-
return match;
|
|
322
|
-
const blame = await blameEntity({
|
|
323
|
-
filePath: match.filePath,
|
|
324
|
-
entityName: match.entityName,
|
|
325
|
-
rev: targetRev,
|
|
326
|
-
});
|
|
327
|
-
return blame ? { ...match, blame } : match;
|
|
328
|
-
}));
|
|
329
|
-
}
|
|
330
|
-
async function buildSearchBatches(input) {
|
|
331
|
-
const first = makeSearchBatch(input, input.contexts, input.initialPack);
|
|
332
|
-
if (first.estimatedInputTokens <= input.maxSearchInputTokens) {
|
|
333
|
-
return {
|
|
334
|
-
batches: [first],
|
|
335
|
-
estimatedInputTokens: first.estimatedInputTokens,
|
|
336
|
-
skippedTargets: 0,
|
|
337
|
-
wasSplit: false,
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
const batches = [];
|
|
341
|
-
let skippedTargets = 0;
|
|
342
|
-
let currentContexts = [];
|
|
343
|
-
let currentBatch = null;
|
|
344
|
-
for (const context of input.contexts) {
|
|
345
|
-
const candidateContexts = [...currentContexts, context];
|
|
346
|
-
const candidateBatch = await makeSearchBatchWithPack(input, candidateContexts);
|
|
347
|
-
if (candidateBatch.estimatedInputTokens <= input.maxSearchInputTokens) {
|
|
348
|
-
currentContexts = candidateContexts;
|
|
349
|
-
currentBatch = candidateBatch;
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
352
|
-
if (currentBatch) {
|
|
353
|
-
batches.push(currentBatch);
|
|
354
|
-
currentContexts = [];
|
|
355
|
-
currentBatch = null;
|
|
356
|
-
}
|
|
357
|
-
const singleBatch = candidateContexts.length === 1
|
|
358
|
-
? candidateBatch
|
|
359
|
-
: await makeSearchBatchWithPack(input, [context]);
|
|
360
|
-
if (singleBatch.estimatedInputTokens <= input.maxSearchInputTokens) {
|
|
361
|
-
currentContexts = [context];
|
|
362
|
-
currentBatch = singleBatch;
|
|
363
|
-
}
|
|
364
|
-
else {
|
|
365
|
-
skippedTargets += 1;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
if (currentBatch)
|
|
369
|
-
batches.push(currentBatch);
|
|
370
|
-
return {
|
|
371
|
-
batches,
|
|
372
|
-
estimatedInputTokens: first.estimatedInputTokens,
|
|
373
|
-
skippedTargets,
|
|
374
|
-
wasSplit: true,
|
|
375
|
-
};
|
|
376
|
-
}
|
|
377
|
-
function makeSearchBatch(input, contexts, pack) {
|
|
378
|
-
const request = buildSearchRequest(input.changeSet, contexts, pack, input.checks, input.profile, input.includeCounterReasonInPrompt);
|
|
379
|
-
return {
|
|
380
|
-
contexts,
|
|
381
|
-
pack,
|
|
382
|
-
request,
|
|
383
|
-
estimatedInputTokens: estimatePromptTokens(request.prompt),
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
async function makeSearchBatchWithPack(input, contexts) {
|
|
387
|
-
const pack = input.profile?.context === "sem"
|
|
388
|
-
? emptyContextPack()
|
|
389
|
-
: await repomixContextPack(input.changeSet.contextCwd, contexts, input.changeSet.changes, input.baseRepomixConfig);
|
|
390
|
-
return makeSearchBatch(input, contexts, pack);
|
|
391
|
-
}
|
|
392
|
-
function buildSearchRequest(changeSet, contexts, pack, patterns, profile, includeCounterReasonInPrompt) {
|
|
393
|
-
return searchRequest({
|
|
394
|
-
changeSet,
|
|
395
|
-
contexts,
|
|
396
|
-
pack,
|
|
397
|
-
patterns,
|
|
398
|
-
includeCounterReasonInPrompt: profile?.includeCounterReasonInPrompt ?? includeCounterReasonInPrompt,
|
|
399
|
-
});
|
|
400
|
-
}
|
|
401
|
-
function printRunPlan(command, patternIds, ui) {
|
|
402
|
-
if (command.json)
|
|
403
|
-
return;
|
|
404
|
-
ui.intro("stupify");
|
|
405
|
-
ui.note([
|
|
406
|
-
`Search: ${sourceLabel(command)}`,
|
|
407
|
-
`Patterns: ${patternIds.join(", ")}`,
|
|
408
|
-
].join("\n"), "Run");
|
|
409
|
-
}
|
|
410
|
-
function formatStartStep(name, detail) {
|
|
411
|
-
if (name === "entity.diff")
|
|
412
|
-
return "Diff: running sem over the selected git range";
|
|
413
|
-
if (name === "context.pack")
|
|
414
|
-
return "Context: packing selected target files with Repomix";
|
|
415
|
-
if (name === "search.batches")
|
|
416
|
-
return `Search: preparing token-bounded model batches${detail ? ` for ${detail}` : ""}`;
|
|
417
|
-
if (name === "prompt.tokens")
|
|
418
|
-
return `Tokens: counting search prompt${detail ? ` for ${detail}` : ""}`;
|
|
419
|
-
if (name === "search.model")
|
|
420
|
-
return `Model: searching selected target/check pairs${detail ? ` (${detail})` : ""}`;
|
|
421
|
-
return `${name}: working`;
|
|
422
|
-
}
|
|
423
|
-
function formatStep(name, ms, count, detail) {
|
|
424
|
-
if (name === "entity.diff")
|
|
425
|
-
return `Diff: ${detail ?? "changed files"}, ${count ?? 0} changed entities (${ms}ms)`;
|
|
426
|
-
if (name === "context.pack")
|
|
427
|
-
return `Context: ${count ?? 0} files, ${detail ?? "0 tokens"} (${ms}ms)`;
|
|
428
|
-
if (name === "search.batches")
|
|
429
|
-
return `Search: ${count ?? 0} model batches, ${detail ?? "0 estimated tokens"} (${ms}ms)`;
|
|
430
|
-
if (name === "prompt.tokens")
|
|
431
|
-
return `Tokens: ${count ?? 0} prompt tokens (${ms}ms)`;
|
|
432
|
-
if (name === "search.model")
|
|
433
|
-
return `Model: ${count ?? 0} matches (${ms}ms)`;
|
|
434
|
-
return `${name}: ${ms}ms`;
|
|
435
|
-
}
|
|
436
|
-
function formatErrorStep(name, ms) {
|
|
437
|
-
if (name === "entity.diff")
|
|
438
|
-
return `Diff failed after ${ms}ms`;
|
|
439
|
-
if (name === "context.pack")
|
|
440
|
-
return `Context packing failed after ${ms}ms`;
|
|
441
|
-
if (name === "search.batches")
|
|
442
|
-
return `Search batch preparation failed after ${ms}ms`;
|
|
443
|
-
if (name === "prompt.tokens")
|
|
444
|
-
return `Token counting failed after ${ms}ms`;
|
|
445
|
-
if (name === "search.model")
|
|
446
|
-
return `Model search failed after ${ms}ms`;
|
|
447
|
-
return `${name} failed after ${ms}ms`;
|
|
448
|
-
}
|
|
449
|
-
function scoutPlanLine(plan, entitiesScanned) {
|
|
450
|
-
if (plan.targets.length === 0) {
|
|
451
|
-
return `Scout: deterministic counters scanned ${entitiesScanned} entities; no target/check pairs selected`;
|
|
452
|
-
}
|
|
453
|
-
return [
|
|
454
|
-
`Scout: deterministic counters scanned ${entitiesScanned} entities`,
|
|
455
|
-
`${plan.totalSignals} counter signals`,
|
|
456
|
-
`selected ${plan.targets.length}/${plan.totalSignals} target/check pairs (cap ${plan.maxTargets}, not exhaustive)`,
|
|
457
|
-
].join("; ");
|
|
458
|
-
}
|
|
459
|
-
function targetPlanLine(searchContexts, selectedTargets, targetsByPattern) {
|
|
460
|
-
const retained = searchContexts.length === selectedTargets
|
|
461
|
-
? `${searchContexts.length} selected targets`
|
|
462
|
-
: `${searchContexts.length}/${selectedTargets} selected targets retained after context packing`;
|
|
463
|
-
return `Targets: model will inspect ${retained}; ${formatCounts(targetsByPattern)}`;
|
|
464
|
-
}
|
|
465
|
-
function formatCounts(counts) {
|
|
466
|
-
const entries = Object.entries(counts).filter(([, count]) => count > 0);
|
|
467
|
-
if (entries.length === 0)
|
|
468
|
-
return "no target/check pairs";
|
|
469
|
-
return entries.map(([id, count]) => `${id}=${count}`).join(", ");
|
|
470
|
-
}
|
|
471
|
-
function sourceLabel(command) {
|
|
472
|
-
if (command.kind === "since")
|
|
473
|
-
return `since ${command.since}`;
|
|
474
|
-
if (command.kind === "commit")
|
|
475
|
-
return `commit ${command.commit}`;
|
|
476
|
-
if (command.kind === "commits")
|
|
477
|
-
return `last ${command.count} commits`;
|
|
478
|
-
if (command.kind === "staged")
|
|
479
|
-
return "staged changes";
|
|
480
|
-
return "stdin diff";
|
|
481
|
-
}
|
|
482
|
-
function estimatePromptTokens(prompt) {
|
|
483
|
-
return Math.ceil(prompt.length / 3);
|
|
484
|
-
}
|
|
485
|
-
function countTargetsByPattern(contexts) {
|
|
486
|
-
const counts = {};
|
|
487
|
-
for (const context of contexts)
|
|
488
|
-
counts[context.checkId] = (counts[context.checkId] ?? 0) + 1;
|
|
489
|
-
return counts;
|
|
490
|
-
}
|
|
491
|
-
function previewTargets(contexts) {
|
|
492
|
-
return contexts.map((context) => ({
|
|
493
|
-
targetId: context.targetId,
|
|
494
|
-
patternId: context.checkId,
|
|
495
|
-
entityKind: context.entityKind || undefined,
|
|
496
|
-
sourceKind: context.filePath ? pathKind(context.filePath) : undefined,
|
|
497
|
-
}));
|
|
498
|
-
}
|
|
499
|
-
function pathKind(filePath) {
|
|
500
|
-
const ext = filePath.split(".").pop();
|
|
501
|
-
return ext && ext !== filePath ? ext : "unknown";
|
|
502
|
-
}
|
|
503
|
-
if (process.argv[1] && realpathSync(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
504
|
-
process.exitCode = await main();
|
|
505
|
-
}
|
package/dist/trace.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export type TraceFields = Record<string, string | number | boolean | null | undefined>;
|
|
2
|
-
export type Tracer = {
|
|
3
|
-
trace<T>(span: string, fn: () => Promise<T>, options?: SpanTraceOptions<T>): Promise<{
|
|
4
|
-
value: T;
|
|
5
|
-
ms: number;
|
|
6
|
-
}>;
|
|
7
|
-
trace<T>(span: string, fn: () => T, options?: SpanTraceOptions<T>): {
|
|
8
|
-
value: T;
|
|
9
|
-
ms: number;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
|
-
export type SpanTraceEvent = Readonly<{
|
|
13
|
-
name: string;
|
|
14
|
-
phase: "start" | "end" | "error";
|
|
15
|
-
ms: number;
|
|
16
|
-
count?: number;
|
|
17
|
-
detail?: string;
|
|
18
|
-
}>;
|
|
19
|
-
export type SpanTraceOptions<T> = Readonly<{
|
|
20
|
-
fields?: TraceFields;
|
|
21
|
-
startDetail?: string | (() => string);
|
|
22
|
-
count?: (value: T) => number;
|
|
23
|
-
detail?: (value: T) => string;
|
|
24
|
-
}>;
|
|
25
|
-
export type CreateTracerOptions = {
|
|
26
|
-
enabled?: boolean;
|
|
27
|
-
writeLine?: (line: string) => void;
|
|
28
|
-
onEvent?: (event: SpanTraceEvent) => void;
|
|
29
|
-
};
|
|
30
|
-
export declare function createTracer(options?: CreateTracerOptions): Tracer;
|
|
31
|
-
export declare const trace: Tracer;
|
package/dist/trace.js
DELETED
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { performance } from "node:perf_hooks";
|
|
2
|
-
export function createTracer(options) {
|
|
3
|
-
const enabled = options?.enabled ?? true;
|
|
4
|
-
const writeLine = options?.writeLine ?? ((line) => process.stderr.write(line + "\n"));
|
|
5
|
-
const onEvent = options?.onEvent;
|
|
6
|
-
const nowMs = () => performance.now();
|
|
7
|
-
function emit(span, durationMs, fields) {
|
|
8
|
-
if (!enabled)
|
|
9
|
-
return;
|
|
10
|
-
const payload = { span, ms: Math.round(durationMs) };
|
|
11
|
-
for (const [k, v] of Object.entries(fields ?? {})) {
|
|
12
|
-
if (v !== undefined)
|
|
13
|
-
payload[k] = v;
|
|
14
|
-
}
|
|
15
|
-
writeLine(`trace ${JSON.stringify(payload)}`);
|
|
16
|
-
}
|
|
17
|
-
function trace(span, fn, options) {
|
|
18
|
-
const startedAtMs = nowMs();
|
|
19
|
-
onEvent?.({
|
|
20
|
-
name: span,
|
|
21
|
-
phase: "start",
|
|
22
|
-
ms: 0,
|
|
23
|
-
detail: typeof options?.startDetail === "function" ? options.startDetail() : options?.startDetail,
|
|
24
|
-
});
|
|
25
|
-
try {
|
|
26
|
-
const out = fn();
|
|
27
|
-
if (isPromiseLike(out)) {
|
|
28
|
-
return (async () => {
|
|
29
|
-
let durationMs;
|
|
30
|
-
try {
|
|
31
|
-
const value = await out;
|
|
32
|
-
durationMs = nowMs() - startedAtMs;
|
|
33
|
-
const event = {
|
|
34
|
-
name: span,
|
|
35
|
-
phase: "end",
|
|
36
|
-
ms: Math.round(durationMs),
|
|
37
|
-
count: options?.count?.(value),
|
|
38
|
-
detail: options?.detail?.(value),
|
|
39
|
-
};
|
|
40
|
-
onEvent?.(event);
|
|
41
|
-
return { value, ms: event.ms };
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
durationMs = nowMs() - startedAtMs;
|
|
45
|
-
onEvent?.({
|
|
46
|
-
name: span,
|
|
47
|
-
phase: "error",
|
|
48
|
-
ms: Math.round(durationMs),
|
|
49
|
-
});
|
|
50
|
-
throw error;
|
|
51
|
-
}
|
|
52
|
-
finally {
|
|
53
|
-
durationMs ??= nowMs() - startedAtMs;
|
|
54
|
-
emit(span, durationMs, options?.fields);
|
|
55
|
-
}
|
|
56
|
-
})();
|
|
57
|
-
}
|
|
58
|
-
const durationMs = nowMs() - startedAtMs;
|
|
59
|
-
emit(span, durationMs, options?.fields);
|
|
60
|
-
const event = {
|
|
61
|
-
name: span,
|
|
62
|
-
phase: "end",
|
|
63
|
-
ms: Math.round(durationMs),
|
|
64
|
-
count: options?.count?.(out),
|
|
65
|
-
detail: options?.detail?.(out),
|
|
66
|
-
};
|
|
67
|
-
onEvent?.(event);
|
|
68
|
-
return { value: out, ms: event.ms };
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
const durationMs = nowMs() - startedAtMs;
|
|
72
|
-
onEvent?.({
|
|
73
|
-
name: span,
|
|
74
|
-
phase: "error",
|
|
75
|
-
ms: Math.round(durationMs),
|
|
76
|
-
});
|
|
77
|
-
emit(span, durationMs, options?.fields);
|
|
78
|
-
throw error;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
return { trace };
|
|
82
|
-
}
|
|
83
|
-
export const trace = createTracer();
|
|
84
|
-
function isPromiseLike(value) {
|
|
85
|
-
return typeof value === "object" && value !== null && "then" in value;
|
|
86
|
-
}
|