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,55 @@
|
|
|
1
|
+
import {
|
|
2
|
+
shortenSymbol
|
|
3
|
+
} from "./chunk-QOV2R2WT.js";
|
|
4
|
+
|
|
5
|
+
// src/queries/bottlenecks.ts
|
|
6
|
+
function bottlenecks(db, opts = {}) {
|
|
7
|
+
const { limit = 20, scope, minFanIn = 2, minFanOut = 2 } = opts;
|
|
8
|
+
const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : "";
|
|
9
|
+
const rows = db.all(
|
|
10
|
+
`SELECT * FROM (
|
|
11
|
+
SELECT
|
|
12
|
+
gs.symbol,
|
|
13
|
+
def_d.relative_path AS defined_in,
|
|
14
|
+
(SELECT COUNT(DISTINCT ref_c.document_id)
|
|
15
|
+
FROM mentions ref_m
|
|
16
|
+
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
17
|
+
WHERE ref_m.symbol_id = gs.id AND ref_m.role = 0
|
|
18
|
+
) AS fan_in,
|
|
19
|
+
(SELECT COUNT(DISTINCT ref_gs.id)
|
|
20
|
+
FROM mentions ref_m
|
|
21
|
+
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
22
|
+
JOIN global_symbols ref_gs ON ref_m.symbol_id = ref_gs.id
|
|
23
|
+
JOIN defn_enclosing_ranges ref_der ON ref_gs.id = ref_der.symbol_id
|
|
24
|
+
WHERE ref_c.document_id = def_d.id
|
|
25
|
+
AND ref_m.role = 0
|
|
26
|
+
AND ref_der.document_id != def_d.id
|
|
27
|
+
) AS fan_out
|
|
28
|
+
FROM global_symbols gs
|
|
29
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
30
|
+
JOIN documents def_d ON der.document_id = def_d.id
|
|
31
|
+
WHERE 1 = 1
|
|
32
|
+
${db.pathExclusionsFor("def_d")}
|
|
33
|
+
${db.symbolNoiseFor("gs")}
|
|
34
|
+
${scopeFilter}
|
|
35
|
+
) WHERE fan_in >= ? AND fan_out >= ?
|
|
36
|
+
ORDER BY (fan_in * fan_out) DESC
|
|
37
|
+
LIMIT ?`,
|
|
38
|
+
minFanIn,
|
|
39
|
+
minFanOut,
|
|
40
|
+
limit
|
|
41
|
+
);
|
|
42
|
+
return rows.filter((r) => !db.isIgnored(r.defined_in)).map((r) => ({
|
|
43
|
+
symbol: r.symbol,
|
|
44
|
+
shortName: shortenSymbol(r.symbol),
|
|
45
|
+
fanIn: r.fan_in,
|
|
46
|
+
fanOut: r.fan_out,
|
|
47
|
+
score: r.fan_in * r.fan_out,
|
|
48
|
+
definedIn: r.defined_in
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export {
|
|
53
|
+
bottlenecks
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=chunk-2QZ23IBN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/bottlenecks.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { BottleneckResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find coupling hubs: symbols with both high fan-in (many consumers)\n * AND high fan-out (references many other symbols).\n *\n * These are the most dangerous symbols to change — they sit at the\n * intersection of many dependency paths. Score = fanIn * fanOut.\n */\nexport function bottlenecks(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string; minFanIn?: number; minFanOut?: number } = {},\n): BottleneckResult[] {\n const { limit = 20, scope, minFanIn = 2, minFanOut = 2 } = opts;\n const scopeFilter = scope ? `AND def_d.relative_path LIKE '%${scope}%'` : '';\n\n // Use a wrapping query to filter on computed columns\n const rows = db.all<{\n symbol: string;\n defined_in: string;\n fan_in: number;\n fan_out: number;\n }>(\n `SELECT * FROM (\n SELECT\n gs.symbol,\n def_d.relative_path AS defined_in,\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 = 0\n ) AS fan_in,\n (SELECT COUNT(DISTINCT ref_gs.id)\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN global_symbols ref_gs ON ref_m.symbol_id = ref_gs.id\n JOIN defn_enclosing_ranges ref_der ON ref_gs.id = ref_der.symbol_id\n WHERE ref_c.document_id = def_d.id\n AND ref_m.role = 0\n AND ref_der.document_id != def_d.id\n ) AS fan_out\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents def_d ON der.document_id = def_d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('def_d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n ) WHERE fan_in >= ? AND fan_out >= ?\n ORDER BY (fan_in * fan_out) DESC\n LIMIT ?`,\n minFanIn, minFanOut, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.defined_in))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n fanIn: r.fan_in,\n fanOut: r.fan_out,\n score: r.fan_in * r.fan_out,\n definedIn: r.defined_in,\n }));\n}\n"],"mappings":";;;;;AAWO,SAAS,YACd,IACA,OAAkF,CAAC,GAC/D;AACpB,QAAM,EAAE,QAAQ,IAAI,OAAO,WAAW,GAAG,YAAY,EAAE,IAAI;AAC3D,QAAM,cAAc,QAAQ,kCAAkC,KAAK,OAAO;AAG1E,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,UAsBM,GAAG,kBAAkB,OAAO,CAAC;AAAA,UAC7B,GAAG,eAAe,IAAI,CAAC;AAAA,UACvB,WAAW;AAAA;AAAA;AAAA;AAAA,IAIjB;AAAA,IAAU;AAAA,IAAW;AAAA,EACvB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,UAAU,CAAC,EACzC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,OAAO,EAAE;AAAA,IACT,QAAQ,EAAE;AAAA,IACV,OAAO,EAAE,SAAS,EAAE;AAAA,IACpB,WAAW,EAAE;AAAA,EACf,EAAE;AACN;","names":[]}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildFileDepGraph
|
|
3
|
+
} from "./chunk-FUHJCHS4.js";
|
|
4
|
+
|
|
5
|
+
// src/queries/drift.ts
|
|
6
|
+
import path from "path";
|
|
7
|
+
function drift(db, opts) {
|
|
8
|
+
const { scope } = opts ?? {};
|
|
9
|
+
const depGraph = buildFileDepGraph(db, scope);
|
|
10
|
+
const symbolRefs = buildSymbolRefGraph(db, scope);
|
|
11
|
+
const results = [];
|
|
12
|
+
for (const [file, deps] of depGraph) {
|
|
13
|
+
if (isStructuralRole(path.basename(file))) continue;
|
|
14
|
+
const referencedFiles = symbolRefs.get(file) ?? /* @__PURE__ */ new Set();
|
|
15
|
+
for (const dep of deps) {
|
|
16
|
+
if (!referencedFiles.has(dep)) {
|
|
17
|
+
if (isLikelyTypeOnlyDep(dep)) continue;
|
|
18
|
+
results.push({
|
|
19
|
+
file,
|
|
20
|
+
kind: "unused-import",
|
|
21
|
+
description: `Depends on ${dep} but references none of its symbols`,
|
|
22
|
+
dep
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
const layerRules = inferLayerRules(depGraph);
|
|
28
|
+
for (const [file, deps] of depGraph) {
|
|
29
|
+
if (isStructuralRole(path.basename(file))) continue;
|
|
30
|
+
const fileLayer = getTopDir(file);
|
|
31
|
+
for (const dep of deps) {
|
|
32
|
+
const depLayer = getTopDir(dep);
|
|
33
|
+
if (fileLayer === depLayer) continue;
|
|
34
|
+
const violation = layerRules.get(`${fileLayer}->${depLayer}`);
|
|
35
|
+
if (violation === "violation") {
|
|
36
|
+
results.push({
|
|
37
|
+
file,
|
|
38
|
+
kind: "layer-violation",
|
|
39
|
+
description: `Imports from ${depLayer}/ (${dep}) \u2014 may cross architectural boundary`,
|
|
40
|
+
dep,
|
|
41
|
+
detail: `${fileLayer}/ should not depend on ${depLayer}/`
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const dirToFiles = /* @__PURE__ */ new Map();
|
|
47
|
+
for (const file of depGraph.keys()) {
|
|
48
|
+
const dir = path.dirname(file);
|
|
49
|
+
if (!dirToFiles.has(dir)) dirToFiles.set(dir, []);
|
|
50
|
+
dirToFiles.get(dir).push(file);
|
|
51
|
+
}
|
|
52
|
+
for (const [dir, files] of dirToFiles) {
|
|
53
|
+
if (files.length < 3) continue;
|
|
54
|
+
const depFreq = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const file of files) {
|
|
56
|
+
if (isStructuralRole(path.basename(file))) continue;
|
|
57
|
+
for (const dep of depGraph.get(file) ?? []) {
|
|
58
|
+
depFreq.set(dep, (depFreq.get(dep) ?? 0) + 1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
for (const file of files) {
|
|
62
|
+
if (isStructuralRole(path.basename(file))) continue;
|
|
63
|
+
for (const dep of depGraph.get(file) ?? []) {
|
|
64
|
+
if ((depFreq.get(dep) ?? 0) === 1) {
|
|
65
|
+
if (path.dirname(dep) === dir) continue;
|
|
66
|
+
results.push({
|
|
67
|
+
file,
|
|
68
|
+
kind: "pattern-deviation",
|
|
69
|
+
description: `Only file in ${dir}/ that depends on ${dep}`,
|
|
70
|
+
dep
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
results,
|
|
78
|
+
unusedImports: results.filter((r) => r.kind === "unused-import").length,
|
|
79
|
+
layerViolations: results.filter((r) => r.kind === "layer-violation").length,
|
|
80
|
+
patternDeviations: results.filter((r) => r.kind === "pattern-deviation").length
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function buildSymbolRefGraph(db, scope) {
|
|
84
|
+
const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : "";
|
|
85
|
+
const rows = db.all(
|
|
86
|
+
`SELECT DISTINCT d1.relative_path AS from_file, d2.relative_path AS to_file
|
|
87
|
+
FROM mentions m
|
|
88
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
89
|
+
JOIN documents d1 ON c.document_id = d1.id
|
|
90
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
91
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
92
|
+
JOIN documents d2 ON der.document_id = d2.id
|
|
93
|
+
WHERE d1.id != d2.id
|
|
94
|
+
AND m.role = 0
|
|
95
|
+
${db.pathExclusionsFor("d1", "d2")}
|
|
96
|
+
${scopeFilter}`
|
|
97
|
+
);
|
|
98
|
+
const graph = /* @__PURE__ */ new Map();
|
|
99
|
+
for (const r of rows) {
|
|
100
|
+
if (db.isIgnored(r.from_file) || db.isIgnored(r.to_file)) continue;
|
|
101
|
+
if (!graph.has(r.from_file)) graph.set(r.from_file, /* @__PURE__ */ new Set());
|
|
102
|
+
graph.get(r.from_file).add(r.to_file);
|
|
103
|
+
}
|
|
104
|
+
return graph;
|
|
105
|
+
}
|
|
106
|
+
function inferLayerRules(depGraph) {
|
|
107
|
+
const layerEdges = /* @__PURE__ */ new Map();
|
|
108
|
+
const layerSet = /* @__PURE__ */ new Set();
|
|
109
|
+
for (const [file, deps] of depGraph) {
|
|
110
|
+
const fromLayer = getTopDir(file);
|
|
111
|
+
layerSet.add(fromLayer);
|
|
112
|
+
for (const dep of deps) {
|
|
113
|
+
const toLayer = getTopDir(dep);
|
|
114
|
+
if (fromLayer === toLayer) continue;
|
|
115
|
+
layerSet.add(toLayer);
|
|
116
|
+
const key = `${fromLayer}->${toLayer}`;
|
|
117
|
+
layerEdges.set(key, (layerEdges.get(key) ?? 0) + 1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const rules = /* @__PURE__ */ new Map();
|
|
121
|
+
for (const [edge, count] of layerEdges) {
|
|
122
|
+
rules.set(edge, count <= 2 ? "violation" : "ok");
|
|
123
|
+
}
|
|
124
|
+
return rules;
|
|
125
|
+
}
|
|
126
|
+
function getTopDir(filePath) {
|
|
127
|
+
const parts = filePath.split("/");
|
|
128
|
+
return parts[0] ?? filePath;
|
|
129
|
+
}
|
|
130
|
+
function isLikelyTypeOnlyDep(dep) {
|
|
131
|
+
return dep.includes("types") || dep.endsWith(".d.ts");
|
|
132
|
+
}
|
|
133
|
+
function isStructuralRole(basename) {
|
|
134
|
+
if (basename === "index.ts" || basename === "index.js") return true;
|
|
135
|
+
if (basename === "cli.ts" || basename === "main.ts" || basename === "main.rs") return true;
|
|
136
|
+
if (basename.includes("worker.") || basename.includes("postinstall.")) return true;
|
|
137
|
+
if (basename === "health.ts" || basename === "health.js") return true;
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export {
|
|
142
|
+
drift
|
|
143
|
+
};
|
|
144
|
+
//# sourceMappingURL=chunk-36OMT7ZJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/drift.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { DriftResult, DriftSummary } from '../types.js';\n\n/**\n * Detect structural drift using the reference graph, not just import patterns.\n *\n * Three types of drift, each detecting a real problem:\n *\n * 1. **Unused imports** — file depends on a module but never references\n * any of its symbols. Dead dependency, safe to remove.\n *\n * 2. **Layer violations** — file imports from a directory it shouldn't\n * based on the project's directory structure (e.g., a query importing\n * from reindex, a helper importing from CLI). Architectural decay.\n *\n * 3. **Pattern deviations** — file imports something no sibling does,\n * suggesting it's reaching outside its expected scope. Only flagged\n * when the file is the ONLY one in its directory with that dep.\n */\nexport function drift(\n db: ScipDatabase,\n opts?: { scope?: string; minDeviation?: number },\n): DriftSummary {\n const { scope } = opts ?? {};\n\n // Build file dep graph (which files depend on which)\n const depGraph = buildFileDepGraph(db, scope);\n\n // Build symbol-level reference graph: for each file, which other files'\n // symbols does it actually reference?\n const symbolRefs = buildSymbolRefGraph(db, scope);\n\n const results: DriftResult[] = [];\n\n // ── Angle 1: Unused imports ──────────────────────────────\n // File depends on module B (via dep graph) but never references\n // any symbol defined in B (via symbol ref graph).\n for (const [file, deps] of depGraph) {\n if (isStructuralRole(path.basename(file))) continue;\n\n const referencedFiles = symbolRefs.get(file) ?? new Set<string>();\n\n for (const dep of deps) {\n if (!referencedFiles.has(dep)) {\n // This file \"depends on\" dep but never references its symbols.\n // This can happen when the dep is imported for types only\n // (which don't appear in the mention graph). Skip type-heavy deps.\n if (isLikelyTypeOnlyDep(dep)) continue;\n\n results.push({\n file,\n kind: 'unused-import',\n description: `Depends on ${dep} but references none of its symbols`,\n dep,\n });\n }\n }\n }\n\n // ── Angle 2: Layer violations ────────────────────────────\n // Detect when a file imports from a directory that represents\n // a different architectural layer. We infer layers from the\n // directory structure: files in the same top-level dir are peers,\n // files in different top-level dirs crossing inward is a violation.\n const layerRules = inferLayerRules(depGraph);\n\n for (const [file, deps] of depGraph) {\n if (isStructuralRole(path.basename(file))) continue;\n\n const fileLayer = getTopDir(file);\n for (const dep of deps) {\n const depLayer = getTopDir(dep);\n if (fileLayer === depLayer) continue; // same layer, fine\n\n const violation = layerRules.get(`${fileLayer}->${depLayer}`);\n if (violation === 'violation') {\n results.push({\n file,\n kind: 'layer-violation',\n description: `Imports from ${depLayer}/ (${dep}) — may cross architectural boundary`,\n dep,\n detail: `${fileLayer}/ should not depend on ${depLayer}/`,\n });\n }\n }\n }\n\n // ── Angle 3: Unique deps (pattern deviation) ─────────────\n // If a file is the ONLY one in its directory that depends on a\n // particular module, that dependency is unusual and worth flagging.\n const dirToFiles = new Map<string, string[]>();\n for (const file of depGraph.keys()) {\n const dir = path.dirname(file);\n if (!dirToFiles.has(dir)) dirToFiles.set(dir, []);\n dirToFiles.get(dir)!.push(file);\n }\n\n for (const [dir, files] of dirToFiles) {\n if (files.length < 3) continue;\n\n // Count dep frequency across siblings\n const depFreq = new Map<string, number>();\n for (const file of files) {\n if (isStructuralRole(path.basename(file))) continue;\n for (const dep of depGraph.get(file) ?? []) {\n depFreq.set(dep, (depFreq.get(dep) ?? 0) + 1);\n }\n }\n\n for (const file of files) {\n if (isStructuralRole(path.basename(file))) continue;\n for (const dep of depGraph.get(file) ?? []) {\n if ((depFreq.get(dep) ?? 0) === 1) {\n // This file is the only one in its dir that depends on this module\n // Skip if dep is in the same directory (sibling imports are normal)\n if (path.dirname(dep) === dir) continue;\n\n results.push({\n file,\n kind: 'pattern-deviation',\n description: `Only file in ${dir}/ that depends on ${dep}`,\n dep,\n });\n }\n }\n }\n }\n\n return {\n results,\n unusedImports: results.filter((r) => r.kind === 'unused-import').length,\n layerViolations: results.filter((r) => r.kind === 'layer-violation').length,\n patternDeviations: results.filter((r) => r.kind === 'pattern-deviation').length,\n };\n}\n\n// ── Helpers ────────────────────────────────────────────────\n\n/**\n * Build a map of file → set of files whose symbols it references.\n * This is more precise than the dep graph because it uses actual\n * symbol mentions, not just import statements.\n */\nfunction buildSymbolRefGraph(\n db: ScipDatabase,\n scope?: string,\n): Map<string, Set<string>> {\n const scopeFilter = scope ? `AND d1.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{ from_file: string; to_file: string }>(\n `SELECT DISTINCT d1.relative_path AS from_file, 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 = 0\n ${db.pathExclusionsFor('d1', 'd2')}\n ${scopeFilter}`,\n );\n\n const graph = new Map<string, Set<string>>();\n for (const r of rows) {\n if (db.isIgnored(r.from_file) || db.isIgnored(r.to_file)) continue;\n if (!graph.has(r.from_file)) graph.set(r.from_file, new Set());\n graph.get(r.from_file)!.add(r.to_file);\n }\n return graph;\n}\n\n/**\n * Infer layer boundary rules from the dependency graph.\n * If directory A never depends on directory B across the entire codebase,\n * then a new A→B dependency is a violation.\n */\nfunction inferLayerRules(\n depGraph: Map<string, Set<string>>,\n): Map<string, 'ok' | 'violation'> {\n const layerEdges = new Map<string, number>();\n const layerSet = new Set<string>();\n\n for (const [file, deps] of depGraph) {\n const fromLayer = getTopDir(file);\n layerSet.add(fromLayer);\n for (const dep of deps) {\n const toLayer = getTopDir(dep);\n if (fromLayer === toLayer) continue;\n layerSet.add(toLayer);\n const key = `${fromLayer}->${toLayer}`;\n layerEdges.set(key, (layerEdges.get(key) ?? 0) + 1);\n }\n }\n\n // An edge that appears only 1-2 times across the whole codebase\n // is likely a violation (anomalous cross-layer dep).\n // Edges that appear many times are established patterns.\n const rules = new Map<string, 'ok' | 'violation'>();\n for (const [edge, count] of layerEdges) {\n rules.set(edge, count <= 2 ? 'violation' : 'ok');\n }\n\n return rules;\n}\n\nfunction getTopDir(filePath: string): string {\n const parts = filePath.split('/');\n return parts[0] ?? filePath;\n}\n\nfunction isLikelyTypeOnlyDep(dep: string): boolean {\n return dep.includes('types') || dep.endsWith('.d.ts');\n}\n\nfunction isStructuralRole(basename: string): boolean {\n if (basename === 'index.ts' || basename === 'index.js') return true;\n if (basename === 'cli.ts' || basename === 'main.ts' || basename === 'main.rs') return true;\n if (basename.includes('worker.') || basename.includes('postinstall.')) return true;\n if (basename === 'health.ts' || basename === 'health.js') return true;\n return false;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
shortenSymbol
|
|
3
|
+
} from "./chunk-QOV2R2WT.js";
|
|
4
|
+
|
|
5
|
+
// src/queries/redundant-reexports.ts
|
|
6
|
+
function redundantReexports(db, opts = {}) {
|
|
7
|
+
const { scope, limit } = opts;
|
|
8
|
+
const scopeFilter = scope ? `AND barrel_d.relative_path LIKE '%${scope}%'` : "";
|
|
9
|
+
const reexportRows = db.all(
|
|
10
|
+
`SELECT DISTINCT
|
|
11
|
+
barrel_d.id AS barrel_doc_id,
|
|
12
|
+
barrel_d.relative_path AS barrel_path,
|
|
13
|
+
gs.id AS symbol_id,
|
|
14
|
+
gs.symbol AS symbol,
|
|
15
|
+
orig_d.id AS original_doc_id,
|
|
16
|
+
orig_d.relative_path AS original_path
|
|
17
|
+
FROM mentions m
|
|
18
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
19
|
+
JOIN documents barrel_d ON c.document_id = barrel_d.id
|
|
20
|
+
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
21
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
22
|
+
JOIN documents orig_d ON der.document_id = orig_d.id
|
|
23
|
+
WHERE m.role = 0
|
|
24
|
+
AND (barrel_d.relative_path LIKE '%/index.ts'
|
|
25
|
+
OR barrel_d.relative_path LIKE '%/index.js'
|
|
26
|
+
OR barrel_d.relative_path = 'index.ts'
|
|
27
|
+
OR barrel_d.relative_path = 'index.js')
|
|
28
|
+
AND orig_d.id != barrel_d.id
|
|
29
|
+
${db.pathExclusionsFor("barrel_d", "orig_d")}
|
|
30
|
+
${db.symbolNoiseFor("gs")}
|
|
31
|
+
-- Only function-level symbols (ending with ().), not module-level
|
|
32
|
+
AND gs.symbol LIKE '%().'
|
|
33
|
+
${scopeFilter}
|
|
34
|
+
ORDER BY barrel_d.relative_path, gs.symbol`
|
|
35
|
+
);
|
|
36
|
+
const results = [];
|
|
37
|
+
for (const row of reexportRows) {
|
|
38
|
+
if (db.isIgnored(row.barrel_path) || db.isIgnored(row.original_path)) continue;
|
|
39
|
+
const consumerCounts = db.get(
|
|
40
|
+
`SELECT
|
|
41
|
+
SUM(CASE WHEN uses_barrel = 1 THEN 1 ELSE 0 END) AS barrel_consumers,
|
|
42
|
+
SUM(CASE WHEN uses_barrel = 0 THEN 1 ELSE 0 END) AS direct_consumers
|
|
43
|
+
FROM (
|
|
44
|
+
SELECT
|
|
45
|
+
consumer_d.id AS consumer_doc_id,
|
|
46
|
+
MAX(CASE WHEN EXISTS (
|
|
47
|
+
SELECT 1
|
|
48
|
+
FROM mentions barrel_m
|
|
49
|
+
JOIN chunks barrel_c ON barrel_m.chunk_id = barrel_c.id
|
|
50
|
+
WHERE barrel_c.document_id = consumer_d.id
|
|
51
|
+
AND barrel_m.role = 0
|
|
52
|
+
AND barrel_m.symbol_id IN (
|
|
53
|
+
SELECT m2.symbol_id
|
|
54
|
+
FROM mentions m2
|
|
55
|
+
JOIN chunks c2 ON m2.chunk_id = c2.id
|
|
56
|
+
WHERE c2.document_id = ?
|
|
57
|
+
AND m2.role = 0
|
|
58
|
+
)
|
|
59
|
+
) THEN 1 ELSE 0 END) AS uses_barrel
|
|
60
|
+
FROM mentions ref_m
|
|
61
|
+
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
62
|
+
JOIN documents consumer_d ON ref_c.document_id = consumer_d.id
|
|
63
|
+
WHERE ref_m.symbol_id = ?
|
|
64
|
+
AND ref_m.role = 0
|
|
65
|
+
AND consumer_d.id != ?
|
|
66
|
+
AND consumer_d.id != ?
|
|
67
|
+
${db.pathExclusionsFor("consumer_d")}
|
|
68
|
+
GROUP BY consumer_d.id
|
|
69
|
+
)`,
|
|
70
|
+
row.barrel_doc_id,
|
|
71
|
+
// for the inner subquery checking barrel mentions
|
|
72
|
+
row.symbol_id,
|
|
73
|
+
// the re-exported symbol
|
|
74
|
+
row.barrel_doc_id,
|
|
75
|
+
// exclude the barrel itself
|
|
76
|
+
row.original_doc_id
|
|
77
|
+
// exclude the original file
|
|
78
|
+
);
|
|
79
|
+
const barrelConsumers = consumerCounts?.barrel_consumers ?? 0;
|
|
80
|
+
const directConsumers = consumerCounts?.direct_consumers ?? 0;
|
|
81
|
+
if (barrelConsumers === 0 && directConsumers === 0) {
|
|
82
|
+
results.push({
|
|
83
|
+
barrelFile: row.barrel_path,
|
|
84
|
+
symbol: row.symbol,
|
|
85
|
+
shortName: shortenSymbol(row.symbol),
|
|
86
|
+
originalFile: row.original_path,
|
|
87
|
+
barrelConsumers,
|
|
88
|
+
directConsumers
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
results.sort(
|
|
93
|
+
(a, b) => b.directConsumers - a.directConsumers || a.barrelFile.localeCompare(b.barrelFile) || a.shortName.localeCompare(b.shortName)
|
|
94
|
+
);
|
|
95
|
+
return limit ? results.slice(0, limit) : results;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export {
|
|
99
|
+
redundantReexports
|
|
100
|
+
};
|
|
101
|
+
//# sourceMappingURL=chunk-3E2X7RIE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 = 0\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 = 0\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 = 0\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 = 0\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":[]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseSymbol,
|
|
3
|
+
shortenSymbol
|
|
4
|
+
} from "./chunk-QOV2R2WT.js";
|
|
5
|
+
|
|
6
|
+
// src/queries/members.ts
|
|
7
|
+
function members(db, symbolPattern) {
|
|
8
|
+
const parents = db.all(
|
|
9
|
+
`SELECT symbol FROM global_symbols WHERE symbol LIKE ?`,
|
|
10
|
+
`%${symbolPattern}%`
|
|
11
|
+
);
|
|
12
|
+
if (parents.length === 0) return [];
|
|
13
|
+
const placeholders = parents.map(() => "?").join(",");
|
|
14
|
+
const parentSymbols = parents.map((p) => p.symbol);
|
|
15
|
+
const rows = db.all(
|
|
16
|
+
`SELECT gs.symbol, der.start_line, der.end_line
|
|
17
|
+
FROM global_symbols gs
|
|
18
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
19
|
+
WHERE gs.enclosing_symbol IN (${placeholders})
|
|
20
|
+
${db.symbolNoise}
|
|
21
|
+
ORDER BY der.start_line`,
|
|
22
|
+
...parentSymbols
|
|
23
|
+
);
|
|
24
|
+
return rows.map((r) => {
|
|
25
|
+
const parsed = parseSymbol(r.symbol);
|
|
26
|
+
let kind = "unknown";
|
|
27
|
+
if (!("kind" in parsed)) {
|
|
28
|
+
const sym = parsed;
|
|
29
|
+
const last = sym.descriptors[sym.descriptors.length - 1];
|
|
30
|
+
if (last) kind = last.suffix;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
symbol: r.symbol,
|
|
34
|
+
shortName: shortenSymbol(r.symbol),
|
|
35
|
+
startLine: r.start_line,
|
|
36
|
+
endLine: r.end_line,
|
|
37
|
+
kind
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
members
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=chunk-3UOUTZQT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/members.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { MemberResult } from '../types.js';\nimport { shortenSymbol, parseSymbol } from '../symbol-parser.js';\nimport type { ScipSymbol } from '../types.js';\n\n/**\n * Find all direct children of a symbol (methods, fields, nested types).\n * Uses the enclosing_symbol relationship in global_symbols.\n */\nexport function members(db: ScipDatabase, symbolPattern: string): MemberResult[] {\n // First find the parent symbol(s)\n const parents = db.all<{ symbol: string }>(\n `SELECT symbol FROM global_symbols WHERE symbol LIKE ?`,\n `%${symbolPattern}%`,\n );\n\n if (parents.length === 0) return [];\n\n // Find children whose enclosing_symbol matches any parent\n const placeholders = parents.map(() => '?').join(',');\n const parentSymbols = parents.map((p) => p.symbol);\n\n const rows = db.all<{\n symbol: string;\n start_line: number;\n end_line: number;\n }>(\n `SELECT gs.symbol, der.start_line, der.end_line\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n WHERE gs.enclosing_symbol IN (${placeholders})\n ${db.symbolNoise}\n ORDER BY der.start_line`,\n ...parentSymbols,\n );\n\n return rows.map((r) => {\n const parsed = parseSymbol(r.symbol);\n let kind = 'unknown';\n if (!('kind' in parsed)) {\n const sym = parsed as ScipSymbol;\n const last = sym.descriptors[sym.descriptors.length - 1];\n if (last) kind = last.suffix;\n }\n\n return {\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n startLine: r.start_line,\n endLine: r.end_line,\n kind,\n };\n });\n}\n"],"mappings":";;;;;;AASO,SAAS,QAAQ,IAAkB,eAAuC;AAE/E,QAAM,UAAU,GAAG;AAAA,IACjB;AAAA,IACA,IAAI,aAAa;AAAA,EACnB;AAEA,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAGlC,QAAM,eAAe,QAAQ,IAAI,MAAM,GAAG,EAAE,KAAK,GAAG;AACpD,QAAM,gBAAgB,QAAQ,IAAI,CAAC,MAAM,EAAE,MAAM;AAEjD,QAAM,OAAO,GAAG;AAAA,IAKd;AAAA;AAAA;AAAA,oCAGgC,YAAY;AAAA,QACxC,GAAG,WAAW;AAAA;AAAA,IAElB,GAAG;AAAA,EACL;AAEA,SAAO,KAAK,IAAI,CAAC,MAAM;AACrB,UAAM,SAAS,YAAY,EAAE,MAAM;AACnC,QAAI,OAAO;AACX,QAAI,EAAE,UAAU,SAAS;AACvB,YAAM,MAAM;AACZ,YAAM,OAAO,IAAI,YAAY,IAAI,YAAY,SAAS,CAAC;AACvD,UAAI,KAAM,QAAO,KAAK;AAAA,IACxB;AAEA,WAAO;AAAA,MACL,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,WAAW,EAAE;AAAA,MACb,SAAS,EAAE;AAAA,MACX;AAAA,IACF;AAAA,EACF,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TEST_FILE_PATTERNS,
|
|
3
|
+
testFileExclusionSql,
|
|
4
|
+
testFileMatchSql
|
|
5
|
+
} from "./chunk-FUHJCHS4.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 = 0
|
|
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 = 0 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-3ZZJVBIO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 = 0\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 = 0 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,mDAG6C,UAAU;AAAA,MACvD,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":[]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// src/queries/clean-signature.ts
|
|
2
|
+
function cleanSignature(sig) {
|
|
3
|
+
if (!sig || !sig.trim()) return null;
|
|
4
|
+
return sig.replace(/^```\w*\s*/, "").replace(/\s*```$/, "").replace(/^\(method\)\s*/, "").replace(/^\(property\)\s*/, "").replace(/^\(function\)\s*/, "").replace(/^\(class\)\s*/, "").replace(/^\(interface\)\s*/, "").replace(/^\(enum\)\s*/, "").replace(/^\(type alias\)\s*/, "").replace(/^\(const\)\s*/, "").replace(/^\(var\)\s*/, "").trim() || null;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
cleanSignature
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=chunk-4TYLS5XX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/clean-signature.ts"],"sourcesContent":["/**\n * Clean up the raw doc/signature string from the SCIP index.\n *\n * Shared across symbols, trace, and system queries.\n * Previously duplicated as cleanSig/cleanSignature in three files.\n */\nexport function cleanSignature(sig: string | null): string | null {\n if (!sig || !sig.trim()) return null;\n return sig\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() || null;\n}\n"],"mappings":";AAMO,SAAS,eAAe,KAAmC;AAChE,MAAI,CAAC,OAAO,CAAC,IAAI,KAAK,EAAG,QAAO;AAChC,SAAO,IACJ,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,KAAK;AACf;","names":[]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TEST_SUPPORT_PATH_PATTERNS,
|
|
3
|
+
testFileExclusionSql
|
|
4
|
+
} from "./chunk-FUHJCHS4.js";
|
|
5
|
+
import {
|
|
6
|
+
shortenSymbol
|
|
7
|
+
} from "./chunk-QOV2R2WT.js";
|
|
8
|
+
|
|
9
|
+
// src/queries/dead.ts
|
|
10
|
+
function dead(db, opts = {}) {
|
|
11
|
+
const {
|
|
12
|
+
scope,
|
|
13
|
+
minLoc = 1,
|
|
14
|
+
includeTests = false,
|
|
15
|
+
skipBarrels = false,
|
|
16
|
+
includeMembers = false
|
|
17
|
+
} = opts;
|
|
18
|
+
const params = [minLoc];
|
|
19
|
+
let testFileExclusions = "";
|
|
20
|
+
let memberExclusion = "";
|
|
21
|
+
if (scope) {
|
|
22
|
+
params.push(`%${scope}%`);
|
|
23
|
+
}
|
|
24
|
+
if (!includeTests) {
|
|
25
|
+
testFileExclusions = `
|
|
26
|
+
AND ${testFileExclusionSql("d", TEST_SUPPORT_PATH_PATTERNS)}
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
if (!includeMembers) {
|
|
30
|
+
memberExclusion = `AND gs.symbol NOT LIKE '%#%'`;
|
|
31
|
+
}
|
|
32
|
+
const barrelExclusions = skipBarrels ? `AND ref_d.relative_path NOT LIKE '%/index.ts'
|
|
33
|
+
AND ref_d.relative_path NOT LIKE '%/index.js'
|
|
34
|
+
AND ref_d.relative_path NOT LIKE '%/mod.rs'
|
|
35
|
+
AND ref_d.relative_path NOT LIKE '%/__init__.py'` : "";
|
|
36
|
+
const sql = `
|
|
37
|
+
SELECT
|
|
38
|
+
d.relative_path,
|
|
39
|
+
der.start_line,
|
|
40
|
+
der.end_line,
|
|
41
|
+
(der.end_line - der.start_line + 1) AS loc,
|
|
42
|
+
gs.symbol,
|
|
43
|
+
(SELECT COUNT(*) FROM mentions m2
|
|
44
|
+
JOIN chunks c2 ON m2.chunk_id = c2.id
|
|
45
|
+
WHERE m2.symbol_id = gs.id AND m2.role = 0 AND c2.document_id = d.id
|
|
46
|
+
) AS same_file_refs
|
|
47
|
+
FROM global_symbols gs
|
|
48
|
+
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
49
|
+
JOIN documents d ON der.document_id = d.id
|
|
50
|
+
WHERE 1 = 1
|
|
51
|
+
${db.pathExclusionsFor("d")}
|
|
52
|
+
${db.symbolNoiseFor("gs")}
|
|
53
|
+
AND (der.end_line - der.start_line + 1) >= ?
|
|
54
|
+
${scope ? "AND d.relative_path LIKE ?" : ""}
|
|
55
|
+
${testFileExclusions}
|
|
56
|
+
${memberExclusion}
|
|
57
|
+
AND NOT EXISTS (
|
|
58
|
+
SELECT 1
|
|
59
|
+
FROM mentions ref_m
|
|
60
|
+
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
61
|
+
JOIN documents ref_d ON ref_c.document_id = ref_d.id
|
|
62
|
+
WHERE ref_m.symbol_id = gs.id
|
|
63
|
+
AND ref_m.role = 0
|
|
64
|
+
AND ref_d.id != d.id
|
|
65
|
+
${barrelExclusions}
|
|
66
|
+
)
|
|
67
|
+
ORDER BY (der.end_line - der.start_line + 1) DESC, d.relative_path, der.start_line
|
|
68
|
+
`;
|
|
69
|
+
const rows = db.all(sql, ...params);
|
|
70
|
+
let deadCodeCount = 0;
|
|
71
|
+
let fileInternalCount = 0;
|
|
72
|
+
let totalLoc = 0;
|
|
73
|
+
const symbols = rows.filter((r) => !db.isIgnored(r.relative_path)).map((r) => {
|
|
74
|
+
const kind = r.same_file_refs === 0 ? "dead-code" : "file-internal";
|
|
75
|
+
if (kind === "dead-code") deadCodeCount++;
|
|
76
|
+
else fileInternalCount++;
|
|
77
|
+
totalLoc += r.loc;
|
|
78
|
+
return {
|
|
79
|
+
relativePath: r.relative_path,
|
|
80
|
+
startLine: r.start_line,
|
|
81
|
+
endLine: r.end_line,
|
|
82
|
+
loc: r.loc,
|
|
83
|
+
symbol: r.symbol,
|
|
84
|
+
shortName: shortenSymbol(r.symbol),
|
|
85
|
+
sameFileRefs: r.same_file_refs,
|
|
86
|
+
kind
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
symbols,
|
|
91
|
+
totalCount: symbols.length,
|
|
92
|
+
deadCodeCount,
|
|
93
|
+
fileInternalCount,
|
|
94
|
+
totalLoc
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export {
|
|
99
|
+
dead
|
|
100
|
+
};
|
|
101
|
+
//# sourceMappingURL=chunk-5FGUEU7N.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/dead.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { TEST_SUPPORT_PATH_PATTERNS, testFileExclusionSql } from '../query-support.js';\nimport type { DeadOptions, DeadSymbolResult, DeadSummary } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find dead exports: symbols defined locally with no cross-file references.\n * Language-agnostic — works with any SCIP index.\n */\nexport function dead(db: ScipDatabase, opts: DeadOptions = {}): DeadSummary {\n const {\n scope,\n minLoc = 1,\n includeTests = false,\n skipBarrels = false,\n includeMembers = false,\n } = opts;\n\n const params: unknown[] = [minLoc];\n let testFileExclusions = '';\n let memberExclusion = '';\n\n if (scope) {\n params.push(`%${scope}%`);\n }\n\n if (!includeTests) {\n testFileExclusions = `\n AND ${testFileExclusionSql('d', TEST_SUPPORT_PATH_PATTERNS)}\n `;\n }\n\n if (!includeMembers) {\n memberExclusion = `AND gs.symbol NOT LIKE '%#%'`;\n }\n\n // Barrel file exclusion for the NOT EXISTS subquery\n const barrelExclusions = skipBarrels\n ? `AND ref_d.relative_path NOT LIKE '%/index.ts'\n AND ref_d.relative_path NOT LIKE '%/index.js'\n AND ref_d.relative_path NOT LIKE '%/mod.rs'\n AND ref_d.relative_path NOT LIKE '%/__init__.py'`\n : '';\n\n const sql = `\n SELECT\n d.relative_path,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n gs.symbol,\n (SELECT COUNT(*) FROM mentions m2\n JOIN chunks c2 ON m2.chunk_id = c2.id\n WHERE m2.symbol_id = gs.id AND m2.role = 0 AND c2.document_id = d.id\n ) AS same_file_refs\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) >= ?\n ${scope ? 'AND d.relative_path LIKE ?' : ''}\n ${testFileExclusions}\n ${memberExclusion}\n AND NOT EXISTS (\n SELECT 1\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 WHERE ref_m.symbol_id = gs.id\n AND ref_m.role = 0\n AND ref_d.id != d.id\n ${barrelExclusions}\n )\n ORDER BY (der.end_line - der.start_line + 1) DESC, d.relative_path, der.start_line\n `;\n\n const rows = db.all<{\n relative_path: string;\n start_line: number;\n end_line: number;\n loc: number;\n symbol: string;\n same_file_refs: number;\n }>(sql, ...params);\n\n let deadCodeCount = 0;\n let fileInternalCount = 0;\n let totalLoc = 0;\n\n const symbols: DeadSymbolResult[] = rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => {\n // dead-code: zero references anywhere (not even in same file) — safe to delete\n // file-internal: referenced within same file but never cross-file —\n // may be a private helper (fine) or a forgotten export (needs review)\n const kind = r.same_file_refs === 0 ? 'dead-code' : 'file-internal';\n if (kind === 'dead-code') deadCodeCount++;\n else fileInternalCount++;\n totalLoc += r.loc;\n\n return {\n relativePath: r.relative_path,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n sameFileRefs: r.same_file_refs,\n kind,\n };\n });\n\n return {\n symbols,\n totalCount: symbols.length,\n deadCodeCount,\n fileInternalCount,\n totalLoc,\n };\n}\n"],"mappings":";;;;;;;;;AASO,SAAS,KAAK,IAAkB,OAAoB,CAAC,GAAgB;AAC1E,QAAM;AAAA,IACJ;AAAA,IACA,SAAS;AAAA,IACT,eAAe;AAAA,IACf,cAAc;AAAA,IACd,iBAAiB;AAAA,EACnB,IAAI;AAEJ,QAAM,SAAoB,CAAC,MAAM;AACjC,MAAI,qBAAqB;AACzB,MAAI,kBAAkB;AAEtB,MAAI,OAAO;AACT,WAAO,KAAK,IAAI,KAAK,GAAG;AAAA,EAC1B;AAEA,MAAI,CAAC,cAAc;AACjB,yBAAqB;AAAA,YACb,qBAAqB,KAAK,0BAA0B,CAAC;AAAA;AAAA,EAE/D;AAEA,MAAI,CAAC,gBAAgB;AACnB,sBAAkB;AAAA,EACpB;AAGA,QAAM,mBAAmB,cACrB;AAAA;AAAA;AAAA,2DAIA;AAEJ,QAAM,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAeN,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA,QAEvB,QAAQ,+BAA+B,EAAE;AAAA,QACzC,kBAAkB;AAAA,QAClB,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YASX,gBAAgB;AAAA;AAAA;AAAA;AAK1B,QAAM,OAAO,GAAG,IAOb,KAAK,GAAG,MAAM;AAEjB,MAAI,gBAAgB;AACpB,MAAI,oBAAoB;AACxB,MAAI,WAAW;AAEf,QAAM,UAA8B,KACjC,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,MAAM;AAIV,UAAM,OAAO,EAAE,mBAAmB,IAAI,cAAc;AACpD,QAAI,SAAS,YAAa;AAAA,QACrB;AACL,gBAAY,EAAE;AAEd,WAAO;AAAA,MACL,cAAc,EAAE;AAAA,MAChB,WAAW,EAAE;AAAA,MACb,SAAS,EAAE;AAAA,MACX,KAAK,EAAE;AAAA,MACP,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,cAAc,EAAE;AAAA,MAChB;AAAA,IACF;AAAA,EACF,CAAC;AAEH,SAAO;AAAA,IACL;AAAA,IACA,YAAY,QAAQ;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|