@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.
Files changed (59) hide show
  1. package/README.md +26 -31
  2. package/dist/analysis.d.ts +11 -9
  3. package/dist/analysis.js +30 -173
  4. package/dist/checks.d.ts +1 -0
  5. package/dist/checks.js +89 -2
  6. package/dist/command.js +55 -91
  7. package/dist/constants.d.ts +1 -1
  8. package/dist/constants.js +1 -1
  9. package/dist/counter-scout.js +70 -8
  10. package/dist/doctor.d.ts +4 -0
  11. package/dist/doctor.js +131 -0
  12. package/dist/git.d.ts +4 -1
  13. package/dist/git.js +34 -0
  14. package/dist/hooks.d.ts +3 -0
  15. package/dist/hooks.js +117 -0
  16. package/dist/model.d.ts +1 -15
  17. package/dist/model.js +37 -21
  18. package/dist/prompts.d.ts +8 -5
  19. package/dist/prompts.js +58 -168
  20. package/dist/render.d.ts +2 -2
  21. package/dist/render.js +70 -78
  22. package/dist/repomix-provider.d.ts +10 -2
  23. package/dist/repomix-provider.js +62 -11
  24. package/dist/search-bench.d.ts +1 -0
  25. package/dist/search-bench.js +675 -0
  26. package/dist/search-profile.d.ts +6 -0
  27. package/dist/search-profile.js +73 -0
  28. package/dist/sem-provider.d.ts +2 -2
  29. package/dist/sem-provider.js +33 -7
  30. package/dist/stupify.d.ts +2 -0
  31. package/dist/stupify.js +185 -334
  32. package/dist/types.d.ts +193 -109
  33. package/package.json +1 -1
  34. package/src/analysis.ts +48 -268
  35. package/src/checks.ts +91 -2
  36. package/src/command.ts +62 -107
  37. package/src/constants.ts +1 -1
  38. package/src/counter-scout.ts +63 -7
  39. package/src/doctor.ts +140 -0
  40. package/src/git.ts +35 -1
  41. package/src/hooks.ts +134 -0
  42. package/src/model.ts +39 -26
  43. package/src/prompts.ts +66 -202
  44. package/src/render.ts +68 -79
  45. package/src/repomix-provider.ts +66 -10
  46. package/src/search-bench.ts +783 -0
  47. package/src/search-profile.ts +89 -0
  48. package/src/sem-provider.ts +36 -9
  49. package/src/stupify.ts +215 -527
  50. package/src/types.ts +195 -119
  51. package/dist/batcher.d.ts +0 -3
  52. package/dist/batcher.js +0 -142
  53. package/dist/candidate-context.d.ts +0 -2
  54. package/dist/candidate-context.js +0 -40
  55. package/dist/experiment.d.ts +0 -1
  56. package/dist/experiment.js +0 -225
  57. package/src/batcher.ts +0 -198
  58. package/src/candidate-context.ts +0 -43
  59. package/src/experiment.ts +0 -317
package/dist/model.d.ts CHANGED
@@ -1,24 +1,10 @@
1
1
  import type { ModelId } from "./types.ts";
2
- export type ModelProfile = "scout" | "audit";
2
+ export type ModelProfile = "scout";
3
3
  export type LocalModel = Readonly<{
4
4
  id: ModelId;
5
5
  name: string;
6
6
  baseUrl: string;
7
7
  profile: ModelProfile;
8
8
  }>;
9
- export declare function loadLocalModels(modelId: ModelId): Promise<{
10
- scoutModel: Readonly<{
11
- id: ModelId;
12
- name: string;
13
- baseUrl: string;
14
- profile: ModelProfile;
15
- }>;
16
- auditModel: Readonly<{
17
- id: ModelId;
18
- name: string;
19
- baseUrl: string;
20
- profile: ModelProfile;
21
- }>;
22
- }>;
23
9
  export declare function firstRunModelBootstrap(modelId: ModelId): Promise<string>;
24
10
  export declare function loadLocalModel(modelPath: string, modelId: ModelId, profile?: ModelProfile): Promise<LocalModel>;
package/dist/model.js CHANGED
@@ -9,12 +9,6 @@ import { promisify } from "node:util";
9
9
  import { MODEL_REGISTRY } from "./constants.js";
10
10
  const execFileAsync = promisify(execFile);
11
11
  const LLAMA_SERVER_HOST = "127.0.0.1";
12
- export async function loadLocalModels(modelId) {
13
- const modelPath = await firstRunModelBootstrap(modelId);
14
- const scoutModel = await loadLocalModel(modelPath, modelId, "scout");
15
- const auditModel = await loadLocalModel(modelPath, modelId, "audit");
16
- return { scoutModel, auditModel };
17
- }
18
12
  export async function firstRunModelBootstrap(modelId) {
19
13
  const selectedModel = MODEL_REGISTRY[modelId];
20
14
  const modelDir = path.join(cacheDir(), "models");
@@ -62,16 +56,6 @@ export async function loadLocalModel(modelPath, modelId, profile = "scout") {
62
56
  };
63
57
  }
64
58
  function modelRuntime(profile) {
65
- if (profile === "audit") {
66
- const baseUrl = process.env.STUPIFY_AUDIT_LLAMA_SERVER_URL ?? "http://127.0.0.1:8092";
67
- return {
68
- profile,
69
- baseUrl,
70
- port: new URL(baseUrl).port || "8092",
71
- reasoning: "on",
72
- reasoningBudget: 4_096,
73
- };
74
- }
75
59
  const baseUrl = process.env.STUPIFY_SCOUT_LLAMA_SERVER_URL ??
76
60
  process.env.STUPIFY_LLAMA_SERVER_URL ??
77
61
  "http://127.0.0.1:8091";
@@ -79,7 +63,15 @@ function modelRuntime(profile) {
79
63
  profile,
80
64
  baseUrl,
81
65
  port: new URL(baseUrl).port || "8091",
66
+ contextSize: envInteger("STUPIFY_LLAMA_CONTEXT") ?? 65_536,
82
67
  reasoning: "off",
68
+ gpuLayers: envInteger("STUPIFY_LLAMA_GPU_LAYERS") ?? 999,
69
+ batchSize: envInteger("STUPIFY_LLAMA_BATCH") ?? 2_048,
70
+ ubatchSize: envInteger("STUPIFY_LLAMA_UBATCH") ?? 512,
71
+ parallel: envInteger("STUPIFY_LLAMA_PARALLEL") ?? 2,
72
+ threads: envInteger("STUPIFY_LLAMA_THREADS"),
73
+ threadsBatch: envInteger("STUPIFY_LLAMA_THREADS_BATCH"),
74
+ flashAttention: envBoolean("STUPIFY_LLAMA_FLASH_ATTN"),
83
75
  };
84
76
  }
85
77
  async function runningServerModel(baseUrl) {
@@ -127,11 +119,25 @@ async function startLlamaServer(modelPath, modelId, modelName, runtime) {
127
119
  "--port",
128
120
  runtime.port,
129
121
  "-c",
130
- "65536",
122
+ String(runtime.contextSize),
131
123
  "--reasoning",
132
124
  runtime.reasoning,
133
125
  "--no-warmup",
134
126
  ];
127
+ if (runtime.gpuLayers !== undefined)
128
+ args.push("-ngl", String(runtime.gpuLayers));
129
+ if (runtime.batchSize !== undefined)
130
+ args.push("-b", String(runtime.batchSize));
131
+ if (runtime.ubatchSize !== undefined)
132
+ args.push("-ub", String(runtime.ubatchSize));
133
+ if (runtime.parallel !== undefined)
134
+ args.push("-np", String(runtime.parallel));
135
+ if (runtime.threads !== undefined)
136
+ args.push("-t", String(runtime.threads));
137
+ if (runtime.threadsBatch !== undefined)
138
+ args.push("-tb", String(runtime.threadsBatch));
139
+ if (runtime.flashAttention !== undefined)
140
+ args.push("-fa", runtime.flashAttention ? "on" : "off");
135
141
  if (runtime.reasoningBudget !== undefined) {
136
142
  args.push("--reasoning-budget", String(runtime.reasoningBudget));
137
143
  }
@@ -180,10 +186,20 @@ async function managedServerPid(runtime) {
180
186
  }
181
187
  }
182
188
  function pidPath(runtime) {
183
- const filename = runtime.profile === "scout"
184
- ? "llama-server.pid"
185
- : `llama-server-${runtime.profile}.pid`;
186
- return path.join(cacheDir(), filename);
189
+ return path.join(cacheDir(), "llama-server.pid");
190
+ }
191
+ function envInteger(name, fallback) {
192
+ const raw = process.env[name];
193
+ if (raw === undefined || raw === "")
194
+ return fallback;
195
+ const value = Number(raw);
196
+ return Number.isInteger(value) && value > 0 ? value : fallback;
197
+ }
198
+ function envBoolean(name) {
199
+ const raw = process.env[name];
200
+ if (raw === undefined || raw === "")
201
+ return undefined;
202
+ return /^(1|true|yes|on)$/i.test(raw);
187
203
  }
188
204
  async function waitForServer(baseUrl, modelId) {
189
205
  const deadline = Date.now() + 120_000;
package/dist/prompts.d.ts CHANGED
@@ -1,5 +1,8 @@
1
- import type { AuditPromptName, CandidateContext, DiffBatch, SemChangeSet, SemContext, SemContextPack, StupifyCheck } from "./types.ts";
2
- export declare function scoutPrompt(batch: DiffBatch, checks: readonly StupifyCheck[], sourceLabel: string): string;
3
- export declare function auditPrompt(contexts: readonly CandidateContext[], checks: readonly StupifyCheck[], sourceLabel: string): string;
4
- export declare function semScoutPrompt(changeSet: SemChangeSet, checks: readonly StupifyCheck[], maxCandidates: number): string;
5
- export declare function findingsAuditPrompt(contexts: readonly SemContext[], pack: SemContextPack, checks: readonly StupifyCheck[], sourceLabel: string, promptName: AuditPromptName): string;
1
+ import type { SemChangeSet, SemContext, SemContextPack, StupifyCheck } from "./types.ts";
2
+ export declare function searchPrompt(input: Readonly<{
3
+ changeSet: SemChangeSet;
4
+ contexts: readonly SemContext[];
5
+ pack: SemContextPack;
6
+ patterns: readonly StupifyCheck[];
7
+ includeCounterReason: boolean;
8
+ }>): string;
package/dist/prompts.js CHANGED
@@ -1,197 +1,87 @@
1
- export function scoutPrompt(batch, checks, sourceLabel) {
2
- return `Pick diff hunks that match enabled checks.
3
- Return JSON only:
4
- { "candidates": ["exact POINTER"] }
5
-
6
- Rules:
7
- - Use POINTER values exactly as shown.
8
- - Return at most 3 candidates.
9
- - Return { "candidates": [] } if clean.
10
- - Pick definitions over usage sites.
11
-
12
- ${formatCompactChecks(checks)}
13
-
14
- SOURCE:
15
- ${sourceLabel}
16
-
17
- DIFF BATCH ${batch.id}:
18
- ${batch.text}`;
19
- }
20
- export function auditPrompt(contexts, checks, sourceLabel) {
21
- return `Audit candidate diff regions against enabled checks.
22
- Return JSON only:
23
- {
24
- "findings": [{ "checkId": "check_id", "why": "one sentence", "proof": "exact POINTER" }],
25
- "summary": "one short sentence"
26
- }
27
-
28
- Rules:
29
- - Use only checks listed below.
30
- - checkId must be a check ID, never a POINTER.
31
- - proof must be one exact POINTER from candidate regions.
32
- - why describes the suspicious structure, not an identifier.
33
- - Do not describe an issue in summary unless it is also in findings.
34
- - If no findings, return { "findings": [], "summary": "No clear judgment-offload signal found." }.
35
-
36
- Allowed proof pointers:
37
- ${contexts.map((context) => `- ${context.pointer}`).join("\n")}
38
-
39
- ${formatFullChecks(checks)}
40
-
41
- SOURCE:
42
- ${sourceLabel}
43
-
44
- CANDIDATE REGIONS:
45
- ${contexts.map(formatContext).join("\n\n")}`;
46
- }
47
- export function semScoutPrompt(changeSet, checks, maxCandidates) {
48
- return `Pick changed entity/check targets worth auditing.
49
- Return JSON only:
50
- { "targets": [{ "entityId": "exact entityId", "checkId": "check_id", "reason": "short scout reason" }] }
51
-
52
- Rules:
53
- - Use entityId values exactly as shown.
54
- - Each target has exactly one checkId.
55
- - Return at most ${maxCandidates} targets.
56
- - Return { "targets": [] } if clean.
57
- - Pick definitions over usage sites.
58
- - Prefer high recall, but do not attach unrelated checks.
59
-
60
- ${formatCompactChecks(checks)}
61
-
62
- SOURCE:
63
- ${changeSet.label}
64
-
65
- SEM CHANGE SUMMARY:
66
- ${JSON.stringify(changeSet.summary, null, 2)}
67
-
68
- SEM ENTITY CHANGES:
69
- ${changeSet.changes.map(formatSemChange).join("\n\n")}`;
70
- }
71
- export function findingsAuditPrompt(contexts, pack, checks, sourceLabel, promptName) {
72
- const task = promptName === "high_bar"
73
- ? `You are Stupify's audit model.
74
- You are reviewing candidate/check targets for signs that AI-assisted coding may have replaced engineering judgment.
75
- Only emit a finding if it is clearly useful to a developer.
76
- A useful finding must:
77
- - match the target's check exactly
78
- - point to a concrete change pattern
79
- - explain why the change may reflect judgment-offload
80
- - avoid generic code-review commentary
81
- If the target is normal engineering work, omit it.
82
- If the target is merely plausible but not strong, omit it.
83
- If the target does not exactly match its assigned check, omit it.`
84
- : `You are Stupify's auditor.
85
- Audit only the listed target/check pairs.
86
- Emit only exceptions.`;
87
- const highBarRules = promptName === "high_bar"
88
- ? `- Prefer clean over weak.
89
- - Prefer no finding over generic finding.
90
- - Do not emit style feedback unless the assigned check is truly about style.
91
- - Do not turn functional refactors into style mismatch findings.`
92
- : "";
93
- return `${task}
1
+ export function searchPrompt(input) {
2
+ return `You are Stupify's local search model.
3
+ Stupify checks whether AI-assisted coding may be replacing developer judgment.
4
+ You will receive:
5
+ 1. Semantic changed entities selected by a fast local counter.
6
+ 2. Compressed local file context from Repomix.
7
+ 3. A list of search targets. Each target has exactly one assigned pattern.
8
+
9
+ Your job:
10
+ Evaluate each target only against its assigned pattern.
11
+ False positives are expensive.
12
+ Only emit a match if the assigned pattern clearly applies to that exact target.
13
+ Do not perform general code review.
14
+ Do not suggest improvements.
15
+ Do not choose a pattern.
16
+ Do not apply other patterns.
17
+ Do not report issues for unlisted targets.
18
+ Do not emit clean results.
19
+ Omitted target = clean.
94
20
  Return JSON only:
95
21
  {
96
- "findings": [
22
+ "matches": [
97
23
  {
98
24
  "targetId": "t001",
99
- "why": "one sentence",
25
+ "reason": "one sentence",
100
26
  "proof": "short pointer"
101
27
  }
102
- ],
103
- "uncertain": [
104
- {
105
- "targetId": "t002",
106
- "why": "one sentence"
107
- }
108
28
  ]
109
29
  }
110
30
 
111
31
  Rules:
112
- - Inspect every target.
113
- - Each target has exactly one check.
114
- - Emit a finding only when the target clearly matches its check.
115
- - Emit uncertain only when the target may match, but evidence is insufficient.
116
- - If a target is clean, emit nothing for it.
117
- - Omitted target means clean.
118
- - Do not output clean reviews.
119
- - Do not explain clean targets.
120
- - Do not write "no evidence" as a finding.
121
- - Do not put negative statements in findings.
122
- - Prefer omission over weak findings.
123
- - Use only provided targetIds.
124
- - Do not search for other checks.
32
+ - Use only targetIds from the input.
33
+ - Emit at most 5 matches.
34
+ - Prefer omission over a weak match.
125
35
  - Do not quote source code.
126
- - Use packed file context only as supporting evidence for these candidate entities.
127
- ${highBarRules}
128
-
129
- Targets:
130
- ${contexts.map((context) => formatAuditTarget(context, checks)).join("\n\n")}
36
+ - Do not write generic feedback.
37
+ - Do not emit "no evidence" or "does not apply."
38
+ - Proof must point to concrete changed product code that implements the pattern.
39
+ - Proof must not be a file header or start with "diff --git".
40
+ - Do not use pattern registry text, prompt text, docs, tests, or examples as proof.
41
+ - Do not treat pattern or prompt wording as the code being evaluated.
42
+ - Do not treat plain conditionals, guard clauses, skip paths, or error handling as indirection.
43
+ - For unnecessary_complexity, identify the exact new named abstraction in proof.
44
+ - If unnecessary_complexity proof would only be a file, hunk, or conditional block, omit it.
45
+ - If nothing clearly matches, return { "matches": [] }.
131
46
 
132
47
  SOURCE:
133
- ${sourceLabel}
48
+ ${input.changeSet.label}
134
49
 
135
- CANDIDATE ENTITY DELTAS:
136
- ${contexts.map(formatSemContext).join("\n\n")}
50
+ SEARCH TARGETS:
51
+ ${input.contexts.map((context) => formatSearchTarget(context, patternForContext(context, input.patterns), input.includeCounterReason)).join("\n\n") || "(none)"}
137
52
 
138
- PACKED FILE CONTEXT (${pack.provider}, ${pack.filePaths.length} files, ${pack.totalTokens} tokens):
139
- ${pack.text || "(none)"}`;
140
- }
141
- function formatCompactChecks(checks) {
142
- return `Checks:
143
- ${checks.map((check) => `- ${check.id}: ${check.lookFor.join("; ")}`).join("\n")}`;
53
+ REPOMIX CONTEXT (${input.pack.filePaths.length} files, ${input.pack.totalTokens} tokens):
54
+ ${input.pack.text || "(none)"}`;
144
55
  }
145
- function formatFullChecks(checks) {
146
- return checks.map(formatCheck).join("\n\n");
147
- }
148
- function formatCheck(check) {
149
- return `# ${check.name}
150
- ID: ${check.id}
151
- Q: ${check.question}
56
+ function formatSearchPattern(check) {
57
+ return `Pattern: ${check.id} (${check.name})
58
+ Question: ${check.searchPrompt ?? check.question}
152
59
  Look for:
153
60
  ${check.lookFor.map((signal) => `- ${signal}`).join("\n")}
154
61
  Ignore when:
155
62
  ${check.ignoreWhen.map((signal) => `- ${signal}`).join("\n")}
156
63
  Match examples:
157
- ${(check.examples?.match ?? []).map((example) => `- ${example}`).join("\n")}
158
- No-match examples:
159
- ${(check.examples?.noMatch ?? []).map((example) => `- ${example}`).join("\n")}`;
160
- }
161
- function formatContext(context) {
162
- return `POINTER ${context.pointer}
163
- ${context.text}`;
64
+ ${(check.searchExamples?.match ?? check.examples?.match ?? []).map((example) => `- ${example}`).join("\n")}
65
+ Non-match examples:
66
+ ${(check.searchExamples?.nonMatch ?? check.examples?.noMatch ?? []).map((example) => `- ${example}`).join("\n")}`;
164
67
  }
165
- function formatSemChange(change) {
166
- return `ENTITY ${change.entityId}
167
- TYPE ${change.entityType}
168
- CHANGE ${change.changeType}
169
- PATH ${change.filePath}`;
170
- }
171
- function formatSemContext(context) {
68
+ function formatSearchTarget(context, pattern, includeCounterReason) {
172
69
  return `TARGET ${context.targetId}
70
+ ASSIGNED ${formatSearchPattern(pattern)}
71
+ SEM TARGET:
173
72
  ENTITY ${context.entityId}
174
73
  NAME ${context.entityName}
175
74
  KIND ${context.entityKind}
176
75
  CHANGE ${context.changeKind}
177
- CHECK ${context.checkId}
178
- SCOUT_REASON ${context.reason}
179
- CONTEXT:
180
- ${context.text}`;
181
- }
182
- function formatAuditTarget(context, checks) {
183
- const check = checks.find((item) => item.id === context.checkId);
184
- return `- targetId=${context.targetId} checkId=${context.checkId} entityId=${context.entityId}
185
- scoutReason=${context.reason}
186
- ${check ? formatCheck(check) : ""}`;
76
+ FILE ${context.filePath ?? "(unknown)"}
77
+ ${includeCounterReason ? `COUNTER_REASON ${context.reason}` : ""}`.trim();
187
78
  }
188
- function shortenCode(value) {
189
- if (!value)
190
- return "(none)";
191
- const lines = value.split(/\r?\n/);
192
- const limit = 80;
193
- if (lines.length <= limit)
194
- return value;
195
- return `${lines.slice(0, limit).join("\n")}
196
- [stupify: sem entity content shortened after ${limit} lines]`;
79
+ function patternForContext(context, patterns) {
80
+ return patterns.find((pattern) => pattern.id === context.checkId) ?? {
81
+ id: context.checkId,
82
+ name: context.checkId,
83
+ question: `Does this target match ${context.checkId}?`,
84
+ lookFor: [],
85
+ ignoreWhen: [],
86
+ };
197
87
  }
package/dist/render.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import type { AnalysisReport, AnalyzeCommand } from "./types.ts";
2
- export declare function renderReport(report: AnalysisReport, command: AnalyzeCommand): string;
1
+ import type { SearchCommand, SearchRunJson } from "./types.ts";
2
+ export declare function renderSearchRun(run: SearchRunJson, command: SearchCommand): string;
3
3
  export declare function helpText(): string;
package/dist/render.js CHANGED
@@ -1,40 +1,37 @@
1
1
  import { VERSION } from "./constants.js";
2
- export function renderReport(report, command) {
3
- if (command.json) {
4
- return JSON.stringify({
5
- schemaVersion: "0.4",
6
- model: { id: report.run.modelId },
7
- checks: report.run.checkIds,
8
- run: report.run,
9
- findings: report.result.findings,
10
- summary: report.result.summary,
11
- }, null, 2);
2
+ export function renderSearchRun(run, command) {
3
+ if (command.json)
4
+ return JSON.stringify(run, null, 2);
5
+ if (run.stats.skipped && run.stats.skipReason === "input_too_large") {
6
+ return `🧙 stupify 🪄
7
+ Search input is too large for precise local search.
8
+ Size:
9
+ ~${run.stats.inputTokens ?? "unknown"} tokens
10
+ Limit:
11
+ ${run.stats.inputTokenCap ?? "unknown"} tokens
12
+ Stupify skipped the search rather than review truncated context.
13
+ Nothing was blocked.
14
+ Try:
15
+ stupify ${sourceHint(command)} --max-search-input-tokens ${Math.max((run.stats.inputTokens ?? 12_000) + 1, (run.stats.inputTokenCap ?? 12_000) * 2)}`;
12
16
  }
13
- if (report.run.engine === "sem") {
14
- return `Search:
15
- ${report.run.entitiesScanned} entities scanned
16
- ${report.run.candidateCount} candidate entities found
17
- Audit:
18
- ${report.run.auditedCandidateCount} candidates inspected
19
- ${report.result.findings.length} findings
20
- ${renderAuditStats(report)}
21
- ${renderWarnings(report)}
22
- Findings:
23
- ${renderFindings(report)}
24
- Timing:
25
- total_ms=${report.run.timingsMs.total} entity_diff_ms=${report.run.timingsMs.diff} model_ms=${report.run.timingsMs.modelLoad} scout_ms=${report.run.timingsMs.search} context_audit_ms=${report.run.timingsMs.audit}`;
17
+ if (run.stats.skipped && run.stats.skipReason === "no_candidates") {
18
+ return `🧙 stupify 🪄
19
+ Search complete.
20
+ Patterns: ${run.patterns.join(", ")}
21
+ No search targets found.`;
26
22
  }
27
- return `Search:
28
- ${report.run.batchesScanned} batches scanned
29
- ${report.run.candidateCount} candidate regions found
30
- Audit:
31
- ${report.run.auditedCandidateCount} candidates inspected
32
- ${report.result.findings.length} findings
33
- ${renderWarnings(report)}
34
- Findings:
35
- ${renderFindings(report)}
36
- Timing:
37
- total_ms=${report.run.timingsMs.total} diff_ms=${report.run.timingsMs.diff} model_ms=${report.run.timingsMs.modelLoad} search_ms=${report.run.timingsMs.search} audit_ms=${report.run.timingsMs.audit}`;
23
+ if (run.matches.length === 0) {
24
+ return `🧙 stupify 🪄
25
+ Search complete.
26
+ Patterns: ${run.patterns.join(", ")}
27
+ No judgment-offload signals found.`;
28
+ }
29
+ return `🧙 stupify 🪄
30
+ Possible judgment-offload detected:
31
+ ${run.matches.map((match, index) => `${index + 1}. ${match.patternId}
32
+ ${match.reason}
33
+ Proof: ${match.proof}`).join("\n")}
34
+ Search mode is warn-only.`;
38
35
  }
39
36
  export function helpText() {
40
37
  return `Stupify ${VERSION}
@@ -44,58 +41,53 @@ Usage:
44
41
  stupify --since "2 weeks ago"
45
42
  stupify --commit <commit>
46
43
  stupify --commits <count>
47
- stupify experiment <config.json>
44
+ stupify --staged
45
+ stupify --mode search --staged
46
+ stupify hook install|uninstall|status
47
+ stupify doctor
48
+ stupify bench search experiments/search-bench.json
48
49
  git diff HEAD~1..HEAD | stupify --stdin
49
50
 
50
51
  Options:
51
- --since <date> Analyze the net diff from the first commit before this git date to HEAD.
52
- --commit <commit> Analyze one commit as a net diff.
53
- --commits <count> Analyze the net diff across the last N non-merge commits.
54
- --stdin Read a git diff from stdin.
55
- --engine <engine> raw-diff or sem. Default: raw-diff.
56
- --scout <mode> llm or counter for --engine sem. Default: counter.
57
- --audit-context <mode>
58
- none or repomix for --engine sem. Default: repomix.
59
- --audit-prompt <name> strict or high_bar for --engine sem. Default: high_bar.
60
- --debug-sem Print sem commands and stderr.
61
- --debug-targets Include audited sem target details in JSON output.
62
- --max-candidates <n> Max semantic candidates for --engine sem. Default: 10.
63
- --audit-batch-size <n>
64
- Semantic candidates per audit model call. Default: 25.
65
- --max-audit-input-tokens <n>
66
- Max findings-audit input tokens before splitting. Default: 20000.
67
- --audit-concurrency <n>
68
- Parallel findings-audit model calls. Default: 2.
69
- --checks <ids> Comma-separated check ids.
70
- --model <id> gemma-4-e2b, gemma-4-e4b, gemma-4-26b-a4b, qwen3-4b-magicquant, qwen2.5-coder-1.5b, qwen2.5-coder-7b, or qwen2.5-coder-32b.
71
- --json Print JSON only.
52
+ --staged Search staged changes.
53
+ --mode <mode> search. Search is the only analysis mode.
54
+ --since <date> Search the net diff from the first commit before this git date to HEAD.
55
+ --commit <commit> Search one commit as a net diff.
56
+ --commits <count> Search the net diff across the last N non-merge commits.
57
+ --stdin Read a git diff from stdin.
58
+ --debug-sem Print sem commands and stderr.
59
+ --max-candidates <n> Max semantic search targets. Default: 10.
60
+ --max-search-input-tokens <n>
61
+ Max search input tokens before skipping. Default: 12000.
62
+ --checks <ids> Comma-separated pattern ids.
63
+ --model <id> gemma-4-e2b, gemma-4-e4b, gemma-4-26b-a4b, qwen3-4b-magicquant, qwen2.5-coder-1.5b, qwen2.5-coder-7b, or qwen2.5-coder-32b.
64
+ --search-profile <path>
65
+ Dev/bench-only search profile override.
66
+ --include-counter-reason-in-prompt
67
+ Debug/bench-only: include counter reason in the model prompt.
68
+ --json Print JSON only.
69
+
70
+ Diagnostics:
71
+ stupify doctor Check local setup, hook status, and privacy boundary.
72
72
 
73
73
  Default:
74
74
  stupify is equivalent to stupify --since "2 weeks ago".
75
75
 
76
+ Pipeline:
77
+ sem diff -> counter scout -> Repomix context -> local search model.
78
+
76
79
  Not included:
77
- Baselines, sharing, hosted server calls, Ollama, GitHub, dashboards, or repo-wide scanning.
78
- `;
79
- }
80
- function renderFindings(report) {
81
- if (report.result.findings.length === 0)
82
- return " None.";
83
- return report.result.findings
84
- .map((finding) => `- ${finding.checkId}
85
- ${finding.why}
86
- Proof: ${finding.proof}`)
87
- .join("\n");
88
- }
89
- function renderWarnings(report) {
90
- if (report.run.warnings.length === 0)
91
- return "";
92
- return `Warnings:
93
- ${report.run.warnings.map((warning) => ` ${warning}`).join("\n")}
80
+ Findings audit, validators, judges, baselines, sharing, hosted server calls, GitHub, dashboards, or repo-wide crawling.
94
81
  `;
95
82
  }
96
- function renderAuditStats(report) {
97
- const stats = report.run.auditStats;
98
- if (!stats)
99
- return "";
100
- return ` ${stats.totalTargets} targets reviewed: ${stats.finding} finding, ${stats.uncertain} uncertain, ${stats.clean} clean, ${stats.invalid} invalid`;
83
+ function sourceHint(command) {
84
+ if (command.kind === "staged")
85
+ return "--staged";
86
+ if (command.kind === "since")
87
+ return `--since "${command.since}"`;
88
+ if (command.kind === "commit")
89
+ return `--commit ${command.commit}`;
90
+ if (command.kind === "commits")
91
+ return `--commits ${command.count}`;
92
+ return "--stdin";
101
93
  }
@@ -1,4 +1,12 @@
1
- import type { SemCandidate, SemChange, SemContext, SemContextPack } from "./types.ts";
1
+ import type { RepomixSearchConfig, SemCandidate, SemChange, SemContext, SemContextPack } from "./types.ts";
2
2
  export declare function emptyContextPack(): SemContextPack;
3
- export declare function repomixContextPack(cwd: string, contexts: readonly SemContext[], changes: readonly SemChange[]): Promise<SemContextPack>;
3
+ export declare function repomixContextPack(cwd: string, contexts: readonly SemContext[], changes: readonly SemChange[], config?: Readonly<{
4
+ compress: boolean;
5
+ showLineNumbers: boolean;
6
+ removeEmptyLines: boolean;
7
+ maxFileSizeBytes: number;
8
+ maxTotalSizeBytes: number;
9
+ ignorePatterns: readonly string[];
10
+ }>): Promise<SemContextPack>;
4
11
  export declare function entityContextsFromChanges(candidates: readonly SemCandidate[], changes: readonly SemChange[]): readonly SemContext[];
12
+ export declare function repomixSearchConfig(): RepomixSearchConfig;