@ninjaxtools/slopdex 0.19.0 → 0.20.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/.gitignore +2 -0
- package/README.md +134 -46
- package/binary-install.js +348 -0
- package/binary.js +124 -0
- package/install.js +4 -0
- package/npm-shrinkwrap.json +52 -0
- package/package.json +78 -61
- package/run-slopdex.js +4 -0
- package/dist/chunk-5BO2LLXC.js +0 -15
- package/dist/chunk-5BO2LLXC.js.map +0 -1
- package/dist/chunk-BXWO2KMC.js +0 -20
- package/dist/chunk-BXWO2KMC.js.map +0 -1
- package/dist/chunk-DKRB2XT5.js +0 -33
- package/dist/chunk-DKRB2XT5.js.map +0 -1
- package/dist/chunk-DTI7SXGL.js +0 -109
- package/dist/chunk-DTI7SXGL.js.map +0 -1
- package/dist/chunk-GBEJHETS.js +0 -1117
- package/dist/chunk-GBEJHETS.js.map +0 -1
- package/dist/chunk-HMKVMRLF.js +0 -2221
- package/dist/chunk-HMKVMRLF.js.map +0 -1
- package/dist/chunk-IQU3YVZ3.js +0 -85
- package/dist/chunk-IQU3YVZ3.js.map +0 -1
- package/dist/chunk-JAVPHCZH.js +0 -24
- package/dist/chunk-JAVPHCZH.js.map +0 -1
- package/dist/chunk-MKUTTXZB.js +0 -74
- package/dist/chunk-MKUTTXZB.js.map +0 -1
- package/dist/chunk-OIKBO3NJ.js +0 -133
- package/dist/chunk-OIKBO3NJ.js.map +0 -1
- package/dist/chunk-P57ZJREE.js +0 -426
- package/dist/chunk-P57ZJREE.js.map +0 -1
- package/dist/chunk-S225GYCL.js +0 -79
- package/dist/chunk-S225GYCL.js.map +0 -1
- package/dist/chunk-TSURHRFF.js +0 -240
- package/dist/chunk-TSURHRFF.js.map +0 -1
- package/dist/chunk-VH5VGRCI.js +0 -103
- package/dist/chunk-VH5VGRCI.js.map +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -1130
- package/dist/cli.js.map +0 -1
- package/dist/code-index-MWSJKV3G.js +0 -14
- package/dist/code-index-MWSJKV3G.js.map +0 -1
- package/dist/cross-search-ERBFC25T.js +0 -10
- package/dist/cross-search-ERBFC25T.js.map +0 -1
- package/dist/database-P5XWJWQL.js +0 -14
- package/dist/database-P5XWJWQL.js.map +0 -1
- package/dist/hosted-GHIUHKOU.js +0 -13
- package/dist/hosted-GHIUHKOU.js.map +0 -1
- package/dist/index.d.ts +0 -681
- package/dist/index.js +0 -102
- package/dist/index.js.map +0 -1
- package/dist/jina-43RL7C6M.js +0 -12
- package/dist/jina-43RL7C6M.js.map +0 -1
- package/dist/openai-CYUPNBGB.js +0 -12
- package/dist/openai-CYUPNBGB.js.map +0 -1
- package/dist/openai-EC6THXKV.js +0 -21
- package/dist/openai-EC6THXKV.js.map +0 -1
- package/dist/openai-TCRXM66O.js +0 -11
- package/dist/openai-TCRXM66O.js.map +0 -1
- package/docs/implementation.md +0 -162
- package/docs/reference.md +0 -187
package/dist/cli.js
DELETED
|
@@ -1,1130 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
TerminalProgress,
|
|
4
|
-
clearProgress
|
|
5
|
-
} from "./chunk-S225GYCL.js";
|
|
6
|
-
import {
|
|
7
|
-
DEFAULT_PARALLELISM,
|
|
8
|
-
compileNameRegex
|
|
9
|
-
} from "./chunk-VH5VGRCI.js";
|
|
10
|
-
import {
|
|
11
|
-
CodeIndexError,
|
|
12
|
-
GitUnavailableError,
|
|
13
|
-
IncompatibleIndexError
|
|
14
|
-
} from "./chunk-DKRB2XT5.js";
|
|
15
|
-
|
|
16
|
-
// src/cli.ts
|
|
17
|
-
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
18
|
-
import path from "node:path";
|
|
19
|
-
import { DatabaseSync } from "node:sqlite";
|
|
20
|
-
import { parseArgs } from "node:util";
|
|
21
|
-
|
|
22
|
-
// src/format.ts
|
|
23
|
-
function functionName(value) {
|
|
24
|
-
return `${value.path} :: ${value.qualifiedName}`;
|
|
25
|
-
}
|
|
26
|
-
function formatSimilaritySummary(matches, source) {
|
|
27
|
-
const lines = matches.map((match) => `${source ? " " : ""}${rankDetails(match)} ${functionName(match.function)}${distanceDetails(match)}${scoreDetails(match)}`);
|
|
28
|
-
if (!source) return lines.length > 0 ? lines.join("\n") : "No matches.";
|
|
29
|
-
return [functionName(source), ...lines.length > 0 ? lines : [" No matches."]].join("\n");
|
|
30
|
-
}
|
|
31
|
-
function rankDetails(match) {
|
|
32
|
-
return match.rerankScore === void 0 ? match.similarity.toFixed(4) : `${match.rerankScore.toFixed(4)} rerank (${match.similarity.toFixed(4)} similarity)`;
|
|
33
|
-
}
|
|
34
|
-
function distanceDetails(match) {
|
|
35
|
-
return "physicalDistance" in match && match.physicalDistance !== void 0 ? ` [distance ${match.physicalDistance}]` : "";
|
|
36
|
-
}
|
|
37
|
-
function formatSimilarityClusters(results, sameIndex, limit) {
|
|
38
|
-
const combined = results.some((result) => result.matches.some((match) => match.descriptionSimilarity !== void 0));
|
|
39
|
-
const nodes = /* @__PURE__ */ new Map();
|
|
40
|
-
const neighbors = /* @__PURE__ */ new Map();
|
|
41
|
-
const edges = [];
|
|
42
|
-
const connect = (left, right) => {
|
|
43
|
-
const leftNeighbors = neighbors.get(left) ?? /* @__PURE__ */ new Set();
|
|
44
|
-
const rightNeighbors = neighbors.get(right) ?? /* @__PURE__ */ new Set();
|
|
45
|
-
leftNeighbors.add(right);
|
|
46
|
-
rightNeighbors.add(left);
|
|
47
|
-
neighbors.set(left, leftNeighbors);
|
|
48
|
-
neighbors.set(right, rightNeighbors);
|
|
49
|
-
};
|
|
50
|
-
for (const result of results) {
|
|
51
|
-
const sourceKey = `${sameIndex ? "index" : "source"}:${result.source.id}`;
|
|
52
|
-
nodes.set(sourceKey, { function: result.source, role: "source" });
|
|
53
|
-
for (const match of result.matches) {
|
|
54
|
-
const matchKey = `${sameIndex ? "index" : "target"}:${match.function.id}`;
|
|
55
|
-
nodes.set(matchKey, { function: match.function, role: sameIndex ? "source" : "target" });
|
|
56
|
-
connect(sourceKey, matchKey);
|
|
57
|
-
edges.push({ left: sourceKey, right: matchKey, similarity: match.similarity });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
const seen = /* @__PURE__ */ new Set();
|
|
61
|
-
const clusters = [];
|
|
62
|
-
for (const start of neighbors.keys()) {
|
|
63
|
-
if (seen.has(start)) continue;
|
|
64
|
-
const pending = [start];
|
|
65
|
-
const keys = /* @__PURE__ */ new Set();
|
|
66
|
-
while (pending.length > 0) {
|
|
67
|
-
const key = pending.pop();
|
|
68
|
-
if (seen.has(key)) continue;
|
|
69
|
-
seen.add(key);
|
|
70
|
-
keys.add(key);
|
|
71
|
-
for (const neighbor of neighbors.get(key) ?? []) pending.push(neighbor);
|
|
72
|
-
}
|
|
73
|
-
const similarities = edges.filter((edge) => keys.has(edge.left) && keys.has(edge.right)).map((edge) => edge.similarity);
|
|
74
|
-
clusters.push({
|
|
75
|
-
members: [...keys].map((key) => nodes.get(key)).sort((left, right) => clusterFunctionName(left, sameIndex).localeCompare(clusterFunctionName(right, sameIndex))),
|
|
76
|
-
min: Math.min(...similarities),
|
|
77
|
-
max: Math.max(...similarities)
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
clusters.sort((left, right) => right.members.length - left.members.length || clusterFunctionName(left.members[0], sameIndex).localeCompare(clusterFunctionName(right.members[0], sameIndex)));
|
|
81
|
-
const limited = limit !== void 0 ? clusters.slice(0, limit) : clusters;
|
|
82
|
-
if (limited.length === 0) return "No clusters.";
|
|
83
|
-
return limited.map((cluster, index) => {
|
|
84
|
-
const similarity = cluster.min === cluster.max ? cluster.min.toFixed(4) : `${cluster.min.toFixed(4)}-${cluster.max.toFixed(4)}`;
|
|
85
|
-
return [
|
|
86
|
-
`Cluster ${index + 1} (${cluster.members.length} functions, similarity ${similarity}${combined ? ", combined code + callable description + file description" : ""})`,
|
|
87
|
-
...cluster.members.map((member) => ` ${clusterFunctionName(member, sameIndex)}`)
|
|
88
|
-
].join("\n");
|
|
89
|
-
}).join("\n\n");
|
|
90
|
-
}
|
|
91
|
-
function scoreDetails(scores) {
|
|
92
|
-
if (scores.descriptionSimilarity === void 0 || scores.fileDescriptionSimilarity === void 0) return "";
|
|
93
|
-
if (scores.codeSimilarity === void 0) {
|
|
94
|
-
return ` [combined 50/50; description ${scores.descriptionSimilarity.toFixed(4)}, file ${scores.fileDescriptionSimilarity.toFixed(4)}]`;
|
|
95
|
-
}
|
|
96
|
-
return ` [combined thirds; code ${scores.codeSimilarity.toFixed(4)}, description ${scores.descriptionSimilarity.toFixed(4)}, file ${scores.fileDescriptionSimilarity.toFixed(4)}]`;
|
|
97
|
-
}
|
|
98
|
-
function clusterFunctionName(member, sameIndex) {
|
|
99
|
-
const role = sameIndex ? "" : `[${member.role}] `;
|
|
100
|
-
return `${role}${member.function.path}:${member.function.startLine}:${member.function.startColumn} :: ${member.function.qualifiedName}`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// src/cli.ts
|
|
104
|
-
var DESCRIPTION_PROVIDER_NAMES = ["openai", "opencode", "opencode-go"];
|
|
105
|
-
function isDescriptionProviderName(value) {
|
|
106
|
-
return DESCRIPTION_PROVIDER_NAMES.includes(value);
|
|
107
|
-
}
|
|
108
|
-
var parsed = (() => {
|
|
109
|
-
try {
|
|
110
|
-
return parseArgs({
|
|
111
|
-
args: process.argv.slice(2),
|
|
112
|
-
allowPositionals: true,
|
|
113
|
-
strict: true,
|
|
114
|
-
options: {
|
|
115
|
-
root: { type: "string", default: process.cwd() },
|
|
116
|
-
config: { type: "string" },
|
|
117
|
-
index: { type: "string" },
|
|
118
|
-
provider: { type: "string" },
|
|
119
|
-
model: { type: "string" },
|
|
120
|
-
"description-provider": { type: "string" },
|
|
121
|
-
"description-model": { type: "string" },
|
|
122
|
-
"reranker-candidates": { type: "string" },
|
|
123
|
-
dimensions: { type: "string" },
|
|
124
|
-
target: { type: "string" },
|
|
125
|
-
"target-root": { type: "string" },
|
|
126
|
-
"target-index": { type: "string" },
|
|
127
|
-
"target-config": { type: "string" },
|
|
128
|
-
"changed-since": { type: "string" },
|
|
129
|
-
uncommitted: { type: "boolean", default: false },
|
|
130
|
-
"source-path": { type: "string" },
|
|
131
|
-
"min-lines": { type: "string" },
|
|
132
|
-
regex: { type: "string" },
|
|
133
|
-
regexp: { type: "string", short: "e" },
|
|
134
|
-
limit: { type: "string" },
|
|
135
|
-
matches: { type: "string" },
|
|
136
|
-
threshold: { type: "string" },
|
|
137
|
-
format: { type: "string" },
|
|
138
|
-
cohesion: { type: "boolean", default: false },
|
|
139
|
-
"include-symmetric-duplicates": { type: "boolean", default: false },
|
|
140
|
-
"cross-file-only": { type: "boolean", default: false },
|
|
141
|
-
"rebuild-on-divergence": { type: "boolean", default: false },
|
|
142
|
-
"force-reindex": { type: "boolean", default: false },
|
|
143
|
-
"no-reindex": { type: "boolean", default: false },
|
|
144
|
-
callables: { type: "boolean", default: false },
|
|
145
|
-
"ignore-errors": { type: "boolean", default: false },
|
|
146
|
-
verbose: { type: "boolean", default: false },
|
|
147
|
-
version: { type: "boolean", default: false },
|
|
148
|
-
help: { type: "boolean", short: "h", default: false }
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
} catch (error) {
|
|
152
|
-
process.stderr.write(`slopdex: ${error instanceof Error ? error.message : String(error)}
|
|
153
|
-
`);
|
|
154
|
-
process.exit(2);
|
|
155
|
-
}
|
|
156
|
-
})();
|
|
157
|
-
var [command, ...positionals] = parsed.positionals;
|
|
158
|
-
var descriptionsAction = command === "descriptions" ? positionals[0] : void 0;
|
|
159
|
-
if (parsed.values.version) {
|
|
160
|
-
const version = true ? "0.19.0" : JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
|
|
161
|
-
process.stdout.write(`${version}
|
|
162
|
-
`);
|
|
163
|
-
process.exit(0);
|
|
164
|
-
}
|
|
165
|
-
if (parsed.values.help || !command) {
|
|
166
|
-
printHelp();
|
|
167
|
-
process.exit(parsed.values.help ? 0 : 1);
|
|
168
|
-
}
|
|
169
|
-
var diagnosticIndexes = /* @__PURE__ */ new Set();
|
|
170
|
-
function readErrorCountsLight(indexPath) {
|
|
171
|
-
const database = new DatabaseSync(indexPath, { readOnly: true });
|
|
172
|
-
try {
|
|
173
|
-
if (!database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'indexing_errors'").get()) {
|
|
174
|
-
return { errors: 0, files: 0 };
|
|
175
|
-
}
|
|
176
|
-
const counts = database.prepare("SELECT COUNT(*) AS errors, COUNT(DISTINCT path) AS files FROM indexing_errors").get();
|
|
177
|
-
return { errors: Number(counts.errors), files: Number(counts.files) };
|
|
178
|
-
} finally {
|
|
179
|
-
database.close();
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
process.on("exit", () => {
|
|
183
|
-
if (parsed.values["ignore-errors"]) return;
|
|
184
|
-
try {
|
|
185
|
-
const rootDir = path.resolve(parsed.values.root);
|
|
186
|
-
const config = loadConfig(rootDir, parsed.values.config);
|
|
187
|
-
diagnosticIndexes.add(path.resolve(parsed.values.index ?? config.indexPath ?? path.join(rootDir, ".slopdex/index.sqlite")));
|
|
188
|
-
if (parsed.values["target-index"]) diagnosticIndexes.add(path.resolve(parsed.values["target-index"]));
|
|
189
|
-
} catch {
|
|
190
|
-
}
|
|
191
|
-
for (const indexPath of diagnosticIndexes) {
|
|
192
|
-
try {
|
|
193
|
-
if (!existsSync(indexPath)) continue;
|
|
194
|
-
const { errors, files } = readErrorCountsLight(indexPath);
|
|
195
|
-
if (errors === 0) continue;
|
|
196
|
-
process.stderr.write(`slopdex: warning: ${errors} unresolved indexing error(s) in ${files} file(s); run slopdex index-errors --index ${JSON.stringify(indexPath)} to inspect, or use --ignore-errors to silence this warning.
|
|
197
|
-
`);
|
|
198
|
-
} catch {
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
void main().catch((error) => {
|
|
203
|
-
clearProgress();
|
|
204
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
205
|
-
process.stderr.write(`slopdex: ${message}
|
|
206
|
-
`);
|
|
207
|
-
process.exitCode = error instanceof CodeIndexError ? 2 : 1;
|
|
208
|
-
});
|
|
209
|
-
async function main() {
|
|
210
|
-
validateInvocation();
|
|
211
|
-
const rootDir = path.resolve(parsed.values.root);
|
|
212
|
-
if (command === "models") {
|
|
213
|
-
await runModels();
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (command === "config") {
|
|
217
|
-
await runConfig(rootDir);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const config = loadConfig(rootDir, parsed.values.config);
|
|
221
|
-
if (command === "index-errors") {
|
|
222
|
-
const indexPath = path.resolve(parsed.values.index ?? config.indexPath ?? path.join(rootDir, ".slopdex/index.sqlite"));
|
|
223
|
-
const { readIndexErrors } = await import("./database-P5XWJWQL.js");
|
|
224
|
-
const errors = existsSync(indexPath) ? readIndexErrors(indexPath) : [];
|
|
225
|
-
if (outputFormat("summary") === "summary") {
|
|
226
|
-
process.stdout.write(errors.length === 0 ? "No indexing errors.\n" : `${errors.map(
|
|
227
|
-
(error) => `${error.path}${error.startLine === null ? "" : `:${error.startLine}:${error.startColumn}`} ${error.qualifiedName ? `:: ${error.qualifiedName} ` : ""}[${error.code}]
|
|
228
|
-
${error.message}`
|
|
229
|
-
).join("\n")}
|
|
230
|
-
`);
|
|
231
|
-
} else printJson(errors);
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
const provider = await createProvider(config);
|
|
235
|
-
const reranker = command === "search" || command === "search-description" ? await createReranker(config) : void 0;
|
|
236
|
-
const descriptionProvider = await createDescriptionProvider(config);
|
|
237
|
-
const progress = new TerminalProgress();
|
|
238
|
-
const indexOptions = {
|
|
239
|
-
rootDir,
|
|
240
|
-
provider,
|
|
241
|
-
parallelism: config.parallelism ?? DEFAULT_PARALLELISM,
|
|
242
|
-
onProgress: (value) => progress.update(value),
|
|
243
|
-
...reranker ? { reranker } : {},
|
|
244
|
-
...descriptionProvider ? { descriptionProvider } : {},
|
|
245
|
-
onWarning: () => {
|
|
246
|
-
},
|
|
247
|
-
// Persisted diagnostics are reported once per index at exit.
|
|
248
|
-
...parsed.values.index || config.indexPath ? { indexPath: parsed.values.index ?? config.indexPath } : {},
|
|
249
|
-
...config.include ? { include: config.include } : {},
|
|
250
|
-
...config.exclude ? { exclude: config.exclude } : {},
|
|
251
|
-
...config.maxFileSize ? { maxFileSize: config.maxFileSize } : {},
|
|
252
|
-
...config.embeddingBatchSize ? { embeddingBatchSize: config.embeddingBatchSize } : {},
|
|
253
|
-
...config.verbose ? { verbose: true } : {}
|
|
254
|
-
};
|
|
255
|
-
const updateTarget = command === "update-git" ? parsed.values.target ?? "HEAD" : "HEAD";
|
|
256
|
-
const updateStats = await ensureIndexUpdated(
|
|
257
|
-
indexOptions,
|
|
258
|
-
"index",
|
|
259
|
-
updateTarget,
|
|
260
|
-
parsed.values["rebuild-on-divergence"],
|
|
261
|
-
parsed.values["force-reindex"],
|
|
262
|
-
parsed.values["no-reindex"],
|
|
263
|
-
descriptionRefresh(config)
|
|
264
|
-
);
|
|
265
|
-
const { CodeIndex } = await import("./code-index-MWSJKV3G.js");
|
|
266
|
-
const index = new CodeIndex(indexOptions);
|
|
267
|
-
try {
|
|
268
|
-
switch (command) {
|
|
269
|
-
case "status":
|
|
270
|
-
printJson(index.status());
|
|
271
|
-
break;
|
|
272
|
-
case "update-files":
|
|
273
|
-
if (positionals.length === 0) throw new CodeIndexError("update-files requires at least one path.");
|
|
274
|
-
printJson(await index.updateFiles({ upsert: positionals }));
|
|
275
|
-
break;
|
|
276
|
-
case "delete-files":
|
|
277
|
-
if (positionals.length === 0) throw new CodeIndexError("delete-files requires at least one path.");
|
|
278
|
-
printJson(await index.updateFiles({ delete: positionals }));
|
|
279
|
-
break;
|
|
280
|
-
case "update-git": {
|
|
281
|
-
printJson(updateStats);
|
|
282
|
-
break;
|
|
283
|
-
}
|
|
284
|
-
case "reindex-files":
|
|
285
|
-
printJson(await index.reindexFiles({ includeCallables: parsed.values.callables }));
|
|
286
|
-
break;
|
|
287
|
-
case "descriptions":
|
|
288
|
-
printJson(descriptionsAction === "enable" ? await index.useDescriptions() : index.disableDescriptions());
|
|
289
|
-
break;
|
|
290
|
-
case "search":
|
|
291
|
-
case "search-description": {
|
|
292
|
-
const query = positionals.join(" ").trim();
|
|
293
|
-
if (!query) throw new CodeIndexError(`${command} requires a query.`);
|
|
294
|
-
const threshold = similarityThreshold();
|
|
295
|
-
const nameRegex = qualifiedNameRegex();
|
|
296
|
-
const results = await (command === "search-description" ? index.searchDescription.bind(index) : index.similaritySearch.bind(index))({
|
|
297
|
-
query,
|
|
298
|
-
...nameRegex !== void 0 ? { nameRegex } : {},
|
|
299
|
-
...parsed.values.limit === void 0 ? {} : { limit: positiveIntegerOption(parsed.values.limit, 1, "limit") },
|
|
300
|
-
minSimilarity: threshold.min,
|
|
301
|
-
...threshold.max !== void 0 ? { maxSimilarity: threshold.max } : {}
|
|
302
|
-
});
|
|
303
|
-
const format = outputFormat("summary");
|
|
304
|
-
if (format === "clusters") throw new CodeIndexError("clusters format is only available for cross-search.");
|
|
305
|
-
if (format === "summary") {
|
|
306
|
-
process.stdout.write(`${command === "search-description" ? results.map((match) => `${formatSimilaritySummary([match])}
|
|
307
|
-
${match.function.description}`).join("\n") : formatSimilaritySummary(results)}
|
|
308
|
-
`);
|
|
309
|
-
} else printJson(results.map(presentMatch));
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
case "cross-search":
|
|
313
|
-
await runCrossSearch(index, indexOptions, provider);
|
|
314
|
-
break;
|
|
315
|
-
default:
|
|
316
|
-
throw new CodeIndexError(`Unknown command: ${command}`);
|
|
317
|
-
}
|
|
318
|
-
} finally {
|
|
319
|
-
index.close();
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
async function runCrossSearch(source, sourceOptions, provider) {
|
|
323
|
-
const targetRoot = parsed.values["target-root"];
|
|
324
|
-
const targetPath = parsed.values["target-index"];
|
|
325
|
-
if (targetRoot && !targetPath || !targetRoot && targetPath) {
|
|
326
|
-
throw new CodeIndexError("Cross-index search requires both --target-root and --target-index.");
|
|
327
|
-
}
|
|
328
|
-
const targetRootDir = targetRoot ? path.resolve(targetRoot) : void 0;
|
|
329
|
-
const resolvedTargetPath = targetPath ? path.resolve(targetPath) : void 0;
|
|
330
|
-
const usesSourceAsTarget = targetRootDir === source.rootDir && resolvedTargetPath === source.indexPath;
|
|
331
|
-
const targetConfig = targetRootDir && !usesSourceAsTarget ? loadConfig(targetRootDir, parsed.values["target-config"]) : void 0;
|
|
332
|
-
const targetDescriptionProvider = targetConfig ? await createDescriptionProvider(targetConfig) : void 0;
|
|
333
|
-
const targetOptions = targetRootDir && resolvedTargetPath && !usesSourceAsTarget ? {
|
|
334
|
-
rootDir: targetRootDir,
|
|
335
|
-
indexPath: resolvedTargetPath,
|
|
336
|
-
provider,
|
|
337
|
-
...sourceOptions.parallelism !== void 0 ? { parallelism: sourceOptions.parallelism } : {},
|
|
338
|
-
...sourceOptions.onProgress ? { onProgress: sourceOptions.onProgress } : {},
|
|
339
|
-
...sourceOptions.onWarning ? { onWarning: sourceOptions.onWarning } : {},
|
|
340
|
-
...targetConfig?.include ? { include: targetConfig.include } : {},
|
|
341
|
-
...targetConfig?.exclude ? { exclude: targetConfig.exclude } : {},
|
|
342
|
-
...targetConfig?.maxFileSize ? { maxFileSize: targetConfig.maxFileSize } : {},
|
|
343
|
-
...targetConfig?.embeddingBatchSize ? { embeddingBatchSize: targetConfig.embeddingBatchSize } : {},
|
|
344
|
-
...targetConfig?.verbose ? { verbose: true } : {},
|
|
345
|
-
...targetDescriptionProvider ? { descriptionProvider: targetDescriptionProvider } : {}
|
|
346
|
-
} : void 0;
|
|
347
|
-
if (targetOptions) {
|
|
348
|
-
await ensureIndexUpdated(
|
|
349
|
-
targetOptions,
|
|
350
|
-
"target index",
|
|
351
|
-
"HEAD",
|
|
352
|
-
parsed.values["rebuild-on-divergence"],
|
|
353
|
-
parsed.values["force-reindex"],
|
|
354
|
-
parsed.values["no-reindex"],
|
|
355
|
-
descriptionRefresh(targetConfig)
|
|
356
|
-
);
|
|
357
|
-
}
|
|
358
|
-
const { CodeIndex: CodeIndexClass } = await import("./code-index-MWSJKV3G.js");
|
|
359
|
-
const { crossSearch } = await import("./cross-search-ERBFC25T.js");
|
|
360
|
-
const target = targetOptions ? new CodeIndexClass({ ...targetOptions, readOnly: true }) : void 0;
|
|
361
|
-
const format = outputFormat(parsed.values.cohesion ? "summary" : "clusters");
|
|
362
|
-
const threshold = similarityThreshold();
|
|
363
|
-
const indexProgress = sourceOptions.onProgress;
|
|
364
|
-
const outputLimit = parsed.values.limit === void 0 ? void 0 : positiveIntegerOption(parsed.values.limit, 1, "limit");
|
|
365
|
-
const searchOptions = {
|
|
366
|
-
source,
|
|
367
|
-
...target ? { target } : {},
|
|
368
|
-
sourceFilter: crossSearchSourceFilter(),
|
|
369
|
-
limitPerFunction: positiveIntegerOption(parsed.values.matches, 5, "matches"),
|
|
370
|
-
minSimilarity: threshold.min,
|
|
371
|
-
...threshold.max !== void 0 ? { maxSimilarity: threshold.max } : {},
|
|
372
|
-
includeSymmetricDuplicates: parsed.values["include-symmetric-duplicates"],
|
|
373
|
-
crossFileOnly: parsed.values["cross-file-only"],
|
|
374
|
-
cohesion: parsed.values.cohesion,
|
|
375
|
-
minLines: minimumLines(),
|
|
376
|
-
// Per-source read progress (including read-repair scans) on the terminal bar.
|
|
377
|
-
...indexProgress ? {
|
|
378
|
-
onProgress: (value) => indexProgress({ phase: "cross-search", ...value })
|
|
379
|
-
} : {}
|
|
380
|
-
};
|
|
381
|
-
try {
|
|
382
|
-
if (format === "clusters") {
|
|
383
|
-
const results = [];
|
|
384
|
-
for await (const result of crossSearch(searchOptions)) results.push(result);
|
|
385
|
-
process.stdout.write(`${formatSimilarityClusters(results, !target, outputLimit)}
|
|
386
|
-
`);
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
let emitted = 0;
|
|
390
|
-
for await (const result of crossSearch(searchOptions)) {
|
|
391
|
-
if (outputLimit !== void 0 && emitted >= outputLimit) break;
|
|
392
|
-
if (format === "summary") {
|
|
393
|
-
process.stdout.write(`${formatSimilaritySummary(result.matches, result.source)}
|
|
394
|
-
`);
|
|
395
|
-
} else {
|
|
396
|
-
process.stdout.write(`${JSON.stringify({
|
|
397
|
-
source: presentFunction(result.source),
|
|
398
|
-
matches: result.matches.map(presentMatch),
|
|
399
|
-
scoring: result.scoring
|
|
400
|
-
})}
|
|
401
|
-
`);
|
|
402
|
-
}
|
|
403
|
-
emitted += 1;
|
|
404
|
-
}
|
|
405
|
-
} finally {
|
|
406
|
-
target?.close();
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
async function ensureIndexUpdated(options, label, target, rebuildOnDivergence, forceRebuild, noReindex, hooks) {
|
|
410
|
-
diagnosticIndexes.add(resolveIndexPath(options));
|
|
411
|
-
const initialized = await initializeMissingIndex(options, label, target, noReindex, hooks);
|
|
412
|
-
if (initialized) return initialized;
|
|
413
|
-
const { CodeIndex } = await import("./code-index-MWSJKV3G.js");
|
|
414
|
-
try {
|
|
415
|
-
const index = new CodeIndex(options);
|
|
416
|
-
try {
|
|
417
|
-
await hooks?.beforeRefresh?.(index);
|
|
418
|
-
const stats = await refreshIndex(index, label, target, rebuildOnDivergence, noReindex);
|
|
419
|
-
await hooks?.afterRefresh?.(index);
|
|
420
|
-
return stats;
|
|
421
|
-
} finally {
|
|
422
|
-
index.close();
|
|
423
|
-
}
|
|
424
|
-
} catch (error) {
|
|
425
|
-
if (!forceRebuild || !(error instanceof IncompatibleIndexError)) throw error;
|
|
426
|
-
process.stderr.write(
|
|
427
|
-
`slopdex: warning: ${label} is incompatible (${error.message}); rebuilding automatically because --force-reindex was specified.
|
|
428
|
-
`
|
|
429
|
-
);
|
|
430
|
-
const indexPath = resolveIndexPath(options);
|
|
431
|
-
const descriptionProfile = descriptionProfileForRebuild(indexPath, options.rootDir);
|
|
432
|
-
try {
|
|
433
|
-
const { resetIndexState } = await import("./database-P5XWJWQL.js");
|
|
434
|
-
resetIndexState(indexPath, options.rootDir, options.provider.profile);
|
|
435
|
-
} catch (resetError) {
|
|
436
|
-
if (!(resetError instanceof IncompatibleIndexError)) throw resetError;
|
|
437
|
-
removeIndexArtifacts(indexPath);
|
|
438
|
-
}
|
|
439
|
-
return initializeIndex(options, indexPath, label, target, noReindex, descriptionProfile, hooks);
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
async function initializeMissingIndex(options, label, target, noReindex, hooks) {
|
|
443
|
-
const indexPath = resolveIndexPath(options);
|
|
444
|
-
if (existsSync(indexPath)) return null;
|
|
445
|
-
process.stderr.write(
|
|
446
|
-
`slopdex: ${label} not found at ${indexPath}; initializing automatically from ${target}${noReindex ? "" : " and the working tree"}.
|
|
447
|
-
`
|
|
448
|
-
);
|
|
449
|
-
return initializeIndex(options, indexPath, label, target, noReindex, null, hooks);
|
|
450
|
-
}
|
|
451
|
-
async function initializeIndex(options, indexPath, label, target, noReindex, descriptionProfile = null, hooks) {
|
|
452
|
-
const { CodeIndex } = await import("./code-index-MWSJKV3G.js");
|
|
453
|
-
const rebuiltDescriptionProvider = descriptionProfile && !options.descriptionProvider ? await createDescriptionProvider({
|
|
454
|
-
descriptionProvider: descriptionProfile.provider,
|
|
455
|
-
descriptionModel: descriptionProfile.model,
|
|
456
|
-
parallelism: options.parallelism ?? DEFAULT_PARALLELISM,
|
|
457
|
-
...options.verbose ? { verbose: true } : {}
|
|
458
|
-
}) : void 0;
|
|
459
|
-
const index = new CodeIndex({
|
|
460
|
-
...options,
|
|
461
|
-
indexPath,
|
|
462
|
-
...rebuiltDescriptionProvider ? { descriptionProvider: rebuiltDescriptionProvider } : {}
|
|
463
|
-
});
|
|
464
|
-
try {
|
|
465
|
-
if (descriptionProfile) await index.useDescriptions();
|
|
466
|
-
await hooks?.beforeRefresh?.(index);
|
|
467
|
-
const stats = await refreshIndex(index, label, target, false, noReindex);
|
|
468
|
-
await hooks?.afterRefresh?.(index);
|
|
469
|
-
return stats;
|
|
470
|
-
} finally {
|
|
471
|
-
index.close();
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
function descriptionProfileForRebuild(indexPath, rootDir) {
|
|
475
|
-
const db = new DatabaseSync(indexPath, { readOnly: true });
|
|
476
|
-
try {
|
|
477
|
-
const metadata = new Map(db.prepare("SELECT key, value FROM metadata").all().map((row) => [row.key, row.value]));
|
|
478
|
-
if (metadata.get("root_dir") !== path.resolve(rootDir) || metadata.get("descriptions_enabled") !== "true") return null;
|
|
479
|
-
const profile = JSON.parse(metadata.get("description_profile"));
|
|
480
|
-
if (!isDescriptionProviderName(profile.provider)) {
|
|
481
|
-
throw new CodeIndexError("Rebuilding this description index requires its custom description provider through the library API.");
|
|
482
|
-
}
|
|
483
|
-
return profile;
|
|
484
|
-
} finally {
|
|
485
|
-
db.close();
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
async function refreshIndex(index, label, target, rebuildOnDivergence, noReindex) {
|
|
489
|
-
try {
|
|
490
|
-
return await index.updateFromGit({ target, rebuildOnDivergence, includeWorkingTree: !noReindex });
|
|
491
|
-
} catch (error) {
|
|
492
|
-
if (!(error instanceof GitUnavailableError)) throw error;
|
|
493
|
-
const status = index.status();
|
|
494
|
-
if (noReindex && status.fileCount > 0) {
|
|
495
|
-
process.stderr.write(
|
|
496
|
-
`slopdex: warning: no Git repository is available for ${label}; full working-tree re-index skipped because --no-reindex was specified.
|
|
497
|
-
`
|
|
498
|
-
);
|
|
499
|
-
return {
|
|
500
|
-
filesUpdated: 0,
|
|
501
|
-
filesDeleted: 0,
|
|
502
|
-
functionsAdded: 0,
|
|
503
|
-
functionsUpdated: 0,
|
|
504
|
-
functionsDeleted: 0,
|
|
505
|
-
embeddingsCreated: 0,
|
|
506
|
-
checkpoint: status.gitCheckpoint
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
process.stderr.write(
|
|
510
|
-
`slopdex: warning: no Git repository is available for ${label}; re-indexing all source files from the working tree.
|
|
511
|
-
`
|
|
512
|
-
);
|
|
513
|
-
return await index.updateFromWorkingTree();
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
function resolveIndexPath(options) {
|
|
517
|
-
return path.resolve(options.indexPath ?? path.join(path.resolve(options.rootDir), ".slopdex", "index.sqlite"));
|
|
518
|
-
}
|
|
519
|
-
function removeIndexArtifacts(indexPath) {
|
|
520
|
-
for (const suffix of ["", "-shm", "-wal", "-journal"]) rmSync(`${indexPath}${suffix}`, { force: true });
|
|
521
|
-
}
|
|
522
|
-
function loadConfig(rootDir, configuredPath) {
|
|
523
|
-
const configPath = path.resolve(configuredPath ?? path.join(rootDir, ".slopdex", "config.json"));
|
|
524
|
-
if (!existsSync(configPath)) return commandLineConfig({});
|
|
525
|
-
const value = JSON.parse(readFileSync(configPath, "utf8"));
|
|
526
|
-
return commandLineConfig(value);
|
|
527
|
-
}
|
|
528
|
-
function commandLineConfig(config) {
|
|
529
|
-
const dimensions = parsed.values.dimensions ? numberOption(parsed.values.dimensions, 0, "dimensions") : config.dimensions;
|
|
530
|
-
const providerValue = parsed.values.provider ?? config.provider;
|
|
531
|
-
if (providerValue !== void 0 && providerValue !== "openai" && providerValue !== "jina") {
|
|
532
|
-
throw new CodeIndexError(`Unsupported provider: ${providerValue}`);
|
|
533
|
-
}
|
|
534
|
-
const provider = providerValue === "openai" || providerValue === "jina" ? providerValue : void 0;
|
|
535
|
-
const descriptionProviderValue = parsed.values["description-provider"] ?? config.descriptionProvider;
|
|
536
|
-
if (descriptionProviderValue !== void 0 && !isDescriptionProviderName(descriptionProviderValue)) {
|
|
537
|
-
throw new CodeIndexError(`Unsupported description provider: ${descriptionProviderValue}`);
|
|
538
|
-
}
|
|
539
|
-
if (config.rerankingEnabled !== void 0 && typeof config.rerankingEnabled !== "boolean") {
|
|
540
|
-
throw new CodeIndexError("rerankingEnabled must be a boolean.");
|
|
541
|
-
}
|
|
542
|
-
if (config.parallelism !== void 0 && (!Number.isInteger(config.parallelism) || config.parallelism < 1)) {
|
|
543
|
-
throw new CodeIndexError("parallelism must be a positive integer.");
|
|
544
|
-
}
|
|
545
|
-
if (config.verbose !== void 0 && typeof config.verbose !== "boolean") {
|
|
546
|
-
throw new CodeIndexError("verbose must be a boolean.");
|
|
547
|
-
}
|
|
548
|
-
if (config.rerankingEnabled === true) {
|
|
549
|
-
if (config.rerankerProvider !== "cohere" && config.rerankerProvider !== "jina" && config.rerankerProvider !== "openai") {
|
|
550
|
-
throw new CodeIndexError(`Unsupported reranker provider: ${String(config.rerankerProvider)}`);
|
|
551
|
-
}
|
|
552
|
-
if (config.rerankerModel !== void 0 && (typeof config.rerankerModel !== "string" || !config.rerankerModel.trim())) {
|
|
553
|
-
throw new CodeIndexError("rerankerModel must be a non-empty string.");
|
|
554
|
-
}
|
|
555
|
-
if (config.rerankerProvider === "openai" && config.rerankerCandidates !== void 0 && (typeof config.rerankerCandidates !== "number" || !Number.isInteger(config.rerankerCandidates) || config.rerankerCandidates < 1)) {
|
|
556
|
-
throw new CodeIndexError("rerankerCandidates must be a positive integer.");
|
|
557
|
-
}
|
|
558
|
-
if (config.rerankerProvider === "openai" && typeof config.rerankerCandidates === "number" && config.rerankerCandidates > 100) {
|
|
559
|
-
throw new CodeIndexError("rerankerCandidates must not exceed 100.");
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
return {
|
|
563
|
-
...config,
|
|
564
|
-
...provider ? { provider } : {},
|
|
565
|
-
...parsed.values.model ? { model: parsed.values.model } : {},
|
|
566
|
-
...descriptionProviderValue ? { descriptionProvider: descriptionProviderValue } : {},
|
|
567
|
-
...parsed.values["description-model"] ? { descriptionModel: parsed.values["description-model"] } : {},
|
|
568
|
-
...dimensions ? { dimensions } : {},
|
|
569
|
-
...parsed.values.verbose ? { verbose: true } : {}
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
async function createDescriptionProvider(config) {
|
|
573
|
-
if (!config.descriptionProvider && !config.descriptionModel) return void 0;
|
|
574
|
-
const { OpenAIDescriptionProvider } = await import("./openai-EC6THXKV.js");
|
|
575
|
-
return new OpenAIDescriptionProvider({
|
|
576
|
-
...config.descriptionProvider ? { provider: config.descriptionProvider } : {},
|
|
577
|
-
...config.descriptionModel ? { model: config.descriptionModel } : {},
|
|
578
|
-
parallelism: config.parallelism ?? DEFAULT_PARALLELISM,
|
|
579
|
-
...config.verbose ? { verbose: true } : {}
|
|
580
|
-
});
|
|
581
|
-
}
|
|
582
|
-
function descriptionRefresh(config) {
|
|
583
|
-
if (command === "descriptions") {
|
|
584
|
-
return descriptionsAction === "disable" ? { beforeRefresh: (index) => {
|
|
585
|
-
index.disableDescriptions();
|
|
586
|
-
} } : enableDescriptionRefresh(false);
|
|
587
|
-
}
|
|
588
|
-
if (config.descriptionsEnabled === true) return enableDescriptionRefresh();
|
|
589
|
-
if (config.descriptionsEnabled === false) return { beforeRefresh: (index) => {
|
|
590
|
-
index.disableDescriptions();
|
|
591
|
-
} };
|
|
592
|
-
return void 0;
|
|
593
|
-
}
|
|
594
|
-
function enableDescriptionRefresh(afterRefresh = true) {
|
|
595
|
-
return {
|
|
596
|
-
...afterRefresh ? { afterRefresh: async (index) => {
|
|
597
|
-
await index.useDescriptions();
|
|
598
|
-
} } : {}
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
async function runModels() {
|
|
602
|
-
const positionalProvider = positionals[0];
|
|
603
|
-
const optionProvider = parsed.values["description-provider"];
|
|
604
|
-
if (positionalProvider && optionProvider && positionalProvider !== optionProvider) {
|
|
605
|
-
throw new CodeIndexError("models provider and --description-provider must match when both are supplied.");
|
|
606
|
-
}
|
|
607
|
-
const requested = positionalProvider ?? optionProvider;
|
|
608
|
-
if (requested !== void 0 && !isOpenCodeDescriptionProvider(requested)) {
|
|
609
|
-
throw new CodeIndexError("models provider must be opencode or opencode-go.");
|
|
610
|
-
}
|
|
611
|
-
const providers = requested ? [requested] : ["opencode", "opencode-go"];
|
|
612
|
-
const models = (await Promise.all(providers.map(fetchPublishedModels))).flat();
|
|
613
|
-
const format = outputFormat("summary");
|
|
614
|
-
if (format === "clusters") throw new CodeIndexError("clusters format is only available for cross-search.");
|
|
615
|
-
if (format === "json") printJson(models);
|
|
616
|
-
else process.stdout.write(`${models.map(({ provider, model }) => `${provider}/${model}`).join("\n")}
|
|
617
|
-
`);
|
|
618
|
-
}
|
|
619
|
-
async function runConfig(rootDir) {
|
|
620
|
-
const configPath = path.resolve(parsed.values.config ?? path.join(rootDir, ".slopdex", "config.json"));
|
|
621
|
-
const config = readConfigFile(configPath);
|
|
622
|
-
const action = positionals[0];
|
|
623
|
-
let result;
|
|
624
|
-
if (action === "descriptions") {
|
|
625
|
-
config.descriptionsEnabled = positionals[1] === "enable";
|
|
626
|
-
result = { descriptionsEnabled: config.descriptionsEnabled };
|
|
627
|
-
} else if (action === "model") {
|
|
628
|
-
const selected = await resolveConfiguredModel(positionals[1]);
|
|
629
|
-
config.descriptionProvider = selected.provider;
|
|
630
|
-
config.descriptionModel = selected.model;
|
|
631
|
-
result = { descriptionProvider: selected.provider, descriptionModel: selected.model };
|
|
632
|
-
} else if (action === "parallelism") {
|
|
633
|
-
const parallelism = Number(positionals[1]);
|
|
634
|
-
config.parallelism = parallelism;
|
|
635
|
-
result = { parallelism };
|
|
636
|
-
} else {
|
|
637
|
-
const provider = positionals[1];
|
|
638
|
-
if (provider === "disable") {
|
|
639
|
-
config.rerankingEnabled = false;
|
|
640
|
-
result = { rerankingEnabled: false };
|
|
641
|
-
} else {
|
|
642
|
-
const sameProvider = config.rerankerProvider === provider;
|
|
643
|
-
const defaultModel = provider === "cohere" ? "rerank-v4.0-pro" : provider === "jina" ? "jina-reranker-v3.5" : "gpt-5.6-luna";
|
|
644
|
-
const model = positionals[2] ?? (sameProvider && typeof config.rerankerModel === "string" && config.rerankerModel.trim() ? config.rerankerModel : defaultModel);
|
|
645
|
-
config.rerankerProvider = provider;
|
|
646
|
-
config.rerankerModel = model;
|
|
647
|
-
if (provider === "openai") {
|
|
648
|
-
const savedCandidates = sameProvider && typeof config.rerankerCandidates === "number" && Number.isInteger(config.rerankerCandidates) && config.rerankerCandidates > 0 ? config.rerankerCandidates : 10;
|
|
649
|
-
config.rerankerCandidates = openAIRerankerCandidateCount(
|
|
650
|
-
parsed.values["reranker-candidates"],
|
|
651
|
-
savedCandidates,
|
|
652
|
-
"reranker candidate count"
|
|
653
|
-
);
|
|
654
|
-
} else {
|
|
655
|
-
delete config.rerankerCandidates;
|
|
656
|
-
}
|
|
657
|
-
config.rerankingEnabled = true;
|
|
658
|
-
result = {
|
|
659
|
-
rerankingEnabled: true,
|
|
660
|
-
rerankerProvider: provider,
|
|
661
|
-
rerankerModel: model,
|
|
662
|
-
...provider === "openai" ? { rerankerCandidates: config.rerankerCandidates } : {}
|
|
663
|
-
};
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
writeConfigFile(configPath, config);
|
|
667
|
-
const format = outputFormat("summary");
|
|
668
|
-
if (format === "clusters") throw new CodeIndexError("clusters format is only available for cross-search.");
|
|
669
|
-
if (format === "json") printJson({ configPath, ...result });
|
|
670
|
-
else process.stdout.write(`Updated ${configPath}: ${Object.entries(result).map(([key, value]) => `${key}=${value}`).join(" ")}
|
|
671
|
-
`);
|
|
672
|
-
}
|
|
673
|
-
async function resolveConfiguredModel(positionalModel) {
|
|
674
|
-
const optionModel = parsed.values["description-model"];
|
|
675
|
-
if (positionalModel && optionModel && positionalModel !== optionModel) {
|
|
676
|
-
throw new CodeIndexError("config model argument and --description-model must match when both are supplied.");
|
|
677
|
-
}
|
|
678
|
-
let reference = positionalModel ?? optionModel;
|
|
679
|
-
if (!reference) throw new CodeIndexError("config model requires a model ID or provider/model reference.");
|
|
680
|
-
let provider = parsed.values["description-provider"];
|
|
681
|
-
const separator = reference.indexOf("/");
|
|
682
|
-
if (separator !== -1) {
|
|
683
|
-
const qualifiedProvider = reference.slice(0, separator);
|
|
684
|
-
reference = reference.slice(separator + 1);
|
|
685
|
-
if (provider && provider !== qualifiedProvider) {
|
|
686
|
-
throw new CodeIndexError("Model reference and --description-provider must select the same provider.");
|
|
687
|
-
}
|
|
688
|
-
provider = qualifiedProvider;
|
|
689
|
-
}
|
|
690
|
-
if (!reference) throw new CodeIndexError("config model requires a non-empty model ID.");
|
|
691
|
-
if (provider !== void 0) {
|
|
692
|
-
if (!isOpenCodeDescriptionProvider(provider)) {
|
|
693
|
-
throw new CodeIndexError("config model provider must be opencode or opencode-go.");
|
|
694
|
-
}
|
|
695
|
-
const models = await fetchPublishedModels(provider);
|
|
696
|
-
if (!models.some(({ model }) => model === reference)) {
|
|
697
|
-
throw new CodeIndexError(`Unknown ${provider} model: ${reference}`);
|
|
698
|
-
}
|
|
699
|
-
return { provider, model: reference };
|
|
700
|
-
}
|
|
701
|
-
const matches = (await Promise.all([
|
|
702
|
-
fetchPublishedModels("opencode"),
|
|
703
|
-
fetchPublishedModels("opencode-go")
|
|
704
|
-
])).flat().filter(({ model }) => model === reference);
|
|
705
|
-
if (matches.length === 0) throw new CodeIndexError(`Unknown OpenCode model: ${reference}`);
|
|
706
|
-
if (matches.length > 1) {
|
|
707
|
-
throw new CodeIndexError(`Model ${reference} is available from multiple providers; use provider/model.`);
|
|
708
|
-
}
|
|
709
|
-
return matches[0];
|
|
710
|
-
}
|
|
711
|
-
async function fetchPublishedModels(provider) {
|
|
712
|
-
const { descriptionProviderBaseUrl } = await import("./openai-EC6THXKV.js");
|
|
713
|
-
const url = `${descriptionProviderBaseUrl(provider)}/models`;
|
|
714
|
-
let response;
|
|
715
|
-
try {
|
|
716
|
-
response = await fetch(url);
|
|
717
|
-
} catch (error) {
|
|
718
|
-
throw new CodeIndexError(`Could not fetch ${provider} models: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
719
|
-
}
|
|
720
|
-
if (!response.ok) {
|
|
721
|
-
throw new CodeIndexError(`Could not fetch ${provider} models (${response.status}): ${(await response.text()).slice(0, 500)}`);
|
|
722
|
-
}
|
|
723
|
-
let body;
|
|
724
|
-
try {
|
|
725
|
-
body = await response.json();
|
|
726
|
-
} catch (error) {
|
|
727
|
-
throw new CodeIndexError(`${provider} returned a malformed model list.`, { cause: error });
|
|
728
|
-
}
|
|
729
|
-
if (!Array.isArray(body.data) || body.data.some((item) => typeof item.id !== "string" || item.id.length === 0)) {
|
|
730
|
-
throw new CodeIndexError(`${provider} returned a malformed model list.`);
|
|
731
|
-
}
|
|
732
|
-
return [...new Set(body.data.map((item) => item.id))].map((model) => ({ provider, model }));
|
|
733
|
-
}
|
|
734
|
-
function isOpenCodeDescriptionProvider(value) {
|
|
735
|
-
return value === "opencode" || value === "opencode-go";
|
|
736
|
-
}
|
|
737
|
-
function readConfigFile(configPath) {
|
|
738
|
-
if (!existsSync(configPath)) return {};
|
|
739
|
-
const value = JSON.parse(readFileSync(configPath, "utf8"));
|
|
740
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
741
|
-
throw new CodeIndexError(`Config must contain a JSON object: ${configPath}`);
|
|
742
|
-
}
|
|
743
|
-
return value;
|
|
744
|
-
}
|
|
745
|
-
function writeConfigFile(configPath, config) {
|
|
746
|
-
mkdirSync(path.dirname(configPath), { recursive: true });
|
|
747
|
-
const temporaryPath = `${configPath}.tmp-${process.pid}`;
|
|
748
|
-
writeFileSync(temporaryPath, `${JSON.stringify(config, null, 2)}
|
|
749
|
-
`);
|
|
750
|
-
renameSync(temporaryPath, configPath);
|
|
751
|
-
}
|
|
752
|
-
async function createProvider(config) {
|
|
753
|
-
if ((config.provider ?? "openai") === "jina") {
|
|
754
|
-
const { JinaEmbeddingProvider } = await import("./jina-43RL7C6M.js");
|
|
755
|
-
return new JinaEmbeddingProvider({
|
|
756
|
-
...config.model ? { model: config.model } : {},
|
|
757
|
-
...config.dimensions ? { dimensions: config.dimensions } : {},
|
|
758
|
-
parallelism: config.parallelism ?? DEFAULT_PARALLELISM,
|
|
759
|
-
...config.verbose ? { verbose: true } : {}
|
|
760
|
-
});
|
|
761
|
-
}
|
|
762
|
-
const { OpenAIEmbeddingProvider } = await import("./openai-CYUPNBGB.js");
|
|
763
|
-
return new OpenAIEmbeddingProvider({
|
|
764
|
-
...config.model ? { model: config.model } : {},
|
|
765
|
-
...config.dimensions ? { dimensions: config.dimensions } : {},
|
|
766
|
-
parallelism: config.parallelism ?? DEFAULT_PARALLELISM,
|
|
767
|
-
...config.verbose ? { verbose: true } : {}
|
|
768
|
-
});
|
|
769
|
-
}
|
|
770
|
-
async function createReranker(config) {
|
|
771
|
-
if (config.rerankingEnabled !== true) return void 0;
|
|
772
|
-
if (config.rerankerProvider === "cohere" || config.rerankerProvider === "jina") {
|
|
773
|
-
const { CohereReranker, JinaReranker } = await import("./hosted-GHIUHKOU.js");
|
|
774
|
-
if (config.rerankerProvider === "cohere") {
|
|
775
|
-
return new CohereReranker({
|
|
776
|
-
...config.rerankerModel ? { model: config.rerankerModel } : {},
|
|
777
|
-
...config.verbose ? { verbose: true } : {}
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
return new JinaReranker({
|
|
781
|
-
...config.rerankerModel ? { model: config.rerankerModel } : {},
|
|
782
|
-
...config.verbose ? { verbose: true } : {}
|
|
783
|
-
});
|
|
784
|
-
}
|
|
785
|
-
if (config.rerankerProvider === "openai") {
|
|
786
|
-
const { OpenAILLMReranker } = await import("./openai-TCRXM66O.js");
|
|
787
|
-
return new OpenAILLMReranker({
|
|
788
|
-
...config.rerankerModel ? { model: config.rerankerModel } : {},
|
|
789
|
-
...config.rerankerCandidates ? { candidateCount: config.rerankerCandidates } : {},
|
|
790
|
-
...config.verbose ? { verbose: true } : {}
|
|
791
|
-
});
|
|
792
|
-
}
|
|
793
|
-
throw new CodeIndexError("rerankerProvider is required when rerankingEnabled is true.");
|
|
794
|
-
}
|
|
795
|
-
function numberOption(value, defaultValue, name) {
|
|
796
|
-
if (value === void 0) return defaultValue;
|
|
797
|
-
const parsedValue = Number(value);
|
|
798
|
-
if (!Number.isFinite(parsedValue)) throw new CodeIndexError(`${name} must be a number.`);
|
|
799
|
-
return parsedValue;
|
|
800
|
-
}
|
|
801
|
-
function validateInvocation() {
|
|
802
|
-
if (parsed.values["reranker-candidates"] !== void 0 && (command !== "config" || positionals[0] !== "reranker" || positionals[1] !== "openai")) {
|
|
803
|
-
throw new CodeIndexError("--reranker-candidates is only available with config reranker openai.");
|
|
804
|
-
}
|
|
805
|
-
if (parsed.values.cohesion && command !== "cross-search") {
|
|
806
|
-
throw new CodeIndexError("--cohesion is only available for cross-search.");
|
|
807
|
-
}
|
|
808
|
-
if (parsed.values.matches !== void 0 && command !== "cross-search") {
|
|
809
|
-
throw new CodeIndexError("--matches is only available for cross-search.");
|
|
810
|
-
}
|
|
811
|
-
const nameRegex = qualifiedNameRegex();
|
|
812
|
-
if (nameRegex !== void 0) {
|
|
813
|
-
if (!["search", "search-description", "cross-search"].includes(command)) {
|
|
814
|
-
throw new CodeIndexError("-e/--regexp/--regex is only available for search, search-description, and cross-search.");
|
|
815
|
-
}
|
|
816
|
-
compileNameRegex(nameRegex, parsed.values.regex !== void 0 ? "--regex value" : "-e/--regexp value");
|
|
817
|
-
}
|
|
818
|
-
switch (command) {
|
|
819
|
-
case "models":
|
|
820
|
-
if (positionals.length > 1) throw new CodeIndexError("models accepts at most one provider.");
|
|
821
|
-
if (outputFormat("summary") === "clusters") throw new CodeIndexError("clusters format is only available for cross-search.");
|
|
822
|
-
return;
|
|
823
|
-
case "config":
|
|
824
|
-
if (outputFormat("summary") === "clusters") throw new CodeIndexError("clusters format is only available for cross-search.");
|
|
825
|
-
if (positionals[0] === "descriptions") {
|
|
826
|
-
if (positionals.length !== 2 || positionals[1] !== "enable" && positionals[1] !== "disable") {
|
|
827
|
-
throw new CodeIndexError("config descriptions requires enable or disable.");
|
|
828
|
-
}
|
|
829
|
-
return;
|
|
830
|
-
}
|
|
831
|
-
if (positionals[0] === "model") {
|
|
832
|
-
if (positionals.length > 2 || !positionals[1] && !parsed.values["description-model"]) {
|
|
833
|
-
throw new CodeIndexError("config model requires a model ID or provider/model reference.");
|
|
834
|
-
}
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
if (positionals[0] === "parallelism") {
|
|
838
|
-
const value = Number(positionals[1]);
|
|
839
|
-
if (positionals.length !== 2 || !Number.isInteger(value) || value < 1) {
|
|
840
|
-
throw new CodeIndexError("config parallelism requires a positive integer.");
|
|
841
|
-
}
|
|
842
|
-
return;
|
|
843
|
-
}
|
|
844
|
-
if (positionals[0] === "reranker") {
|
|
845
|
-
const provider = positionals[1];
|
|
846
|
-
if (provider !== "cohere" && provider !== "jina" && provider !== "openai" && provider !== "disable" || positionals.length > (provider === "disable" ? 2 : 3) || positionals[2] !== void 0 && !positionals[2].trim()) {
|
|
847
|
-
throw new CodeIndexError("config reranker requires cohere, jina, openai, or disable, optionally followed by a model ID.");
|
|
848
|
-
}
|
|
849
|
-
if (provider === "openai") openAIRerankerCandidateCount(parsed.values["reranker-candidates"], 10, "reranker candidate count");
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
throw new CodeIndexError("config requires descriptions, model, parallelism, or reranker.");
|
|
853
|
-
case "index-errors":
|
|
854
|
-
if (outputFormat("summary") === "clusters") throw new CodeIndexError("clusters format is only available for cross-search.");
|
|
855
|
-
return;
|
|
856
|
-
case "status":
|
|
857
|
-
case "update-git":
|
|
858
|
-
return;
|
|
859
|
-
case "reindex-files":
|
|
860
|
-
if (positionals.length > 0) throw new CodeIndexError("reindex-files does not accept positional arguments.");
|
|
861
|
-
return;
|
|
862
|
-
case "descriptions":
|
|
863
|
-
if (positionals.length !== 1 || descriptionsAction !== "enable" && descriptionsAction !== "disable") {
|
|
864
|
-
throw new CodeIndexError("descriptions requires enable or disable.");
|
|
865
|
-
}
|
|
866
|
-
return;
|
|
867
|
-
case "update-files":
|
|
868
|
-
if (positionals.length === 0) throw new CodeIndexError("update-files requires at least one path.");
|
|
869
|
-
return;
|
|
870
|
-
case "delete-files":
|
|
871
|
-
if (positionals.length === 0) throw new CodeIndexError("delete-files requires at least one path.");
|
|
872
|
-
return;
|
|
873
|
-
case "search":
|
|
874
|
-
case "search-description": {
|
|
875
|
-
if (!positionals.join(" ").trim()) throw new CodeIndexError(`${command} requires a query.`);
|
|
876
|
-
validateOutputLimit();
|
|
877
|
-
similarityThreshold();
|
|
878
|
-
if (outputFormat("summary") === "clusters") throw new CodeIndexError("clusters format is only available for cross-search.");
|
|
879
|
-
return;
|
|
880
|
-
}
|
|
881
|
-
case "cross-search":
|
|
882
|
-
if (Boolean(parsed.values["target-root"]) !== Boolean(parsed.values["target-index"])) {
|
|
883
|
-
throw new CodeIndexError("Cross-index search requires both --target-root and --target-index.");
|
|
884
|
-
}
|
|
885
|
-
if (parsed.values["target-config"] && !parsed.values["target-root"]) {
|
|
886
|
-
throw new CodeIndexError("--target-config requires --target-root and --target-index.");
|
|
887
|
-
}
|
|
888
|
-
validateMatches(5);
|
|
889
|
-
validateOutputLimit();
|
|
890
|
-
similarityThreshold();
|
|
891
|
-
if (parsed.values.cohesion && outputFormat("summary") === "clusters") {
|
|
892
|
-
throw new CodeIndexError("clusters format does not preserve cohesion match order; use summary or json.");
|
|
893
|
-
}
|
|
894
|
-
outputFormat(parsed.values.cohesion ? "summary" : "clusters");
|
|
895
|
-
minimumLines();
|
|
896
|
-
crossSearchSourceFilter();
|
|
897
|
-
return;
|
|
898
|
-
default:
|
|
899
|
-
throw new CodeIndexError(`Unknown command: ${command}`);
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
function validateMatches(defaultValue) {
|
|
903
|
-
positiveIntegerOption(parsed.values.matches, defaultValue, "matches");
|
|
904
|
-
}
|
|
905
|
-
function validateOutputLimit() {
|
|
906
|
-
if (parsed.values.limit !== void 0) positiveIntegerOption(parsed.values.limit, 1, "limit");
|
|
907
|
-
}
|
|
908
|
-
function positiveIntegerOption(value, defaultValue, name) {
|
|
909
|
-
const parsedValue = numberOption(value, defaultValue, name);
|
|
910
|
-
if (!Number.isInteger(parsedValue) || parsedValue < 1) throw new CodeIndexError(`${name} must be a positive integer.`);
|
|
911
|
-
return parsedValue;
|
|
912
|
-
}
|
|
913
|
-
function openAIRerankerCandidateCount(value, defaultValue, name) {
|
|
914
|
-
const count = positiveIntegerOption(value, defaultValue, name);
|
|
915
|
-
if (count > 100) throw new CodeIndexError(`${name} must not exceed 100.`);
|
|
916
|
-
return count;
|
|
917
|
-
}
|
|
918
|
-
function similarityThreshold(defaultMin = 0.3) {
|
|
919
|
-
const threshold = parsed.values.threshold;
|
|
920
|
-
if (threshold === void 0) return { min: defaultMin };
|
|
921
|
-
const number = "[+-]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:e[+-]?\\d+)?";
|
|
922
|
-
const range = new RegExp(`^\\s*(${number})\\s*-\\s*(${number})\\s*$`, "i").exec(threshold);
|
|
923
|
-
if (!range) return { min: numberOption(threshold, defaultMin, "threshold") };
|
|
924
|
-
const min = Number(range[1]);
|
|
925
|
-
const max = Number(range[2]);
|
|
926
|
-
if (!Number.isFinite(min) || !Number.isFinite(max)) throw new CodeIndexError("threshold range bounds must be numbers.");
|
|
927
|
-
if (min >= max) throw new CodeIndexError("threshold range minimum must be less than its maximum.");
|
|
928
|
-
return { min, max };
|
|
929
|
-
}
|
|
930
|
-
function outputFormat(defaultValue) {
|
|
931
|
-
const value = parsed.values.format ?? defaultValue;
|
|
932
|
-
if (value !== "json" && value !== "summary" && value !== "clusters") {
|
|
933
|
-
throw new CodeIndexError("format must be json, summary, or clusters.");
|
|
934
|
-
}
|
|
935
|
-
return value;
|
|
936
|
-
}
|
|
937
|
-
function minimumLines() {
|
|
938
|
-
const value = numberOption(parsed.values["min-lines"], 2, "min-lines");
|
|
939
|
-
if (!Number.isInteger(value) || value < 1) throw new CodeIndexError("min-lines must be a positive integer.");
|
|
940
|
-
return value;
|
|
941
|
-
}
|
|
942
|
-
function qualifiedNameRegex() {
|
|
943
|
-
if (parsed.values.regexp !== void 0 && parsed.values.regex !== void 0 && parsed.values.regexp !== parsed.values.regex) {
|
|
944
|
-
throw new CodeIndexError("-e/--regexp and --regex are aliases and cannot use different values.");
|
|
945
|
-
}
|
|
946
|
-
return parsed.values.regexp ?? parsed.values.regex;
|
|
947
|
-
}
|
|
948
|
-
function crossSearchSourceFilter() {
|
|
949
|
-
const changedSince = parsed.values["changed-since"];
|
|
950
|
-
const uncommitted = parsed.values.uncommitted;
|
|
951
|
-
const nameRegex = qualifiedNameRegex();
|
|
952
|
-
const restrictions = {
|
|
953
|
-
...parsed.values["source-path"] ? { path: parsed.values["source-path"] } : {},
|
|
954
|
-
...nameRegex !== void 0 ? { nameRegex } : {}
|
|
955
|
-
};
|
|
956
|
-
if (changedSince) return { type: "changed-since", commit: changedSince, ...uncommitted ? { uncommitted: true } : {}, ...restrictions };
|
|
957
|
-
if (uncommitted) return { type: "uncommitted", ...restrictions };
|
|
958
|
-
return { type: "all", ...restrictions };
|
|
959
|
-
}
|
|
960
|
-
function presentFunction(value) {
|
|
961
|
-
const { embeddingInput: _embeddingInput, embeddingId: _embeddingId, descriptionEmbeddingId: _descriptionEmbeddingId, ...result } = value;
|
|
962
|
-
return result;
|
|
963
|
-
}
|
|
964
|
-
function presentMatch(value) {
|
|
965
|
-
const { function: callable, ...scores } = value;
|
|
966
|
-
return { ...scores, function: presentFunction(callable) };
|
|
967
|
-
}
|
|
968
|
-
function printJson(value) {
|
|
969
|
-
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
970
|
-
`);
|
|
971
|
-
}
|
|
972
|
-
function printHelp() {
|
|
973
|
-
process.stdout.write(`Usage: slopdex <command> [arguments] [options]
|
|
974
|
-
|
|
975
|
-
Commands:
|
|
976
|
-
models [opencode|opencode-go] List current published OpenCode models
|
|
977
|
-
config model <model|provider/model> Validate and save an OpenCode description model
|
|
978
|
-
config descriptions <enable|disable> Save description state without opening an index
|
|
979
|
-
config parallelism <count> Save the concurrent provider request limit (default: 10)
|
|
980
|
-
config reranker <provider|disable> Save Cohere, Jina, or OpenAI reranking settings
|
|
981
|
-
status Show index metadata
|
|
982
|
-
index-errors List persisted file and function indexing failures
|
|
983
|
-
update-files <path...> Index specific working-tree files
|
|
984
|
-
reindex-files Regenerate stale file descriptions
|
|
985
|
-
delete-files <path...> Remove specific files from the index
|
|
986
|
-
update-git Index a Git snapshot plus working-tree changes
|
|
987
|
-
search <query> Search functions by semantic similarity
|
|
988
|
-
descriptions <enable|disable> Enable or disable automatic purpose descriptions
|
|
989
|
-
search-description <query> Search functions using description embeddings
|
|
990
|
-
cross-search Find nearest functions for each source function
|
|
991
|
-
|
|
992
|
-
Examples:
|
|
993
|
-
Search code:
|
|
994
|
-
slopdex search "validate an authenticated session"
|
|
995
|
-
|
|
996
|
-
slopdex search "keep the repository index synchronized"
|
|
997
|
-
0.4284 tests/languages.test.ts :: refresh
|
|
998
|
-
0.4200 src/cli.ts :: refreshIndex
|
|
999
|
-
0.4113 src/code-index.ts :: CodeIndex.updateFromGit
|
|
1000
|
-
...
|
|
1001
|
-
|
|
1002
|
-
Enable descriptions, then search by their meaning:
|
|
1003
|
-
slopdex descriptions enable
|
|
1004
|
-
slopdex search-description "keep the repository index synchronized"
|
|
1005
|
-
|
|
1006
|
-
slopdex models opencode-go
|
|
1007
|
-
slopdex config model opencode-go/gpt-5.6-luna
|
|
1008
|
-
|
|
1009
|
-
Find duplicate code:
|
|
1010
|
-
Compare functions across files, exclude short wrappers, and group matches into clusters:
|
|
1011
|
-
slopdex cross-search --cross-file-only --min-lines 4 --threshold 0.9
|
|
1012
|
-
|
|
1013
|
-
Cluster 1 (3 functions, similarity 0.9124-0.9568)
|
|
1014
|
-
src/auth/session.ts:18:1 :: validateSession
|
|
1015
|
-
src/http/middleware.ts:42:1 :: authenticate
|
|
1016
|
-
src/users/user-service.ts:27:3 :: UserService.authenticate
|
|
1017
|
-
|
|
1018
|
-
This found three substantial authentication functions in separate files with very
|
|
1019
|
-
high similarity. Review them for repeated validation or session logic that could be shared.
|
|
1020
|
-
Middleware and service locations may be intentional architectural layers, so this is
|
|
1021
|
-
evidence to inspect rather than proof they should merge. Connected components may use
|
|
1022
|
-
transitive links, so every function need not directly match every other function.
|
|
1023
|
-
|
|
1024
|
-
Using --cross-file-only is useful to exclude similar code in the same file.
|
|
1025
|
-
|
|
1026
|
-
Review adjacent bands with threshold ranges:
|
|
1027
|
-
slopdex cross-search --cross-file-only --min-lines 4 --threshold 0.9
|
|
1028
|
-
slopdex cross-search --cross-file-only --min-lines 4 --threshold 0.85-0.9
|
|
1029
|
-
|
|
1030
|
-
Restrict functions used in cross-search:
|
|
1031
|
-
Only use uncommitted working-tree functions as sources:
|
|
1032
|
-
slopdex cross-search --uncommitted --cross-file-only --min-lines 4 --threshold 0.9
|
|
1033
|
-
|
|
1034
|
-
Only use functions changed since origin/main as sources:
|
|
1035
|
-
slopdex cross-search --changed-since origin/main --threshold 0.9
|
|
1036
|
-
|
|
1037
|
-
Only use matching symbols under src/services as sources:
|
|
1038
|
-
slopdex cross-search --source-path src/services -e '^UserService\\.' --threshold 0.9
|
|
1039
|
-
|
|
1040
|
-
Find related code stored far apart:
|
|
1041
|
-
--cohesion orders matches from farthest to nearest, which can highlight similar code
|
|
1042
|
-
that could be made more cohesive through an abstraction:
|
|
1043
|
-
slopdex cross-search --cross-file-only --cohesion --threshold 0.8
|
|
1044
|
-
|
|
1045
|
-
src/auth/session.ts :: validateSession
|
|
1046
|
-
0.9400 packages/http/middleware.ts :: authenticate [distance 4]
|
|
1047
|
-
0.9300 src/auth/token.ts :: validateToken [distance 1]
|
|
1048
|
-
|
|
1049
|
-
--cohesion keeps cross-search's semantic matches but orders each source's matches
|
|
1050
|
-
from greatest to least path distance. Similarity breaks distance ties. This highlights
|
|
1051
|
-
related functions stored far apart without introducing a separate analysis command.
|
|
1052
|
-
|
|
1053
|
-
Use with agents:
|
|
1054
|
-
slopdex search "..." --threshold 0.5
|
|
1055
|
-
slopdex cross-search --uncommitted --threshold 0.8
|
|
1056
|
-
|
|
1057
|
-
Reranking (second-stage reranker for search and search-description):
|
|
1058
|
-
slopdex config reranker cohere
|
|
1059
|
-
slopdex config reranker jina
|
|
1060
|
-
slopdex config reranker openai
|
|
1061
|
-
|
|
1062
|
-
Compare repositories:
|
|
1063
|
-
slopdex cross-search --target-root /path/to/other/repo --target-index /path/to/other/repo/.slopdex/index.sqlite --threshold 0.9
|
|
1064
|
-
|
|
1065
|
-
Inspect index health:
|
|
1066
|
-
slopdex status
|
|
1067
|
-
slopdex index-errors --format summary
|
|
1068
|
-
slopdex --version
|
|
1069
|
-
|
|
1070
|
-
Options:
|
|
1071
|
-
--version Show the package version
|
|
1072
|
-
--root <path> Repository root (default: current directory)
|
|
1073
|
-
--config <path> Config file (default: .slopdex/config.json)
|
|
1074
|
-
--index <path> SQLite index path
|
|
1075
|
-
--provider <openai|jina> Embedding provider
|
|
1076
|
-
--model <name> Embedding model
|
|
1077
|
-
--description-provider <name> Description provider: openai, opencode, or opencode-go
|
|
1078
|
-
--description-model <name> Description model (provider default: gpt-5.6-luna or muse-spark-1.3-contributor)
|
|
1079
|
-
--reranker-candidates <number> Candidates sent to the OpenAI LLM reranker (default: 10)
|
|
1080
|
-
--dimensions <number> Embedding dimensions
|
|
1081
|
-
--target <ref> Target ref for update-git (default: HEAD)
|
|
1082
|
-
--rebuild-on-divergence Rebuild after a rebase or branch change
|
|
1083
|
-
--force-reindex Rebuild an incompatible existing index
|
|
1084
|
-
--no-reindex Skip worktree overlays or reuse a non-Git index
|
|
1085
|
-
--callables With reindex-files, also regenerate callable descriptions
|
|
1086
|
-
--ignore-errors Silence warnings about persisted indexing errors
|
|
1087
|
-
--verbose Log every external model call instead of one per kind/model
|
|
1088
|
-
--limit <number> Output limit (default: unlimited; threshold filters results)
|
|
1089
|
-
--matches <number> Cross-search matches per source function (default: 5)
|
|
1090
|
-
--threshold <number|range> Show similarities at/above a value or within a range (default: 0.3)
|
|
1091
|
-
--format <json|summary|clusters> Output format (default: summary; cross-search: clusters)
|
|
1092
|
-
--cohesion Re-rank cross-search matches by physical distance
|
|
1093
|
-
--include-symmetric-duplicates Show both directions of same-index matches
|
|
1094
|
-
--cross-file-only Exclude matches from the source file
|
|
1095
|
-
--min-lines <number> Minimum callable length for cross-search (default: 2)
|
|
1096
|
-
-e, --regexp <regex> Filter qualified symbols (analysis: sources only)
|
|
1097
|
-
--regex <regex> Alias for -e/--regexp
|
|
1098
|
-
--changed-since <commit> Search added, modified, or moved functions
|
|
1099
|
-
--uncommitted Search functions from uncommitted files
|
|
1100
|
-
--source-path <path> Restrict cross-search sources to a file or directory
|
|
1101
|
-
--target-root <path> Root of a second indexed codebase
|
|
1102
|
-
--target-index <path> SQLite path of a second index
|
|
1103
|
-
--target-config <path> Config file for a second indexed codebase
|
|
1104
|
-
|
|
1105
|
-
Other Examples:
|
|
1106
|
-
Save description state and parallelism without opening an index:
|
|
1107
|
-
slopdex config descriptions enable
|
|
1108
|
-
slopdex config parallelism 10
|
|
1109
|
-
|
|
1110
|
-
Index specific working-tree files:
|
|
1111
|
-
slopdex update-files src/service.ts src/model.ts
|
|
1112
|
-
|
|
1113
|
-
Regenerate stale file descriptions, optionally continuing through callable descriptions:
|
|
1114
|
-
slopdex reindex-files
|
|
1115
|
-
slopdex reindex-files --callables
|
|
1116
|
-
|
|
1117
|
-
Remove deleted files from the index:
|
|
1118
|
-
slopdex delete-files src/removed.ts
|
|
1119
|
-
|
|
1120
|
-
Index HEAD and overlay uncommitted working-tree changes:
|
|
1121
|
-
slopdex update-git --target HEAD
|
|
1122
|
-
|
|
1123
|
-
Combine source restrictions (all must match):
|
|
1124
|
-
slopdex cross-search -e 'validate' --source-path src --changed-since origin/main --uncommitted
|
|
1125
|
-
|
|
1126
|
-
Filter semantic search results by qualified symbol before applying the limit:
|
|
1127
|
-
slopdex search "validate session" -e '^Session\\.' --limit 10
|
|
1128
|
-
`);
|
|
1129
|
-
}
|
|
1130
|
-
//# sourceMappingURL=cli.js.map
|