@stupify/cli 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -31
- package/dist/analysis.d.ts +11 -9
- package/dist/analysis.js +30 -173
- package/dist/checks.d.ts +1 -0
- package/dist/checks.js +89 -2
- package/dist/command.js +55 -91
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/counter-scout.js +70 -8
- package/dist/doctor.d.ts +4 -0
- package/dist/doctor.js +131 -0
- package/dist/git.d.ts +4 -1
- package/dist/git.js +34 -0
- package/dist/hooks.d.ts +3 -0
- package/dist/hooks.js +117 -0
- package/dist/model.d.ts +1 -15
- package/dist/model.js +37 -21
- package/dist/prompts.d.ts +8 -5
- package/dist/prompts.js +58 -168
- package/dist/render.d.ts +2 -2
- package/dist/render.js +70 -78
- package/dist/repomix-provider.d.ts +10 -2
- package/dist/repomix-provider.js +62 -11
- package/dist/search-bench.d.ts +1 -0
- package/dist/search-bench.js +675 -0
- package/dist/search-profile.d.ts +6 -0
- package/dist/search-profile.js +73 -0
- package/dist/sem-provider.d.ts +2 -2
- package/dist/sem-provider.js +33 -7
- package/dist/stupify.d.ts +2 -0
- package/dist/stupify.js +185 -334
- package/dist/types.d.ts +193 -109
- package/package.json +1 -1
- package/src/analysis.ts +48 -268
- package/src/checks.ts +91 -2
- package/src/command.ts +62 -107
- package/src/constants.ts +1 -1
- package/src/counter-scout.ts +63 -7
- package/src/doctor.ts +140 -0
- package/src/git.ts +35 -1
- package/src/hooks.ts +134 -0
- package/src/model.ts +39 -26
- package/src/prompts.ts +66 -202
- package/src/render.ts +68 -79
- package/src/repomix-provider.ts +66 -10
- package/src/search-bench.ts +783 -0
- package/src/search-profile.ts +89 -0
- package/src/sem-provider.ts +36 -9
- package/src/stupify.ts +215 -527
- package/src/types.ts +195 -119
- package/dist/batcher.d.ts +0 -3
- package/dist/batcher.js +0 -142
- package/dist/candidate-context.d.ts +0 -2
- package/dist/candidate-context.js +0 -40
- package/dist/experiment.d.ts +0 -1
- package/dist/experiment.js +0 -225
- package/src/batcher.ts +0 -198
- package/src/candidate-context.ts +0 -43
- package/src/experiment.ts +0 -317
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { chmod, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { promisify } from "node:util";
|
|
6
|
+
import { gitPath, gitRoot } from "./git.ts";
|
|
7
|
+
import type { HookAction } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
const START = "# stupify hook start";
|
|
11
|
+
const END = "# stupify hook end";
|
|
12
|
+
|
|
13
|
+
export async function runHookCommand(action: HookAction): Promise<string> {
|
|
14
|
+
if (action === "status") return hookStatus();
|
|
15
|
+
if (action === "install") return installHook();
|
|
16
|
+
return uninstallHook();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function hookSnippet(): string {
|
|
20
|
+
return managedBlock("stupify --staged");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function hookStatus(): Promise<string> {
|
|
24
|
+
const hookPath = await preCommitHookPath();
|
|
25
|
+
if (!existsSync(hookPath)) return "Stupify hook: not installed";
|
|
26
|
+
|
|
27
|
+
const content = await readFile(hookPath, "utf8");
|
|
28
|
+
if (hasManagedBlock(content)) return "Stupify hook: installed";
|
|
29
|
+
return "Stupify hook: existing non-Stupify pre-commit hook found";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function installHook(): Promise<string> {
|
|
33
|
+
const hookPath = await preCommitHookPath();
|
|
34
|
+
const block = await managedBlockForInstall();
|
|
35
|
+
if (!existsSync(hookPath)) {
|
|
36
|
+
await writeFile(hookPath, `#!/bin/sh\n${block}\n`, "utf8");
|
|
37
|
+
await chmod(hookPath, 0o755);
|
|
38
|
+
return "Stupify hook: installed";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const content = await readFile(hookPath, "utf8");
|
|
42
|
+
if (hasManagedBlock(content)) {
|
|
43
|
+
await writeFile(hookPath, `${replaceManagedBlock(content, block).trimEnd()}\n`, "utf8");
|
|
44
|
+
await chmod(hookPath, 0o755);
|
|
45
|
+
return "Stupify hook: updated";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (isEffectivelyEmptyHook(content)) {
|
|
49
|
+
await writeFile(hookPath, `#!/bin/sh\n${block}\n`, "utf8");
|
|
50
|
+
await chmod(hookPath, 0o755);
|
|
51
|
+
return "Stupify hook: installed";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return `Stupify hook: existing non-Stupify pre-commit hook found; not modified.
|
|
55
|
+
Add this snippet manually if you want Stupify in that hook:
|
|
56
|
+
${block}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function uninstallHook(): Promise<string> {
|
|
60
|
+
const hookPath = await preCommitHookPath();
|
|
61
|
+
if (!existsSync(hookPath)) return "Stupify hook: not installed";
|
|
62
|
+
|
|
63
|
+
const content = await readFile(hookPath, "utf8");
|
|
64
|
+
if (!hasManagedBlock(content)) return "Stupify hook: not installed";
|
|
65
|
+
|
|
66
|
+
const next = replaceManagedBlock(content, "").trim();
|
|
67
|
+
if (isEffectivelyEmptyHook(next)) {
|
|
68
|
+
await rm(hookPath, { force: true });
|
|
69
|
+
return "Stupify hook: uninstalled";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await writeFile(hookPath, `${next}\n`, "utf8");
|
|
73
|
+
await chmod(hookPath, 0o755);
|
|
74
|
+
return "Stupify hook: uninstalled";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function preCommitHookPath(): Promise<string> {
|
|
78
|
+
const [root, hook] = await Promise.all([gitRoot(), gitPath("hooks/pre-commit")]);
|
|
79
|
+
return path.isAbsolute(hook) ? hook : path.join(root, hook);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hasManagedBlock(content: string): boolean {
|
|
83
|
+
return content.includes(START) && content.includes(END);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function managedBlockForInstall(): Promise<string> {
|
|
87
|
+
if (await commandExists("stupify")) return managedBlock("stupify --staged");
|
|
88
|
+
|
|
89
|
+
const root = await gitRoot();
|
|
90
|
+
const localEntrypoint = path.join(root, "packages", "cli", "src", "stupify.ts");
|
|
91
|
+
if (existsSync(localEntrypoint) && await commandExists("bun")) {
|
|
92
|
+
return managedBlock(`bun ${shellQuote(localEntrypoint)} --staged`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return managedBlock("stupify --staged");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function managedBlock(command: string): string {
|
|
99
|
+
return `${START}
|
|
100
|
+
${command} || true
|
|
101
|
+
${END}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function replaceManagedBlock(content: string, replacement: string): string {
|
|
105
|
+
const pattern = new RegExp(`${escapeRegExp(START)}[\\s\\S]*?${escapeRegExp(END)}`);
|
|
106
|
+
return content.replace(pattern, replacement);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function isEffectivelyEmptyHook(content: string): boolean {
|
|
110
|
+
return content
|
|
111
|
+
.split(/\r?\n/)
|
|
112
|
+
.map((line) => line.trim())
|
|
113
|
+
.filter((line) => line && line !== "#!/bin/sh" && line !== "#!/usr/bin/env sh")
|
|
114
|
+
.length === 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function escapeRegExp(value: string): string {
|
|
118
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function commandExists(command: string): Promise<boolean> {
|
|
122
|
+
try {
|
|
123
|
+
await execFileAsync("sh", ["-c", `command -v ${shellQuote(command)}`], {
|
|
124
|
+
maxBuffer: 1024 * 1024,
|
|
125
|
+
});
|
|
126
|
+
return true;
|
|
127
|
+
} catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function shellQuote(value: string): string {
|
|
133
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
134
|
+
}
|
package/src/model.ts
CHANGED
|
@@ -24,14 +24,22 @@ import type { ModelId } from "./types.ts";
|
|
|
24
24
|
const execFileAsync = promisify(execFile);
|
|
25
25
|
const LLAMA_SERVER_HOST = "127.0.0.1";
|
|
26
26
|
|
|
27
|
-
export type ModelProfile = "scout"
|
|
27
|
+
export type ModelProfile = "scout";
|
|
28
28
|
|
|
29
29
|
type ModelRuntime = Readonly<{
|
|
30
30
|
profile: ModelProfile;
|
|
31
31
|
baseUrl: string;
|
|
32
32
|
port: string;
|
|
33
|
+
contextSize: number;
|
|
33
34
|
reasoning: "on" | "off" | "auto";
|
|
34
35
|
reasoningBudget?: number;
|
|
36
|
+
gpuLayers?: number;
|
|
37
|
+
batchSize?: number;
|
|
38
|
+
ubatchSize?: number;
|
|
39
|
+
parallel?: number;
|
|
40
|
+
threads?: number;
|
|
41
|
+
threadsBatch?: number;
|
|
42
|
+
flashAttention?: boolean;
|
|
35
43
|
}>;
|
|
36
44
|
|
|
37
45
|
export type LocalModel = Readonly<{
|
|
@@ -41,13 +49,6 @@ export type LocalModel = Readonly<{
|
|
|
41
49
|
profile: ModelProfile;
|
|
42
50
|
}>;
|
|
43
51
|
|
|
44
|
-
export async function loadLocalModels(modelId: ModelId) {
|
|
45
|
-
const modelPath = await firstRunModelBootstrap(modelId);
|
|
46
|
-
const scoutModel = await loadLocalModel(modelPath, modelId, "scout");
|
|
47
|
-
const auditModel = await loadLocalModel(modelPath, modelId, "audit");
|
|
48
|
-
return { scoutModel, auditModel };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
52
|
export async function firstRunModelBootstrap(
|
|
52
53
|
modelId: ModelId,
|
|
53
54
|
): Promise<string> {
|
|
@@ -107,18 +108,6 @@ export async function loadLocalModel(
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
function modelRuntime(profile: ModelProfile): ModelRuntime {
|
|
110
|
-
if (profile === "audit") {
|
|
111
|
-
const baseUrl =
|
|
112
|
-
process.env.STUPIFY_AUDIT_LLAMA_SERVER_URL ?? "http://127.0.0.1:8092";
|
|
113
|
-
return {
|
|
114
|
-
profile,
|
|
115
|
-
baseUrl,
|
|
116
|
-
port: new URL(baseUrl).port || "8092",
|
|
117
|
-
reasoning: "on",
|
|
118
|
-
reasoningBudget: 4_096,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
111
|
const baseUrl =
|
|
123
112
|
process.env.STUPIFY_SCOUT_LLAMA_SERVER_URL ??
|
|
124
113
|
process.env.STUPIFY_LLAMA_SERVER_URL ??
|
|
@@ -127,7 +116,15 @@ function modelRuntime(profile: ModelProfile): ModelRuntime {
|
|
|
127
116
|
profile,
|
|
128
117
|
baseUrl,
|
|
129
118
|
port: new URL(baseUrl).port || "8091",
|
|
119
|
+
contextSize: envInteger("STUPIFY_LLAMA_CONTEXT") ?? 65_536,
|
|
130
120
|
reasoning: "off",
|
|
121
|
+
gpuLayers: envInteger("STUPIFY_LLAMA_GPU_LAYERS") ?? 999,
|
|
122
|
+
batchSize: envInteger("STUPIFY_LLAMA_BATCH") ?? 2_048,
|
|
123
|
+
ubatchSize: envInteger("STUPIFY_LLAMA_UBATCH") ?? 512,
|
|
124
|
+
parallel: envInteger("STUPIFY_LLAMA_PARALLEL") ?? 2,
|
|
125
|
+
threads: envInteger("STUPIFY_LLAMA_THREADS"),
|
|
126
|
+
threadsBatch: envInteger("STUPIFY_LLAMA_THREADS_BATCH"),
|
|
127
|
+
flashAttention: envBoolean("STUPIFY_LLAMA_FLASH_ATTN"),
|
|
131
128
|
};
|
|
132
129
|
}
|
|
133
130
|
|
|
@@ -182,11 +179,18 @@ async function startLlamaServer(
|
|
|
182
179
|
"--port",
|
|
183
180
|
runtime.port,
|
|
184
181
|
"-c",
|
|
185
|
-
|
|
182
|
+
String(runtime.contextSize),
|
|
186
183
|
"--reasoning",
|
|
187
184
|
runtime.reasoning,
|
|
188
185
|
"--no-warmup",
|
|
189
186
|
];
|
|
187
|
+
if (runtime.gpuLayers !== undefined) args.push("-ngl", String(runtime.gpuLayers));
|
|
188
|
+
if (runtime.batchSize !== undefined) args.push("-b", String(runtime.batchSize));
|
|
189
|
+
if (runtime.ubatchSize !== undefined) args.push("-ub", String(runtime.ubatchSize));
|
|
190
|
+
if (runtime.parallel !== undefined) args.push("-np", String(runtime.parallel));
|
|
191
|
+
if (runtime.threads !== undefined) args.push("-t", String(runtime.threads));
|
|
192
|
+
if (runtime.threadsBatch !== undefined) args.push("-tb", String(runtime.threadsBatch));
|
|
193
|
+
if (runtime.flashAttention !== undefined) args.push("-fa", runtime.flashAttention ? "on" : "off");
|
|
190
194
|
if (runtime.reasoningBudget !== undefined) {
|
|
191
195
|
args.push("--reasoning-budget", String(runtime.reasoningBudget));
|
|
192
196
|
}
|
|
@@ -242,11 +246,20 @@ async function managedServerPid(runtime: ModelRuntime): Promise<number | null> {
|
|
|
242
246
|
}
|
|
243
247
|
|
|
244
248
|
function pidPath(runtime: ModelRuntime): string {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
249
|
+
return path.join(cacheDir(), "llama-server.pid");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function envInteger(name: string, fallback?: number): number | undefined {
|
|
253
|
+
const raw = process.env[name];
|
|
254
|
+
if (raw === undefined || raw === "") return fallback;
|
|
255
|
+
const value = Number(raw);
|
|
256
|
+
return Number.isInteger(value) && value > 0 ? value : fallback;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function envBoolean(name: string): boolean | undefined {
|
|
260
|
+
const raw = process.env[name];
|
|
261
|
+
if (raw === undefined || raw === "") return undefined;
|
|
262
|
+
return /^(1|true|yes|on)$/i.test(raw);
|
|
250
263
|
}
|
|
251
264
|
|
|
252
265
|
async function waitForServer(baseUrl: string, modelId: ModelId): Promise<void> {
|
package/src/prompts.ts
CHANGED
|
@@ -1,234 +1,98 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
${batch.text}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function auditPrompt(
|
|
32
|
-
contexts: readonly CandidateContext[],
|
|
33
|
-
checks: readonly StupifyCheck[],
|
|
34
|
-
sourceLabel: string,
|
|
35
|
-
): string {
|
|
36
|
-
return `Audit candidate diff regions against enabled checks.
|
|
1
|
+
import type { SemChangeSet, SemContext, SemContextPack, StupifyCheck } from "./types.ts";
|
|
2
|
+
|
|
3
|
+
export function searchPrompt(input: Readonly<{
|
|
4
|
+
changeSet: SemChangeSet;
|
|
5
|
+
contexts: readonly SemContext[];
|
|
6
|
+
pack: SemContextPack;
|
|
7
|
+
patterns: readonly StupifyCheck[];
|
|
8
|
+
includeCounterReason: boolean;
|
|
9
|
+
}>): string {
|
|
10
|
+
return `You are Stupify's local search model.
|
|
11
|
+
Stupify checks whether AI-assisted coding may be replacing developer judgment.
|
|
12
|
+
You will receive:
|
|
13
|
+
1. Semantic changed entities selected by a fast local counter.
|
|
14
|
+
2. Compressed local file context from Repomix.
|
|
15
|
+
3. A list of search targets. Each target has exactly one assigned pattern.
|
|
16
|
+
|
|
17
|
+
Your job:
|
|
18
|
+
Evaluate each target only against its assigned pattern.
|
|
19
|
+
False positives are expensive.
|
|
20
|
+
Only emit a match if the assigned pattern clearly applies to that exact target.
|
|
21
|
+
Do not perform general code review.
|
|
22
|
+
Do not suggest improvements.
|
|
23
|
+
Do not choose a pattern.
|
|
24
|
+
Do not apply other patterns.
|
|
25
|
+
Do not report issues for unlisted targets.
|
|
26
|
+
Do not emit clean results.
|
|
27
|
+
Omitted target = clean.
|
|
37
28
|
Return JSON only:
|
|
38
29
|
{
|
|
39
|
-
"
|
|
40
|
-
"summary": "one short sentence"
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
Rules:
|
|
44
|
-
- Use only checks listed below.
|
|
45
|
-
- checkId must be a check ID, never a POINTER.
|
|
46
|
-
- proof must be one exact POINTER from candidate regions.
|
|
47
|
-
- why describes the suspicious structure, not an identifier.
|
|
48
|
-
- Do not describe an issue in summary unless it is also in findings.
|
|
49
|
-
- If no findings, return { "findings": [], "summary": "No clear judgment-offload signal found." }.
|
|
50
|
-
|
|
51
|
-
Allowed proof pointers:
|
|
52
|
-
${contexts.map((context) => `- ${context.pointer}`).join("\n")}
|
|
53
|
-
|
|
54
|
-
${formatFullChecks(checks)}
|
|
55
|
-
|
|
56
|
-
SOURCE:
|
|
57
|
-
${sourceLabel}
|
|
58
|
-
|
|
59
|
-
CANDIDATE REGIONS:
|
|
60
|
-
${contexts.map(formatContext).join("\n\n")}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function semScoutPrompt(
|
|
64
|
-
changeSet: SemChangeSet,
|
|
65
|
-
checks: readonly StupifyCheck[],
|
|
66
|
-
maxCandidates: number,
|
|
67
|
-
): string {
|
|
68
|
-
return `Pick changed entity/check targets worth auditing.
|
|
69
|
-
Return JSON only:
|
|
70
|
-
{ "targets": [{ "entityId": "exact entityId", "checkId": "check_id", "reason": "short scout reason" }] }
|
|
71
|
-
|
|
72
|
-
Rules:
|
|
73
|
-
- Use entityId values exactly as shown.
|
|
74
|
-
- Each target has exactly one checkId.
|
|
75
|
-
- Return at most ${maxCandidates} targets.
|
|
76
|
-
- Return { "targets": [] } if clean.
|
|
77
|
-
- Pick definitions over usage sites.
|
|
78
|
-
- Prefer high recall, but do not attach unrelated checks.
|
|
79
|
-
|
|
80
|
-
${formatCompactChecks(checks)}
|
|
81
|
-
|
|
82
|
-
SOURCE:
|
|
83
|
-
${changeSet.label}
|
|
84
|
-
|
|
85
|
-
SEM CHANGE SUMMARY:
|
|
86
|
-
${JSON.stringify(changeSet.summary, null, 2)}
|
|
87
|
-
|
|
88
|
-
SEM ENTITY CHANGES:
|
|
89
|
-
${changeSet.changes.map(formatSemChange).join("\n\n")}`;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export function findingsAuditPrompt(
|
|
93
|
-
contexts: readonly SemContext[],
|
|
94
|
-
pack: SemContextPack,
|
|
95
|
-
checks: readonly StupifyCheck[],
|
|
96
|
-
sourceLabel: string,
|
|
97
|
-
promptName: AuditPromptName,
|
|
98
|
-
): string {
|
|
99
|
-
const task =
|
|
100
|
-
promptName === "high_bar"
|
|
101
|
-
? `You are Stupify's audit model.
|
|
102
|
-
You are reviewing candidate/check targets for signs that AI-assisted coding may have replaced engineering judgment.
|
|
103
|
-
Only emit a finding if it is clearly useful to a developer.
|
|
104
|
-
A useful finding must:
|
|
105
|
-
- match the target's check exactly
|
|
106
|
-
- point to a concrete change pattern
|
|
107
|
-
- explain why the change may reflect judgment-offload
|
|
108
|
-
- avoid generic code-review commentary
|
|
109
|
-
If the target is normal engineering work, omit it.
|
|
110
|
-
If the target is merely plausible but not strong, omit it.
|
|
111
|
-
If the target does not exactly match its assigned check, omit it.`
|
|
112
|
-
: `You are Stupify's auditor.
|
|
113
|
-
Audit only the listed target/check pairs.
|
|
114
|
-
Emit only exceptions.`;
|
|
115
|
-
|
|
116
|
-
const highBarRules =
|
|
117
|
-
promptName === "high_bar"
|
|
118
|
-
? `- Prefer clean over weak.
|
|
119
|
-
- Prefer no finding over generic finding.
|
|
120
|
-
- Do not emit style feedback unless the assigned check is truly about style.
|
|
121
|
-
- Do not turn functional refactors into style mismatch findings.`
|
|
122
|
-
: "";
|
|
123
|
-
|
|
124
|
-
return `${task}
|
|
125
|
-
Return JSON only:
|
|
126
|
-
{
|
|
127
|
-
"findings": [
|
|
30
|
+
"matches": [
|
|
128
31
|
{
|
|
129
32
|
"targetId": "t001",
|
|
130
|
-
"
|
|
33
|
+
"reason": "one sentence",
|
|
131
34
|
"proof": "short pointer"
|
|
132
35
|
}
|
|
133
|
-
],
|
|
134
|
-
"uncertain": [
|
|
135
|
-
{
|
|
136
|
-
"targetId": "t002",
|
|
137
|
-
"why": "one sentence"
|
|
138
|
-
}
|
|
139
36
|
]
|
|
140
37
|
}
|
|
141
38
|
|
|
142
39
|
Rules:
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
- Emit uncertain only when the target may match, but evidence is insufficient.
|
|
147
|
-
- If a target is clean, emit nothing for it.
|
|
148
|
-
- Omitted target means clean.
|
|
149
|
-
- Do not output clean reviews.
|
|
150
|
-
- Do not explain clean targets.
|
|
151
|
-
- Do not write "no evidence" as a finding.
|
|
152
|
-
- Do not put negative statements in findings.
|
|
153
|
-
- Prefer omission over weak findings.
|
|
154
|
-
- Use only provided targetIds.
|
|
155
|
-
- Do not search for other checks.
|
|
40
|
+
- Use only targetIds from the input.
|
|
41
|
+
- Emit at most 5 matches.
|
|
42
|
+
- Prefer omission over a weak match.
|
|
156
43
|
- Do not quote source code.
|
|
157
|
-
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
44
|
+
- Do not write generic feedback.
|
|
45
|
+
- Do not emit "no evidence" or "does not apply."
|
|
46
|
+
- Proof must point to concrete changed product code that implements the pattern.
|
|
47
|
+
- Proof must not be a file header or start with "diff --git".
|
|
48
|
+
- Do not use pattern registry text, prompt text, docs, tests, or examples as proof.
|
|
49
|
+
- Do not treat pattern or prompt wording as the code being evaluated.
|
|
50
|
+
- Do not treat plain conditionals, guard clauses, skip paths, or error handling as indirection.
|
|
51
|
+
- For unnecessary_complexity, identify the exact new named abstraction in proof.
|
|
52
|
+
- If unnecessary_complexity proof would only be a file, hunk, or conditional block, omit it.
|
|
53
|
+
- If nothing clearly matches, return { "matches": [] }.
|
|
162
54
|
|
|
163
55
|
SOURCE:
|
|
164
|
-
${
|
|
165
|
-
|
|
166
|
-
CANDIDATE ENTITY DELTAS:
|
|
167
|
-
${contexts.map(formatSemContext).join("\n\n")}
|
|
168
|
-
|
|
169
|
-
PACKED FILE CONTEXT (${pack.provider}, ${pack.filePaths.length} files, ${pack.totalTokens} tokens):
|
|
170
|
-
${pack.text || "(none)"}`;
|
|
171
|
-
}
|
|
56
|
+
${input.changeSet.label}
|
|
172
57
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
${checks.map((check) => `- ${check.id}: ${check.lookFor.join("; ")}`).join("\n")}`;
|
|
176
|
-
}
|
|
58
|
+
SEARCH TARGETS:
|
|
59
|
+
${input.contexts.map((context) => formatSearchTarget(context, patternForContext(context, input.patterns), input.includeCounterReason)).join("\n\n") || "(none)"}
|
|
177
60
|
|
|
178
|
-
|
|
179
|
-
|
|
61
|
+
REPOMIX CONTEXT (${input.pack.filePaths.length} files, ${input.pack.totalTokens} tokens):
|
|
62
|
+
${input.pack.text || "(none)"}`;
|
|
180
63
|
}
|
|
181
64
|
|
|
182
|
-
function
|
|
183
|
-
return
|
|
184
|
-
|
|
185
|
-
Q: ${check.question}
|
|
65
|
+
function formatSearchPattern(check: StupifyCheck): string {
|
|
66
|
+
return `Pattern: ${check.id} (${check.name})
|
|
67
|
+
Question: ${check.searchPrompt ?? check.question}
|
|
186
68
|
Look for:
|
|
187
69
|
${check.lookFor.map((signal) => `- ${signal}`).join("\n")}
|
|
188
70
|
Ignore when:
|
|
189
71
|
${check.ignoreWhen.map((signal) => `- ${signal}`).join("\n")}
|
|
190
72
|
Match examples:
|
|
191
|
-
${(check.examples?.match ?? []).map((example) => `- ${example}`).join("\n")}
|
|
192
|
-
|
|
193
|
-
${(check.examples?.noMatch ?? []).map((example) => `- ${example}`).join("\n")}`;
|
|
73
|
+
${(check.searchExamples?.match ?? check.examples?.match ?? []).map((example) => `- ${example}`).join("\n")}
|
|
74
|
+
Non-match examples:
|
|
75
|
+
${(check.searchExamples?.nonMatch ?? check.examples?.noMatch ?? []).map((example) => `- ${example}`).join("\n")}`;
|
|
194
76
|
}
|
|
195
77
|
|
|
196
|
-
function
|
|
197
|
-
return `POINTER ${context.pointer}
|
|
198
|
-
${context.text}`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function formatSemChange(change: SemChangeSet["changes"][number]): string {
|
|
202
|
-
return `ENTITY ${change.entityId}
|
|
203
|
-
TYPE ${change.entityType}
|
|
204
|
-
CHANGE ${change.changeType}
|
|
205
|
-
PATH ${change.filePath}`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function formatSemContext(context: SemContext): string {
|
|
78
|
+
function formatSearchTarget(context: SemContext, pattern: StupifyCheck, includeCounterReason: boolean): string {
|
|
209
79
|
return `TARGET ${context.targetId}
|
|
80
|
+
ASSIGNED ${formatSearchPattern(pattern)}
|
|
81
|
+
SEM TARGET:
|
|
210
82
|
ENTITY ${context.entityId}
|
|
211
83
|
NAME ${context.entityName}
|
|
212
84
|
KIND ${context.entityKind}
|
|
213
85
|
CHANGE ${context.changeKind}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
CONTEXT:
|
|
217
|
-
${context.text}`;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function formatAuditTarget(context: SemContext, checks: readonly StupifyCheck[]): string {
|
|
221
|
-
const check = checks.find((item) => item.id === context.checkId);
|
|
222
|
-
return `- targetId=${context.targetId} checkId=${context.checkId} entityId=${context.entityId}
|
|
223
|
-
scoutReason=${context.reason}
|
|
224
|
-
${check ? formatCheck(check) : ""}`;
|
|
86
|
+
FILE ${context.filePath ?? "(unknown)"}
|
|
87
|
+
${includeCounterReason ? `COUNTER_REASON ${context.reason}` : ""}`.trim();
|
|
225
88
|
}
|
|
226
89
|
|
|
227
|
-
function
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
90
|
+
function patternForContext(context: SemContext, patterns: readonly StupifyCheck[]): StupifyCheck {
|
|
91
|
+
return patterns.find((pattern) => pattern.id === context.checkId) ?? {
|
|
92
|
+
id: context.checkId,
|
|
93
|
+
name: context.checkId,
|
|
94
|
+
question: `Does this target match ${context.checkId}?`,
|
|
95
|
+
lookFor: [],
|
|
96
|
+
ignoreWhen: [],
|
|
97
|
+
};
|
|
234
98
|
}
|