@stupify/cli 0.0.5 → 0.0.6
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/cache.js +0 -2
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/model.js +3 -3
- package/dist/render.js +1 -1
- package/dist/stupify.js +59 -11
- package/package.json +1 -1
- package/src/cache.ts +0 -2
- package/src/constants.ts +1 -1
- package/src/model.ts +3 -3
- package/src/render.ts +1 -1
- package/src/stupify.ts +55 -11
package/dist/cache.js
CHANGED
|
@@ -10,11 +10,9 @@ export async function cachedJson(namespace, key, compute) {
|
|
|
10
10
|
const filePath = cachePath(namespace, key);
|
|
11
11
|
try {
|
|
12
12
|
const value = JSON.parse(await readFile(filePath, "utf8"));
|
|
13
|
-
console.error(`cache hit ${namespace} ${key.slice(0, 12)}`);
|
|
14
13
|
return value;
|
|
15
14
|
}
|
|
16
15
|
catch {
|
|
17
|
-
console.error(`cache miss ${namespace} ${key.slice(0, 12)}`);
|
|
18
16
|
}
|
|
19
17
|
const value = await compute();
|
|
20
18
|
await writeCache(filePath, value).catch(() => undefined);
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
package/dist/model.js
CHANGED
|
@@ -36,7 +36,7 @@ export async function loadLocalModel(modelPath, modelId, profile = "scout") {
|
|
|
36
36
|
if (runningModel !== modelId)
|
|
37
37
|
await stopManagedServer(runtime);
|
|
38
38
|
if (runningModel === modelId) {
|
|
39
|
-
console.error(`Using
|
|
39
|
+
console.error(`Using local model: ${selectedModel.name}`);
|
|
40
40
|
return {
|
|
41
41
|
id: modelId,
|
|
42
42
|
name: selectedModel.name,
|
|
@@ -107,7 +107,7 @@ async function startLlamaServer(modelPath, modelId, modelName, runtime) {
|
|
|
107
107
|
const logPath = path.join(logDir, "llama-server.log");
|
|
108
108
|
const out = await open(logPath, "a");
|
|
109
109
|
const err = await open(logPath, "a");
|
|
110
|
-
console.error(`Starting local
|
|
110
|
+
console.error(`Starting local model server: ${modelName}`);
|
|
111
111
|
console.error(`llama-server log: ${logPath}`);
|
|
112
112
|
const args = [
|
|
113
113
|
"-m",
|
|
@@ -158,7 +158,7 @@ async function stopManagedServer(runtime) {
|
|
|
158
158
|
throw new Error(`A llama-server is already running with ${runningModel ?? "another model"}.
|
|
159
159
|
Stop it before switching models, or use STUPIFY_LLAMA_SERVER_URL for that server.`);
|
|
160
160
|
}
|
|
161
|
-
console.error(
|
|
161
|
+
console.error("Restarting local model server for selected model.");
|
|
162
162
|
try {
|
|
163
163
|
process.kill(pid, "SIGTERM");
|
|
164
164
|
}
|
package/dist/render.js
CHANGED
|
@@ -12,7 +12,7 @@ ${run.stats.inputTokenCap ?? "unknown"} tokens
|
|
|
12
12
|
Stupify skipped the search rather than review truncated context.
|
|
13
13
|
Nothing was blocked.
|
|
14
14
|
Try:
|
|
15
|
-
|
|
15
|
+
rerun with ${sourceHint(command)} --max-search-input-tokens ${Math.max((run.stats.inputTokens ?? 12_000) + 1, (run.stats.inputTokenCap ?? 12_000) * 2)}`;
|
|
16
16
|
}
|
|
17
17
|
if (run.stats.skipped && run.stats.skipReason === "no_candidates") {
|
|
18
18
|
return `🧙 stupify 🪄
|
package/dist/stupify.js
CHANGED
|
@@ -48,12 +48,9 @@ export async function runSearchCommand(command, startedAt) {
|
|
|
48
48
|
const t = createTracer({
|
|
49
49
|
writeLine: () => undefined,
|
|
50
50
|
onEvent: (event) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (event.detail)
|
|
55
|
-
parts.push(event.detail);
|
|
56
|
-
console.error(parts.join(" "));
|
|
51
|
+
if (command.json)
|
|
52
|
+
return;
|
|
53
|
+
console.error(formatStep(event.name, event.ms, event.count, event.detail));
|
|
57
54
|
},
|
|
58
55
|
});
|
|
59
56
|
const profile = await loadSearchProfile(command.searchProfilePath);
|
|
@@ -61,12 +58,12 @@ export async function runSearchCommand(command, startedAt) {
|
|
|
61
58
|
const patternIds = checks.map((check) => check.id);
|
|
62
59
|
const maxCandidates = effectiveMaxCandidates(command.maxCandidates, profile);
|
|
63
60
|
const maxSearchInputTokens = effectiveMaxSearchInputTokens(command.maxSearchInputTokens, profile);
|
|
61
|
+
printRunPlan(command, patternIds);
|
|
64
62
|
const { value: changeSet } = await t.trace("entity.diff", () => semChangeSetForCommand(command), {
|
|
65
63
|
count: (v) => v.summary.total,
|
|
66
64
|
detail: (v) => `${v.summary.fileCount} files`,
|
|
67
65
|
});
|
|
68
66
|
try {
|
|
69
|
-
printRunPlan(command, changeSet.summary.fileCount, changeSet.summary.total, patternIds);
|
|
70
67
|
const candidates = counterScoutTargets(changeSet, checks, maxCandidates);
|
|
71
68
|
const contexts = entityContextsFromChanges(candidates, changeSet.changes);
|
|
72
69
|
const targetsByPattern = countTargetsByPattern(contexts);
|
|
@@ -136,9 +133,38 @@ export async function runSearchCommand(command, startedAt) {
|
|
|
136
133
|
const pack = profile?.context === "sem" || searchContexts.length === contexts.length
|
|
137
134
|
? initialPack
|
|
138
135
|
: await repomixContextPack(changeSet.contextCwd, searchContexts, changeSet.changes, baseRepomixConfig);
|
|
136
|
+
const request = buildSearchRequest(changeSet, searchContexts, pack, checks, profile, command.includeCounterReasonInPrompt);
|
|
137
|
+
const estimatedInputTokens = estimatePromptTokens(request.prompt);
|
|
138
|
+
if (estimatedInputTokens > maxSearchInputTokens) {
|
|
139
|
+
return {
|
|
140
|
+
schemaVersion: "search.v1",
|
|
141
|
+
mode: "search",
|
|
142
|
+
source: command.source,
|
|
143
|
+
model: { id: command.model },
|
|
144
|
+
patterns: patternIds,
|
|
145
|
+
stats: {
|
|
146
|
+
elapsedMs: Date.now() - startedAt,
|
|
147
|
+
modelCalls: 0,
|
|
148
|
+
inputTokens: estimatedInputTokens,
|
|
149
|
+
inputTokenCap: maxSearchInputTokens,
|
|
150
|
+
skipped: true,
|
|
151
|
+
skipReason: "input_too_large",
|
|
152
|
+
filesChanged: changeSet.summary.fileCount,
|
|
153
|
+
entitiesScanned: changeSet.summary.total,
|
|
154
|
+
candidates: contexts.length,
|
|
155
|
+
searchTargets: searchContexts.length,
|
|
156
|
+
repomixFiles: pack.filePaths.length,
|
|
157
|
+
repomixTokens: pack.totalTokens,
|
|
158
|
+
repomixConfig: pack.config,
|
|
159
|
+
profileId: profile?.id,
|
|
160
|
+
targetsByPattern: countTargetsByPattern(searchContexts),
|
|
161
|
+
targetsPreview: previewTargets(searchContexts),
|
|
162
|
+
},
|
|
163
|
+
matches: [],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
139
166
|
const modelPath = await firstRunModelBootstrap(command.model);
|
|
140
167
|
const model = await loadLocalModel(modelPath, command.model, "scout");
|
|
141
|
-
const request = buildSearchRequest(changeSet, searchContexts, pack, checks, profile, command.includeCounterReasonInPrompt);
|
|
142
168
|
const inputTokens = await countPromptTokens(model, request.prompt);
|
|
143
169
|
if (inputTokens > maxSearchInputTokens) {
|
|
144
170
|
return {
|
|
@@ -207,14 +233,36 @@ function buildSearchRequest(changeSet, contexts, pack, patterns, profile, includ
|
|
|
207
233
|
includeCounterReasonInPrompt: profile?.includeCounterReasonInPrompt ?? includeCounterReasonInPrompt,
|
|
208
234
|
});
|
|
209
235
|
}
|
|
210
|
-
function printRunPlan(command,
|
|
236
|
+
function printRunPlan(command, patternIds) {
|
|
211
237
|
if (command.json)
|
|
212
238
|
return;
|
|
213
239
|
console.error("🧙 stupify 🪄");
|
|
214
|
-
console.error(`
|
|
215
|
-
console.error(`Sem: ${filesChanged} files, ${entitiesScanned} changed entities`);
|
|
240
|
+
console.error(`Search: ${sourceLabel(command)}`);
|
|
216
241
|
console.error(`Patterns: ${patternIds.join(", ")}`);
|
|
217
242
|
}
|
|
243
|
+
function formatStep(name, ms, count, detail) {
|
|
244
|
+
if (name === "entity.diff")
|
|
245
|
+
return `Diff: ${detail ?? "changed files"}, ${count ?? 0} changed entities (${ms}ms)`;
|
|
246
|
+
if (name === "context.pack")
|
|
247
|
+
return `Context: ${count ?? 0} files, ${detail ?? "0 tokens"} (${ms}ms)`;
|
|
248
|
+
if (name === "search.model")
|
|
249
|
+
return `Model: ${count ?? 0} matches (${ms}ms)`;
|
|
250
|
+
return `${name}: ${ms}ms`;
|
|
251
|
+
}
|
|
252
|
+
function sourceLabel(command) {
|
|
253
|
+
if (command.kind === "since")
|
|
254
|
+
return `since ${command.since}`;
|
|
255
|
+
if (command.kind === "commit")
|
|
256
|
+
return `commit ${command.commit}`;
|
|
257
|
+
if (command.kind === "commits")
|
|
258
|
+
return `last ${command.count} commits`;
|
|
259
|
+
if (command.kind === "staged")
|
|
260
|
+
return "staged changes";
|
|
261
|
+
return "stdin diff";
|
|
262
|
+
}
|
|
263
|
+
function estimatePromptTokens(prompt) {
|
|
264
|
+
return Math.ceil(prompt.length / 4);
|
|
265
|
+
}
|
|
218
266
|
function countTargetsByPattern(contexts) {
|
|
219
267
|
const counts = {};
|
|
220
268
|
for (const context of contexts)
|
package/package.json
CHANGED
package/src/cache.ts
CHANGED
|
@@ -16,10 +16,8 @@ export async function cachedJson<T>(
|
|
|
16
16
|
const filePath = cachePath(namespace, key);
|
|
17
17
|
try {
|
|
18
18
|
const value = JSON.parse(await readFile(filePath, "utf8")) as T;
|
|
19
|
-
console.error(`cache hit ${namespace} ${key.slice(0, 12)}`);
|
|
20
19
|
return value;
|
|
21
20
|
} catch {
|
|
22
|
-
console.error(`cache miss ${namespace} ${key.slice(0, 12)}`);
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
const value = await compute();
|
package/src/constants.ts
CHANGED
package/src/model.ts
CHANGED
|
@@ -85,7 +85,7 @@ export async function loadLocalModel(
|
|
|
85
85
|
if (runningModel !== modelId) await stopManagedServer(runtime);
|
|
86
86
|
if (runningModel === modelId) {
|
|
87
87
|
console.error(
|
|
88
|
-
`Using
|
|
88
|
+
`Using local model: ${selectedModel.name}`,
|
|
89
89
|
);
|
|
90
90
|
return {
|
|
91
91
|
id: modelId,
|
|
@@ -166,7 +166,7 @@ async function startLlamaServer(
|
|
|
166
166
|
const out = await open(logPath, "a");
|
|
167
167
|
const err = await open(logPath, "a");
|
|
168
168
|
|
|
169
|
-
console.error(`Starting local
|
|
169
|
+
console.error(`Starting local model server: ${modelName}`);
|
|
170
170
|
console.error(`llama-server log: ${logPath}`);
|
|
171
171
|
|
|
172
172
|
const args = [
|
|
@@ -215,7 +215,7 @@ Stop it before switching models, or use STUPIFY_LLAMA_SERVER_URL for that server
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
console.error(
|
|
218
|
-
|
|
218
|
+
"Restarting local model server for selected model.",
|
|
219
219
|
);
|
|
220
220
|
try {
|
|
221
221
|
process.kill(pid, "SIGTERM");
|
package/src/render.ts
CHANGED
|
@@ -14,7 +14,7 @@ ${run.stats.inputTokenCap ?? "unknown"} tokens
|
|
|
14
14
|
Stupify skipped the search rather than review truncated context.
|
|
15
15
|
Nothing was blocked.
|
|
16
16
|
Try:
|
|
17
|
-
|
|
17
|
+
rerun with ${sourceHint(command)} --max-search-input-tokens ${Math.max((run.stats.inputTokens ?? 12_000) + 1, (run.stats.inputTokenCap ?? 12_000) * 2)}`;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
if (run.stats.skipped && run.stats.skipReason === "no_candidates") {
|
package/src/stupify.ts
CHANGED
|
@@ -58,10 +58,8 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
58
58
|
const t = createTracer({
|
|
59
59
|
writeLine: () => undefined,
|
|
60
60
|
onEvent: (event) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (event.detail) parts.push(event.detail);
|
|
64
|
-
console.error(parts.join(" "));
|
|
61
|
+
if (command.json) return;
|
|
62
|
+
console.error(formatStep(event.name, event.ms, event.count, event.detail));
|
|
65
63
|
},
|
|
66
64
|
});
|
|
67
65
|
|
|
@@ -70,6 +68,7 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
70
68
|
const patternIds = checks.map((check) => check.id);
|
|
71
69
|
const maxCandidates = effectiveMaxCandidates(command.maxCandidates, profile);
|
|
72
70
|
const maxSearchInputTokens = effectiveMaxSearchInputTokens(command.maxSearchInputTokens, profile);
|
|
71
|
+
printRunPlan(command, patternIds);
|
|
73
72
|
const { value: changeSet } = await t.trace(
|
|
74
73
|
"entity.diff",
|
|
75
74
|
() => semChangeSetForCommand(command),
|
|
@@ -80,7 +79,6 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
80
79
|
);
|
|
81
80
|
|
|
82
81
|
try {
|
|
83
|
-
printRunPlan(command, changeSet.summary.fileCount, changeSet.summary.total, patternIds);
|
|
84
82
|
const candidates = counterScoutTargets(changeSet, checks, maxCandidates);
|
|
85
83
|
const contexts = entityContextsFromChanges(candidates, changeSet.changes);
|
|
86
84
|
const targetsByPattern = countTargetsByPattern(contexts);
|
|
@@ -156,8 +154,6 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
156
154
|
? initialPack
|
|
157
155
|
: await repomixContextPack(changeSet.contextCwd, searchContexts, changeSet.changes, baseRepomixConfig);
|
|
158
156
|
|
|
159
|
-
const modelPath = await firstRunModelBootstrap(command.model);
|
|
160
|
-
const model = await loadLocalModel(modelPath, command.model, "scout");
|
|
161
157
|
const request = buildSearchRequest(
|
|
162
158
|
changeSet,
|
|
163
159
|
searchContexts,
|
|
@@ -166,6 +162,38 @@ export async function runSearchCommand(command: SearchCommand, startedAt: number
|
|
|
166
162
|
profile,
|
|
167
163
|
command.includeCounterReasonInPrompt,
|
|
168
164
|
);
|
|
165
|
+
const estimatedInputTokens = estimatePromptTokens(request.prompt);
|
|
166
|
+
if (estimatedInputTokens > maxSearchInputTokens) {
|
|
167
|
+
return {
|
|
168
|
+
schemaVersion: "search.v1",
|
|
169
|
+
mode: "search",
|
|
170
|
+
source: command.source,
|
|
171
|
+
model: { id: command.model },
|
|
172
|
+
patterns: patternIds,
|
|
173
|
+
stats: {
|
|
174
|
+
elapsedMs: Date.now() - startedAt,
|
|
175
|
+
modelCalls: 0,
|
|
176
|
+
inputTokens: estimatedInputTokens,
|
|
177
|
+
inputTokenCap: maxSearchInputTokens,
|
|
178
|
+
skipped: true,
|
|
179
|
+
skipReason: "input_too_large",
|
|
180
|
+
filesChanged: changeSet.summary.fileCount,
|
|
181
|
+
entitiesScanned: changeSet.summary.total,
|
|
182
|
+
candidates: contexts.length,
|
|
183
|
+
searchTargets: searchContexts.length,
|
|
184
|
+
repomixFiles: pack.filePaths.length,
|
|
185
|
+
repomixTokens: pack.totalTokens,
|
|
186
|
+
repomixConfig: pack.config,
|
|
187
|
+
profileId: profile?.id,
|
|
188
|
+
targetsByPattern: countTargetsByPattern(searchContexts),
|
|
189
|
+
targetsPreview: previewTargets(searchContexts),
|
|
190
|
+
},
|
|
191
|
+
matches: [],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const modelPath = await firstRunModelBootstrap(command.model);
|
|
196
|
+
const model = await loadLocalModel(modelPath, command.model, "scout");
|
|
169
197
|
const inputTokens = await countPromptTokens(model, request.prompt);
|
|
170
198
|
if (inputTokens > maxSearchInputTokens) {
|
|
171
199
|
return {
|
|
@@ -250,17 +278,33 @@ function buildSearchRequest(
|
|
|
250
278
|
|
|
251
279
|
function printRunPlan(
|
|
252
280
|
command: SearchCommand,
|
|
253
|
-
filesChanged: number,
|
|
254
|
-
entitiesScanned: number,
|
|
255
281
|
patternIds: readonly string[],
|
|
256
282
|
): void {
|
|
257
283
|
if (command.json) return;
|
|
258
284
|
console.error("🧙 stupify 🪄");
|
|
259
|
-
console.error(`
|
|
260
|
-
console.error(`Sem: ${filesChanged} files, ${entitiesScanned} changed entities`);
|
|
285
|
+
console.error(`Search: ${sourceLabel(command)}`);
|
|
261
286
|
console.error(`Patterns: ${patternIds.join(", ")}`);
|
|
262
287
|
}
|
|
263
288
|
|
|
289
|
+
function formatStep(name: string, ms: number, count?: number, detail?: string): string {
|
|
290
|
+
if (name === "entity.diff") return `Diff: ${detail ?? "changed files"}, ${count ?? 0} changed entities (${ms}ms)`;
|
|
291
|
+
if (name === "context.pack") return `Context: ${count ?? 0} files, ${detail ?? "0 tokens"} (${ms}ms)`;
|
|
292
|
+
if (name === "search.model") return `Model: ${count ?? 0} matches (${ms}ms)`;
|
|
293
|
+
return `${name}: ${ms}ms`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function sourceLabel(command: SearchCommand): string {
|
|
297
|
+
if (command.kind === "since") return `since ${command.since}`;
|
|
298
|
+
if (command.kind === "commit") return `commit ${command.commit}`;
|
|
299
|
+
if (command.kind === "commits") return `last ${command.count} commits`;
|
|
300
|
+
if (command.kind === "staged") return "staged changes";
|
|
301
|
+
return "stdin diff";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function estimatePromptTokens(prompt: string): number {
|
|
305
|
+
return Math.ceil(prompt.length / 4);
|
|
306
|
+
}
|
|
307
|
+
|
|
264
308
|
function countTargetsByPattern(contexts: readonly SemContext[]): Record<string, number> {
|
|
265
309
|
const counts: Record<string, number> = {};
|
|
266
310
|
for (const context of contexts) counts[context.checkId] = (counts[context.checkId] ?? 0) + 1;
|