@stupify/cli 0.0.15 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.review/CORPUS.md +73 -0
- package/.review/REVIEW-PROMPT.md +52 -0
- package/.review/RUBRIC.md +46 -0
- package/LICENSE +1 -1
- package/README.md +41 -39
- package/package.json +24 -25
- package/src/cli.ts +358 -0
- package/src/review-sweep.ts +492 -0
- package/dist/analysis.d.ts +0 -16
- package/dist/analysis.js +0 -165
- package/dist/cache.d.ts +0 -2
- package/dist/cache.js +0 -57
- package/dist/checks.d.ts +0 -4
- package/dist/checks.js +0 -228
- package/dist/command.d.ts +0 -2
- package/dist/command.js +0 -147
- package/dist/constants.d.ts +0 -4
- package/dist/constants.js +0 -53
- package/dist/counter-scout.d.ts +0 -21
- package/dist/counter-scout.js +0 -167
- package/dist/diff.d.ts +0 -1
- package/dist/diff.js +0 -10
- package/dist/doctor.d.ts +0 -4
- package/dist/doctor.js +0 -131
- package/dist/git.d.ts +0 -12
- package/dist/git.js +0 -298
- package/dist/hooks.d.ts +0 -3
- package/dist/hooks.js +0 -117
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/model.d.ts +0 -11
- package/dist/model.js +0 -296
- package/dist/prompts.d.ts +0 -8
- package/dist/prompts.js +0 -89
- package/dist/render.d.ts +0 -3
- package/dist/render.js +0 -151
- package/dist/repomix-provider.d.ts +0 -12
- package/dist/repomix-provider.js +0 -196
- package/dist/search-bench.d.ts +0 -1
- package/dist/search-bench.js +0 -677
- package/dist/search-profile.d.ts +0 -6
- package/dist/search-profile.js +0 -73
- package/dist/sem-provider.d.ts +0 -2
- package/dist/sem-provider.js +0 -252
- package/dist/stupify.d.ts +0 -38
- package/dist/stupify.js +0 -474
- package/dist/trace.d.ts +0 -31
- package/dist/trace.js +0 -86
- package/dist/types.d.ts +0 -328
- package/dist/types.js +0 -6
- package/dist/ui.d.ts +0 -34
- package/dist/ui.js +0 -143
- package/src/analysis.ts +0 -220
- package/src/cache.ts +0 -63
- package/src/checks.ts +0 -231
- package/src/command.ts +0 -173
- package/src/constants.ts +0 -56
- package/src/counter-scout.ts +0 -195
- package/src/diff.ts +0 -9
- package/src/doctor.ts +0 -140
- package/src/git.ts +0 -306
- package/src/hooks.ts +0 -134
- package/src/index.ts +0 -1
- package/src/model.ts +0 -367
- package/src/prompts.ts +0 -100
- package/src/render.ts +0 -154
- package/src/repomix-provider.ts +0 -219
- package/src/search-bench.ts +0 -783
- package/src/search-profile.ts +0 -89
- package/src/sem-provider.ts +0 -297
- package/src/stupify.ts +0 -571
- package/src/trace.ts +0 -126
- package/src/types.ts +0 -348
- package/src/ui.ts +0 -187
package/src/repomix-provider.ts
DELETED
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
|
|
2
|
-
import { tmpdir } from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { pack, setLogLevel } from "repomix";
|
|
5
|
-
import type { RepomixSearchConfig, SemCandidate, SemChange, SemContext, SemContextPack } from "./types.ts";
|
|
6
|
-
|
|
7
|
-
const MAX_PACK_FILE_SIZE_BYTES = 48 * 1024;
|
|
8
|
-
const MAX_PACK_TOTAL_SIZE_BYTES = 128 * 1024;
|
|
9
|
-
|
|
10
|
-
export function emptyContextPack(): SemContextPack {
|
|
11
|
-
const config = repomixSearchConfig();
|
|
12
|
-
return {
|
|
13
|
-
provider: "repomix",
|
|
14
|
-
filePaths: [],
|
|
15
|
-
totalCharacters: 0,
|
|
16
|
-
totalTokens: 0,
|
|
17
|
-
text: "",
|
|
18
|
-
config,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function repomixContextPack(
|
|
23
|
-
cwd: string,
|
|
24
|
-
contexts: readonly SemContext[],
|
|
25
|
-
changes: readonly SemChange[],
|
|
26
|
-
config = repomixSearchConfig(),
|
|
27
|
-
): Promise<SemContextPack> {
|
|
28
|
-
const filePaths = await candidateFilePaths(cwd, contexts, changes, config);
|
|
29
|
-
if (filePaths.length === 0) {
|
|
30
|
-
return {
|
|
31
|
-
...emptyContextPack(),
|
|
32
|
-
config,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
setLogLevel(-1);
|
|
37
|
-
const tempDir = await mkdtemp(path.join(tmpdir(), "stupify-repomix-"));
|
|
38
|
-
const outputPath = path.join(tempDir, "context.xml");
|
|
39
|
-
try {
|
|
40
|
-
const result = await pack(
|
|
41
|
-
[cwd],
|
|
42
|
-
{
|
|
43
|
-
cwd,
|
|
44
|
-
input: { maxFileSize: config.maxFileSizeBytes },
|
|
45
|
-
output: {
|
|
46
|
-
filePath: outputPath,
|
|
47
|
-
style: "xml",
|
|
48
|
-
parsableStyle: false,
|
|
49
|
-
fileSummary: false,
|
|
50
|
-
directoryStructure: false,
|
|
51
|
-
files: true,
|
|
52
|
-
removeComments: false,
|
|
53
|
-
removeEmptyLines: config.removeEmptyLines,
|
|
54
|
-
compress: config.compress,
|
|
55
|
-
topFilesLength: 0,
|
|
56
|
-
showLineNumbers: config.showLineNumbers,
|
|
57
|
-
truncateBase64: true,
|
|
58
|
-
copyToClipboard: false,
|
|
59
|
-
includeFullDirectoryStructure: false,
|
|
60
|
-
tokenCountTree: false,
|
|
61
|
-
git: {
|
|
62
|
-
sortByChanges: false,
|
|
63
|
-
sortByChangesMaxCommits: 1,
|
|
64
|
-
includeDiffs: false,
|
|
65
|
-
includeLogs: false,
|
|
66
|
-
includeLogsCount: 1,
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
include: [],
|
|
70
|
-
ignore: {
|
|
71
|
-
useGitignore: true,
|
|
72
|
-
useDotIgnore: true,
|
|
73
|
-
useDefaultPatterns: true,
|
|
74
|
-
customPatterns: [...config.ignorePatterns],
|
|
75
|
-
},
|
|
76
|
-
security: { enableSecurityCheck: false },
|
|
77
|
-
tokenCount: { encoding: "o200k_base" },
|
|
78
|
-
} satisfies Parameters<typeof pack>[1],
|
|
79
|
-
() => undefined,
|
|
80
|
-
{},
|
|
81
|
-
[...filePaths],
|
|
82
|
-
);
|
|
83
|
-
return {
|
|
84
|
-
provider: "repomix",
|
|
85
|
-
filePaths,
|
|
86
|
-
totalCharacters: result.totalCharacters,
|
|
87
|
-
totalTokens: result.totalTokens,
|
|
88
|
-
text: await readFile(outputPath, "utf8"),
|
|
89
|
-
config,
|
|
90
|
-
};
|
|
91
|
-
} finally {
|
|
92
|
-
await rm(tempDir, { recursive: true, force: true });
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export function entityContextsFromChanges(
|
|
97
|
-
candidates: readonly SemCandidate[],
|
|
98
|
-
changes: readonly SemChange[],
|
|
99
|
-
): readonly SemContext[] {
|
|
100
|
-
const byEntityId = new Map(changes.map((change) => [change.entityId, change]));
|
|
101
|
-
return candidates.flatMap((candidate): readonly SemContext[] => {
|
|
102
|
-
const change = byEntityId.get(candidate.entityId);
|
|
103
|
-
if (!change) return [];
|
|
104
|
-
return [{
|
|
105
|
-
targetId: candidate.targetId,
|
|
106
|
-
entityId: change.entityId,
|
|
107
|
-
entityName: change.entityName,
|
|
108
|
-
entityKind: change.entityType,
|
|
109
|
-
changeKind: change.changeType,
|
|
110
|
-
checkId: candidate.checkId,
|
|
111
|
-
reason: candidate.reason,
|
|
112
|
-
filePath: change.filePath,
|
|
113
|
-
text: JSON.stringify({
|
|
114
|
-
source: "sem diff",
|
|
115
|
-
file: change.filePath,
|
|
116
|
-
type: change.entityType,
|
|
117
|
-
name: change.entityName,
|
|
118
|
-
change: change.changeType,
|
|
119
|
-
before: shortenCode(change.beforeContent),
|
|
120
|
-
after: shortenCode(change.afterContent),
|
|
121
|
-
}, null, 2),
|
|
122
|
-
}];
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
async function candidateFilePaths(
|
|
127
|
-
cwd: string,
|
|
128
|
-
contexts: readonly SemContext[],
|
|
129
|
-
changes: readonly SemChange[],
|
|
130
|
-
config: RepomixSearchConfig,
|
|
131
|
-
): Promise<readonly string[]> {
|
|
132
|
-
const byEntityId = new Map(changes.map((change) => [change.entityId, change.filePath]));
|
|
133
|
-
const paths = contexts.flatMap((context) => context.filePath ?? byEntityId.get(context.entityId) ?? []);
|
|
134
|
-
const safePaths = [...new Set(paths)].filter(isSafeRelativeFilePath);
|
|
135
|
-
const selected = [];
|
|
136
|
-
let totalBytes = 0;
|
|
137
|
-
for (const filePath of safePaths) {
|
|
138
|
-
if (matchesAnyPattern(filePath, config.ignorePatterns)) continue;
|
|
139
|
-
const bytes = await fileSize(cwd, filePath);
|
|
140
|
-
if (bytes === null || bytes > config.maxFileSizeBytes) continue;
|
|
141
|
-
if (totalBytes + bytes > config.maxTotalSizeBytes) continue;
|
|
142
|
-
totalBytes += bytes;
|
|
143
|
-
selected.push(filePath);
|
|
144
|
-
}
|
|
145
|
-
return selected;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function repomixSearchConfig(): RepomixSearchConfig {
|
|
149
|
-
return {
|
|
150
|
-
compress: envBoolean("STUPIFY_REPOMIX_COMPRESS", true),
|
|
151
|
-
showLineNumbers: envBoolean("STUPIFY_REPOMIX_SHOW_LINE_NUMBERS", true),
|
|
152
|
-
removeEmptyLines: envBoolean("STUPIFY_REPOMIX_REMOVE_EMPTY_LINES", true),
|
|
153
|
-
maxFileSizeBytes: envInteger("STUPIFY_REPOMIX_MAX_FILE_BYTES", MAX_PACK_FILE_SIZE_BYTES),
|
|
154
|
-
maxTotalSizeBytes: envInteger("STUPIFY_REPOMIX_MAX_TOTAL_BYTES", MAX_PACK_TOTAL_SIZE_BYTES),
|
|
155
|
-
ignorePatterns: envList("STUPIFY_REPOMIX_IGNORE_PATTERNS"),
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function envBoolean(name: string, fallback: boolean): boolean {
|
|
160
|
-
const value = process.env[name];
|
|
161
|
-
if (value === undefined || value === "") return fallback;
|
|
162
|
-
return /^(1|true|yes|on)$/i.test(value);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function envInteger(name: string, fallback: number): number {
|
|
166
|
-
const value = Number(process.env[name]);
|
|
167
|
-
return Number.isInteger(value) && value > 0 ? value : fallback;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function envList(name: string): readonly string[] {
|
|
171
|
-
return (process.env[name] ?? "")
|
|
172
|
-
.split(",")
|
|
173
|
-
.map((item) => item.trim())
|
|
174
|
-
.filter(Boolean);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function matchesAnyPattern(filePath: string, patterns: readonly string[]): boolean {
|
|
178
|
-
return patterns.some((pattern) => matchesPattern(filePath, pattern));
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function matchesPattern(filePath: string, pattern: string): boolean {
|
|
182
|
-
if (pattern === filePath) return true;
|
|
183
|
-
if (!pattern.includes("*")) return false;
|
|
184
|
-
const escaped = pattern
|
|
185
|
-
.split("*")
|
|
186
|
-
.map(escapeRegExp)
|
|
187
|
-
.join(".*");
|
|
188
|
-
return new RegExp(`^${escaped}$`).test(filePath);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function escapeRegExp(value: string): string {
|
|
192
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
function isSafeRelativeFilePath(value: string): boolean {
|
|
196
|
-
if (!value || path.isAbsolute(value)) return false;
|
|
197
|
-
const normalized = path.normalize(value);
|
|
198
|
-
return normalized !== "." && !normalized.startsWith("..") && !path.isAbsolute(normalized);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async function fileSize(cwd: string, filePath: string): Promise<number | null> {
|
|
202
|
-
try {
|
|
203
|
-
const fullPath = path.join(cwd, filePath);
|
|
204
|
-
if (!fullPath.startsWith(`${cwd}${path.sep}`)) return null;
|
|
205
|
-
const result = await stat(fullPath);
|
|
206
|
-
return result.isFile() ? result.size : null;
|
|
207
|
-
} catch {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function shortenCode(value: string | null): string {
|
|
213
|
-
if (!value) return "(none)";
|
|
214
|
-
const lines = value.split(/\r?\n/);
|
|
215
|
-
const limit = 120;
|
|
216
|
-
if (lines.length <= limit) return value;
|
|
217
|
-
return `${lines.slice(0, limit).join("\n")}
|
|
218
|
-
[stupify: sem entity content shortened after ${limit} lines]`;
|
|
219
|
-
}
|