scip-query 0.2.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-W4ALF422.js → chunk-3566TKJ5.js} +3 -3
- package/dist/{chunk-Z6YZJ36C.js → chunk-4ACRRQC4.js} +8 -4
- package/dist/{chunk-Z4GHE2HD.js → chunk-4BQFSNFI.js} +6 -2
- package/dist/{chunk-4XHWPRAX.js → chunk-6QSHLFSL.js} +3 -3
- package/dist/{chunk-4EXL2CUA.js → chunk-6WVR5K46.js} +16 -8
- package/dist/{chunk-ZQIIPFD7.js → chunk-75RQSBTK.js} +2 -2
- package/dist/{chunk-KPPHZCZJ.js → chunk-7HK5ZLOE.js} +28 -46
- package/dist/{chunk-MCUX5LA7.js → chunk-7JFZSOJ7.js} +3 -3
- package/dist/{chunk-6SXADWLW.js → chunk-AKMBBKWV.js} +2 -2
- package/dist/{chunk-NHBZIL2J.js → chunk-AMNISGYR.js} +3 -3
- package/dist/{chunk-2CKGIR6G.js → chunk-BFLULBEU.js} +3 -3
- package/dist/{chunk-HPFZLISB.js → chunk-CU62ZDHI.js} +2 -2
- package/dist/{chunk-EQYLEQCW.js → chunk-DY4AFG2W.js} +12 -10
- package/dist/{chunk-5RMYT5WH.js → chunk-F7XU27LU.js} +2 -2
- package/dist/{chunk-KCBMVQL5.js → chunk-GPJVPT3U.js} +2 -2
- package/dist/{chunk-NUZ4OMU3.js → chunk-GU2H5QRN.js} +2 -2
- package/dist/{chunk-UJQN5N3I.js → chunk-H6WCPKCX.js} +6 -3
- package/dist/{chunk-OVPLOMPY.js → chunk-HMYJJ3HY.js} +7 -4
- package/dist/chunk-IJKLB2JW.js +69 -0
- package/dist/{chunk-DGUPQSOR.js → chunk-IXPHLF6K.js} +2 -2
- package/dist/{chunk-7PBOG4YE.js → chunk-KBOQX573.js} +2 -2
- package/dist/{chunk-ZOGY2V3N.js → chunk-LLMPAG56.js} +93 -30
- package/dist/{chunk-BOVXCR46.js → chunk-LTJC5ZQL.js} +2 -2
- package/dist/{chunk-LAWMH22O.js → chunk-M3NPW3FC.js} +2 -2
- package/dist/{chunk-NWCE4CIC.js → chunk-M4QGEKKD.js} +5 -27
- package/dist/{chunk-63G7IQTD.js → chunk-MVH45PYK.js} +20 -40
- package/dist/chunk-N4C3H7LH.js +37 -0
- package/dist/chunk-NG5F43OU.js +200 -0
- package/dist/{chunk-D567NFIF.js → chunk-NVIIM34O.js} +3 -3
- package/dist/{chunk-BNN2RKD2.js → chunk-ORINICIZ.js} +3 -3
- package/dist/{chunk-4PDAL6IL.js → chunk-PMJKOXOT.js} +3 -3
- package/dist/{chunk-QOV2R2WT.js → chunk-QIXNAB5K.js} +42 -2
- package/dist/{chunk-7LLPRPR5.js → chunk-R2I3M5B4.js} +2 -2
- package/dist/{chunk-7RLE5EWE.js → chunk-R56FJU3E.js} +34 -13
- package/dist/{chunk-H2MDONBU.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-ZK6GXM3J.js → chunk-WVK7AASK.js} +3 -3
- package/dist/{chunk-HMLMH7VZ.js → chunk-Y3M323OX.js} +2 -2
- package/dist/{chunk-DCKMSTJ4.js → chunk-Y4JFVQ7C.js} +2 -2
- package/dist/{chunk-7UCKSQRS.js → chunk-YAFWL3RA.js} +3 -3
- package/dist/{chunk-FGXRVW7G.js → chunk-YZ6L7GFO.js} +2 -2
- package/dist/cli.js +1355 -671
- package/dist/{db-BNVVZSfP.d.ts → db-BHYam4BK.d.ts} +6 -18
- package/dist/index.d.ts +15 -15
- package/dist/index.js +260 -231
- 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/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 +14 -14
- 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 +43 -48
- 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/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/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-2CKGIR6G.js.map +0 -1
- package/dist/chunk-3UOUTZQT.js +0 -45
- package/dist/chunk-3UOUTZQT.js.map +0 -1
- package/dist/chunk-4EXL2CUA.js.map +0 -1
- package/dist/chunk-4PDAL6IL.js.map +0 -1
- package/dist/chunk-4TYLS5XX.js.map +0 -1
- package/dist/chunk-4XHWPRAX.js.map +0 -1
- package/dist/chunk-5RMYT5WH.js.map +0 -1
- package/dist/chunk-63G7IQTD.js.map +0 -1
- package/dist/chunk-6SXADWLW.js.map +0 -1
- package/dist/chunk-74RFWB5T.js.map +0 -1
- package/dist/chunk-7LLPRPR5.js.map +0 -1
- package/dist/chunk-7PBOG4YE.js.map +0 -1
- package/dist/chunk-7RLE5EWE.js.map +0 -1
- package/dist/chunk-7UCKSQRS.js.map +0 -1
- package/dist/chunk-BNN2RKD2.js.map +0 -1
- package/dist/chunk-BOVXCR46.js.map +0 -1
- package/dist/chunk-D567NFIF.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-DGUPQSOR.js.map +0 -1
- package/dist/chunk-DVWGWHFW.js +0 -99
- package/dist/chunk-DVWGWHFW.js.map +0 -1
- package/dist/chunk-EQYLEQCW.js.map +0 -1
- package/dist/chunk-FGXRVW7G.js.map +0 -1
- package/dist/chunk-H2MDONBU.js.map +0 -1
- package/dist/chunk-HB7MRLLL.js +0 -76
- package/dist/chunk-HB7MRLLL.js.map +0 -1
- package/dist/chunk-HDSRORNV.js.map +0 -1
- package/dist/chunk-HMLMH7VZ.js.map +0 -1
- package/dist/chunk-HPFZLISB.js.map +0 -1
- package/dist/chunk-HZBC7PPD.js +0 -88
- package/dist/chunk-HZBC7PPD.js.map +0 -1
- package/dist/chunk-ITZ3DDOG.js.map +0 -1
- package/dist/chunk-JJP7KQND.js +0 -1
- package/dist/chunk-JJP7KQND.js.map +0 -1
- package/dist/chunk-KCBMVQL5.js.map +0 -1
- package/dist/chunk-KPPHZCZJ.js.map +0 -1
- package/dist/chunk-LAWMH22O.js.map +0 -1
- package/dist/chunk-MCUX5LA7.js.map +0 -1
- package/dist/chunk-MGNMHKX3.js.map +0 -1
- package/dist/chunk-N5KEREIA.js.map +0 -1
- package/dist/chunk-NHBZIL2J.js.map +0 -1
- package/dist/chunk-NUZ4OMU3.js.map +0 -1
- package/dist/chunk-NWCE4CIC.js.map +0 -1
- package/dist/chunk-OVPLOMPY.js.map +0 -1
- package/dist/chunk-QOV2R2WT.js.map +0 -1
- package/dist/chunk-SEFSL2GF.js.map +0 -1
- package/dist/chunk-UJQN5N3I.js.map +0 -1
- package/dist/chunk-W4ALF422.js.map +0 -1
- package/dist/chunk-Z4GHE2HD.js.map +0 -1
- package/dist/chunk-Z6YZJ36C.js.map +0 -1
- package/dist/chunk-ZK6GXM3J.js.map +0 -1
- package/dist/chunk-ZOGY2V3N.js.map +0 -1
- package/dist/chunk-ZQIIPFD7.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/chunk-HZBC7PPD.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TEST_FILE_PATTERNS,
|
|
3
|
-
testFileExclusionSql,
|
|
4
|
-
testFileMatchSql
|
|
5
|
-
} from "./chunk-ZOGY2V3N.js";
|
|
6
|
-
import {
|
|
7
|
-
shortenSymbol
|
|
8
|
-
} from "./chunk-QOV2R2WT.js";
|
|
9
|
-
|
|
10
|
-
// src/queries/test-coverage.ts
|
|
11
|
-
function testCoverage(db, symbolPattern) {
|
|
12
|
-
const syms = db.all(
|
|
13
|
-
`SELECT gs.id, gs.symbol, d.relative_path
|
|
14
|
-
FROM global_symbols gs
|
|
15
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
16
|
-
JOIN documents d ON der.document_id = d.id
|
|
17
|
-
WHERE gs.symbol LIKE ?
|
|
18
|
-
${db.pathExclusionsFor("d")}
|
|
19
|
-
${db.symbolNoiseFor("gs")}
|
|
20
|
-
ORDER BY d.relative_path`,
|
|
21
|
-
`%${symbolPattern}%`
|
|
22
|
-
);
|
|
23
|
-
const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
|
|
24
|
-
return syms.filter((s) => !db.isIgnored(s.relative_path)).map((s) => {
|
|
25
|
-
const testFiles = db.all(
|
|
26
|
-
`SELECT DISTINCT ref_d.relative_path
|
|
27
|
-
FROM mentions m
|
|
28
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
29
|
-
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
30
|
-
WHERE m.symbol_id = ?
|
|
31
|
-
AND m.role != 1
|
|
32
|
-
AND (${testPatternSql})
|
|
33
|
-
ORDER BY ref_d.relative_path`,
|
|
34
|
-
s.id
|
|
35
|
-
).map((r) => r.relative_path);
|
|
36
|
-
return {
|
|
37
|
-
symbol: s.symbol,
|
|
38
|
-
shortName: shortenSymbol(s.symbol),
|
|
39
|
-
definedIn: s.relative_path,
|
|
40
|
-
testFiles,
|
|
41
|
-
covered: testFiles.length > 0
|
|
42
|
-
};
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
function testCoverageSummary(db, opts = {}) {
|
|
46
|
-
const { scope, minLoc = 3 } = opts;
|
|
47
|
-
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
48
|
-
const testPatternSql = testFileExclusionSql("d");
|
|
49
|
-
const symbols = db.all(
|
|
50
|
-
`SELECT gs.id
|
|
51
|
-
FROM global_symbols gs
|
|
52
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
53
|
-
JOIN documents d ON der.document_id = d.id
|
|
54
|
-
WHERE 1 = 1
|
|
55
|
-
${db.pathExclusionsFor("d")}
|
|
56
|
-
AND ${testPatternSql}
|
|
57
|
-
${db.symbolNoiseFor("gs")}
|
|
58
|
-
AND gs.symbol NOT LIKE '%#%'
|
|
59
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
60
|
-
${scopeFilter}`,
|
|
61
|
-
minLoc
|
|
62
|
-
);
|
|
63
|
-
const testRefSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
|
|
64
|
-
let covered = 0;
|
|
65
|
-
for (const s of symbols) {
|
|
66
|
-
const hasTest = db.get(
|
|
67
|
-
`SELECT COUNT(*) AS c FROM mentions m
|
|
68
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
69
|
-
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
70
|
-
WHERE m.symbol_id = ? AND m.role != 1 AND (${testRefSql})`,
|
|
71
|
-
s.id
|
|
72
|
-
);
|
|
73
|
-
if (hasTest && hasTest.c > 0) covered++;
|
|
74
|
-
}
|
|
75
|
-
const total = symbols.length;
|
|
76
|
-
return {
|
|
77
|
-
total,
|
|
78
|
-
covered,
|
|
79
|
-
uncovered: total - covered,
|
|
80
|
-
percent: total > 0 ? Math.round(covered / total * 100) : 0
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export {
|
|
85
|
-
testCoverage,
|
|
86
|
-
testCoverageSummary
|
|
87
|
-
};
|
|
88
|
-
//# sourceMappingURL=chunk-HZBC7PPD.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/test-coverage.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { TEST_FILE_PATTERNS, testFileExclusionSql, testFileMatchSql } from '../query-support.js';\nimport type { TestCoverageResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Check if a symbol is referenced by any test file.\n * Reports which test files cover (reference) each matching symbol.\n */\nexport function testCoverage(\n db: ScipDatabase,\n symbolPattern: string,\n): TestCoverageResult[] {\n // Find matching symbols\n const syms = db.all<{\n id: number;\n symbol: string;\n relative_path: string;\n }>(\n `SELECT gs.id, gs.symbol, d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.symbol LIKE ?\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n ORDER BY d.relative_path`,\n `%${symbolPattern}%`,\n );\n\n const testPatternSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);\n\n return syms\n .filter((s) => !db.isIgnored(s.relative_path))\n .map((s) => {\n // Find test files that reference this symbol\n const testFiles = db.all<{ relative_path: string }>(\n `SELECT DISTINCT ref_d.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND (${testPatternSql})\n ORDER BY ref_d.relative_path`,\n s.id,\n ).map((r) => r.relative_path);\n\n return {\n symbol: s.symbol,\n shortName: shortenSymbol(s.symbol),\n definedIn: s.relative_path,\n testFiles,\n covered: testFiles.length > 0,\n };\n });\n}\n\n/**\n * Summary: what percentage of symbols in scope are referenced by test files?\n */\nexport function testCoverageSummary(\n db: ScipDatabase,\n opts: { scope?: string; minLoc?: number } = {},\n): { total: number; covered: number; uncovered: number; percent: number } {\n const { scope, minLoc = 3 } = opts;\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n const testPatternSql = testFileExclusionSql('d');\n\n const symbols = db.all<{ id: number }>(\n `SELECT gs.id\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testPatternSql}\n ${db.symbolNoiseFor('gs')}\n AND gs.symbol NOT LIKE '%#%'\n AND (der.end_line - der.start_line + 1) >= ?\n ${scopeFilter}`,\n minLoc,\n );\n\n const testRefSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);\n\n let covered = 0;\n for (const s of symbols) {\n const hasTest = db.get<{ c: number }>(\n `SELECT COUNT(*) AS c FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n WHERE m.symbol_id = ? AND m.role != 1 AND (${testRefSql})`,\n s.id,\n );\n if (hasTest && hasTest.c > 0) covered++;\n }\n\n const total = symbols.length;\n return {\n total,\n covered,\n uncovered: total - covered,\n percent: total > 0 ? Math.round((covered / total) * 100) : 0,\n };\n}\n"],"mappings":";;;;;;;;;;AASO,SAAS,aACd,IACA,eACsB;AAEtB,QAAM,OAAO,GAAG;AAAA,IAKd;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA,IAE3B,IAAI,aAAa;AAAA,EACnB;AAEA,QAAM,iBAAiB,iBAAiB,SAAS,kBAAkB;AAEnE,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,MAAM;AAEV,UAAM,YAAY,GAAG;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAMS,cAAc;AAAA;AAAA,MAEvB,EAAE;AAAA,IACJ,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa;AAE5B,WAAO;AAAA,MACL,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,WAAW,EAAE;AAAA,MACb;AAAA,MACA,SAAS,UAAU,SAAS;AAAA,IAC9B;AAAA,EACF,CAAC;AACL;AAKO,SAAS,oBACd,IACA,OAA4C,CAAC,GAC2B;AACxE,QAAM,EAAE,OAAO,SAAS,EAAE,IAAI;AAC9B,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AACtE,QAAM,iBAAiB,qBAAqB,GAAG;AAE/C,QAAM,UAAU,GAAG;AAAA,IACjB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA,YACrB,cAAc;AAAA,QAClB,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,QAGvB,WAAW;AAAA,IACf;AAAA,EACF;AAEA,QAAM,aAAa,iBAAiB,SAAS,kBAAkB;AAE/D,MAAI,UAAU;AACd,aAAW,KAAK,SAAS;AACvB,UAAM,UAAU,GAAG;AAAA,MACjB;AAAA;AAAA;AAAA,oDAG8C,UAAU;AAAA,MACxD,EAAE;AAAA,IACJ;AACA,QAAI,WAAW,QAAQ,IAAI,EAAG;AAAA,EAChC;AAEA,QAAM,QAAQ,QAAQ;AACtB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW,QAAQ;AAAA,IACnB,SAAS,QAAQ,IAAI,KAAK,MAAO,UAAU,QAAS,GAAG,IAAI;AAAA,EAC7D;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/refs.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { RefResult } from '../types.js';\n\nexport function refs(db: ScipDatabase, symbolPattern: string): RefResult[] {\n const rows = db.all<{\n relative_path: string;\n start_line: number;\n }>(\n `SELECT DISTINCT d.relative_path, c.start_line\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n WHERE gs.symbol LIKE ?\n AND ${db.localSymbolPredicate}\n AND m.role != 1\n ORDER BY d.relative_path, c.start_line`,\n `%${symbolPattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n relativePath: r.relative_path,\n line: r.start_line,\n }));\n}\n"],"mappings":";AAGO,SAAS,KAAK,IAAkB,eAAoC;AACzE,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMQ,GAAG,oBAAoB;AAAA;AAAA;AAAA,IAG/B,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,cAAc,EAAE;AAAA,IAChB,MAAM,EAAE;AAAA,EACV,EAAE;AACN;","names":[]}
|
package/dist/chunk-JJP7KQND.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=chunk-JJP7KQND.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/symbols.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { SymbolResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\nimport { cleanSignature } from './clean-signature.js';\n\nexport function symbols(db: ScipDatabase, filePattern: string): SymbolResult[] {\n const rows = db.all<{\n start_line: number;\n end_line: number;\n sig: string | null;\n symbol: string;\n relative_path: string;\n }>(\n `SELECT\n der.start_line,\n der.end_line,\n REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig,\n gs.symbol,\n d.relative_path\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n JOIN documents d ON der.document_id = d.id\n WHERE d.relative_path LIKE ?\n AND ${db.localSymbolPredicate}\n ${db.symbolNoise}\n ORDER BY der.start_line`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n startLine: r.start_line,\n endLine: r.end_line,\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n signature: cleanSignature(r.sig),\n }));\n}\n"],"mappings":";;;;;;;;AAKO,SAAS,QAAQ,IAAkB,aAAqC;AAC7E,QAAM,OAAO,GAAG;AAAA,IAOd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAUQ,GAAG,oBAAoB;AAAA,QAC3B,GAAG,WAAW;AAAA;AAAA,IAElB,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,WAAW,eAAe,EAAE,GAAG;AAAA,EACjC,EAAE;AACN;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/diff-impact.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process';\nimport type { ScipDatabase } from '../db.js';\nimport { TEST_FILE_PATTERNS, testFileMatchSql } from '../query-support.js';\nimport type { DiffImpactResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Given a git diff, compute the affected symbol set.\n * Finds all symbols defined in changed files, their fan-in,\n * the files that consume them, and test coverage gaps.\n */\nexport function diffImpact(\n db: ScipDatabase,\n opts: { base?: string } = {},\n): DiffImpactResult {\n const { base = 'HEAD' } = opts;\n\n // Get changed files from git\n let changedFileLines: string[];\n try {\n const stdout = execFileSync('git', ['diff', '--name-only', base], {\n encoding: 'utf-8',\n cwd: db.config.projectRoot,\n timeout: 10_000,\n });\n changedFileLines = stdout\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n } catch {\n // Not in a git repo or git not available — return empty result\n return {\n changedFiles: [],\n changedSymbols: [],\n affectedConsumers: [],\n uncoveredSymbols: [],\n summary: {\n totalChangedFiles: 0,\n totalChangedSymbols: 0,\n totalAffectedFiles: 0,\n testCoveragePercent: 0,\n },\n };\n }\n\n if (changedFileLines.length === 0) {\n return {\n changedFiles: [],\n changedSymbols: [],\n affectedConsumers: [],\n uncoveredSymbols: [],\n summary: {\n totalChangedFiles: 0,\n totalChangedSymbols: 0,\n totalAffectedFiles: 0,\n testCoveragePercent: 0,\n },\n };\n }\n\n // Match changed files against the index\n const changedFiles: string[] = [];\n const changedDocIds: number[] = [];\n\n for (const file of changedFileLines) {\n const doc = db.get<{ id: number; relative_path: string }>(\n `SELECT id, relative_path FROM documents\n WHERE relative_path LIKE ?\n LIMIT 1`,\n `%${file}`,\n );\n if (doc && !db.isIgnored(doc.relative_path)) {\n changedFiles.push(doc.relative_path);\n changedDocIds.push(doc.id);\n }\n }\n\n if (changedDocIds.length === 0) {\n return {\n changedFiles: changedFileLines,\n changedSymbols: [],\n affectedConsumers: [],\n uncoveredSymbols: [],\n summary: {\n totalChangedFiles: changedFileLines.length,\n totalChangedSymbols: 0,\n totalAffectedFiles: 0,\n testCoveragePercent: 0,\n },\n };\n }\n\n // Get all symbols defined in changed files\n const docPlaceholders = changedDocIds.map(() => '?').join(',');\n const syms = db.all<{\n symbol_id: number;\n symbol: string;\n relative_path: string;\n }>(\n `SELECT DISTINCT gs.id AS symbol_id, gs.symbol, d.relative_path\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n JOIN documents d ON der.document_id = d.id\n WHERE der.document_id IN (${docPlaceholders})\n ${db.symbolNoiseFor('gs')}\n ORDER BY d.relative_path`,\n ...changedDocIds,\n );\n\n // For each symbol, compute fan-in (distinct referencing documents)\n const testPatternSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);\n const changedSymbols: DiffImpactResult['changedSymbols'] = [];\n const consumerMap = new Map<string, Set<string>>(); // file -> set of consumed symbol shortNames\n const uncoveredSymbols: DiffImpactResult['uncoveredSymbols'] = [];\n let coveredCount = 0;\n\n for (const sym of syms) {\n // Fan-in: distinct files that reference this symbol\n const fanInRow = db.get<{ fan_in: number }>(\n `SELECT COUNT(DISTINCT c.document_id) AS fan_in\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = ?\n AND m.role != 1`,\n sym.symbol_id,\n );\n\n const fanIn = fanInRow?.fan_in ?? 0;\n const shortName = shortenSymbol(sym.symbol);\n\n changedSymbols.push({\n symbol: sym.symbol,\n shortName,\n file: sym.relative_path,\n fanIn,\n });\n\n // Collect consumer files (excluding the changed files themselves)\n const consumers = db.all<{ relative_path: string }>(\n `SELECT DISTINCT ref_d.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND ref_d.relative_path NOT IN (${changedFiles.map(() => '?').join(',')})\n ${db.pathExclusionsFor('ref_d')}`,\n sym.symbol_id,\n ...changedFiles,\n );\n\n for (const consumer of consumers) {\n if (db.isIgnored(consumer.relative_path)) continue;\n if (!consumerMap.has(consumer.relative_path)) {\n consumerMap.set(consumer.relative_path, new Set());\n }\n consumerMap.get(consumer.relative_path)!.add(shortName);\n }\n\n // Check test coverage\n const hasTest = db.get<{ c: number }>(\n `SELECT COUNT(*) AS c\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND (${testPatternSql})`,\n sym.symbol_id,\n );\n\n if (hasTest && hasTest.c > 0) {\n coveredCount++;\n } else {\n uncoveredSymbols.push({\n symbol: sym.symbol,\n shortName,\n file: sym.relative_path,\n });\n }\n }\n\n // Build affected consumers list\n const affectedConsumers = [...consumerMap.entries()]\n .map(([file, symbols]) => ({ file, consumedSymbols: symbols.size }))\n .sort((a, b) => b.consumedSymbols - a.consumedSymbols);\n\n const totalSymbols = changedSymbols.length;\n const testCoveragePercent =\n totalSymbols > 0 ? Math.round((coveredCount / totalSymbols) * 100) : 0;\n\n return {\n changedFiles,\n changedSymbols,\n affectedConsumers,\n uncoveredSymbols,\n summary: {\n totalChangedFiles: changedFiles.length,\n totalChangedSymbols: totalSymbols,\n totalAffectedFiles: affectedConsumers.length,\n testCoveragePercent,\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/by-kind.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { ByKindResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * SCIP SymbolInformation.Kind enum values.\n * From: https://github.com/sourcegraph/scip/blob/main/scip.proto\n */\nconst KIND_NAMES: Record<number, string> = {\n 0: 'UnspecifiedKind',\n 1: 'AbstractMethod',\n 2: 'Accessor',\n 3: 'Array',\n 4: 'Assertion',\n 5: 'AssociatedType',\n 6: 'Attribute',\n 7: 'Axiom',\n 8: 'Boolean',\n 9: 'Class',\n 10: 'Constant',\n 11: 'Constructor',\n 12: 'Contract',\n 13: 'DataFamily',\n 14: 'DefinitionMacro',\n 15: 'Delegate',\n 16: 'Enum',\n 17: 'EnumMember',\n 18: 'Error',\n 19: 'Event',\n 20: 'Fact',\n 21: 'Field',\n 22: 'File',\n 23: 'Function',\n 24: 'Getter',\n 25: 'Grammar',\n 26: 'Instance',\n 27: 'Interface',\n 28: 'Key',\n 29: 'Lang',\n 30: 'Lemma',\n 31: 'Library',\n 32: 'Macro',\n 33: 'Method',\n 34: 'MethodAlias',\n 35: 'MethodReceiver',\n 36: 'MethodSpecification',\n 37: 'Message',\n 38: 'Modifier',\n 39: 'Module',\n 40: 'Namespace',\n 41: 'Null',\n 42: 'Number',\n 43: 'Object',\n 44: 'Operator',\n 45: 'Package',\n 46: 'PackageObject',\n 47: 'Parameter',\n 48: 'ParameterLabel',\n 49: 'Pattern',\n 50: 'Predicate',\n 51: 'Property',\n 52: 'Protocol',\n 53: 'ProtocolMethod',\n 54: 'PureVirtualMethod',\n 55: 'Quasiquoter',\n 56: 'SelfParameter',\n 57: 'Setter',\n 58: 'Signature',\n 59: 'SingletonClass',\n 60: 'SingletonMethod',\n 61: 'StaticDataMember',\n 62: 'StaticEvent',\n 63: 'StaticField',\n 64: 'StaticMethod',\n 65: 'StaticProperty',\n 66: 'StaticVariable',\n 67: 'String',\n 68: 'Struct',\n 69: 'Subscript',\n 70: 'Tactic',\n 71: 'Theorem',\n 72: 'ThisParameter',\n 73: 'Trait',\n 74: 'TraitMethod',\n 75: 'Type',\n 76: 'TypeAlias',\n 77: 'TypeClass',\n 78: 'TypeClassMethod',\n 79: 'TypeFamily',\n 80: 'TypeParameter',\n 81: 'Union',\n 82: 'Value',\n 83: 'Variable',\n};\n\n/** Reverse lookup: name -> kind number */\nconst KIND_BY_NAME = new Map<string, number>();\nfor (const [k, v] of Object.entries(KIND_NAMES)) {\n KIND_BY_NAME.set(v.toLowerCase(), Number(k));\n}\n\n/**\n * Find symbols by SCIP kind (class, interface, enum, function, etc.)\n */\nexport function byKind(\n db: ScipDatabase,\n kindQuery: string,\n opts: { scope?: string; limit?: number } = {},\n): ByKindResult[] {\n const { scope, limit = 100 } = opts;\n\n // Resolve kind: accept number or name\n let kindNum: number | null = null;\n const asNum = parseInt(kindQuery, 10);\n if (!isNaN(asNum)) {\n kindNum = asNum;\n } else {\n kindNum = KIND_BY_NAME.get(kindQuery.toLowerCase()) ?? null;\n // Fuzzy match: try partial name\n if (kindNum === null) {\n for (const [name, num] of KIND_BY_NAME) {\n if (name.includes(kindQuery.toLowerCase())) {\n kindNum = num;\n break;\n }\n }\n }\n }\n\n if (kindNum === null) {\n return [];\n }\n\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Check if the index actually has kind data populated\n const hasKinds = db.get<{ c: number }>(\n `SELECT COUNT(*) AS c FROM global_symbols WHERE kind IS NOT NULL`,\n );\n if (!hasKinds || hasKinds.c === 0) {\n return []; // Indexer doesn't populate kind field\n }\n\n const rows = db.all<{\n symbol: string;\n kind: number;\n relative_path: string;\n start_line: number;\n end_line: number;\n }>(\n `SELECT gs.symbol, gs.kind, d.relative_path, der.start_line, der.end_line\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.kind = ?\n ${db.pathExclusionsFor('d')}\n ${scopeFilter}\n ORDER BY d.relative_path, der.start_line\n LIMIT ?`,\n kindNum, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n kind: r.kind,\n kindName: KIND_NAMES[r.kind] ?? 'Unknown',\n relativePath: r.relative_path,\n startLine: r.start_line,\n endLine: r.end_line,\n }));\n}\n\n/** List all symbol kinds present in the index with counts */\nexport function kindCounts(\n db: ScipDatabase,\n opts: { scope?: string } = {},\n): Array<{ kind: number; kindName: string; count: number }> {\n const scopeFilter = opts.scope\n ? `AND d.relative_path LIKE '%${opts.scope}%'`\n : '';\n\n const rows = db.all<{ kind: number; cnt: number }>(\n `SELECT gs.kind, COUNT(*) AS cnt\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND gs.kind IS NOT NULL\n AND gs.kind != 0\n ${scopeFilter}\n GROUP BY gs.kind\n ORDER BY cnt DESC`,\n );\n\n return rows.map((r) => ({\n kind: r.kind,\n kindName: KIND_NAMES[r.kind] ?? 'Unknown',\n count: r.cnt,\n }));\n}\n"],"mappings":";;;;;AAQA,IAAM,aAAqC;AAAA,EACzC,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGA,IAAM,eAAe,oBAAI,IAAoB;AAC7C,WAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC/C,eAAa,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC;AAC7C;AAKO,SAAS,OACd,IACA,WACA,OAA2C,CAAC,GAC5B;AAChB,QAAM,EAAE,OAAO,QAAQ,IAAI,IAAI;AAG/B,MAAI,UAAyB;AAC7B,QAAM,QAAQ,SAAS,WAAW,EAAE;AACpC,MAAI,CAAC,MAAM,KAAK,GAAG;AACjB,cAAU;AAAA,EACZ,OAAO;AACL,cAAU,aAAa,IAAI,UAAU,YAAY,CAAC,KAAK;AAEvD,QAAI,YAAY,MAAM;AACpB,iBAAW,CAAC,MAAM,GAAG,KAAK,cAAc;AACtC,YAAI,KAAK,SAAS,UAAU,YAAY,CAAC,GAAG;AAC1C,oBAAU;AACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,MAAM;AACpB,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAGtE,QAAM,WAAW,GAAG;AAAA,IAClB;AAAA,EACF;AACA,MAAI,CAAC,YAAY,SAAS,MAAM,GAAG;AACjC,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,OAAO,GAAG;AAAA,IAOd;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,WAAW;AAAA;AAAA;AAAA,IAGf;AAAA,IAAS;AAAA,EACX;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,MAAM,EAAE;AAAA,IACR,UAAU,WAAW,EAAE,IAAI,KAAK;AAAA,IAChC,cAAc,EAAE;AAAA,IAChB,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,EACb,EAAE;AACN;AAGO,SAAS,WACd,IACA,OAA2B,CAAC,GAC8B;AAC1D,QAAM,cAAc,KAAK,QACrB,8BAA8B,KAAK,KAAK,OACxC;AAEJ,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA;AAAA;AAAA,QAGzB,WAAW;AAAA;AAAA;AAAA,EAGjB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,MAAM,EAAE;AAAA,IACR,UAAU,WAAW,EAAE,IAAI,KAAK;AAAA,IAChC,OAAO,EAAE;AAAA,EACX,EAAE;AACJ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/dataflow.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch } from '../query-support.js';\nimport type { DataflowResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Reference-level dataflow analysis: where does data around this symbol\n * come from and where does it go?\n *\n * This is not value-level dataflow (we can't trace x = foo(); bar(x);\n * as a chain). Instead it shows:\n * - Where the symbol is defined and used\n * - What other symbols appear in the same enclosing scope (co-occurring data)\n * - What feeds into the function that defines it (producers)\n * - What consumes the function that uses it (consumers)\n *\n * Language-agnostic: works with any SCIP index.\n */\nexport function dataflow(\n db: ScipDatabase,\n symbolPattern: string,\n): DataflowResult | null {\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n // Definition sites (role=1)\n const defSites = db.all<{ file: string; line: number }>(\n `SELECT d.relative_path AS file, c.start_line AS line\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n WHERE m.symbol_id = ? AND m.role = 1\n ORDER BY d.relative_path, c.start_line`,\n match.symbolId,\n );\n\n // Usage sites (role=0) with enclosing symbol\n const usageSites = db.all<{\n file: string;\n line: number;\n enclosing_symbol: string | null;\n }>(\n `SELECT d.relative_path AS file, c.start_line AS line,\n (SELECT enc_gs.symbol\n FROM defn_enclosing_ranges enc_der\n JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id\n WHERE enc_der.document_id = d.id\n AND enc_der.start_line <= c.start_line\n AND enc_der.end_line >= c.end_line\n ORDER BY (enc_der.end_line - enc_der.start_line) ASC\n LIMIT 1\n ) AS enclosing_symbol\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n WHERE m.symbol_id = ? AND m.role != 1\n ${db.pathExclusionsFor('d')}\n ORDER BY d.relative_path, c.start_line`,\n match.symbolId,\n );\n\n // Producers: other symbols referenced within the same function that defines our target\n const producers = db.all<{ symbol: string; file: string }>(\n `SELECT DISTINCT other_gs.symbol, other_d.relative_path AS file\n FROM mentions other_m\n JOIN chunks other_c ON other_m.chunk_id = other_c.id\n JOIN global_symbols other_gs ON other_m.symbol_id = other_gs.id\n JOIN defn_enclosing_ranges other_der ON other_gs.id = other_der.symbol_id\n JOIN documents other_d ON other_der.document_id = other_d.id\n WHERE other_c.document_id = ?\n AND other_c.start_line >= ? AND other_c.end_line <= ?\n AND other_m.role != 1\n AND other_gs.id != ?\n ${db.symbolNoiseFor('other_gs')}\n ${db.pathExclusionsFor('other_d')}\n ORDER BY other_d.relative_path\n LIMIT 30`,\n match.documentId, match.startLine, match.endLine, match.symbolId,\n );\n\n // Consumers: symbols exported/defined by functions that reference our target\n // (what does the data flow into after being used)\n const consumers = db.all<{ symbol: string; file: string }>(\n `SELECT DISTINCT consumer_gs.symbol, consumer_d.relative_path AS file\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN documents ref_d ON ref_c.document_id = ref_d.id\n -- Find the enclosing function at each usage site\n JOIN defn_enclosing_ranges enc_der\n ON enc_der.document_id = ref_d.id\n AND enc_der.start_line <= ref_c.start_line\n AND enc_der.end_line >= ref_c.end_line\n JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id\n -- Find other symbols defined by that enclosing function's file\n JOIN mentions consumer_m ON consumer_m.symbol_id = enc_gs.id AND consumer_m.role != 1\n JOIN chunks consumer_c ON consumer_m.chunk_id = consumer_c.id\n JOIN documents consumer_d ON consumer_c.document_id = consumer_d.id\n JOIN global_symbols consumer_gs ON consumer_m.symbol_id = consumer_gs.id\n WHERE ref_m.symbol_id = ? AND ref_m.role != 1\n AND consumer_d.id != ref_d.id\n ${db.symbolNoiseFor('consumer_gs')}\n ${db.pathExclusionsFor('consumer_d')}\n ORDER BY consumer_d.relative_path\n LIMIT 30`,\n match.symbolId,\n );\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n relativePath: match.relativePath,\n definitionSites: defSites.filter((s) => !db.isIgnored(s.file)),\n usageSites: usageSites\n .filter((s) => !db.isIgnored(s.file))\n .map((s) => ({\n file: s.file,\n line: s.line,\n enclosingSymbol: s.enclosing_symbol ?? '(top-level)',\n enclosingShort: s.enclosing_symbol ? shortenSymbol(s.enclosing_symbol) : '(top-level)',\n })),\n producers: producers\n .filter((p) => !db.isIgnored(p.file))\n .map((p) => ({ symbol: p.symbol, shortName: shortenSymbol(p.symbol), file: p.file })),\n consumers: consumers\n .filter((c) => !db.isIgnored(c.file))\n .map((c) => ({ symbol: c.symbol, shortName: shortenSymbol(c.symbol), file: c.file })),\n };\n}\n"],"mappings":";;;;;;;;AAkBO,SAAS,SACd,IACA,eACuB;AACvB,QAAM,QAAQ,qBAAqB,IAAI,aAAa;AACpD,MAAI,CAAC,MAAO,QAAO;AAGnB,QAAM,WAAW,GAAG;AAAA,IAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,MAAM;AAAA,EACR;AAGA,QAAM,aAAa,GAAG;AAAA,IAKpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcI,GAAG,kBAAkB,GAAG,CAAC;AAAA;AAAA,IAE7B,MAAM;AAAA,EACR;AAGA,QAAM,YAAY,GAAG;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUI,GAAG,eAAe,UAAU,CAAC;AAAA,QAC7B,GAAG,kBAAkB,SAAS,CAAC;AAAA;AAAA;AAAA,IAGnC,MAAM;AAAA,IAAY,MAAM;AAAA,IAAW,MAAM;AAAA,IAAS,MAAM;AAAA,EAC1D;AAIA,QAAM,YAAY,GAAG;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAiBI,GAAG,eAAe,aAAa,CAAC;AAAA,QAChC,GAAG,kBAAkB,YAAY,CAAC;AAAA;AAAA;AAAA,IAGtC,MAAM;AAAA,EACR;AAEA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,cAAc,MAAM;AAAA,IACpB,iBAAiB,SAAS,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC;AAAA,IAC7D,YAAY,WACT,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO;AAAA,MACX,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,MACR,iBAAiB,EAAE,oBAAoB;AAAA,MACvC,gBAAgB,EAAE,mBAAmB,cAAc,EAAE,gBAAgB,IAAI;AAAA,IAC3E,EAAE;AAAA,IACJ,WAAW,UACR,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,WAAW,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE;AAAA,IACtF,WAAW,UACR,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,WAAW,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE;AAAA,EACxF;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/files.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { FileResult } from '../types.js';\n\nexport function files(db: ScipDatabase, pattern: string): FileResult[] {\n const rows = db.all<{ relative_path: string }>(\n `SELECT relative_path FROM documents\n WHERE relative_path LIKE ?\n ORDER BY relative_path`,\n `%${pattern}%`,\n );\n\n // Apply gitignore filtering\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({ relativePath: r.relative_path }));\n}\n"],"mappings":";AAGO,SAAS,MAAM,IAAkB,SAA+B;AACrE,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA,IAGA,IAAI,OAAO;AAAA,EACb;AAGA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE;AACnD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/deps.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { DepResult } from '../types.js';\n\n/** What internal files does this file depend on? (forward dependencies) */\nexport function deps(db: ScipDatabase, filePattern: string): DepResult[] {\n const rows = db.all<{ relative_path: string }>(\n `SELECT DISTINCT d2.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d1.relative_path LIKE ?\n AND d2.relative_path <> d1.relative_path\n AND ${db.localSymbolPredicate}\n ORDER BY d2.relative_path`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({ relativePath: r.relative_path }));\n}\n\n/** What files depend on this file/module? (reverse dependencies) */\nexport function rdeps(db: ScipDatabase, filePattern: string): DepResult[] {\n const rows = db.all<{ relative_path: string }>(\n `SELECT DISTINCT d1.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d2.relative_path LIKE ?\n AND d1.relative_path NOT LIKE ?\n ORDER BY d1.relative_path`,\n `%${filePattern}%`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({ relativePath: r.relative_path }));\n}\n"],"mappings":";AAIO,SAAS,KAAK,IAAkB,aAAkC;AACvE,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YASQ,GAAG,oBAAoB;AAAA;AAAA,IAE/B,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE;AACnD;AAGO,SAAS,MAAM,IAAkB,aAAkC;AACxE,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,IAAI,WAAW;AAAA,IACf,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE;AACnD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/slice.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol, type SymbolMatch } from '../query-support.js';\nimport type { SliceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Reference-level program slicing: track what affects a symbol (backward)\n * or what a symbol affects (forward).\n *\n * Backward slice: \"What feeds into this?\" — symbols referenced in the same\n * function that defines the target. These are the inputs/dependencies.\n *\n * Forward slice: \"What does this feed into?\" — at each site where the target\n * is referenced, find the enclosing function, then find what that function\n * exports/defines. These are the outputs/consumers.\n *\n * Language-agnostic: works with any SCIP index.\n */\nexport function slice(\n db: ScipDatabase,\n symbolPattern: string,\n opts: { direction?: 'backward' | 'forward' } = {},\n): SliceResult | null {\n const { direction = 'backward' } = opts;\n\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n if (direction === 'backward') {\n return backwardSlice(db, match);\n } else {\n return forwardSlice(db, match);\n }\n}\n\n\nfunction backwardSlice(db: ScipDatabase, match: SymbolMatch): SliceResult {\n // Find all symbols referenced within the definition range of the target.\n // These are what \"feeds into\" the target — the inputs.\n const callees = getCalleeRowsForSymbol(db, match);\n\n // Also find symbols whose definitions are in the same file and whose\n // ranges overlap or precede the target — local variables, parameters, etc.\n const localPredecessors = db.all<{ symbol: string; file: string }>(\n `SELECT DISTINCT gs.symbol, d.relative_path AS file\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n JOIN documents d ON der.document_id = d.id\n WHERE der.document_id = ?\n AND der.end_line < ?\n AND gs.id != ?\n ${db.symbolNoiseFor('gs')}\n ORDER BY der.start_line DESC\n LIMIT 15`,\n match.documentId, match.startLine, match.symbolId,\n );\n\n const seen = new Set<string>();\n const connected: SliceResult['connectedSymbols'] = [];\n\n for (const c of callees) {\n if (seen.has(c.symbol)) continue;\n seen.add(c.symbol);\n connected.push({\n symbol: c.symbol,\n shortName: shortenSymbol(c.symbol),\n file: c.file,\n relationship: 'referenced within definition (callee)',\n });\n }\n\n for (const p of localPredecessors) {\n if (seen.has(p.symbol) || db.isIgnored(p.file)) continue;\n seen.add(p.symbol);\n connected.push({\n symbol: p.symbol,\n shortName: shortenSymbol(p.symbol),\n file: p.file,\n relationship: 'defined before target in same file (local predecessor)',\n });\n }\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n direction: 'backward',\n connectedSymbols: connected,\n };\n}\n\nfunction forwardSlice(db: ScipDatabase, match: SymbolMatch): SliceResult {\n // Find where the target is referenced, then at each reference site,\n // find what else the enclosing function defines/exports.\n const rows = db.all<{\n enclosing_symbol: string;\n enclosing_file: string;\n output_symbol: string;\n output_file: string;\n }>(\n `SELECT DISTINCT\n enc_gs.symbol AS enclosing_symbol,\n enc_d.relative_path AS enclosing_file,\n out_gs.symbol AS output_symbol,\n out_d.relative_path AS output_file\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN documents ref_d ON ref_c.document_id = ref_d.id\n -- Find enclosing function at each reference site\n JOIN defn_enclosing_ranges enc_der\n ON enc_der.document_id = ref_d.id\n AND enc_der.start_line <= ref_c.start_line\n AND enc_der.end_line >= ref_c.end_line\n JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id\n JOIN documents enc_d ON enc_der.document_id = enc_d.id\n -- Find other symbols referenced within that enclosing function\n JOIN mentions out_m ON out_m.role != 1\n JOIN chunks out_c ON out_m.chunk_id = out_c.id\n AND out_c.document_id = enc_der.document_id\n AND out_c.start_line >= enc_der.start_line\n AND out_c.end_line <= enc_der.end_line\n JOIN global_symbols out_gs ON out_m.symbol_id = out_gs.id\n JOIN defn_enclosing_ranges out_der ON out_gs.id = out_der.symbol_id\n JOIN documents out_d ON out_der.document_id = out_d.id\n WHERE ref_m.symbol_id = ? AND ref_m.role != 1\n AND out_gs.id != ? AND out_gs.id != enc_gs.id\n AND out_d.id != ref_d.id\n ${db.symbolNoiseFor('out_gs')}\n ${db.pathExclusionsFor('out_d')}\n ORDER BY out_d.relative_path\n LIMIT 30`,\n match.symbolId, match.symbolId,\n );\n\n const seen = new Set<string>();\n const connected: SliceResult['connectedSymbols'] = [];\n\n for (const r of rows) {\n if (seen.has(r.output_symbol) || db.isIgnored(r.output_file)) continue;\n seen.add(r.output_symbol);\n connected.push({\n symbol: r.output_symbol,\n shortName: shortenSymbol(r.output_symbol),\n file: r.output_file,\n relationship: `used alongside target in ${shortenSymbol(r.enclosing_symbol)}`,\n });\n }\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n direction: 'forward',\n connectedSymbols: connected,\n };\n}\n"],"mappings":";;;;;;;;;AAkBO,SAAS,MACd,IACA,eACA,OAA+C,CAAC,GAC5B;AACpB,QAAM,EAAE,YAAY,WAAW,IAAI;AAEnC,QAAM,QAAQ,qBAAqB,IAAI,aAAa;AACpD,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI,cAAc,YAAY;AAC5B,WAAO,cAAc,IAAI,KAAK;AAAA,EAChC,OAAO;AACL,WAAO,aAAa,IAAI,KAAK;AAAA,EAC/B;AACF;AAGA,SAAS,cAAc,IAAkB,OAAiC;AAGxE,QAAM,UAAU,uBAAuB,IAAI,KAAK;AAIhD,QAAM,oBAAoB,GAAG;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOI,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,IAG3B,MAAM;AAAA,IAAY,MAAM;AAAA,IAAW,MAAM;AAAA,EAC3C;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,YAA6C,CAAC;AAEpD,aAAW,KAAK,SAAS;AACvB,QAAI,KAAK,IAAI,EAAE,MAAM,EAAG;AACxB,SAAK,IAAI,EAAE,MAAM;AACjB,cAAU,KAAK;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,MAAM,EAAE;AAAA,MACR,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAEA,aAAW,KAAK,mBAAmB;AACjC,QAAI,KAAK,IAAI,EAAE,MAAM,KAAK,GAAG,UAAU,EAAE,IAAI,EAAG;AAChD,SAAK,IAAI,EAAE,MAAM;AACjB,cAAU,KAAK;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,MAAM,EAAE;AAAA,MACR,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,WAAW;AAAA,IACX,kBAAkB;AAAA,EACpB;AACF;AAEA,SAAS,aAAa,IAAkB,OAAiC;AAGvE,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QA2BI,GAAG,eAAe,QAAQ,CAAC;AAAA,QAC3B,GAAG,kBAAkB,OAAO,CAAC;AAAA;AAAA;AAAA,IAGjC,MAAM;AAAA,IAAU,MAAM;AAAA,EACxB;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,YAA6C,CAAC;AAEpD,aAAW,KAAK,MAAM;AACpB,QAAI,KAAK,IAAI,EAAE,aAAa,KAAK,GAAG,UAAU,EAAE,WAAW,EAAG;AAC9D,SAAK,IAAI,EAAE,aAAa;AACxB,cAAU,KAAK;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,aAAa;AAAA,MACxC,MAAM,EAAE;AAAA,MACR,cAAc,4BAA4B,cAAc,EAAE,gBAAgB,CAAC;AAAA,IAC7E,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,WAAW;AAAA,IACX,kBAAkB;AAAA,EACpB;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/methods.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { MethodResult } from '../types.js';\nimport { leafName } from '../symbol-parser.js';\n\nexport function methods(db: ScipDatabase, className: string): MethodResult[] {\n const rows = db.all<{\n start_line: number;\n end_line: number;\n symbol: string;\n }>(\n `SELECT der.start_line, der.end_line, gs.symbol\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n WHERE gs.symbol LIKE ?\n AND ${db.localSymbolPredicate}\n AND gs.symbol LIKE '%().%'\n ${db.symbolNoise}\n ORDER BY der.start_line`,\n `%${className}#%`,\n );\n\n return rows.map((r) => ({\n startLine: r.start_line,\n endLine: r.end_line,\n name: leafName(r.symbol),\n }));\n}\n"],"mappings":";;;;;AAIO,SAAS,QAAQ,IAAkB,WAAmC;AAC3E,QAAM,OAAO,GAAG;AAAA,IAKd;AAAA;AAAA;AAAA;AAAA,YAIQ,GAAG,oBAAoB;AAAA;AAAA,QAE3B,GAAG,WAAW;AAAA;AAAA,IAElB,IAAI,SAAS;AAAA,EACf;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,MAAM,SAAS,EAAE,MAAM;AAAA,EACzB,EAAE;AACJ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/change-surface.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { TEST_FILE_PATTERNS, testFileMatchSql } from '../query-support.js';\nimport type { ChangeSurfaceEntry, ChangeSurfaceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Pre-change briefing for a file. For each symbol defined in the file,\n * reports external consumer count, test coverage, and risk level.\n */\nexport function changeSurface(\n db: ScipDatabase,\n filePattern: string,\n): ChangeSurfaceResult | null {\n // Find the file\n const doc = db.get<{ id: number; relative_path: string }>(\n `SELECT id, relative_path FROM documents\n WHERE relative_path LIKE ?\n ${db.pathExclusionsFor('documents')}\n LIMIT 1`,\n `%${filePattern}%`,\n );\n\n if (!doc || db.isIgnored(doc.relative_path)) return null;\n\n // Get all symbols defined in this file, excluding typeLiterals\n const syms = db.all<{\n symbol_id: number;\n symbol: string;\n start_line: number;\n end_line: number;\n }>(\n `SELECT DISTINCT gs.id AS symbol_id, gs.symbol, der.start_line, der.end_line\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n WHERE der.document_id = ?\n ${db.symbolNoiseFor('gs')}\n ORDER BY der.start_line`,\n doc.id,\n );\n\n const testPatternSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);\n\n const symbols: ChangeSurfaceEntry[] = [];\n let totalExternalConsumers = 0;\n let coveredCount = 0;\n\n for (const sym of syms) {\n // Count external consumers: mentions with role=0 from different documents\n const consumerRow = db.get<{ consumer_count: number }>(\n `SELECT COUNT(DISTINCT c.document_id) AS consumer_count\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND c.document_id != ?`,\n sym.symbol_id,\n doc.id,\n );\n\n const externalConsumers = consumerRow?.consumer_count ?? 0;\n\n // Find test files that reference this symbol\n const testFiles = db.all<{ relative_path: string }>(\n `SELECT DISTINCT ref_d.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND (${testPatternSql})\n ORDER BY ref_d.relative_path`,\n sym.symbol_id,\n ).map((r) => r.relative_path);\n\n const hasTests = testFiles.length > 0;\n if (hasTests) coveredCount++;\n\n // Risk level determination\n let riskLevel: 'low' | 'medium' | 'high';\n if (externalConsumers > 10 && !hasTests) {\n riskLevel = 'high';\n } else if (externalConsumers > 5 || !hasTests) {\n riskLevel = 'medium';\n } else {\n riskLevel = 'low';\n }\n\n totalExternalConsumers += externalConsumers;\n\n symbols.push({\n symbol: sym.symbol,\n shortName: shortenSymbol(sym.symbol),\n startLine: sym.start_line,\n endLine: sym.end_line,\n externalConsumers,\n testFiles,\n riskLevel,\n });\n }\n\n const testCoveragePercent =\n symbols.length > 0 ? Math.round((coveredCount / symbols.length) * 100) : 0;\n\n return {\n file: doc.relative_path,\n symbols,\n totalExternalConsumers,\n testCoveragePercent,\n };\n}\n"],"mappings":";;;;;;;;;AASO,SAAS,cACd,IACA,aAC4B;AAE5B,QAAM,MAAM,GAAG;AAAA,IACb;AAAA;AAAA,SAEK,GAAG,kBAAkB,WAAW,CAAC;AAAA;AAAA,IAEtC,IAAI,WAAW;AAAA,EACjB;AAEA,MAAI,CAAC,OAAO,GAAG,UAAU,IAAI,aAAa,EAAG,QAAO;AAGpD,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA,QAII,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA,IAE3B,IAAI;AAAA,EACN;AAEA,QAAM,iBAAiB,iBAAiB,SAAS,kBAAkB;AAEnE,QAAM,UAAgC,CAAC;AACvC,MAAI,yBAAyB;AAC7B,MAAI,eAAe;AAEnB,aAAW,OAAO,MAAM;AAEtB,UAAM,cAAc,GAAG;AAAA,MACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,IAAI;AAAA,MACJ,IAAI;AAAA,IACN;AAEA,UAAM,oBAAoB,aAAa,kBAAkB;AAGzD,UAAM,YAAY,GAAG;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAMS,cAAc;AAAA;AAAA,MAEvB,IAAI;AAAA,IACN,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa;AAE5B,UAAM,WAAW,UAAU,SAAS;AACpC,QAAI,SAAU;AAGd,QAAI;AACJ,QAAI,oBAAoB,MAAM,CAAC,UAAU;AACvC,kBAAY;AAAA,IACd,WAAW,oBAAoB,KAAK,CAAC,UAAU;AAC7C,kBAAY;AAAA,IACd,OAAO;AACL,kBAAY;AAAA,IACd;AAEA,8BAA0B;AAE1B,YAAQ,KAAK;AAAA,MACX,QAAQ,IAAI;AAAA,MACZ,WAAW,cAAc,IAAI,MAAM;AAAA,MACnC,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,sBACJ,QAAQ,SAAS,IAAI,KAAK,MAAO,eAAe,QAAQ,SAAU,GAAG,IAAI;AAE3E,SAAO;AAAA,IACL,MAAM,IAAI;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/isolated.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { testFileExclusionSql } from '../query-support.js';\nimport type { IsolatedResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find isolated symbols: defined locally, referenced by nothing,\n * and referencing nothing external. These are truly orphaned code —\n * not just unused exports, but completely disconnected from the graph.\n */\nexport function isolated(\n db: ScipDatabase,\n opts: { scope?: string; minLoc?: number } = {},\n): IsolatedResult[] {\n const { scope, minLoc = 3 } = opts;\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{\n symbol: string;\n relative_path: string;\n start_line: number;\n end_line: number;\n loc: number;\n }>(\n `SELECT\n gs.symbol,\n d.relative_path,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n AND gs.symbol NOT LIKE '%#%'\n AND (der.end_line - der.start_line + 1) >= ?\n ${scopeFilter}\n -- No cross-file references TO this symbol\n AND NOT EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id != d.id\n )\n -- No same-file references either\n AND NOT EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id = d.id\n )\n ORDER BY loc DESC, d.relative_path`,\n minLoc,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n relativePath: r.relative_path,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n }));\n}\n"],"mappings":";;;;;;;;AAUO,SAAS,SACd,IACA,OAA4C,CAAC,GAC3B;AAClB,QAAM,EAAE,OAAO,SAAS,EAAE,IAAI;AAC9B,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAEtE,QAAM,OAAO,GAAG;AAAA,IAOd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUI,GAAG,kBAAkB,GAAG,CAAC;AAAA,YACrB,qBAAqB,GAAG,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,QAGvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAcf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,cAAc,EAAE;AAAA,IAChB,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,KAAK,EAAE;AAAA,EACT,EAAE;AACN;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/symbol-parser.ts"],"sourcesContent":["import type { ScipSymbol, ScipDescriptor, ScipLocalSymbol, DescriptorSuffix } from './types.js';\n\n/**\n * SCIP Symbol Grammar (from the SCIP spec):\n *\n * <symbol> ::= <scheme> ' ' <package> ' ' <descriptor>+ | 'local ' <local-id>\n * <package> ::= <manager> ' ' <package-name> ' ' <version> ' '\n * <descriptor> ::= <name> <suffix>\n *\n * Suffix characters:\n * / namespace\n * # type (class, interface, enum)\n * . term (variable, field, property)\n * (). method\n * [ type parameter\n * () parameter\n * : meta\n * ! macro\n *\n * Names may be backtick-escaped: `some.weird/name`\n */\n\nconst SUFFIX_MAP: Record<string, DescriptorSuffix> = {\n '/': 'namespace',\n '#': 'type',\n '.': 'term',\n '[': 'type-param',\n ':': 'meta',\n '!': 'macro',\n};\n\n/**\n * Parse a SCIP symbol string into its structured components.\n * Works for any SCIP-indexed language (TypeScript, Java, Rust, Python, etc.)\n */\nexport function parseSymbol(raw: string): ScipSymbol | ScipLocalSymbol {\n if (raw.startsWith('local ')) {\n return { kind: 'local', id: raw.slice(6), raw };\n }\n\n // Split: <scheme> <manager> <package-name> <version> <descriptors...>\n // The tricky part: package-name can contain spaces if backtick-escaped\n const parts = raw.split(' ');\n if (parts.length < 4) {\n // Malformed — return a best-effort parse\n return {\n scheme: parts[0] ?? '',\n manager: parts[1] ?? '',\n packageName: parts[2] ?? '',\n version: '',\n descriptors: [],\n raw,\n };\n }\n\n const scheme = parts[0]!;\n const manager = parts[1]!;\n\n // Package name and version: package name might be backtick-escaped\n // After scheme + manager, we need to find package + version + descriptor string\n let restAfterManager = raw.slice(scheme.length + 1 + manager.length + 1);\n\n // Parse package name (may be backtick-escaped)\n let packageName: string;\n if (restAfterManager.startsWith('`')) {\n const closingTick = restAfterManager.indexOf('`', 1);\n if (closingTick === -1) {\n packageName = restAfterManager.slice(1);\n restAfterManager = '';\n } else {\n packageName = restAfterManager.slice(1, closingTick);\n restAfterManager = restAfterManager.slice(closingTick + 2); // skip ` and space\n }\n } else {\n const spaceIdx = restAfterManager.indexOf(' ');\n if (spaceIdx === -1) {\n packageName = restAfterManager;\n restAfterManager = '';\n } else {\n packageName = restAfterManager.slice(0, spaceIdx);\n restAfterManager = restAfterManager.slice(spaceIdx + 1);\n }\n }\n\n // Parse version\n let version: string;\n const versionSpaceIdx = restAfterManager.indexOf(' ');\n if (versionSpaceIdx === -1) {\n version = restAfterManager;\n restAfterManager = '';\n } else {\n version = restAfterManager.slice(0, versionSpaceIdx);\n restAfterManager = restAfterManager.slice(versionSpaceIdx + 1);\n }\n\n // Parse descriptors from the remaining string\n const descriptors = parseDescriptors(restAfterManager);\n\n return { scheme, manager, packageName, version, descriptors, raw };\n}\n\n/**\n * Parse the descriptor chain from a SCIP symbol.\n *\n * SCIP descriptor grammar:\n * namespace: name/\n * type: name#\n * term: name.\n * method: name(disambiguator).\n * type-param: [name] (bracket-wrapped, prefix syntax)\n * parameter: (name) (paren-wrapped, prefix syntax)\n * meta: name:\n * macro: name!\n *\n * Names can be backtick-escaped: `some/name.with.dots`\n */\nfunction parseDescriptors(input: string): ScipDescriptor[] {\n const descriptors: ScipDescriptor[] = [];\n let i = 0;\n\n while (i < input.length) {\n // Type parameter: [name]\n if (input[i] === '[') {\n const closeBracket = input.indexOf(']', i + 1);\n if (closeBracket === -1) {\n descriptors.push({ name: input.slice(i + 1), suffix: 'type-param' });\n break;\n }\n descriptors.push({ name: input.slice(i + 1, closeBracket), suffix: 'type-param' });\n i = closeBracket + 1;\n continue;\n }\n\n // Parameter: (name) — only when ( appears at descriptor start with no preceding name\n if (input[i] === '(' && (descriptors.length === 0 || i === 0 || isSuffixChar(input[i - 1]!))) {\n const closeParen = input.indexOf(')', i + 1);\n if (closeParen !== -1 && input[closeParen + 1] !== '.') {\n // This is a parameter (name), not a method disambiguator\n descriptors.push({ name: input.slice(i + 1, closeParen), suffix: 'parameter' });\n i = closeParen + 1;\n continue;\n }\n }\n\n let name: string;\n\n // Backtick-escaped name\n if (input[i] === '`') {\n const closingTick = input.indexOf('`', i + 1);\n if (closingTick === -1) {\n name = input.slice(i + 1);\n i = input.length;\n descriptors.push({ name, suffix: 'term' });\n break;\n }\n name = input.slice(i + 1, closingTick);\n i = closingTick + 1;\n } else {\n // Read name until we hit a suffix character\n const start = i;\n while (i < input.length && !isSuffixChar(input[i]!)) {\n i++;\n }\n name = input.slice(start, i);\n }\n\n // Parse suffix after name\n if (i >= input.length) {\n if (name) descriptors.push({ name, suffix: 'term' });\n break;\n }\n\n const char = input[i]!;\n\n // Method: name(disambiguator).\n if (char === '(') {\n const closeParen = input.indexOf(')', i + 1);\n if (closeParen !== -1 && input[closeParen + 1] === '.') {\n descriptors.push({ name, suffix: 'method' });\n i = closeParen + 2; // skip past ).\n } else if (closeParen !== -1) {\n // Bare (disambiguator) without . — treat as method anyway (common in practice)\n descriptors.push({ name, suffix: 'method' });\n i = closeParen + 1;\n } else {\n descriptors.push({ name, suffix: 'term' });\n i++;\n }\n } else {\n const suffix = SUFFIX_MAP[char];\n if (suffix) {\n descriptors.push({ name, suffix });\n i += 1;\n } else {\n i += 1; // Unknown suffix — skip\n }\n }\n }\n\n return descriptors;\n}\n\nfunction isSuffixChar(c: string): boolean {\n return c === '/' || c === '#' || c === '.' || c === '(' || c === '[' || c === ':' || c === '!';\n}\n\n/**\n * Convert a parsed SCIP symbol to a short, human-readable name.\n * Language-agnostic: works for any SCIP-indexed language.\n *\n * Examples:\n * \"scip-typescript npm @vega/api 0.1.3 src/modules/auth/auth.service.ts/AuthService#login().\"\n * → \"auth.service:AuthService:login()\"\n *\n * \"scip-java maven com.example/mylib 1.0.0 com/example/MyClass#doStuff().\"\n * → \"MyClass:doStuff()\"\n *\n * \"rust-analyzer cargo my-crate 0.1.0 src/lib.rs/MyStruct#new().\"\n * → \"lib:MyStruct:new()\"\n */\nexport function shortenSymbol(raw: string): string {\n const parsed = parseSymbol(raw);\n if ('kind' in parsed && parsed.kind === 'local') {\n return `local:${parsed.id}`;\n }\n\n const sym = parsed as ScipSymbol;\n if (sym.descriptors.length === 0) return sym.raw;\n\n const parts: string[] = [];\n for (const desc of sym.descriptors) {\n // Strip file extensions from namespace descriptors (the file path parts)\n let name = desc.name;\n if (desc.suffix === 'namespace') {\n // Remove common file extensions\n name = name\n .replace(/\\.(ts|tsx|js|jsx|mjs|cjs)$/, '')\n .replace(/\\.(py|pyi)$/, '')\n .replace(/\\.(rs)$/, '')\n .replace(/\\.(java|scala|kt|kts)$/, '')\n .replace(/\\.(rb)$/, '')\n .replace(/\\.(go)$/, '')\n .replace(/\\.(cs|vb)$/, '')\n .replace(/\\.(dart)$/, '')\n .replace(/\\.(php)$/, '')\n .replace(/\\.(c|cc|cpp|cxx|h|hpp)$/, '');\n }\n\n // Skip empty names (can happen with trailing suffixes)\n if (!name) continue;\n\n // For methods, append () for clarity\n if (desc.suffix === 'method') {\n parts.push(`${name}()`);\n } else {\n parts.push(name);\n }\n }\n\n return parts.join(':');\n}\n\n/**\n * Extract just the leaf name from a SCIP symbol.\n * Useful when you only need the function/class/variable name without path context.\n */\nexport function leafName(raw: string): string {\n const parsed = parseSymbol(raw);\n if ('kind' in parsed && parsed.kind === 'local') {\n return parsed.id;\n }\n\n const sym = parsed as ScipSymbol;\n if (sym.descriptors.length === 0) return '';\n\n const last = sym.descriptors[sym.descriptors.length - 1]!;\n return last.name;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/similar-signatures.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { SimilarSignatureGroup } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find functions with near-identical type signatures (same parameter types\n * and return type) but different names. These are \"same shape\" functions\n * that may be doing similar work even if their internal implementation differs.\n *\n * The SCIP `documentation` field often contains the full type signature\n * after a `|` delimiter. We parse it, normalize it (strip the function name,\n * whitespace, and case), then group by normalized signature.\n *\n * Groups with 2+ functions = same-shape candidates.\n */\nexport function similarSignatures(\n db: ScipDatabase,\n opts: { scope?: string; minLoc?: number; limit?: number } = {},\n): SimilarSignatureGroup[] {\n const { scope, minLoc = 1, limit } = opts;\n\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Get all function-level symbols with their documentation/signature strings.\n // We use the same signature extraction pattern as symbols.ts / trace.ts.\n // Filter to symbols that have a documentation field containing '|' (the sig delimiter)\n // and whose signature contains '(' (indicating a callable).\n const rows = db.all<{\n symbol: string;\n relative_path: string;\n start_line: number;\n end_line: number;\n loc: number;\n sig: string;\n }>(\n `SELECT\n gs.symbol,\n d.relative_path,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.documentation IS NOT NULL\n AND gs.documentation != ''\n AND INSTR(gs.documentation, '|') > 0\n AND (der.end_line - der.start_line + 1) >= ?\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n ORDER BY d.relative_path, der.start_line`,\n minLoc,\n );\n\n // Group by normalized signature\n const sigGroups = new Map<string, Array<{\n symbol: string;\n shortName: string;\n file: string;\n startLine: number;\n endLine: number;\n loc: number;\n }>>();\n\n for (const row of rows) {\n if (db.isIgnored(row.relative_path)) continue;\n\n const normalized = normalizeSignature(row.sig);\n if (!normalized) continue;\n\n const entry = {\n symbol: row.symbol,\n shortName: shortenSymbol(row.symbol),\n file: row.relative_path,\n startLine: row.start_line,\n endLine: row.end_line,\n loc: row.loc,\n };\n\n const existing = sigGroups.get(normalized);\n if (existing) {\n existing.push(entry);\n } else {\n sigGroups.set(normalized, [entry]);\n }\n }\n\n // Collect groups with 2+ functions\n const results: SimilarSignatureGroup[] = [];\n\n for (const [signature, functions] of sigGroups) {\n if (functions.length < 2) continue;\n\n results.push({ signature, functions });\n }\n\n // Sort by group size descending (largest groups = most duplication),\n // then by total LOC in the group\n results.sort((a, b) => {\n const sizeDiff = b.functions.length - a.functions.length;\n if (sizeDiff !== 0) return sizeDiff;\n const locA = a.functions.reduce((sum, f) => sum + f.loc, 0);\n const locB = b.functions.reduce((sum, f) => sum + f.loc, 0);\n return locB - locA;\n });\n\n return limit ? results.slice(0, limit) : results;\n}\n\n/**\n * Normalize a signature for comparison:\n * 1. Clean markdown fences and SCIP prefixes\n * 2. Strip everything before the first '(' (removes the function name)\n * 3. Strip whitespace and lowercase\n *\n * Returns null if the signature doesn't contain a callable form.\n */\nfunction normalizeSignature(raw: string): string | null {\n if (!raw || !raw.trim()) return null;\n\n // Clean markdown and SCIP decoration (same as cleanSignature)\n let sig = raw\n .replace(/^```\\w*\\s*/, '')\n .replace(/\\s*```$/, '')\n .replace(/^\\(method\\)\\s*/, '')\n .replace(/^\\(property\\)\\s*/, '')\n .replace(/^\\(function\\)\\s*/, '')\n .replace(/^\\(class\\)\\s*/, '')\n .replace(/^\\(interface\\)\\s*/, '')\n .replace(/^\\(enum\\)\\s*/, '')\n .replace(/^\\(type alias\\)\\s*/, '')\n .replace(/^\\(const\\)\\s*/, '')\n .replace(/^\\(var\\)\\s*/, '')\n .trim();\n\n // Find the first '(' — everything from there is the parameter/return signature\n const parenIdx = sig.indexOf('(');\n if (parenIdx === -1) return null;\n\n sig = sig.slice(parenIdx);\n\n // Normalize: strip all whitespace, lowercase\n sig = sig.replace(/\\s+/g, '').toLowerCase();\n\n // Must have meaningful content after normalization\n if (sig.length < 3) return null; // e.g. \"()\" alone is too generic\n\n return sig;\n}\n"],"mappings":";;;;;AAeO,SAAS,kBACd,IACA,OAA4D,CAAC,GACpC;AACzB,QAAM,EAAE,OAAO,SAAS,GAAG,MAAM,IAAI;AAErC,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAMtE,QAAM,OAAO,GAAG;AAAA,IAQd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA,QACvB,WAAW;AAAA;AAAA,IAEf;AAAA,EACF;AAGA,QAAM,YAAY,oBAAI,IAOlB;AAEJ,aAAW,OAAO,MAAM;AACtB,QAAI,GAAG,UAAU,IAAI,aAAa,EAAG;AAErC,UAAM,aAAa,mBAAmB,IAAI,GAAG;AAC7C,QAAI,CAAC,WAAY;AAEjB,UAAM,QAAQ;AAAA,MACZ,QAAQ,IAAI;AAAA,MACZ,WAAW,cAAc,IAAI,MAAM;AAAA,MACnC,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,KAAK,IAAI;AAAA,IACX;AAEA,UAAM,WAAW,UAAU,IAAI,UAAU;AACzC,QAAI,UAAU;AACZ,eAAS,KAAK,KAAK;AAAA,IACrB,OAAO;AACL,gBAAU,IAAI,YAAY,CAAC,KAAK,CAAC;AAAA,IACnC;AAAA,EACF;AAGA,QAAM,UAAmC,CAAC;AAE1C,aAAW,CAAC,WAAW,SAAS,KAAK,WAAW;AAC9C,QAAI,UAAU,SAAS,EAAG;AAE1B,YAAQ,KAAK,EAAE,WAAW,UAAU,CAAC;AAAA,EACvC;AAIA,UAAQ,KAAK,CAAC,GAAG,MAAM;AACrB,UAAM,WAAW,EAAE,UAAU,SAAS,EAAE,UAAU;AAClD,QAAI,aAAa,EAAG,QAAO;AAC3B,UAAM,OAAO,EAAE,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,CAAC;AAC1D,UAAM,OAAO,EAAE,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,CAAC;AAC1D,WAAO,OAAO;AAAA,EAChB,CAAC;AAED,SAAO,QAAQ,QAAQ,MAAM,GAAG,KAAK,IAAI;AAC3C;AAUA,SAAS,mBAAmB,KAA4B;AACtD,MAAI,CAAC,OAAO,CAAC,IAAI,KAAK,EAAG,QAAO;AAGhC,MAAI,MAAM,IACP,QAAQ,cAAc,EAAE,EACxB,QAAQ,WAAW,EAAE,EACrB,QAAQ,kBAAkB,EAAE,EAC5B,QAAQ,oBAAoB,EAAE,EAC9B,QAAQ,oBAAoB,EAAE,EAC9B,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,qBAAqB,EAAE,EAC/B,QAAQ,gBAAgB,EAAE,EAC1B,QAAQ,sBAAsB,EAAE,EAChC,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,eAAe,EAAE,EACzB,KAAK;AAGR,QAAM,WAAW,IAAI,QAAQ,GAAG;AAChC,MAAI,aAAa,GAAI,QAAO;AAE5B,QAAM,IAAI,MAAM,QAAQ;AAGxB,QAAM,IAAI,QAAQ,QAAQ,EAAE,EAAE,YAAY;AAG1C,MAAI,IAAI,SAAS,EAAG,QAAO;AAE3B,SAAO;AACT;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/similar.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { SimilarSymbolResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find functions with similar callee fingerprints using TF-IDF weighted\n * cosine similarity.\n *\n * Plain Jaccard similarity inflates scores when functions share ubiquitous\n * infrastructure imports (db, types, shortenSymbol). TF-IDF fixes this by\n * weighting each shared callee by how rare it is:\n *\n * - A callee used by 2 functions (rare) gets high weight → strong signal\n * - A callee used by 30 functions (common) gets low weight → noise\n *\n * This means two functions sharing `sendWelcomeEmail()` (rare) score\n * much higher than two functions sharing `db.all()` (ubiquitous), even\n * though both are \"shared callees.\"\n */\nexport function similar(\n db: ScipDatabase,\n symbolPattern: string,\n opts: { minSimilarity?: number; limit?: number } = {},\n): SimilarSymbolResult[] {\n const { minSimilarity = 0.4, limit = 20 } = opts;\n\n const target = findCallees(db, symbolPattern);\n if (!target || target.callees.size === 0) return [];\n\n const candidates = getAllCalleeFingerprints(db, {\n minCallees: 3,\n excludeSymbol: target.symbol,\n });\n\n // Compute IDF weights across all fingerprints + target\n const allFingerprints = [target, ...candidates];\n const idfWeights = computeIdf(allFingerprints);\n\n const results: SimilarSymbolResult[] = [];\n\n for (const candidate of candidates) {\n if (candidate.callees.size < 3) continue;\n\n const { similarity, significantShared, trivialShared } = weightedSimilarity(\n target.callees, candidate.callees, idfWeights,\n );\n\n if (similarity < minSimilarity) continue;\n if (significantShared.length < 1) continue; // no real overlap\n\n results.push({\n symbolA: target.symbol,\n shortNameA: shortenSymbol(target.symbol),\n fileA: target.file,\n symbolB: candidate.symbol,\n shortNameB: shortenSymbol(candidate.symbol),\n fileB: candidate.file,\n similarity,\n sharedCallees: significantShared.map(shortenSymbol),\n uniqueToA: [...difference(target.callees, candidate.callees)].map(shortenSymbol),\n uniqueToB: [...difference(candidate.callees, target.callees)].map(shortenSymbol),\n });\n }\n\n results.sort((a, b) => b.similarity - a.similarity);\n return results.slice(0, limit);\n}\n\n/**\n * Find similar symbols across the entire codebase.\n * Uses TF-IDF weighted similarity to filter out infrastructure noise.\n */\nexport function similarAll(\n db: ScipDatabase,\n opts: { minSimilarity?: number; limit?: number; scope?: string; minCallees?: number } = {},\n): SimilarSymbolResult[] {\n const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4 } = opts;\n\n const all = getAllCalleeFingerprints(db, { minCallees, scope });\n const idfWeights = computeIdf(all);\n\n const results: SimilarSymbolResult[] = [];\n\n for (let i = 0; i < all.length; i++) {\n for (let j = i + 1; j < all.length; j++) {\n const a = all[i]!;\n const b = all[j]!;\n\n if (a.file === b.file) continue;\n\n const { similarity, significantShared } = weightedSimilarity(\n a.callees, b.callees, idfWeights,\n );\n\n if (similarity < minSimilarity) continue;\n if (significantShared.length < 2) continue;\n\n results.push({\n symbolA: a.symbol,\n shortNameA: shortenSymbol(a.symbol),\n fileA: a.file,\n symbolB: b.symbol,\n shortNameB: shortenSymbol(b.symbol),\n fileB: b.file,\n similarity,\n sharedCallees: significantShared.map(shortenSymbol),\n uniqueToA: [...difference(a.callees, b.callees)].map(shortenSymbol),\n uniqueToB: [...difference(b.callees, a.callees)].map(shortenSymbol),\n });\n }\n\n if (results.length > limit * 5) break;\n }\n\n results.sort((a, b) => b.similarity - a.similarity);\n return results.slice(0, limit);\n}\n\n// ── TF-IDF Engine ──────────────────────────────────────────\n\n/**\n * Compute inverse document frequency for each callee.\n * IDF(callee) = log(N / df(callee)) where N is total functions\n * and df is how many functions reference that callee.\n *\n * High IDF = rare callee = strong similarity signal.\n * Low IDF = ubiquitous callee = noise.\n */\nfunction computeIdf(fingerprints: SymbolFingerprint[]): Map<string, number> {\n const n = fingerprints.length;\n if (n === 0) return new Map();\n\n // Count how many functions reference each callee\n const docFreq = new Map<string, number>();\n for (const fp of fingerprints) {\n for (const callee of fp.callees) {\n docFreq.set(callee, (docFreq.get(callee) ?? 0) + 1);\n }\n }\n\n // Compute IDF\n const idf = new Map<string, number>();\n for (const [callee, df] of docFreq) {\n idf.set(callee, Math.log(n / df));\n }\n\n return idf;\n}\n\n/**\n * Compute TF-IDF weighted cosine similarity between two callee sets.\n *\n * Each callee is a dimension. Its weight is its IDF score.\n * Cosine similarity of the weighted vectors gives a similarity\n * that ignores ubiquitous callees and emphasizes rare shared ones.\n *\n * Also returns which shared callees are \"significant\" (above-median IDF)\n * vs \"trivial\" (below-median IDF, i.e., infrastructure).\n */\nfunction weightedSimilarity(\n a: Set<string>,\n b: Set<string>,\n idf: Map<string, number>,\n): { similarity: number; significantShared: string[]; trivialShared: string[] } {\n const shared = intersection(a, b);\n if (shared.size === 0) return { similarity: 0, significantShared: [], trivialShared: [] };\n\n // Compute weighted dot product and magnitudes\n let dotProduct = 0;\n let magA = 0;\n let magB = 0;\n\n const allCallees = new Set([...a, ...b]);\n for (const callee of allCallees) {\n const weight = idf.get(callee) ?? 0;\n const inA = a.has(callee) ? weight : 0;\n const inB = b.has(callee) ? weight : 0;\n dotProduct += inA * inB;\n magA += inA * inA;\n magB += inB * inB;\n }\n\n const magnitude = Math.sqrt(magA) * Math.sqrt(magB);\n const similarity = magnitude > 0 ? dotProduct / magnitude : 0;\n\n // Split shared callees into significant (high IDF) and trivial (low IDF)\n const medianIdf = getMedianIdf(idf);\n const significantShared: string[] = [];\n const trivialShared: string[] = [];\n\n for (const callee of shared) {\n const weight = idf.get(callee) ?? 0;\n if (weight >= medianIdf) {\n significantShared.push(callee);\n } else {\n trivialShared.push(callee);\n }\n }\n\n // Sort significant callees by IDF descending (most distinctive first)\n significantShared.sort((x, y) => (idf.get(y) ?? 0) - (idf.get(x) ?? 0));\n\n return { similarity, significantShared, trivialShared };\n}\n\nfunction getMedianIdf(idf: Map<string, number>): number {\n const values = [...idf.values()].sort((a, b) => a - b);\n if (values.length === 0) return 0;\n const mid = Math.floor(values.length / 2);\n return values.length % 2 === 0\n ? (values[mid - 1]! + values[mid]!) / 2\n : values[mid]!;\n}\n\n// ── Internal helpers ───────────────────────────────────────\n\ninterface SymbolFingerprint {\n symbol: string;\n file: string;\n callees: Set<string>;\n}\n\nfunction findCallees(\n db: ScipDatabase,\n symbolPattern: string,\n): SymbolFingerprint | null {\n const target = findFirstSymbolMatch(db, symbolPattern);\n\n if (!target) return null;\n\n const calleeRows = getCalleeRowsForSymbol(db, target);\n\n return {\n symbol: target.symbol,\n file: target.relativePath,\n callees: new Set(calleeRows.map((r) => r.symbol)),\n };\n}\n\nfunction getAllCalleeFingerprints(\n db: ScipDatabase,\n opts: { minCallees: number; scope?: string; excludeSymbol?: string },\n): SymbolFingerprint[] {\n const { minCallees, scope, excludeSymbol } = opts;\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n const excludeFilter = excludeSymbol ? `AND gs.symbol != '${excludeSymbol.replace(/'/g, \"''\")}'` : '';\n\n const symbols = db.all<{\n id: number;\n symbol: string;\n document_id: number;\n start_line: number;\n end_line: number;\n relative_path: string;\n }>(\n `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n AND (der.end_line - der.start_line + 1) >= 5\n ${scopeFilter}\n ${excludeFilter}\n ORDER BY d.relative_path`,\n );\n\n const fingerprints: SymbolFingerprint[] = [];\n\n for (const sym of symbols) {\n if (db.isIgnored(sym.relative_path)) continue;\n\n const calleeRows = getCalleeRowsForSymbol(db, {\n documentId: sym.document_id,\n startLine: sym.start_line,\n endLine: sym.end_line,\n symbolId: sym.id,\n });\n\n const callees = new Set(calleeRows.map((r) => r.symbol));\n if (callees.size >= minCallees) {\n fingerprints.push({ symbol: sym.symbol, file: sym.relative_path, callees });\n }\n }\n\n return fingerprints;\n}\n\nfunction intersection<T>(a: Set<T>, b: Set<T>): Set<T> {\n const result = new Set<T>();\n for (const item of a) {\n if (b.has(item)) result.add(item);\n }\n return result;\n}\n\nfunction difference<T>(a: Set<T>, b: Set<T>): Set<T> {\n const result = new Set<T>();\n for (const item of a) {\n if (!b.has(item)) result.add(item);\n }\n return result;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/passthrough-candidates.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { getCalleeRowsForSymbol, testFileExclusionSql } from '../query-support.js';\nimport type { PassthroughCandidate } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find passthrough candidates: functions that just forward to one\n * other function.\n *\n * A function with exactly 1 callee and small LOC is likely a thin\n * wrapper that adds no value — it just passes arguments through to\n * the real implementation.\n */\nexport function passthroughCandidates(\n db: ScipDatabase,\n opts?: { scope?: string; maxLoc?: number; limit?: number },\n): PassthroughCandidate[] {\n const { scope, maxLoc = 15, limit = 30 } = opts ?? {};\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // 1. Find all non-trivial symbols with definition ranges (>= 3 LOC)\n const symbols = db.all<{\n id: number;\n symbol: string;\n document_id: number;\n start_line: number;\n end_line: number;\n loc: number;\n relative_path: string;\n }>(\n `SELECT\n gs.id,\n gs.symbol,\n der.document_id,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n AND (der.end_line - der.start_line + 1) >= 3\n AND (der.end_line - der.start_line + 1) <= ?\n ${scopeFilter}\n ORDER BY d.relative_path`,\n maxLoc,\n );\n\n const results: PassthroughCandidate[] = [];\n\n for (const sym of symbols) {\n if (db.isIgnored(sym.relative_path)) continue;\n\n // 2. Count callees for this symbol\n const callees = getCalleeRowsForSymbol(db, {\n documentId: sym.document_id,\n startLine: sym.start_line,\n endLine: sym.end_line,\n symbolId: sym.id,\n });\n\n // Deduplicate by symbol (same callee may appear in multiple chunks)\n const uniqueCallees = new Map<string, { symbol: string; file: string }>();\n for (const c of callees) {\n if (!uniqueCallees.has(c.symbol)) {\n uniqueCallees.set(c.symbol, { symbol: c.symbol, file: c.file });\n }\n }\n\n // 3. Passthrough = exactly 1 unique callee\n if (uniqueCallees.size !== 1) continue;\n\n const [, callee] = [...uniqueCallees.entries()][0]!;\n\n results.push({\n symbol: sym.symbol,\n shortName: shortenSymbol(sym.symbol),\n file: sym.relative_path,\n startLine: sym.start_line,\n endLine: sym.end_line,\n loc: sym.loc,\n forwardsTo: callee.symbol,\n forwardsToShort: shortenSymbol(callee.symbol),\n forwardsToFile: callee.file,\n });\n }\n\n results.sort((a, b) => a.loc - b.loc || a.file.localeCompare(b.file));\n return results.slice(0, limit);\n}\n"],"mappings":";;;;;;;;;AAaO,SAAS,sBACd,IACA,MACwB;AACxB,QAAM,EAAE,OAAO,SAAS,IAAI,QAAQ,GAAG,IAAI,QAAQ,CAAC;AACpD,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAGtE,QAAM,UAAU,GAAG;AAAA,IASjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYI,GAAG,kBAAkB,GAAG,CAAC;AAAA,YACrB,qBAAqB,GAAG,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,QAGvB,WAAW;AAAA;AAAA,IAEf;AAAA,EACF;AAEA,QAAM,UAAkC,CAAC;AAEzC,aAAW,OAAO,SAAS;AACzB,QAAI,GAAG,UAAU,IAAI,aAAa,EAAG;AAGrC,UAAM,UAAU,uBAAuB,IAAI;AAAA,MACzC,YAAY,IAAI;AAAA,MAChB,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,UAAU,IAAI;AAAA,IAChB,CAAC;AAGD,UAAM,gBAAgB,oBAAI,IAA8C;AACxE,eAAW,KAAK,SAAS;AACvB,UAAI,CAAC,cAAc,IAAI,EAAE,MAAM,GAAG;AAChC,sBAAc,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,MAAM,EAAE,KAAK,CAAC;AAAA,MAChE;AAAA,IACF;AAGA,QAAI,cAAc,SAAS,EAAG;AAE9B,UAAM,CAAC,EAAE,MAAM,IAAI,CAAC,GAAG,cAAc,QAAQ,CAAC,EAAE,CAAC;AAEjD,YAAQ,KAAK;AAAA,MACX,QAAQ,IAAI;AAAA,MACZ,WAAW,cAAc,IAAI,MAAM;AAAA,MACnC,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,KAAK,IAAI;AAAA,MACT,YAAY,OAAO;AAAA,MACnB,iBAAiB,cAAc,OAAO,MAAM;AAAA,MAC5C,gBAAgB,OAAO;AAAA,IACzB,CAAC;AAAA,EACH;AAEA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACpE,SAAO,QAAQ,MAAM,GAAG,KAAK;AAC/B;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/redundant-reexports.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { RedundantReexport } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find barrel re-exports that no consumer actually imports through.\n *\n * If `queries/index.ts` re-exports `byKind` from `by-kind.ts`, but every\n * consumer of `byKind` imports it directly from `by-kind.ts` (not through\n * `index.ts`), the re-export in the barrel is dead weight.\n *\n * Algorithm:\n * 1. Find all barrel files (index.ts / index.js)\n * 2. For each barrel, find symbols it re-exports (defined elsewhere, referenced in barrel with role=0)\n * 3. For each re-exported symbol, count consumers through the barrel vs direct from the source\n * 4. If zero consumers go through the barrel, the re-export is redundant\n */\nexport function redundantReexports(\n db: ScipDatabase,\n opts: { scope?: string; limit?: number } = {},\n): RedundantReexport[] {\n const { scope, limit } = opts;\n\n const scopeFilter = scope ? `AND barrel_d.relative_path LIKE '%${scope}%'` : '';\n\n // Step 1 + 2: Find all barrel files and symbols they re-export.\n // A re-export is a symbol that:\n // - is mentioned in a barrel file with role=0 (reference/import)\n // - has its definition (defn_enclosing_ranges) in a DIFFERENT file\n const reexportRows = db.all<{\n barrel_doc_id: number;\n barrel_path: string;\n symbol_id: number;\n symbol: string;\n original_doc_id: number;\n original_path: string;\n }>(\n `SELECT DISTINCT\n barrel_d.id AS barrel_doc_id,\n barrel_d.relative_path AS barrel_path,\n gs.id AS symbol_id,\n gs.symbol AS symbol,\n orig_d.id AS original_doc_id,\n orig_d.relative_path AS original_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents barrel_d ON c.document_id = barrel_d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents orig_d ON der.document_id = orig_d.id\n WHERE m.role != 1\n AND (barrel_d.relative_path LIKE '%/index.ts'\n OR barrel_d.relative_path LIKE '%/index.js'\n OR barrel_d.relative_path = 'index.ts'\n OR barrel_d.relative_path = 'index.js')\n AND orig_d.id != barrel_d.id\n ${db.pathExclusionsFor('barrel_d', 'orig_d')}\n ${db.symbolNoiseFor('gs')}\n -- Only function-level symbols (ending with ().), not module-level\n AND gs.symbol LIKE '%().'\n ${scopeFilter}\n ORDER BY barrel_d.relative_path, gs.symbol`,\n );\n\n const results: RedundantReexport[] = [];\n\n for (const row of reexportRows) {\n if (db.isIgnored(row.barrel_path) || db.isIgnored(row.original_path)) continue;\n\n // Step 3: Count consumers that reference this symbol through the barrel\n // A \"barrel consumer\" is a file (other than the barrel itself and the original file)\n // that mentions this symbol AND also mentions something from the barrel document.\n // More precisely: count distinct files that reference this symbol AND whose\n // chunk is in a document that also has a role=0 mention pointing to the barrel file's symbols.\n //\n // Simpler approach: count distinct documents that reference this symbol (role=0)\n // grouped by whether the reference chunk is in a file that imports from the barrel\n // or from the original.\n //\n // Actually, the most reliable approach with SCIP data: count how many distinct\n // consumer documents reference this symbol_id with role=0, excluding the barrel\n // and the original file themselves. Then check if those consumers also reference\n // ANY symbol through a mention in the barrel doc vs the original doc.\n //\n // Simplest correct approach: In SCIP, when file A does `import { foo } from './bar/index'`,\n // the mention of `foo` in file A points to the same global symbol regardless of import path.\n // SCIP doesn't track import provenance. BUT the barrel file itself contains mentions\n // (role=0 references) of the re-exported symbols. So we can check:\n // - barrelConsumers: files that mention both this symbol AND any symbol whose definition\n // is in the barrel (i.e., they import the barrel)\n // - directConsumers: files that mention this symbol but don't import the barrel\n //\n // Even simpler: check if the barrel document is in the deps of the consumer.\n // A consumer \"goes through the barrel\" if it has ANY role=0 mention pointing to a\n // chunk in the barrel file. Otherwise it goes direct.\n\n const consumerCounts = db.get<{\n barrel_consumers: number;\n direct_consumers: number;\n }>(\n `SELECT\n SUM(CASE WHEN uses_barrel = 1 THEN 1 ELSE 0 END) AS barrel_consumers,\n SUM(CASE WHEN uses_barrel = 0 THEN 1 ELSE 0 END) AS direct_consumers\n FROM (\n SELECT\n consumer_d.id AS consumer_doc_id,\n MAX(CASE WHEN EXISTS (\n SELECT 1\n FROM mentions barrel_m\n JOIN chunks barrel_c ON barrel_m.chunk_id = barrel_c.id\n WHERE barrel_c.document_id = consumer_d.id\n AND barrel_m.role != 1\n AND barrel_m.symbol_id IN (\n SELECT m2.symbol_id\n FROM mentions m2\n JOIN chunks c2 ON m2.chunk_id = c2.id\n WHERE c2.document_id = ?\n AND m2.role != 1\n )\n ) THEN 1 ELSE 0 END) AS uses_barrel\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN documents consumer_d ON ref_c.document_id = consumer_d.id\n WHERE ref_m.symbol_id = ?\n AND ref_m.role != 1\n AND consumer_d.id != ?\n AND consumer_d.id != ?\n ${db.pathExclusionsFor('consumer_d')}\n GROUP BY consumer_d.id\n )`,\n row.barrel_doc_id, // for the inner subquery checking barrel mentions\n row.symbol_id, // the re-exported symbol\n row.barrel_doc_id, // exclude the barrel itself\n row.original_doc_id, // exclude the original file\n );\n\n const barrelConsumers = consumerCounts?.barrel_consumers ?? 0;\n const directConsumers = consumerCounts?.direct_consumers ?? 0;\n\n // In TypeScript, `import * as X from './barrel'` resolves all references\n // directly to the source file — the barrel is transparent to SCIP.\n // This means barrelConsumers is always 0 for namespace imports.\n //\n // We can only confidently report symbols with 0 consumers EVERYWHERE\n // (both barrel and direct). These are truly dead re-exports.\n //\n // Symbols with directConsumers > 0 but barrelConsumers === 0 might be\n // consumed through a namespace import — we can't tell, so we skip them.\n if (barrelConsumers === 0 && directConsumers === 0) {\n results.push({\n barrelFile: row.barrel_path,\n symbol: row.symbol,\n shortName: shortenSymbol(row.symbol),\n originalFile: row.original_path,\n barrelConsumers,\n directConsumers,\n });\n }\n }\n\n // Sort: symbols with the most direct consumers first (biggest cleanup wins),\n // then by barrel file path for stable output\n results.sort((a, b) =>\n b.directConsumers - a.directConsumers\n || a.barrelFile.localeCompare(b.barrelFile)\n || a.shortName.localeCompare(b.shortName),\n );\n\n return limit ? results.slice(0, limit) : results;\n}\n"],"mappings":";;;;;AAiBO,SAAS,mBACd,IACA,OAA2C,CAAC,GACvB;AACrB,QAAM,EAAE,OAAO,MAAM,IAAI;AAEzB,QAAM,cAAc,QAAQ,qCAAqC,KAAK,OAAO;AAM7E,QAAM,eAAe,GAAG;AAAA,IAQtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAmBI,GAAG,kBAAkB,YAAY,QAAQ,CAAC;AAAA,QAC1C,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,QAGvB,WAAW;AAAA;AAAA,EAEjB;AAEA,QAAM,UAA+B,CAAC;AAEtC,aAAW,OAAO,cAAc;AAC9B,QAAI,GAAG,UAAU,IAAI,WAAW,KAAK,GAAG,UAAU,IAAI,aAAa,EAAG;AA6BtE,UAAM,iBAAiB,GAAG;AAAA,MAIxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YA2BM,GAAG,kBAAkB,YAAY,CAAC;AAAA;AAAA;AAAA,MAGxC,IAAI;AAAA;AAAA,MACJ,IAAI;AAAA;AAAA,MACJ,IAAI;AAAA;AAAA,MACJ,IAAI;AAAA;AAAA,IACN;AAEA,UAAM,kBAAkB,gBAAgB,oBAAoB;AAC5D,UAAM,kBAAkB,gBAAgB,oBAAoB;AAW5D,QAAI,oBAAoB,KAAK,oBAAoB,GAAG;AAClD,cAAQ,KAAK;AAAA,QACX,YAAY,IAAI;AAAA,QAChB,QAAQ,IAAI;AAAA,QACZ,WAAW,cAAc,IAAI,MAAM;AAAA,QACnC,cAAc,IAAI;AAAA,QAClB;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAIA,UAAQ;AAAA,IAAK,CAAC,GAAG,MACf,EAAE,kBAAkB,EAAE,mBACnB,EAAE,WAAW,cAAc,EAAE,UAAU,KACvC,EAAE,UAAU,cAAc,EAAE,SAAS;AAAA,EAC1C;AAEA,SAAO,QAAQ,QAAQ,MAAM,GAAG,KAAK,IAAI;AAC3C;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/convergence.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { ConvergenceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Given two similar symbols, show what a consolidated version would look like.\n * The shared callee set becomes the common body. The unique callees become\n * the parameterization points.\n */\nexport function convergence(\n db: ScipDatabase,\n symbolPatternA: string,\n symbolPatternB: string,\n): ConvergenceResult | null {\n const matchA = findFirstSymbolMatch(db, symbolPatternA);\n const matchB = findFirstSymbolMatch(db, symbolPatternB);\n\n if (!matchA || !matchB) return null;\n\n const calleesA = new Set(\n getCalleeRowsForSymbol(db, matchA).map((r) => r.symbol),\n );\n const calleesB = new Set(\n getCalleeRowsForSymbol(db, matchB).map((r) => r.symbol),\n );\n\n const shared: string[] = [];\n for (const c of calleesA) {\n if (calleesB.has(c)) shared.push(c);\n }\n\n const uniqueA: string[] = [];\n for (const c of calleesA) {\n if (!calleesB.has(c)) uniqueA.push(c);\n }\n\n const uniqueB: string[] = [];\n for (const c of calleesB) {\n if (!calleesA.has(c)) uniqueB.push(c);\n }\n\n const union = new Set([...calleesA, ...calleesB]);\n const similarity = union.size > 0 ? shared.length / union.size : 0;\n\n // Generate a consolidation strategy description\n let strategy: string;\n if (uniqueA.length === 0 && uniqueB.length === 0) {\n strategy = 'These functions have identical callee sets. One can replace the other directly.';\n } else if (uniqueA.length === 0) {\n strategy = `A is a subset of B. A can be replaced by calling B (B does everything A does plus more).`;\n } else if (uniqueB.length === 0) {\n strategy = `B is a subset of A. B can be replaced by calling A (A does everything B does plus more).`;\n } else if (uniqueA.length <= 2 && uniqueB.length <= 2) {\n strategy = `Create a shared function with the ${shared.length} common callees. Pass the ${uniqueA.length + uniqueB.length} divergent callees as parameters or strategy callbacks.`;\n } else {\n strategy = `Extract the ${shared.length} shared callees into a common helper. Each function calls the helper plus its own unique logic (${uniqueA.length} callees in A, ${uniqueB.length} in B).`;\n }\n\n const locA = matchA.endLine - matchA.startLine + 1;\n const locB = matchB.endLine - matchB.startLine + 1;\n\n return {\n symbolA: {\n symbol: matchA.symbol,\n shortName: shortenSymbol(matchA.symbol),\n file: matchA.relativePath,\n loc: locA,\n },\n symbolB: {\n symbol: matchB.symbol,\n shortName: shortenSymbol(matchB.symbol),\n file: matchB.relativePath,\n loc: locB,\n },\n similarity,\n sharedCallees: shared.map(shortenSymbol),\n uniqueToA: uniqueA.map(shortenSymbol),\n uniqueToB: uniqueB.map(shortenSymbol),\n consolidationStrategy: strategy,\n };\n}\n"],"mappings":";;;;;;;;;AAUO,SAAS,YACd,IACA,gBACA,gBAC0B;AAC1B,QAAM,SAAS,qBAAqB,IAAI,cAAc;AACtD,QAAM,SAAS,qBAAqB,IAAI,cAAc;AAEtD,MAAI,CAAC,UAAU,CAAC,OAAQ,QAAO;AAE/B,QAAM,WAAW,IAAI;AAAA,IACnB,uBAAuB,IAAI,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM;AAAA,EACxD;AACA,QAAM,WAAW,IAAI;AAAA,IACnB,uBAAuB,IAAI,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM;AAAA,EACxD;AAEA,QAAM,SAAmB,CAAC;AAC1B,aAAW,KAAK,UAAU;AACxB,QAAI,SAAS,IAAI,CAAC,EAAG,QAAO,KAAK,CAAC;AAAA,EACpC;AAEA,QAAM,UAAoB,CAAC;AAC3B,aAAW,KAAK,UAAU;AACxB,QAAI,CAAC,SAAS,IAAI,CAAC,EAAG,SAAQ,KAAK,CAAC;AAAA,EACtC;AAEA,QAAM,UAAoB,CAAC;AAC3B,aAAW,KAAK,UAAU;AACxB,QAAI,CAAC,SAAS,IAAI,CAAC,EAAG,SAAQ,KAAK,CAAC;AAAA,EACtC;AAEA,QAAM,QAAQ,oBAAI,IAAI,CAAC,GAAG,UAAU,GAAG,QAAQ,CAAC;AAChD,QAAM,aAAa,MAAM,OAAO,IAAI,OAAO,SAAS,MAAM,OAAO;AAGjE,MAAI;AACJ,MAAI,QAAQ,WAAW,KAAK,QAAQ,WAAW,GAAG;AAChD,eAAW;AAAA,EACb,WAAW,QAAQ,WAAW,GAAG;AAC/B,eAAW;AAAA,EACb,WAAW,QAAQ,WAAW,GAAG;AAC/B,eAAW;AAAA,EACb,WAAW,QAAQ,UAAU,KAAK,QAAQ,UAAU,GAAG;AACrD,eAAW,qCAAqC,OAAO,MAAM,6BAA6B,QAAQ,SAAS,QAAQ,MAAM;AAAA,EAC3H,OAAO;AACL,eAAW,eAAe,OAAO,MAAM,mGAAmG,QAAQ,MAAM,kBAAkB,QAAQ,MAAM;AAAA,EAC1L;AAEA,QAAM,OAAO,OAAO,UAAU,OAAO,YAAY;AACjD,QAAM,OAAO,OAAO,UAAU,OAAO,YAAY;AAEjD,SAAO;AAAA,IACL,SAAS;AAAA,MACP,QAAQ,OAAO;AAAA,MACf,WAAW,cAAc,OAAO,MAAM;AAAA,MACtC,MAAM,OAAO;AAAA,MACb,KAAK;AAAA,IACP;AAAA,IACA,SAAS;AAAA,MACP,QAAQ,OAAO;AAAA,MACf,WAAW,cAAc,OAAO,MAAM;AAAA,MACtC,MAAM,OAAO;AAAA,MACb,KAAK;AAAA,IACP;AAAA,IACA;AAAA,IACA,eAAe,OAAO,IAAI,aAAa;AAAA,IACvC,WAAW,QAAQ,IAAI,aAAa;AAAA,IACpC,WAAW,QAAQ,IAAI,aAAa;AAAA,IACpC,uBAAuB;AAAA,EACzB;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/complexity-hotspots.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { testFileExclusionSql } from '../query-support.js';\nimport type { ComplexityHotspot } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find complexity hotspots: symbols with a composite score based on\n * LOC, fan-in, fan-out, and callee count.\n *\n * Score = (loc / 50) * (fanIn / 5) * max(fanOut / 5, 1)\n *\n * High scores indicate symbols that are large, widely depended upon,\n * AND reach out to many other modules — the riskiest code to change.\n */\nexport function complexityHotspots(\n db: ScipDatabase,\n opts?: { scope?: string; minLoc?: number; limit?: number },\n): ComplexityHotspot[] {\n const { scope, minLoc = 10, limit = 30 } = opts ?? {};\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{\n symbol: string;\n file: string;\n start_line: number;\n end_line: number;\n loc: number;\n fan_in: number;\n fan_out: number;\n callee_count: number;\n }>(\n `SELECT\n gs.symbol,\n d.relative_path AS file,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n -- fanIn: distinct files that reference this symbol\n (SELECT COUNT(DISTINCT ref_c.document_id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n WHERE ref_m.symbol_id = gs.id AND ref_m.role != 1\n ) AS fan_in,\n -- fanOut: distinct symbols referenced within this definition range\n -- that are defined in different files\n (SELECT COUNT(DISTINCT out_gs.id)\n FROM mentions out_m\n JOIN chunks out_c ON out_m.chunk_id = out_c.id\n JOIN global_symbols out_gs ON out_m.symbol_id = out_gs.id\n JOIN defn_enclosing_ranges out_der ON out_gs.id = out_der.symbol_id\n WHERE out_c.document_id = der.document_id\n AND out_c.start_line >= der.start_line\n AND out_c.end_line <= der.end_line\n AND out_m.role != 1\n AND out_gs.id != gs.id\n AND out_der.document_id != der.document_id\n ) AS fan_out,\n -- calleeCount: total distinct callees within definition range\n (SELECT COUNT(DISTINCT callee_gs.id)\n FROM mentions callee_m\n JOIN chunks callee_c ON callee_m.chunk_id = callee_c.id\n JOIN global_symbols callee_gs ON callee_m.symbol_id = callee_gs.id\n WHERE callee_c.document_id = der.document_id\n AND callee_c.start_line >= der.start_line\n AND callee_c.end_line <= der.end_line\n AND callee_m.role != 1\n AND callee_gs.id != gs.id\n ) AS callee_count\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n AND (der.end_line - der.start_line + 1) >= ?\n ${scopeFilter}\n ORDER BY (\n CAST((der.end_line - der.start_line + 1) AS REAL) / 50.0\n * CAST((SELECT COUNT(DISTINCT ref_c2.document_id)\n FROM mentions ref_m2\n JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id\n WHERE ref_m2.symbol_id = gs.id AND ref_m2.role != 1\n ) AS REAL) / 5.0\n * MAX(CAST((SELECT COUNT(DISTINCT out_gs2.id)\n FROM mentions out_m2\n JOIN chunks out_c2 ON out_m2.chunk_id = out_c2.id\n JOIN global_symbols out_gs2 ON out_m2.symbol_id = out_gs2.id\n JOIN defn_enclosing_ranges out_der2 ON out_gs2.id = out_der2.symbol_id\n WHERE out_c2.document_id = der.document_id\n AND out_c2.start_line >= der.start_line\n AND out_c2.end_line <= der.end_line\n AND out_m2.role != 1\n AND out_gs2.id != gs.id\n AND out_der2.document_id != der.document_id\n ) AS REAL) / 5.0, 1.0)\n ) DESC\n LIMIT ?`,\n minLoc, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.file))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n file: r.file,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n fanIn: r.fan_in,\n fanOut: r.fan_out,\n calleeCount: r.callee_count,\n score:\n Math.round(\n (r.loc / 50) * (r.fan_in / 5) * Math.max(r.fan_out / 5, 1) * 100,\n ) / 100,\n }));\n}\n"],"mappings":";;;;;;;;AAcO,SAAS,mBACd,IACA,MACqB;AACrB,QAAM,EAAE,OAAO,SAAS,IAAI,QAAQ,GAAG,IAAI,QAAQ,CAAC;AACpD,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAEtE,QAAM,OAAO,GAAG;AAAA,IAUd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAyCI,GAAG,kBAAkB,GAAG,CAAC;AAAA,YACrB,qBAAqB,GAAG,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA,QAEvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAsBf;AAAA,IAAQ;AAAA,EACV;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,MAAM,EAAE;AAAA,IACR,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,KAAK,EAAE;AAAA,IACP,OAAO,EAAE;AAAA,IACT,QAAQ,EAAE;AAAA,IACV,aAAa,EAAE;AAAA,IACf,OACE,KAAK;AAAA,MACF,EAAE,MAAM,MAAO,EAAE,SAAS,KAAK,KAAK,IAAI,EAAE,UAAU,GAAG,CAAC,IAAI;AAAA,IAC/D,IAAI;AAAA,EACR,EAAE;AACN;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/query-support.ts"],"sourcesContent":["import type { ScipDatabase } from './db.js';\n\nexport interface SymbolLocation {\n documentId: number;\n startLine: number;\n endLine: number;\n symbolId: number;\n}\n\nexport interface SymbolMatch extends SymbolLocation {\n symbol: string;\n relativePath: string;\n}\n\nexport interface CalleeRow {\n symbol: string;\n file: string;\n chunkId: number;\n}\n\nexport const TEST_FILE_PATTERNS = [\n '%/__tests__/%',\n '%.test.%',\n '%.spec.%',\n '%/test/%',\n '%/tests/%',\n '%_test.%',\n '%_spec.%',\n '%/test_%.%',\n '%/spec_%.%',\n] as const;\n\nexport const TEST_SUPPORT_PATH_PATTERNS = [\n '%/test-utils/%',\n] as const;\n\nexport function testFileMatchSql(\n alias: string,\n patterns: readonly string[] = TEST_FILE_PATTERNS,\n): string {\n return `(${patterns.map((pattern) => `${alias}.relative_path LIKE '${pattern}'`).join(' OR ')})`;\n}\n\nexport function testFileExclusionSql(\n alias: string,\n extraPatterns: readonly string[] = [],\n): string {\n const patterns = uniquePatterns([...TEST_FILE_PATTERNS, ...extraPatterns]);\n return patterns\n .map((pattern) => `${alias}.relative_path NOT LIKE '${pattern}'`)\n .join('\\n AND ');\n}\n\nexport function buildFileDepGraph(\n db: ScipDatabase,\n scope?: string,\n): Map<string, Set<string>> {\n const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : '';\n\n const edges = db.all<{ from_file: string; to_file: string }>(\n `SELECT DISTINCT\n d1.relative_path AS from_file,\n d2.relative_path AS to_file\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d1.id != d2.id\n AND m.role != 1\n ${db.pathExclusionsFor('d1', 'd2')}\n ${scopeFilter}`,\n );\n\n const graph = new Map<string, Set<string>>();\n for (const edge of edges) {\n if (db.isIgnored(edge.from_file) || db.isIgnored(edge.to_file)) continue;\n if (!graph.has(edge.from_file)) graph.set(edge.from_file, new Set());\n graph.get(edge.from_file)!.add(edge.to_file);\n }\n\n return graph;\n}\n\nexport function findFirstSymbolMatch(\n db: ScipDatabase,\n symbolPattern: string,\n): SymbolMatch | null {\n // Handle file:line-line syntax (e.g., \"src/foo.ts:10-50\")\n const fileLineMatch = symbolPattern.match(/^(.+):(\\d+)-(\\d+)$/);\n if (fileLineMatch) {\n const [, filePath, startStr, endStr] = fileLineMatch;\n const row = db.get<{\n id: number;\n symbol: string;\n document_id: number;\n start_line: number;\n end_line: number;\n relative_path: string;\n }>(\n `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE d.relative_path LIKE ?\n AND der.start_line <= ? AND der.end_line >= ?\n ${db.pathExclusionsFor('d')}\n ORDER BY (der.end_line - der.start_line) ASC\n LIMIT 1`,\n `%${filePath}%`, parseInt(startStr!, 10), parseInt(endStr!, 10),\n );\n if (row && !db.isIgnored(row.relative_path)) {\n return {\n symbolId: row.id,\n symbol: row.symbol,\n documentId: row.document_id,\n startLine: row.start_line,\n endLine: row.end_line,\n relativePath: row.relative_path,\n };\n }\n }\n\n // Strip parentheses from the pattern to avoid shell escaping issues.\n // Agents often pass \"functionName()\" — strip the () for matching.\n const cleaned = symbolPattern.replace(/\\(\\)$/, '').replace(/\\(.*$/, '');\n\n // Try exact-ish match first (with noise filter), then fallback without noise filter.\n // The noise filter excludes %().(% which can incorrectly match some symbols.\n for (const useNoiseFilter of [true, false]) {\n const noiseClause = useNoiseFilter ? db.symbolNoiseFor('gs') : '';\n const row = db.get<{\n id: number;\n symbol: string;\n document_id: number;\n start_line: number;\n end_line: number;\n relative_path: string;\n }>(\n `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.symbol LIKE ?\n ${db.pathExclusionsFor('d')}\n ${noiseClause}\n ORDER BY (der.end_line - der.start_line) DESC\n LIMIT 1`,\n `%${cleaned}%`,\n );\n\n if (row && !db.isIgnored(row.relative_path)) {\n return {\n symbolId: row.id,\n symbol: row.symbol,\n documentId: row.document_id,\n startLine: row.start_line,\n endLine: row.end_line,\n relativePath: row.relative_path,\n };\n }\n }\n\n return null;\n}\n\nexport function getCalleeRowsForSymbol(\n db: ScipDatabase,\n symbol: SymbolLocation,\n opts: { limit?: number } = {},\n): CalleeRow[] {\n const rows = db.all<{\n symbol: string;\n file: string;\n chunk_id: number;\n }>(\n `SELECT DISTINCT\n callee_gs.symbol AS symbol,\n callee_d.relative_path AS file,\n c.id AS chunk_id\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN global_symbols callee_gs ON m.symbol_id = callee_gs.id\n JOIN defn_enclosing_ranges callee_der ON callee_gs.id = callee_der.symbol_id\n JOIN documents callee_d ON callee_der.document_id = callee_d.id\n WHERE c.document_id = ?\n AND c.start_line >= ?\n AND c.end_line <= ?\n AND m.role != 1\n AND callee_gs.id != ?\n ${db.symbolNoiseFor('callee_gs')}\n ${db.pathExclusionsFor('callee_d')}\n ORDER BY callee_d.relative_path\n ${opts.limit ? 'LIMIT ?' : ''}`,\n ...calleeQueryParams(symbol, opts.limit),\n );\n\n return rows.filter((row) => !db.isIgnored(row.file)).map((row) => ({\n symbol: row.symbol,\n file: row.file,\n chunkId: row.chunk_id,\n }));\n}\n\nfunction calleeQueryParams(\n symbol: SymbolLocation,\n limit?: number,\n): number[] {\n const params = [\n symbol.documentId,\n symbol.startLine,\n symbol.endLine,\n symbol.symbolId,\n ];\n\n if (typeof limit === 'number') {\n params.push(limit);\n }\n\n return params;\n}\n\nfunction uniquePatterns(patterns: readonly string[]): string[] {\n return [...new Set(patterns)];\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/deep-chains.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { DeepChainResult } from '../types.js';\n\n/**\n * Find the longest transitive dependency chains between files.\n * A chain A → B → C → D means A depends on B, B on C, C on D.\n *\n * Long chains = high coupling depth = changes at the end ripple through many layers.\n */\nexport function deepChains(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string; minDepth?: number } = {},\n): DeepChainResult[] {\n const { limit = 10, scope, minDepth = 3 } = opts;\n const graph = buildFileDepGraph(db, scope);\n\n // DFS to find longest paths (with cycle detection)\n const results: DeepChainResult[] = [];\n\n function dfs(node: string, path: string[], visited: Set<string>): void {\n const neighbors = graph.get(node);\n if (!neighbors || neighbors.size === 0) {\n if (path.length >= minDepth) {\n results.push({ chain: [...path], depth: path.length });\n }\n return;\n }\n\n let extended = false;\n for (const next of neighbors) {\n if (visited.has(next)) continue; // skip cycles\n visited.add(next);\n path.push(next);\n dfs(next, path, visited);\n path.pop();\n visited.delete(next);\n extended = true;\n }\n\n // If no unvisited neighbors, this is a leaf in this path\n if (!extended && path.length >= minDepth) {\n results.push({ chain: [...path], depth: path.length });\n }\n }\n\n // Start DFS from each node\n for (const startNode of graph.keys()) {\n const visited = new Set<string>([startNode]);\n dfs(startNode, [startNode], visited);\n\n // Early termination if we have enough results\n if (results.length > limit * 10) break;\n }\n\n // Sort by depth descending, take top N\n results.sort((a, b) => b.depth - a.depth);\n return results.slice(0, limit);\n}\n"],"mappings":";;;;;AAUO,SAAS,WACd,IACA,OAA8D,CAAC,GAC5C;AACnB,QAAM,EAAE,QAAQ,IAAI,OAAO,WAAW,EAAE,IAAI;AAC5C,QAAM,QAAQ,kBAAkB,IAAI,KAAK;AAGzC,QAAM,UAA6B,CAAC;AAEpC,WAAS,IAAI,MAAc,MAAgB,SAA4B;AACrE,UAAM,YAAY,MAAM,IAAI,IAAI;AAChC,QAAI,CAAC,aAAa,UAAU,SAAS,GAAG;AACtC,UAAI,KAAK,UAAU,UAAU;AAC3B,gBAAQ,KAAK,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,OAAO,KAAK,OAAO,CAAC;AAAA,MACvD;AACA;AAAA,IACF;AAEA,QAAI,WAAW;AACf,eAAW,QAAQ,WAAW;AAC5B,UAAI,QAAQ,IAAI,IAAI,EAAG;AACvB,cAAQ,IAAI,IAAI;AAChB,WAAK,KAAK,IAAI;AACd,UAAI,MAAM,MAAM,OAAO;AACvB,WAAK,IAAI;AACT,cAAQ,OAAO,IAAI;AACnB,iBAAW;AAAA,IACb;AAGA,QAAI,CAAC,YAAY,KAAK,UAAU,UAAU;AACxC,cAAQ,KAAK,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,OAAO,KAAK,OAAO,CAAC;AAAA,IACvD;AAAA,EACF;AAGA,aAAW,aAAa,MAAM,KAAK,GAAG;AACpC,UAAM,UAAU,oBAAI,IAAY,CAAC,SAAS,CAAC;AAC3C,QAAI,WAAW,CAAC,SAAS,GAAG,OAAO;AAGnC,QAAI,QAAQ,SAAS,QAAQ,GAAI;AAAA,EACnC;AAGA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACxC,SAAO,QAAQ,MAAM,GAAG,KAAK;AAC/B;","names":[]}
|