scip-query 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +280 -109
- package/dist/index.d.ts +18 -1
- package/dist/index.js +192 -24
- package/dist/reindex-worker.js +146 -5
- package/package.json +3 -1
package/dist/index.d.ts
CHANGED
|
@@ -43,6 +43,7 @@ export { dataflow } from './queries/dataflow.js';
|
|
|
43
43
|
export { slice } from './queries/slice.js';
|
|
44
44
|
export { redundantReexports } from './queries/redundant-reexports.js';
|
|
45
45
|
export { similarSignatures } from './queries/similar-signatures.js';
|
|
46
|
+
import { Index } from '@c4312/scip';
|
|
46
47
|
import 'better-sqlite3';
|
|
47
48
|
|
|
48
49
|
/**
|
|
@@ -88,6 +89,22 @@ declare const INDEXER_CONFIGS: Record<SupportedLanguage, IndexerConfig>;
|
|
|
88
89
|
/** Get the indexer config for a language */
|
|
89
90
|
declare function getIndexerConfig(language: SupportedLanguage): IndexerConfig;
|
|
90
91
|
|
|
92
|
+
interface MergeScipResult {
|
|
93
|
+
documentCount: number;
|
|
94
|
+
externalSymbolCount: number;
|
|
95
|
+
inputCount: number;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Merge multiple SCIP indices into a single index message.
|
|
99
|
+
*
|
|
100
|
+
* A SCIP index is a code graph snapshot: metadata about one indexed project plus
|
|
101
|
+
* the documents and external symbols discovered by one or more indexers. Merging
|
|
102
|
+
* means producing one unified snapshot so later conversion to SQLite sees the
|
|
103
|
+
* whole repo instead of whichever language indexed last.
|
|
104
|
+
*/
|
|
105
|
+
declare function mergeScipIndexes(indexes: readonly Index[]): Index;
|
|
106
|
+
declare function mergeScipFiles(inputPaths: readonly string[], outputPath: string): MergeScipResult;
|
|
107
|
+
|
|
91
108
|
/**
|
|
92
109
|
* Check if a binary is available on PATH.
|
|
93
110
|
*/
|
|
@@ -250,4 +267,4 @@ declare function installSkills(opts?: {
|
|
|
250
267
|
quiet?: boolean;
|
|
251
268
|
}): InstallSkillsResult;
|
|
252
269
|
|
|
253
|
-
export { INDEXER_CONFIGS, IndexerConfig, ProjectConfig, ScipLocalSymbol, ScipSymbol, SupportedLanguage, Watcher, WatcherStatus, detectLanguages, getIndexerConfig, getScipVersion, initProjectConfig, installSkills, isBinaryAvailable, isIndexerInstalled, isScipInstalled, leafName, loadProjectConfig, parseSymbol, printScipInstallInstructions, reindex, resolveCacheDir, resolveIndexPaths, shortenSymbol, tryInstallIndexer, tryInstallScipCli };
|
|
270
|
+
export { INDEXER_CONFIGS, IndexerConfig, ProjectConfig, ScipLocalSymbol, ScipSymbol, SupportedLanguage, Watcher, WatcherStatus, detectLanguages, getIndexerConfig, getScipVersion, initProjectConfig, installSkills, isBinaryAvailable, isIndexerInstalled, isScipInstalled, leafName, loadProjectConfig, mergeScipFiles, mergeScipIndexes, parseSymbol, printScipInstallInstructions, reindex, resolveCacheDir, resolveIndexPaths, shortenSymbol, tryInstallIndexer, tryInstallScipCli };
|
package/dist/index.js
CHANGED
|
@@ -254,7 +254,7 @@ var ScipDatabase = class {
|
|
|
254
254
|
// src/gitignore-filter.ts
|
|
255
255
|
import ignore from "ignore";
|
|
256
256
|
import { readFileSync, existsSync } from "fs";
|
|
257
|
-
import { join,
|
|
257
|
+
import { dirname, isAbsolute, join, relative, resolve } from "path";
|
|
258
258
|
function createGitignoreFilter(projectRoot) {
|
|
259
259
|
const ig = ignore();
|
|
260
260
|
let loaded = false;
|
|
@@ -271,8 +271,8 @@ function createGitignoreFilter(projectRoot) {
|
|
|
271
271
|
ig.add(DEFAULT_IGNORES);
|
|
272
272
|
}
|
|
273
273
|
return {
|
|
274
|
-
isIgnored: (relativePath) => ig
|
|
275
|
-
filter: (paths) => paths.filter((p) => !ig
|
|
274
|
+
isIgnored: (relativePath) => safeIgnores(ig, projectRoot, relativePath),
|
|
275
|
+
filter: (paths) => paths.filter((p) => !safeIgnores(ig, projectRoot, p))
|
|
276
276
|
};
|
|
277
277
|
}
|
|
278
278
|
function findGitignoreFiles(projectRoot) {
|
|
@@ -352,11 +352,36 @@ Thumbs.db
|
|
|
352
352
|
# Type definitions (often noise in queries)
|
|
353
353
|
*.d.ts
|
|
354
354
|
`;
|
|
355
|
+
function safeIgnores(ig, projectRoot, inputPath) {
|
|
356
|
+
const relativePath = normalizeForIgnore(projectRoot, inputPath);
|
|
357
|
+
if (!relativePath) {
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
return ig.ignores(relativePath);
|
|
362
|
+
} catch {
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
function normalizeForIgnore(projectRoot, inputPath) {
|
|
367
|
+
if (!inputPath || inputPath === ".") {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
if (!isAbsolute(inputPath) && !inputPath.startsWith("..")) {
|
|
371
|
+
return inputPath.replaceAll("\\", "/");
|
|
372
|
+
}
|
|
373
|
+
const absolutePath = isAbsolute(inputPath) ? inputPath : resolve(projectRoot, inputPath);
|
|
374
|
+
const relativePath = relative(projectRoot, absolutePath).replaceAll("\\", "/");
|
|
375
|
+
if (!relativePath || relativePath === "." || relativePath.startsWith("..")) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
return relativePath;
|
|
379
|
+
}
|
|
355
380
|
|
|
356
381
|
// src/reindex/index.ts
|
|
357
382
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
358
|
-
import { existsSync as existsSync5, renameSync } from "fs";
|
|
359
|
-
import { join as join5 } from "path";
|
|
383
|
+
import { existsSync as existsSync5, renameSync, rmSync } from "fs";
|
|
384
|
+
import { basename, dirname as dirname2, extname as extname2, join as join5 } from "path";
|
|
360
385
|
|
|
361
386
|
// src/scip-cli.ts
|
|
362
387
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
@@ -991,6 +1016,128 @@ function resolveDotnetProject(projectRoot, suffixes) {
|
|
|
991
1016
|
return null;
|
|
992
1017
|
}
|
|
993
1018
|
|
|
1019
|
+
// src/reindex/merge.ts
|
|
1020
|
+
import { readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
1021
|
+
import { create } from "@bufbuild/protobuf";
|
|
1022
|
+
import {
|
|
1023
|
+
deserializeSCIP,
|
|
1024
|
+
serializeSCIP,
|
|
1025
|
+
DocumentSchema,
|
|
1026
|
+
IndexSchema,
|
|
1027
|
+
SymbolInformationSchema
|
|
1028
|
+
} from "@c4312/scip";
|
|
1029
|
+
function mergeScipIndexes(indexes) {
|
|
1030
|
+
if (indexes.length === 0) {
|
|
1031
|
+
throw new Error("Cannot merge zero SCIP indexes");
|
|
1032
|
+
}
|
|
1033
|
+
if (indexes.length === 1) {
|
|
1034
|
+
return indexes[0];
|
|
1035
|
+
}
|
|
1036
|
+
const metadata = mergeMetadata(indexes);
|
|
1037
|
+
const documents = mergeDocuments(indexes.flatMap((index) => index.documents ?? []));
|
|
1038
|
+
const externalSymbols = mergeSymbolInfos(indexes.flatMap((index) => index.externalSymbols ?? []));
|
|
1039
|
+
return create(IndexSchema, {
|
|
1040
|
+
metadata,
|
|
1041
|
+
documents,
|
|
1042
|
+
externalSymbols
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
function mergeScipFiles(inputPaths, outputPath) {
|
|
1046
|
+
if (inputPaths.length === 0) {
|
|
1047
|
+
throw new Error("Cannot merge zero SCIP files");
|
|
1048
|
+
}
|
|
1049
|
+
const indexes = inputPaths.map((path) => deserializeSCIP(readFileSync2(path)));
|
|
1050
|
+
const merged = mergeScipIndexes(indexes);
|
|
1051
|
+
writeFileSync(outputPath, Buffer.from(serializeSCIP(merged)));
|
|
1052
|
+
return {
|
|
1053
|
+
documentCount: merged.documents.length,
|
|
1054
|
+
externalSymbolCount: merged.externalSymbols.length,
|
|
1055
|
+
inputCount: inputPaths.length
|
|
1056
|
+
};
|
|
1057
|
+
}
|
|
1058
|
+
function mergeMetadata(indexes) {
|
|
1059
|
+
const first = indexes[0]?.metadata;
|
|
1060
|
+
if (!first) {
|
|
1061
|
+
return void 0;
|
|
1062
|
+
}
|
|
1063
|
+
const expectedProjectRoot = first.projectRoot;
|
|
1064
|
+
for (const index of indexes.slice(1)) {
|
|
1065
|
+
const actualProjectRoot = index.metadata?.projectRoot;
|
|
1066
|
+
if (expectedProjectRoot && actualProjectRoot && actualProjectRoot !== expectedProjectRoot) {
|
|
1067
|
+
throw new Error(
|
|
1068
|
+
`Cannot merge SCIP indexes with different project roots: ${expectedProjectRoot} vs ${actualProjectRoot}`
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
return first;
|
|
1073
|
+
}
|
|
1074
|
+
function mergeDocuments(documents) {
|
|
1075
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
1076
|
+
for (const document of documents) {
|
|
1077
|
+
const existing = byPath.get(document.relativePath);
|
|
1078
|
+
if (!existing) {
|
|
1079
|
+
byPath.set(document.relativePath, document);
|
|
1080
|
+
continue;
|
|
1081
|
+
}
|
|
1082
|
+
byPath.set(document.relativePath, create(DocumentSchema, {
|
|
1083
|
+
language: existing.language || document.language,
|
|
1084
|
+
relativePath: existing.relativePath || document.relativePath,
|
|
1085
|
+
occurrences: [...existing.occurrences, ...document.occurrences],
|
|
1086
|
+
symbols: mergeSymbolInfos([...existing.symbols, ...document.symbols]),
|
|
1087
|
+
text: chooseText(existing.text, document.text),
|
|
1088
|
+
positionEncoding: existing.positionEncoding || document.positionEncoding
|
|
1089
|
+
}));
|
|
1090
|
+
}
|
|
1091
|
+
return [...byPath.values()];
|
|
1092
|
+
}
|
|
1093
|
+
function mergeSymbolInfos(symbols2) {
|
|
1094
|
+
const bySymbol = /* @__PURE__ */ new Map();
|
|
1095
|
+
for (const symbol of symbols2) {
|
|
1096
|
+
const existing = bySymbol.get(symbol.symbol);
|
|
1097
|
+
if (!existing) {
|
|
1098
|
+
bySymbol.set(symbol.symbol, symbol);
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
bySymbol.set(symbol.symbol, create(SymbolInformationSchema, {
|
|
1102
|
+
symbol: existing.symbol,
|
|
1103
|
+
documentation: uniqueStrings([...existing.documentation, ...symbol.documentation]),
|
|
1104
|
+
relationships: mergeRelationships([...existing.relationships, ...symbol.relationships]),
|
|
1105
|
+
kind: existing.kind || symbol.kind,
|
|
1106
|
+
displayName: existing.displayName || symbol.displayName,
|
|
1107
|
+
enclosingSymbol: existing.enclosingSymbol || symbol.enclosingSymbol,
|
|
1108
|
+
signatureDocumentation: existing.signatureDocumentation ?? symbol.signatureDocumentation
|
|
1109
|
+
}));
|
|
1110
|
+
}
|
|
1111
|
+
return [...bySymbol.values()];
|
|
1112
|
+
}
|
|
1113
|
+
function mergeRelationships(relationships) {
|
|
1114
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1115
|
+
const merged = [];
|
|
1116
|
+
for (const relationship of relationships) {
|
|
1117
|
+
const key = [
|
|
1118
|
+
relationship.symbol,
|
|
1119
|
+
relationship.isReference ? "1" : "0",
|
|
1120
|
+
relationship.isImplementation ? "1" : "0",
|
|
1121
|
+
relationship.isTypeDefinition ? "1" : "0",
|
|
1122
|
+
relationship.isDefinition ? "1" : "0"
|
|
1123
|
+
].join("|");
|
|
1124
|
+
if (seen.has(key)) {
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
seen.add(key);
|
|
1128
|
+
merged.push(relationship);
|
|
1129
|
+
}
|
|
1130
|
+
return merged;
|
|
1131
|
+
}
|
|
1132
|
+
function chooseText(left, right) {
|
|
1133
|
+
if (!left) return right;
|
|
1134
|
+
if (!right) return left;
|
|
1135
|
+
return left.length >= right.length ? left : right;
|
|
1136
|
+
}
|
|
1137
|
+
function uniqueStrings(values) {
|
|
1138
|
+
return [...new Set(values)];
|
|
1139
|
+
}
|
|
1140
|
+
|
|
994
1141
|
// src/reindex/index.ts
|
|
995
1142
|
async function reindex(opts) {
|
|
996
1143
|
const {
|
|
@@ -1026,7 +1173,11 @@ async function reindex(opts) {
|
|
|
1026
1173
|
...process.env,
|
|
1027
1174
|
NODE_OPTIONS: `--max-old-space-size=${maxHeapMb}`
|
|
1028
1175
|
};
|
|
1029
|
-
|
|
1176
|
+
const languageOutputs = languages.map((language, index) => ({
|
|
1177
|
+
language,
|
|
1178
|
+
scipPath: languages.length > 1 ? tempScipPath(outputScip, language, index) : outputScip
|
|
1179
|
+
}));
|
|
1180
|
+
for (const { language: lang, scipPath } of languageOutputs) {
|
|
1030
1181
|
const config = getIndexerConfig(lang);
|
|
1031
1182
|
const binaryLabel = describeIndexerBinary(config);
|
|
1032
1183
|
const projectLocalBinary = resolveProjectLocalIndexerBinary(config, projectRoot);
|
|
@@ -1056,7 +1207,7 @@ async function reindex(opts) {
|
|
|
1056
1207
|
const indexerEnv = getIndexerExecutionEnv(config, env, resolvedBinary);
|
|
1057
1208
|
const { binary, args } = config.indexArgs({
|
|
1058
1209
|
projectRoot,
|
|
1059
|
-
outputPath:
|
|
1210
|
+
outputPath: scipPath,
|
|
1060
1211
|
pnpmWorkspaces: opts.pnpmWorkspaces,
|
|
1061
1212
|
indexerBinary: resolvedBinary
|
|
1062
1213
|
});
|
|
@@ -1075,7 +1226,11 @@ Make sure ${binaryLabel} is installed and available on PATH.`,
|
|
|
1075
1226
|
{ cause: err }
|
|
1076
1227
|
);
|
|
1077
1228
|
}
|
|
1078
|
-
moveDefaultOutputIfNeeded(config, projectRoot,
|
|
1229
|
+
moveDefaultOutputIfNeeded(config, projectRoot, scipPath);
|
|
1230
|
+
}
|
|
1231
|
+
if (languageOutputs.length > 1) {
|
|
1232
|
+
onStatus(`Merging ${languageOutputs.length} language indexes...`);
|
|
1233
|
+
mergeScipFiles(languageOutputs.map((entry) => entry.scipPath), outputScip);
|
|
1079
1234
|
}
|
|
1080
1235
|
onStatus("Converting to SQLite...");
|
|
1081
1236
|
if (!existsSync5(outputScip)) {
|
|
@@ -1090,6 +1245,12 @@ Make sure ${binaryLabel} is installed and available on PATH.`,
|
|
|
1090
1245
|
} catch (err) {
|
|
1091
1246
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1092
1247
|
throw new Error(`Failed to convert SCIP index to SQLite: ${msg}`, { cause: err });
|
|
1248
|
+
} finally {
|
|
1249
|
+
for (const { scipPath } of languageOutputs) {
|
|
1250
|
+
if (scipPath !== outputScip) {
|
|
1251
|
+
rmSync(scipPath, { force: true });
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1093
1254
|
}
|
|
1094
1255
|
const durationMs = Date.now() - start;
|
|
1095
1256
|
onStatus(`Done in ${(durationMs / 1e3).toFixed(1)}s`);
|
|
@@ -1104,10 +1265,15 @@ function moveDefaultOutputIfNeeded(config, projectRoot, outputScip) {
|
|
|
1104
1265
|
renameSync(defaultOutputPath, outputScip);
|
|
1105
1266
|
}
|
|
1106
1267
|
}
|
|
1268
|
+
function tempScipPath(outputScip, language, index) {
|
|
1269
|
+
const extension = extname2(outputScip) || ".scip";
|
|
1270
|
+
const stem = basename(outputScip, extension);
|
|
1271
|
+
return join5(dirname2(outputScip), `${stem}.${index + 1}.${language}${extension}`);
|
|
1272
|
+
}
|
|
1107
1273
|
|
|
1108
1274
|
// src/config.ts
|
|
1109
|
-
import { readFileSync as
|
|
1110
|
-
import { join as join6, resolve } from "path";
|
|
1275
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync6, mkdirSync } from "fs";
|
|
1276
|
+
import { join as join6, resolve as resolve2 } from "path";
|
|
1111
1277
|
import { createHash } from "crypto";
|
|
1112
1278
|
import { homedir } from "os";
|
|
1113
1279
|
var CONFIG_FILENAME = ".scipquery.json";
|
|
@@ -1123,7 +1289,7 @@ function loadProjectConfig(projectRoot) {
|
|
|
1123
1289
|
return {};
|
|
1124
1290
|
}
|
|
1125
1291
|
try {
|
|
1126
|
-
const raw =
|
|
1292
|
+
const raw = readFileSync3(configPath, "utf-8");
|
|
1127
1293
|
return JSON.parse(raw);
|
|
1128
1294
|
} catch {
|
|
1129
1295
|
return {};
|
|
@@ -1138,10 +1304,10 @@ function resolveWatchConfig(config) {
|
|
|
1138
1304
|
function resolveCacheDir(projectRoot, config) {
|
|
1139
1305
|
const envOverride = process.env["SCIP_QUERY_CACHE_DIR"];
|
|
1140
1306
|
if (envOverride) return ensureDir(envOverride);
|
|
1141
|
-
if (config?.dbPath) return ensureDir(
|
|
1307
|
+
if (config?.dbPath) return ensureDir(resolve2(projectRoot, config.dbPath));
|
|
1142
1308
|
const xdgCache = process.env["XDG_CACHE_HOME"];
|
|
1143
1309
|
const cacheBase = xdgCache || join6(homedir(), ".cache");
|
|
1144
|
-
const projectHash = createHash("sha256").update(
|
|
1310
|
+
const projectHash = createHash("sha256").update(resolve2(projectRoot)).digest("hex").slice(0, 12);
|
|
1145
1311
|
const dir = join6(cacheBase, "scip-query", "projects", projectHash);
|
|
1146
1312
|
return ensureDir(dir);
|
|
1147
1313
|
}
|
|
@@ -1167,7 +1333,7 @@ function initProjectConfig(projectRoot, languages) {
|
|
|
1167
1333
|
cooldownMs: 6e4
|
|
1168
1334
|
}
|
|
1169
1335
|
};
|
|
1170
|
-
|
|
1336
|
+
writeFileSync2(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
1171
1337
|
return configPath;
|
|
1172
1338
|
}
|
|
1173
1339
|
function ensureDir(dir) {
|
|
@@ -1178,7 +1344,7 @@ function ensureDir(dir) {
|
|
|
1178
1344
|
// src/watch.ts
|
|
1179
1345
|
import { watch } from "fs";
|
|
1180
1346
|
import { existsSync as existsSync7, renameSync as renameSync2 } from "fs";
|
|
1181
|
-
import { join as join7, relative } from "path";
|
|
1347
|
+
import { join as join7, relative as relative2 } from "path";
|
|
1182
1348
|
import { fork } from "child_process";
|
|
1183
1349
|
import ignore2 from "ignore";
|
|
1184
1350
|
var Watcher = class {
|
|
@@ -1256,7 +1422,7 @@ var Watcher = class {
|
|
|
1256
1422
|
}
|
|
1257
1423
|
// ── Internal ─────────────────────────────────────────────
|
|
1258
1424
|
handleFileChange(filename) {
|
|
1259
|
-
const rel =
|
|
1425
|
+
const rel = relative2(this.projectRoot, join7(this.projectRoot, filename));
|
|
1260
1426
|
if (this.gitignoreFilter.isIgnored(rel)) return;
|
|
1261
1427
|
if (this.extraIgnore.ignores(rel)) return;
|
|
1262
1428
|
if (filename.endsWith("index.db") || filename.endsWith("index.scip") || filename.endsWith("index.db.tmp") || filename.endsWith(".scipquery.json")) {
|
|
@@ -1337,10 +1503,10 @@ var Watcher = class {
|
|
|
1337
1503
|
* Writes to index.db.tmp, then atomically renames to index.db.
|
|
1338
1504
|
*/
|
|
1339
1505
|
runReindex() {
|
|
1340
|
-
return new Promise((
|
|
1506
|
+
return new Promise((resolve4, reject) => {
|
|
1341
1507
|
const start = Date.now();
|
|
1342
1508
|
const tmpDb = this.indexPaths.dbPath + ".tmp";
|
|
1343
|
-
const tmpScip =
|
|
1509
|
+
const tmpScip = tempScipPath2(this.indexPaths.indexPath);
|
|
1344
1510
|
const child = fork(
|
|
1345
1511
|
new URL("./reindex-worker.js", import.meta.url).pathname,
|
|
1346
1512
|
[],
|
|
@@ -1365,7 +1531,7 @@ var Watcher = class {
|
|
|
1365
1531
|
if (existsSync7(tmpScip)) {
|
|
1366
1532
|
renameSync2(tmpScip, this.indexPaths.indexPath);
|
|
1367
1533
|
}
|
|
1368
|
-
|
|
1534
|
+
resolve4(Date.now() - start);
|
|
1369
1535
|
} catch (err) {
|
|
1370
1536
|
reject(new Error(`Atomic swap failed: ${err}`));
|
|
1371
1537
|
}
|
|
@@ -1381,7 +1547,7 @@ var Watcher = class {
|
|
|
1381
1547
|
this.onStatus(status);
|
|
1382
1548
|
}
|
|
1383
1549
|
};
|
|
1384
|
-
function
|
|
1550
|
+
function tempScipPath2(indexPath) {
|
|
1385
1551
|
return indexPath.endsWith(".scip") ? indexPath.slice(0, -".scip".length) + ".tmp.scip" : indexPath + ".tmp.scip";
|
|
1386
1552
|
}
|
|
1387
1553
|
|
|
@@ -1393,7 +1559,7 @@ import {
|
|
|
1393
1559
|
readlinkSync,
|
|
1394
1560
|
unlinkSync
|
|
1395
1561
|
} from "fs";
|
|
1396
|
-
import { join as join8, dirname as
|
|
1562
|
+
import { join as join8, dirname as dirname3, resolve as resolve3 } from "path";
|
|
1397
1563
|
import { homedir as homedir2, platform as platform3 } from "os";
|
|
1398
1564
|
import { fileURLToPath } from "url";
|
|
1399
1565
|
var IS_WINDOWS3 = platform3() === "win32";
|
|
@@ -1408,7 +1574,7 @@ function installSkills(opts = {}) {
|
|
|
1408
1574
|
const log = opts.quiet ? () => {
|
|
1409
1575
|
} : console.log;
|
|
1410
1576
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1411
|
-
const skillsSource =
|
|
1577
|
+
const skillsSource = resolve3(dirname3(thisFile), "..", "skills");
|
|
1412
1578
|
const targets = [
|
|
1413
1579
|
join8(homedir2(), ".claude", "skills"),
|
|
1414
1580
|
join8(homedir2(), ".codex", "skills")
|
|
@@ -1419,7 +1585,7 @@ function installSkills(opts = {}) {
|
|
|
1419
1585
|
alreadyLinked: []
|
|
1420
1586
|
};
|
|
1421
1587
|
for (const targetDir of targets) {
|
|
1422
|
-
const parentDir =
|
|
1588
|
+
const parentDir = dirname3(targetDir);
|
|
1423
1589
|
if (!existsSync8(parentDir)) {
|
|
1424
1590
|
continue;
|
|
1425
1591
|
}
|
|
@@ -1435,7 +1601,7 @@ function installSkills(opts = {}) {
|
|
|
1435
1601
|
if (existsSync8(target)) {
|
|
1436
1602
|
try {
|
|
1437
1603
|
const existing = readlinkSync(target);
|
|
1438
|
-
if (
|
|
1604
|
+
if (resolve3(existing) === resolve3(source)) {
|
|
1439
1605
|
result.alreadyLinked.push(`${toolName}/${skill}`);
|
|
1440
1606
|
log(` ok: ${skill} \u2192 ${toolName} (already linked)`);
|
|
1441
1607
|
continue;
|
|
@@ -1498,6 +1664,8 @@ export {
|
|
|
1498
1664
|
leafName,
|
|
1499
1665
|
loadProjectConfig,
|
|
1500
1666
|
members,
|
|
1667
|
+
mergeScipFiles,
|
|
1668
|
+
mergeScipIndexes,
|
|
1501
1669
|
methods,
|
|
1502
1670
|
outline,
|
|
1503
1671
|
parseSymbol,
|
package/dist/reindex-worker.js
CHANGED
|
@@ -11,8 +11,8 @@ import {
|
|
|
11
11
|
|
|
12
12
|
// src/reindex/index.ts
|
|
13
13
|
import { execFileSync } from "child_process";
|
|
14
|
-
import { existsSync as existsSync3, renameSync } from "fs";
|
|
15
|
-
import { join as join3 } from "path";
|
|
14
|
+
import { existsSync as existsSync3, renameSync, rmSync } from "fs";
|
|
15
|
+
import { basename, dirname, extname as extname2, join as join3 } from "path";
|
|
16
16
|
|
|
17
17
|
// src/reindex/detect.ts
|
|
18
18
|
import { existsSync, readdirSync } from "fs";
|
|
@@ -390,6 +390,128 @@ function resolveDotnetProject(projectRoot2, suffixes) {
|
|
|
390
390
|
return null;
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
+
// src/reindex/merge.ts
|
|
394
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
395
|
+
import { create } from "@bufbuild/protobuf";
|
|
396
|
+
import {
|
|
397
|
+
deserializeSCIP,
|
|
398
|
+
serializeSCIP,
|
|
399
|
+
DocumentSchema,
|
|
400
|
+
IndexSchema,
|
|
401
|
+
SymbolInformationSchema
|
|
402
|
+
} from "@c4312/scip";
|
|
403
|
+
function mergeScipIndexes(indexes) {
|
|
404
|
+
if (indexes.length === 0) {
|
|
405
|
+
throw new Error("Cannot merge zero SCIP indexes");
|
|
406
|
+
}
|
|
407
|
+
if (indexes.length === 1) {
|
|
408
|
+
return indexes[0];
|
|
409
|
+
}
|
|
410
|
+
const metadata = mergeMetadata(indexes);
|
|
411
|
+
const documents = mergeDocuments(indexes.flatMap((index) => index.documents ?? []));
|
|
412
|
+
const externalSymbols = mergeSymbolInfos(indexes.flatMap((index) => index.externalSymbols ?? []));
|
|
413
|
+
return create(IndexSchema, {
|
|
414
|
+
metadata,
|
|
415
|
+
documents,
|
|
416
|
+
externalSymbols
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
function mergeScipFiles(inputPaths, outputPath) {
|
|
420
|
+
if (inputPaths.length === 0) {
|
|
421
|
+
throw new Error("Cannot merge zero SCIP files");
|
|
422
|
+
}
|
|
423
|
+
const indexes = inputPaths.map((path) => deserializeSCIP(readFileSync(path)));
|
|
424
|
+
const merged = mergeScipIndexes(indexes);
|
|
425
|
+
writeFileSync(outputPath, Buffer.from(serializeSCIP(merged)));
|
|
426
|
+
return {
|
|
427
|
+
documentCount: merged.documents.length,
|
|
428
|
+
externalSymbolCount: merged.externalSymbols.length,
|
|
429
|
+
inputCount: inputPaths.length
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
function mergeMetadata(indexes) {
|
|
433
|
+
const first = indexes[0]?.metadata;
|
|
434
|
+
if (!first) {
|
|
435
|
+
return void 0;
|
|
436
|
+
}
|
|
437
|
+
const expectedProjectRoot = first.projectRoot;
|
|
438
|
+
for (const index of indexes.slice(1)) {
|
|
439
|
+
const actualProjectRoot = index.metadata?.projectRoot;
|
|
440
|
+
if (expectedProjectRoot && actualProjectRoot && actualProjectRoot !== expectedProjectRoot) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`Cannot merge SCIP indexes with different project roots: ${expectedProjectRoot} vs ${actualProjectRoot}`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return first;
|
|
447
|
+
}
|
|
448
|
+
function mergeDocuments(documents) {
|
|
449
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
450
|
+
for (const document of documents) {
|
|
451
|
+
const existing = byPath.get(document.relativePath);
|
|
452
|
+
if (!existing) {
|
|
453
|
+
byPath.set(document.relativePath, document);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
byPath.set(document.relativePath, create(DocumentSchema, {
|
|
457
|
+
language: existing.language || document.language,
|
|
458
|
+
relativePath: existing.relativePath || document.relativePath,
|
|
459
|
+
occurrences: [...existing.occurrences, ...document.occurrences],
|
|
460
|
+
symbols: mergeSymbolInfos([...existing.symbols, ...document.symbols]),
|
|
461
|
+
text: chooseText(existing.text, document.text),
|
|
462
|
+
positionEncoding: existing.positionEncoding || document.positionEncoding
|
|
463
|
+
}));
|
|
464
|
+
}
|
|
465
|
+
return [...byPath.values()];
|
|
466
|
+
}
|
|
467
|
+
function mergeSymbolInfos(symbols) {
|
|
468
|
+
const bySymbol = /* @__PURE__ */ new Map();
|
|
469
|
+
for (const symbol of symbols) {
|
|
470
|
+
const existing = bySymbol.get(symbol.symbol);
|
|
471
|
+
if (!existing) {
|
|
472
|
+
bySymbol.set(symbol.symbol, symbol);
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
bySymbol.set(symbol.symbol, create(SymbolInformationSchema, {
|
|
476
|
+
symbol: existing.symbol,
|
|
477
|
+
documentation: uniqueStrings([...existing.documentation, ...symbol.documentation]),
|
|
478
|
+
relationships: mergeRelationships([...existing.relationships, ...symbol.relationships]),
|
|
479
|
+
kind: existing.kind || symbol.kind,
|
|
480
|
+
displayName: existing.displayName || symbol.displayName,
|
|
481
|
+
enclosingSymbol: existing.enclosingSymbol || symbol.enclosingSymbol,
|
|
482
|
+
signatureDocumentation: existing.signatureDocumentation ?? symbol.signatureDocumentation
|
|
483
|
+
}));
|
|
484
|
+
}
|
|
485
|
+
return [...bySymbol.values()];
|
|
486
|
+
}
|
|
487
|
+
function mergeRelationships(relationships) {
|
|
488
|
+
const seen = /* @__PURE__ */ new Set();
|
|
489
|
+
const merged = [];
|
|
490
|
+
for (const relationship of relationships) {
|
|
491
|
+
const key = [
|
|
492
|
+
relationship.symbol,
|
|
493
|
+
relationship.isReference ? "1" : "0",
|
|
494
|
+
relationship.isImplementation ? "1" : "0",
|
|
495
|
+
relationship.isTypeDefinition ? "1" : "0",
|
|
496
|
+
relationship.isDefinition ? "1" : "0"
|
|
497
|
+
].join("|");
|
|
498
|
+
if (seen.has(key)) {
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
seen.add(key);
|
|
502
|
+
merged.push(relationship);
|
|
503
|
+
}
|
|
504
|
+
return merged;
|
|
505
|
+
}
|
|
506
|
+
function chooseText(left, right) {
|
|
507
|
+
if (!left) return right;
|
|
508
|
+
if (!right) return left;
|
|
509
|
+
return left.length >= right.length ? left : right;
|
|
510
|
+
}
|
|
511
|
+
function uniqueStrings(values) {
|
|
512
|
+
return [...new Set(values)];
|
|
513
|
+
}
|
|
514
|
+
|
|
393
515
|
// src/reindex/index.ts
|
|
394
516
|
async function reindex(opts) {
|
|
395
517
|
const {
|
|
@@ -425,7 +547,11 @@ async function reindex(opts) {
|
|
|
425
547
|
...process.env,
|
|
426
548
|
NODE_OPTIONS: `--max-old-space-size=${maxHeapMb}`
|
|
427
549
|
};
|
|
428
|
-
|
|
550
|
+
const languageOutputs = languages2.map((language, index) => ({
|
|
551
|
+
language,
|
|
552
|
+
scipPath: languages2.length > 1 ? tempScipPath(outputScip2, language, index) : outputScip2
|
|
553
|
+
}));
|
|
554
|
+
for (const { language: lang, scipPath } of languageOutputs) {
|
|
429
555
|
const config = getIndexerConfig(lang);
|
|
430
556
|
const binaryLabel = describeIndexerBinary(config);
|
|
431
557
|
const projectLocalBinary = resolveProjectLocalIndexerBinary(config, projectRoot2);
|
|
@@ -455,7 +581,7 @@ async function reindex(opts) {
|
|
|
455
581
|
const indexerEnv = getIndexerExecutionEnv(config, env, resolvedBinary);
|
|
456
582
|
const { binary, args } = config.indexArgs({
|
|
457
583
|
projectRoot: projectRoot2,
|
|
458
|
-
outputPath:
|
|
584
|
+
outputPath: scipPath,
|
|
459
585
|
pnpmWorkspaces: opts.pnpmWorkspaces,
|
|
460
586
|
indexerBinary: resolvedBinary
|
|
461
587
|
});
|
|
@@ -474,7 +600,11 @@ Make sure ${binaryLabel} is installed and available on PATH.`,
|
|
|
474
600
|
{ cause: err }
|
|
475
601
|
);
|
|
476
602
|
}
|
|
477
|
-
moveDefaultOutputIfNeeded(config, projectRoot2,
|
|
603
|
+
moveDefaultOutputIfNeeded(config, projectRoot2, scipPath);
|
|
604
|
+
}
|
|
605
|
+
if (languageOutputs.length > 1) {
|
|
606
|
+
onStatus(`Merging ${languageOutputs.length} language indexes...`);
|
|
607
|
+
mergeScipFiles(languageOutputs.map((entry) => entry.scipPath), outputScip2);
|
|
478
608
|
}
|
|
479
609
|
onStatus("Converting to SQLite...");
|
|
480
610
|
if (!existsSync3(outputScip2)) {
|
|
@@ -489,6 +619,12 @@ Make sure ${binaryLabel} is installed and available on PATH.`,
|
|
|
489
619
|
} catch (err) {
|
|
490
620
|
const msg = err instanceof Error ? err.message : String(err);
|
|
491
621
|
throw new Error(`Failed to convert SCIP index to SQLite: ${msg}`, { cause: err });
|
|
622
|
+
} finally {
|
|
623
|
+
for (const { scipPath } of languageOutputs) {
|
|
624
|
+
if (scipPath !== outputScip2) {
|
|
625
|
+
rmSync(scipPath, { force: true });
|
|
626
|
+
}
|
|
627
|
+
}
|
|
492
628
|
}
|
|
493
629
|
const durationMs = Date.now() - start;
|
|
494
630
|
onStatus(`Done in ${(durationMs / 1e3).toFixed(1)}s`);
|
|
@@ -503,6 +639,11 @@ function moveDefaultOutputIfNeeded(config, projectRoot2, outputScip2) {
|
|
|
503
639
|
renameSync(defaultOutputPath, outputScip2);
|
|
504
640
|
}
|
|
505
641
|
}
|
|
642
|
+
function tempScipPath(outputScip2, language, index) {
|
|
643
|
+
const extension = extname2(outputScip2) || ".scip";
|
|
644
|
+
const stem = basename(outputScip2, extension);
|
|
645
|
+
return join3(dirname(outputScip2), `${stem}.${index + 1}.${language}${extension}`);
|
|
646
|
+
}
|
|
506
647
|
|
|
507
648
|
// src/reindex-worker.ts
|
|
508
649
|
var projectRoot = process.env["SCIP_REINDEX_PROJECT_ROOT"];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scip-query",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Language-agnostic code intelligence CLI powered by SCIP indexes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -60,6 +60,8 @@
|
|
|
60
60
|
"node": ">=18.0.0"
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
+
"@bufbuild/protobuf": "^2.11.0",
|
|
64
|
+
"@c4312/scip": "^0.1.0",
|
|
63
65
|
"better-sqlite3": "^11.7.0",
|
|
64
66
|
"commander": "^13.1.0",
|
|
65
67
|
"ignore": "^7.0.3"
|