@stupify/cli 0.0.6 → 0.0.7
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/dist/checks.js +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/counter-scout.js +1 -1
- package/dist/stupify.js +116 -36
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
- package/src/checks.ts +1 -1
- package/src/constants.ts +1 -1
- package/src/counter-scout.ts +1 -1
- package/src/stupify.ts +175 -47
- package/src/types.ts +2 -0
package/dist/checks.js
CHANGED
|
@@ -173,7 +173,7 @@ Prefer no match over a weak match.`,
|
|
|
173
173
|
"helper is domain-specific or used by multiple local call sites",
|
|
174
174
|
],
|
|
175
175
|
hookMode: "warn",
|
|
176
|
-
searchPrompt: "Find only tiny generic utility functions that recreate common helpers such as clamp, debounce, throttle, slugify,
|
|
176
|
+
searchPrompt: "Find only tiny generic utility functions that recreate common helpers such as clamp, debounce, throttle, slugify, sort, pick, omit, uniq, or shuffle without domain-specific behavior. Do not match group/resolve/parse/format helpers, domain formatting, feature constants, or helpers with multiple obvious call sites.",
|
|
177
177
|
searchExamples: {
|
|
178
178
|
match: [
|
|
179
179
|
"clampValue returns min, max, or value.",
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
package/dist/counter-scout.js
CHANGED
|
@@ -120,7 +120,7 @@ function lintBypassSignal(value) {
|
|
|
120
120
|
}
|
|
121
121
|
function reinventedUtilitySignal(change) {
|
|
122
122
|
const name = change.entityName;
|
|
123
|
-
if (!/^(clamp|debounce|throttle|slug|slugify|
|
|
123
|
+
if (!/^(clamp|debounce|throttle|slug|slugify|sort|shuffle|memoize|pick|omit|uniq)/i.test(name))
|
|
124
124
|
return false;
|
|
125
125
|
const content = change.afterContent ?? "";
|
|
126
126
|
if (/currency|invoice|refund|subscription|tier|domain/i.test(`${name}\n${content}`))
|
package/dist/stupify.js
CHANGED
|
@@ -133,9 +133,18 @@ export async function runSearchCommand(command, startedAt) {
|
|
|
133
133
|
const pack = profile?.context === "sem" || searchContexts.length === contexts.length
|
|
134
134
|
? initialPack
|
|
135
135
|
: await repomixContextPack(changeSet.contextCwd, searchContexts, changeSet.changes, baseRepomixConfig);
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
136
|
+
const batches = await buildSearchBatches({
|
|
137
|
+
command,
|
|
138
|
+
changeSet,
|
|
139
|
+
contexts: searchContexts,
|
|
140
|
+
initialPack: pack,
|
|
141
|
+
checks,
|
|
142
|
+
profile,
|
|
143
|
+
includeCounterReasonInPrompt: command.includeCounterReasonInPrompt,
|
|
144
|
+
maxSearchInputTokens,
|
|
145
|
+
baseRepomixConfig,
|
|
146
|
+
});
|
|
147
|
+
if (batches.batches.length === 0) {
|
|
139
148
|
return {
|
|
140
149
|
schemaVersion: "search.v1",
|
|
141
150
|
mode: "search",
|
|
@@ -145,7 +154,7 @@ export async function runSearchCommand(command, startedAt) {
|
|
|
145
154
|
stats: {
|
|
146
155
|
elapsedMs: Date.now() - startedAt,
|
|
147
156
|
modelCalls: 0,
|
|
148
|
-
inputTokens: estimatedInputTokens,
|
|
157
|
+
inputTokens: batches.estimatedInputTokens,
|
|
149
158
|
inputTokenCap: maxSearchInputTokens,
|
|
150
159
|
skipped: true,
|
|
151
160
|
skipReason: "input_too_large",
|
|
@@ -156,6 +165,8 @@ export async function runSearchCommand(command, startedAt) {
|
|
|
156
165
|
repomixFiles: pack.filePaths.length,
|
|
157
166
|
repomixTokens: pack.totalTokens,
|
|
158
167
|
repomixConfig: pack.config,
|
|
168
|
+
searchBatches: 0,
|
|
169
|
+
skippedTargets: batches.skippedTargets,
|
|
159
170
|
profileId: profile?.id,
|
|
160
171
|
targetsByPattern: countTargetsByPattern(searchContexts),
|
|
161
172
|
targetsPreview: previewTargets(searchContexts),
|
|
@@ -163,38 +174,33 @@ export async function runSearchCommand(command, startedAt) {
|
|
|
163
174
|
matches: [],
|
|
164
175
|
};
|
|
165
176
|
}
|
|
177
|
+
if (batches.wasSplit && !command.json) {
|
|
178
|
+
console.error(`Search input is large; queued ${batches.batches.length} smaller search batches.`);
|
|
179
|
+
if (batches.skippedTargets > 0) {
|
|
180
|
+
console.error(`Skipped ${batches.skippedTargets} oversized targets that could not fit alone.`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
166
183
|
const modelPath = await firstRunModelBootstrap(command.model);
|
|
167
184
|
const model = await loadLocalModel(modelPath, command.model, "scout");
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
candidates: contexts.length,
|
|
186
|
-
searchTargets: searchContexts.length,
|
|
187
|
-
repomixFiles: pack.filePaths.length,
|
|
188
|
-
repomixTokens: pack.totalTokens,
|
|
189
|
-
repomixConfig: pack.config,
|
|
190
|
-
profileId: profile?.id,
|
|
191
|
-
targetsByPattern: countTargetsByPattern(searchContexts),
|
|
192
|
-
targetsPreview: previewTargets(searchContexts),
|
|
193
|
-
},
|
|
194
|
-
matches: [],
|
|
195
|
-
};
|
|
185
|
+
const matches = [];
|
|
186
|
+
let modelCalls = 0;
|
|
187
|
+
let inputTokens = 0;
|
|
188
|
+
let exactSkippedTargets = batches.skippedTargets;
|
|
189
|
+
for (const batch of batches.batches) {
|
|
190
|
+
const batchInputTokens = await countPromptTokens(model, batch.request.prompt);
|
|
191
|
+
inputTokens += batchInputTokens;
|
|
192
|
+
if (batchInputTokens > maxSearchInputTokens) {
|
|
193
|
+
exactSkippedTargets += batch.contexts.length;
|
|
194
|
+
if (!command.json) {
|
|
195
|
+
console.error(`Skipped ${batch.contexts.length} targets after exact token count exceeded the limit.`);
|
|
196
|
+
}
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const { value } = await t.trace("search.model", () => runSearch(model, batch.request), { count: (v) => v.length });
|
|
200
|
+
modelCalls += 1;
|
|
201
|
+
matches.push(...value);
|
|
196
202
|
}
|
|
197
|
-
const
|
|
203
|
+
const uniqueMatches = dedupeMatches(matches);
|
|
198
204
|
return {
|
|
199
205
|
schemaVersion: "search.v1",
|
|
200
206
|
mode: "search",
|
|
@@ -203,7 +209,7 @@ export async function runSearchCommand(command, startedAt) {
|
|
|
203
209
|
patterns: patternIds,
|
|
204
210
|
stats: {
|
|
205
211
|
elapsedMs: Date.now() - startedAt,
|
|
206
|
-
modelCalls
|
|
212
|
+
modelCalls,
|
|
207
213
|
inputTokens,
|
|
208
214
|
inputTokenCap: maxSearchInputTokens,
|
|
209
215
|
filesChanged: changeSet.summary.fileCount,
|
|
@@ -213,17 +219,91 @@ export async function runSearchCommand(command, startedAt) {
|
|
|
213
219
|
repomixFiles: pack.filePaths.length,
|
|
214
220
|
repomixTokens: pack.totalTokens,
|
|
215
221
|
repomixConfig: pack.config,
|
|
222
|
+
searchBatches: batches.batches.length,
|
|
223
|
+
skippedTargets: exactSkippedTargets,
|
|
216
224
|
profileId: profile?.id,
|
|
217
225
|
targetsByPattern: countTargetsByPattern(searchContexts),
|
|
218
226
|
targetsPreview: previewTargets(searchContexts),
|
|
219
227
|
},
|
|
220
|
-
matches,
|
|
228
|
+
matches: uniqueMatches,
|
|
221
229
|
};
|
|
222
230
|
}
|
|
223
231
|
finally {
|
|
224
232
|
await changeSet.cleanup();
|
|
225
233
|
}
|
|
226
234
|
}
|
|
235
|
+
function dedupeMatches(matches) {
|
|
236
|
+
const seen = new Set();
|
|
237
|
+
return matches.filter((match) => {
|
|
238
|
+
const key = `${match.patternId}\n${match.proof.trim()}`;
|
|
239
|
+
if (seen.has(key))
|
|
240
|
+
return false;
|
|
241
|
+
seen.add(key);
|
|
242
|
+
return true;
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
async function buildSearchBatches(input) {
|
|
246
|
+
const first = makeSearchBatch(input, input.contexts, input.initialPack);
|
|
247
|
+
if (first.estimatedInputTokens <= input.maxSearchInputTokens) {
|
|
248
|
+
return {
|
|
249
|
+
batches: [first],
|
|
250
|
+
estimatedInputTokens: first.estimatedInputTokens,
|
|
251
|
+
skippedTargets: 0,
|
|
252
|
+
wasSplit: false,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const batches = [];
|
|
256
|
+
let skippedTargets = 0;
|
|
257
|
+
let currentContexts = [];
|
|
258
|
+
let currentBatch = null;
|
|
259
|
+
for (const context of input.contexts) {
|
|
260
|
+
const candidateContexts = [...currentContexts, context];
|
|
261
|
+
const candidateBatch = await makeSearchBatchWithPack(input, candidateContexts);
|
|
262
|
+
if (candidateBatch.estimatedInputTokens <= input.maxSearchInputTokens) {
|
|
263
|
+
currentContexts = candidateContexts;
|
|
264
|
+
currentBatch = candidateBatch;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (currentBatch) {
|
|
268
|
+
batches.push(currentBatch);
|
|
269
|
+
currentContexts = [];
|
|
270
|
+
currentBatch = null;
|
|
271
|
+
}
|
|
272
|
+
const singleBatch = candidateContexts.length === 1
|
|
273
|
+
? candidateBatch
|
|
274
|
+
: await makeSearchBatchWithPack(input, [context]);
|
|
275
|
+
if (singleBatch.estimatedInputTokens <= input.maxSearchInputTokens) {
|
|
276
|
+
currentContexts = [context];
|
|
277
|
+
currentBatch = singleBatch;
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
skippedTargets += 1;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (currentBatch)
|
|
284
|
+
batches.push(currentBatch);
|
|
285
|
+
return {
|
|
286
|
+
batches,
|
|
287
|
+
estimatedInputTokens: first.estimatedInputTokens,
|
|
288
|
+
skippedTargets,
|
|
289
|
+
wasSplit: true,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function makeSearchBatch(input, contexts, pack) {
|
|
293
|
+
const request = buildSearchRequest(input.changeSet, contexts, pack, input.checks, input.profile, input.includeCounterReasonInPrompt);
|
|
294
|
+
return {
|
|
295
|
+
contexts,
|
|
296
|
+
pack,
|
|
297
|
+
request,
|
|
298
|
+
estimatedInputTokens: estimatePromptTokens(request.prompt),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
async function makeSearchBatchWithPack(input, contexts) {
|
|
302
|
+
const pack = input.profile?.context === "sem"
|
|
303
|
+
? emptyContextPack()
|
|
304
|
+
: await repomixContextPack(input.changeSet.contextCwd, contexts, input.changeSet.changes, input.baseRepomixConfig);
|
|
305
|
+
return makeSearchBatch(input, contexts, pack);
|
|
306
|
+
}
|
|
227
307
|
function buildSearchRequest(changeSet, contexts, pack, patterns, profile, includeCounterReasonInPrompt) {
|
|
228
308
|
return searchRequest({
|
|
229
309
|
changeSet,
|
|
@@ -261,7 +341,7 @@ function sourceLabel(command) {
|
|
|
261
341
|
return "stdin diff";
|
|
262
342
|
}
|
|
263
343
|
function estimatePromptTokens(prompt) {
|
|
264
|
-
return Math.ceil(prompt.length /
|
|
344
|
+
return Math.ceil(prompt.length / 3);
|
|
265
345
|
}
|
|
266
346
|
function countTargetsByPattern(contexts) {
|
|
267
347
|
const counts = {};
|
package/dist/types.d.ts
CHANGED
|
@@ -219,6 +219,8 @@ export type SearchRunJson = Readonly<{
|
|
|
219
219
|
repomixTokens?: number;
|
|
220
220
|
repomixConfig?: RepomixSearchConfig;
|
|
221
221
|
searchTargets?: number;
|
|
222
|
+
searchBatches?: number;
|
|
223
|
+
skippedTargets?: number;
|
|
222
224
|
profileId?: string;
|
|
223
225
|
targetsByPattern?: Readonly<Record<string, number>>;
|
|
224
226
|
targetsPreview?: readonly SearchTargetPreview[];
|
package/package.json
CHANGED
package/src/checks.ts
CHANGED
|
@@ -174,7 +174,7 @@ Prefer no match over a weak match.`,
|
|
|
174
174
|
"helper is domain-specific or used by multiple local call sites",
|
|
175
175
|
],
|
|
176
176
|
hookMode: "warn",
|
|
177
|
-
searchPrompt: "Find only tiny generic utility functions that recreate common helpers such as clamp, debounce, throttle, slugify,
|
|
177
|
+
searchPrompt: "Find only tiny generic utility functions that recreate common helpers such as clamp, debounce, throttle, slugify, sort, pick, omit, uniq, or shuffle without domain-specific behavior. Do not match group/resolve/parse/format helpers, domain formatting, feature constants, or helpers with multiple obvious call sites.",
|
|
178
178
|
searchExamples: {
|
|
179
179
|
match: [
|
|
180
180
|
"clampValue returns min, max, or value.",
|
package/src/constants.ts
CHANGED
package/src/counter-scout.ts
CHANGED
|
@@ -140,7 +140,7 @@ function lintBypassSignal(value: string): boolean {
|
|
|
140
140
|
|
|
141
141
|
function reinventedUtilitySignal(change: SemChange): boolean {
|
|
142
142
|
const name = change.entityName;
|
|
143
|
-
if (!/^(clamp|debounce|throttle|slug|slugify|
|
|
143
|
+
if (!/^(clamp|debounce|throttle|slug|slugify|sort|shuffle|memoize|pick|omit|uniq)/i.test(name)) return false;
|
|
144
144
|
const content = change.afterContent ?? "";
|
|
145
145
|
if (/currency|invoice|refund|subscription|tier|domain/i.test(`${name}\n${content}`)) return false;
|
|
146
146
|
return true;
|
package/src/stupify.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { realpathSync } from "node:fs";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { countPromptTokens, runSearch, searchRequest } from "./analysis.ts";
|
|
5
|
+
import { countPromptTokens, runSearch, searchRequest, type SearchRequest } from "./analysis.ts";
|
|
6
6
|
import { searchChecks } from "./checks.ts";
|
|
7
7
|
import { parseCommand } from "./command.ts";
|
|
8
8
|
import { counterScoutTargets } from "./counter-scout.ts";
|
|
@@ -153,17 +153,19 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
153
153
|
const pack = profile?.context === "sem" || searchContexts.length === contexts.length
|
|
154
154
|
? initialPack
|
|
155
155
|
: await repomixContextPack(changeSet.contextCwd, searchContexts, changeSet.changes, baseRepomixConfig);
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
const batches = await buildSearchBatches({
|
|
157
|
+
command,
|
|
158
158
|
changeSet,
|
|
159
|
-
searchContexts,
|
|
160
|
-
pack,
|
|
159
|
+
contexts: searchContexts,
|
|
160
|
+
initialPack: pack,
|
|
161
161
|
checks,
|
|
162
162
|
profile,
|
|
163
|
-
command.includeCounterReasonInPrompt,
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
163
|
+
includeCounterReasonInPrompt: command.includeCounterReasonInPrompt,
|
|
164
|
+
maxSearchInputTokens,
|
|
165
|
+
baseRepomixConfig,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (batches.batches.length === 0) {
|
|
167
169
|
return {
|
|
168
170
|
schemaVersion: "search.v1",
|
|
169
171
|
mode: "search",
|
|
@@ -173,7 +175,7 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
173
175
|
stats: {
|
|
174
176
|
elapsedMs: Date.now() - startedAt,
|
|
175
177
|
modelCalls: 0,
|
|
176
|
-
inputTokens: estimatedInputTokens,
|
|
178
|
+
inputTokens: batches.estimatedInputTokens,
|
|
177
179
|
inputTokenCap: maxSearchInputTokens,
|
|
178
180
|
skipped: true,
|
|
179
181
|
skipReason: "input_too_large",
|
|
@@ -184,6 +186,8 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
184
186
|
repomixFiles: pack.filePaths.length,
|
|
185
187
|
repomixTokens: pack.totalTokens,
|
|
186
188
|
repomixConfig: pack.config,
|
|
189
|
+
searchBatches: 0,
|
|
190
|
+
skippedTargets: batches.skippedTargets,
|
|
187
191
|
profileId: profile?.id,
|
|
188
192
|
targetsByPattern: countTargetsByPattern(searchContexts),
|
|
189
193
|
targetsPreview: previewTargets(searchContexts),
|
|
@@ -192,43 +196,38 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
192
196
|
};
|
|
193
197
|
}
|
|
194
198
|
|
|
199
|
+
if (batches.wasSplit && !command.json) {
|
|
200
|
+
console.error(`Search input is large; queued ${batches.batches.length} smaller search batches.`);
|
|
201
|
+
if (batches.skippedTargets > 0) {
|
|
202
|
+
console.error(`Skipped ${batches.skippedTargets} oversized targets that could not fit alone.`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
195
206
|
const modelPath = await firstRunModelBootstrap(command.model);
|
|
196
207
|
const model = await loadLocalModel(modelPath, command.model, "scout");
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
repomixConfig: pack.config,
|
|
219
|
-
profileId: profile?.id,
|
|
220
|
-
targetsByPattern: countTargetsByPattern(searchContexts),
|
|
221
|
-
targetsPreview: previewTargets(searchContexts),
|
|
222
|
-
},
|
|
223
|
-
matches: [],
|
|
224
|
-
};
|
|
208
|
+
const matches = [];
|
|
209
|
+
let modelCalls = 0;
|
|
210
|
+
let inputTokens = 0;
|
|
211
|
+
let exactSkippedTargets = batches.skippedTargets;
|
|
212
|
+
for (const batch of batches.batches) {
|
|
213
|
+
const batchInputTokens = await countPromptTokens(model, batch.request.prompt);
|
|
214
|
+
inputTokens += batchInputTokens;
|
|
215
|
+
if (batchInputTokens > maxSearchInputTokens) {
|
|
216
|
+
exactSkippedTargets += batch.contexts.length;
|
|
217
|
+
if (!command.json) {
|
|
218
|
+
console.error(`Skipped ${batch.contexts.length} targets after exact token count exceeded the limit.`);
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
const { value } = await t.trace(
|
|
223
|
+
"search.model",
|
|
224
|
+
() => runSearch(model, batch.request),
|
|
225
|
+
{ count: (v) => v.length },
|
|
226
|
+
);
|
|
227
|
+
modelCalls += 1;
|
|
228
|
+
matches.push(...value);
|
|
225
229
|
}
|
|
226
|
-
|
|
227
|
-
const { value: matches } = await t.trace(
|
|
228
|
-
"search.model",
|
|
229
|
-
() => runSearch(model, request),
|
|
230
|
-
{ count: (v) => v.length },
|
|
231
|
-
);
|
|
230
|
+
const uniqueMatches = dedupeMatches(matches);
|
|
232
231
|
|
|
233
232
|
return {
|
|
234
233
|
schemaVersion: "search.v1",
|
|
@@ -238,7 +237,7 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
238
237
|
patterns: patternIds,
|
|
239
238
|
stats: {
|
|
240
239
|
elapsedMs: Date.now() - startedAt,
|
|
241
|
-
modelCalls
|
|
240
|
+
modelCalls,
|
|
242
241
|
inputTokens,
|
|
243
242
|
inputTokenCap: maxSearchInputTokens,
|
|
244
243
|
filesChanged: changeSet.summary.fileCount,
|
|
@@ -248,17 +247,146 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
248
247
|
repomixFiles: pack.filePaths.length,
|
|
249
248
|
repomixTokens: pack.totalTokens,
|
|
250
249
|
repomixConfig: pack.config,
|
|
250
|
+
searchBatches: batches.batches.length,
|
|
251
|
+
skippedTargets: exactSkippedTargets,
|
|
251
252
|
profileId: profile?.id,
|
|
252
253
|
targetsByPattern: countTargetsByPattern(searchContexts),
|
|
253
254
|
targetsPreview: previewTargets(searchContexts),
|
|
254
255
|
},
|
|
255
|
-
matches,
|
|
256
|
+
matches: uniqueMatches,
|
|
256
257
|
};
|
|
257
258
|
} finally {
|
|
258
259
|
await changeSet.cleanup();
|
|
259
260
|
}
|
|
260
261
|
}
|
|
261
262
|
|
|
263
|
+
function dedupeMatches<T extends { targetId: string; patternId: string; proof: string }>(matches: readonly T[]): readonly T[] {
|
|
264
|
+
const seen = new Set<string>();
|
|
265
|
+
return matches.filter((match) => {
|
|
266
|
+
const key = `${match.patternId}\n${match.proof.trim()}`;
|
|
267
|
+
if (seen.has(key)) return false;
|
|
268
|
+
seen.add(key);
|
|
269
|
+
return true;
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
type SearchBatch = Readonly<{
|
|
274
|
+
contexts: readonly SemContext[];
|
|
275
|
+
pack: SemContextPack;
|
|
276
|
+
request: SearchRequest;
|
|
277
|
+
estimatedInputTokens: number;
|
|
278
|
+
}>;
|
|
279
|
+
|
|
280
|
+
async function buildSearchBatches(input: Readonly<{
|
|
281
|
+
command: SearchCommand;
|
|
282
|
+
changeSet: Parameters<typeof searchRequest>[0]["changeSet"];
|
|
283
|
+
contexts: readonly SemContext[];
|
|
284
|
+
initialPack: SemContextPack;
|
|
285
|
+
checks: readonly StupifyCheck[];
|
|
286
|
+
profile: SearchProfile | null;
|
|
287
|
+
includeCounterReasonInPrompt: boolean;
|
|
288
|
+
maxSearchInputTokens: number;
|
|
289
|
+
baseRepomixConfig: Parameters<typeof repomixContextPack>[3];
|
|
290
|
+
}>): Promise<Readonly<{
|
|
291
|
+
batches: readonly SearchBatch[];
|
|
292
|
+
estimatedInputTokens: number;
|
|
293
|
+
skippedTargets: number;
|
|
294
|
+
wasSplit: boolean;
|
|
295
|
+
}>> {
|
|
296
|
+
const first = makeSearchBatch(input, input.contexts, input.initialPack);
|
|
297
|
+
if (first.estimatedInputTokens <= input.maxSearchInputTokens) {
|
|
298
|
+
return {
|
|
299
|
+
batches: [first],
|
|
300
|
+
estimatedInputTokens: first.estimatedInputTokens,
|
|
301
|
+
skippedTargets: 0,
|
|
302
|
+
wasSplit: false,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const batches: SearchBatch[] = [];
|
|
307
|
+
let skippedTargets = 0;
|
|
308
|
+
let currentContexts: readonly SemContext[] = [];
|
|
309
|
+
let currentBatch: SearchBatch | null = null;
|
|
310
|
+
|
|
311
|
+
for (const context of input.contexts) {
|
|
312
|
+
const candidateContexts = [...currentContexts, context];
|
|
313
|
+
const candidateBatch = await makeSearchBatchWithPack(input, candidateContexts);
|
|
314
|
+
if (candidateBatch.estimatedInputTokens <= input.maxSearchInputTokens) {
|
|
315
|
+
currentContexts = candidateContexts;
|
|
316
|
+
currentBatch = candidateBatch;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (currentBatch) {
|
|
321
|
+
batches.push(currentBatch);
|
|
322
|
+
currentContexts = [];
|
|
323
|
+
currentBatch = null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const singleBatch = candidateContexts.length === 1
|
|
327
|
+
? candidateBatch
|
|
328
|
+
: await makeSearchBatchWithPack(input, [context]);
|
|
329
|
+
if (singleBatch.estimatedInputTokens <= input.maxSearchInputTokens) {
|
|
330
|
+
currentContexts = [context];
|
|
331
|
+
currentBatch = singleBatch;
|
|
332
|
+
} else {
|
|
333
|
+
skippedTargets += 1;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (currentBatch) batches.push(currentBatch);
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
batches,
|
|
341
|
+
estimatedInputTokens: first.estimatedInputTokens,
|
|
342
|
+
skippedTargets,
|
|
343
|
+
wasSplit: true,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function makeSearchBatch(
|
|
348
|
+
input: Readonly<{
|
|
349
|
+
changeSet: Parameters<typeof searchRequest>[0]["changeSet"];
|
|
350
|
+
checks: readonly StupifyCheck[];
|
|
351
|
+
profile: SearchProfile | null;
|
|
352
|
+
includeCounterReasonInPrompt: boolean;
|
|
353
|
+
}>,
|
|
354
|
+
contexts: readonly SemContext[],
|
|
355
|
+
pack: SemContextPack,
|
|
356
|
+
): SearchBatch {
|
|
357
|
+
const request = buildSearchRequest(
|
|
358
|
+
input.changeSet,
|
|
359
|
+
contexts,
|
|
360
|
+
pack,
|
|
361
|
+
input.checks,
|
|
362
|
+
input.profile,
|
|
363
|
+
input.includeCounterReasonInPrompt,
|
|
364
|
+
);
|
|
365
|
+
return {
|
|
366
|
+
contexts,
|
|
367
|
+
pack,
|
|
368
|
+
request,
|
|
369
|
+
estimatedInputTokens: estimatePromptTokens(request.prompt),
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async function makeSearchBatchWithPack(
|
|
374
|
+
input: Readonly<{
|
|
375
|
+
command: SearchCommand;
|
|
376
|
+
changeSet: Parameters<typeof searchRequest>[0]["changeSet"];
|
|
377
|
+
checks: readonly StupifyCheck[];
|
|
378
|
+
profile: SearchProfile | null;
|
|
379
|
+
includeCounterReasonInPrompt: boolean;
|
|
380
|
+
baseRepomixConfig: Parameters<typeof repomixContextPack>[3];
|
|
381
|
+
}>,
|
|
382
|
+
contexts: readonly SemContext[],
|
|
383
|
+
): Promise<SearchBatch> {
|
|
384
|
+
const pack = input.profile?.context === "sem"
|
|
385
|
+
? emptyContextPack()
|
|
386
|
+
: await repomixContextPack(input.changeSet.contextCwd, contexts, input.changeSet.changes, input.baseRepomixConfig);
|
|
387
|
+
return makeSearchBatch(input, contexts, pack);
|
|
388
|
+
}
|
|
389
|
+
|
|
262
390
|
function buildSearchRequest(
|
|
263
391
|
changeSet: Parameters<typeof searchRequest>[0]["changeSet"],
|
|
264
392
|
contexts: Parameters<typeof searchRequest>[0]["contexts"],
|
|
@@ -302,7 +430,7 @@ function sourceLabel(command: SearchCommand): string {
|
|
|
302
430
|
}
|
|
303
431
|
|
|
304
432
|
function estimatePromptTokens(prompt: string): number {
|
|
305
|
-
return Math.ceil(prompt.length /
|
|
433
|
+
return Math.ceil(prompt.length / 3);
|
|
306
434
|
}
|
|
307
435
|
|
|
308
436
|
function countTargetsByPattern(contexts: readonly SemContext[]): Record<string, number> {
|
package/src/types.ts
CHANGED
|
@@ -216,6 +216,8 @@ export type SearchRunJson = Readonly<{
|
|
|
216
216
|
repomixTokens?: number;
|
|
217
217
|
repomixConfig?: RepomixSearchConfig;
|
|
218
218
|
searchTargets?: number;
|
|
219
|
+
searchBatches?: number;
|
|
220
|
+
skippedTargets?: number;
|
|
219
221
|
profileId?: string;
|
|
220
222
|
targetsByPattern?: Readonly<Record<string, number>>;
|
|
221
223
|
targetsPreview?: readonly SearchTargetPreview[];
|