scip-query 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -43
- package/dist/chunk-2UELLEBI.js +1 -0
- package/dist/chunk-34JPTNRN.js +601 -0
- package/dist/{chunk-NDSQYIWT.js → chunk-3566TKJ5.js} +3 -3
- package/dist/{chunk-LB7OS35Q.js → chunk-4ACRRQC4.js} +8 -4
- package/dist/{chunk-3E2X7RIE.js → chunk-4BQFSNFI.js} +10 -6
- package/dist/{chunk-BP2ATLK2.js → chunk-6QSHLFSL.js} +4 -4
- package/dist/{chunk-5FGUEU7N.js → chunk-6WVR5K46.js} +18 -10
- package/dist/{chunk-XFXDXEUN.js → chunk-74RFWB5T.js} +2 -2
- package/dist/{chunk-MBVNHJVN.js → chunk-75RQSBTK.js} +2 -2
- package/dist/{chunk-YZAA4LYG.js → chunk-7HK5ZLOE.js} +30 -48
- package/dist/{chunk-T6ARFSBZ.js → chunk-7JFZSOJ7.js} +7 -7
- package/dist/{chunk-6SXADWLW.js → chunk-AKMBBKWV.js} +2 -2
- package/dist/{chunk-ZJRYBOEE.js → chunk-AMNISGYR.js} +5 -5
- package/dist/{chunk-CM454WL3.js → chunk-BFLULBEU.js} +3 -3
- package/dist/{chunk-Z73NYSBZ.js → chunk-CU62ZDHI.js} +2 -2
- package/dist/{chunk-TBP6BICL.js → chunk-DY4AFG2W.js} +13 -11
- package/dist/{chunk-2QZ23IBN.js → chunk-F7XU27LU.js} +4 -4
- package/dist/{chunk-KCBMVQL5.js → chunk-GPJVPT3U.js} +2 -2
- package/dist/{chunk-NUZ4OMU3.js → chunk-GU2H5QRN.js} +2 -2
- package/dist/{chunk-TSPZOMHC.js → chunk-H6WCPKCX.js} +6 -3
- package/dist/{chunk-KVSW5KYP.js → chunk-HDSRORNV.js} +4 -4
- package/dist/{chunk-LUSIFBXO.js → chunk-HMYJJ3HY.js} +9 -6
- package/dist/chunk-IJKLB2JW.js +69 -0
- package/dist/{chunk-6NBLIDF4.js → chunk-ITZ3DDOG.js} +2 -2
- package/dist/{chunk-GTILYBH6.js → chunk-IXPHLF6K.js} +6 -6
- package/dist/{chunk-BFSCMC22.js → chunk-KBOQX573.js} +3 -3
- package/dist/{chunk-FUHJCHS4.js → chunk-LLMPAG56.js} +95 -32
- package/dist/{chunk-FFSWWE5O.js → chunk-LTJC5ZQL.js} +3 -3
- package/dist/{chunk-LAWMH22O.js → chunk-M3NPW3FC.js} +2 -2
- package/dist/{chunk-VRUJH4BO.js → chunk-M4QGEKKD.js} +6 -28
- package/dist/{chunk-7OZPA5OO.js → chunk-MVH45PYK.js} +21 -41
- package/dist/chunk-N4C3H7LH.js +37 -0
- package/dist/chunk-NG5F43OU.js +200 -0
- package/dist/{chunk-6VJ6Q7IE.js → chunk-NVIIM34O.js} +4 -4
- package/dist/{chunk-GJFURBEW.js → chunk-ORINICIZ.js} +4 -4
- package/dist/{chunk-TDNNOR6D.js → chunk-PMJKOXOT.js} +7 -7
- package/dist/{chunk-QOV2R2WT.js → chunk-QIXNAB5K.js} +42 -2
- package/dist/{chunk-JKP5GH6T.js → chunk-R2I3M5B4.js} +2 -2
- package/dist/{chunk-36OMT7ZJ.js → chunk-R56FJU3E.js} +35 -14
- package/dist/{chunk-VZ7AMAFL.js → chunk-RFMT7UAZ.js} +3 -3
- package/dist/{chunk-SEFSL2GF.js → chunk-TOIEB3LG.js} +2 -2
- package/dist/chunk-VO4QI3LS.js +84 -0
- package/dist/{chunk-EMDQWNYR.js → chunk-WVK7AASK.js} +8 -8
- package/dist/{chunk-5WTJAXY2.js → chunk-Y3M323OX.js} +2 -2
- package/dist/{chunk-DCKMSTJ4.js → chunk-Y4JFVQ7C.js} +2 -2
- package/dist/{chunk-UNTPVD36.js → chunk-YAFWL3RA.js} +4 -4
- package/dist/{chunk-FGXRVW7G.js → chunk-YZ6L7GFO.js} +2 -2
- package/dist/cli.js +1401 -717
- package/dist/{db-BxaevAyc.d.ts → db-BHYam4BK.d.ts} +7 -19
- package/dist/index.d.ts +15 -15
- package/dist/index.js +263 -234
- package/dist/postinstall.js +5 -76
- package/dist/queries/affected.d.ts +1 -1
- package/dist/queries/affected.js +3 -3
- package/dist/queries/bottlenecks.d.ts +1 -1
- package/dist/queries/bottlenecks.js +2 -2
- package/dist/queries/by-kind.d.ts +1 -1
- package/dist/queries/by-kind.js +2 -2
- package/dist/queries/call-graph.d.ts +1 -1
- package/dist/queries/call-graph.js +3 -3
- package/dist/queries/change-surface.d.ts +2 -2
- package/dist/queries/change-surface.js +2 -3
- package/dist/queries/code.d.ts +1 -1
- package/dist/queries/code.js +3 -3
- package/dist/queries/complexity-hotspots.d.ts +1 -1
- package/dist/queries/complexity-hotspots.js +3 -3
- package/dist/queries/complexity.d.ts +1 -1
- package/dist/queries/complexity.js +3 -3
- package/dist/queries/convergence.d.ts +1 -1
- package/dist/queries/convergence.js +3 -3
- package/dist/queries/coupling.d.ts +1 -1
- package/dist/queries/coupling.js +1 -1
- package/dist/queries/cycles.d.ts +1 -1
- package/dist/queries/cycles.js +3 -2
- package/dist/queries/dataflow.d.ts +1 -1
- package/dist/queries/dataflow.js +3 -3
- package/dist/queries/dead.d.ts +1 -1
- package/dist/queries/dead.js +4 -3
- package/dist/queries/deep-chains.d.ts +1 -1
- package/dist/queries/deep-chains.js +3 -2
- package/dist/queries/deps.d.ts +1 -1
- package/dist/queries/diff-impact.d.ts +2 -2
- package/dist/queries/diff-impact.js +2 -3
- package/dist/queries/doc-coverage.d.ts +1 -1
- package/dist/queries/doc-coverage.js +2 -2
- package/dist/queries/drift.d.ts +1 -1
- package/dist/queries/drift.js +3 -2
- package/dist/queries/extract-candidates.d.ts +1 -1
- package/dist/queries/extract-candidates.js +3 -3
- package/dist/queries/fan.d.ts +1 -1
- package/dist/queries/fan.js +2 -2
- package/dist/queries/files.d.ts +1 -1
- package/dist/queries/health.d.ts +1 -1
- package/dist/queries/health.js +15 -15
- package/dist/queries/hierarchy.d.ts +1 -1
- package/dist/queries/hierarchy.js +3 -2
- package/dist/queries/hotspots.d.ts +1 -1
- package/dist/queries/hotspots.js +2 -2
- package/dist/queries/imports.d.ts +1 -1
- package/dist/queries/imports.js +3 -2
- package/dist/queries/index.d.ts +1 -2
- package/dist/queries/index.js +46 -51
- package/dist/queries/isolated.d.ts +1 -1
- package/dist/queries/isolated.js +4 -3
- package/dist/queries/members.d.ts +2 -2
- package/dist/queries/members.js +3 -2
- package/dist/queries/methods.d.ts +1 -1
- package/dist/queries/methods.js +2 -2
- package/dist/queries/outline.d.ts +1 -1
- package/dist/queries/outline.js +2 -2
- package/dist/queries/passthrough-candidates.d.ts +1 -1
- package/dist/queries/passthrough-candidates.js +3 -3
- package/dist/queries/redundant-reexports.d.ts +1 -1
- package/dist/queries/redundant-reexports.js +4 -2
- package/dist/queries/refs.d.ts +1 -1
- package/dist/queries/refs.js +1 -1
- package/dist/queries/similar-chains.d.ts +1 -1
- package/dist/queries/similar-chains.js +3 -2
- package/dist/queries/similar-files.d.ts +1 -1
- package/dist/queries/similar-files.js +3 -2
- package/dist/queries/similar-signatures.d.ts +1 -1
- package/dist/queries/similar-signatures.js +2 -2
- package/dist/queries/similar.d.ts +1 -1
- package/dist/queries/similar.js +3 -3
- package/dist/queries/slice.d.ts +1 -1
- package/dist/queries/slice.js +3 -3
- package/dist/queries/stale-abstractions.d.ts +1 -1
- package/dist/queries/stale-abstractions.js +3 -3
- package/dist/queries/stats.d.ts +1 -1
- package/dist/queries/stats.js +1 -1
- package/dist/queries/surface.d.ts +1 -1
- package/dist/queries/surface.js +2 -2
- package/dist/queries/symbols.d.ts +1 -1
- package/dist/queries/symbols.js +2 -2
- package/dist/queries/system.d.ts +1 -1
- package/dist/queries/system.js +2 -2
- package/dist/queries/trace.d.ts +1 -1
- package/dist/queries/trace.js +3 -1
- package/dist/queries/wrapper-candidates.d.ts +1 -1
- package/dist/queries/wrapper-candidates.js +3 -3
- package/dist/reindex-worker.js +24 -12
- package/package.json +6 -1
- package/IMPROVEMENTS.md +0 -143
- package/PLAN.md +0 -320
- package/dist/chunk-2QZ23IBN.js.map +0 -1
- package/dist/chunk-36OMT7ZJ.js.map +0 -1
- package/dist/chunk-3E2X7RIE.js.map +0 -1
- package/dist/chunk-3UOUTZQT.js +0 -45
- package/dist/chunk-3UOUTZQT.js.map +0 -1
- package/dist/chunk-3ZZJVBIO.js +0 -88
- package/dist/chunk-3ZZJVBIO.js.map +0 -1
- package/dist/chunk-4TYLS5XX.js.map +0 -1
- package/dist/chunk-5FGUEU7N.js.map +0 -1
- package/dist/chunk-5WTJAXY2.js.map +0 -1
- package/dist/chunk-6NBLIDF4.js.map +0 -1
- package/dist/chunk-6SXADWLW.js.map +0 -1
- package/dist/chunk-6VJ6Q7IE.js.map +0 -1
- package/dist/chunk-7OZPA5OO.js.map +0 -1
- package/dist/chunk-BEPIEVLR.js +0 -76
- package/dist/chunk-BEPIEVLR.js.map +0 -1
- package/dist/chunk-BFSCMC22.js.map +0 -1
- package/dist/chunk-BP2ATLK2.js.map +0 -1
- package/dist/chunk-CM454WL3.js.map +0 -1
- package/dist/chunk-DCKMSTJ4.js.map +0 -1
- package/dist/chunk-DEZKCZXD.js +0 -40
- package/dist/chunk-DEZKCZXD.js.map +0 -1
- package/dist/chunk-DVWGWHFW.js +0 -99
- package/dist/chunk-DVWGWHFW.js.map +0 -1
- package/dist/chunk-EMDQWNYR.js.map +0 -1
- package/dist/chunk-FFSWWE5O.js.map +0 -1
- package/dist/chunk-FGXRVW7G.js.map +0 -1
- package/dist/chunk-FUHJCHS4.js.map +0 -1
- package/dist/chunk-GJFURBEW.js.map +0 -1
- package/dist/chunk-GTILYBH6.js.map +0 -1
- package/dist/chunk-JJP7KQND.js +0 -1
- package/dist/chunk-JJP7KQND.js.map +0 -1
- package/dist/chunk-JKP5GH6T.js.map +0 -1
- package/dist/chunk-KCBMVQL5.js.map +0 -1
- package/dist/chunk-KVSW5KYP.js.map +0 -1
- package/dist/chunk-LAWMH22O.js.map +0 -1
- package/dist/chunk-LB7OS35Q.js.map +0 -1
- package/dist/chunk-LUSIFBXO.js.map +0 -1
- package/dist/chunk-MBVNHJVN.js.map +0 -1
- package/dist/chunk-MGNMHKX3.js.map +0 -1
- package/dist/chunk-N5KEREIA.js.map +0 -1
- package/dist/chunk-NDSQYIWT.js.map +0 -1
- package/dist/chunk-NUZ4OMU3.js.map +0 -1
- package/dist/chunk-QOV2R2WT.js.map +0 -1
- package/dist/chunk-SEFSL2GF.js.map +0 -1
- package/dist/chunk-T6ARFSBZ.js.map +0 -1
- package/dist/chunk-TBP6BICL.js.map +0 -1
- package/dist/chunk-TDNNOR6D.js.map +0 -1
- package/dist/chunk-TSPZOMHC.js.map +0 -1
- package/dist/chunk-UNTPVD36.js.map +0 -1
- package/dist/chunk-VRUJH4BO.js.map +0 -1
- package/dist/chunk-VZ7AMAFL.js.map +0 -1
- package/dist/chunk-XFXDXEUN.js.map +0 -1
- package/dist/chunk-YZAA4LYG.js.map +0 -1
- package/dist/chunk-Z73NYSBZ.js.map +0 -1
- package/dist/chunk-ZJRYBOEE.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/postinstall.js.map +0 -1
- package/dist/queries/affected.js.map +0 -1
- package/dist/queries/bottlenecks.js.map +0 -1
- package/dist/queries/by-kind.js.map +0 -1
- package/dist/queries/call-graph.js.map +0 -1
- package/dist/queries/change-surface.js.map +0 -1
- package/dist/queries/clean-signature.js.map +0 -1
- package/dist/queries/code.js.map +0 -1
- package/dist/queries/complexity-hotspots.js.map +0 -1
- package/dist/queries/complexity.js.map +0 -1
- package/dist/queries/convergence.js.map +0 -1
- package/dist/queries/coupling.js.map +0 -1
- package/dist/queries/cycles.js.map +0 -1
- package/dist/queries/dataflow.js.map +0 -1
- package/dist/queries/dead.js.map +0 -1
- package/dist/queries/deep-chains.js.map +0 -1
- package/dist/queries/deps.js.map +0 -1
- package/dist/queries/diff-impact.js.map +0 -1
- package/dist/queries/doc-coverage.js.map +0 -1
- package/dist/queries/drift.js.map +0 -1
- package/dist/queries/extract-candidates.js.map +0 -1
- package/dist/queries/fan.js.map +0 -1
- package/dist/queries/files.js.map +0 -1
- package/dist/queries/health.js.map +0 -1
- package/dist/queries/hierarchy.js.map +0 -1
- package/dist/queries/hotspots.js.map +0 -1
- package/dist/queries/imports.js.map +0 -1
- package/dist/queries/index.js.map +0 -1
- package/dist/queries/isolated.js.map +0 -1
- package/dist/queries/members.js.map +0 -1
- package/dist/queries/methods.js.map +0 -1
- package/dist/queries/outline.js.map +0 -1
- package/dist/queries/passthrough-candidates.js.map +0 -1
- package/dist/queries/redundant-reexports.js.map +0 -1
- package/dist/queries/refs.js.map +0 -1
- package/dist/queries/similar-chains.js.map +0 -1
- package/dist/queries/similar-files.js.map +0 -1
- package/dist/queries/similar-signatures.js.map +0 -1
- package/dist/queries/similar.js.map +0 -1
- package/dist/queries/slice.js.map +0 -1
- package/dist/queries/stale-abstractions.js.map +0 -1
- package/dist/queries/stats.js.map +0 -1
- package/dist/queries/surface.js.map +0 -1
- package/dist/queries/symbols.js.map +0 -1
- package/dist/queries/system.js.map +0 -1
- package/dist/queries/test-coverage.d.ts +0 -22
- package/dist/queries/test-coverage.js +0 -11
- package/dist/queries/test-coverage.js.map +0 -1
- package/dist/queries/trace.js.map +0 -1
- package/dist/queries/wrapper-candidates.js.map +0 -1
- package/dist/reindex-worker.js.map +0 -1
- package/docs/AGENT_GUIDE.md +0 -359
- package/reports/debloat/2026-04-10-scip-query-self-audit.md +0 -161
- package/src/cli.ts +0 -1480
- package/src/config.ts +0 -117
- package/src/db.ts +0 -127
- package/src/gitignore-filter.ts +0 -143
- package/src/index.ts +0 -11
- package/src/postinstall.ts +0 -8
- package/src/queries/affected.ts +0 -86
- package/src/queries/bottlenecks.ts +0 -67
- package/src/queries/by-kind.ts +0 -204
- package/src/queries/call-graph.ts +0 -66
- package/src/queries/change-surface.ts +0 -110
- package/src/queries/clean-signature.ts +0 -22
- package/src/queries/code.ts +0 -101
- package/src/queries/complexity-hotspots.ts +0 -119
- package/src/queries/complexity.ts +0 -152
- package/src/queries/convergence.ts +0 -82
- package/src/queries/coupling.ts +0 -99
- package/src/queries/cycles.ts +0 -78
- package/src/queries/dataflow.ts +0 -128
- package/src/queries/dead.ts +0 -122
- package/src/queries/deep-chains.ts +0 -59
- package/src/queries/deps.ts +0 -46
- package/src/queries/diff-impact.ts +0 -204
- package/src/queries/doc-coverage.ts +0 -86
- package/src/queries/drift.ts +0 -224
- package/src/queries/extract-candidates.ts +0 -167
- package/src/queries/fan.ts +0 -148
- package/src/queries/files.ts +0 -16
- package/src/queries/health.ts +0 -324
- package/src/queries/hierarchy.ts +0 -49
- package/src/queries/hotspots.ts +0 -53
- package/src/queries/imports.ts +0 -95
- package/src/queries/index.ts +0 -45
- package/src/queries/isolated.ts +0 -67
- package/src/queries/members.ts +0 -54
- package/src/queries/methods.ts +0 -27
- package/src/queries/outline.ts +0 -52
- package/src/queries/passthrough-candidates.ts +0 -94
- package/src/queries/redundant-reexports.ts +0 -170
- package/src/queries/refs.ts +0 -27
- package/src/queries/similar-chains.ts +0 -314
- package/src/queries/similar-files.ts +0 -140
- package/src/queries/similar-signatures.ts +0 -151
- package/src/queries/similar.ts +0 -305
- package/src/queries/slice.ts +0 -154
- package/src/queries/stale-abstractions.ts +0 -82
- package/src/queries/stats.ts +0 -22
- package/src/queries/surface.ts +0 -34
- package/src/queries/symbols.ts +0 -39
- package/src/queries/system.ts +0 -86
- package/src/queries/test-coverage.ts +0 -106
- package/src/queries/trace.ts +0 -55
- package/src/queries/wrapper-candidates.ts +0 -112
- package/src/query-support.ts +0 -226
- package/src/reindex/detect.ts +0 -58
- package/src/reindex/index.ts +0 -153
- package/src/reindex/indexers.ts +0 -220
- package/src/reindex/install.ts +0 -125
- package/src/reindex-worker.ts +0 -35
- package/src/setup.ts +0 -202
- package/src/symbol-parser.ts +0 -278
- package/src/types.ts +0 -654
- package/src/watch.ts +0 -274
- package/tests/gitignore-filter.test.ts +0 -48
- package/tests/queries.test.ts +0 -300
- package/tests/symbol-parser.test.ts +0 -157
- package/tsconfig.json +0 -20
- package/tsup.config.ts +0 -40
- package/vitest.config.ts +0 -7
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { program } from "commander";
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { join as
|
|
5
|
+
import { existsSync as existsSync8 } from "fs";
|
|
6
|
+
import { join as join10 } from "path";
|
|
7
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
7
8
|
|
|
8
9
|
// src/db.ts
|
|
9
10
|
import Database from "better-sqlite3";
|
|
@@ -272,10 +273,192 @@ function ensureDir(dir) {
|
|
|
272
273
|
}
|
|
273
274
|
|
|
274
275
|
// src/reindex/index.ts
|
|
275
|
-
import { execFileSync as
|
|
276
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
276
277
|
import { existsSync as existsSync4 } from "fs";
|
|
277
278
|
import { join as join4 } from "path";
|
|
278
279
|
|
|
280
|
+
// src/scip-cli.ts
|
|
281
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
282
|
+
import { platform as platform2, arch } from "os";
|
|
283
|
+
|
|
284
|
+
// src/reindex/install.ts
|
|
285
|
+
import { execFileSync } from "child_process";
|
|
286
|
+
import { platform } from "os";
|
|
287
|
+
var IS_WINDOWS = platform() === "win32";
|
|
288
|
+
function isBinaryAvailable(name) {
|
|
289
|
+
const cmd = IS_WINDOWS ? "where" : "which";
|
|
290
|
+
try {
|
|
291
|
+
execFileSync(cmd, [name], { stdio: "pipe" });
|
|
292
|
+
return true;
|
|
293
|
+
} catch {
|
|
294
|
+
return false;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function getBinaryCandidates(config) {
|
|
298
|
+
return [config.indexerBinary, ...config.binaryAliases ?? []];
|
|
299
|
+
}
|
|
300
|
+
function describeIndexerBinary(config) {
|
|
301
|
+
const candidates = getBinaryCandidates(config);
|
|
302
|
+
return candidates.length === 1 ? candidates[0] : candidates.join(" or ");
|
|
303
|
+
}
|
|
304
|
+
function resolveIndexerBinary(config) {
|
|
305
|
+
for (const candidate of getBinaryCandidates(config)) {
|
|
306
|
+
if (isBinaryAvailable(candidate)) {
|
|
307
|
+
return candidate;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
function isIndexerInstalled(config) {
|
|
313
|
+
return resolveIndexerBinary(config) !== null;
|
|
314
|
+
}
|
|
315
|
+
function tryInstallIndexer(config, onStatus) {
|
|
316
|
+
const methods2 = config.installMethods;
|
|
317
|
+
const binaryLabel = describeIndexerBinary(config);
|
|
318
|
+
if (!methods2?.length) {
|
|
319
|
+
onStatus(`No auto-install method available for ${binaryLabel}.`);
|
|
320
|
+
if (config.installUrl) {
|
|
321
|
+
onStatus(`Install manually from: ${config.installUrl}`);
|
|
322
|
+
}
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
for (const method of methods2) {
|
|
326
|
+
if (!isBinaryAvailable(method.prerequisite)) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
onStatus(`Installing ${binaryLabel} via ${method.label}...`);
|
|
330
|
+
try {
|
|
331
|
+
execFileSync(method.binary, method.args, {
|
|
332
|
+
stdio: "inherit",
|
|
333
|
+
timeout: 3e5,
|
|
334
|
+
env: process.env
|
|
335
|
+
});
|
|
336
|
+
const resolvedBinary = resolveIndexerBinary(config);
|
|
337
|
+
if (resolvedBinary) {
|
|
338
|
+
const resolutionNote = resolvedBinary === config.indexerBinary ? "" : ` (using ${resolvedBinary})`;
|
|
339
|
+
onStatus(`Successfully installed ${binaryLabel} via ${method.label}${resolutionNote}`);
|
|
340
|
+
return true;
|
|
341
|
+
}
|
|
342
|
+
onStatus(`${method.label} command completed but ${binaryLabel} was not found on PATH`);
|
|
343
|
+
} catch (err) {
|
|
344
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
345
|
+
onStatus(`${method.label} install failed: ${msg}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
onStatus(`Could not auto-install ${binaryLabel}.`);
|
|
349
|
+
if (config.installUrl) {
|
|
350
|
+
onStatus(`Install manually from: ${config.installUrl}`);
|
|
351
|
+
}
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// src/scip-cli.ts
|
|
356
|
+
var IS_WINDOWS2 = platform2() === "win32";
|
|
357
|
+
var SCIP_VERSION = "v0.7.0";
|
|
358
|
+
function isScipInstalled() {
|
|
359
|
+
try {
|
|
360
|
+
const cmd = IS_WINDOWS2 ? "where" : "which";
|
|
361
|
+
execFileSync2(cmd, ["scip"], { stdio: "pipe" });
|
|
362
|
+
return true;
|
|
363
|
+
} catch {
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
function getScipDownloadUrl() {
|
|
368
|
+
const os = platform2();
|
|
369
|
+
const cpu = arch();
|
|
370
|
+
let osName;
|
|
371
|
+
let archName;
|
|
372
|
+
let ext;
|
|
373
|
+
switch (os) {
|
|
374
|
+
case "darwin":
|
|
375
|
+
osName = "darwin";
|
|
376
|
+
ext = "tar.gz";
|
|
377
|
+
break;
|
|
378
|
+
case "linux":
|
|
379
|
+
osName = "linux";
|
|
380
|
+
ext = "tar.gz";
|
|
381
|
+
break;
|
|
382
|
+
case "win32":
|
|
383
|
+
osName = "windows";
|
|
384
|
+
ext = "zip";
|
|
385
|
+
break;
|
|
386
|
+
default:
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
switch (cpu) {
|
|
390
|
+
case "arm64":
|
|
391
|
+
archName = "arm64";
|
|
392
|
+
break;
|
|
393
|
+
case "x64":
|
|
394
|
+
archName = "amd64";
|
|
395
|
+
break;
|
|
396
|
+
default:
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const filename = `scip-${osName}-${archName}.${ext}`;
|
|
400
|
+
const url = `https://github.com/sourcegraph/scip/releases/download/${SCIP_VERSION}/${filename}`;
|
|
401
|
+
return { url, filename };
|
|
402
|
+
}
|
|
403
|
+
function printScipInstallInstructions() {
|
|
404
|
+
const download = getScipDownloadUrl();
|
|
405
|
+
console.log("\nThe `scip` CLI is required but not found on PATH.\n");
|
|
406
|
+
if (platform2() === "darwin") {
|
|
407
|
+
console.log("Install via Homebrew:");
|
|
408
|
+
console.log(" brew install sourcegraph/scip/scip\n");
|
|
409
|
+
console.log("Or download manually:");
|
|
410
|
+
} else {
|
|
411
|
+
console.log("Download from:");
|
|
412
|
+
}
|
|
413
|
+
if (download) {
|
|
414
|
+
console.log(` ${download.url}
|
|
415
|
+
`);
|
|
416
|
+
} else {
|
|
417
|
+
console.log(` https://github.com/sourcegraph/scip/releases/tag/${SCIP_VERSION}
|
|
418
|
+
`);
|
|
419
|
+
}
|
|
420
|
+
console.log("After installing, ensure `scip` is on your PATH and run `scip-query reindex`.");
|
|
421
|
+
}
|
|
422
|
+
function tryInstallScipCli(onStatus) {
|
|
423
|
+
if (platform2() === "darwin" && isBinaryAvailable("brew")) {
|
|
424
|
+
onStatus("Installing scip CLI via Homebrew...");
|
|
425
|
+
try {
|
|
426
|
+
execFileSync2("brew", ["install", "sourcegraph/scip/scip"], {
|
|
427
|
+
stdio: "inherit",
|
|
428
|
+
timeout: 3e5,
|
|
429
|
+
env: process.env
|
|
430
|
+
});
|
|
431
|
+
if (isBinaryAvailable("scip")) {
|
|
432
|
+
onStatus("Successfully installed scip CLI via Homebrew");
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
} catch (err) {
|
|
436
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
437
|
+
onStatus(`Homebrew install failed: ${msg}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (isBinaryAvailable("go")) {
|
|
441
|
+
onStatus("Installing scip CLI via go install...");
|
|
442
|
+
try {
|
|
443
|
+
execFileSync2("go", ["install", "github.com/sourcegraph/scip/cmd/scip@latest"], {
|
|
444
|
+
stdio: "inherit",
|
|
445
|
+
timeout: 3e5,
|
|
446
|
+
env: process.env
|
|
447
|
+
});
|
|
448
|
+
if (isBinaryAvailable("scip")) {
|
|
449
|
+
onStatus("Successfully installed scip CLI via go install");
|
|
450
|
+
return true;
|
|
451
|
+
}
|
|
452
|
+
} catch (err) {
|
|
453
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
454
|
+
onStatus(`go install failed: ${msg}`);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
onStatus("Could not auto-install scip CLI.");
|
|
458
|
+
onStatus("Install manually from: https://github.com/sourcegraph/scip/releases");
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
|
|
279
462
|
// src/reindex/detect.ts
|
|
280
463
|
import { existsSync as existsSync3 } from "fs";
|
|
281
464
|
import { join as join3 } from "path";
|
|
@@ -406,9 +589,10 @@ var INDEXER_CONFIGS = {
|
|
|
406
589
|
python: {
|
|
407
590
|
language: "python",
|
|
408
591
|
indexerBinary: "scip-python",
|
|
592
|
+
binaryAliases: ["scip-python-plus"],
|
|
409
593
|
checkCommand: "scip-python --version",
|
|
410
|
-
indexArgs: ({ outputPath }) => ({
|
|
411
|
-
binary:
|
|
594
|
+
indexArgs: ({ outputPath, indexerBinary }) => ({
|
|
595
|
+
binary: indexerBinary,
|
|
412
596
|
args: ["index", "--output", outputPath, "--project-name", "project"]
|
|
413
597
|
}),
|
|
414
598
|
markerFiles: ["pyproject.toml", "setup.py"],
|
|
@@ -514,98 +698,6 @@ function getIndexerConfig(language) {
|
|
|
514
698
|
return INDEXER_CONFIGS[language];
|
|
515
699
|
}
|
|
516
700
|
|
|
517
|
-
// src/reindex/install.ts
|
|
518
|
-
import { execFileSync } from "child_process";
|
|
519
|
-
import { platform } from "os";
|
|
520
|
-
var IS_WINDOWS = platform() === "win32";
|
|
521
|
-
function isBinaryAvailable(name) {
|
|
522
|
-
const cmd = IS_WINDOWS ? "where" : "which";
|
|
523
|
-
try {
|
|
524
|
-
execFileSync(cmd, [name], { stdio: "pipe" });
|
|
525
|
-
return true;
|
|
526
|
-
} catch {
|
|
527
|
-
return false;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
function isIndexerInstalled(config) {
|
|
531
|
-
return isBinaryAvailable(config.indexerBinary);
|
|
532
|
-
}
|
|
533
|
-
function tryInstallIndexer(config, onStatus) {
|
|
534
|
-
const methods2 = config.installMethods;
|
|
535
|
-
if (!methods2?.length) {
|
|
536
|
-
onStatus(`No auto-install method available for ${config.indexerBinary}.`);
|
|
537
|
-
if (config.installUrl) {
|
|
538
|
-
onStatus(`Install manually from: ${config.installUrl}`);
|
|
539
|
-
}
|
|
540
|
-
return false;
|
|
541
|
-
}
|
|
542
|
-
for (const method of methods2) {
|
|
543
|
-
if (!isBinaryAvailable(method.prerequisite)) {
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
onStatus(`Installing ${config.indexerBinary} via ${method.label}...`);
|
|
547
|
-
try {
|
|
548
|
-
execFileSync(method.binary, method.args, {
|
|
549
|
-
stdio: "inherit",
|
|
550
|
-
timeout: 3e5,
|
|
551
|
-
env: process.env
|
|
552
|
-
});
|
|
553
|
-
if (isIndexerInstalled(config)) {
|
|
554
|
-
onStatus(`Successfully installed ${config.indexerBinary} via ${method.label}`);
|
|
555
|
-
return true;
|
|
556
|
-
}
|
|
557
|
-
onStatus(`${method.label} command completed but ${config.indexerBinary} not found on PATH`);
|
|
558
|
-
} catch (err) {
|
|
559
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
560
|
-
onStatus(`${method.label} install failed: ${msg}`);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
onStatus(`Could not auto-install ${config.indexerBinary}.`);
|
|
564
|
-
if (config.installUrl) {
|
|
565
|
-
onStatus(`Install manually from: ${config.installUrl}`);
|
|
566
|
-
}
|
|
567
|
-
return false;
|
|
568
|
-
}
|
|
569
|
-
function tryInstallScipCli(onStatus) {
|
|
570
|
-
if (platform() === "darwin" && isBinaryAvailable("brew")) {
|
|
571
|
-
onStatus("Installing scip CLI via Homebrew...");
|
|
572
|
-
try {
|
|
573
|
-
execFileSync("brew", ["install", "sourcegraph/scip/scip"], {
|
|
574
|
-
stdio: "inherit",
|
|
575
|
-
timeout: 3e5,
|
|
576
|
-
env: process.env
|
|
577
|
-
});
|
|
578
|
-
if (isBinaryAvailable("scip")) {
|
|
579
|
-
onStatus("Successfully installed scip CLI via Homebrew");
|
|
580
|
-
return true;
|
|
581
|
-
}
|
|
582
|
-
} catch (err) {
|
|
583
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
584
|
-
onStatus(`Homebrew install failed: ${msg}`);
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
if (isBinaryAvailable("go")) {
|
|
588
|
-
onStatus("Installing scip CLI via go install...");
|
|
589
|
-
try {
|
|
590
|
-
execFileSync("go", ["install", "github.com/sourcegraph/scip/cmd/scip@latest"], {
|
|
591
|
-
stdio: "inherit",
|
|
592
|
-
timeout: 3e5,
|
|
593
|
-
env: process.env
|
|
594
|
-
});
|
|
595
|
-
if (isBinaryAvailable("scip")) {
|
|
596
|
-
onStatus("Successfully installed scip CLI via go install");
|
|
597
|
-
return true;
|
|
598
|
-
}
|
|
599
|
-
} catch (err) {
|
|
600
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
601
|
-
onStatus(`go install failed: ${msg}`);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
onStatus("Could not auto-install scip CLI.");
|
|
605
|
-
onStatus("Install manually from: https://github.com/sourcegraph/scip/releases");
|
|
606
|
-
return false;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
701
|
// src/reindex/index.ts
|
|
610
702
|
async function reindex(opts) {
|
|
611
703
|
const {
|
|
@@ -643,29 +735,38 @@ async function reindex(opts) {
|
|
|
643
735
|
};
|
|
644
736
|
for (const lang of languages) {
|
|
645
737
|
const config = getIndexerConfig(lang);
|
|
738
|
+
const binaryLabel = describeIndexerBinary(config);
|
|
646
739
|
if (!isIndexerInstalled(config)) {
|
|
647
740
|
if (skipAutoInstall) {
|
|
648
741
|
throw new Error(
|
|
649
|
-
`${
|
|
650
|
-
` + (config.installUrl ? `Install from: ${config.installUrl}` : `Make sure ${
|
|
742
|
+
`${binaryLabel} is required to index ${lang} but not found on PATH.
|
|
743
|
+
` + (config.installUrl ? `Install from: ${config.installUrl}` : `Make sure ${binaryLabel} is installed and available on PATH.`)
|
|
651
744
|
);
|
|
652
745
|
}
|
|
653
|
-
onStatus(`${
|
|
746
|
+
onStatus(`${binaryLabel} not found. Attempting auto-install...`);
|
|
654
747
|
if (!tryInstallIndexer(config, onStatus)) {
|
|
655
748
|
throw new Error(
|
|
656
|
-
`${
|
|
657
|
-
` + (config.installUrl ? `Install manually from: ${config.installUrl}` : `Make sure ${
|
|
749
|
+
`${binaryLabel} is required to index ${lang} but could not be installed.
|
|
750
|
+
` + (config.installUrl ? `Install manually from: ${config.installUrl}` : `Make sure ${binaryLabel} is installed and available on PATH.`)
|
|
658
751
|
);
|
|
659
752
|
}
|
|
660
753
|
}
|
|
661
|
-
|
|
754
|
+
const resolvedBinary = resolveIndexerBinary(config);
|
|
755
|
+
if (!resolvedBinary) {
|
|
756
|
+
throw new Error(
|
|
757
|
+
`${binaryLabel} is required to index ${lang} but was not found on PATH after installation checks.
|
|
758
|
+
` + (config.installUrl ? `Install manually from: ${config.installUrl}` : `Make sure ${binaryLabel} is installed and available on PATH.`)
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
onStatus(`Indexing ${lang} with ${resolvedBinary}...`);
|
|
662
762
|
const { binary, args } = config.indexArgs({
|
|
663
763
|
projectRoot,
|
|
664
764
|
outputPath: outputScip,
|
|
665
|
-
pnpmWorkspaces: opts.pnpmWorkspaces
|
|
765
|
+
pnpmWorkspaces: opts.pnpmWorkspaces,
|
|
766
|
+
indexerBinary: resolvedBinary
|
|
666
767
|
});
|
|
667
768
|
try {
|
|
668
|
-
|
|
769
|
+
execFileSync3(binary, args, {
|
|
669
770
|
cwd: projectRoot,
|
|
670
771
|
env,
|
|
671
772
|
stdio: "pipe",
|
|
@@ -674,8 +775,8 @@ async function reindex(opts) {
|
|
|
674
775
|
} catch (err) {
|
|
675
776
|
const msg = err instanceof Error ? err.message : String(err);
|
|
676
777
|
throw new Error(
|
|
677
|
-
`Failed to index ${lang} with ${
|
|
678
|
-
Make sure ${
|
|
778
|
+
`Failed to index ${lang} with ${resolvedBinary}: ${msg}
|
|
779
|
+
Make sure ${binaryLabel} is installed and available on PATH.`
|
|
679
780
|
);
|
|
680
781
|
}
|
|
681
782
|
}
|
|
@@ -684,7 +785,7 @@ Make sure ${config.indexerBinary} is installed and available on PATH.`
|
|
|
684
785
|
throw new Error(`SCIP index not found at ${outputScip} after indexing`);
|
|
685
786
|
}
|
|
686
787
|
try {
|
|
687
|
-
|
|
788
|
+
execFileSync3("scip", ["expt-convert", "--output", outputDb, outputScip], {
|
|
688
789
|
env,
|
|
689
790
|
stdio: "pipe",
|
|
690
791
|
maxBuffer: 50 * 1024 * 1024
|
|
@@ -860,7 +961,7 @@ var Watcher = class {
|
|
|
860
961
|
* Writes to index.db.tmp, then atomically renames to index.db.
|
|
861
962
|
*/
|
|
862
963
|
runReindex() {
|
|
863
|
-
return new Promise((
|
|
964
|
+
return new Promise((resolve4, reject) => {
|
|
864
965
|
const start = Date.now();
|
|
865
966
|
const tmpDb = this.indexPaths.dbPath + ".tmp";
|
|
866
967
|
const tmpScip = this.indexPaths.indexPath + ".tmp";
|
|
@@ -888,7 +989,7 @@ var Watcher = class {
|
|
|
888
989
|
if (existsSync5(tmpScip)) {
|
|
889
990
|
renameSync(tmpScip, this.indexPaths.indexPath);
|
|
890
991
|
}
|
|
891
|
-
|
|
992
|
+
resolve4(Date.now() - start);
|
|
892
993
|
} catch (err) {
|
|
893
994
|
reject(new Error(`Atomic swap failed: ${err}`));
|
|
894
995
|
}
|
|
@@ -913,7 +1014,7 @@ function stats(db) {
|
|
|
913
1014
|
"SELECT COUNT(*) as c FROM mentions WHERE role = 1"
|
|
914
1015
|
).c;
|
|
915
1016
|
const references = db.get(
|
|
916
|
-
"SELECT COUNT(*) as c FROM mentions WHERE role
|
|
1017
|
+
"SELECT COUNT(*) as c FROM mentions WHERE role != 1"
|
|
917
1018
|
).c;
|
|
918
1019
|
return {
|
|
919
1020
|
documents,
|
|
@@ -1099,11 +1200,47 @@ function leafName(raw) {
|
|
|
1099
1200
|
const last = sym.descriptors[sym.descriptors.length - 1];
|
|
1100
1201
|
return last.name;
|
|
1101
1202
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1203
|
+
function leafSuffix(raw) {
|
|
1204
|
+
const parsed = parseSymbol(raw);
|
|
1205
|
+
if ("kind" in parsed && parsed.kind === "local") {
|
|
1206
|
+
return null;
|
|
1207
|
+
}
|
|
1208
|
+
const sym = parsed;
|
|
1209
|
+
const last = sym.descriptors[sym.descriptors.length - 1];
|
|
1210
|
+
return last?.suffix ?? null;
|
|
1211
|
+
}
|
|
1212
|
+
function isFunctionLikeSymbol(raw) {
|
|
1213
|
+
const suffix = leafSuffix(raw);
|
|
1214
|
+
return suffix === "method" || suffix === "term";
|
|
1215
|
+
}
|
|
1216
|
+
function isModuleLikeSymbol(raw) {
|
|
1217
|
+
return leafSuffix(raw) === "namespace";
|
|
1218
|
+
}
|
|
1219
|
+
function isDirectChildSymbol(parentRaw, candidateRaw) {
|
|
1220
|
+
const parent = parseSymbol(parentRaw);
|
|
1221
|
+
const candidate = parseSymbol(candidateRaw);
|
|
1222
|
+
if ("kind" in parent || "kind" in candidate) {
|
|
1223
|
+
return false;
|
|
1224
|
+
}
|
|
1225
|
+
const parentDescriptors = parent.descriptors;
|
|
1226
|
+
const candidateDescriptors = candidate.descriptors;
|
|
1227
|
+
if (candidateDescriptors.length !== parentDescriptors.length + 1) {
|
|
1228
|
+
return false;
|
|
1229
|
+
}
|
|
1230
|
+
for (let i = 0; i < parentDescriptors.length; i++) {
|
|
1231
|
+
const parentDesc = parentDescriptors[i];
|
|
1232
|
+
const candidateDesc = candidateDescriptors[i];
|
|
1233
|
+
if (parentDesc.name !== candidateDesc.name || parentDesc.suffix !== candidateDesc.suffix) {
|
|
1234
|
+
return false;
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
return true;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// src/queries/clean-signature.ts
|
|
1241
|
+
function cleanSignature(sig) {
|
|
1242
|
+
if (!sig || !sig.trim()) return null;
|
|
1243
|
+
return sig.replace(/^```\w*\s*/, "").replace(/\s*```$/, "").replace(/^\(method\)\s*/, "").replace(/^\(property\)\s*/, "").replace(/^\(function\)\s*/, "").replace(/^\(class\)\s*/, "").replace(/^\(interface\)\s*/, "").replace(/^\(enum\)\s*/, "").replace(/^\(type alias\)\s*/, "").replace(/^\(const\)\s*/, "").replace(/^\(var\)\s*/, "").trim() || null;
|
|
1107
1244
|
}
|
|
1108
1245
|
|
|
1109
1246
|
// src/queries/symbols.ts
|
|
@@ -1163,7 +1300,7 @@ function refs(db, symbolPattern) {
|
|
|
1163
1300
|
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
1164
1301
|
WHERE gs.symbol LIKE ?
|
|
1165
1302
|
AND ${db.localSymbolPredicate}
|
|
1166
|
-
AND m.role
|
|
1303
|
+
AND m.role != 1
|
|
1167
1304
|
ORDER BY d.relative_path, c.start_line`,
|
|
1168
1305
|
`%${symbolPattern}%`
|
|
1169
1306
|
);
|
|
@@ -1173,20 +1310,228 @@ function refs(db, symbolPattern) {
|
|
|
1173
1310
|
}));
|
|
1174
1311
|
}
|
|
1175
1312
|
|
|
1313
|
+
// src/query-support.ts
|
|
1314
|
+
var TEST_FILE_PATTERNS = [
|
|
1315
|
+
"%/__tests__/%",
|
|
1316
|
+
"%.test.%",
|
|
1317
|
+
"%.spec.%",
|
|
1318
|
+
"%/test/%",
|
|
1319
|
+
"%/tests/%",
|
|
1320
|
+
"%_test.%",
|
|
1321
|
+
"%_spec.%",
|
|
1322
|
+
"%/test_%.%",
|
|
1323
|
+
"%/spec_%.%"
|
|
1324
|
+
];
|
|
1325
|
+
var TEST_SUPPORT_PATH_PATTERNS = [
|
|
1326
|
+
"%/test-utils/%"
|
|
1327
|
+
];
|
|
1328
|
+
function testFileExclusionSql(alias, extraPatterns = []) {
|
|
1329
|
+
const patterns = uniquePatterns([...TEST_FILE_PATTERNS, ...extraPatterns]);
|
|
1330
|
+
return patterns.map((pattern) => `${alias}.relative_path NOT LIKE '${pattern}'`).join("\n AND ");
|
|
1331
|
+
}
|
|
1332
|
+
function buildFileDepGraph(db, scope) {
|
|
1333
|
+
const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : "";
|
|
1334
|
+
const edges = db.all(
|
|
1335
|
+
`SELECT DISTINCT
|
|
1336
|
+
d1.relative_path AS from_file,
|
|
1337
|
+
d2.relative_path AS to_file
|
|
1338
|
+
FROM mentions m
|
|
1339
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
1340
|
+
JOIN documents d1 ON c.document_id = d1.id
|
|
1341
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
1342
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1343
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
1344
|
+
WHERE d1.id != d2.id
|
|
1345
|
+
AND m.role != 1
|
|
1346
|
+
${db.pathExclusionsFor("d1", "d2")}
|
|
1347
|
+
${scopeFilter}`
|
|
1348
|
+
);
|
|
1349
|
+
const graph = /* @__PURE__ */ new Map();
|
|
1350
|
+
for (const edge of edges) {
|
|
1351
|
+
if (db.isIgnored(edge.from_file) || db.isIgnored(edge.to_file)) continue;
|
|
1352
|
+
if (!graph.has(edge.from_file)) graph.set(edge.from_file, /* @__PURE__ */ new Set());
|
|
1353
|
+
graph.get(edge.from_file).add(edge.to_file);
|
|
1354
|
+
}
|
|
1355
|
+
return graph;
|
|
1356
|
+
}
|
|
1357
|
+
function findFirstSymbolMatch(db, symbolPattern) {
|
|
1358
|
+
const fileLineMatch = symbolPattern.match(/^(.+):(\d+)-(\d+)$/);
|
|
1359
|
+
if (fileLineMatch) {
|
|
1360
|
+
const [, filePath, startStr, endStr] = fileLineMatch;
|
|
1361
|
+
const row = db.get(
|
|
1362
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
1363
|
+
FROM global_symbols gs
|
|
1364
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1365
|
+
JOIN documents d ON der.document_id = d.id
|
|
1366
|
+
WHERE d.relative_path LIKE ?
|
|
1367
|
+
AND der.start_line <= ? AND der.end_line >= ?
|
|
1368
|
+
${db.pathExclusionsFor("d")}
|
|
1369
|
+
ORDER BY (der.end_line - der.start_line) ASC
|
|
1370
|
+
LIMIT 1`,
|
|
1371
|
+
`%${filePath}%`,
|
|
1372
|
+
parseInt(startStr, 10),
|
|
1373
|
+
parseInt(endStr, 10)
|
|
1374
|
+
);
|
|
1375
|
+
if (row && !db.isIgnored(row.relative_path)) {
|
|
1376
|
+
return {
|
|
1377
|
+
symbolId: row.id,
|
|
1378
|
+
symbol: row.symbol,
|
|
1379
|
+
documentId: row.document_id,
|
|
1380
|
+
startLine: row.start_line,
|
|
1381
|
+
endLine: row.end_line,
|
|
1382
|
+
relativePath: row.relative_path
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
const cleaned = normalizeLookupPattern(symbolPattern);
|
|
1387
|
+
const tokens = lookupTokens(symbolPattern);
|
|
1388
|
+
const candidates = getSymbolLookupCandidates(db, tokens);
|
|
1389
|
+
let best = null;
|
|
1390
|
+
for (const row of candidates) {
|
|
1391
|
+
if (db.isIgnored(row.relative_path)) continue;
|
|
1392
|
+
const score = scoreSymbolCandidate(row, symbolPattern, cleaned, tokens);
|
|
1393
|
+
if (score <= 0) continue;
|
|
1394
|
+
if (!best || score > best.score) {
|
|
1395
|
+
best = { row, score };
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
if (best) {
|
|
1399
|
+
return {
|
|
1400
|
+
symbolId: best.row.id,
|
|
1401
|
+
symbol: best.row.symbol,
|
|
1402
|
+
documentId: best.row.document_id,
|
|
1403
|
+
startLine: best.row.start_line,
|
|
1404
|
+
endLine: best.row.end_line,
|
|
1405
|
+
relativePath: best.row.relative_path
|
|
1406
|
+
};
|
|
1407
|
+
}
|
|
1408
|
+
return null;
|
|
1409
|
+
}
|
|
1410
|
+
function normalizeLookupPattern(symbolPattern) {
|
|
1411
|
+
return symbolPattern.trim().replace(/\(\)$/, "").replace(/\(.*$/, "");
|
|
1412
|
+
}
|
|
1413
|
+
function lookupTokens(symbolPattern) {
|
|
1414
|
+
const cleaned = normalizeLookupPattern(symbolPattern);
|
|
1415
|
+
const tokens = cleaned.split(/[^A-Za-z0-9_]+/).map((token) => token.trim()).filter((token) => token.length > 0);
|
|
1416
|
+
return tokens.length > 0 ? [...new Set(tokens)] : [cleaned];
|
|
1417
|
+
}
|
|
1418
|
+
function getSymbolLookupCandidates(db, tokens) {
|
|
1419
|
+
const tokenClauses = tokens.map(
|
|
1420
|
+
() => `(gs.symbol LIKE ? OR d.relative_path LIKE ? OR COALESCE(gs.display_name, '') LIKE ?)`
|
|
1421
|
+
);
|
|
1422
|
+
const params = tokens.flatMap((token) => {
|
|
1423
|
+
const like = `%${token}%`;
|
|
1424
|
+
return [like, like, like];
|
|
1425
|
+
});
|
|
1426
|
+
return db.all(
|
|
1427
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path, gs.display_name
|
|
1428
|
+
FROM global_symbols gs
|
|
1429
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1430
|
+
JOIN documents d ON der.document_id = d.id
|
|
1431
|
+
WHERE ${tokenClauses.join("\n AND ")}
|
|
1432
|
+
${db.pathExclusionsFor("d")}
|
|
1433
|
+
LIMIT 200`,
|
|
1434
|
+
...params
|
|
1435
|
+
);
|
|
1436
|
+
}
|
|
1437
|
+
function scoreSymbolCandidate(row, originalPattern, cleanedPattern, tokens) {
|
|
1438
|
+
const original = originalPattern.toLowerCase();
|
|
1439
|
+
const cleaned = cleanedPattern.toLowerCase();
|
|
1440
|
+
const noParens = cleaned.replace(/\(\)$/, "");
|
|
1441
|
+
const raw = row.symbol.toLowerCase();
|
|
1442
|
+
const short = shortenSymbol(row.symbol).toLowerCase();
|
|
1443
|
+
const leaf = leafName(row.symbol).toLowerCase();
|
|
1444
|
+
const display = (row.display_name ?? "").toLowerCase();
|
|
1445
|
+
const path2 = row.relative_path.toLowerCase();
|
|
1446
|
+
const looksPathLike = /[/:.]/.test(cleanedPattern);
|
|
1447
|
+
let score = 0;
|
|
1448
|
+
if (raw === original || raw === cleaned) score += 1e3;
|
|
1449
|
+
if (short === original || short === cleaned) score += 950;
|
|
1450
|
+
if (path2 === original || path2 === cleaned) score += 925;
|
|
1451
|
+
if (path2.endsWith(`/${cleaned}`) || path2.endsWith(`/${original}`)) score += 875;
|
|
1452
|
+
if (display === noParens) score += 850;
|
|
1453
|
+
if (leaf === noParens) score += 825;
|
|
1454
|
+
if (`${leaf}()` === original || `${leaf}()` === cleaned) score += 820;
|
|
1455
|
+
if (short.endsWith(`:${cleaned}`) || short.endsWith(`:${noParens}`) || short.endsWith(`:${noParens}()`)) score += 800;
|
|
1456
|
+
if (raw.includes(cleaned)) score += 120;
|
|
1457
|
+
if (short.includes(cleaned)) score += 140;
|
|
1458
|
+
if (path2.includes(cleaned)) score += 140;
|
|
1459
|
+
if (display.includes(cleaned)) score += 110;
|
|
1460
|
+
if (tokens.every((token) => {
|
|
1461
|
+
const lower = token.toLowerCase();
|
|
1462
|
+
return raw.includes(lower) || short.includes(lower) || path2.includes(lower) || display.includes(lower);
|
|
1463
|
+
})) {
|
|
1464
|
+
score += 100 + tokens.length * 15;
|
|
1465
|
+
}
|
|
1466
|
+
if (isFunctionLikeSymbol(row.symbol) && leaf === noParens) {
|
|
1467
|
+
score += 60;
|
|
1468
|
+
}
|
|
1469
|
+
if (!looksPathLike && isModuleLikeSymbol(row.symbol)) {
|
|
1470
|
+
score -= 160;
|
|
1471
|
+
}
|
|
1472
|
+
score -= Math.min(50, Math.max(0, row.end_line - row.start_line));
|
|
1473
|
+
return score;
|
|
1474
|
+
}
|
|
1475
|
+
function getCalleeRowsForSymbol(db, symbol, opts = {}) {
|
|
1476
|
+
const rows = db.all(
|
|
1477
|
+
`SELECT DISTINCT
|
|
1478
|
+
callee_gs.symbol AS symbol,
|
|
1479
|
+
callee_d.relative_path AS file,
|
|
1480
|
+
c.id AS chunk_id
|
|
1481
|
+
FROM mentions m
|
|
1482
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
1483
|
+
JOIN global_symbols callee_gs ON m.symbol_id = callee_gs.id
|
|
1484
|
+
JOIN defn_enclosing_ranges callee_der ON callee_gs.id = callee_der.symbol_id
|
|
1485
|
+
JOIN documents callee_d ON callee_der.document_id = callee_d.id
|
|
1486
|
+
WHERE c.document_id = ?
|
|
1487
|
+
AND c.start_line >= ?
|
|
1488
|
+
AND c.end_line <= ?
|
|
1489
|
+
AND m.role != 1
|
|
1490
|
+
AND callee_gs.id != ?
|
|
1491
|
+
${db.symbolNoiseFor("callee_gs")}
|
|
1492
|
+
${db.pathExclusionsFor("callee_d")}
|
|
1493
|
+
ORDER BY callee_d.relative_path
|
|
1494
|
+
${opts.limit ? "LIMIT ?" : ""}`,
|
|
1495
|
+
...calleeQueryParams(symbol, opts.limit)
|
|
1496
|
+
);
|
|
1497
|
+
return rows.filter((row) => !db.isIgnored(row.file)).map((row) => ({
|
|
1498
|
+
symbol: row.symbol,
|
|
1499
|
+
file: row.file,
|
|
1500
|
+
chunkId: row.chunk_id
|
|
1501
|
+
}));
|
|
1502
|
+
}
|
|
1503
|
+
function calleeQueryParams(symbol, limit) {
|
|
1504
|
+
const params = [
|
|
1505
|
+
symbol.documentId,
|
|
1506
|
+
symbol.startLine,
|
|
1507
|
+
symbol.endLine,
|
|
1508
|
+
symbol.symbolId
|
|
1509
|
+
];
|
|
1510
|
+
if (typeof limit === "number") {
|
|
1511
|
+
params.push(limit);
|
|
1512
|
+
}
|
|
1513
|
+
return params;
|
|
1514
|
+
}
|
|
1515
|
+
function uniquePatterns(patterns) {
|
|
1516
|
+
return [...new Set(patterns)];
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1176
1519
|
// src/queries/trace.ts
|
|
1177
1520
|
function trace(db, symbolPattern) {
|
|
1521
|
+
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
1522
|
+
if (!match) {
|
|
1523
|
+
return { definitions: [], referencedBy: [] };
|
|
1524
|
+
}
|
|
1178
1525
|
const defRows = db.all(
|
|
1179
1526
|
`SELECT d.relative_path, der.start_line, der.end_line,
|
|
1180
1527
|
REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
|
|
1181
1528
|
FROM global_symbols gs
|
|
1182
1529
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1183
1530
|
JOIN documents d ON der.document_id = d.id
|
|
1184
|
-
WHERE gs.
|
|
1185
|
-
AND ${db.localSymbolPredicate}
|
|
1186
|
-
${db.symbolNoise}
|
|
1531
|
+
WHERE gs.id = ?
|
|
1187
1532
|
ORDER BY d.relative_path, der.start_line
|
|
1188
1533
|
LIMIT 10`,
|
|
1189
|
-
|
|
1534
|
+
match.symbolId
|
|
1190
1535
|
);
|
|
1191
1536
|
const definitions = defRows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
1192
1537
|
relativePath: r.relative_path,
|
|
@@ -1199,13 +1544,10 @@ function trace(db, symbolPattern) {
|
|
|
1199
1544
|
FROM mentions m
|
|
1200
1545
|
JOIN chunks c ON m.chunk_id = c.id
|
|
1201
1546
|
JOIN documents d ON c.document_id = d.id
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
AND ${db.localSymbolPredicate}
|
|
1205
|
-
${db.symbolNoise}
|
|
1206
|
-
AND m.role = 0
|
|
1547
|
+
WHERE m.symbol_id = ?
|
|
1548
|
+
AND m.role != 1
|
|
1207
1549
|
ORDER BY d.relative_path`,
|
|
1208
|
-
|
|
1550
|
+
match.symbolId
|
|
1209
1551
|
);
|
|
1210
1552
|
const referencedBy = refRows.map((r) => r.relative_path).filter((p) => !db.isIgnored(p));
|
|
1211
1553
|
return { definitions, referencedBy };
|
|
@@ -1323,7 +1665,7 @@ function surface(db, modulePattern) {
|
|
|
1323
1665
|
WHERE d2.relative_path LIKE ?
|
|
1324
1666
|
AND d1.relative_path NOT LIKE ?
|
|
1325
1667
|
AND ${db.localSymbolPredicate}
|
|
1326
|
-
AND m.role
|
|
1668
|
+
AND m.role != 1
|
|
1327
1669
|
ORDER BY d1.relative_path`,
|
|
1328
1670
|
`%${modulePattern}%`,
|
|
1329
1671
|
`%${modulePattern}%`
|
|
@@ -1335,152 +1677,78 @@ function surface(db, modulePattern) {
|
|
|
1335
1677
|
}));
|
|
1336
1678
|
}
|
|
1337
1679
|
|
|
1338
|
-
// src/
|
|
1339
|
-
var
|
|
1340
|
-
|
|
1341
|
-
"
|
|
1342
|
-
"%.spec.%",
|
|
1343
|
-
"%/test/%",
|
|
1344
|
-
"%/tests/%",
|
|
1345
|
-
"%_test.%",
|
|
1346
|
-
"%_spec.%",
|
|
1347
|
-
"%/test_%.%",
|
|
1348
|
-
"%/spec_%.%"
|
|
1349
|
-
];
|
|
1350
|
-
var TEST_SUPPORT_PATH_PATTERNS = [
|
|
1351
|
-
"%/test-utils/%"
|
|
1352
|
-
];
|
|
1353
|
-
function testFileMatchSql(alias, patterns = TEST_FILE_PATTERNS) {
|
|
1354
|
-
return `(${patterns.map((pattern) => `${alias}.relative_path LIKE '${pattern}'`).join(" OR ")})`;
|
|
1680
|
+
// src/entry-surfaces.ts
|
|
1681
|
+
var liveBarrelCache = /* @__PURE__ */ new WeakMap();
|
|
1682
|
+
function normalizePath(path2) {
|
|
1683
|
+
return path2.replace(/\\/g, "/");
|
|
1355
1684
|
}
|
|
1356
|
-
function
|
|
1357
|
-
const
|
|
1358
|
-
return
|
|
1685
|
+
function isBarrelFile(path2) {
|
|
1686
|
+
const normalized = normalizePath(path2);
|
|
1687
|
+
return normalized === "index.ts" || normalized === "index.js" || normalized.endsWith("/index.ts") || normalized.endsWith("/index.js") || normalized.endsWith("/mod.rs") || normalized.endsWith("/__init__.py");
|
|
1359
1688
|
}
|
|
1360
|
-
function
|
|
1361
|
-
const
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1371
|
-
JOIN documents d2 ON der.document_id = d2.id
|
|
1372
|
-
WHERE d1.id != d2.id
|
|
1373
|
-
AND m.role = 0
|
|
1374
|
-
${db.pathExclusionsFor("d1", "d2")}
|
|
1375
|
-
${scopeFilter}`
|
|
1376
|
-
);
|
|
1377
|
-
const graph = /* @__PURE__ */ new Map();
|
|
1378
|
-
for (const edge of edges) {
|
|
1379
|
-
if (db.isIgnored(edge.from_file) || db.isIgnored(edge.to_file)) continue;
|
|
1380
|
-
if (!graph.has(edge.from_file)) graph.set(edge.from_file, /* @__PURE__ */ new Set());
|
|
1381
|
-
graph.get(edge.from_file).add(edge.to_file);
|
|
1689
|
+
function isWorkerEntrySurface(path2) {
|
|
1690
|
+
const normalized = normalizePath(path2);
|
|
1691
|
+
return /(^|\/)[^/]*worker\.(ts|js|mjs|cjs|rs|py|go)$/.test(normalized);
|
|
1692
|
+
}
|
|
1693
|
+
function isStructuralEntrySurface(path2) {
|
|
1694
|
+
const normalized = normalizePath(path2);
|
|
1695
|
+
const segments = normalized.split("/");
|
|
1696
|
+
const basename = segments[segments.length - 1] ?? normalized;
|
|
1697
|
+
if (basename === "cli.ts" || basename === "cli.js" || basename === "postinstall.ts" || basename === "postinstall.js" || basename === "main.ts" || basename === "main.js" || basename === "main.rs" || basename === "main.go" || basename === "main.py") {
|
|
1698
|
+
return true;
|
|
1382
1699
|
}
|
|
1383
|
-
|
|
1700
|
+
if (basename === "index.ts" || basename === "index.js") {
|
|
1701
|
+
return segments.length <= 2;
|
|
1702
|
+
}
|
|
1703
|
+
return normalized.endsWith("/mod.rs") || normalized.endsWith("/__init__.py");
|
|
1384
1704
|
}
|
|
1385
|
-
function
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
endLine: row.end_line,
|
|
1410
|
-
relativePath: row.relative_path
|
|
1411
|
-
};
|
|
1705
|
+
function getIndexedPaths(db) {
|
|
1706
|
+
return db.all(
|
|
1707
|
+
`SELECT d.relative_path
|
|
1708
|
+
FROM documents d
|
|
1709
|
+
WHERE 1 = 1
|
|
1710
|
+
${db.pathExclusionsFor("d")}
|
|
1711
|
+
ORDER BY d.relative_path`
|
|
1712
|
+
).map((row) => row.relative_path).filter((path2) => !db.isIgnored(path2));
|
|
1713
|
+
}
|
|
1714
|
+
function getLiveBarrelPaths(db) {
|
|
1715
|
+
const cached = liveBarrelCache.get(db);
|
|
1716
|
+
if (cached) {
|
|
1717
|
+
return cached;
|
|
1718
|
+
}
|
|
1719
|
+
const graph = buildFileDepGraph(db);
|
|
1720
|
+
const queue = getIndexedPaths(db).filter(
|
|
1721
|
+
(path2) => isStructuralEntrySurface(path2) || isWorkerEntrySurface(path2)
|
|
1722
|
+
);
|
|
1723
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1724
|
+
const liveBarrels = /* @__PURE__ */ new Set();
|
|
1725
|
+
while (queue.length > 0) {
|
|
1726
|
+
const current = queue.shift();
|
|
1727
|
+
if (visited.has(current)) {
|
|
1728
|
+
continue;
|
|
1412
1729
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
JOIN documents d ON der.document_id = d.id
|
|
1422
|
-
WHERE gs.symbol LIKE ?
|
|
1423
|
-
${db.pathExclusionsFor("d")}
|
|
1424
|
-
${noiseClause}
|
|
1425
|
-
ORDER BY (der.end_line - der.start_line) DESC
|
|
1426
|
-
LIMIT 1`,
|
|
1427
|
-
`%${cleaned}%`
|
|
1428
|
-
);
|
|
1429
|
-
if (row && !db.isIgnored(row.relative_path)) {
|
|
1430
|
-
return {
|
|
1431
|
-
symbolId: row.id,
|
|
1432
|
-
symbol: row.symbol,
|
|
1433
|
-
documentId: row.document_id,
|
|
1434
|
-
startLine: row.start_line,
|
|
1435
|
-
endLine: row.end_line,
|
|
1436
|
-
relativePath: row.relative_path
|
|
1437
|
-
};
|
|
1730
|
+
visited.add(current);
|
|
1731
|
+
if (isBarrelFile(current)) {
|
|
1732
|
+
liveBarrels.add(current);
|
|
1733
|
+
}
|
|
1734
|
+
for (const dep of graph.get(current) ?? []) {
|
|
1735
|
+
if (!visited.has(dep)) {
|
|
1736
|
+
queue.push(dep);
|
|
1737
|
+
}
|
|
1438
1738
|
}
|
|
1439
1739
|
}
|
|
1440
|
-
|
|
1740
|
+
liveBarrelCache.set(db, liveBarrels);
|
|
1741
|
+
return liveBarrels;
|
|
1441
1742
|
}
|
|
1442
|
-
function
|
|
1443
|
-
|
|
1444
|
-
`SELECT DISTINCT
|
|
1445
|
-
callee_gs.symbol AS symbol,
|
|
1446
|
-
callee_d.relative_path AS file,
|
|
1447
|
-
c.id AS chunk_id
|
|
1448
|
-
FROM mentions m
|
|
1449
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
1450
|
-
JOIN global_symbols callee_gs ON m.symbol_id = callee_gs.id
|
|
1451
|
-
JOIN defn_enclosing_ranges callee_der ON callee_gs.id = callee_der.symbol_id
|
|
1452
|
-
JOIN documents callee_d ON callee_der.document_id = callee_d.id
|
|
1453
|
-
WHERE c.document_id = ?
|
|
1454
|
-
AND c.start_line >= ?
|
|
1455
|
-
AND c.end_line <= ?
|
|
1456
|
-
AND m.role = 0
|
|
1457
|
-
AND callee_gs.id != ?
|
|
1458
|
-
${db.symbolNoiseFor("callee_gs")}
|
|
1459
|
-
${db.pathExclusionsFor("callee_d")}
|
|
1460
|
-
ORDER BY callee_d.relative_path
|
|
1461
|
-
${opts.limit ? "LIMIT ?" : ""}`,
|
|
1462
|
-
...calleeQueryParams(symbol, opts.limit)
|
|
1463
|
-
);
|
|
1464
|
-
return rows.filter((row) => !db.isIgnored(row.file)).map((row) => ({
|
|
1465
|
-
symbol: row.symbol,
|
|
1466
|
-
file: row.file,
|
|
1467
|
-
chunkId: row.chunk_id
|
|
1468
|
-
}));
|
|
1743
|
+
function isLiveBarrel(db, path2) {
|
|
1744
|
+
return getLiveBarrelPaths(db).has(normalizePath(path2));
|
|
1469
1745
|
}
|
|
1470
|
-
function
|
|
1471
|
-
|
|
1472
|
-
symbol.documentId,
|
|
1473
|
-
symbol.startLine,
|
|
1474
|
-
symbol.endLine,
|
|
1475
|
-
symbol.symbolId
|
|
1476
|
-
];
|
|
1477
|
-
if (typeof limit === "number") {
|
|
1478
|
-
params.push(limit);
|
|
1479
|
-
}
|
|
1480
|
-
return params;
|
|
1746
|
+
function isEntrySurface(db, path2) {
|
|
1747
|
+
return isStructuralEntrySurface(path2) || isWorkerEntrySurface(path2) || isLiveBarrel(db, path2);
|
|
1481
1748
|
}
|
|
1482
|
-
function
|
|
1483
|
-
|
|
1749
|
+
function getInactiveBarrelPaths(db) {
|
|
1750
|
+
const liveBarrels = getLiveBarrelPaths(db);
|
|
1751
|
+
return getIndexedPaths(db).filter((path2) => isBarrelFile(path2)).filter((path2) => !liveBarrels.has(path2));
|
|
1484
1752
|
}
|
|
1485
1753
|
|
|
1486
1754
|
// src/queries/dead.ts
|
|
@@ -1495,6 +1763,7 @@ function dead(db, opts = {}) {
|
|
|
1495
1763
|
const params = [minLoc];
|
|
1496
1764
|
let testFileExclusions = "";
|
|
1497
1765
|
let memberExclusion = "";
|
|
1766
|
+
let barrelExclusions = "";
|
|
1498
1767
|
if (scope) {
|
|
1499
1768
|
params.push(`%${scope}%`);
|
|
1500
1769
|
}
|
|
@@ -1506,10 +1775,13 @@ function dead(db, opts = {}) {
|
|
|
1506
1775
|
if (!includeMembers) {
|
|
1507
1776
|
memberExclusion = `AND gs.symbol NOT LIKE '%#%'`;
|
|
1508
1777
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1778
|
+
if (skipBarrels) {
|
|
1779
|
+
const inactiveBarrelPaths = getInactiveBarrelPaths(db);
|
|
1780
|
+
if (inactiveBarrelPaths.length > 0) {
|
|
1781
|
+
barrelExclusions = `AND ref_d.relative_path NOT IN (${inactiveBarrelPaths.map(() => "?").join(", ")})`;
|
|
1782
|
+
params.push(...inactiveBarrelPaths);
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1513
1785
|
const sql = `
|
|
1514
1786
|
SELECT
|
|
1515
1787
|
d.relative_path,
|
|
@@ -1519,7 +1791,7 @@ function dead(db, opts = {}) {
|
|
|
1519
1791
|
gs.symbol,
|
|
1520
1792
|
(SELECT COUNT(*) FROM mentions m2
|
|
1521
1793
|
JOIN chunks c2 ON m2.chunk_id = c2.id
|
|
1522
|
-
WHERE m2.symbol_id = gs.id AND m2.role
|
|
1794
|
+
WHERE m2.symbol_id = gs.id AND m2.role != 1 AND c2.document_id = d.id
|
|
1523
1795
|
) AS same_file_refs
|
|
1524
1796
|
FROM global_symbols gs
|
|
1525
1797
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
@@ -1537,7 +1809,7 @@ function dead(db, opts = {}) {
|
|
|
1537
1809
|
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
1538
1810
|
JOIN documents ref_d ON ref_c.document_id = ref_d.id
|
|
1539
1811
|
WHERE ref_m.symbol_id = gs.id
|
|
1540
|
-
AND ref_m.role
|
|
1812
|
+
AND ref_m.role != 1
|
|
1541
1813
|
AND ref_d.id != d.id
|
|
1542
1814
|
${barrelExclusions}
|
|
1543
1815
|
)
|
|
@@ -1547,7 +1819,7 @@ function dead(db, opts = {}) {
|
|
|
1547
1819
|
let deadCodeCount = 0;
|
|
1548
1820
|
let fileInternalCount = 0;
|
|
1549
1821
|
let totalLoc = 0;
|
|
1550
|
-
const symbols2 = rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => {
|
|
1822
|
+
const symbols2 = rows.filter((r) => !db.isIgnored(r.relative_path)).filter((r) => !isEntrySurface(db, r.relative_path)).map((r) => {
|
|
1551
1823
|
const kind = r.same_file_refs === 0 ? "dead-code" : "file-internal";
|
|
1552
1824
|
if (kind === "dead-code") deadCodeCount++;
|
|
1553
1825
|
else fileInternalCount++;
|
|
@@ -1588,7 +1860,7 @@ function hotspots(db, opts = {}) {
|
|
|
1588
1860
|
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
1589
1861
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1590
1862
|
JOIN documents def_d ON der.document_id = def_d.id
|
|
1591
|
-
WHERE m.role
|
|
1863
|
+
WHERE m.role != 1
|
|
1592
1864
|
${db.pathExclusionsFor("def_d")}
|
|
1593
1865
|
${db.symbolNoiseFor("gs")}
|
|
1594
1866
|
${scopeFilter}
|
|
@@ -1597,19 +1869,447 @@ function hotspots(db, opts = {}) {
|
|
|
1597
1869
|
LIMIT ?`,
|
|
1598
1870
|
limit
|
|
1599
1871
|
);
|
|
1600
|
-
return rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
|
|
1601
|
-
symbol: r.symbol,
|
|
1602
|
-
shortName: shortenSymbol(r.symbol),
|
|
1603
|
-
refCount: r.ref_count,
|
|
1604
|
-
fileCount: r.file_count,
|
|
1605
|
-
definedIn: r.defined_in
|
|
1606
|
-
}));
|
|
1872
|
+
return rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
|
|
1873
|
+
symbol: r.symbol,
|
|
1874
|
+
shortName: shortenSymbol(r.symbol),
|
|
1875
|
+
refCount: r.ref_count,
|
|
1876
|
+
fileCount: r.file_count,
|
|
1877
|
+
definedIn: r.defined_in
|
|
1878
|
+
}));
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// src/source-analysis.ts
|
|
1882
|
+
import {
|
|
1883
|
+
existsSync as existsSync6,
|
|
1884
|
+
readFileSync as readFileSync4
|
|
1885
|
+
} from "fs";
|
|
1886
|
+
import {
|
|
1887
|
+
dirname as dirname2,
|
|
1888
|
+
extname,
|
|
1889
|
+
join as join6,
|
|
1890
|
+
relative as relative2,
|
|
1891
|
+
resolve as resolve2
|
|
1892
|
+
} from "path";
|
|
1893
|
+
var SOURCE_IMPORT_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1894
|
+
var INDEXED_PATH_CACHE = /* @__PURE__ */ new WeakMap();
|
|
1895
|
+
var SOURCE_EXTENSIONS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
1896
|
+
var PYTHON_SOURCE_EXTENSIONS = [".py", ".pyi"];
|
|
1897
|
+
function getSourceImports(db, relativePath) {
|
|
1898
|
+
const cache = getCachedMap(SOURCE_IMPORT_CACHE, db);
|
|
1899
|
+
const normalized = normalizePath2(relativePath);
|
|
1900
|
+
const cached = cache.get(normalized);
|
|
1901
|
+
if (cached) {
|
|
1902
|
+
return cached;
|
|
1903
|
+
}
|
|
1904
|
+
const fullPath = join6(db.config.projectRoot, normalized);
|
|
1905
|
+
if (!existsSync6(fullPath)) {
|
|
1906
|
+
cache.set(normalized, []);
|
|
1907
|
+
return [];
|
|
1908
|
+
}
|
|
1909
|
+
const source = readFileSync4(fullPath, "utf-8");
|
|
1910
|
+
const parsed = isPythonSourcePath(normalized) ? parsePythonImports(db, normalized, source) : parseJavaScriptImports(db, normalized, source);
|
|
1911
|
+
cache.set(normalized, parsed);
|
|
1912
|
+
return parsed;
|
|
1913
|
+
}
|
|
1914
|
+
function parseJavaScriptImports(db, importerPath, source) {
|
|
1915
|
+
return parseJavaScriptImportStatements(source).flatMap((statement) => parseJavaScriptImportStatement(
|
|
1916
|
+
db,
|
|
1917
|
+
importerPath,
|
|
1918
|
+
statement.clause,
|
|
1919
|
+
statement.specifier,
|
|
1920
|
+
statement.start,
|
|
1921
|
+
statement.end,
|
|
1922
|
+
source
|
|
1923
|
+
));
|
|
1924
|
+
}
|
|
1925
|
+
function parseJavaScriptImportStatements(source) {
|
|
1926
|
+
const statements = [];
|
|
1927
|
+
const importFromRegex = /^[ \t]*import\s+([\s\S]*?)\s+from\s+['"]([^'"]+)['"]\s*;?/gm;
|
|
1928
|
+
for (const match of source.matchAll(importFromRegex)) {
|
|
1929
|
+
const full = match[0];
|
|
1930
|
+
const clause = match[1];
|
|
1931
|
+
const specifier = match[2];
|
|
1932
|
+
if (!full || !specifier || typeof match.index !== "number") continue;
|
|
1933
|
+
statements.push({
|
|
1934
|
+
clause,
|
|
1935
|
+
specifier,
|
|
1936
|
+
start: match.index,
|
|
1937
|
+
end: match.index + full.length
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
const sideEffectRegex = /^[ \t]*import\s+['"]([^'"]+)['"]\s*;?/gm;
|
|
1941
|
+
for (const match of source.matchAll(sideEffectRegex)) {
|
|
1942
|
+
const full = match[0];
|
|
1943
|
+
const specifier = match[1];
|
|
1944
|
+
if (!full || !specifier || typeof match.index !== "number") continue;
|
|
1945
|
+
statements.push({
|
|
1946
|
+
clause: null,
|
|
1947
|
+
specifier,
|
|
1948
|
+
start: match.index,
|
|
1949
|
+
end: match.index + full.length
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
return statements.sort((a, b) => a.start - b.start);
|
|
1953
|
+
}
|
|
1954
|
+
function parseJavaScriptImportStatement(db, importerPath, clause, specifier, start, end, source) {
|
|
1955
|
+
const resolvedSource = resolveImportPath(db, importerPath, specifier);
|
|
1956
|
+
const body = buildUsageBody(source, start, end);
|
|
1957
|
+
if (!clause) {
|
|
1958
|
+
return [{
|
|
1959
|
+
importedName: "*",
|
|
1960
|
+
localName: null,
|
|
1961
|
+
sourcePath: resolvedSource,
|
|
1962
|
+
kind: "side-effect",
|
|
1963
|
+
used: true,
|
|
1964
|
+
usedMembers: []
|
|
1965
|
+
}];
|
|
1966
|
+
}
|
|
1967
|
+
const bindings = parseImportClause(clause).map((binding) => ({
|
|
1968
|
+
...binding,
|
|
1969
|
+
sourcePath: resolvedSource
|
|
1970
|
+
}));
|
|
1971
|
+
return bindings.map((binding) => {
|
|
1972
|
+
if (binding.kind === "namespace") {
|
|
1973
|
+
const usedMembers = collectNamespaceMembers(body, binding.localName);
|
|
1974
|
+
return {
|
|
1975
|
+
...binding,
|
|
1976
|
+
used: usedMembers.length > 0 || hasIdentifierUsage(body, binding.localName),
|
|
1977
|
+
usedMembers
|
|
1978
|
+
};
|
|
1979
|
+
}
|
|
1980
|
+
if (binding.kind === "side-effect") {
|
|
1981
|
+
return { ...binding, used: true, usedMembers: [] };
|
|
1982
|
+
}
|
|
1983
|
+
return {
|
|
1984
|
+
...binding,
|
|
1985
|
+
used: binding.localName ? hasIdentifierUsage(body, binding.localName) : false,
|
|
1986
|
+
usedMembers: []
|
|
1987
|
+
};
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
function parsePythonImports(db, importerPath, source) {
|
|
1991
|
+
return collectPythonImportStatements(source).flatMap(
|
|
1992
|
+
(statement) => parsePythonImportStatement(db, importerPath, statement, source)
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1995
|
+
function collectPythonImportStatements(source) {
|
|
1996
|
+
const lines = source.split("\n");
|
|
1997
|
+
const statements = [];
|
|
1998
|
+
let offset = 0;
|
|
1999
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
|
2000
|
+
const line = lines[lineIndex];
|
|
2001
|
+
const trimmed = line.trimStart();
|
|
2002
|
+
const lineStart = offset;
|
|
2003
|
+
offset += line.length + 1;
|
|
2004
|
+
if (!trimmed.startsWith("import ") && !trimmed.startsWith("from ")) {
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
let statement = line;
|
|
2008
|
+
let statementEnd = lineStart + line.length;
|
|
2009
|
+
let balance = pythonParenBalance(line);
|
|
2010
|
+
while (lineIndex + 1 < lines.length && (balance > 0 || statement.trimEnd().endsWith("\\"))) {
|
|
2011
|
+
lineIndex++;
|
|
2012
|
+
const nextLine = lines[lineIndex];
|
|
2013
|
+
statement += `
|
|
2014
|
+
${nextLine}`;
|
|
2015
|
+
statementEnd += 1 + nextLine.length;
|
|
2016
|
+
balance += pythonParenBalance(nextLine);
|
|
2017
|
+
offset += nextLine.length + 1;
|
|
2018
|
+
}
|
|
2019
|
+
const parsed = parsePythonStatementHeader(statement);
|
|
2020
|
+
if (parsed) {
|
|
2021
|
+
statements.push({
|
|
2022
|
+
...parsed,
|
|
2023
|
+
start: lineStart,
|
|
2024
|
+
end: statementEnd
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
return statements;
|
|
2029
|
+
}
|
|
2030
|
+
function parsePythonStatementHeader(statement) {
|
|
2031
|
+
const normalized = statement.replace(/\\\s*\n/g, " ").trim();
|
|
2032
|
+
if (normalized.startsWith("import ")) {
|
|
2033
|
+
return {
|
|
2034
|
+
kind: "import",
|
|
2035
|
+
module: null,
|
|
2036
|
+
clause: normalized.slice("import ".length).trim()
|
|
2037
|
+
};
|
|
2038
|
+
}
|
|
2039
|
+
const fromMatch = normalized.match(/^from\s+([.\w]+)\s+import\s+([\s\S]+)$/);
|
|
2040
|
+
if (!fromMatch) {
|
|
2041
|
+
return null;
|
|
2042
|
+
}
|
|
2043
|
+
let clause = fromMatch[2].trim();
|
|
2044
|
+
if (clause.startsWith("(") && clause.endsWith(")")) {
|
|
2045
|
+
clause = clause.slice(1, -1).trim();
|
|
2046
|
+
}
|
|
2047
|
+
return {
|
|
2048
|
+
kind: "from",
|
|
2049
|
+
module: fromMatch[1],
|
|
2050
|
+
clause
|
|
2051
|
+
};
|
|
2052
|
+
}
|
|
2053
|
+
function parsePythonImportStatement(db, importerPath, statement, source) {
|
|
2054
|
+
const body = buildUsageBody(source, statement.start, statement.end);
|
|
2055
|
+
const normalizedClause = statement.clause.replace(/\n/g, " ").trim();
|
|
2056
|
+
if (statement.kind === "import") {
|
|
2057
|
+
return splitTopLevel(normalizedClause).flatMap((entry) => {
|
|
2058
|
+
const cleaned = entry.trim().replace(/,$/, "");
|
|
2059
|
+
if (!cleaned) return [];
|
|
2060
|
+
const [moduleName, alias] = cleaned.split(/\s+as\s+/);
|
|
2061
|
+
const importedName = moduleName.trim();
|
|
2062
|
+
const localName = (alias ?? importedName.split(".")[0] ?? importedName).trim();
|
|
2063
|
+
const sourcePath2 = resolvePythonImportPath(db, importerPath, importedName);
|
|
2064
|
+
const usedMembers = collectNamespaceMembers(body, localName);
|
|
2065
|
+
return [{
|
|
2066
|
+
importedName,
|
|
2067
|
+
localName,
|
|
2068
|
+
sourcePath: sourcePath2,
|
|
2069
|
+
kind: "namespace",
|
|
2070
|
+
used: hasIdentifierUsage(body, localName) || usedMembers.length > 0,
|
|
2071
|
+
usedMembers
|
|
2072
|
+
}];
|
|
2073
|
+
});
|
|
2074
|
+
}
|
|
2075
|
+
const sourcePath = statement.module ? resolvePythonImportPath(db, importerPath, statement.module) : null;
|
|
2076
|
+
const results = [];
|
|
2077
|
+
for (const entry of splitTopLevel(normalizedClause)) {
|
|
2078
|
+
const cleaned = entry.trim().replace(/,$/, "");
|
|
2079
|
+
if (!cleaned) continue;
|
|
2080
|
+
if (cleaned === "*") {
|
|
2081
|
+
results.push({
|
|
2082
|
+
importedName: "*",
|
|
2083
|
+
localName: null,
|
|
2084
|
+
sourcePath,
|
|
2085
|
+
kind: "side-effect",
|
|
2086
|
+
used: true,
|
|
2087
|
+
usedMembers: []
|
|
2088
|
+
});
|
|
2089
|
+
continue;
|
|
2090
|
+
}
|
|
2091
|
+
const [importedName, alias] = cleaned.split(/\s+as\s+/);
|
|
2092
|
+
const localName = (alias ?? importedName).trim();
|
|
2093
|
+
results.push({
|
|
2094
|
+
importedName: importedName.trim(),
|
|
2095
|
+
localName,
|
|
2096
|
+
sourcePath,
|
|
2097
|
+
kind: "named",
|
|
2098
|
+
used: hasIdentifierUsage(body, localName),
|
|
2099
|
+
usedMembers: []
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
return results;
|
|
2103
|
+
}
|
|
2104
|
+
function parseImportClause(clause) {
|
|
2105
|
+
const trimmed = clause.trim().replace(/^type\s+/, "");
|
|
2106
|
+
const [first, second] = splitImportClause(trimmed);
|
|
2107
|
+
const entries = [];
|
|
2108
|
+
if (first) {
|
|
2109
|
+
entries.push(...parseImportBinding(first));
|
|
2110
|
+
}
|
|
2111
|
+
if (second) {
|
|
2112
|
+
entries.push(...parseImportBinding(second));
|
|
2113
|
+
}
|
|
2114
|
+
return entries;
|
|
2115
|
+
}
|
|
2116
|
+
function parseImportBinding(binding) {
|
|
2117
|
+
const trimmed = binding.trim();
|
|
2118
|
+
if (!trimmed) return [];
|
|
2119
|
+
if (trimmed.startsWith("{")) {
|
|
2120
|
+
const inner = trimmed.slice(1, -1).trim();
|
|
2121
|
+
if (!inner) return [];
|
|
2122
|
+
return splitTopLevel(inner).map((entry) => {
|
|
2123
|
+
const cleaned = entry.trim().replace(/^type\s+/, "");
|
|
2124
|
+
const [importedName, alias] = cleaned.split(/\s+as\s+/);
|
|
2125
|
+
return {
|
|
2126
|
+
importedName: importedName.trim(),
|
|
2127
|
+
localName: (alias ?? importedName).trim(),
|
|
2128
|
+
kind: "named"
|
|
2129
|
+
};
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
if (trimmed.startsWith("* as ")) {
|
|
2133
|
+
return [{
|
|
2134
|
+
importedName: "*",
|
|
2135
|
+
localName: trimmed.slice(5).trim(),
|
|
2136
|
+
kind: "namespace"
|
|
2137
|
+
}];
|
|
2138
|
+
}
|
|
2139
|
+
return [{
|
|
2140
|
+
importedName: "default",
|
|
2141
|
+
localName: trimmed,
|
|
2142
|
+
kind: "default"
|
|
2143
|
+
}];
|
|
2144
|
+
}
|
|
2145
|
+
function splitImportClause(clause) {
|
|
2146
|
+
let depth = 0;
|
|
2147
|
+
for (let i = 0; i < clause.length; i++) {
|
|
2148
|
+
const char = clause[i];
|
|
2149
|
+
if (char === "{") depth++;
|
|
2150
|
+
if (char === "}") depth--;
|
|
2151
|
+
if (char === "," && depth === 0) {
|
|
2152
|
+
return [clause.slice(0, i).trim(), clause.slice(i + 1).trim()];
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
return [clause.trim(), null];
|
|
2156
|
+
}
|
|
2157
|
+
function splitTopLevel(input) {
|
|
2158
|
+
const parts = [];
|
|
2159
|
+
let depth = 0;
|
|
2160
|
+
let start = 0;
|
|
2161
|
+
for (let i = 0; i < input.length; i++) {
|
|
2162
|
+
const char = input[i];
|
|
2163
|
+
if (char === "{" || char === "[" || char === "(") depth++;
|
|
2164
|
+
if (char === "}" || char === "]" || char === ")") depth--;
|
|
2165
|
+
if (char === "," && depth === 0) {
|
|
2166
|
+
parts.push(input.slice(start, i));
|
|
2167
|
+
start = i + 1;
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
parts.push(input.slice(start));
|
|
2171
|
+
return parts;
|
|
2172
|
+
}
|
|
2173
|
+
function buildUsageBody(source, start, end) {
|
|
2174
|
+
const masked = `${source.slice(0, start)}${" ".repeat(end - start)}${source.slice(end)}`;
|
|
2175
|
+
return stripCommentsAndStrings(masked);
|
|
2176
|
+
}
|
|
2177
|
+
function stripCommentsAndStrings(source) {
|
|
2178
|
+
return source.replace(/'''[\s\S]*?'''/g, " ").replace(/"""[\s\S]*?"""/g, " ").replace(/#.*$/gm, " ").replace(/\/\/.*$/gm, " ").replace(/\/\*[\s\S]*?\*\//g, " ").replace(/`(?:\\[\s\S]|[^`])*`/g, " ").replace(/'(?:\\.|[^'\\\r\n])*'/g, " ").replace(/"(?:\\.|[^"\\\r\n])*"/g, " ");
|
|
2179
|
+
}
|
|
2180
|
+
function hasIdentifierUsage(body, identifier) {
|
|
2181
|
+
return new RegExp(`\\b${escapeRegex(identifier)}\\b`, "m").test(body);
|
|
2182
|
+
}
|
|
2183
|
+
function collectNamespaceMembers(body, namespaceName) {
|
|
2184
|
+
const members2 = /* @__PURE__ */ new Set();
|
|
2185
|
+
const regex = new RegExp(`\\b${escapeRegex(namespaceName)}\\s*\\.\\s*([A-Za-z_$][\\w$]*)`, "g");
|
|
2186
|
+
for (const match of body.matchAll(regex)) {
|
|
2187
|
+
const member = match[1];
|
|
2188
|
+
if (member) {
|
|
2189
|
+
members2.add(member);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
return [...members2];
|
|
2193
|
+
}
|
|
2194
|
+
function resolveImportPath(db, importerPath, specifier) {
|
|
2195
|
+
if (isPythonSourcePath(importerPath)) {
|
|
2196
|
+
return resolvePythonImportPath(db, importerPath, specifier);
|
|
2197
|
+
}
|
|
2198
|
+
return resolveJavaScriptImportPath(db, importerPath, specifier);
|
|
2199
|
+
}
|
|
2200
|
+
function resolveJavaScriptImportPath(db, importerPath, specifier) {
|
|
2201
|
+
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
2202
|
+
return null;
|
|
2203
|
+
}
|
|
2204
|
+
const importerDir = dirname2(join6(db.config.projectRoot, importerPath));
|
|
2205
|
+
const absolute = resolve2(importerDir, specifier);
|
|
2206
|
+
const indexedPaths = getIndexedPaths2(db);
|
|
2207
|
+
for (const candidate of candidateImportPaths(absolute)) {
|
|
2208
|
+
const relativeCandidate = normalizePath2(relative2(db.config.projectRoot, candidate));
|
|
2209
|
+
if (indexedPaths.has(relativeCandidate) || existsSync6(candidate)) {
|
|
2210
|
+
return relativeCandidate;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
return normalizePath2(relative2(db.config.projectRoot, absolute));
|
|
2214
|
+
}
|
|
2215
|
+
function resolvePythonImportPath(db, importerPath, specifier) {
|
|
2216
|
+
const indexedPaths = getIndexedPaths2(db);
|
|
2217
|
+
let basePath;
|
|
2218
|
+
if (specifier.startsWith(".")) {
|
|
2219
|
+
const match = specifier.match(/^(\.+)(.*)$/);
|
|
2220
|
+
if (!match) return null;
|
|
2221
|
+
const dots = match[1].length;
|
|
2222
|
+
const remainder = match[2].replace(/^\./, "");
|
|
2223
|
+
let baseDir = dirname2(join6(db.config.projectRoot, importerPath));
|
|
2224
|
+
for (let i = 1; i < dots; i++) {
|
|
2225
|
+
baseDir = dirname2(baseDir);
|
|
2226
|
+
}
|
|
2227
|
+
basePath = remainder ? resolve2(baseDir, remainder.replace(/\./g, "/")) : baseDir;
|
|
2228
|
+
} else {
|
|
2229
|
+
basePath = resolve2(db.config.projectRoot, specifier.replace(/\./g, "/"));
|
|
2230
|
+
}
|
|
2231
|
+
for (const candidate of pythonCandidateImportPaths(basePath)) {
|
|
2232
|
+
const relativeCandidate = normalizePath2(relative2(db.config.projectRoot, candidate));
|
|
2233
|
+
if (indexedPaths.has(relativeCandidate) || existsSync6(candidate)) {
|
|
2234
|
+
return relativeCandidate;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
return null;
|
|
2238
|
+
}
|
|
2239
|
+
function pythonCandidateImportPaths(basePath) {
|
|
2240
|
+
const ext = extname(basePath);
|
|
2241
|
+
if (PYTHON_SOURCE_EXTENSIONS.includes(ext)) {
|
|
2242
|
+
return [basePath];
|
|
2243
|
+
}
|
|
2244
|
+
return [
|
|
2245
|
+
`${basePath}.py`,
|
|
2246
|
+
`${basePath}.pyi`,
|
|
2247
|
+
join6(basePath, "__init__.py"),
|
|
2248
|
+
join6(basePath, "__init__.pyi")
|
|
2249
|
+
];
|
|
2250
|
+
}
|
|
2251
|
+
function candidateImportPaths(absolute) {
|
|
2252
|
+
const ext = extname(absolute);
|
|
2253
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
2254
|
+
if (ext) {
|
|
2255
|
+
candidates.add(absolute);
|
|
2256
|
+
for (const sourceExt of SOURCE_EXTENSIONS) {
|
|
2257
|
+
candidates.add(absolute.slice(0, -ext.length) + sourceExt);
|
|
2258
|
+
}
|
|
2259
|
+
} else {
|
|
2260
|
+
for (const sourceExt of SOURCE_EXTENSIONS) {
|
|
2261
|
+
candidates.add(`${absolute}${sourceExt}`);
|
|
2262
|
+
candidates.add(join6(absolute, `index${sourceExt}`));
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
return [...candidates];
|
|
2266
|
+
}
|
|
2267
|
+
function getIndexedPaths2(db) {
|
|
2268
|
+
const cached = INDEXED_PATH_CACHE.get(db);
|
|
2269
|
+
if (cached) {
|
|
2270
|
+
return cached;
|
|
2271
|
+
}
|
|
2272
|
+
const paths = new Set(
|
|
2273
|
+
db.all(
|
|
2274
|
+
`SELECT relative_path
|
|
2275
|
+
FROM documents
|
|
2276
|
+
WHERE 1 = 1
|
|
2277
|
+
${db.pathExclusionsFor("documents")}`
|
|
2278
|
+
).map((row) => normalizePath2(row.relative_path)).filter((relativePath) => !db.isIgnored(relativePath))
|
|
2279
|
+
);
|
|
2280
|
+
INDEXED_PATH_CACHE.set(db, paths);
|
|
2281
|
+
return paths;
|
|
2282
|
+
}
|
|
2283
|
+
function getCachedMap(cache, db) {
|
|
2284
|
+
let map = cache.get(db);
|
|
2285
|
+
if (!map) {
|
|
2286
|
+
map = /* @__PURE__ */ new Map();
|
|
2287
|
+
cache.set(db, map);
|
|
2288
|
+
}
|
|
2289
|
+
return map;
|
|
2290
|
+
}
|
|
2291
|
+
function normalizePath2(path2) {
|
|
2292
|
+
return path2.replace(/\\/g, "/");
|
|
2293
|
+
}
|
|
2294
|
+
function isPythonSourcePath(relativePath) {
|
|
2295
|
+
return PYTHON_SOURCE_EXTENSIONS.includes(extname(relativePath).toLowerCase());
|
|
2296
|
+
}
|
|
2297
|
+
function pythonParenBalance(value) {
|
|
2298
|
+
let balance = 0;
|
|
2299
|
+
for (const char of value) {
|
|
2300
|
+
if (char === "(") balance++;
|
|
2301
|
+
if (char === ")") balance--;
|
|
2302
|
+
}
|
|
2303
|
+
return balance;
|
|
2304
|
+
}
|
|
2305
|
+
function escapeRegex(value) {
|
|
2306
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1607
2307
|
}
|
|
1608
2308
|
|
|
1609
2309
|
// src/queries/imports.ts
|
|
1610
2310
|
function imports(db, filePattern) {
|
|
1611
2311
|
const rows = db.all(
|
|
1612
|
-
`SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file
|
|
2312
|
+
`SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file, imp_d.relative_path AS importer
|
|
1613
2313
|
FROM mentions m
|
|
1614
2314
|
JOIN chunks c ON m.chunk_id = c.id
|
|
1615
2315
|
JOIN documents imp_d ON c.document_id = imp_d.id
|
|
@@ -1621,11 +2321,21 @@ function imports(db, filePattern) {
|
|
|
1621
2321
|
ORDER BY def_d.relative_path, gs.symbol`,
|
|
1622
2322
|
`%${filePattern}%`
|
|
1623
2323
|
);
|
|
1624
|
-
|
|
2324
|
+
const indexedResults = rows.filter((row) => !db.isIgnored(row.importer)).map((r) => ({
|
|
1625
2325
|
symbol: r.symbol,
|
|
1626
2326
|
shortName: shortenSymbol(r.symbol),
|
|
1627
2327
|
fromFile: r.from_file ?? "(external)"
|
|
1628
2328
|
}));
|
|
2329
|
+
if (indexedResults.length > 0) {
|
|
2330
|
+
return indexedResults;
|
|
2331
|
+
}
|
|
2332
|
+
const importer = findIndexedFile(db, filePattern);
|
|
2333
|
+
if (!importer) return [];
|
|
2334
|
+
return getSourceImports(db, importer).map((entry) => ({
|
|
2335
|
+
symbol: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
|
|
2336
|
+
shortName: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
|
|
2337
|
+
fromFile: entry.sourcePath ?? "(external)"
|
|
2338
|
+
}));
|
|
1629
2339
|
}
|
|
1630
2340
|
function importedBy(db, symbolPattern) {
|
|
1631
2341
|
const rows = db.all(
|
|
@@ -1639,15 +2349,55 @@ function importedBy(db, symbolPattern) {
|
|
|
1639
2349
|
ORDER BY d.relative_path`,
|
|
1640
2350
|
`%${symbolPattern}%`
|
|
1641
2351
|
);
|
|
1642
|
-
|
|
2352
|
+
const indexedResults = rows.filter((r) => !db.isIgnored(r.importer)).map((r) => ({
|
|
1643
2353
|
symbol: r.symbol,
|
|
1644
2354
|
shortName: shortenSymbol(r.symbol),
|
|
1645
2355
|
fromFile: r.importer
|
|
1646
2356
|
}));
|
|
2357
|
+
if (indexedResults.length > 0) {
|
|
2358
|
+
return indexedResults;
|
|
2359
|
+
}
|
|
2360
|
+
const target = findFirstSymbolMatch(db, symbolPattern);
|
|
2361
|
+
const targetFile = target?.relativePath ?? null;
|
|
2362
|
+
const targetLeaf = target ? leafName(target.symbol) : symbolPattern.replace(/\(\)$/, "");
|
|
2363
|
+
const targetIsModule = target ? isModuleLikeSymbol(target.symbol) : false;
|
|
2364
|
+
const files2 = db.all(
|
|
2365
|
+
`SELECT relative_path
|
|
2366
|
+
FROM documents
|
|
2367
|
+
WHERE 1 = 1
|
|
2368
|
+
${db.pathExclusionsFor("documents")}
|
|
2369
|
+
ORDER BY relative_path`
|
|
2370
|
+
);
|
|
2371
|
+
const importers = /* @__PURE__ */ new Set();
|
|
2372
|
+
for (const row of files2) {
|
|
2373
|
+
if (db.isIgnored(row.relative_path)) continue;
|
|
2374
|
+
for (const entry of getSourceImports(db, row.relative_path)) {
|
|
2375
|
+
if (!entry.sourcePath) continue;
|
|
2376
|
+
if (targetFile && normalizePath3(entry.sourcePath) !== normalizePath3(targetFile)) {
|
|
2377
|
+
continue;
|
|
2378
|
+
}
|
|
2379
|
+
if (targetIsModule) {
|
|
2380
|
+
importers.add(row.relative_path);
|
|
2381
|
+
continue;
|
|
2382
|
+
}
|
|
2383
|
+
if (entry.kind === "named" && entry.importedName === targetLeaf) {
|
|
2384
|
+
importers.add(row.relative_path);
|
|
2385
|
+
continue;
|
|
2386
|
+
}
|
|
2387
|
+
if (entry.kind === "namespace" && entry.usedMembers.includes(targetLeaf)) {
|
|
2388
|
+
importers.add(row.relative_path);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
return [...importers].sort().map((importer) => ({
|
|
2393
|
+
symbol: target?.symbol ?? targetLeaf,
|
|
2394
|
+
shortName: target ? shortenSymbol(target.symbol) : targetLeaf,
|
|
2395
|
+
fromFile: importer
|
|
2396
|
+
}));
|
|
1647
2397
|
}
|
|
1648
2398
|
function unusedImports(db, filePattern) {
|
|
1649
2399
|
const rows = db.all(
|
|
1650
|
-
`SELECT gs.symbol, d.relative_path AS imported_in
|
|
2400
|
+
`SELECT gs.symbol, d.relative_path AS imported_in, d.relative_path AS importer
|
|
1651
2401
|
FROM mentions m
|
|
1652
2402
|
JOIN chunks c ON m.chunk_id = c.id
|
|
1653
2403
|
JOIN documents d ON c.document_id = d.id
|
|
@@ -1659,17 +2409,59 @@ function unusedImports(db, filePattern) {
|
|
|
1659
2409
|
FROM mentions ref_m
|
|
1660
2410
|
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
1661
2411
|
WHERE ref_m.symbol_id = gs.id
|
|
1662
|
-
AND ref_m.role
|
|
2412
|
+
AND ref_m.role != 1
|
|
1663
2413
|
AND ref_c.document_id = d.id
|
|
1664
2414
|
)
|
|
1665
2415
|
ORDER BY d.relative_path, gs.symbol`,
|
|
1666
2416
|
`%${filePattern}%`
|
|
1667
2417
|
);
|
|
1668
|
-
|
|
2418
|
+
const indexedResults = rows.filter((row) => !db.isIgnored(row.importer)).map((r) => ({
|
|
1669
2419
|
symbol: r.symbol,
|
|
1670
2420
|
shortName: shortenSymbol(r.symbol),
|
|
1671
2421
|
importedIn: r.imported_in
|
|
1672
2422
|
}));
|
|
2423
|
+
if (indexedResults.length > 0) {
|
|
2424
|
+
return indexedResults;
|
|
2425
|
+
}
|
|
2426
|
+
const importer = findIndexedFile(db, filePattern);
|
|
2427
|
+
if (!importer) return [];
|
|
2428
|
+
return getSourceImports(db, importer).filter((entry) => entry.kind !== "side-effect" && !entry.used).map((entry) => ({
|
|
2429
|
+
symbol: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
|
|
2430
|
+
shortName: renderImportSymbol(entry.importedName, entry.localName, entry.kind),
|
|
2431
|
+
importedIn: importer
|
|
2432
|
+
}));
|
|
2433
|
+
}
|
|
2434
|
+
function findIndexedFile(db, filePattern) {
|
|
2435
|
+
const doc = db.get(
|
|
2436
|
+
`SELECT relative_path
|
|
2437
|
+
FROM documents
|
|
2438
|
+
WHERE relative_path LIKE ?
|
|
2439
|
+
${db.pathExclusionsFor("documents")}
|
|
2440
|
+
LIMIT 1`,
|
|
2441
|
+
`%${filePattern}%`
|
|
2442
|
+
);
|
|
2443
|
+
if (!doc || db.isIgnored(doc.relative_path)) {
|
|
2444
|
+
return null;
|
|
2445
|
+
}
|
|
2446
|
+
return doc.relative_path;
|
|
2447
|
+
}
|
|
2448
|
+
function renderImportSymbol(importedName, localName, kind) {
|
|
2449
|
+
if (kind === "namespace" && importedName === "*" && localName) {
|
|
2450
|
+
return `* as ${localName}`;
|
|
2451
|
+
}
|
|
2452
|
+
if (kind === "default" && localName) {
|
|
2453
|
+
return `default as ${localName}`;
|
|
2454
|
+
}
|
|
2455
|
+
if (kind === "side-effect") {
|
|
2456
|
+
return "(side effect import)";
|
|
2457
|
+
}
|
|
2458
|
+
if (localName && localName !== importedName) {
|
|
2459
|
+
return `${importedName} as ${localName}`;
|
|
2460
|
+
}
|
|
2461
|
+
return importedName;
|
|
2462
|
+
}
|
|
2463
|
+
function normalizePath3(path2) {
|
|
2464
|
+
return path2.replace(/\\/g, "/");
|
|
1673
2465
|
}
|
|
1674
2466
|
|
|
1675
2467
|
// src/queries/outline.ts
|
|
@@ -1709,38 +2501,26 @@ function outline(db, filePattern) {
|
|
|
1709
2501
|
|
|
1710
2502
|
// src/queries/members.ts
|
|
1711
2503
|
function members(db, symbolPattern) {
|
|
1712
|
-
const
|
|
1713
|
-
|
|
1714
|
-
`%${symbolPattern}%`
|
|
1715
|
-
);
|
|
1716
|
-
if (parents.length === 0) return [];
|
|
1717
|
-
const placeholders = parents.map(() => "?").join(",");
|
|
1718
|
-
const parentSymbols = parents.map((p) => p.symbol);
|
|
2504
|
+
const parent = findFirstSymbolMatch(db, symbolPattern);
|
|
2505
|
+
if (!parent) return [];
|
|
1719
2506
|
const rows = db.all(
|
|
1720
2507
|
`SELECT gs.symbol, der.start_line, der.end_line
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
2508
|
+
FROM global_symbols gs
|
|
2509
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2510
|
+
WHERE der.document_id = ?
|
|
2511
|
+
AND gs.symbol != ?
|
|
2512
|
+
${db.symbolNoiseFor("gs")}
|
|
2513
|
+
ORDER BY der.start_line`,
|
|
2514
|
+
parent.documentId,
|
|
2515
|
+
parent.symbol
|
|
1727
2516
|
);
|
|
1728
|
-
return rows.map((
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
}
|
|
1736
|
-
return {
|
|
1737
|
-
symbol: r.symbol,
|
|
1738
|
-
shortName: shortenSymbol(r.symbol),
|
|
1739
|
-
startLine: r.start_line,
|
|
1740
|
-
endLine: r.end_line,
|
|
1741
|
-
kind
|
|
1742
|
-
};
|
|
1743
|
-
});
|
|
2517
|
+
return rows.filter((row) => isDirectChildSymbol(parent.symbol, row.symbol)).map((row) => ({
|
|
2518
|
+
symbol: row.symbol,
|
|
2519
|
+
shortName: shortenSymbol(row.symbol),
|
|
2520
|
+
startLine: row.start_line,
|
|
2521
|
+
endLine: row.end_line,
|
|
2522
|
+
kind: leafSuffix(row.symbol) ?? "unknown"
|
|
2523
|
+
}));
|
|
1744
2524
|
}
|
|
1745
2525
|
|
|
1746
2526
|
// src/queries/fan.ts
|
|
@@ -1751,7 +2531,7 @@ function fanIn(db, symbolPattern) {
|
|
|
1751
2531
|
JOIN chunks c ON m.chunk_id = c.id
|
|
1752
2532
|
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
1753
2533
|
WHERE gs.symbol LIKE ?
|
|
1754
|
-
AND m.role
|
|
2534
|
+
AND m.role != 1
|
|
1755
2535
|
GROUP BY gs.id
|
|
1756
2536
|
ORDER BY file_count DESC`,
|
|
1757
2537
|
`%${symbolPattern}%`
|
|
@@ -1771,7 +2551,7 @@ function fanOut(db, filePattern) {
|
|
|
1771
2551
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1772
2552
|
JOIN documents def_d ON der.document_id = def_d.id
|
|
1773
2553
|
WHERE d.relative_path LIKE ?
|
|
1774
|
-
AND m.role
|
|
2554
|
+
AND m.role != 1
|
|
1775
2555
|
AND def_d.id != d.id
|
|
1776
2556
|
GROUP BY d.id
|
|
1777
2557
|
ORDER BY symbol_count DESC`,
|
|
@@ -1792,7 +2572,7 @@ function topFanIn(db, opts = {}) {
|
|
|
1792
2572
|
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
1793
2573
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1794
2574
|
JOIN documents def_d ON der.document_id = def_d.id
|
|
1795
|
-
WHERE m.role
|
|
2575
|
+
WHERE m.role != 1
|
|
1796
2576
|
${db.pathExclusionsFor("def_d")}
|
|
1797
2577
|
${db.symbolNoiseFor("gs")}
|
|
1798
2578
|
${scopeFilter}
|
|
@@ -1818,7 +2598,7 @@ function topFanOut(db, opts = {}) {
|
|
|
1818
2598
|
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
1819
2599
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1820
2600
|
JOIN documents def_d ON der.document_id = def_d.id
|
|
1821
|
-
WHERE m.role
|
|
2601
|
+
WHERE m.role != 1
|
|
1822
2602
|
AND def_d.id != d.id
|
|
1823
2603
|
${db.pathExclusionsFor("d")}
|
|
1824
2604
|
${db.symbolNoiseFor("gs")}
|
|
@@ -1850,7 +2630,7 @@ function coupling(db, file1, file2) {
|
|
|
1850
2630
|
SELECT 1 FROM mentions m
|
|
1851
2631
|
JOIN chunks c ON m.chunk_id = c.id
|
|
1852
2632
|
JOIN documents d ON c.document_id = d.id
|
|
1853
|
-
WHERE m.symbol_id = gs.id AND m.role
|
|
2633
|
+
WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path LIKE ?
|
|
1854
2634
|
)
|
|
1855
2635
|
) OR (
|
|
1856
2636
|
-- Defined in file2, referenced in file1
|
|
@@ -1863,7 +2643,7 @@ function coupling(db, file1, file2) {
|
|
|
1863
2643
|
SELECT 1 FROM mentions m
|
|
1864
2644
|
JOIN chunks c ON m.chunk_id = c.id
|
|
1865
2645
|
JOIN documents d ON c.document_id = d.id
|
|
1866
|
-
WHERE m.symbol_id = gs.id AND m.role
|
|
2646
|
+
WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path LIKE ?
|
|
1867
2647
|
)
|
|
1868
2648
|
)`,
|
|
1869
2649
|
`%${file1}%`,
|
|
@@ -1891,7 +2671,7 @@ function topCoupling(db, opts = {}) {
|
|
|
1891
2671
|
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
1892
2672
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
1893
2673
|
JOIN documents def_d ON der.document_id = def_d.id
|
|
1894
|
-
WHERE m.role
|
|
2674
|
+
WHERE m.role != 1
|
|
1895
2675
|
AND def_d.id != ref_d.id
|
|
1896
2676
|
${db.pathExclusionsFor("def_d", "ref_d")}
|
|
1897
2677
|
${scopeFilter}
|
|
@@ -1972,7 +2752,7 @@ function bottlenecks(db, opts = {}) {
|
|
|
1972
2752
|
(SELECT COUNT(DISTINCT ref_c.document_id)
|
|
1973
2753
|
FROM mentions ref_m
|
|
1974
2754
|
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
1975
|
-
WHERE ref_m.symbol_id = gs.id AND ref_m.role
|
|
2755
|
+
WHERE ref_m.symbol_id = gs.id AND ref_m.role != 1
|
|
1976
2756
|
) AS fan_in,
|
|
1977
2757
|
(SELECT COUNT(DISTINCT ref_gs.id)
|
|
1978
2758
|
FROM mentions ref_m
|
|
@@ -1980,7 +2760,7 @@ function bottlenecks(db, opts = {}) {
|
|
|
1980
2760
|
JOIN global_symbols ref_gs ON ref_m.symbol_id = ref_gs.id
|
|
1981
2761
|
JOIN defn_enclosing_ranges ref_der ON ref_gs.id = ref_der.symbol_id
|
|
1982
2762
|
WHERE ref_c.document_id = def_d.id
|
|
1983
|
-
AND ref_m.role
|
|
2763
|
+
AND ref_m.role != 1
|
|
1984
2764
|
AND ref_der.document_id != def_d.id
|
|
1985
2765
|
) AS fan_out
|
|
1986
2766
|
FROM global_symbols gs
|
|
@@ -2032,18 +2812,18 @@ function isolated(db, opts = {}) {
|
|
|
2032
2812
|
AND NOT EXISTS (
|
|
2033
2813
|
SELECT 1 FROM mentions m
|
|
2034
2814
|
JOIN chunks c ON m.chunk_id = c.id
|
|
2035
|
-
WHERE m.symbol_id = gs.id AND m.role
|
|
2815
|
+
WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id != d.id
|
|
2036
2816
|
)
|
|
2037
2817
|
-- No same-file references either
|
|
2038
2818
|
AND NOT EXISTS (
|
|
2039
2819
|
SELECT 1 FROM mentions m
|
|
2040
2820
|
JOIN chunks c ON m.chunk_id = c.id
|
|
2041
|
-
WHERE m.symbol_id = gs.id AND m.role
|
|
2821
|
+
WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id = d.id
|
|
2042
2822
|
)
|
|
2043
2823
|
ORDER BY loc DESC, d.relative_path`,
|
|
2044
2824
|
minLoc
|
|
2045
2825
|
);
|
|
2046
|
-
return rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => ({
|
|
2826
|
+
return rows.filter((r) => !db.isIgnored(r.relative_path)).filter((r) => !isEntrySurface(db, r.relative_path)).map((r) => ({
|
|
2047
2827
|
symbol: r.symbol,
|
|
2048
2828
|
shortName: shortenSymbol(r.symbol),
|
|
2049
2829
|
relativePath: r.relative_path,
|
|
@@ -2216,80 +2996,6 @@ function kindCounts(db, opts = {}) {
|
|
|
2216
2996
|
}));
|
|
2217
2997
|
}
|
|
2218
2998
|
|
|
2219
|
-
// src/queries/test-coverage.ts
|
|
2220
|
-
function testCoverage(db, symbolPattern) {
|
|
2221
|
-
const syms = db.all(
|
|
2222
|
-
`SELECT gs.id, gs.symbol, d.relative_path
|
|
2223
|
-
FROM global_symbols gs
|
|
2224
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2225
|
-
JOIN documents d ON der.document_id = d.id
|
|
2226
|
-
WHERE gs.symbol LIKE ?
|
|
2227
|
-
${db.pathExclusionsFor("d")}
|
|
2228
|
-
${db.symbolNoiseFor("gs")}
|
|
2229
|
-
ORDER BY d.relative_path`,
|
|
2230
|
-
`%${symbolPattern}%`
|
|
2231
|
-
);
|
|
2232
|
-
const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
|
|
2233
|
-
return syms.filter((s) => !db.isIgnored(s.relative_path)).map((s) => {
|
|
2234
|
-
const testFiles = db.all(
|
|
2235
|
-
`SELECT DISTINCT ref_d.relative_path
|
|
2236
|
-
FROM mentions m
|
|
2237
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
2238
|
-
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
2239
|
-
WHERE m.symbol_id = ?
|
|
2240
|
-
AND m.role = 0
|
|
2241
|
-
AND (${testPatternSql})
|
|
2242
|
-
ORDER BY ref_d.relative_path`,
|
|
2243
|
-
s.id
|
|
2244
|
-
).map((r) => r.relative_path);
|
|
2245
|
-
return {
|
|
2246
|
-
symbol: s.symbol,
|
|
2247
|
-
shortName: shortenSymbol(s.symbol),
|
|
2248
|
-
definedIn: s.relative_path,
|
|
2249
|
-
testFiles,
|
|
2250
|
-
covered: testFiles.length > 0
|
|
2251
|
-
};
|
|
2252
|
-
});
|
|
2253
|
-
}
|
|
2254
|
-
function testCoverageSummary(db, opts = {}) {
|
|
2255
|
-
const { scope, minLoc = 3 } = opts;
|
|
2256
|
-
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
2257
|
-
const testPatternSql = testFileExclusionSql("d");
|
|
2258
|
-
const symbols2 = db.all(
|
|
2259
|
-
`SELECT gs.id
|
|
2260
|
-
FROM global_symbols gs
|
|
2261
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
2262
|
-
JOIN documents d ON der.document_id = d.id
|
|
2263
|
-
WHERE 1 = 1
|
|
2264
|
-
${db.pathExclusionsFor("d")}
|
|
2265
|
-
AND ${testPatternSql}
|
|
2266
|
-
${db.symbolNoiseFor("gs")}
|
|
2267
|
-
AND gs.symbol NOT LIKE '%#%'
|
|
2268
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
2269
|
-
${scopeFilter}`,
|
|
2270
|
-
minLoc
|
|
2271
|
-
);
|
|
2272
|
-
const testRefSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
|
|
2273
|
-
let covered = 0;
|
|
2274
|
-
for (const s of symbols2) {
|
|
2275
|
-
const hasTest = db.get(
|
|
2276
|
-
`SELECT COUNT(*) AS c FROM mentions m
|
|
2277
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
2278
|
-
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
2279
|
-
WHERE m.symbol_id = ? AND m.role = 0 AND (${testRefSql})`,
|
|
2280
|
-
s.id
|
|
2281
|
-
);
|
|
2282
|
-
if (hasTest && hasTest.c > 0) covered++;
|
|
2283
|
-
}
|
|
2284
|
-
const total = symbols2.length;
|
|
2285
|
-
return {
|
|
2286
|
-
total,
|
|
2287
|
-
covered,
|
|
2288
|
-
uncovered: total - covered,
|
|
2289
|
-
percent: total > 0 ? Math.round(covered / total * 100) : 0
|
|
2290
|
-
};
|
|
2291
|
-
}
|
|
2292
|
-
|
|
2293
2999
|
// src/queries/doc-coverage.ts
|
|
2294
3000
|
function docCoverage(db, opts = {}) {
|
|
2295
3001
|
const { scope, minLoc = 3, limit = 50 } = opts;
|
|
@@ -2393,10 +3099,12 @@ function deepChains(db, opts = {}) {
|
|
|
2393
3099
|
|
|
2394
3100
|
// src/queries/hierarchy.ts
|
|
2395
3101
|
function hierarchy(db, symbolPattern) {
|
|
3102
|
+
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
3103
|
+
if (!match) return [];
|
|
2396
3104
|
const sym = db.get(
|
|
2397
3105
|
`SELECT symbol, enclosing_symbol FROM global_symbols
|
|
2398
|
-
WHERE
|
|
2399
|
-
|
|
3106
|
+
WHERE id = ? LIMIT 1`,
|
|
3107
|
+
match.symbolId
|
|
2400
3108
|
);
|
|
2401
3109
|
if (!sym) return [];
|
|
2402
3110
|
const chain = [
|
|
@@ -2420,7 +3128,30 @@ function hierarchy(db, symbolPattern) {
|
|
|
2420
3128
|
current = parent.enclosing_symbol;
|
|
2421
3129
|
depth++;
|
|
2422
3130
|
}
|
|
2423
|
-
|
|
3131
|
+
if (chain.length > 1) {
|
|
3132
|
+
return chain;
|
|
3133
|
+
}
|
|
3134
|
+
const parsed = parseSymbol(sym.symbol);
|
|
3135
|
+
if ("kind" in parsed) {
|
|
3136
|
+
return chain;
|
|
3137
|
+
}
|
|
3138
|
+
const descriptors = parsed.descriptors;
|
|
3139
|
+
if (descriptors.length <= 1) {
|
|
3140
|
+
return chain;
|
|
3141
|
+
}
|
|
3142
|
+
const syntheticChain = [chain[0]];
|
|
3143
|
+
for (let i = descriptors.length - 2, syntheticDepth = 1; i >= 0; i--, syntheticDepth++) {
|
|
3144
|
+
const partial = descriptors.slice(0, i + 1);
|
|
3145
|
+
const shortName = partial.map(
|
|
3146
|
+
(descriptor) => descriptor.suffix === "method" ? `${descriptor.name}()` : descriptor.name.replace(/\.(ts|tsx|js|jsx|mjs|cjs|py|pyi|rs|java|scala|kt|kts|rb|go|cs|vb|dart|php|c|cc|cpp|cxx|h|hpp)$/, "")
|
|
3147
|
+
).join(":");
|
|
3148
|
+
syntheticChain.push({
|
|
3149
|
+
symbol: shortName,
|
|
3150
|
+
shortName,
|
|
3151
|
+
depth: syntheticDepth
|
|
3152
|
+
});
|
|
3153
|
+
}
|
|
3154
|
+
return syntheticChain;
|
|
2424
3155
|
}
|
|
2425
3156
|
|
|
2426
3157
|
// src/queries/call-graph.ts
|
|
@@ -2440,7 +3171,7 @@ function callGraph(db, symbolPattern) {
|
|
|
2440
3171
|
JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
|
|
2441
3172
|
JOIN documents caller_d ON caller_der.document_id = caller_d.id
|
|
2442
3173
|
WHERE m.symbol_id = ?
|
|
2443
|
-
AND m.role
|
|
3174
|
+
AND m.role != 1
|
|
2444
3175
|
AND caller_gs.id != ?
|
|
2445
3176
|
${db.symbolNoiseFor("caller_gs")}
|
|
2446
3177
|
${db.pathExclusionsFor("caller_d")}
|
|
@@ -2471,6 +3202,7 @@ function similar(db, symbolPattern, opts = {}) {
|
|
|
2471
3202
|
const { minSimilarity = 0.4, limit = 20 } = opts;
|
|
2472
3203
|
const target = findCallees(db, symbolPattern);
|
|
2473
3204
|
if (!target || target.callees.size === 0) return [];
|
|
3205
|
+
if (!isFunctionLikeSymbol(target.symbol)) return [];
|
|
2474
3206
|
const candidates = getAllCalleeFingerprints(db, {
|
|
2475
3207
|
minCallees: 3,
|
|
2476
3208
|
excludeSymbol: target.symbol
|
|
@@ -2620,6 +3352,7 @@ function getAllCalleeFingerprints(db, opts) {
|
|
|
2620
3352
|
const fingerprints = [];
|
|
2621
3353
|
for (const sym of symbols2) {
|
|
2622
3354
|
if (db.isIgnored(sym.relative_path)) continue;
|
|
3355
|
+
if (!isFunctionLikeSymbol(sym.symbol)) continue;
|
|
2623
3356
|
const calleeRows = getCalleeRowsForSymbol(db, {
|
|
2624
3357
|
documentId: sym.document_id,
|
|
2625
3358
|
startLine: sym.start_line,
|
|
@@ -3068,7 +3801,7 @@ function affected(db, symbolPattern, opts = {}) {
|
|
|
3068
3801
|
JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
|
|
3069
3802
|
JOIN documents enc_d ON enc_der.document_id = enc_d.id
|
|
3070
3803
|
WHERE m.symbol_id IN (${placeholders})
|
|
3071
|
-
AND m.role
|
|
3804
|
+
AND m.role != 1
|
|
3072
3805
|
AND enc_gs.id NOT IN (${placeholders})
|
|
3073
3806
|
${db.symbolNoiseFor("enc_gs")}
|
|
3074
3807
|
${db.pathExclusionsFor("enc_d")}
|
|
@@ -3113,39 +3846,24 @@ function changeSurface(db, filePattern) {
|
|
|
3113
3846
|
ORDER BY der.start_line`,
|
|
3114
3847
|
doc.id
|
|
3115
3848
|
);
|
|
3116
|
-
const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
|
|
3117
3849
|
const symbols2 = [];
|
|
3118
3850
|
let totalExternalConsumers = 0;
|
|
3119
|
-
let coveredCount = 0;
|
|
3120
3851
|
for (const sym of syms) {
|
|
3121
3852
|
const consumerRow = db.get(
|
|
3122
3853
|
`SELECT COUNT(DISTINCT c.document_id) AS consumer_count
|
|
3123
3854
|
FROM mentions m
|
|
3124
3855
|
JOIN chunks c ON m.chunk_id = c.id
|
|
3125
3856
|
WHERE m.symbol_id = ?
|
|
3126
|
-
AND m.role
|
|
3857
|
+
AND m.role != 1
|
|
3127
3858
|
AND c.document_id != ?`,
|
|
3128
3859
|
sym.symbol_id,
|
|
3129
3860
|
doc.id
|
|
3130
3861
|
);
|
|
3131
3862
|
const externalConsumers = consumerRow?.consumer_count ?? 0;
|
|
3132
|
-
const testFiles = db.all(
|
|
3133
|
-
`SELECT DISTINCT ref_d.relative_path
|
|
3134
|
-
FROM mentions m
|
|
3135
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
3136
|
-
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
3137
|
-
WHERE m.symbol_id = ?
|
|
3138
|
-
AND m.role = 0
|
|
3139
|
-
AND (${testPatternSql})
|
|
3140
|
-
ORDER BY ref_d.relative_path`,
|
|
3141
|
-
sym.symbol_id
|
|
3142
|
-
).map((r) => r.relative_path);
|
|
3143
|
-
const hasTests = testFiles.length > 0;
|
|
3144
|
-
if (hasTests) coveredCount++;
|
|
3145
3863
|
let riskLevel;
|
|
3146
|
-
if (externalConsumers > 10
|
|
3864
|
+
if (externalConsumers > 10) {
|
|
3147
3865
|
riskLevel = "high";
|
|
3148
|
-
} else if (externalConsumers >
|
|
3866
|
+
} else if (externalConsumers > 0) {
|
|
3149
3867
|
riskLevel = "medium";
|
|
3150
3868
|
} else {
|
|
3151
3869
|
riskLevel = "low";
|
|
@@ -3157,42 +3875,33 @@ function changeSurface(db, filePattern) {
|
|
|
3157
3875
|
startLine: sym.start_line,
|
|
3158
3876
|
endLine: sym.end_line,
|
|
3159
3877
|
externalConsumers,
|
|
3160
|
-
testFiles,
|
|
3161
3878
|
riskLevel
|
|
3162
3879
|
});
|
|
3163
3880
|
}
|
|
3164
|
-
const testCoveragePercent = symbols2.length > 0 ? Math.round(coveredCount / symbols2.length * 100) : 0;
|
|
3165
3881
|
return {
|
|
3166
3882
|
file: doc.relative_path,
|
|
3167
3883
|
symbols: symbols2,
|
|
3168
|
-
totalExternalConsumers
|
|
3169
|
-
testCoveragePercent
|
|
3884
|
+
totalExternalConsumers
|
|
3170
3885
|
};
|
|
3171
3886
|
}
|
|
3172
3887
|
|
|
3173
3888
|
// src/queries/diff-impact.ts
|
|
3174
|
-
import { execFileSync as
|
|
3889
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
3175
3890
|
function diffImpact(db, opts = {}) {
|
|
3176
3891
|
const { base = "HEAD" } = opts;
|
|
3177
3892
|
let changedFileLines;
|
|
3178
3893
|
try {
|
|
3179
|
-
|
|
3180
|
-
encoding: "utf-8",
|
|
3181
|
-
cwd: db.config.projectRoot,
|
|
3182
|
-
timeout: 1e4
|
|
3183
|
-
});
|
|
3184
|
-
changedFileLines = stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
3894
|
+
changedFileLines = getChangedFiles(db.config.projectRoot, base);
|
|
3185
3895
|
} catch {
|
|
3186
3896
|
return {
|
|
3187
3897
|
changedFiles: [],
|
|
3188
3898
|
changedSymbols: [],
|
|
3189
3899
|
affectedConsumers: [],
|
|
3190
|
-
uncoveredSymbols: [],
|
|
3191
3900
|
summary: {
|
|
3192
3901
|
totalChangedFiles: 0,
|
|
3193
3902
|
totalChangedSymbols: 0,
|
|
3194
3903
|
totalAffectedFiles: 0,
|
|
3195
|
-
|
|
3904
|
+
note: "Unable to compute git diff."
|
|
3196
3905
|
}
|
|
3197
3906
|
};
|
|
3198
3907
|
}
|
|
@@ -3201,12 +3910,11 @@ function diffImpact(db, opts = {}) {
|
|
|
3201
3910
|
changedFiles: [],
|
|
3202
3911
|
changedSymbols: [],
|
|
3203
3912
|
affectedConsumers: [],
|
|
3204
|
-
uncoveredSymbols: [],
|
|
3205
3913
|
summary: {
|
|
3206
3914
|
totalChangedFiles: 0,
|
|
3207
3915
|
totalChangedSymbols: 0,
|
|
3208
3916
|
totalAffectedFiles: 0,
|
|
3209
|
-
|
|
3917
|
+
note: "No changed files found."
|
|
3210
3918
|
}
|
|
3211
3919
|
};
|
|
3212
3920
|
}
|
|
@@ -3229,12 +3937,11 @@ function diffImpact(db, opts = {}) {
|
|
|
3229
3937
|
changedFiles: changedFileLines,
|
|
3230
3938
|
changedSymbols: [],
|
|
3231
3939
|
affectedConsumers: [],
|
|
3232
|
-
uncoveredSymbols: [],
|
|
3233
3940
|
summary: {
|
|
3234
3941
|
totalChangedFiles: changedFileLines.length,
|
|
3235
3942
|
totalChangedSymbols: 0,
|
|
3236
3943
|
totalAffectedFiles: 0,
|
|
3237
|
-
|
|
3944
|
+
note: "Changed files are not present in the current SCIP index."
|
|
3238
3945
|
}
|
|
3239
3946
|
};
|
|
3240
3947
|
}
|
|
@@ -3249,18 +3956,15 @@ function diffImpact(db, opts = {}) {
|
|
|
3249
3956
|
ORDER BY d.relative_path`,
|
|
3250
3957
|
...changedDocIds
|
|
3251
3958
|
);
|
|
3252
|
-
const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
|
|
3253
3959
|
const changedSymbols = [];
|
|
3254
3960
|
const consumerMap = /* @__PURE__ */ new Map();
|
|
3255
|
-
const uncoveredSymbols = [];
|
|
3256
|
-
let coveredCount = 0;
|
|
3257
3961
|
for (const sym of syms) {
|
|
3258
3962
|
const fanInRow = db.get(
|
|
3259
3963
|
`SELECT COUNT(DISTINCT c.document_id) AS fan_in
|
|
3260
3964
|
FROM mentions m
|
|
3261
3965
|
JOIN chunks c ON m.chunk_id = c.id
|
|
3262
3966
|
WHERE m.symbol_id = ?
|
|
3263
|
-
AND m.role
|
|
3967
|
+
AND m.role != 1`,
|
|
3264
3968
|
sym.symbol_id
|
|
3265
3969
|
);
|
|
3266
3970
|
const fanIn2 = fanInRow?.fan_in ?? 0;
|
|
@@ -3277,7 +3981,7 @@ function diffImpact(db, opts = {}) {
|
|
|
3277
3981
|
JOIN chunks c ON m.chunk_id = c.id
|
|
3278
3982
|
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
3279
3983
|
WHERE m.symbol_id = ?
|
|
3280
|
-
AND m.role
|
|
3984
|
+
AND m.role != 1
|
|
3281
3985
|
AND ref_d.relative_path NOT IN (${changedFiles.map(() => "?").join(",")})
|
|
3282
3986
|
${db.pathExclusionsFor("ref_d")}`,
|
|
3283
3987
|
sym.symbol_id,
|
|
@@ -3290,42 +3994,39 @@ function diffImpact(db, opts = {}) {
|
|
|
3290
3994
|
}
|
|
3291
3995
|
consumerMap.get(consumer.relative_path).add(shortName);
|
|
3292
3996
|
}
|
|
3293
|
-
const hasTest = db.get(
|
|
3294
|
-
`SELECT COUNT(*) AS c
|
|
3295
|
-
FROM mentions m
|
|
3296
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
3297
|
-
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
3298
|
-
WHERE m.symbol_id = ?
|
|
3299
|
-
AND m.role = 0
|
|
3300
|
-
AND (${testPatternSql})`,
|
|
3301
|
-
sym.symbol_id
|
|
3302
|
-
);
|
|
3303
|
-
if (hasTest && hasTest.c > 0) {
|
|
3304
|
-
coveredCount++;
|
|
3305
|
-
} else {
|
|
3306
|
-
uncoveredSymbols.push({
|
|
3307
|
-
symbol: sym.symbol,
|
|
3308
|
-
shortName,
|
|
3309
|
-
file: sym.relative_path
|
|
3310
|
-
});
|
|
3311
|
-
}
|
|
3312
3997
|
}
|
|
3313
3998
|
const affectedConsumers = [...consumerMap.entries()].map(([file, symbols2]) => ({ file, consumedSymbols: symbols2.size })).sort((a, b) => b.consumedSymbols - a.consumedSymbols);
|
|
3314
|
-
const totalSymbols = changedSymbols.length;
|
|
3315
|
-
const testCoveragePercent = totalSymbols > 0 ? Math.round(coveredCount / totalSymbols * 100) : 0;
|
|
3316
3999
|
return {
|
|
3317
4000
|
changedFiles,
|
|
3318
4001
|
changedSymbols,
|
|
3319
4002
|
affectedConsumers,
|
|
3320
|
-
uncoveredSymbols,
|
|
3321
4003
|
summary: {
|
|
3322
4004
|
totalChangedFiles: changedFiles.length,
|
|
3323
|
-
totalChangedSymbols:
|
|
3324
|
-
totalAffectedFiles: affectedConsumers.length
|
|
3325
|
-
testCoveragePercent
|
|
4005
|
+
totalChangedSymbols: changedSymbols.length,
|
|
4006
|
+
totalAffectedFiles: affectedConsumers.length
|
|
3326
4007
|
}
|
|
3327
4008
|
};
|
|
3328
4009
|
}
|
|
4010
|
+
function getChangedFiles(projectRoot, base) {
|
|
4011
|
+
const diff = execFileSync4("git", ["diff", "--name-only", base], {
|
|
4012
|
+
encoding: "utf-8",
|
|
4013
|
+
cwd: projectRoot,
|
|
4014
|
+
timeout: 1e4
|
|
4015
|
+
});
|
|
4016
|
+
const staged = execFileSync4("git", ["diff", "--name-only", "--cached", base], {
|
|
4017
|
+
encoding: "utf-8",
|
|
4018
|
+
cwd: projectRoot,
|
|
4019
|
+
timeout: 1e4
|
|
4020
|
+
});
|
|
4021
|
+
const untracked = execFileSync4("git", ["ls-files", "--others", "--exclude-standard"], {
|
|
4022
|
+
encoding: "utf-8",
|
|
4023
|
+
cwd: projectRoot,
|
|
4024
|
+
timeout: 1e4
|
|
4025
|
+
});
|
|
4026
|
+
return [...new Set(
|
|
4027
|
+
[diff, staged, untracked].flatMap((chunk) => chunk.split("\n")).map((line) => line.trim()).filter((line) => line.length > 0)
|
|
4028
|
+
)];
|
|
4029
|
+
}
|
|
3329
4030
|
|
|
3330
4031
|
// src/queries/drift.ts
|
|
3331
4032
|
import path from "path";
|
|
@@ -3335,9 +4036,10 @@ function drift(db, opts) {
|
|
|
3335
4036
|
const symbolRefs = buildSymbolRefGraph(db, scope);
|
|
3336
4037
|
const results = [];
|
|
3337
4038
|
for (const [file, deps2] of depGraph) {
|
|
3338
|
-
if (
|
|
4039
|
+
if (shouldSkipDriftFile(file)) continue;
|
|
3339
4040
|
const referencedFiles = symbolRefs.get(file) ?? /* @__PURE__ */ new Set();
|
|
3340
4041
|
for (const dep of deps2) {
|
|
4042
|
+
if (shouldSkipDriftFile(dep)) continue;
|
|
3341
4043
|
if (!referencedFiles.has(dep)) {
|
|
3342
4044
|
if (isLikelyTypeOnlyDep(dep)) continue;
|
|
3343
4045
|
results.push({
|
|
@@ -3351,10 +4053,11 @@ function drift(db, opts) {
|
|
|
3351
4053
|
}
|
|
3352
4054
|
const layerRules = inferLayerRules(depGraph);
|
|
3353
4055
|
for (const [file, deps2] of depGraph) {
|
|
3354
|
-
if (
|
|
3355
|
-
const fileLayer =
|
|
4056
|
+
if (shouldSkipDriftFile(file)) continue;
|
|
4057
|
+
const fileLayer = getArchitecturalLayer(file);
|
|
3356
4058
|
for (const dep of deps2) {
|
|
3357
|
-
|
|
4059
|
+
if (shouldSkipDriftFile(dep)) continue;
|
|
4060
|
+
const depLayer = getArchitecturalLayer(dep);
|
|
3358
4061
|
if (fileLayer === depLayer) continue;
|
|
3359
4062
|
const violation = layerRules.get(`${fileLayer}->${depLayer}`);
|
|
3360
4063
|
if (violation === "violation") {
|
|
@@ -3378,14 +4081,16 @@ function drift(db, opts) {
|
|
|
3378
4081
|
if (files2.length < 3) continue;
|
|
3379
4082
|
const depFreq = /* @__PURE__ */ new Map();
|
|
3380
4083
|
for (const file of files2) {
|
|
3381
|
-
if (
|
|
4084
|
+
if (shouldSkipDriftFile(file)) continue;
|
|
3382
4085
|
for (const dep of depGraph.get(file) ?? []) {
|
|
4086
|
+
if (shouldSkipDriftFile(dep)) continue;
|
|
3383
4087
|
depFreq.set(dep, (depFreq.get(dep) ?? 0) + 1);
|
|
3384
4088
|
}
|
|
3385
4089
|
}
|
|
3386
4090
|
for (const file of files2) {
|
|
3387
|
-
if (
|
|
4091
|
+
if (shouldSkipDriftFile(file)) continue;
|
|
3388
4092
|
for (const dep of depGraph.get(file) ?? []) {
|
|
4093
|
+
if (shouldSkipDriftFile(dep)) continue;
|
|
3389
4094
|
if ((depFreq.get(dep) ?? 0) === 1) {
|
|
3390
4095
|
if (path.dirname(dep) === dir) continue;
|
|
3391
4096
|
results.push({
|
|
@@ -3416,7 +4121,7 @@ function buildSymbolRefGraph(db, scope) {
|
|
|
3416
4121
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
3417
4122
|
JOIN documents d2 ON der.document_id = d2.id
|
|
3418
4123
|
WHERE d1.id != d2.id
|
|
3419
|
-
AND m.role
|
|
4124
|
+
AND m.role != 1
|
|
3420
4125
|
${db.pathExclusionsFor("d1", "d2")}
|
|
3421
4126
|
${scopeFilter}`
|
|
3422
4127
|
);
|
|
@@ -3432,10 +4137,12 @@ function inferLayerRules(depGraph) {
|
|
|
3432
4137
|
const layerEdges = /* @__PURE__ */ new Map();
|
|
3433
4138
|
const layerSet = /* @__PURE__ */ new Set();
|
|
3434
4139
|
for (const [file, deps2] of depGraph) {
|
|
3435
|
-
|
|
4140
|
+
if (shouldSkipDriftFile(file)) continue;
|
|
4141
|
+
const fromLayer = getArchitecturalLayer(file);
|
|
3436
4142
|
layerSet.add(fromLayer);
|
|
3437
4143
|
for (const dep of deps2) {
|
|
3438
|
-
|
|
4144
|
+
if (shouldSkipDriftFile(dep)) continue;
|
|
4145
|
+
const toLayer = getArchitecturalLayer(dep);
|
|
3439
4146
|
if (fromLayer === toLayer) continue;
|
|
3440
4147
|
layerSet.add(toLayer);
|
|
3441
4148
|
const key = `${fromLayer}->${toLayer}`;
|
|
@@ -3448,13 +4155,23 @@ function inferLayerRules(depGraph) {
|
|
|
3448
4155
|
}
|
|
3449
4156
|
return rules;
|
|
3450
4157
|
}
|
|
3451
|
-
function
|
|
3452
|
-
const
|
|
3453
|
-
|
|
4158
|
+
function getArchitecturalLayer(filePath) {
|
|
4159
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
4160
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
4161
|
+
if (parts.length <= 1) {
|
|
4162
|
+
return "(root)";
|
|
4163
|
+
}
|
|
4164
|
+
if (parts.length >= 3 && ["src", "lib", "app", "server", "client"].includes(parts[0])) {
|
|
4165
|
+
return `${parts[0]}/${parts[1]}`;
|
|
4166
|
+
}
|
|
4167
|
+
return parts[0];
|
|
3454
4168
|
}
|
|
3455
4169
|
function isLikelyTypeOnlyDep(dep) {
|
|
3456
4170
|
return dep.includes("types") || dep.endsWith(".d.ts");
|
|
3457
4171
|
}
|
|
4172
|
+
function shouldSkipDriftFile(filePath) {
|
|
4173
|
+
return isStructuralRole(path.basename(filePath)) || isTestLikePath(filePath);
|
|
4174
|
+
}
|
|
3458
4175
|
function isStructuralRole(basename) {
|
|
3459
4176
|
if (basename === "index.ts" || basename === "index.js") return true;
|
|
3460
4177
|
if (basename === "cli.ts" || basename === "main.ts" || basename === "main.rs") return true;
|
|
@@ -3462,6 +4179,11 @@ function isStructuralRole(basename) {
|
|
|
3462
4179
|
if (basename === "health.ts" || basename === "health.js") return true;
|
|
3463
4180
|
return false;
|
|
3464
4181
|
}
|
|
4182
|
+
function isTestLikePath(filePath) {
|
|
4183
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
4184
|
+
const basename = path.basename(normalized);
|
|
4185
|
+
return normalized.includes("/__tests__/") || normalized.includes("/tests/") || normalized.includes("/test/") || /\.(test|spec)\.[A-Za-z0-9]+$/.test(basename) || /_(test|spec)\.[A-Za-z0-9]+$/.test(basename) || /^test[_-]/.test(basename) || /^test\./.test(basename);
|
|
4186
|
+
}
|
|
3465
4187
|
|
|
3466
4188
|
// src/queries/wrapper-candidates.ts
|
|
3467
4189
|
function wrapperCandidates(db, opts) {
|
|
@@ -3486,7 +4208,7 @@ function wrapperCandidates(db, opts) {
|
|
|
3486
4208
|
AND ref_c.end_line <= caller_der.end_line
|
|
3487
4209
|
JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
|
|
3488
4210
|
WHERE ref_m.symbol_id = gs.id
|
|
3489
|
-
AND ref_m.role
|
|
4211
|
+
AND ref_m.role != 1
|
|
3490
4212
|
AND ref_c.document_id != der.document_id
|
|
3491
4213
|
LIMIT 1
|
|
3492
4214
|
) AS caller_symbol,
|
|
@@ -3503,11 +4225,11 @@ function wrapperCandidates(db, opts) {
|
|
|
3503
4225
|
AND ref_c2.start_line >= caller_der2.start_line
|
|
3504
4226
|
AND ref_c2.end_line <= caller_der2.end_line
|
|
3505
4227
|
WHERE ref_m2.symbol_id = gs.id
|
|
3506
|
-
AND ref_m2.role
|
|
4228
|
+
AND ref_m2.role != 1
|
|
3507
4229
|
AND ref_c2.document_id != der.document_id
|
|
3508
4230
|
LIMIT 1
|
|
3509
4231
|
)
|
|
3510
|
-
AND caller_ref_m.role
|
|
4232
|
+
AND caller_ref_m.role != 1
|
|
3511
4233
|
) AS caller_fan_in
|
|
3512
4234
|
FROM global_symbols gs
|
|
3513
4235
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
@@ -3527,7 +4249,7 @@ function wrapperCandidates(db, opts) {
|
|
|
3527
4249
|
FROM mentions ref_m
|
|
3528
4250
|
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
3529
4251
|
WHERE ref_m.symbol_id = gs.id
|
|
3530
|
-
AND ref_m.role
|
|
4252
|
+
AND ref_m.role != 1
|
|
3531
4253
|
AND ref_c.document_id != der.document_id
|
|
3532
4254
|
) = 1
|
|
3533
4255
|
) WHERE caller_symbol IS NOT NULL AND caller_fan_in > 3
|
|
@@ -3624,7 +4346,7 @@ function staleAbstractions(db, opts) {
|
|
|
3624
4346
|
FROM mentions ref_m
|
|
3625
4347
|
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
3626
4348
|
WHERE ref_m.symbol_id = gs.id
|
|
3627
|
-
AND ref_m.role
|
|
4349
|
+
AND ref_m.role != 1
|
|
3628
4350
|
AND ref_c.document_id != der.document_id
|
|
3629
4351
|
) AS consumers
|
|
3630
4352
|
FROM global_symbols gs
|
|
@@ -3676,7 +4398,7 @@ function complexityHotspots(db, opts) {
|
|
|
3676
4398
|
(SELECT COUNT(DISTINCT ref_c.document_id)
|
|
3677
4399
|
FROM mentions ref_m
|
|
3678
4400
|
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
3679
|
-
WHERE ref_m.symbol_id = gs.id AND ref_m.role
|
|
4401
|
+
WHERE ref_m.symbol_id = gs.id AND ref_m.role != 1
|
|
3680
4402
|
) AS fan_in,
|
|
3681
4403
|
-- fanOut: distinct symbols referenced within this definition range
|
|
3682
4404
|
-- that are defined in different files
|
|
@@ -3688,7 +4410,7 @@ function complexityHotspots(db, opts) {
|
|
|
3688
4410
|
WHERE out_c.document_id = der.document_id
|
|
3689
4411
|
AND out_c.start_line >= der.start_line
|
|
3690
4412
|
AND out_c.end_line <= der.end_line
|
|
3691
|
-
AND out_m.role
|
|
4413
|
+
AND out_m.role != 1
|
|
3692
4414
|
AND out_gs.id != gs.id
|
|
3693
4415
|
AND out_der.document_id != der.document_id
|
|
3694
4416
|
) AS fan_out,
|
|
@@ -3700,7 +4422,7 @@ function complexityHotspots(db, opts) {
|
|
|
3700
4422
|
WHERE callee_c.document_id = der.document_id
|
|
3701
4423
|
AND callee_c.start_line >= der.start_line
|
|
3702
4424
|
AND callee_c.end_line <= der.end_line
|
|
3703
|
-
AND callee_m.role
|
|
4425
|
+
AND callee_m.role != 1
|
|
3704
4426
|
AND callee_gs.id != gs.id
|
|
3705
4427
|
) AS callee_count
|
|
3706
4428
|
FROM global_symbols gs
|
|
@@ -3717,7 +4439,7 @@ function complexityHotspots(db, opts) {
|
|
|
3717
4439
|
* CAST((SELECT COUNT(DISTINCT ref_c2.document_id)
|
|
3718
4440
|
FROM mentions ref_m2
|
|
3719
4441
|
JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id
|
|
3720
|
-
WHERE ref_m2.symbol_id = gs.id AND ref_m2.role
|
|
4442
|
+
WHERE ref_m2.symbol_id = gs.id AND ref_m2.role != 1
|
|
3721
4443
|
) AS REAL) / 5.0
|
|
3722
4444
|
* MAX(CAST((SELECT COUNT(DISTINCT out_gs2.id)
|
|
3723
4445
|
FROM mentions out_m2
|
|
@@ -3727,7 +4449,7 @@ function complexityHotspots(db, opts) {
|
|
|
3727
4449
|
WHERE out_c2.document_id = der.document_id
|
|
3728
4450
|
AND out_c2.start_line >= der.start_line
|
|
3729
4451
|
AND out_c2.end_line <= der.end_line
|
|
3730
|
-
AND out_m2.role
|
|
4452
|
+
AND out_m2.role != 1
|
|
3731
4453
|
AND out_gs2.id != gs.id
|
|
3732
4454
|
AND out_der2.document_id != der.document_id
|
|
3733
4455
|
) AS REAL) / 5.0, 1.0)
|
|
@@ -3756,7 +4478,7 @@ function complexityHotspots(db, opts) {
|
|
|
3756
4478
|
function health(db, opts = {}) {
|
|
3757
4479
|
const { scope } = opts;
|
|
3758
4480
|
const s = stats(db);
|
|
3759
|
-
const deadResult = dead(db, { scope, minLoc: 3, skipBarrels:
|
|
4481
|
+
const deadResult = dead(db, { scope, minLoc: 3, skipBarrels: true });
|
|
3760
4482
|
const isolatedResult = isolated(db, { scope, minLoc: 3 });
|
|
3761
4483
|
const cycleResult = cycles(db, { scope });
|
|
3762
4484
|
const similarResult = similarAll(db, { scope, minSimilarity: 0.6, limit: 50, minCallees: 4 });
|
|
@@ -3766,20 +4488,13 @@ function health(db, opts = {}) {
|
|
|
3766
4488
|
const staleResult = staleAbstractions(db, { scope, minLoc: 3, limit: 50 });
|
|
3767
4489
|
const driftResult = drift(db, { scope });
|
|
3768
4490
|
const complexResult = complexityHotspots(db, { scope, minLoc: 10, limit: 10 });
|
|
3769
|
-
const testResult = testCoverageSummary(db, { scope, minLoc: 3 });
|
|
3770
|
-
const isolatedLoc = isolatedResult.reduce((sum, r) => sum + r.loc, 0);
|
|
3771
|
-
const entryPointPatterns = ["/index.ts", "/index.js", "cli.ts", "worker.ts", "postinstall.ts", "/mod.rs", "__init__.py", "main.ts", "main.rs", "main.go", "main.py"];
|
|
3772
|
-
const isEntryPoint = (path2) => entryPointPatterns.some((p) => path2.endsWith(p));
|
|
3773
4491
|
const trueDeadSymbols = deadResult.symbols.filter(
|
|
3774
|
-
(s2) => !
|
|
4492
|
+
(s2) => !isEntrySurface(db, s2.relativePath) && s2.kind === "dead-code"
|
|
3775
4493
|
);
|
|
3776
4494
|
const trueDeadCount = trueDeadSymbols.length;
|
|
3777
4495
|
const trueDeadLoc = trueDeadSymbols.reduce((sum, s2) => sum + s2.loc, 0);
|
|
3778
|
-
const fileInternalCount = deadResult.symbols.filter(
|
|
3779
|
-
(s2) => !isEntryPoint(s2.relativePath) && s2.kind === "file-internal"
|
|
3780
|
-
).length;
|
|
3781
4496
|
const trueIsolatedCount = isolatedResult.filter(
|
|
3782
|
-
(s2) => !
|
|
4497
|
+
(s2) => !isEntrySurface(db, s2.relativePath)
|
|
3783
4498
|
).length;
|
|
3784
4499
|
const filesWithFunctions = new Set(
|
|
3785
4500
|
db.all(
|
|
@@ -3808,16 +4523,6 @@ function health(db, opts = {}) {
|
|
|
3808
4523
|
locRecoverable: trueDeadLoc
|
|
3809
4524
|
});
|
|
3810
4525
|
}
|
|
3811
|
-
if (testResult.percent < 50) {
|
|
3812
|
-
actions.push({
|
|
3813
|
-
category: "Test coverage",
|
|
3814
|
-
description: `${testResult.percent}% of symbols referenced by tests (${testResult.uncovered} uncovered)`,
|
|
3815
|
-
effort: "high",
|
|
3816
|
-
impact: "high",
|
|
3817
|
-
count: testResult.uncovered,
|
|
3818
|
-
locRecoverable: 0
|
|
3819
|
-
});
|
|
3820
|
-
}
|
|
3821
4526
|
if (trueIsolatedCount > 0) {
|
|
3822
4527
|
actions.push({
|
|
3823
4528
|
category: "Isolated symbols",
|
|
@@ -3825,7 +4530,7 @@ function health(db, opts = {}) {
|
|
|
3825
4530
|
effort: "low",
|
|
3826
4531
|
impact: "medium",
|
|
3827
4532
|
count: trueIsolatedCount,
|
|
3828
|
-
locRecoverable: isolatedResult.filter((s2) => !
|
|
4533
|
+
locRecoverable: isolatedResult.filter((s2) => !isEntrySurface(db, s2.relativePath)).reduce((sum, s2) => sum + s2.loc, 0)
|
|
3829
4534
|
});
|
|
3830
4535
|
}
|
|
3831
4536
|
if (cycleResult.length > 0) {
|
|
@@ -3936,8 +4641,6 @@ function health(db, opts = {}) {
|
|
|
3936
4641
|
score -= Math.min(5, Math.round(driftPercent * 50));
|
|
3937
4642
|
const extremeComplexity = complexResult.filter((r) => r.score > 50).length;
|
|
3938
4643
|
score -= Math.min(5, extremeComplexity * 2);
|
|
3939
|
-
const coverageDeduction = Math.round(15 * (1 - testResult.percent / 100));
|
|
3940
|
-
score -= coverageDeduction;
|
|
3941
4644
|
score = Math.max(0, Math.min(100, score));
|
|
3942
4645
|
return {
|
|
3943
4646
|
score,
|
|
@@ -3950,7 +4653,7 @@ function health(db, opts = {}) {
|
|
|
3950
4653
|
deadSymbols: trueDeadCount,
|
|
3951
4654
|
deadLoc: trueDeadLoc,
|
|
3952
4655
|
isolatedSymbols: trueIsolatedCount,
|
|
3953
|
-
isolatedLoc: isolatedResult.filter((s2) => !
|
|
4656
|
+
isolatedLoc: isolatedResult.filter((s2) => !isEntrySurface(db, s2.relativePath)).reduce((sum, s2) => sum + s2.loc, 0),
|
|
3954
4657
|
cycles: cycleResult.length,
|
|
3955
4658
|
similarPairs: trueSimilarCount,
|
|
3956
4659
|
extractionCandidates: extractResult.length,
|
|
@@ -3958,8 +4661,7 @@ function health(db, opts = {}) {
|
|
|
3958
4661
|
passthroughs: passthroughResult.length,
|
|
3959
4662
|
staleTypes: trueStaleCount,
|
|
3960
4663
|
driftedFiles: trueDriftCount,
|
|
3961
|
-
complexityHotspotCount: complexResult.length
|
|
3962
|
-
testCoveragePercent: testResult.percent
|
|
4664
|
+
complexityHotspotCount: complexResult.length
|
|
3963
4665
|
},
|
|
3964
4666
|
actions,
|
|
3965
4667
|
topComplexity: complexResult.slice(0, 5).map((r) => ({
|
|
@@ -3995,7 +4697,11 @@ function convergence(db, symbolPatternA, symbolPatternB) {
|
|
|
3995
4697
|
const union = /* @__PURE__ */ new Set([...calleesA, ...calleesB]);
|
|
3996
4698
|
const similarity = union.size > 0 ? shared.length / union.size : 0;
|
|
3997
4699
|
let strategy;
|
|
3998
|
-
if (
|
|
4700
|
+
if (union.size === 0) {
|
|
4701
|
+
strategy = "Neither function calls other tracked symbols. There is no callee-pattern evidence for consolidation; inspect the source bodies directly.";
|
|
4702
|
+
} else if (shared.length === 0) {
|
|
4703
|
+
strategy = "These functions do not share any callees. They are not a callee-based consolidation candidate.";
|
|
4704
|
+
} else if (uniqueA.length === 0 && uniqueB.length === 0) {
|
|
3999
4705
|
strategy = "These functions have identical callee sets. One can replace the other directly.";
|
|
4000
4706
|
} else if (uniqueA.length === 0) {
|
|
4001
4707
|
strategy = `A is a subset of B. A can be replaced by calling B (B does everything A does plus more).`;
|
|
@@ -4030,8 +4736,8 @@ function convergence(db, symbolPatternA, symbolPatternB) {
|
|
|
4030
4736
|
}
|
|
4031
4737
|
|
|
4032
4738
|
// src/queries/code.ts
|
|
4033
|
-
import { readFileSync as
|
|
4034
|
-
import { join as
|
|
4739
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
4740
|
+
import { join as join7 } from "path";
|
|
4035
4741
|
function code(db, symbolPattern, opts = {}) {
|
|
4036
4742
|
const { context = 0 } = opts;
|
|
4037
4743
|
const fileLineMatch = symbolPattern.match(/^(.+\.\w+):(\d+)-(\d+)$/);
|
|
@@ -4044,10 +4750,10 @@ function code(db, symbolPattern, opts = {}) {
|
|
|
4044
4750
|
`SELECT language FROM documents WHERE relative_path = ?`,
|
|
4045
4751
|
match.relativePath
|
|
4046
4752
|
);
|
|
4047
|
-
const filePath =
|
|
4753
|
+
const filePath = join7(db.config.projectRoot, match.relativePath);
|
|
4048
4754
|
let fileContent;
|
|
4049
4755
|
try {
|
|
4050
|
-
fileContent =
|
|
4756
|
+
fileContent = readFileSync5(filePath, "utf-8");
|
|
4051
4757
|
} catch {
|
|
4052
4758
|
return null;
|
|
4053
4759
|
}
|
|
@@ -4072,10 +4778,10 @@ function readFileRange(db, filePath, startLine, endLine, context) {
|
|
|
4072
4778
|
`%${filePath}%`
|
|
4073
4779
|
);
|
|
4074
4780
|
if (!doc) return null;
|
|
4075
|
-
const fullPath =
|
|
4781
|
+
const fullPath = join7(db.config.projectRoot, doc.relative_path);
|
|
4076
4782
|
let fileContent;
|
|
4077
4783
|
try {
|
|
4078
|
-
fileContent =
|
|
4784
|
+
fileContent = readFileSync5(fullPath, "utf-8");
|
|
4079
4785
|
} catch {
|
|
4080
4786
|
return null;
|
|
4081
4787
|
}
|
|
@@ -4095,8 +4801,8 @@ function readFileRange(db, filePath, startLine, endLine, context) {
|
|
|
4095
4801
|
}
|
|
4096
4802
|
|
|
4097
4803
|
// src/queries/complexity.ts
|
|
4098
|
-
import { readFileSync as
|
|
4099
|
-
import { join as
|
|
4804
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
4805
|
+
import { join as join8 } from "path";
|
|
4100
4806
|
function complexity(db, symbolPattern) {
|
|
4101
4807
|
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
4102
4808
|
if (!match) return null;
|
|
@@ -4105,10 +4811,10 @@ function complexity(db, symbolPattern) {
|
|
|
4105
4811
|
match.relativePath
|
|
4106
4812
|
);
|
|
4107
4813
|
const language = doc?.language ?? "unknown";
|
|
4108
|
-
const filePath =
|
|
4814
|
+
const filePath = join8(db.config.projectRoot, match.relativePath);
|
|
4109
4815
|
let source = "";
|
|
4110
4816
|
try {
|
|
4111
|
-
const lines =
|
|
4817
|
+
const lines = readFileSync6(filePath, "utf-8").split("\n");
|
|
4112
4818
|
source = lines.slice(match.startLine, match.endLine + 1).join("\n");
|
|
4113
4819
|
} catch {
|
|
4114
4820
|
}
|
|
@@ -4120,7 +4826,7 @@ function complexity(db, symbolPattern) {
|
|
|
4120
4826
|
`SELECT COUNT(DISTINCT c.document_id) AS c
|
|
4121
4827
|
FROM mentions m
|
|
4122
4828
|
JOIN chunks c ON m.chunk_id = c.id
|
|
4123
|
-
WHERE m.symbol_id = ? AND m.role
|
|
4829
|
+
WHERE m.symbol_id = ? AND m.role != 1`,
|
|
4124
4830
|
match.symbolId
|
|
4125
4831
|
);
|
|
4126
4832
|
const fanOut2 = new Set(
|
|
@@ -4141,7 +4847,7 @@ function complexity(db, symbolPattern) {
|
|
|
4141
4847
|
};
|
|
4142
4848
|
}
|
|
4143
4849
|
function countBranches(source, language) {
|
|
4144
|
-
const stripped =
|
|
4850
|
+
const stripped = stripCommentsAndStrings2(source);
|
|
4145
4851
|
let count = 0;
|
|
4146
4852
|
const universalPatterns = [
|
|
4147
4853
|
/\bif\b/g,
|
|
@@ -4188,7 +4894,7 @@ function countBranches(source, language) {
|
|
|
4188
4894
|
}
|
|
4189
4895
|
return count;
|
|
4190
4896
|
}
|
|
4191
|
-
function
|
|
4897
|
+
function stripCommentsAndStrings2(source) {
|
|
4192
4898
|
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*/g, "").replace(/#.*/g, "").replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''").replace(/`(?:[^`\\]|\\.)*`/g, "``");
|
|
4193
4899
|
}
|
|
4194
4900
|
|
|
@@ -4219,7 +4925,7 @@ function dataflow(db, symbolPattern) {
|
|
|
4219
4925
|
FROM mentions m
|
|
4220
4926
|
JOIN chunks c ON m.chunk_id = c.id
|
|
4221
4927
|
JOIN documents d ON c.document_id = d.id
|
|
4222
|
-
WHERE m.symbol_id = ? AND m.role
|
|
4928
|
+
WHERE m.symbol_id = ? AND m.role != 1
|
|
4223
4929
|
${db.pathExclusionsFor("d")}
|
|
4224
4930
|
ORDER BY d.relative_path, c.start_line`,
|
|
4225
4931
|
match.symbolId
|
|
@@ -4233,7 +4939,7 @@ function dataflow(db, symbolPattern) {
|
|
|
4233
4939
|
JOIN documents other_d ON other_der.document_id = other_d.id
|
|
4234
4940
|
WHERE other_c.document_id = ?
|
|
4235
4941
|
AND other_c.start_line >= ? AND other_c.end_line <= ?
|
|
4236
|
-
AND other_m.role
|
|
4942
|
+
AND other_m.role != 1
|
|
4237
4943
|
AND other_gs.id != ?
|
|
4238
4944
|
${db.symbolNoiseFor("other_gs")}
|
|
4239
4945
|
${db.pathExclusionsFor("other_d")}
|
|
@@ -4256,11 +4962,11 @@ function dataflow(db, symbolPattern) {
|
|
|
4256
4962
|
AND enc_der.end_line >= ref_c.end_line
|
|
4257
4963
|
JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
|
|
4258
4964
|
-- Find other symbols defined by that enclosing function's file
|
|
4259
|
-
JOIN mentions consumer_m ON consumer_m.symbol_id = enc_gs.id AND consumer_m.role
|
|
4965
|
+
JOIN mentions consumer_m ON consumer_m.symbol_id = enc_gs.id AND consumer_m.role != 1
|
|
4260
4966
|
JOIN chunks consumer_c ON consumer_m.chunk_id = consumer_c.id
|
|
4261
4967
|
JOIN documents consumer_d ON consumer_c.document_id = consumer_d.id
|
|
4262
4968
|
JOIN global_symbols consumer_gs ON consumer_m.symbol_id = consumer_gs.id
|
|
4263
|
-
WHERE ref_m.symbol_id = ? AND ref_m.role
|
|
4969
|
+
WHERE ref_m.symbol_id = ? AND ref_m.role != 1
|
|
4264
4970
|
AND consumer_d.id != ref_d.id
|
|
4265
4971
|
${db.symbolNoiseFor("consumer_gs")}
|
|
4266
4972
|
${db.pathExclusionsFor("consumer_d")}
|
|
@@ -4359,7 +5065,7 @@ function forwardSlice(db, match) {
|
|
|
4359
5065
|
JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
|
|
4360
5066
|
JOIN documents enc_d ON enc_der.document_id = enc_d.id
|
|
4361
5067
|
-- Find other symbols referenced within that enclosing function
|
|
4362
|
-
JOIN mentions out_m ON out_m.role
|
|
5068
|
+
JOIN mentions out_m ON out_m.role != 1
|
|
4363
5069
|
JOIN chunks out_c ON out_m.chunk_id = out_c.id
|
|
4364
5070
|
AND out_c.document_id = enc_der.document_id
|
|
4365
5071
|
AND out_c.start_line >= enc_der.start_line
|
|
@@ -4367,7 +5073,7 @@ function forwardSlice(db, match) {
|
|
|
4367
5073
|
JOIN global_symbols out_gs ON out_m.symbol_id = out_gs.id
|
|
4368
5074
|
JOIN defn_enclosing_ranges out_der ON out_gs.id = out_der.symbol_id
|
|
4369
5075
|
JOIN documents out_d ON out_der.document_id = out_d.id
|
|
4370
|
-
WHERE ref_m.symbol_id = ? AND ref_m.role
|
|
5076
|
+
WHERE ref_m.symbol_id = ? AND ref_m.role != 1
|
|
4371
5077
|
AND out_gs.id != ? AND out_gs.id != enc_gs.id
|
|
4372
5078
|
AND out_d.id != ref_d.id
|
|
4373
5079
|
${db.symbolNoiseFor("out_gs")}
|
|
@@ -4415,7 +5121,7 @@ function redundantReexports(db, opts = {}) {
|
|
|
4415
5121
|
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
4416
5122
|
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
4417
5123
|
JOIN documents orig_d ON der.document_id = orig_d.id
|
|
4418
|
-
WHERE m.role
|
|
5124
|
+
WHERE m.role != 1
|
|
4419
5125
|
AND (barrel_d.relative_path LIKE '%/index.ts'
|
|
4420
5126
|
OR barrel_d.relative_path LIKE '%/index.js'
|
|
4421
5127
|
OR barrel_d.relative_path = 'index.ts'
|
|
@@ -4431,6 +5137,7 @@ function redundantReexports(db, opts = {}) {
|
|
|
4431
5137
|
const results = [];
|
|
4432
5138
|
for (const row of reexportRows) {
|
|
4433
5139
|
if (db.isIgnored(row.barrel_path) || db.isIgnored(row.original_path)) continue;
|
|
5140
|
+
if (isLiveBarrel(db, row.barrel_path)) continue;
|
|
4434
5141
|
const consumerCounts = db.get(
|
|
4435
5142
|
`SELECT
|
|
4436
5143
|
SUM(CASE WHEN uses_barrel = 1 THEN 1 ELSE 0 END) AS barrel_consumers,
|
|
@@ -4443,20 +5150,20 @@ function redundantReexports(db, opts = {}) {
|
|
|
4443
5150
|
FROM mentions barrel_m
|
|
4444
5151
|
JOIN chunks barrel_c ON barrel_m.chunk_id = barrel_c.id
|
|
4445
5152
|
WHERE barrel_c.document_id = consumer_d.id
|
|
4446
|
-
AND barrel_m.role
|
|
5153
|
+
AND barrel_m.role != 1
|
|
4447
5154
|
AND barrel_m.symbol_id IN (
|
|
4448
5155
|
SELECT m2.symbol_id
|
|
4449
5156
|
FROM mentions m2
|
|
4450
5157
|
JOIN chunks c2 ON m2.chunk_id = c2.id
|
|
4451
5158
|
WHERE c2.document_id = ?
|
|
4452
|
-
AND m2.role
|
|
5159
|
+
AND m2.role != 1
|
|
4453
5160
|
)
|
|
4454
5161
|
) THEN 1 ELSE 0 END) AS uses_barrel
|
|
4455
5162
|
FROM mentions ref_m
|
|
4456
5163
|
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
4457
5164
|
JOIN documents consumer_d ON ref_c.document_id = consumer_d.id
|
|
4458
5165
|
WHERE ref_m.symbol_id = ?
|
|
4459
|
-
AND ref_m.role
|
|
5166
|
+
AND ref_m.role != 1
|
|
4460
5167
|
AND consumer_d.id != ?
|
|
4461
5168
|
AND consumer_d.id != ?
|
|
4462
5169
|
${db.pathExclusionsFor("consumer_d")}
|
|
@@ -4562,27 +5269,25 @@ function normalizeSignature(raw) {
|
|
|
4562
5269
|
|
|
4563
5270
|
// src/setup.ts
|
|
4564
5271
|
import {
|
|
4565
|
-
existsSync as
|
|
5272
|
+
existsSync as existsSync7,
|
|
4566
5273
|
mkdirSync as mkdirSync2,
|
|
4567
5274
|
symlinkSync,
|
|
4568
5275
|
readlinkSync,
|
|
4569
5276
|
unlinkSync
|
|
4570
5277
|
} from "fs";
|
|
4571
|
-
import { join as
|
|
4572
|
-
import { homedir as homedir2, platform as
|
|
4573
|
-
import { execFileSync as execFileSync4 } from "child_process";
|
|
5278
|
+
import { join as join9, dirname as dirname3, resolve as resolve3 } from "path";
|
|
5279
|
+
import { homedir as homedir2, platform as platform3 } from "os";
|
|
4574
5280
|
import { fileURLToPath } from "url";
|
|
4575
|
-
var
|
|
5281
|
+
var IS_WINDOWS3 = platform3() === "win32";
|
|
4576
5282
|
var SKILLS = ["concrete-plan", "scip-explore", "scip-debloat", "scip-verify"];
|
|
4577
|
-
var SCIP_VERSION = "v0.7.0";
|
|
4578
5283
|
function installSkills(opts = {}) {
|
|
4579
5284
|
const log = opts.quiet ? () => {
|
|
4580
5285
|
} : console.log;
|
|
4581
5286
|
const thisFile = fileURLToPath(import.meta.url);
|
|
4582
|
-
const skillsSource =
|
|
5287
|
+
const skillsSource = resolve3(dirname3(thisFile), "..", "skills");
|
|
4583
5288
|
const targets = [
|
|
4584
|
-
|
|
4585
|
-
|
|
5289
|
+
join9(homedir2(), ".claude", "skills"),
|
|
5290
|
+
join9(homedir2(), ".codex", "skills")
|
|
4586
5291
|
];
|
|
4587
5292
|
const result = {
|
|
4588
5293
|
installed: [],
|
|
@@ -4590,23 +5295,23 @@ function installSkills(opts = {}) {
|
|
|
4590
5295
|
alreadyLinked: []
|
|
4591
5296
|
};
|
|
4592
5297
|
for (const targetDir of targets) {
|
|
4593
|
-
const parentDir =
|
|
4594
|
-
if (!
|
|
5298
|
+
const parentDir = dirname3(targetDir);
|
|
5299
|
+
if (!existsSync7(parentDir)) {
|
|
4595
5300
|
continue;
|
|
4596
5301
|
}
|
|
4597
5302
|
mkdirSync2(targetDir, { recursive: true });
|
|
4598
5303
|
const toolName = targetDir.includes(".codex") ? "Codex" : "Claude";
|
|
4599
5304
|
for (const skill of SKILLS) {
|
|
4600
|
-
const source =
|
|
4601
|
-
const target =
|
|
4602
|
-
if (!
|
|
5305
|
+
const source = join9(skillsSource, skill);
|
|
5306
|
+
const target = join9(targetDir, skill);
|
|
5307
|
+
if (!existsSync7(source)) {
|
|
4603
5308
|
result.skipped.push(`${toolName}/${skill}`);
|
|
4604
5309
|
continue;
|
|
4605
5310
|
}
|
|
4606
|
-
if (
|
|
5311
|
+
if (existsSync7(target)) {
|
|
4607
5312
|
try {
|
|
4608
5313
|
const existing = readlinkSync(target);
|
|
4609
|
-
if (
|
|
5314
|
+
if (resolve3(existing) === resolve3(source)) {
|
|
4610
5315
|
result.alreadyLinked.push(`${toolName}/${skill}`);
|
|
4611
5316
|
log(` ok: ${skill} \u2192 ${toolName} (already linked)`);
|
|
4612
5317
|
continue;
|
|
@@ -4618,88 +5323,29 @@ function installSkills(opts = {}) {
|
|
|
4618
5323
|
}
|
|
4619
5324
|
unlinkSync(target);
|
|
4620
5325
|
}
|
|
4621
|
-
symlinkSync(source, target,
|
|
5326
|
+
symlinkSync(source, target, IS_WINDOWS3 ? "junction" : "dir");
|
|
4622
5327
|
result.installed.push(`${toolName}/${skill}`);
|
|
4623
5328
|
log(` done: ${skill} \u2192 ${toolName}`);
|
|
4624
5329
|
}
|
|
4625
5330
|
}
|
|
4626
5331
|
return result;
|
|
4627
5332
|
}
|
|
4628
|
-
function isScipInstalled() {
|
|
4629
|
-
try {
|
|
4630
|
-
const cmd = IS_WINDOWS2 ? "where" : "which";
|
|
4631
|
-
execFileSync4(cmd, ["scip"], { stdio: "pipe" });
|
|
4632
|
-
return true;
|
|
4633
|
-
} catch {
|
|
4634
|
-
return false;
|
|
4635
|
-
}
|
|
4636
|
-
}
|
|
4637
|
-
function getScipDownloadUrl() {
|
|
4638
|
-
const os = platform2();
|
|
4639
|
-
const cpu = arch();
|
|
4640
|
-
let osName;
|
|
4641
|
-
let archName;
|
|
4642
|
-
let ext;
|
|
4643
|
-
switch (os) {
|
|
4644
|
-
case "darwin":
|
|
4645
|
-
osName = "darwin";
|
|
4646
|
-
ext = "tar.gz";
|
|
4647
|
-
break;
|
|
4648
|
-
case "linux":
|
|
4649
|
-
osName = "linux";
|
|
4650
|
-
ext = "tar.gz";
|
|
4651
|
-
break;
|
|
4652
|
-
case "win32":
|
|
4653
|
-
osName = "windows";
|
|
4654
|
-
ext = "zip";
|
|
4655
|
-
break;
|
|
4656
|
-
default:
|
|
4657
|
-
return null;
|
|
4658
|
-
}
|
|
4659
|
-
switch (cpu) {
|
|
4660
|
-
case "arm64":
|
|
4661
|
-
archName = "arm64";
|
|
4662
|
-
break;
|
|
4663
|
-
case "x64":
|
|
4664
|
-
archName = "amd64";
|
|
4665
|
-
break;
|
|
4666
|
-
default:
|
|
4667
|
-
return null;
|
|
4668
|
-
}
|
|
4669
|
-
const filename = `scip-${osName}-${archName}.${ext}`;
|
|
4670
|
-
const url = `https://github.com/sourcegraph/scip/releases/download/${SCIP_VERSION}/${filename}`;
|
|
4671
|
-
return { url, filename };
|
|
4672
|
-
}
|
|
4673
|
-
function printScipInstallInstructions() {
|
|
4674
|
-
const download = getScipDownloadUrl();
|
|
4675
|
-
console.log("\nThe `scip` CLI is required but not found on PATH.\n");
|
|
4676
|
-
if (platform2() === "darwin") {
|
|
4677
|
-
console.log("Install via Homebrew:");
|
|
4678
|
-
console.log(" brew install sourcegraph/scip/scip\n");
|
|
4679
|
-
console.log("Or download manually:");
|
|
4680
|
-
} else {
|
|
4681
|
-
console.log("Download from:");
|
|
4682
|
-
}
|
|
4683
|
-
if (download) {
|
|
4684
|
-
console.log(` ${download.url}
|
|
4685
|
-
`);
|
|
4686
|
-
} else {
|
|
4687
|
-
console.log(` https://github.com/sourcegraph/scip/releases/tag/${SCIP_VERSION}
|
|
4688
|
-
`);
|
|
4689
|
-
}
|
|
4690
|
-
console.log("After installing, ensure `scip` is on your PATH and run `scip-query reindex`.");
|
|
4691
|
-
}
|
|
4692
5333
|
|
|
4693
5334
|
// src/cli.ts
|
|
4694
5335
|
function resolveProjectRoot() {
|
|
4695
5336
|
return process.env["SCIP_QUERY_PROJECT_ROOT"] ?? process.cwd();
|
|
4696
5337
|
}
|
|
5338
|
+
function resolveActiveDbPath(projectRoot) {
|
|
5339
|
+
const config = loadProjectConfig(projectRoot);
|
|
5340
|
+
const paths = resolveIndexPaths(projectRoot, config);
|
|
5341
|
+
return process.env["SCIP_QUERY_INDEX_DB"] ?? (existsSync8(paths.dbPath) ? paths.dbPath : join10(projectRoot, "index.db"));
|
|
5342
|
+
}
|
|
4697
5343
|
function openDb() {
|
|
4698
5344
|
const projectRoot = resolveProjectRoot();
|
|
4699
5345
|
const config = loadProjectConfig(projectRoot);
|
|
4700
5346
|
const paths = resolveIndexPaths(projectRoot, config);
|
|
4701
|
-
const dbPath =
|
|
4702
|
-
if (!
|
|
5347
|
+
const dbPath = resolveActiveDbPath(projectRoot);
|
|
5348
|
+
if (!existsSync8(dbPath)) {
|
|
4703
5349
|
console.error(`error: No index.db found. Run: scip-query reindex`);
|
|
4704
5350
|
process.exit(1);
|
|
4705
5351
|
}
|
|
@@ -4724,6 +5370,61 @@ function runQuery(query, render) {
|
|
|
4724
5370
|
render(query(db));
|
|
4725
5371
|
});
|
|
4726
5372
|
}
|
|
5373
|
+
var queries = {
|
|
5374
|
+
stats,
|
|
5375
|
+
files,
|
|
5376
|
+
symbols,
|
|
5377
|
+
methods,
|
|
5378
|
+
refs,
|
|
5379
|
+
trace,
|
|
5380
|
+
deps,
|
|
5381
|
+
rdeps,
|
|
5382
|
+
system,
|
|
5383
|
+
surface,
|
|
5384
|
+
dead,
|
|
5385
|
+
hotspots,
|
|
5386
|
+
imports,
|
|
5387
|
+
importedBy,
|
|
5388
|
+
unusedImports,
|
|
5389
|
+
outline,
|
|
5390
|
+
members,
|
|
5391
|
+
fanIn,
|
|
5392
|
+
fanOut,
|
|
5393
|
+
topFanIn,
|
|
5394
|
+
topFanOut,
|
|
5395
|
+
coupling,
|
|
5396
|
+
topCoupling,
|
|
5397
|
+
cycles,
|
|
5398
|
+
bottlenecks,
|
|
5399
|
+
isolated,
|
|
5400
|
+
byKind,
|
|
5401
|
+
kindCounts,
|
|
5402
|
+
docCoverage,
|
|
5403
|
+
deepChains,
|
|
5404
|
+
hierarchy,
|
|
5405
|
+
callGraph,
|
|
5406
|
+
similar,
|
|
5407
|
+
similarAll,
|
|
5408
|
+
similarFiles,
|
|
5409
|
+
similarChains,
|
|
5410
|
+
extractCandidates,
|
|
5411
|
+
affected,
|
|
5412
|
+
changeSurface,
|
|
5413
|
+
diffImpact,
|
|
5414
|
+
drift,
|
|
5415
|
+
wrapperCandidates,
|
|
5416
|
+
passthroughCandidates,
|
|
5417
|
+
staleAbstractions,
|
|
5418
|
+
complexityHotspots,
|
|
5419
|
+
health,
|
|
5420
|
+
convergence,
|
|
5421
|
+
code,
|
|
5422
|
+
complexity,
|
|
5423
|
+
dataflow,
|
|
5424
|
+
slice,
|
|
5425
|
+
redundantReexports,
|
|
5426
|
+
similarSignatures
|
|
5427
|
+
};
|
|
4727
5428
|
program.name("scip-query").description("Language-agnostic code intelligence CLI powered by SCIP indexes").version("0.1.0");
|
|
4728
5429
|
program.command("reindex").description("Index the codebase and convert to SQLite").option("-l, --language <lang>", "Index only this language (can be repeated)", collect, []).option("--pnpm-workspaces", "Enable pnpm workspace support (TypeScript)").action(async (opts) => {
|
|
4729
5430
|
const projectRoot = resolveProjectRoot();
|
|
@@ -4741,7 +5442,7 @@ program.command("reindex").description("Index the codebase and convert to SQLite
|
|
|
4741
5442
|
});
|
|
4742
5443
|
program.command("stats").description("Show index statistics").action(() => {
|
|
4743
5444
|
runQuery(
|
|
4744
|
-
(db) => stats(db),
|
|
5445
|
+
(db) => queries.stats(db),
|
|
4745
5446
|
(s) => {
|
|
4746
5447
|
console.log(`Documents: ${s.documents}`);
|
|
4747
5448
|
console.log(`Symbols: ${s.symbols}`);
|
|
@@ -4756,7 +5457,7 @@ program.command("stats").description("Show index statistics").action(() => {
|
|
|
4756
5457
|
});
|
|
4757
5458
|
program.command("files <pattern>").description("Find files matching a pattern").action((pattern) => {
|
|
4758
5459
|
runQuery(
|
|
4759
|
-
(db) => files(db, pattern),
|
|
5460
|
+
(db) => queries.files(db, pattern),
|
|
4760
5461
|
(results) => {
|
|
4761
5462
|
for (const r of results) console.log(r.relativePath);
|
|
4762
5463
|
}
|
|
@@ -4764,7 +5465,7 @@ program.command("files <pattern>").description("Find files matching a pattern").
|
|
|
4764
5465
|
});
|
|
4765
5466
|
program.command("symbols <file>").description("List symbols defined in a file (with line ranges + signatures)").action((file) => {
|
|
4766
5467
|
runQuery(
|
|
4767
|
-
(db) => symbols(db, file),
|
|
5468
|
+
(db) => queries.symbols(db, file),
|
|
4768
5469
|
(results) => {
|
|
4769
5470
|
for (const r of results) {
|
|
4770
5471
|
const sig = r.signature ? ` \u2014 ${r.signature}` : "";
|
|
@@ -4775,7 +5476,7 @@ program.command("symbols <file>").description("List symbols defined in a file (w
|
|
|
4775
5476
|
});
|
|
4776
5477
|
program.command("methods <className>").description("List methods of a class (with line ranges)").action((className) => {
|
|
4777
5478
|
runQuery(
|
|
4778
|
-
(db) => methods(db, className),
|
|
5479
|
+
(db) => queries.methods(db, className),
|
|
4779
5480
|
(results) => {
|
|
4780
5481
|
for (const r of results) {
|
|
4781
5482
|
console.log(` ${r.startLine}-${r.endLine} ${r.name}`);
|
|
@@ -4785,7 +5486,7 @@ program.command("methods <className>").description("List methods of a class (wit
|
|
|
4785
5486
|
});
|
|
4786
5487
|
program.command("refs <symbol>").description("Find all files referencing a symbol").action((symbol) => {
|
|
4787
5488
|
runQuery(
|
|
4788
|
-
(db) => refs(db, symbol),
|
|
5489
|
+
(db) => queries.refs(db, symbol),
|
|
4789
5490
|
(results) => {
|
|
4790
5491
|
let prevFile = "";
|
|
4791
5492
|
for (const r of results) {
|
|
@@ -4801,7 +5502,7 @@ program.command("refs <symbol>").description("Find all files referencing a symbo
|
|
|
4801
5502
|
});
|
|
4802
5503
|
program.command("trace <symbol>").description("Trace a symbol: definition + all references").action((symbol) => {
|
|
4803
5504
|
runQuery(
|
|
4804
|
-
(db) => trace(db, symbol),
|
|
5505
|
+
(db) => queries.trace(db, symbol),
|
|
4805
5506
|
(result) => {
|
|
4806
5507
|
console.log("\u2550\u2550\u2550 DEFINITION \u2550\u2550\u2550");
|
|
4807
5508
|
for (const d of result.definitions) {
|
|
@@ -4817,7 +5518,7 @@ program.command("trace <symbol>").description("Trace a symbol: definition + all
|
|
|
4817
5518
|
});
|
|
4818
5519
|
program.command("deps <file>").description("Files this file depends on (internal)").action((file) => {
|
|
4819
5520
|
runQuery(
|
|
4820
|
-
(db) => deps(db, file),
|
|
5521
|
+
(db) => queries.deps(db, file),
|
|
4821
5522
|
(results) => {
|
|
4822
5523
|
for (const r of results) console.log(r.relativePath);
|
|
4823
5524
|
}
|
|
@@ -4825,7 +5526,7 @@ program.command("deps <file>").description("Files this file depends on (internal
|
|
|
4825
5526
|
});
|
|
4826
5527
|
program.command("rdeps <file>").description("Files that depend on this file/module").action((file) => {
|
|
4827
5528
|
runQuery(
|
|
4828
|
-
(db) => rdeps(db, file),
|
|
5529
|
+
(db) => queries.rdeps(db, file),
|
|
4829
5530
|
(results) => {
|
|
4830
5531
|
for (const r of results) console.log(r.relativePath);
|
|
4831
5532
|
}
|
|
@@ -4833,7 +5534,7 @@ program.command("rdeps <file>").description("Files that depend on this file/modu
|
|
|
4833
5534
|
});
|
|
4834
5535
|
program.command("system <module>").description("Full module map: files, symbols, deps in/out").action((module) => {
|
|
4835
5536
|
runQuery(
|
|
4836
|
-
(db) => system(db, module),
|
|
5537
|
+
(db) => queries.system(db, module),
|
|
4837
5538
|
(result) => {
|
|
4838
5539
|
console.log("\u2550\u2550\u2550 FILES \u2550\u2550\u2550");
|
|
4839
5540
|
for (const f of result.files) console.log(f);
|
|
@@ -4850,7 +5551,7 @@ program.command("system <module>").description("Full module map: files, symbols,
|
|
|
4850
5551
|
});
|
|
4851
5552
|
program.command("surface <module>").description("What symbols consumers actually use from this module").action((module) => {
|
|
4852
5553
|
runQuery(
|
|
4853
|
-
(db) => surface(db, module),
|
|
5554
|
+
(db) => queries.surface(db, module),
|
|
4854
5555
|
(results) => {
|
|
4855
5556
|
for (const r of results) {
|
|
4856
5557
|
console.log(` ${r.consumer} \u2192 ${r.shortName}`);
|
|
@@ -4867,7 +5568,7 @@ program.command("dead [scope]").description("Find dead code and file-internal sy
|
|
|
4867
5568
|
skipBarrels: opts.skipBarrels,
|
|
4868
5569
|
includeMembers: opts.includeMembers
|
|
4869
5570
|
};
|
|
4870
|
-
const result = dead(db, deadOpts);
|
|
5571
|
+
const result = queries.dead(db, deadOpts);
|
|
4871
5572
|
if (result.symbols.length === 0) {
|
|
4872
5573
|
console.log("No dead code found.");
|
|
4873
5574
|
return;
|
|
@@ -4890,7 +5591,7 @@ program.command("dead [scope]").description("Find dead code and file-internal sy
|
|
|
4890
5591
|
});
|
|
4891
5592
|
program.command("hotspots").description("Most-referenced symbols in the codebase (choke points)").option("-n, --limit <n>", "Number of results", parseIntSafe, 30).option("-s, --scope <path>", "Limit to files matching path").action((opts) => {
|
|
4892
5593
|
runQuery(
|
|
4893
|
-
(db) => hotspots(db, { limit: opts.limit, scope: opts.scope }),
|
|
5594
|
+
(db) => queries.hotspots(db, { limit: opts.limit, scope: opts.scope }),
|
|
4894
5595
|
(results) => {
|
|
4895
5596
|
console.log(" refs files symbol");
|
|
4896
5597
|
console.log(" \u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500");
|
|
@@ -4902,7 +5603,7 @@ program.command("hotspots").description("Most-referenced symbols in the codebase
|
|
|
4902
5603
|
});
|
|
4903
5604
|
program.command("imports <file>").description("What symbols does this file import?").action((file) => {
|
|
4904
5605
|
runQuery(
|
|
4905
|
-
(db) => imports(db, file),
|
|
5606
|
+
(db) => queries.imports(db, file),
|
|
4906
5607
|
(results) => {
|
|
4907
5608
|
if (results.length === 0) {
|
|
4908
5609
|
console.log("No imports found (indexer may not emit role=2 for this language).");
|
|
@@ -4915,7 +5616,7 @@ program.command("imports <file>").description("What symbols does this file impor
|
|
|
4915
5616
|
});
|
|
4916
5617
|
program.command("imported-by <symbol>").description("Which files import this symbol?").action((symbol) => {
|
|
4917
5618
|
runQuery(
|
|
4918
|
-
(db) => importedBy(db, symbol),
|
|
5619
|
+
(db) => queries.importedBy(db, symbol),
|
|
4919
5620
|
(results) => {
|
|
4920
5621
|
for (const r of results) {
|
|
4921
5622
|
console.log(` ${r.fromFile}`);
|
|
@@ -4925,7 +5626,7 @@ program.command("imported-by <symbol>").description("Which files import this sym
|
|
|
4925
5626
|
});
|
|
4926
5627
|
program.command("unused-imports <file>").description("Find imports not referenced in the same file").action((file) => {
|
|
4927
5628
|
runQuery(
|
|
4928
|
-
(db) => unusedImports(db, file),
|
|
5629
|
+
(db) => queries.unusedImports(db, file),
|
|
4929
5630
|
(results) => {
|
|
4930
5631
|
if (results.length === 0) {
|
|
4931
5632
|
console.log("No unused imports found.");
|
|
@@ -4941,7 +5642,7 @@ ${results.length} unused import(s)`);
|
|
|
4941
5642
|
});
|
|
4942
5643
|
program.command("outline <file>").description("Tree view of symbols in a file (using nesting hierarchy)").action((file) => {
|
|
4943
5644
|
runQuery(
|
|
4944
|
-
(db) => outline(db, file),
|
|
5645
|
+
(db) => queries.outline(db, file),
|
|
4945
5646
|
(roots) => {
|
|
4946
5647
|
function printTree(nodes, indent) {
|
|
4947
5648
|
for (const n of nodes) {
|
|
@@ -4956,7 +5657,7 @@ program.command("outline <file>").description("Tree view of symbols in a file (u
|
|
|
4956
5657
|
});
|
|
4957
5658
|
program.command("members <symbol>").description("All children of a symbol (methods, fields, nested types)").action((symbol) => {
|
|
4958
5659
|
runQuery(
|
|
4959
|
-
(db) => members(db, symbol),
|
|
5660
|
+
(db) => queries.members(db, symbol),
|
|
4960
5661
|
(results) => {
|
|
4961
5662
|
for (const r of results) {
|
|
4962
5663
|
console.log(` ${r.startLine}-${r.endLine} [${r.kind}] ${r.shortName}`);
|
|
@@ -4967,12 +5668,12 @@ program.command("members <symbol>").description("All children of a symbol (metho
|
|
|
4967
5668
|
program.command("fan-in [symbol]").description("How many files reference a symbol (or top fan-in across codebase)").option("-n, --limit <n>", "Number of results for top mode", parseIntSafe, 30).option("-s, --scope <path>", "Limit to files matching path").action((symbol, opts) => {
|
|
4968
5669
|
withDb((db) => {
|
|
4969
5670
|
if (symbol) {
|
|
4970
|
-
const results = fanIn(db, symbol);
|
|
5671
|
+
const results = queries.fanIn(db, symbol);
|
|
4971
5672
|
for (const r of results) {
|
|
4972
5673
|
console.log(` ${String(r.count).padStart(4)} files ${r.name}`);
|
|
4973
5674
|
}
|
|
4974
5675
|
} else {
|
|
4975
|
-
const results = topFanIn(db, { limit: opts.limit, scope: opts.scope });
|
|
5676
|
+
const results = queries.topFanIn(db, { limit: opts.limit, scope: opts.scope });
|
|
4976
5677
|
console.log(" files symbol");
|
|
4977
5678
|
console.log(" \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500");
|
|
4978
5679
|
for (const r of results) {
|
|
@@ -4984,12 +5685,12 @@ program.command("fan-in [symbol]").description("How many files reference a symbo
|
|
|
4984
5685
|
program.command("fan-out [file]").description("How many external symbols a file uses (or top fan-out across codebase)").option("-n, --limit <n>", "Number of results for top mode", parseIntSafe, 30).option("-s, --scope <path>", "Limit to files matching path").action((file, opts) => {
|
|
4985
5686
|
withDb((db) => {
|
|
4986
5687
|
if (file) {
|
|
4987
|
-
const results = fanOut(db, file);
|
|
5688
|
+
const results = queries.fanOut(db, file);
|
|
4988
5689
|
for (const r of results) {
|
|
4989
5690
|
console.log(` ${String(r.count).padStart(4)} symbols ${r.name}`);
|
|
4990
5691
|
}
|
|
4991
5692
|
} else {
|
|
4992
|
-
const results = topFanOut(db, { limit: opts.limit, scope: opts.scope });
|
|
5693
|
+
const results = queries.topFanOut(db, { limit: opts.limit, scope: opts.scope });
|
|
4993
5694
|
console.log(" symbols file");
|
|
4994
5695
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500");
|
|
4995
5696
|
for (const r of results) {
|
|
@@ -5001,10 +5702,10 @@ program.command("fan-out [file]").description("How many external symbols a file
|
|
|
5001
5702
|
program.command("coupling [file1] [file2]").description("Coupling between two files, or top coupled pairs in codebase").option("-n, --limit <n>", "Number of results for top mode", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").action((file1, file2, opts) => {
|
|
5002
5703
|
withDb((db) => {
|
|
5003
5704
|
if (file1 && file2) {
|
|
5004
|
-
const result = coupling(db, file1, file2);
|
|
5705
|
+
const result = queries.coupling(db, file1, file2);
|
|
5005
5706
|
console.log(`${result.file1} \u2194 ${result.file2}: ${result.sharedSymbols} shared symbols`);
|
|
5006
5707
|
} else {
|
|
5007
|
-
const results = topCoupling(db, { limit: opts.limit, scope: opts.scope });
|
|
5708
|
+
const results = queries.topCoupling(db, { limit: opts.limit, scope: opts.scope });
|
|
5008
5709
|
console.log(" shared file1 \u2192 file2");
|
|
5009
5710
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
5010
5711
|
for (const r of results) {
|
|
@@ -5015,7 +5716,7 @@ program.command("coupling [file1] [file2]").description("Coupling between two fi
|
|
|
5015
5716
|
});
|
|
5016
5717
|
program.command("cycles").description("Detect circular dependency chains between files").option("-s, --scope <path>", "Limit to files matching path").option("--max-depth <n>", "Maximum cycle depth", parseIntSafe, 10).action((opts) => {
|
|
5017
5718
|
runQuery(
|
|
5018
|
-
(db) => cycles(db, { scope: opts.scope, maxDepth: opts.maxDepth }),
|
|
5719
|
+
(db) => queries.cycles(db, { scope: opts.scope, maxDepth: opts.maxDepth }),
|
|
5019
5720
|
(results) => {
|
|
5020
5721
|
if (results.length === 0) {
|
|
5021
5722
|
console.log("No circular dependencies found.");
|
|
@@ -5036,7 +5737,7 @@ ${results.length} cycle(s) found.`);
|
|
|
5036
5737
|
});
|
|
5037
5738
|
program.command("bottlenecks").description("Find coupling hubs: high fan-in AND high fan-out").option("-n, --limit <n>", "Number of results", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").option("--min-fan-in <n>", "Minimum fan-in", parseIntSafe, 2).option("--min-fan-out <n>", "Minimum fan-out", parseIntSafe, 2).action((opts) => {
|
|
5038
5739
|
runQuery(
|
|
5039
|
-
(db) => bottlenecks(db, {
|
|
5740
|
+
(db) => queries.bottlenecks(db, {
|
|
5040
5741
|
limit: opts.limit,
|
|
5041
5742
|
scope: opts.scope,
|
|
5042
5743
|
minFanIn: opts.minFanIn,
|
|
@@ -5057,7 +5758,7 @@ program.command("bottlenecks").description("Find coupling hubs: high fan-in AND
|
|
|
5057
5758
|
});
|
|
5058
5759
|
program.command("isolated").description("Find completely orphaned symbols (no references at all)").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum lines of code", parseIntSafe, 3).action((opts) => {
|
|
5059
5760
|
runQuery(
|
|
5060
|
-
(db) => isolated(db, { scope: opts.scope, minLoc: opts.minLoc }),
|
|
5761
|
+
(db) => queries.isolated(db, { scope: opts.scope, minLoc: opts.minLoc }),
|
|
5061
5762
|
(results) => {
|
|
5062
5763
|
if (results.length === 0) {
|
|
5063
5764
|
console.log("No isolated symbols found.");
|
|
@@ -5079,7 +5780,7 @@ ${results.length} isolated symbol(s)`);
|
|
|
5079
5780
|
});
|
|
5080
5781
|
program.command("by-kind <kind>").description("Find symbols by SCIP kind (class, interface, enum, function, etc.)").option("-s, --scope <path>", "Limit to files matching path").option("-n, --limit <n>", "Number of results", parseIntSafe, 100).action((kind, opts) => {
|
|
5081
5782
|
runQuery(
|
|
5082
|
-
(db) => byKind(db, kind, { scope: opts.scope, limit: opts.limit }),
|
|
5783
|
+
(db) => queries.byKind(db, kind, { scope: opts.scope, limit: opts.limit }),
|
|
5083
5784
|
(results) => {
|
|
5084
5785
|
if (results.length === 0) {
|
|
5085
5786
|
console.log(`No symbols found for kind "${kind}". Use "kind-counts" to see available kinds.`);
|
|
@@ -5095,7 +5796,7 @@ ${results.length} symbol(s)`);
|
|
|
5095
5796
|
});
|
|
5096
5797
|
program.command("kind-counts").description("Histogram of symbol kinds in the codebase").option("-s, --scope <path>", "Limit to files matching path").action((opts) => {
|
|
5097
5798
|
runQuery(
|
|
5098
|
-
(db) => kindCounts(db, { scope: opts.scope }),
|
|
5799
|
+
(db) => queries.kindCounts(db, { scope: opts.scope }),
|
|
5099
5800
|
(results) => {
|
|
5100
5801
|
console.log(" count kind");
|
|
5101
5802
|
console.log(" \u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500");
|
|
@@ -5105,29 +5806,9 @@ program.command("kind-counts").description("Histogram of symbol kinds in the cod
|
|
|
5105
5806
|
}
|
|
5106
5807
|
);
|
|
5107
5808
|
});
|
|
5108
|
-
program.command("test-coverage [symbol]").description("Check if symbols are referenced by test files").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC for summary mode", parseIntSafe, 3).action((symbol, opts) => {
|
|
5109
|
-
withDb((db) => {
|
|
5110
|
-
if (symbol) {
|
|
5111
|
-
const results = testCoverage(db, symbol);
|
|
5112
|
-
for (const r of results) {
|
|
5113
|
-
const status = r.covered ? "covered" : "NOT COVERED";
|
|
5114
|
-
console.log(` [${status}] ${r.shortName} (${r.definedIn})`);
|
|
5115
|
-
for (const tf of r.testFiles) {
|
|
5116
|
-
console.log(` \u2190 ${tf}`);
|
|
5117
|
-
}
|
|
5118
|
-
}
|
|
5119
|
-
} else {
|
|
5120
|
-
const summary = testCoverageSummary(db, { scope: opts.scope, minLoc: opts.minLoc });
|
|
5121
|
-
console.log(`Test coverage: ${summary.percent}%`);
|
|
5122
|
-
console.log(` Total symbols: ${summary.total}`);
|
|
5123
|
-
console.log(` Covered: ${summary.covered}`);
|
|
5124
|
-
console.log(` Not covered: ${summary.uncovered}`);
|
|
5125
|
-
}
|
|
5126
|
-
});
|
|
5127
|
-
});
|
|
5128
5809
|
program.command("doc-coverage").description("Check documentation coverage across symbols").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC to consider", parseIntSafe, 3).option("-n, --limit <n>", "Max undocumented symbols to show", parseIntSafe, 50).action((opts) => {
|
|
5129
5810
|
runQuery(
|
|
5130
|
-
(db) => docCoverage(db, {
|
|
5811
|
+
(db) => queries.docCoverage(db, {
|
|
5131
5812
|
scope: opts.scope,
|
|
5132
5813
|
minLoc: opts.minLoc,
|
|
5133
5814
|
limit: opts.limit
|
|
@@ -5148,7 +5829,7 @@ program.command("doc-coverage").description("Check documentation coverage across
|
|
|
5148
5829
|
});
|
|
5149
5830
|
program.command("deep-chains").description("Find the longest transitive dependency chains").option("-n, --limit <n>", "Number of chains to show", parseIntSafe, 10).option("-s, --scope <path>", "Limit to files matching path").option("--min-depth <n>", "Minimum chain depth", parseIntSafe, 3).action((opts) => {
|
|
5150
5831
|
runQuery(
|
|
5151
|
-
(db) => deepChains(db, {
|
|
5832
|
+
(db) => queries.deepChains(db, {
|
|
5152
5833
|
limit: opts.limit,
|
|
5153
5834
|
scope: opts.scope,
|
|
5154
5835
|
minDepth: opts.minDepth
|
|
@@ -5170,7 +5851,7 @@ Chain ${i + 1} (depth ${results[i].depth}):`);
|
|
|
5170
5851
|
});
|
|
5171
5852
|
program.command("hierarchy <symbol>").description("Show a symbol's ancestry chain (method \u2192 class \u2192 module)").action((symbol) => {
|
|
5172
5853
|
runQuery(
|
|
5173
|
-
(db) => hierarchy(db, symbol),
|
|
5854
|
+
(db) => queries.hierarchy(db, symbol),
|
|
5174
5855
|
(chain) => {
|
|
5175
5856
|
if (chain.length === 0) {
|
|
5176
5857
|
console.log("Symbol not found.");
|
|
@@ -5185,7 +5866,7 @@ program.command("hierarchy <symbol>").description("Show a symbol's ancestry chai
|
|
|
5185
5866
|
});
|
|
5186
5867
|
program.command("call-graph <symbol>").description("Show incoming callers and outgoing callees for a symbol").action((symbol) => {
|
|
5187
5868
|
runQuery(
|
|
5188
|
-
(db) => callGraph(db, symbol),
|
|
5869
|
+
(db) => queries.callGraph(db, symbol),
|
|
5189
5870
|
(result) => {
|
|
5190
5871
|
if (!result) {
|
|
5191
5872
|
console.log("Symbol not found.");
|
|
@@ -5208,7 +5889,7 @@ program.command("call-graph <symbol>").description("Show incoming callers and ou
|
|
|
5208
5889
|
program.command("similar [symbol]").description("Find functions with similar callee fingerprints (consolidation candidates)").option("--min-similarity <n>", "Minimum Jaccard similarity (0-1)", parseFloat, 0.4).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").option("--min-callees <n>", "Minimum callees to consider", parseIntSafe, 4).action((symbol, opts) => {
|
|
5209
5890
|
withDb((db) => {
|
|
5210
5891
|
if (symbol) {
|
|
5211
|
-
const results = similar(db, symbol, {
|
|
5892
|
+
const results = queries.similar(db, symbol, {
|
|
5212
5893
|
minSimilarity: opts.minSimilarity,
|
|
5213
5894
|
limit: opts.limit
|
|
5214
5895
|
});
|
|
@@ -5226,7 +5907,7 @@ ${Math.round(r.similarity * 100)}% similar:`);
|
|
|
5226
5907
|
}
|
|
5227
5908
|
}
|
|
5228
5909
|
} else {
|
|
5229
|
-
const results = similarAll(db, {
|
|
5910
|
+
const results = queries.similarAll(db, {
|
|
5230
5911
|
minSimilarity: opts.minSimilarity,
|
|
5231
5912
|
limit: opts.limit,
|
|
5232
5913
|
scope: opts.scope,
|
|
@@ -5250,7 +5931,7 @@ ${results.length} similar pair(s) found.`);
|
|
|
5250
5931
|
});
|
|
5251
5932
|
program.command("similar-files [file]").description("Find files with similar dependency profiles").option("--min-similarity <n>", "Minimum Jaccard similarity (0-1)", parseFloat, 0.5).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).option("-s, --scope <path>", "Limit to files matching path").option("--min-deps <n>", "Minimum dependencies to consider", parseIntSafe, 3).action((file, opts) => {
|
|
5252
5933
|
runQuery(
|
|
5253
|
-
(db) => similarFiles(db, {
|
|
5934
|
+
(db) => queries.similarFiles(db, {
|
|
5254
5935
|
minSimilarity: opts.minSimilarity,
|
|
5255
5936
|
limit: opts.limit,
|
|
5256
5937
|
scope: opts.scope,
|
|
@@ -5278,7 +5959,7 @@ ${results.length} similar pair(s) found.`);
|
|
|
5278
5959
|
});
|
|
5279
5960
|
program.command("similar-chains").description("Find end-to-end dependency flows that diverge at few points").option("--min-similarity <n>", "Minimum chain similarity (0-1)", parseFloat, 0.5).option("-n, --limit <n>", "Number of results", parseIntSafe, 15).option("-s, --scope <path>", "Limit to files matching path").option("--min-length <n>", "Minimum chain length", parseIntSafe, 3).option("--max-length <n>", "Maximum chain length", parseIntSafe, 8).action((opts) => {
|
|
5280
5961
|
runQuery(
|
|
5281
|
-
(db) => similarChains(db, {
|
|
5962
|
+
(db) => queries.similarChains(db, {
|
|
5282
5963
|
minSimilarity: opts.minSimilarity,
|
|
5283
5964
|
limit: opts.limit,
|
|
5284
5965
|
scope: opts.scope,
|
|
@@ -5310,7 +5991,7 @@ ${results.length} similar chain pair(s) found.`);
|
|
|
5310
5991
|
});
|
|
5311
5992
|
program.command("extract-candidates").description("Find functions with natural extraction seams (isolated callee clusters)").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum function LOC", parseIntSafe, 10).option("--min-callees <n>", "Minimum callees to analyze", parseIntSafe, 6).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).action((opts) => {
|
|
5312
5993
|
runQuery(
|
|
5313
|
-
(db) => extractCandidates(db, {
|
|
5994
|
+
(db) => queries.extractCandidates(db, {
|
|
5314
5995
|
scope: opts.scope,
|
|
5315
5996
|
minLoc: opts.minLoc,
|
|
5316
5997
|
minCallees: opts.minCallees,
|
|
@@ -5339,7 +6020,7 @@ ${results.length} extraction candidate(s) found.`);
|
|
|
5339
6020
|
});
|
|
5340
6021
|
program.command("affected <symbol>").description("Transitive closure of symbols that could break if this symbol changes").option("--max-depth <n>", "Maximum traversal depth", parseIntSafe, 5).option("-s, --scope <path>", "Limit to files matching path").action((symbol, opts) => {
|
|
5341
6022
|
const db = openDb();
|
|
5342
|
-
const results = affected(db, symbol, { maxDepth: opts.maxDepth, scope: opts.scope });
|
|
6023
|
+
const results = queries.affected(db, symbol, { maxDepth: opts.maxDepth, scope: opts.scope });
|
|
5343
6024
|
if (results.length === 0) {
|
|
5344
6025
|
console.log("No affected symbols found.");
|
|
5345
6026
|
} else {
|
|
@@ -5357,44 +6038,39 @@ ${results.length} affected symbol(s) across ${new Set(results.map((r) => r.file)
|
|
|
5357
6038
|
}
|
|
5358
6039
|
db.close();
|
|
5359
6040
|
});
|
|
5360
|
-
program.command("change-surface <file>").description("Pre-change briefing: exports, consumers,
|
|
6041
|
+
program.command("change-surface <file>").description("Pre-change briefing: exports, consumers, and blast-radius risk").action((file) => {
|
|
5361
6042
|
const db = openDb();
|
|
5362
|
-
const result = changeSurface(db, file);
|
|
6043
|
+
const result = queries.changeSurface(db, file);
|
|
5363
6044
|
if (!result) {
|
|
5364
6045
|
console.log("File not found in index.");
|
|
5365
6046
|
db.close();
|
|
5366
6047
|
return;
|
|
5367
6048
|
}
|
|
5368
6049
|
console.log(`File: ${result.file}`);
|
|
5369
|
-
console.log(`
|
|
6050
|
+
console.log(`External consumers: ${result.totalExternalConsumers}
|
|
5370
6051
|
`);
|
|
5371
6052
|
for (const s of result.symbols) {
|
|
5372
6053
|
const risk = s.riskLevel === "high" ? " *** HIGH RISK ***" : s.riskLevel === "medium" ? " * medium risk *" : "";
|
|
5373
|
-
|
|
5374
|
-
console.log(` ${s.startLine}-${s.endLine} ${s.shortName} [${s.externalConsumers} consumers]${tests}${risk}`);
|
|
6054
|
+
console.log(` ${s.startLine}-${s.endLine} ${s.shortName} [${s.externalConsumers} consumers]${risk}`);
|
|
5375
6055
|
}
|
|
5376
6056
|
db.close();
|
|
5377
6057
|
});
|
|
5378
|
-
program.command("diff-impact").description("Compute
|
|
6058
|
+
program.command("diff-impact").description("Compute changed symbols and downstream consumers from current git diff").option("--base <ref>", "Git ref to diff against (default: HEAD)").action((opts) => {
|
|
5379
6059
|
const db = openDb();
|
|
5380
|
-
const result = diffImpact(db, { base: opts.base });
|
|
6060
|
+
const result = queries.diffImpact(db, { base: opts.base });
|
|
5381
6061
|
console.log(`Changed files: ${result.summary.totalChangedFiles}`);
|
|
5382
6062
|
console.log(`Changed symbols: ${result.summary.totalChangedSymbols}`);
|
|
5383
6063
|
console.log(`Affected consumer files: ${result.summary.totalAffectedFiles}`);
|
|
5384
|
-
|
|
5385
|
-
`);
|
|
6064
|
+
if (result.summary.note) {
|
|
6065
|
+
console.log(`Note: ${result.summary.note}`);
|
|
6066
|
+
}
|
|
6067
|
+
console.log("");
|
|
5386
6068
|
if (result.changedSymbols.length > 0) {
|
|
5387
6069
|
console.log("Changed symbols:");
|
|
5388
6070
|
for (const s of result.changedSymbols) {
|
|
5389
6071
|
console.log(` ${s.file} ${s.shortName} (fan-in: ${s.fanIn})`);
|
|
5390
6072
|
}
|
|
5391
6073
|
}
|
|
5392
|
-
if (result.uncoveredSymbols.length > 0) {
|
|
5393
|
-
console.log("\nUncovered (no test references):");
|
|
5394
|
-
for (const s of result.uncoveredSymbols) {
|
|
5395
|
-
console.log(` ${s.file} ${s.shortName}`);
|
|
5396
|
-
}
|
|
5397
|
-
}
|
|
5398
6074
|
if (result.affectedConsumers.length > 0) {
|
|
5399
6075
|
console.log("\nAffected consumer files:");
|
|
5400
6076
|
for (const c of result.affectedConsumers) {
|
|
@@ -5405,7 +6081,7 @@ program.command("diff-impact").description("Compute affected symbols from curren
|
|
|
5405
6081
|
});
|
|
5406
6082
|
program.command("drift [module]").description("Detect unused imports, layer violations, and pattern deviations").action((module) => {
|
|
5407
6083
|
const db = openDb();
|
|
5408
|
-
const summary = drift(db, { scope: module });
|
|
6084
|
+
const summary = queries.drift(db, { scope: module });
|
|
5409
6085
|
if (summary.results.length === 0) {
|
|
5410
6086
|
console.log("No drift detected.");
|
|
5411
6087
|
} else {
|
|
@@ -5430,7 +6106,7 @@ ${summary.unusedImports} unused import(s), ${summary.layerViolations} layer viol
|
|
|
5430
6106
|
});
|
|
5431
6107
|
program.command("wrapper-candidates").description("Find symbols only called by one consumer (premature abstractions)").option("-s, --scope <path>", "Limit to files matching path").option("--max-loc <n>", "Maximum LOC for candidates", parseIntSafe, 15).option("-n, --limit <n>", "Number of results", parseIntSafe, 30).action((opts) => {
|
|
5432
6108
|
const db = openDb();
|
|
5433
|
-
const results = wrapperCandidates(db, { scope: opts.scope, maxLoc: opts.maxLoc, limit: opts.limit });
|
|
6109
|
+
const results = queries.wrapperCandidates(db, { scope: opts.scope, maxLoc: opts.maxLoc, limit: opts.limit });
|
|
5434
6110
|
if (results.length === 0) {
|
|
5435
6111
|
console.log("No wrapper candidates found.");
|
|
5436
6112
|
} else {
|
|
@@ -5445,7 +6121,7 @@ ${results.length} wrapper candidate(s).`);
|
|
|
5445
6121
|
});
|
|
5446
6122
|
program.command("passthrough-candidates").description("Find functions that just forward to one other function").option("-s, --scope <path>", "Limit to files matching path").option("--max-loc <n>", "Maximum LOC for candidates", parseIntSafe, 15).option("-n, --limit <n>", "Number of results", parseIntSafe, 30).action((opts) => {
|
|
5447
6123
|
const db = openDb();
|
|
5448
|
-
const results = passthroughCandidates(db, { scope: opts.scope, maxLoc: opts.maxLoc, limit: opts.limit });
|
|
6124
|
+
const results = queries.passthroughCandidates(db, { scope: opts.scope, maxLoc: opts.maxLoc, limit: opts.limit });
|
|
5449
6125
|
if (results.length === 0) {
|
|
5450
6126
|
console.log("No passthrough candidates found.");
|
|
5451
6127
|
} else {
|
|
@@ -5460,7 +6136,7 @@ ${results.length} passthrough candidate(s).`);
|
|
|
5460
6136
|
});
|
|
5461
6137
|
program.command("stale-abstractions").description("Find types/interfaces with 0-1 consumers (premature abstractions)").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC", parseIntSafe, 3).option("-n, --limit <n>", "Number of results", parseIntSafe, 30).action((opts) => {
|
|
5462
6138
|
const db = openDb();
|
|
5463
|
-
const results = staleAbstractions(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
|
|
6139
|
+
const results = queries.staleAbstractions(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
|
|
5464
6140
|
if (results.length === 0) {
|
|
5465
6141
|
console.log("No stale abstractions found.");
|
|
5466
6142
|
} else {
|
|
@@ -5475,7 +6151,7 @@ ${results.length} stale abstraction(s).`);
|
|
|
5475
6151
|
});
|
|
5476
6152
|
program.command("complexity-hotspots").description("Composite complexity score: LOC x fan-in x fan-out").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC", parseIntSafe, 10).option("-n, --limit <n>", "Number of results", parseIntSafe, 20).action((opts) => {
|
|
5477
6153
|
const db = openDb();
|
|
5478
|
-
const results = complexityHotspots(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
|
|
6154
|
+
const results = queries.complexityHotspots(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
|
|
5479
6155
|
if (results.length === 0) {
|
|
5480
6156
|
console.log("No complexity hotspots found.");
|
|
5481
6157
|
} else {
|
|
@@ -5489,7 +6165,7 @@ program.command("complexity-hotspots").description("Composite complexity score:
|
|
|
5489
6165
|
});
|
|
5490
6166
|
program.command("health").description("Composite codebase health report with prioritized action list").option("-s, --scope <path>", "Limit to files matching path").option("--json", "Output as JSON for programmatic consumption").action((opts) => {
|
|
5491
6167
|
const db = openDb();
|
|
5492
|
-
const report = health(db, { scope: opts.scope });
|
|
6168
|
+
const report = queries.health(db, { scope: opts.scope });
|
|
5493
6169
|
if (opts.json) {
|
|
5494
6170
|
console.log(JSON.stringify(report, null, 2));
|
|
5495
6171
|
} else {
|
|
@@ -5510,7 +6186,6 @@ program.command("health").description("Composite codebase health report with pri
|
|
|
5510
6186
|
if (f.staleTypes > 0) console.log(` Stale abstractions: ${f.staleTypes}`);
|
|
5511
6187
|
if (f.driftedFiles > 0) console.log(` Pattern drift: ${f.driftedFiles} files`);
|
|
5512
6188
|
if (f.complexityHotspotCount > 0) console.log(` Complexity hotspots: ${f.complexityHotspotCount}`);
|
|
5513
|
-
console.log(` Test coverage: ${f.testCoveragePercent}%`);
|
|
5514
6189
|
if (report.actions.length > 0) {
|
|
5515
6190
|
console.log("\n Prioritized Actions (highest impact + lowest effort first):");
|
|
5516
6191
|
for (let i = 0; i < report.actions.length; i++) {
|
|
@@ -5533,7 +6208,7 @@ program.command("health").description("Composite codebase health report with pri
|
|
|
5533
6208
|
});
|
|
5534
6209
|
program.command("convergence <symbol1> <symbol2>").description("Show what a consolidated version of two similar functions would look like").action((symbol1, symbol2) => {
|
|
5535
6210
|
const db = openDb();
|
|
5536
|
-
const result = convergence(db, symbol1, symbol2);
|
|
6211
|
+
const result = queries.convergence(db, symbol1, symbol2);
|
|
5537
6212
|
if (!result) {
|
|
5538
6213
|
console.log("One or both symbols not found.");
|
|
5539
6214
|
db.close();
|
|
@@ -5563,7 +6238,7 @@ ${Math.round(result.similarity * 100)}% callee overlap
|
|
|
5563
6238
|
});
|
|
5564
6239
|
program.command("code <symbol>").description("Read the source code for a symbol (bounded to its definition range)").option("-C, --context <n>", "Extra lines of context above/below", parseIntSafe, 0).action((symbol, opts) => {
|
|
5565
6240
|
const db = openDb();
|
|
5566
|
-
const result = code(db, symbol, { context: opts.context });
|
|
6241
|
+
const result = queries.code(db, symbol, { context: opts.context });
|
|
5567
6242
|
if (!result) {
|
|
5568
6243
|
console.log("Symbol not found or file unreadable.");
|
|
5569
6244
|
db.close();
|
|
@@ -5579,7 +6254,7 @@ program.command("code <symbol>").description("Read the source code for a symbol
|
|
|
5579
6254
|
});
|
|
5580
6255
|
program.command("complexity <symbol>").description("Per-symbol complexity: branches, cyclomatic estimate, fan-in/out, callees").action((symbol) => {
|
|
5581
6256
|
const db = openDb();
|
|
5582
|
-
const result = complexity(db, symbol);
|
|
6257
|
+
const result = queries.complexity(db, symbol);
|
|
5583
6258
|
if (!result) {
|
|
5584
6259
|
console.log("Symbol not found.");
|
|
5585
6260
|
db.close();
|
|
@@ -5597,7 +6272,7 @@ program.command("complexity <symbol>").description("Per-symbol complexity: branc
|
|
|
5597
6272
|
});
|
|
5598
6273
|
program.command("dataflow <symbol>").description("Reference-level dataflow: definition sites, usage sites, producers, consumers").action((symbol) => {
|
|
5599
6274
|
const db = openDb();
|
|
5600
|
-
const result = dataflow(db, symbol);
|
|
6275
|
+
const result = queries.dataflow(db, symbol);
|
|
5601
6276
|
if (!result) {
|
|
5602
6277
|
console.log("Symbol not found.");
|
|
5603
6278
|
db.close();
|
|
@@ -5634,7 +6309,7 @@ program.command("dataflow <symbol>").description("Reference-level dataflow: defi
|
|
|
5634
6309
|
program.command("slice <symbol>").description("Reference-level program slice: what affects this (backward) or what this affects (forward)").option("--forward", "Forward slice (what does this affect). Default is backward.").action((symbol, opts) => {
|
|
5635
6310
|
const db = openDb();
|
|
5636
6311
|
const direction = opts.forward ? "forward" : "backward";
|
|
5637
|
-
const result = slice(db, symbol, { direction });
|
|
6312
|
+
const result = queries.slice(db, symbol, { direction });
|
|
5638
6313
|
if (!result) {
|
|
5639
6314
|
console.log("Symbol not found.");
|
|
5640
6315
|
db.close();
|
|
@@ -5672,7 +6347,7 @@ program.command("check-deps").description("Check if required dependencies (scip
|
|
|
5672
6347
|
});
|
|
5673
6348
|
program.command("redundant-reexports").description("Find barrel re-exports that nobody imports through").option("-s, --scope <path>", "Limit to files matching path").option("-n, --limit <n>", "Number of results", parseIntSafe, 30).action((opts) => {
|
|
5674
6349
|
const db = openDb();
|
|
5675
|
-
const results = redundantReexports(db, { scope: opts.scope, limit: opts.limit });
|
|
6350
|
+
const results = queries.redundantReexports(db, { scope: opts.scope, limit: opts.limit });
|
|
5676
6351
|
if (results.length === 0) {
|
|
5677
6352
|
console.log("No redundant re-exports found.");
|
|
5678
6353
|
} else {
|
|
@@ -5693,7 +6368,7 @@ ${results.length} redundant re-export(s).`);
|
|
|
5693
6368
|
});
|
|
5694
6369
|
program.command("similar-signatures").description("Find functions with near-identical type signatures (same shape)").option("-s, --scope <path>", "Limit to files matching path").option("--min-loc <n>", "Minimum LOC per function", parseIntSafe, 3).option("-n, --limit <n>", "Number of groups", parseIntSafe, 20).action((opts) => {
|
|
5695
6370
|
const db = openDb();
|
|
5696
|
-
const groups = similarSignatures(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
|
|
6371
|
+
const groups = queries.similarSignatures(db, { scope: opts.scope, minLoc: opts.minLoc, limit: opts.limit });
|
|
5697
6372
|
if (groups.length === 0) {
|
|
5698
6373
|
console.log("No same-shape function groups found.");
|
|
5699
6374
|
} else {
|
|
@@ -5750,12 +6425,16 @@ program.command("status").description("Show index status for this project").acti
|
|
|
5750
6425
|
const projectRoot = resolveProjectRoot();
|
|
5751
6426
|
const config = loadProjectConfig(projectRoot);
|
|
5752
6427
|
const paths = resolveIndexPaths(projectRoot, config);
|
|
6428
|
+
const dbPath = resolveActiveDbPath(projectRoot);
|
|
5753
6429
|
console.log(`Project: ${projectRoot}`);
|
|
5754
|
-
console.log(`DB path: ${
|
|
5755
|
-
|
|
5756
|
-
|
|
6430
|
+
console.log(`DB path: ${dbPath}`);
|
|
6431
|
+
if (dbPath !== paths.dbPath) {
|
|
6432
|
+
console.log(`Config: ${paths.dbPath} (fallback to project root index.db)`);
|
|
6433
|
+
}
|
|
6434
|
+
console.log(`Exists: ${existsSync8(dbPath) ? "yes" : "no"}`);
|
|
6435
|
+
if (existsSync8(dbPath)) {
|
|
5757
6436
|
withDb((db) => {
|
|
5758
|
-
const s = stats(db);
|
|
6437
|
+
const s = queries.stats(db);
|
|
5759
6438
|
console.log(`Symbols: ${s.symbols}`);
|
|
5760
6439
|
console.log(`Files: ${s.documents}`);
|
|
5761
6440
|
console.log(`Size: ${formatBytes(s.indexSizeBytes)}`);
|
|
@@ -5766,7 +6445,9 @@ program.command("status").description("Show index status for this project").acti
|
|
|
5766
6445
|
});
|
|
5767
6446
|
}
|
|
5768
6447
|
});
|
|
5769
|
-
|
|
6448
|
+
if (process.argv[1] && fileURLToPath2(import.meta.url) === process.argv[1]) {
|
|
6449
|
+
program.parse();
|
|
6450
|
+
}
|
|
5770
6451
|
function collect(value, prev) {
|
|
5771
6452
|
return prev.concat([value]);
|
|
5772
6453
|
}
|
|
@@ -5795,4 +6476,7 @@ function formatStatus(status) {
|
|
|
5795
6476
|
}
|
|
5796
6477
|
}
|
|
5797
6478
|
}
|
|
6479
|
+
export {
|
|
6480
|
+
program
|
|
6481
|
+
};
|
|
5798
6482
|
//# sourceMappingURL=cli.js.map
|