scip-query 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/IMPROVEMENTS.md +143 -0
- package/PLAN.md +320 -0
- package/README.md +1213 -0
- package/dist/chunk-2QZ23IBN.js +55 -0
- package/dist/chunk-2QZ23IBN.js.map +1 -0
- package/dist/chunk-36OMT7ZJ.js +144 -0
- package/dist/chunk-36OMT7ZJ.js.map +1 -0
- package/dist/chunk-3E2X7RIE.js +101 -0
- package/dist/chunk-3E2X7RIE.js.map +1 -0
- package/dist/chunk-3UOUTZQT.js +45 -0
- package/dist/chunk-3UOUTZQT.js.map +1 -0
- package/dist/chunk-3ZZJVBIO.js +88 -0
- package/dist/chunk-3ZZJVBIO.js.map +1 -0
- package/dist/chunk-4TYLS5XX.js +10 -0
- package/dist/chunk-4TYLS5XX.js.map +1 -0
- package/dist/chunk-5FGUEU7N.js +101 -0
- package/dist/chunk-5FGUEU7N.js.map +1 -0
- package/dist/chunk-5WTJAXY2.js +61 -0
- package/dist/chunk-5WTJAXY2.js.map +1 -0
- package/dist/chunk-6NBLIDF4.js +24 -0
- package/dist/chunk-6NBLIDF4.js.map +1 -0
- package/dist/chunk-6SXADWLW.js +43 -0
- package/dist/chunk-6SXADWLW.js.map +1 -0
- package/dist/chunk-6VJ6Q7IE.js +65 -0
- package/dist/chunk-6VJ6Q7IE.js.map +1 -0
- package/dist/chunk-7OZPA5OO.js +258 -0
- package/dist/chunk-7OZPA5OO.js.map +1 -0
- package/dist/chunk-BEPIEVLR.js +76 -0
- package/dist/chunk-BEPIEVLR.js.map +1 -0
- package/dist/chunk-BFSCMC22.js +42 -0
- package/dist/chunk-BFSCMC22.js.map +1 -0
- package/dist/chunk-BP2ATLK2.js +110 -0
- package/dist/chunk-BP2ATLK2.js.map +1 -0
- package/dist/chunk-CM454WL3.js +114 -0
- package/dist/chunk-CM454WL3.js.map +1 -0
- package/dist/chunk-DCKMSTJ4.js +74 -0
- package/dist/chunk-DCKMSTJ4.js.map +1 -0
- package/dist/chunk-DEZKCZXD.js +40 -0
- package/dist/chunk-DEZKCZXD.js.map +1 -0
- package/dist/chunk-DVWGWHFW.js +99 -0
- package/dist/chunk-DVWGWHFW.js.map +1 -0
- package/dist/chunk-EMDQWNYR.js +102 -0
- package/dist/chunk-EMDQWNYR.js.map +1 -0
- package/dist/chunk-FFSWWE5O.js +33 -0
- package/dist/chunk-FFSWWE5O.js.map +1 -0
- package/dist/chunk-FGXRVW7G.js +73 -0
- package/dist/chunk-FGXRVW7G.js.map +1 -0
- package/dist/chunk-FUHJCHS4.js +158 -0
- package/dist/chunk-FUHJCHS4.js.map +1 -0
- package/dist/chunk-GJFURBEW.js +64 -0
- package/dist/chunk-GJFURBEW.js.map +1 -0
- package/dist/chunk-GTILYBH6.js +102 -0
- package/dist/chunk-GTILYBH6.js.map +1 -0
- package/dist/chunk-JJP7KQND.js +1 -0
- package/dist/chunk-JJP7KQND.js.map +1 -0
- package/dist/chunk-JKP5GH6T.js +213 -0
- package/dist/chunk-JKP5GH6T.js.map +1 -0
- package/dist/chunk-KCBMVQL5.js +38 -0
- package/dist/chunk-KCBMVQL5.js.map +1 -0
- package/dist/chunk-KVSW5KYP.js +78 -0
- package/dist/chunk-KVSW5KYP.js.map +1 -0
- package/dist/chunk-LAWMH22O.js +172 -0
- package/dist/chunk-LAWMH22O.js.map +1 -0
- package/dist/chunk-LB7OS35Q.js +72 -0
- package/dist/chunk-LB7OS35Q.js.map +1 -0
- package/dist/chunk-LUSIFBXO.js +57 -0
- package/dist/chunk-LUSIFBXO.js.map +1 -0
- package/dist/chunk-MBVNHJVN.js +44 -0
- package/dist/chunk-MBVNHJVN.js.map +1 -0
- package/dist/chunk-MGNMHKX3.js +15 -0
- package/dist/chunk-MGNMHKX3.js.map +1 -0
- package/dist/chunk-N5KEREIA.js +41 -0
- package/dist/chunk-N5KEREIA.js.map +1 -0
- package/dist/chunk-NDSQYIWT.js +71 -0
- package/dist/chunk-NDSQYIWT.js.map +1 -0
- package/dist/chunk-NUZ4OMU3.js +28 -0
- package/dist/chunk-NUZ4OMU3.js.map +1 -0
- package/dist/chunk-QOV2R2WT.js +170 -0
- package/dist/chunk-QOV2R2WT.js.map +1 -0
- package/dist/chunk-SEFSL2GF.js +78 -0
- package/dist/chunk-SEFSL2GF.js.map +1 -0
- package/dist/chunk-T6ARFSBZ.js +103 -0
- package/dist/chunk-T6ARFSBZ.js.map +1 -0
- package/dist/chunk-TBP6BICL.js +46 -0
- package/dist/chunk-TBP6BICL.js.map +1 -0
- package/dist/chunk-TDNNOR6D.js +97 -0
- package/dist/chunk-TDNNOR6D.js.map +1 -0
- package/dist/chunk-TSPZOMHC.js +195 -0
- package/dist/chunk-TSPZOMHC.js.map +1 -0
- package/dist/chunk-UNTPVD36.js +55 -0
- package/dist/chunk-UNTPVD36.js.map +1 -0
- package/dist/chunk-VRUJH4BO.js +88 -0
- package/dist/chunk-VRUJH4BO.js.map +1 -0
- package/dist/chunk-VZ7AMAFL.js +76 -0
- package/dist/chunk-VZ7AMAFL.js.map +1 -0
- package/dist/chunk-XFXDXEUN.js +24 -0
- package/dist/chunk-XFXDXEUN.js.map +1 -0
- package/dist/chunk-YZAA4LYG.js +169 -0
- package/dist/chunk-YZAA4LYG.js.map +1 -0
- package/dist/chunk-Z73NYSBZ.js +92 -0
- package/dist/chunk-Z73NYSBZ.js.map +1 -0
- package/dist/chunk-ZJRYBOEE.js +125 -0
- package/dist/chunk-ZJRYBOEE.js.map +1 -0
- package/dist/cli.js +5798 -0
- package/dist/cli.js.map +1 -0
- package/dist/db-BxaevAyc.d.ts +683 -0
- package/dist/index.d.ts +254 -0
- package/dist/index.js +1271 -0
- package/dist/index.js.map +1 -0
- package/dist/postinstall.js +167 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/queries/affected.d.ts +14 -0
- package/dist/queries/affected.js +9 -0
- package/dist/queries/affected.js.map +1 -0
- package/dist/queries/bottlenecks.d.ts +18 -0
- package/dist/queries/bottlenecks.js +8 -0
- package/dist/queries/bottlenecks.js.map +1 -0
- package/dist/queries/by-kind.d.ts +20 -0
- package/dist/queries/by-kind.js +10 -0
- package/dist/queries/by-kind.js.map +1 -0
- package/dist/queries/call-graph.d.ts +13 -0
- package/dist/queries/call-graph.js +9 -0
- package/dist/queries/call-graph.js.map +1 -0
- package/dist/queries/change-surface.d.ts +10 -0
- package/dist/queries/change-surface.js +9 -0
- package/dist/queries/change-surface.js.map +1 -0
- package/dist/queries/clean-signature.d.ts +9 -0
- package/dist/queries/clean-signature.js +7 -0
- package/dist/queries/clean-signature.js.map +1 -0
- package/dist/queries/code.d.ts +17 -0
- package/dist/queries/code.js +9 -0
- package/dist/queries/code.js.map +1 -0
- package/dist/queries/complexity-hotspots.d.ts +19 -0
- package/dist/queries/complexity-hotspots.js +9 -0
- package/dist/queries/complexity-hotspots.js.map +1 -0
- package/dist/queries/complexity.d.ts +13 -0
- package/dist/queries/complexity.js +9 -0
- package/dist/queries/complexity.js.map +1 -0
- package/dist/queries/convergence.d.ts +11 -0
- package/dist/queries/convergence.js +9 -0
- package/dist/queries/convergence.js.map +1 -0
- package/dist/queries/coupling.d.ts +17 -0
- package/dist/queries/coupling.js +9 -0
- package/dist/queries/coupling.js.map +1 -0
- package/dist/queries/cycles.d.ts +16 -0
- package/dist/queries/cycles.js +8 -0
- package/dist/queries/cycles.js.map +1 -0
- package/dist/queries/dataflow.d.ts +19 -0
- package/dist/queries/dataflow.js +9 -0
- package/dist/queries/dataflow.js.map +1 -0
- package/dist/queries/dead.d.ts +10 -0
- package/dist/queries/dead.js +9 -0
- package/dist/queries/dead.js.map +1 -0
- package/dist/queries/deep-chains.d.ts +16 -0
- package/dist/queries/deep-chains.js +8 -0
- package/dist/queries/deep-chains.js.map +1 -0
- package/dist/queries/deps.d.ts +9 -0
- package/dist/queries/deps.js +9 -0
- package/dist/queries/deps.js.map +1 -0
- package/dist/queries/diff-impact.d.ts +13 -0
- package/dist/queries/diff-impact.js +9 -0
- package/dist/queries/diff-impact.js.map +1 -0
- package/dist/queries/doc-coverage.d.ts +14 -0
- package/dist/queries/doc-coverage.js +8 -0
- package/dist/queries/doc-coverage.js.map +1 -0
- package/dist/queries/drift.d.ts +25 -0
- package/dist/queries/drift.js +8 -0
- package/dist/queries/drift.js.map +1 -0
- package/dist/queries/extract-candidates.d.ts +25 -0
- package/dist/queries/extract-candidates.js +9 -0
- package/dist/queries/extract-candidates.js.map +1 -0
- package/dist/queries/fan.d.ts +29 -0
- package/dist/queries/fan.js +14 -0
- package/dist/queries/fan.js.map +1 -0
- package/dist/queries/files.d.ts +6 -0
- package/dist/queries/files.js +7 -0
- package/dist/queries/files.js.map +1 -0
- package/dist/queries/health.d.ts +18 -0
- package/dist/queries/health.js +21 -0
- package/dist/queries/health.js.map +1 -0
- package/dist/queries/hierarchy.d.ts +13 -0
- package/dist/queries/hierarchy.js +8 -0
- package/dist/queries/hierarchy.js.map +1 -0
- package/dist/queries/hotspots.d.ts +13 -0
- package/dist/queries/hotspots.js +8 -0
- package/dist/queries/hotspots.js.map +1 -0
- package/dist/queries/imports.d.ts +19 -0
- package/dist/queries/imports.js +12 -0
- package/dist/queries/imports.js.map +1 -0
- package/dist/queries/index.d.ts +47 -0
- package/dist/queries/index.js +207 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/isolated.d.ts +14 -0
- package/dist/queries/isolated.js +9 -0
- package/dist/queries/isolated.js.map +1 -0
- package/dist/queries/members.d.ts +10 -0
- package/dist/queries/members.js +8 -0
- package/dist/queries/members.js.map +1 -0
- package/dist/queries/methods.d.ts +6 -0
- package/dist/queries/methods.js +8 -0
- package/dist/queries/methods.js.map +1 -0
- package/dist/queries/outline.d.ts +10 -0
- package/dist/queries/outline.js +8 -0
- package/dist/queries/outline.js.map +1 -0
- package/dist/queries/passthrough-candidates.d.ts +18 -0
- package/dist/queries/passthrough-candidates.js +9 -0
- package/dist/queries/passthrough-candidates.js.map +1 -0
- package/dist/queries/redundant-reexports.d.ts +22 -0
- package/dist/queries/redundant-reexports.js +8 -0
- package/dist/queries/redundant-reexports.js.map +1 -0
- package/dist/queries/refs.d.ts +6 -0
- package/dist/queries/refs.js +7 -0
- package/dist/queries/refs.js.map +1 -0
- package/dist/queries/similar-chains.d.ts +29 -0
- package/dist/queries/similar-chains.js +8 -0
- package/dist/queries/similar-chains.js.map +1 -0
- package/dist/queries/similar-files.d.ts +19 -0
- package/dist/queries/similar-files.js +8 -0
- package/dist/queries/similar-files.js.map +1 -0
- package/dist/queries/similar-signatures.d.ts +21 -0
- package/dist/queries/similar-signatures.js +8 -0
- package/dist/queries/similar-signatures.js.map +1 -0
- package/dist/queries/similar.d.ts +34 -0
- package/dist/queries/similar.js +11 -0
- package/dist/queries/similar.js.map +1 -0
- package/dist/queries/slice.d.ts +21 -0
- package/dist/queries/slice.js +9 -0
- package/dist/queries/slice.js.map +1 -0
- package/dist/queries/stale-abstractions.d.ts +18 -0
- package/dist/queries/stale-abstractions.js +9 -0
- package/dist/queries/stale-abstractions.js.map +1 -0
- package/dist/queries/stats.d.ts +6 -0
- package/dist/queries/stats.js +7 -0
- package/dist/queries/stats.js.map +1 -0
- package/dist/queries/surface.d.ts +7 -0
- package/dist/queries/surface.js +8 -0
- package/dist/queries/surface.js.map +1 -0
- package/dist/queries/symbols.d.ts +6 -0
- package/dist/queries/symbols.js +9 -0
- package/dist/queries/symbols.js.map +1 -0
- package/dist/queries/system.d.ts +7 -0
- package/dist/queries/system.js +9 -0
- package/dist/queries/system.js.map +1 -0
- package/dist/queries/test-coverage.d.ts +22 -0
- package/dist/queries/test-coverage.js +11 -0
- package/dist/queries/test-coverage.js.map +1 -0
- package/dist/queries/trace.d.ts +6 -0
- package/dist/queries/trace.js +8 -0
- package/dist/queries/trace.js.map +1 -0
- package/dist/queries/wrapper-candidates.d.ts +17 -0
- package/dist/queries/wrapper-candidates.js +9 -0
- package/dist/queries/wrapper-candidates.js.map +1 -0
- package/dist/reindex-worker.js +368 -0
- package/dist/reindex-worker.js.map +1 -0
- package/docs/AGENT_GUIDE.md +359 -0
- package/package.json +70 -0
- package/reports/debloat/2026-04-10-scip-query-self-audit.md +161 -0
- package/skills/concrete-plan/SKILL.md +318 -0
- package/skills/scip-debloat/SKILL.md +413 -0
- package/skills/scip-explore/SKILL.md +235 -0
- package/skills/scip-verify/SKILL.md +323 -0
- package/src/cli.ts +1480 -0
- package/src/config.ts +117 -0
- package/src/db.ts +127 -0
- package/src/gitignore-filter.ts +143 -0
- package/src/index.ts +11 -0
- package/src/postinstall.ts +8 -0
- package/src/queries/affected.ts +86 -0
- package/src/queries/bottlenecks.ts +67 -0
- package/src/queries/by-kind.ts +204 -0
- package/src/queries/call-graph.ts +66 -0
- package/src/queries/change-surface.ts +110 -0
- package/src/queries/clean-signature.ts +22 -0
- package/src/queries/code.ts +101 -0
- package/src/queries/complexity-hotspots.ts +119 -0
- package/src/queries/complexity.ts +152 -0
- package/src/queries/convergence.ts +82 -0
- package/src/queries/coupling.ts +99 -0
- package/src/queries/cycles.ts +78 -0
- package/src/queries/dataflow.ts +128 -0
- package/src/queries/dead.ts +122 -0
- package/src/queries/deep-chains.ts +59 -0
- package/src/queries/deps.ts +46 -0
- package/src/queries/diff-impact.ts +204 -0
- package/src/queries/doc-coverage.ts +86 -0
- package/src/queries/drift.ts +224 -0
- package/src/queries/extract-candidates.ts +167 -0
- package/src/queries/fan.ts +148 -0
- package/src/queries/files.ts +16 -0
- package/src/queries/health.ts +324 -0
- package/src/queries/hierarchy.ts +49 -0
- package/src/queries/hotspots.ts +53 -0
- package/src/queries/imports.ts +95 -0
- package/src/queries/index.ts +45 -0
- package/src/queries/isolated.ts +67 -0
- package/src/queries/members.ts +54 -0
- package/src/queries/methods.ts +27 -0
- package/src/queries/outline.ts +52 -0
- package/src/queries/passthrough-candidates.ts +94 -0
- package/src/queries/redundant-reexports.ts +170 -0
- package/src/queries/refs.ts +27 -0
- package/src/queries/similar-chains.ts +314 -0
- package/src/queries/similar-files.ts +140 -0
- package/src/queries/similar-signatures.ts +151 -0
- package/src/queries/similar.ts +305 -0
- package/src/queries/slice.ts +154 -0
- package/src/queries/stale-abstractions.ts +82 -0
- package/src/queries/stats.ts +22 -0
- package/src/queries/surface.ts +34 -0
- package/src/queries/symbols.ts +39 -0
- package/src/queries/system.ts +86 -0
- package/src/queries/test-coverage.ts +106 -0
- package/src/queries/trace.ts +55 -0
- package/src/queries/wrapper-candidates.ts +112 -0
- package/src/query-support.ts +226 -0
- package/src/reindex/detect.ts +58 -0
- package/src/reindex/index.ts +153 -0
- package/src/reindex/indexers.ts +220 -0
- package/src/reindex/install.ts +125 -0
- package/src/reindex-worker.ts +35 -0
- package/src/setup.ts +202 -0
- package/src/symbol-parser.ts +278 -0
- package/src/types.ts +654 -0
- package/src/watch.ts +274 -0
- package/tests/gitignore-filter.test.ts +48 -0
- package/tests/queries.test.ts +300 -0
- package/tests/symbol-parser.test.ts +157 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +40 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ScipDatabase } from '../db.js';
|
|
2
|
+
import type { StatsResult } from '../types.js';
|
|
3
|
+
|
|
4
|
+
export function stats(db: ScipDatabase): StatsResult {
|
|
5
|
+
const documents = db.get<{ c: number }>('SELECT COUNT(*) as c FROM documents')!.c;
|
|
6
|
+
const symbols = db.get<{ c: number }>('SELECT COUNT(*) as c FROM global_symbols')!.c;
|
|
7
|
+
const definitions = db.get<{ c: number }>(
|
|
8
|
+
'SELECT COUNT(*) as c FROM mentions WHERE role = 1',
|
|
9
|
+
)!.c;
|
|
10
|
+
const references = db.get<{ c: number }>(
|
|
11
|
+
'SELECT COUNT(*) as c FROM mentions WHERE role = 0',
|
|
12
|
+
)!.c;
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
documents,
|
|
16
|
+
symbols,
|
|
17
|
+
definitions,
|
|
18
|
+
references,
|
|
19
|
+
indexSizeBytes: db.sizeBytes(),
|
|
20
|
+
lastBuilt: db.lastModified(),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { ScipDatabase } from '../db.js';
|
|
2
|
+
import type { SurfaceResult } from '../types.js';
|
|
3
|
+
import { shortenSymbol } from '../symbol-parser.js';
|
|
4
|
+
|
|
5
|
+
/** Public API surface: what symbols do external consumers actually use from this module? */
|
|
6
|
+
export function surface(db: ScipDatabase, modulePattern: string): SurfaceResult[] {
|
|
7
|
+
const rows = db.all<{
|
|
8
|
+
relative_path: string;
|
|
9
|
+
symbol: string;
|
|
10
|
+
}>(
|
|
11
|
+
`SELECT DISTINCT d1.relative_path, gs.symbol
|
|
12
|
+
FROM mentions m
|
|
13
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
14
|
+
JOIN documents d1 ON c.document_id = d1.id
|
|
15
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
16
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
17
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
18
|
+
WHERE d2.relative_path LIKE ?
|
|
19
|
+
AND d1.relative_path NOT LIKE ?
|
|
20
|
+
AND ${db.localSymbolPredicate}
|
|
21
|
+
AND m.role = 0
|
|
22
|
+
ORDER BY d1.relative_path`,
|
|
23
|
+
`%${modulePattern}%`,
|
|
24
|
+
`%${modulePattern}%`,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return rows
|
|
28
|
+
.filter((r) => !db.isIgnored(r.relative_path))
|
|
29
|
+
.map((r) => ({
|
|
30
|
+
consumer: r.relative_path,
|
|
31
|
+
symbol: r.symbol,
|
|
32
|
+
shortName: shortenSymbol(r.symbol),
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ScipDatabase } from '../db.js';
|
|
2
|
+
import type { SymbolResult } from '../types.js';
|
|
3
|
+
import { shortenSymbol } from '../symbol-parser.js';
|
|
4
|
+
import { cleanSignature } from './clean-signature.js';
|
|
5
|
+
|
|
6
|
+
export function symbols(db: ScipDatabase, filePattern: string): SymbolResult[] {
|
|
7
|
+
const rows = db.all<{
|
|
8
|
+
start_line: number;
|
|
9
|
+
end_line: number;
|
|
10
|
+
sig: string | null;
|
|
11
|
+
symbol: string;
|
|
12
|
+
relative_path: string;
|
|
13
|
+
}>(
|
|
14
|
+
`SELECT
|
|
15
|
+
der.start_line,
|
|
16
|
+
der.end_line,
|
|
17
|
+
REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig,
|
|
18
|
+
gs.symbol,
|
|
19
|
+
d.relative_path
|
|
20
|
+
FROM defn_enclosing_ranges der
|
|
21
|
+
JOIN global_symbols gs ON der.symbol_id = gs.id
|
|
22
|
+
JOIN documents d ON der.document_id = d.id
|
|
23
|
+
WHERE d.relative_path LIKE ?
|
|
24
|
+
AND ${db.localSymbolPredicate}
|
|
25
|
+
${db.symbolNoise}
|
|
26
|
+
ORDER BY der.start_line`,
|
|
27
|
+
`%${filePattern}%`,
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
return rows
|
|
31
|
+
.filter((r) => !db.isIgnored(r.relative_path))
|
|
32
|
+
.map((r) => ({
|
|
33
|
+
startLine: r.start_line,
|
|
34
|
+
endLine: r.end_line,
|
|
35
|
+
symbol: r.symbol,
|
|
36
|
+
shortName: shortenSymbol(r.symbol),
|
|
37
|
+
signature: cleanSignature(r.sig),
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ScipDatabase } from '../db.js';
|
|
2
|
+
import type { SystemResult, SymbolResult } from '../types.js';
|
|
3
|
+
import { shortenSymbol } from '../symbol-parser.js';
|
|
4
|
+
import { cleanSignature } from './clean-signature.js';
|
|
5
|
+
|
|
6
|
+
/** Full system map for a module path: files, symbols, deps in/out */
|
|
7
|
+
export function system(db: ScipDatabase, modulePattern: string): SystemResult {
|
|
8
|
+
// Files in this module
|
|
9
|
+
const fileRows = db.all<{ relative_path: string }>(
|
|
10
|
+
`SELECT relative_path FROM documents
|
|
11
|
+
WHERE relative_path LIKE ?
|
|
12
|
+
ORDER BY relative_path`,
|
|
13
|
+
`%${modulePattern}%`,
|
|
14
|
+
);
|
|
15
|
+
const files = fileRows
|
|
16
|
+
.map((r) => r.relative_path)
|
|
17
|
+
.filter((p) => !db.isIgnored(p));
|
|
18
|
+
|
|
19
|
+
// Exported symbols
|
|
20
|
+
const symbolRows = db.all<{
|
|
21
|
+
start_line: number;
|
|
22
|
+
end_line: number;
|
|
23
|
+
symbol: string;
|
|
24
|
+
sig: string | null;
|
|
25
|
+
}>(
|
|
26
|
+
`SELECT der.start_line, der.end_line, gs.symbol,
|
|
27
|
+
REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
|
|
28
|
+
FROM defn_enclosing_ranges der
|
|
29
|
+
JOIN global_symbols gs ON der.symbol_id = gs.id
|
|
30
|
+
JOIN documents d ON der.document_id = d.id
|
|
31
|
+
WHERE d.relative_path LIKE ?
|
|
32
|
+
AND ${db.localSymbolPredicate}
|
|
33
|
+
${db.symbolNoise}
|
|
34
|
+
AND gs.documentation IS NOT NULL
|
|
35
|
+
ORDER BY d.relative_path, der.start_line`,
|
|
36
|
+
`%${modulePattern}%`,
|
|
37
|
+
);
|
|
38
|
+
const symbols: SymbolResult[] = symbolRows.map((r) => ({
|
|
39
|
+
startLine: r.start_line,
|
|
40
|
+
endLine: r.end_line,
|
|
41
|
+
symbol: r.symbol,
|
|
42
|
+
shortName: shortenSymbol(r.symbol),
|
|
43
|
+
signature: cleanSignature(r.sig),
|
|
44
|
+
}));
|
|
45
|
+
|
|
46
|
+
// Internal dependencies (what this module depends on)
|
|
47
|
+
const depRows = db.all<{ relative_path: string }>(
|
|
48
|
+
`SELECT DISTINCT d2.relative_path
|
|
49
|
+
FROM mentions m
|
|
50
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
51
|
+
JOIN documents d1 ON c.document_id = d1.id
|
|
52
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
53
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
54
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
55
|
+
WHERE d1.relative_path LIKE ?
|
|
56
|
+
AND d2.relative_path NOT LIKE ?
|
|
57
|
+
AND ${db.localSymbolPredicate}
|
|
58
|
+
ORDER BY d2.relative_path`,
|
|
59
|
+
`%${modulePattern}%`,
|
|
60
|
+
`%${modulePattern}%`,
|
|
61
|
+
);
|
|
62
|
+
const dependsOn = depRows
|
|
63
|
+
.map((r) => r.relative_path)
|
|
64
|
+
.filter((p) => !db.isIgnored(p));
|
|
65
|
+
|
|
66
|
+
// Reverse dependencies (who depends on this module)
|
|
67
|
+
const rdepRows = db.all<{ relative_path: string }>(
|
|
68
|
+
`SELECT DISTINCT d1.relative_path
|
|
69
|
+
FROM mentions m
|
|
70
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
71
|
+
JOIN documents d1 ON c.document_id = d1.id
|
|
72
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
73
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
74
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
75
|
+
WHERE d2.relative_path LIKE ?
|
|
76
|
+
AND d1.relative_path NOT LIKE ?
|
|
77
|
+
ORDER BY d1.relative_path`,
|
|
78
|
+
`%${modulePattern}%`,
|
|
79
|
+
`%${modulePattern}%`,
|
|
80
|
+
);
|
|
81
|
+
const dependedOnBy = rdepRows
|
|
82
|
+
.map((r) => r.relative_path)
|
|
83
|
+
.filter((p) => !db.isIgnored(p));
|
|
84
|
+
|
|
85
|
+
return { files, symbols, dependsOn, dependedOnBy };
|
|
86
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ScipDatabase } from '../db.js';
|
|
2
|
+
import { TEST_FILE_PATTERNS, testFileExclusionSql, testFileMatchSql } from '../query-support.js';
|
|
3
|
+
import type { TestCoverageResult } from '../types.js';
|
|
4
|
+
import { shortenSymbol } from '../symbol-parser.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check if a symbol is referenced by any test file.
|
|
8
|
+
* Reports which test files cover (reference) each matching symbol.
|
|
9
|
+
*/
|
|
10
|
+
export function testCoverage(
|
|
11
|
+
db: ScipDatabase,
|
|
12
|
+
symbolPattern: string,
|
|
13
|
+
): TestCoverageResult[] {
|
|
14
|
+
// Find matching symbols
|
|
15
|
+
const syms = db.all<{
|
|
16
|
+
id: number;
|
|
17
|
+
symbol: string;
|
|
18
|
+
relative_path: string;
|
|
19
|
+
}>(
|
|
20
|
+
`SELECT gs.id, gs.symbol, d.relative_path
|
|
21
|
+
FROM global_symbols gs
|
|
22
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
23
|
+
JOIN documents d ON der.document_id = d.id
|
|
24
|
+
WHERE gs.symbol LIKE ?
|
|
25
|
+
${db.pathExclusionsFor('d')}
|
|
26
|
+
${db.symbolNoiseFor('gs')}
|
|
27
|
+
ORDER BY d.relative_path`,
|
|
28
|
+
`%${symbolPattern}%`,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const testPatternSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);
|
|
32
|
+
|
|
33
|
+
return syms
|
|
34
|
+
.filter((s) => !db.isIgnored(s.relative_path))
|
|
35
|
+
.map((s) => {
|
|
36
|
+
// Find test files that reference this symbol
|
|
37
|
+
const testFiles = db.all<{ relative_path: string }>(
|
|
38
|
+
`SELECT DISTINCT ref_d.relative_path
|
|
39
|
+
FROM mentions m
|
|
40
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
41
|
+
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
42
|
+
WHERE m.symbol_id = ?
|
|
43
|
+
AND m.role = 0
|
|
44
|
+
AND (${testPatternSql})
|
|
45
|
+
ORDER BY ref_d.relative_path`,
|
|
46
|
+
s.id,
|
|
47
|
+
).map((r) => r.relative_path);
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
symbol: s.symbol,
|
|
51
|
+
shortName: shortenSymbol(s.symbol),
|
|
52
|
+
definedIn: s.relative_path,
|
|
53
|
+
testFiles,
|
|
54
|
+
covered: testFiles.length > 0,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Summary: what percentage of symbols in scope are referenced by test files?
|
|
61
|
+
*/
|
|
62
|
+
export function testCoverageSummary(
|
|
63
|
+
db: ScipDatabase,
|
|
64
|
+
opts: { scope?: string; minLoc?: number } = {},
|
|
65
|
+
): { total: number; covered: number; uncovered: number; percent: number } {
|
|
66
|
+
const { scope, minLoc = 3 } = opts;
|
|
67
|
+
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';
|
|
68
|
+
const testPatternSql = testFileExclusionSql('d');
|
|
69
|
+
|
|
70
|
+
const symbols = db.all<{ id: number }>(
|
|
71
|
+
`SELECT gs.id
|
|
72
|
+
FROM global_symbols gs
|
|
73
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
74
|
+
JOIN documents d ON der.document_id = d.id
|
|
75
|
+
WHERE 1 = 1
|
|
76
|
+
${db.pathExclusionsFor('d')}
|
|
77
|
+
AND ${testPatternSql}
|
|
78
|
+
${db.symbolNoiseFor('gs')}
|
|
79
|
+
AND gs.symbol NOT LIKE '%#%'
|
|
80
|
+
AND (der.end_line - der.start_line + 1) >= ?
|
|
81
|
+
${scopeFilter}`,
|
|
82
|
+
minLoc,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const testRefSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);
|
|
86
|
+
|
|
87
|
+
let covered = 0;
|
|
88
|
+
for (const s of symbols) {
|
|
89
|
+
const hasTest = db.get<{ c: number }>(
|
|
90
|
+
`SELECT COUNT(*) AS c FROM mentions m
|
|
91
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
92
|
+
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
93
|
+
WHERE m.symbol_id = ? AND m.role = 0 AND (${testRefSql})`,
|
|
94
|
+
s.id,
|
|
95
|
+
);
|
|
96
|
+
if (hasTest && hasTest.c > 0) covered++;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const total = symbols.length;
|
|
100
|
+
return {
|
|
101
|
+
total,
|
|
102
|
+
covered,
|
|
103
|
+
uncovered: total - covered,
|
|
104
|
+
percent: total > 0 ? Math.round((covered / total) * 100) : 0,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ScipDatabase } from '../db.js';
|
|
2
|
+
import type { TraceResult } from '../types.js';
|
|
3
|
+
import { cleanSignature } from './clean-signature.js';
|
|
4
|
+
|
|
5
|
+
export function trace(db: ScipDatabase, symbolPattern: string): TraceResult {
|
|
6
|
+
// Definitions
|
|
7
|
+
const defRows = db.all<{
|
|
8
|
+
relative_path: string;
|
|
9
|
+
start_line: number;
|
|
10
|
+
end_line: number;
|
|
11
|
+
sig: string | null;
|
|
12
|
+
}>(
|
|
13
|
+
`SELECT d.relative_path, der.start_line, der.end_line,
|
|
14
|
+
REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig
|
|
15
|
+
FROM global_symbols gs
|
|
16
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
17
|
+
JOIN documents d ON der.document_id = d.id
|
|
18
|
+
WHERE gs.symbol LIKE ?
|
|
19
|
+
AND ${db.localSymbolPredicate}
|
|
20
|
+
${db.symbolNoise}
|
|
21
|
+
ORDER BY d.relative_path, der.start_line
|
|
22
|
+
LIMIT 10`,
|
|
23
|
+
`%${symbolPattern}%`,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
const definitions = defRows
|
|
27
|
+
.filter((r) => !db.isIgnored(r.relative_path))
|
|
28
|
+
.map((r) => ({
|
|
29
|
+
relativePath: r.relative_path,
|
|
30
|
+
startLine: r.start_line,
|
|
31
|
+
endLine: r.end_line,
|
|
32
|
+
signature: cleanSignature(r.sig),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
// References
|
|
36
|
+
const refRows = db.all<{ relative_path: string }>(
|
|
37
|
+
`SELECT DISTINCT d.relative_path
|
|
38
|
+
FROM mentions m
|
|
39
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
40
|
+
JOIN documents d ON c.document_id = d.id
|
|
41
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
42
|
+
WHERE gs.symbol LIKE ?
|
|
43
|
+
AND ${db.localSymbolPredicate}
|
|
44
|
+
${db.symbolNoise}
|
|
45
|
+
AND m.role = 0
|
|
46
|
+
ORDER BY d.relative_path`,
|
|
47
|
+
`%${symbolPattern}%`,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const referencedBy = refRows
|
|
51
|
+
.map((r) => r.relative_path)
|
|
52
|
+
.filter((p) => !db.isIgnored(p));
|
|
53
|
+
|
|
54
|
+
return { definitions, referencedBy };
|
|
55
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { ScipDatabase } from '../db.js';
|
|
2
|
+
import { testFileExclusionSql } from '../query-support.js';
|
|
3
|
+
import type { WrapperCandidate } from '../types.js';
|
|
4
|
+
import { shortenSymbol } from '../symbol-parser.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Find wrapper candidates: symbols called by only one other symbol.
|
|
8
|
+
*
|
|
9
|
+
* These are premature abstractions that add indirection without
|
|
10
|
+
* providing reuse. A function with fan-in = 1 whose sole caller
|
|
11
|
+
* is widely used is a strong signal of unnecessary wrapping.
|
|
12
|
+
*/
|
|
13
|
+
export function wrapperCandidates(
|
|
14
|
+
db: ScipDatabase,
|
|
15
|
+
opts?: { scope?: string; maxLoc?: number; limit?: number },
|
|
16
|
+
): WrapperCandidate[] {
|
|
17
|
+
const { scope, maxLoc = 15, limit = 30 } = opts ?? {};
|
|
18
|
+
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';
|
|
19
|
+
|
|
20
|
+
// Find all symbols with exactly 1 cross-file consumer (fan-in = 1),
|
|
21
|
+
// along with who that single caller is and the caller's own fan-in.
|
|
22
|
+
const rows = db.all<{
|
|
23
|
+
symbol: string;
|
|
24
|
+
file: string;
|
|
25
|
+
start_line: number;
|
|
26
|
+
end_line: number;
|
|
27
|
+
loc: number;
|
|
28
|
+
caller_symbol: string;
|
|
29
|
+
caller_fan_in: number;
|
|
30
|
+
}>(
|
|
31
|
+
`SELECT * FROM (
|
|
32
|
+
SELECT
|
|
33
|
+
gs.symbol,
|
|
34
|
+
d.relative_path AS file,
|
|
35
|
+
der.start_line,
|
|
36
|
+
der.end_line,
|
|
37
|
+
(der.end_line - der.start_line + 1) AS loc,
|
|
38
|
+
-- The single caller: the symbol whose definition range contains
|
|
39
|
+
-- the chunk that references our target
|
|
40
|
+
(SELECT caller_gs.symbol
|
|
41
|
+
FROM mentions ref_m
|
|
42
|
+
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
43
|
+
JOIN defn_enclosing_ranges caller_der
|
|
44
|
+
ON caller_der.document_id = ref_c.document_id
|
|
45
|
+
AND ref_c.start_line >= caller_der.start_line
|
|
46
|
+
AND ref_c.end_line <= caller_der.end_line
|
|
47
|
+
JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id
|
|
48
|
+
WHERE ref_m.symbol_id = gs.id
|
|
49
|
+
AND ref_m.role = 0
|
|
50
|
+
AND ref_c.document_id != der.document_id
|
|
51
|
+
LIMIT 1
|
|
52
|
+
) AS caller_symbol,
|
|
53
|
+
-- Fan-in of that single caller
|
|
54
|
+
(SELECT COUNT(DISTINCT caller_ref_c.document_id)
|
|
55
|
+
FROM mentions caller_ref_m
|
|
56
|
+
JOIN chunks caller_ref_c ON caller_ref_m.chunk_id = caller_ref_c.id
|
|
57
|
+
WHERE caller_ref_m.symbol_id = (
|
|
58
|
+
SELECT caller_der2.symbol_id
|
|
59
|
+
FROM mentions ref_m2
|
|
60
|
+
JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id
|
|
61
|
+
JOIN defn_enclosing_ranges caller_der2
|
|
62
|
+
ON caller_der2.document_id = ref_c2.document_id
|
|
63
|
+
AND ref_c2.start_line >= caller_der2.start_line
|
|
64
|
+
AND ref_c2.end_line <= caller_der2.end_line
|
|
65
|
+
WHERE ref_m2.symbol_id = gs.id
|
|
66
|
+
AND ref_m2.role = 0
|
|
67
|
+
AND ref_c2.document_id != der.document_id
|
|
68
|
+
LIMIT 1
|
|
69
|
+
)
|
|
70
|
+
AND caller_ref_m.role = 0
|
|
71
|
+
) AS caller_fan_in
|
|
72
|
+
FROM global_symbols gs
|
|
73
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
74
|
+
JOIN documents d ON der.document_id = d.id
|
|
75
|
+
WHERE 1 = 1
|
|
76
|
+
${db.pathExclusionsFor('d')}
|
|
77
|
+
AND ${testFileExclusionSql('d')}
|
|
78
|
+
${db.symbolNoiseFor('gs')}
|
|
79
|
+
-- Only functions/terms, not type definitions (types with # are not wrappers)
|
|
80
|
+
AND gs.symbol NOT LIKE '%#'
|
|
81
|
+
AND (der.end_line - der.start_line + 1) <= ?
|
|
82
|
+
AND (der.end_line - der.start_line + 1) >= 2
|
|
83
|
+
${scopeFilter}
|
|
84
|
+
-- Exactly 1 cross-file consumer
|
|
85
|
+
AND (
|
|
86
|
+
SELECT COUNT(DISTINCT ref_c.document_id)
|
|
87
|
+
FROM mentions ref_m
|
|
88
|
+
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
89
|
+
WHERE ref_m.symbol_id = gs.id
|
|
90
|
+
AND ref_m.role = 0
|
|
91
|
+
AND ref_c.document_id != der.document_id
|
|
92
|
+
) = 1
|
|
93
|
+
) WHERE caller_symbol IS NOT NULL AND caller_fan_in > 3
|
|
94
|
+
ORDER BY caller_fan_in DESC, loc DESC
|
|
95
|
+
LIMIT ?`,
|
|
96
|
+
maxLoc, limit,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
return rows
|
|
100
|
+
.filter((r) => !db.isIgnored(r.file))
|
|
101
|
+
.map((r) => ({
|
|
102
|
+
symbol: r.symbol,
|
|
103
|
+
shortName: shortenSymbol(r.symbol),
|
|
104
|
+
file: r.file,
|
|
105
|
+
startLine: r.start_line,
|
|
106
|
+
endLine: r.end_line,
|
|
107
|
+
loc: r.loc,
|
|
108
|
+
singleCaller: r.caller_symbol,
|
|
109
|
+
singleCallerShort: shortenSymbol(r.caller_symbol),
|
|
110
|
+
callerFanIn: r.caller_fan_in,
|
|
111
|
+
}));
|
|
112
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { ScipDatabase } from './db.js';
|
|
2
|
+
|
|
3
|
+
export interface SymbolLocation {
|
|
4
|
+
documentId: number;
|
|
5
|
+
startLine: number;
|
|
6
|
+
endLine: number;
|
|
7
|
+
symbolId: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface SymbolMatch extends SymbolLocation {
|
|
11
|
+
symbol: string;
|
|
12
|
+
relativePath: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface CalleeRow {
|
|
16
|
+
symbol: string;
|
|
17
|
+
file: string;
|
|
18
|
+
chunkId: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const TEST_FILE_PATTERNS = [
|
|
22
|
+
'%/__tests__/%',
|
|
23
|
+
'%.test.%',
|
|
24
|
+
'%.spec.%',
|
|
25
|
+
'%/test/%',
|
|
26
|
+
'%/tests/%',
|
|
27
|
+
'%_test.%',
|
|
28
|
+
'%_spec.%',
|
|
29
|
+
'%/test_%.%',
|
|
30
|
+
'%/spec_%.%',
|
|
31
|
+
] as const;
|
|
32
|
+
|
|
33
|
+
export const TEST_SUPPORT_PATH_PATTERNS = [
|
|
34
|
+
'%/test-utils/%',
|
|
35
|
+
] as const;
|
|
36
|
+
|
|
37
|
+
export function testFileMatchSql(
|
|
38
|
+
alias: string,
|
|
39
|
+
patterns: readonly string[] = TEST_FILE_PATTERNS,
|
|
40
|
+
): string {
|
|
41
|
+
return `(${patterns.map((pattern) => `${alias}.relative_path LIKE '${pattern}'`).join(' OR ')})`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function testFileExclusionSql(
|
|
45
|
+
alias: string,
|
|
46
|
+
extraPatterns: readonly string[] = [],
|
|
47
|
+
): string {
|
|
48
|
+
const patterns = uniquePatterns([...TEST_FILE_PATTERNS, ...extraPatterns]);
|
|
49
|
+
return patterns
|
|
50
|
+
.map((pattern) => `${alias}.relative_path NOT LIKE '${pattern}'`)
|
|
51
|
+
.join('\n AND ');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function buildFileDepGraph(
|
|
55
|
+
db: ScipDatabase,
|
|
56
|
+
scope?: string,
|
|
57
|
+
): Map<string, Set<string>> {
|
|
58
|
+
const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : '';
|
|
59
|
+
|
|
60
|
+
const edges = db.all<{ from_file: string; to_file: string }>(
|
|
61
|
+
`SELECT DISTINCT
|
|
62
|
+
d1.relative_path AS from_file,
|
|
63
|
+
d2.relative_path AS to_file
|
|
64
|
+
FROM mentions m
|
|
65
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
66
|
+
JOIN documents d1 ON c.document_id = d1.id
|
|
67
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
68
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
69
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
70
|
+
WHERE d1.id != d2.id
|
|
71
|
+
AND m.role = 0
|
|
72
|
+
${db.pathExclusionsFor('d1', 'd2')}
|
|
73
|
+
${scopeFilter}`,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
const graph = new Map<string, Set<string>>();
|
|
77
|
+
for (const edge of edges) {
|
|
78
|
+
if (db.isIgnored(edge.from_file) || db.isIgnored(edge.to_file)) continue;
|
|
79
|
+
if (!graph.has(edge.from_file)) graph.set(edge.from_file, new Set());
|
|
80
|
+
graph.get(edge.from_file)!.add(edge.to_file);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return graph;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function findFirstSymbolMatch(
|
|
87
|
+
db: ScipDatabase,
|
|
88
|
+
symbolPattern: string,
|
|
89
|
+
): SymbolMatch | null {
|
|
90
|
+
// Handle file:line-line syntax (e.g., "src/foo.ts:10-50")
|
|
91
|
+
const fileLineMatch = symbolPattern.match(/^(.+):(\d+)-(\d+)$/);
|
|
92
|
+
if (fileLineMatch) {
|
|
93
|
+
const [, filePath, startStr, endStr] = fileLineMatch;
|
|
94
|
+
const row = db.get<{
|
|
95
|
+
id: number;
|
|
96
|
+
symbol: string;
|
|
97
|
+
document_id: number;
|
|
98
|
+
start_line: number;
|
|
99
|
+
end_line: number;
|
|
100
|
+
relative_path: string;
|
|
101
|
+
}>(
|
|
102
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
103
|
+
FROM global_symbols gs
|
|
104
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
105
|
+
JOIN documents d ON der.document_id = d.id
|
|
106
|
+
WHERE d.relative_path LIKE ?
|
|
107
|
+
AND der.start_line <= ? AND der.end_line >= ?
|
|
108
|
+
${db.pathExclusionsFor('d')}
|
|
109
|
+
ORDER BY (der.end_line - der.start_line) ASC
|
|
110
|
+
LIMIT 1`,
|
|
111
|
+
`%${filePath}%`, parseInt(startStr!, 10), parseInt(endStr!, 10),
|
|
112
|
+
);
|
|
113
|
+
if (row && !db.isIgnored(row.relative_path)) {
|
|
114
|
+
return {
|
|
115
|
+
symbolId: row.id,
|
|
116
|
+
symbol: row.symbol,
|
|
117
|
+
documentId: row.document_id,
|
|
118
|
+
startLine: row.start_line,
|
|
119
|
+
endLine: row.end_line,
|
|
120
|
+
relativePath: row.relative_path,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Strip parentheses from the pattern to avoid shell escaping issues.
|
|
126
|
+
// Agents often pass "functionName()" — strip the () for matching.
|
|
127
|
+
const cleaned = symbolPattern.replace(/\(\)$/, '').replace(/\(.*$/, '');
|
|
128
|
+
|
|
129
|
+
// Try exact-ish match first (with noise filter), then fallback without noise filter.
|
|
130
|
+
// The noise filter excludes %().(% which can incorrectly match some symbols.
|
|
131
|
+
for (const useNoiseFilter of [true, false]) {
|
|
132
|
+
const noiseClause = useNoiseFilter ? db.symbolNoiseFor('gs') : '';
|
|
133
|
+
const row = db.get<{
|
|
134
|
+
id: number;
|
|
135
|
+
symbol: string;
|
|
136
|
+
document_id: number;
|
|
137
|
+
start_line: number;
|
|
138
|
+
end_line: number;
|
|
139
|
+
relative_path: string;
|
|
140
|
+
}>(
|
|
141
|
+
`SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path
|
|
142
|
+
FROM global_symbols gs
|
|
143
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
144
|
+
JOIN documents d ON der.document_id = d.id
|
|
145
|
+
WHERE gs.symbol LIKE ?
|
|
146
|
+
${db.pathExclusionsFor('d')}
|
|
147
|
+
${noiseClause}
|
|
148
|
+
ORDER BY (der.end_line - der.start_line) DESC
|
|
149
|
+
LIMIT 1`,
|
|
150
|
+
`%${cleaned}%`,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (row && !db.isIgnored(row.relative_path)) {
|
|
154
|
+
return {
|
|
155
|
+
symbolId: row.id,
|
|
156
|
+
symbol: row.symbol,
|
|
157
|
+
documentId: row.document_id,
|
|
158
|
+
startLine: row.start_line,
|
|
159
|
+
endLine: row.end_line,
|
|
160
|
+
relativePath: row.relative_path,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getCalleeRowsForSymbol(
|
|
169
|
+
db: ScipDatabase,
|
|
170
|
+
symbol: SymbolLocation,
|
|
171
|
+
opts: { limit?: number } = {},
|
|
172
|
+
): CalleeRow[] {
|
|
173
|
+
const rows = db.all<{
|
|
174
|
+
symbol: string;
|
|
175
|
+
file: string;
|
|
176
|
+
chunk_id: number;
|
|
177
|
+
}>(
|
|
178
|
+
`SELECT DISTINCT
|
|
179
|
+
callee_gs.symbol AS symbol,
|
|
180
|
+
callee_d.relative_path AS file,
|
|
181
|
+
c.id AS chunk_id
|
|
182
|
+
FROM mentions m
|
|
183
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
184
|
+
JOIN global_symbols callee_gs ON m.symbol_id = callee_gs.id
|
|
185
|
+
JOIN defn_enclosing_ranges callee_der ON callee_gs.id = callee_der.symbol_id
|
|
186
|
+
JOIN documents callee_d ON callee_der.document_id = callee_d.id
|
|
187
|
+
WHERE c.document_id = ?
|
|
188
|
+
AND c.start_line >= ?
|
|
189
|
+
AND c.end_line <= ?
|
|
190
|
+
AND m.role = 0
|
|
191
|
+
AND callee_gs.id != ?
|
|
192
|
+
${db.symbolNoiseFor('callee_gs')}
|
|
193
|
+
${db.pathExclusionsFor('callee_d')}
|
|
194
|
+
ORDER BY callee_d.relative_path
|
|
195
|
+
${opts.limit ? 'LIMIT ?' : ''}`,
|
|
196
|
+
...calleeQueryParams(symbol, opts.limit),
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
return rows.filter((row) => !db.isIgnored(row.file)).map((row) => ({
|
|
200
|
+
symbol: row.symbol,
|
|
201
|
+
file: row.file,
|
|
202
|
+
chunkId: row.chunk_id,
|
|
203
|
+
}));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function calleeQueryParams(
|
|
207
|
+
symbol: SymbolLocation,
|
|
208
|
+
limit?: number,
|
|
209
|
+
): number[] {
|
|
210
|
+
const params = [
|
|
211
|
+
symbol.documentId,
|
|
212
|
+
symbol.startLine,
|
|
213
|
+
symbol.endLine,
|
|
214
|
+
symbol.symbolId,
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
if (typeof limit === 'number') {
|
|
218
|
+
params.push(limit);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return params;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function uniquePatterns(patterns: readonly string[]): string[] {
|
|
225
|
+
return [...new Set(patterns)];
|
|
226
|
+
}
|