scip-query 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -43
- package/dist/chunk-2UELLEBI.js +1 -0
- package/dist/chunk-34JPTNRN.js +601 -0
- package/dist/{chunk-W4ALF422.js → chunk-3566TKJ5.js} +3 -3
- package/dist/{chunk-Z6YZJ36C.js → chunk-4ACRRQC4.js} +8 -4
- package/dist/{chunk-Z4GHE2HD.js → chunk-4BQFSNFI.js} +6 -2
- package/dist/{chunk-4XHWPRAX.js → chunk-6QSHLFSL.js} +3 -3
- package/dist/{chunk-4EXL2CUA.js → chunk-6WVR5K46.js} +16 -8
- package/dist/{chunk-ZQIIPFD7.js → chunk-75RQSBTK.js} +2 -2
- package/dist/{chunk-KPPHZCZJ.js → chunk-7HK5ZLOE.js} +28 -46
- package/dist/{chunk-MCUX5LA7.js → chunk-7JFZSOJ7.js} +3 -3
- package/dist/{chunk-6SXADWLW.js → chunk-AKMBBKWV.js} +2 -2
- package/dist/{chunk-NHBZIL2J.js → chunk-AMNISGYR.js} +3 -3
- package/dist/{chunk-2CKGIR6G.js → chunk-BFLULBEU.js} +3 -3
- package/dist/{chunk-HPFZLISB.js → chunk-CU62ZDHI.js} +2 -2
- package/dist/{chunk-EQYLEQCW.js → chunk-DY4AFG2W.js} +12 -10
- package/dist/{chunk-5RMYT5WH.js → chunk-F7XU27LU.js} +2 -2
- package/dist/{chunk-KCBMVQL5.js → chunk-GPJVPT3U.js} +2 -2
- package/dist/{chunk-NUZ4OMU3.js → chunk-GU2H5QRN.js} +2 -2
- package/dist/{chunk-UJQN5N3I.js → chunk-H6WCPKCX.js} +6 -3
- package/dist/{chunk-OVPLOMPY.js → chunk-HMYJJ3HY.js} +7 -4
- package/dist/chunk-IJKLB2JW.js +69 -0
- package/dist/{chunk-DGUPQSOR.js → chunk-IXPHLF6K.js} +2 -2
- package/dist/{chunk-7PBOG4YE.js → chunk-KBOQX573.js} +2 -2
- package/dist/{chunk-ZOGY2V3N.js → chunk-LLMPAG56.js} +93 -30
- package/dist/{chunk-BOVXCR46.js → chunk-LTJC5ZQL.js} +2 -2
- package/dist/{chunk-LAWMH22O.js → chunk-M3NPW3FC.js} +2 -2
- package/dist/{chunk-NWCE4CIC.js → chunk-M4QGEKKD.js} +5 -27
- package/dist/{chunk-63G7IQTD.js → chunk-MVH45PYK.js} +20 -40
- package/dist/chunk-N4C3H7LH.js +37 -0
- package/dist/chunk-NG5F43OU.js +200 -0
- package/dist/{chunk-D567NFIF.js → chunk-NVIIM34O.js} +3 -3
- package/dist/{chunk-BNN2RKD2.js → chunk-ORINICIZ.js} +3 -3
- package/dist/{chunk-4PDAL6IL.js → chunk-PMJKOXOT.js} +3 -3
- package/dist/{chunk-QOV2R2WT.js → chunk-QIXNAB5K.js} +42 -2
- package/dist/{chunk-7LLPRPR5.js → chunk-R2I3M5B4.js} +2 -2
- package/dist/{chunk-7RLE5EWE.js → chunk-R56FJU3E.js} +34 -13
- package/dist/{chunk-H2MDONBU.js → chunk-RFMT7UAZ.js} +3 -3
- package/dist/{chunk-SEFSL2GF.js → chunk-TOIEB3LG.js} +2 -2
- package/dist/chunk-VO4QI3LS.js +84 -0
- package/dist/{chunk-ZK6GXM3J.js → chunk-WVK7AASK.js} +3 -3
- package/dist/{chunk-HMLMH7VZ.js → chunk-Y3M323OX.js} +2 -2
- package/dist/{chunk-DCKMSTJ4.js → chunk-Y4JFVQ7C.js} +2 -2
- package/dist/{chunk-7UCKSQRS.js → chunk-YAFWL3RA.js} +3 -3
- package/dist/{chunk-FGXRVW7G.js → chunk-YZ6L7GFO.js} +2 -2
- package/dist/cli.js +1355 -671
- package/dist/{db-BNVVZSfP.d.ts → db-BHYam4BK.d.ts} +6 -18
- package/dist/index.d.ts +15 -15
- package/dist/index.js +260 -231
- package/dist/postinstall.js +5 -76
- package/dist/queries/affected.d.ts +1 -1
- package/dist/queries/affected.js +3 -3
- package/dist/queries/bottlenecks.d.ts +1 -1
- package/dist/queries/bottlenecks.js +2 -2
- package/dist/queries/by-kind.d.ts +1 -1
- package/dist/queries/by-kind.js +2 -2
- package/dist/queries/call-graph.d.ts +1 -1
- package/dist/queries/call-graph.js +3 -3
- package/dist/queries/change-surface.d.ts +2 -2
- package/dist/queries/change-surface.js +2 -3
- package/dist/queries/code.d.ts +1 -1
- package/dist/queries/code.js +3 -3
- package/dist/queries/complexity-hotspots.d.ts +1 -1
- package/dist/queries/complexity-hotspots.js +3 -3
- package/dist/queries/complexity.d.ts +1 -1
- package/dist/queries/complexity.js +3 -3
- package/dist/queries/convergence.d.ts +1 -1
- package/dist/queries/convergence.js +3 -3
- package/dist/queries/coupling.d.ts +1 -1
- package/dist/queries/cycles.d.ts +1 -1
- package/dist/queries/cycles.js +3 -2
- package/dist/queries/dataflow.d.ts +1 -1
- package/dist/queries/dataflow.js +3 -3
- package/dist/queries/dead.d.ts +1 -1
- package/dist/queries/dead.js +4 -3
- package/dist/queries/deep-chains.d.ts +1 -1
- package/dist/queries/deep-chains.js +3 -2
- package/dist/queries/deps.d.ts +1 -1
- package/dist/queries/diff-impact.d.ts +2 -2
- package/dist/queries/diff-impact.js +2 -3
- package/dist/queries/doc-coverage.d.ts +1 -1
- package/dist/queries/doc-coverage.js +2 -2
- package/dist/queries/drift.d.ts +1 -1
- package/dist/queries/drift.js +3 -2
- package/dist/queries/extract-candidates.d.ts +1 -1
- package/dist/queries/extract-candidates.js +3 -3
- package/dist/queries/fan.d.ts +1 -1
- package/dist/queries/fan.js +2 -2
- package/dist/queries/files.d.ts +1 -1
- package/dist/queries/health.d.ts +1 -1
- package/dist/queries/health.js +14 -14
- package/dist/queries/hierarchy.d.ts +1 -1
- package/dist/queries/hierarchy.js +3 -2
- package/dist/queries/hotspots.d.ts +1 -1
- package/dist/queries/hotspots.js +2 -2
- package/dist/queries/imports.d.ts +1 -1
- package/dist/queries/imports.js +3 -2
- package/dist/queries/index.d.ts +1 -2
- package/dist/queries/index.js +43 -48
- package/dist/queries/isolated.d.ts +1 -1
- package/dist/queries/isolated.js +4 -3
- package/dist/queries/members.d.ts +2 -2
- package/dist/queries/members.js +3 -2
- package/dist/queries/methods.d.ts +1 -1
- package/dist/queries/methods.js +2 -2
- package/dist/queries/outline.d.ts +1 -1
- package/dist/queries/outline.js +2 -2
- package/dist/queries/passthrough-candidates.d.ts +1 -1
- package/dist/queries/passthrough-candidates.js +3 -3
- package/dist/queries/redundant-reexports.d.ts +1 -1
- package/dist/queries/redundant-reexports.js +4 -2
- package/dist/queries/refs.d.ts +1 -1
- package/dist/queries/similar-chains.d.ts +1 -1
- package/dist/queries/similar-chains.js +3 -2
- package/dist/queries/similar-files.d.ts +1 -1
- package/dist/queries/similar-files.js +3 -2
- package/dist/queries/similar-signatures.d.ts +1 -1
- package/dist/queries/similar-signatures.js +2 -2
- package/dist/queries/similar.d.ts +1 -1
- package/dist/queries/similar.js +3 -3
- package/dist/queries/slice.d.ts +1 -1
- package/dist/queries/slice.js +3 -3
- package/dist/queries/stale-abstractions.d.ts +1 -1
- package/dist/queries/stale-abstractions.js +3 -3
- package/dist/queries/stats.d.ts +1 -1
- package/dist/queries/surface.d.ts +1 -1
- package/dist/queries/surface.js +2 -2
- package/dist/queries/symbols.d.ts +1 -1
- package/dist/queries/symbols.js +2 -2
- package/dist/queries/system.d.ts +1 -1
- package/dist/queries/system.js +2 -2
- package/dist/queries/trace.d.ts +1 -1
- package/dist/queries/trace.js +3 -1
- package/dist/queries/wrapper-candidates.d.ts +1 -1
- package/dist/queries/wrapper-candidates.js +3 -3
- package/dist/reindex-worker.js +24 -12
- package/package.json +6 -1
- package/IMPROVEMENTS.md +0 -143
- package/PLAN.md +0 -320
- package/dist/chunk-2CKGIR6G.js.map +0 -1
- package/dist/chunk-3UOUTZQT.js +0 -45
- package/dist/chunk-3UOUTZQT.js.map +0 -1
- package/dist/chunk-4EXL2CUA.js.map +0 -1
- package/dist/chunk-4PDAL6IL.js.map +0 -1
- package/dist/chunk-4TYLS5XX.js.map +0 -1
- package/dist/chunk-4XHWPRAX.js.map +0 -1
- package/dist/chunk-5RMYT5WH.js.map +0 -1
- package/dist/chunk-63G7IQTD.js.map +0 -1
- package/dist/chunk-6SXADWLW.js.map +0 -1
- package/dist/chunk-74RFWB5T.js.map +0 -1
- package/dist/chunk-7LLPRPR5.js.map +0 -1
- package/dist/chunk-7PBOG4YE.js.map +0 -1
- package/dist/chunk-7RLE5EWE.js.map +0 -1
- package/dist/chunk-7UCKSQRS.js.map +0 -1
- package/dist/chunk-BNN2RKD2.js.map +0 -1
- package/dist/chunk-BOVXCR46.js.map +0 -1
- package/dist/chunk-D567NFIF.js.map +0 -1
- package/dist/chunk-DCKMSTJ4.js.map +0 -1
- package/dist/chunk-DEZKCZXD.js +0 -40
- package/dist/chunk-DEZKCZXD.js.map +0 -1
- package/dist/chunk-DGUPQSOR.js.map +0 -1
- package/dist/chunk-DVWGWHFW.js +0 -99
- package/dist/chunk-DVWGWHFW.js.map +0 -1
- package/dist/chunk-EQYLEQCW.js.map +0 -1
- package/dist/chunk-FGXRVW7G.js.map +0 -1
- package/dist/chunk-H2MDONBU.js.map +0 -1
- package/dist/chunk-HB7MRLLL.js +0 -76
- package/dist/chunk-HB7MRLLL.js.map +0 -1
- package/dist/chunk-HDSRORNV.js.map +0 -1
- package/dist/chunk-HMLMH7VZ.js.map +0 -1
- package/dist/chunk-HPFZLISB.js.map +0 -1
- package/dist/chunk-HZBC7PPD.js +0 -88
- package/dist/chunk-HZBC7PPD.js.map +0 -1
- package/dist/chunk-ITZ3DDOG.js.map +0 -1
- package/dist/chunk-JJP7KQND.js +0 -1
- package/dist/chunk-JJP7KQND.js.map +0 -1
- package/dist/chunk-KCBMVQL5.js.map +0 -1
- package/dist/chunk-KPPHZCZJ.js.map +0 -1
- package/dist/chunk-LAWMH22O.js.map +0 -1
- package/dist/chunk-MCUX5LA7.js.map +0 -1
- package/dist/chunk-MGNMHKX3.js.map +0 -1
- package/dist/chunk-N5KEREIA.js.map +0 -1
- package/dist/chunk-NHBZIL2J.js.map +0 -1
- package/dist/chunk-NUZ4OMU3.js.map +0 -1
- package/dist/chunk-NWCE4CIC.js.map +0 -1
- package/dist/chunk-OVPLOMPY.js.map +0 -1
- package/dist/chunk-QOV2R2WT.js.map +0 -1
- package/dist/chunk-SEFSL2GF.js.map +0 -1
- package/dist/chunk-UJQN5N3I.js.map +0 -1
- package/dist/chunk-W4ALF422.js.map +0 -1
- package/dist/chunk-Z4GHE2HD.js.map +0 -1
- package/dist/chunk-Z6YZJ36C.js.map +0 -1
- package/dist/chunk-ZK6GXM3J.js.map +0 -1
- package/dist/chunk-ZOGY2V3N.js.map +0 -1
- package/dist/chunk-ZQIIPFD7.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/postinstall.js.map +0 -1
- package/dist/queries/affected.js.map +0 -1
- package/dist/queries/bottlenecks.js.map +0 -1
- package/dist/queries/by-kind.js.map +0 -1
- package/dist/queries/call-graph.js.map +0 -1
- package/dist/queries/change-surface.js.map +0 -1
- package/dist/queries/clean-signature.js.map +0 -1
- package/dist/queries/code.js.map +0 -1
- package/dist/queries/complexity-hotspots.js.map +0 -1
- package/dist/queries/complexity.js.map +0 -1
- package/dist/queries/convergence.js.map +0 -1
- package/dist/queries/coupling.js.map +0 -1
- package/dist/queries/cycles.js.map +0 -1
- package/dist/queries/dataflow.js.map +0 -1
- package/dist/queries/dead.js.map +0 -1
- package/dist/queries/deep-chains.js.map +0 -1
- package/dist/queries/deps.js.map +0 -1
- package/dist/queries/diff-impact.js.map +0 -1
- package/dist/queries/doc-coverage.js.map +0 -1
- package/dist/queries/drift.js.map +0 -1
- package/dist/queries/extract-candidates.js.map +0 -1
- package/dist/queries/fan.js.map +0 -1
- package/dist/queries/files.js.map +0 -1
- package/dist/queries/health.js.map +0 -1
- package/dist/queries/hierarchy.js.map +0 -1
- package/dist/queries/hotspots.js.map +0 -1
- package/dist/queries/imports.js.map +0 -1
- package/dist/queries/index.js.map +0 -1
- package/dist/queries/isolated.js.map +0 -1
- package/dist/queries/members.js.map +0 -1
- package/dist/queries/methods.js.map +0 -1
- package/dist/queries/outline.js.map +0 -1
- package/dist/queries/passthrough-candidates.js.map +0 -1
- package/dist/queries/redundant-reexports.js.map +0 -1
- package/dist/queries/refs.js.map +0 -1
- package/dist/queries/similar-chains.js.map +0 -1
- package/dist/queries/similar-files.js.map +0 -1
- package/dist/queries/similar-signatures.js.map +0 -1
- package/dist/queries/similar.js.map +0 -1
- package/dist/queries/slice.js.map +0 -1
- package/dist/queries/stale-abstractions.js.map +0 -1
- package/dist/queries/stats.js.map +0 -1
- package/dist/queries/surface.js.map +0 -1
- package/dist/queries/symbols.js.map +0 -1
- package/dist/queries/system.js.map +0 -1
- package/dist/queries/test-coverage.d.ts +0 -22
- package/dist/queries/test-coverage.js +0 -11
- package/dist/queries/test-coverage.js.map +0 -1
- package/dist/queries/trace.js.map +0 -1
- package/dist/queries/wrapper-candidates.js.map +0 -1
- package/dist/reindex-worker.js.map +0 -1
- package/docs/AGENT_GUIDE.md +0 -359
- package/reports/debloat/2026-04-10-scip-query-self-audit.md +0 -161
- package/src/cli.ts +0 -1480
- package/src/config.ts +0 -117
- package/src/db.ts +0 -127
- package/src/gitignore-filter.ts +0 -143
- package/src/index.ts +0 -11
- package/src/postinstall.ts +0 -8
- package/src/queries/affected.ts +0 -86
- package/src/queries/bottlenecks.ts +0 -67
- package/src/queries/by-kind.ts +0 -204
- package/src/queries/call-graph.ts +0 -66
- package/src/queries/change-surface.ts +0 -110
- package/src/queries/clean-signature.ts +0 -22
- package/src/queries/code.ts +0 -101
- package/src/queries/complexity-hotspots.ts +0 -119
- package/src/queries/complexity.ts +0 -152
- package/src/queries/convergence.ts +0 -82
- package/src/queries/coupling.ts +0 -99
- package/src/queries/cycles.ts +0 -78
- package/src/queries/dataflow.ts +0 -128
- package/src/queries/dead.ts +0 -122
- package/src/queries/deep-chains.ts +0 -59
- package/src/queries/deps.ts +0 -46
- package/src/queries/diff-impact.ts +0 -204
- package/src/queries/doc-coverage.ts +0 -86
- package/src/queries/drift.ts +0 -224
- package/src/queries/extract-candidates.ts +0 -167
- package/src/queries/fan.ts +0 -148
- package/src/queries/files.ts +0 -16
- package/src/queries/health.ts +0 -324
- package/src/queries/hierarchy.ts +0 -49
- package/src/queries/hotspots.ts +0 -53
- package/src/queries/imports.ts +0 -95
- package/src/queries/index.ts +0 -45
- package/src/queries/isolated.ts +0 -67
- package/src/queries/members.ts +0 -54
- package/src/queries/methods.ts +0 -27
- package/src/queries/outline.ts +0 -52
- package/src/queries/passthrough-candidates.ts +0 -94
- package/src/queries/redundant-reexports.ts +0 -170
- package/src/queries/refs.ts +0 -27
- package/src/queries/similar-chains.ts +0 -314
- package/src/queries/similar-files.ts +0 -140
- package/src/queries/similar-signatures.ts +0 -151
- package/src/queries/similar.ts +0 -305
- package/src/queries/slice.ts +0 -154
- package/src/queries/stale-abstractions.ts +0 -82
- package/src/queries/stats.ts +0 -22
- package/src/queries/surface.ts +0 -34
- package/src/queries/symbols.ts +0 -39
- package/src/queries/system.ts +0 -86
- package/src/queries/test-coverage.ts +0 -106
- package/src/queries/trace.ts +0 -55
- package/src/queries/wrapper-candidates.ts +0 -112
- package/src/query-support.ts +0 -226
- package/src/reindex/detect.ts +0 -58
- package/src/reindex/index.ts +0 -153
- package/src/reindex/indexers.ts +0 -220
- package/src/reindex/install.ts +0 -125
- package/src/reindex-worker.ts +0 -35
- package/src/setup.ts +0 -202
- package/src/symbol-parser.ts +0 -278
- package/src/types.ts +0 -654
- package/src/watch.ts +0 -274
- package/tests/gitignore-filter.test.ts +0 -48
- package/tests/queries.test.ts +0 -300
- package/tests/symbol-parser.test.ts +0 -157
- package/tsconfig.json +0 -20
- package/tsup.config.ts +0 -40
- package/vitest.config.ts +0 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scip-query",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Language-agnostic code intelligence CLI powered by SCIP indexes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
"bin": {
|
|
9
9
|
"scip-query": "dist/cli.js"
|
|
10
10
|
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/**/*.js",
|
|
13
|
+
"dist/**/*.d.ts",
|
|
14
|
+
"skills/**/SKILL.md"
|
|
15
|
+
],
|
|
11
16
|
"sideEffects": false,
|
|
12
17
|
"exports": {
|
|
13
18
|
".": {
|
package/IMPROVEMENTS.md
DELETED
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
# Improvement Opportunities
|
|
2
|
-
|
|
3
|
-
Self-audit of the scip-query codebase using its own analysis tools. Each finding includes the command that surfaced it, what it means, and what the fix looks like.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 1. Replace 26 inline `symbolNoise` filters with `db.symbolNoise`
|
|
8
|
-
|
|
9
|
-
**Found by:** `grep` on `similar` output (78-80% callee overlap driven by shared SQL boilerplate)
|
|
10
|
-
|
|
11
|
-
**Problem:** The fragment `AND gs.symbol NOT LIKE '%typeLiteral%'` appears 26 times across 16 query files. The companion `AND gs.symbol NOT LIKE '%().(%'` appears in most of those too. `db.symbolNoise` was added as a reusable getter but never wired in.
|
|
12
|
-
|
|
13
|
-
**Files affected:**
|
|
14
|
-
- `bottlenecks.ts`, `by-kind.ts`, `call-graph.ts` (3 occurrences), `dead.ts`, `doc-coverage.ts` (3), `extract-candidates.ts` (2), `fan.ts`, `hotspots.ts`, `isolated.ts`, `members.ts`, `methods.ts`, `outline.ts`, `similar.ts` (4), `symbols.ts`, `system.ts`, `test-coverage.ts` (2), `trace.ts` (2)
|
|
15
|
-
|
|
16
|
-
**Fix:** Replace each inline `AND gs.symbol NOT LIKE '%typeLiteral%' AND gs.symbol NOT LIKE '%().(%'` with `AND ${db.symbolNoise}` (or a string interpolation of the getter). This cuts ~50 lines and ensures the noise filter is defined in one place — if we need to add a new pattern (e.g., filtering out synthetic generics), it changes in one spot.
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
|
-
## 2. Replace 32 inline `node_modules` exclusions with `db.pathExclusions`
|
|
21
|
-
|
|
22
|
-
**Found by:** `grep` on `similar-files` output (100% dep-profile similarity across all query modules)
|
|
23
|
-
|
|
24
|
-
**Problem:** `d.relative_path NOT LIKE 'node_modules/%'` appears 32 times across 18 files. Often paired with `.git/%` exclusions. The `db.pathExclusions` getter exists but isn't used.
|
|
25
|
-
|
|
26
|
-
**Files affected:** Every query file.
|
|
27
|
-
|
|
28
|
-
**Fix:** Same pattern as #1 — interpolate `${db.pathExclusions}` where applicable. Some queries use different table aliases (`def_d`, `ref_d`, `d1`, `d2`) so the getter may need a parameter for the alias, or we add alias-specific variants.
|
|
29
|
-
|
|
30
|
-
---
|
|
31
|
-
|
|
32
|
-
## 3. Extract shared `buildFileDepGraph()` helper
|
|
33
|
-
|
|
34
|
-
**Found by:** `similar-chains`, `cycles`, `deep-chains`, `similar-files` all contain identical graph-building SQL
|
|
35
|
-
|
|
36
|
-
**Problem:** Four query modules build the exact same file dependency graph:
|
|
37
|
-
- `cycles.ts:17-35`
|
|
38
|
-
- `deep-chains.ts:16-34`
|
|
39
|
-
- `similar-chains.ts:125-143`
|
|
40
|
-
- `similar-files.ts:66-84`
|
|
41
|
-
|
|
42
|
-
Each runs this ~18-line SQL query, builds a `Map<string, Set<string>>` adjacency list, and filters by gitignore. The code is identical except for the `scopeFilter` variable name.
|
|
43
|
-
|
|
44
|
-
**Fix:** Extract a shared `buildFileDepGraph(db, scope?)` helper that returns a `Map<string, Set<string>>`. All four modules import and call it. Saves ~54 lines and ensures graph-building logic stays consistent (e.g., if we later add `.d.ts` exclusions, it changes in one place).
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
|
-
## 4. Extract shared test-file pattern constants
|
|
49
|
-
|
|
50
|
-
**Found by:** `grep` on test-pattern strings
|
|
51
|
-
|
|
52
|
-
**Problem:** Test file path patterns (`%/__tests__/%`, `%.test.%`, `%.spec.%`, etc.) are defined:
|
|
53
|
-
- As an array in `test-coverage.ts:7-16`
|
|
54
|
-
- As individual SQL fragments in `dead.ts:34-37`
|
|
55
|
-
- As individual SQL fragments in `isolated.ts:35-37`
|
|
56
|
-
|
|
57
|
-
Three different representations of the same concept.
|
|
58
|
-
|
|
59
|
-
**Fix:** Export a `TEST_FILE_PATTERNS` constant (and a `testFileExclusionSql(alias)` helper that generates the SQL) from a shared location. `dead.ts`, `isolated.ts`, and `test-coverage.ts` all import it.
|
|
60
|
-
|
|
61
|
-
---
|
|
62
|
-
|
|
63
|
-
## 5. `queries/index.ts` barrel has score 136 bottleneck
|
|
64
|
-
|
|
65
|
-
**Found by:** `bottlenecks` command
|
|
66
|
-
|
|
67
|
-
**Problem:** The barrel re-export file (`queries/index.ts`) has fan-in=2, fan-out=68. Every query symbol is re-exported through it, so any consumer (`cli.ts`, `index.ts`) pulls the entire query surface. This is fine for a CLI tool, but if this package is used as a library, consumers pay for every query module even if they use one.
|
|
68
|
-
|
|
69
|
-
**Fix (for later):** Support tree-shaking by also exporting individual query modules:
|
|
70
|
-
```ts
|
|
71
|
-
// Direct import for library consumers
|
|
72
|
-
import { hotspots } from 'scip-query/queries/hotspots';
|
|
73
|
-
```
|
|
74
|
-
This needs `exports` map entries in `package.json`. Not urgent — the barrel is correct for CLI use.
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## 6. `cli.ts` is the highest fan-out non-barrel file (23 external symbols)
|
|
79
|
-
|
|
80
|
-
**Found by:** `fan-out` command
|
|
81
|
-
|
|
82
|
-
**Problem:** `cli.ts` imports from 8 internal modules and references 23 external symbols. It's a 770+ line file that defines 34 commands inline. Each command's `.action()` handler does its own `openDb()` / `queries.X()` / `console.log()` / `db.close()` dance.
|
|
83
|
-
|
|
84
|
-
**Fix:** This isn't a bug — CLIs are inherently high fan-out. But if the file keeps growing, the repetitive `openDb` → query → format → `close` pattern could be extracted into a `runQuery(queryFn, formatter)` wrapper that handles the lifecycle. Each command would then be ~3 lines instead of ~15.
|
|
85
|
-
|
|
86
|
-
---
|
|
87
|
-
|
|
88
|
-
## 7. `similar-files` shows 100% similarity across all query modules
|
|
89
|
-
|
|
90
|
-
**Found by:** `similar-files --min-similarity 0.7`
|
|
91
|
-
|
|
92
|
-
**Problem:** Every query file depends on the same 3 files: `db.ts`, `types.ts`, `symbol-parser.ts`. This makes the file-level similarity metric saturate at 100%. It's not a code quality issue — it's a signal that the dependency profile is too uniform to distinguish files at this level.
|
|
93
|
-
|
|
94
|
-
**Implication for the tool itself:** The `similar-files` command should probably discount "universal" dependencies (files imported by >50% of the codebase) to surface more meaningful similarity. Universal deps like `types.ts` are infrastructure, not similarity signals.
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## 8. Callee-set queries repeat identical SQL in `similar.ts` and `extract-candidates.ts`
|
|
99
|
-
|
|
100
|
-
**Found by:** `similar` command (78% overlap between those two files)
|
|
101
|
-
|
|
102
|
-
**Problem:** Both `similar.ts` and `extract-candidates.ts` run the same "find all callees of a symbol within its definition range" SQL query. `similar.ts` has it in `findCallees()` (line ~120) and `getAllCalleeFingerprints()` (line ~175). `extract-candidates.ts` has it inline (line ~55).
|
|
103
|
-
|
|
104
|
-
**Fix:** Extract a `getCalleesForSymbol(db, documentId, startLine, endLine, symbolId)` helper. Used by `similar.ts` (twice) and `extract-candidates.ts` (once). Also usable by `call-graph.ts` which runs a similar query.
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## 9. Deep chains are all rooted at `queries/index.ts` → `cli.ts`
|
|
109
|
-
|
|
110
|
-
**Found by:** `deep-chains --min-depth 4`
|
|
111
|
-
|
|
112
|
-
**Problem:** Every deep chain starts at `index.ts` or `cli.ts` because they're the barrel/entry points. The chains themselves are only depth 4-5, which is healthy. No action needed — this confirms the architecture is flat.
|
|
113
|
-
|
|
114
|
-
**Assessment:** Not an issue. Healthy architecture signal.
|
|
115
|
-
|
|
116
|
-
---
|
|
117
|
-
|
|
118
|
-
## 10. No circular dependencies
|
|
119
|
-
|
|
120
|
-
**Found by:** `cycles` command
|
|
121
|
-
|
|
122
|
-
**Assessment:** Clean. No action needed.
|
|
123
|
-
|
|
124
|
-
---
|
|
125
|
-
|
|
126
|
-
## Summary
|
|
127
|
-
|
|
128
|
-
| # | Finding | Severity | Effort | Lines saved |
|
|
129
|
-
|---|---------|----------|--------|-------------|
|
|
130
|
-
| 1 | Inline `symbolNoise` filters (26x) | Medium | Low | ~50 |
|
|
131
|
-
| 2 | Inline `node_modules` exclusions (32x) | Medium | Low | ~30 |
|
|
132
|
-
| 3 | Duplicated graph-building SQL (4 files) | Medium | Low | ~54 |
|
|
133
|
-
| 4 | Duplicated test-file patterns (3 files) | Low | Low | ~15 |
|
|
134
|
-
| 5 | Barrel bottleneck (tree-shaking) | Low | Medium | 0 (structure) |
|
|
135
|
-
| 6 | CLI fan-out / repetitive handlers | Low | Medium | ~100 |
|
|
136
|
-
| 7 | `similar-files` universal dep discount | Low | Medium | 0 (algorithm) |
|
|
137
|
-
| 8 | Duplicated callee-set SQL | Medium | Low | ~30 |
|
|
138
|
-
| 9 | Deep chains rooted at entry points | None | — | — |
|
|
139
|
-
| 10 | No cycles | None | — | — |
|
|
140
|
-
|
|
141
|
-
**Quick wins (items 1-4, 8):** ~180 lines eliminated, 5 shared helpers, ~30 minutes of work. All low-risk mechanical extractions.
|
|
142
|
-
|
|
143
|
-
**Structural improvements (items 5-7):** Algorithm and architecture changes that improve the tool's own quality and the accuracy of its similarity detection. Medium effort, high value for the product.
|
package/PLAN.md
DELETED
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
# Implementation Plan: Phase 2 Commands + Agent Documentation
|
|
2
|
-
|
|
3
|
-
This plan adds 10 new analysis commands and a comprehensive agent usage guide. Organized into 4 phases to keep diffs bounded and independently testable.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Phase 1: Transitive Impact + Change Planning (3 commands)
|
|
8
|
-
|
|
9
|
-
These serve Use Case 1 — deep understanding for concrete planning.
|
|
10
|
-
|
|
11
|
-
### 1.1 `affected <symbol>`
|
|
12
|
-
|
|
13
|
-
**Purpose:** Full transitive closure of symbols that could break if a given symbol changes. Walks rdeps recursively at the symbol level (not just file level like `rdeps`).
|
|
14
|
-
|
|
15
|
-
**Implementation:**
|
|
16
|
-
- New file: `src/queries/affected.ts`
|
|
17
|
-
- Algorithm: BFS from the target symbol through the mention graph. For each symbol that references the target, find symbols that reference *that* symbol, and so on. Track depth. Cap at configurable max depth (default 5) to avoid full-graph traversal on hub symbols.
|
|
18
|
-
- Reuse: `findFirstSymbolMatch()` from `query-support.ts` to resolve the target.
|
|
19
|
-
- SQL core: Recursive walk on `mentions` (role=0) → `defn_enclosing_ranges` → back to `mentions`. Each hop finds the enclosing symbol of each reference site, then finds references to *that* symbol.
|
|
20
|
-
- Output type: `AffectedResult` — array of `{ symbol, shortName, file, depth }` sorted by depth then file.
|
|
21
|
-
- CLI: `scip-query affected <symbol> [--max-depth N] [--scope path]`
|
|
22
|
-
|
|
23
|
-
**Value:** "If I change this function's signature, what's the full blast wave?" Direct rdeps are depth 1. Their consumers are depth 2. This shows the full picture.
|
|
24
|
-
|
|
25
|
-
### 1.2 `change-surface <file>`
|
|
26
|
-
|
|
27
|
-
**Purpose:** Pre-change briefing: "I'm about to modify this file. What do I need to know?"
|
|
28
|
-
|
|
29
|
-
**Implementation:**
|
|
30
|
-
- New file: `src/queries/change-surface.ts`
|
|
31
|
-
- Composes existing queries internally — not a raw SQL query but an orchestrator:
|
|
32
|
-
1. Call `symbols()` to get all symbols in the file
|
|
33
|
-
2. For each symbol, call the refs logic to count external consumers
|
|
34
|
-
3. Call `testCoverage()` to check which symbols are test-covered
|
|
35
|
-
4. Call `fanIn()` to get reference counts
|
|
36
|
-
- Output type: `ChangeSurfaceResult` — per-symbol: `{ symbol, shortName, externalConsumers: number, testFiles: string[], riskLevel: 'low' | 'medium' | 'high' }` where risk = high if fan-in > 10 and no test coverage.
|
|
37
|
-
- CLI: `scip-query change-surface <file>`
|
|
38
|
-
|
|
39
|
-
**Value:** One command before modifying a file. Shows what's exported, who uses it, what's tested, and what's risky.
|
|
40
|
-
|
|
41
|
-
### 1.3 `diff-impact`
|
|
42
|
-
|
|
43
|
-
**Purpose:** Given the current git diff, compute the affected symbol set.
|
|
44
|
-
|
|
45
|
-
**Implementation:**
|
|
46
|
-
- New file: `src/queries/diff-impact.ts`
|
|
47
|
-
- Algorithm:
|
|
48
|
-
1. Run `git diff --name-only HEAD` (via `execFileSync`) to get changed files
|
|
49
|
-
2. For each changed file, get all symbols defined in it via `symbols()` logic
|
|
50
|
-
3. For each symbol, get its fan-in count and test coverage
|
|
51
|
-
4. Aggregate: total changed symbols, total consumers affected, test coverage gaps
|
|
52
|
-
- Output type: `DiffImpactResult` — `{ changedFiles, changedSymbols[], affectedConsumers[], uncoveredSymbols[], summary }`
|
|
53
|
-
- CLI: `scip-query diff-impact [--base <ref>]` (default: diff against HEAD)
|
|
54
|
-
- Note: Needs git available. If not in a git repo, error gracefully.
|
|
55
|
-
|
|
56
|
-
**Value:** "You changed 3 files — here are the 47 symbols affected, the 12 files that consume them, and the 5 gaps in test coverage."
|
|
57
|
-
|
|
58
|
-
### Phase 1 files to create:
|
|
59
|
-
- `src/queries/affected.ts`
|
|
60
|
-
- `src/queries/change-surface.ts`
|
|
61
|
-
- `src/queries/diff-impact.ts`
|
|
62
|
-
- Types added to `src/types.ts`
|
|
63
|
-
- Exports added to `src/queries/index.ts`
|
|
64
|
-
- CLI commands added to `src/cli.ts`
|
|
65
|
-
|
|
66
|
-
### Phase 1 files to modify:
|
|
67
|
-
- `src/types.ts` — add `AffectedResult`, `ChangeSurfaceResult`, `DiffImpactResult`
|
|
68
|
-
- `src/queries/index.ts` — add exports
|
|
69
|
-
- `src/cli.ts` — add 3 commands
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## Phase 2: De-bloating Commands (5 commands)
|
|
74
|
-
|
|
75
|
-
These serve Use Case 2 — keeping the codebase clean.
|
|
76
|
-
|
|
77
|
-
### 2.1 `drift [module]`
|
|
78
|
-
|
|
79
|
-
**Purpose:** Detect pattern drift — files that don't match the typical dependency profile for their directory.
|
|
80
|
-
|
|
81
|
-
**Implementation:**
|
|
82
|
-
- New file: `src/queries/drift.ts`
|
|
83
|
-
- Algorithm:
|
|
84
|
-
1. Build file dep profiles per directory (group files by their parent dir)
|
|
85
|
-
2. For each directory with 3+ files, compute the "median" dependency set — deps that appear in >50% of files in that dir
|
|
86
|
-
3. For each file, compute how much it deviates from the median: which expected deps are missing, which unexpected deps are present
|
|
87
|
-
4. Score deviation as a percentage. Report files with highest deviation.
|
|
88
|
-
- Reuse: `buildFileDepGraph()` from `query-support.ts` for the dep edges.
|
|
89
|
-
- Output type: `DriftResult` — `{ file, directory, deviationPercent, missingExpectedDeps[], unexpectedDeps[] }`
|
|
90
|
-
- CLI: `scip-query drift [module] [--min-deviation N]` (default min-deviation: 30%)
|
|
91
|
-
|
|
92
|
-
**Value:** Finds the files that don't follow the conventions of their neighbors. If 8 of 10 services import a validator and 2 don't, those 2 are flagged.
|
|
93
|
-
|
|
94
|
-
### 2.2 `wrapper-candidates`
|
|
95
|
-
|
|
96
|
-
**Purpose:** Find symbols that are only ever called through one intermediary — premature abstractions that add indirection without value.
|
|
97
|
-
|
|
98
|
-
**Implementation:**
|
|
99
|
-
- New file: `src/queries/wrapper-candidates.ts`
|
|
100
|
-
- Algorithm:
|
|
101
|
-
1. Find all symbols with fan-in = 1 (exactly one caller)
|
|
102
|
-
2. For each, check if that single caller has fan-in > 3 (is widely used)
|
|
103
|
-
3. If so, the single-caller symbol is a wrapper candidate — it could be inlined into its caller
|
|
104
|
-
4. Also check LOC: small wrappers (< 10 LOC) are the strongest candidates
|
|
105
|
-
- SQL: Subquery on `mentions` grouped by `symbol_id`, `HAVING COUNT(DISTINCT document_id) = 1`, then join to find the caller's fan-in.
|
|
106
|
-
- Output type: `WrapperCandidate` — `{ symbol, shortName, file, loc, singleCaller, callerFanIn }`
|
|
107
|
-
- CLI: `scip-query wrapper-candidates [--scope path] [--max-loc N]`
|
|
108
|
-
|
|
109
|
-
**Value:** "This function exists only to call another function. You can inline it." Catches over-engineering.
|
|
110
|
-
|
|
111
|
-
### 2.3 `passthrough-candidates`
|
|
112
|
-
|
|
113
|
-
**Purpose:** Find functions that just forward to one other function without adding logic.
|
|
114
|
-
|
|
115
|
-
**Implementation:**
|
|
116
|
-
- New file: `src/queries/passthrough-candidates.ts`
|
|
117
|
-
- Algorithm:
|
|
118
|
-
1. Find symbols with exactly 1 callee (they only call one external thing)
|
|
119
|
-
2. Filter to small functions (< 15 LOC)
|
|
120
|
-
3. These are likely passthroughs: `getUser(id) { return userRepo.findById(id); }`
|
|
121
|
-
- Reuse: `getCalleeRowsForSymbol()` from `query-support.ts` to count callees.
|
|
122
|
-
- Output type: `PassthroughCandidate` — `{ symbol, shortName, file, loc, forwardsTo, forwardsToFile }`
|
|
123
|
-
- CLI: `scip-query passthrough-candidates [--scope path] [--max-loc N]`
|
|
124
|
-
|
|
125
|
-
**Value:** Finds functions that are pure indirection. Either inline them or verify they exist for a reason (dependency inversion, testing boundary, etc.)
|
|
126
|
-
|
|
127
|
-
### 2.4 `stale-abstractions`
|
|
128
|
-
|
|
129
|
-
**Purpose:** Find interfaces/base classes/generics with exactly 1 implementation or 1 caller.
|
|
130
|
-
|
|
131
|
-
**Implementation:**
|
|
132
|
-
- New file: `src/queries/stale-abstractions.ts`
|
|
133
|
-
- Algorithm:
|
|
134
|
-
1. Find type-level symbols (using `#` in the SCIP symbol — indicates class/interface/type)
|
|
135
|
-
2. For each, count cross-file references (fan-in)
|
|
136
|
-
3. Symbols with fan-in = 1 are single-consumer abstractions
|
|
137
|
-
4. Cross-reference with LOC: large single-consumer abstractions are the most wasteful
|
|
138
|
-
- SQL: Filter `gs.symbol LIKE '%#%'` (type-level), then count mentions with role=0 from different documents.
|
|
139
|
-
- Output type: `StaleAbstraction` — `{ symbol, shortName, file, loc, consumers: number, implementors: number }`
|
|
140
|
-
- CLI: `scip-query stale-abstractions [--scope path] [--min-loc N]`
|
|
141
|
-
|
|
142
|
-
**Value:** An interface with one implementation isn't an abstraction — it's indirection. A generic helper called from one place isn't reusable — it's premature.
|
|
143
|
-
|
|
144
|
-
### 2.5 `complexity-hotspots`
|
|
145
|
-
|
|
146
|
-
**Purpose:** Composite complexity score per symbol combining LOC, fan-in, fan-out, and callee count.
|
|
147
|
-
|
|
148
|
-
**Implementation:**
|
|
149
|
-
- New file: `src/queries/complexity-hotspots.ts`
|
|
150
|
-
- Algorithm:
|
|
151
|
-
1. For each non-trivial symbol (>= minLoc), compute:
|
|
152
|
-
- LOC (from defn_enclosing_ranges)
|
|
153
|
-
- Fan-in (count of distinct referencing documents)
|
|
154
|
-
- Fan-out (count of distinct referenced symbols in other files)
|
|
155
|
-
- Callee count (total callees within definition range)
|
|
156
|
-
2. Score = `(LOC / 50) * (fanIn / 5) * (fanOut / 5)` (normalized so a 50-LOC function with 5 consumers and 5 callees scores ~1.0)
|
|
157
|
-
3. Sort by score descending
|
|
158
|
-
- Reuse: Similar SQL patterns to `bottlenecks.ts` and `fan.ts`.
|
|
159
|
-
- Output type: `ComplexityHotspot` — `{ symbol, shortName, file, loc, fanIn, fanOut, calleeCount, score }`
|
|
160
|
-
- CLI: `scip-query complexity-hotspots [--scope path] [--min-loc N] [-n limit]`
|
|
161
|
-
|
|
162
|
-
**Value:** The symbols with the highest scores are the ones most likely to contain bugs, be hardest to modify, and benefit most from decomposition. Combines multiple signals into one prioritized view.
|
|
163
|
-
|
|
164
|
-
### Phase 2 files to create:
|
|
165
|
-
- `src/queries/drift.ts`
|
|
166
|
-
- `src/queries/wrapper-candidates.ts`
|
|
167
|
-
- `src/queries/passthrough-candidates.ts`
|
|
168
|
-
- `src/queries/stale-abstractions.ts`
|
|
169
|
-
- `src/queries/complexity-hotspots.ts`
|
|
170
|
-
|
|
171
|
-
### Phase 2 files to modify:
|
|
172
|
-
- `src/types.ts` — add 5 result types
|
|
173
|
-
- `src/queries/index.ts` — add 5 exports
|
|
174
|
-
- `src/cli.ts` — add 5 commands
|
|
175
|
-
|
|
176
|
-
---
|
|
177
|
-
|
|
178
|
-
## Phase 3: Composite Health Report (2 commands)
|
|
179
|
-
|
|
180
|
-
### 3.1 `health`
|
|
181
|
-
|
|
182
|
-
**Purpose:** Single command that runs all de-bloat analyses and produces a prioritized action list.
|
|
183
|
-
|
|
184
|
-
**Implementation:**
|
|
185
|
-
- New file: `src/queries/health.ts`
|
|
186
|
-
- Algorithm: Run each analysis in sequence, aggregate results:
|
|
187
|
-
1. `dead()` → count dead symbols, total recoverable LOC
|
|
188
|
-
2. `isolated()` → count orphaned symbols
|
|
189
|
-
3. `cycles()` → count circular deps
|
|
190
|
-
4. `similarAll()` → count high-similarity pairs
|
|
191
|
-
5. `extractCandidates()` → count extraction opportunities
|
|
192
|
-
6. `wrapperCandidates()` → count wrapper symbols (new, from Phase 2)
|
|
193
|
-
7. `passthroughCandidates()` → count passthroughs (new, from Phase 2)
|
|
194
|
-
8. `staleAbstractions()` → count single-consumer types (new, from Phase 2)
|
|
195
|
-
9. `drift()` → count drifted files (new, from Phase 2)
|
|
196
|
-
10. `complexityHotspots()` → top 5 most complex symbols (new, from Phase 2)
|
|
197
|
-
- Output: Grouped sections with counts and top items. A "health score" (0-100) based on weighted findings. Concrete action items sorted by effort/impact.
|
|
198
|
-
- Output type: `HealthReport` — sections for each analysis, overall score, prioritized action list.
|
|
199
|
-
- CLI: `scip-query health [--scope path] [--json]` (JSON mode for programmatic consumption by agents)
|
|
200
|
-
|
|
201
|
-
**Value:** The difference between "powerful tool for experts" and "tool that actually gets used." One command, one report, one action list.
|
|
202
|
-
|
|
203
|
-
### 3.2 `convergence <symbol1> <symbol2>`
|
|
204
|
-
|
|
205
|
-
**Purpose:** Given two similar symbols (flagged by `similar`), show what a consolidated version would look like.
|
|
206
|
-
|
|
207
|
-
**Implementation:**
|
|
208
|
-
- New file: `src/queries/convergence.ts`
|
|
209
|
-
- Algorithm:
|
|
210
|
-
1. Get callee sets for both symbols (via `getCalleeRowsForSymbol()`)
|
|
211
|
-
2. Compute shared callees (the body of the consolidated function)
|
|
212
|
-
3. Compute unique-to-A and unique-to-B (the parameterization points)
|
|
213
|
-
4. Report: "The consolidated function would call [shared callees]. A's unique behavior ([unique-to-A]) and B's unique behavior ([unique-to-B]) become parameters or strategy arguments."
|
|
214
|
-
5. Also show the file locations and LOC of both symbols for context.
|
|
215
|
-
- Output type: `ConvergenceResult` — `{ symbolA, symbolB, sharedCallees[], uniqueToA[], uniqueToB[], consolidationStrategy }`
|
|
216
|
-
- CLI: `scip-query convergence <symbol1> <symbol2>`
|
|
217
|
-
|
|
218
|
-
**Value:** Turns a similarity finding into a concrete refactoring prescription. "These two are 75% similar" becomes "here's what the merged version looks like."
|
|
219
|
-
|
|
220
|
-
### Phase 3 files to create:
|
|
221
|
-
- `src/queries/health.ts`
|
|
222
|
-
- `src/queries/convergence.ts`
|
|
223
|
-
|
|
224
|
-
### Phase 3 files to modify:
|
|
225
|
-
- `src/types.ts` — add `HealthReport`, `ConvergenceResult`
|
|
226
|
-
- `src/queries/index.ts` — add 2 exports
|
|
227
|
-
- `src/cli.ts` — add 2 commands
|
|
228
|
-
|
|
229
|
-
### Phase 3 depends on: Phase 2 (health report calls Phase 2 commands)
|
|
230
|
-
|
|
231
|
-
---
|
|
232
|
-
|
|
233
|
-
## Phase 4: Agent Usage Guide + Use Case Documentation
|
|
234
|
-
|
|
235
|
-
### 4.1 `docs/AGENT_GUIDE.md`
|
|
236
|
-
|
|
237
|
-
Comprehensive guide for AI agents (and humans) on how to use scip-query for specific goals. Structured as goal-oriented workflows, not command reference (that's already in README.md).
|
|
238
|
-
|
|
239
|
-
**Sections:**
|
|
240
|
-
|
|
241
|
-
#### "I need to understand how a system works before making changes"
|
|
242
|
-
1. Start with `system <module>` for the full map
|
|
243
|
-
2. Pick the entry point and run `call-graph <symbol>` to see what it calls and who calls it
|
|
244
|
-
3. Run `deps <file>` and `rdeps <file>` to map the file-level dependency boundary
|
|
245
|
-
4. Run `surface <module>` to understand the true public API (not just what's exported)
|
|
246
|
-
5. Run `trace <symbol>` for any specific symbol you need to understand
|
|
247
|
-
6. Run `change-surface <file>` for a pre-change briefing on anything you're about to modify
|
|
248
|
-
7. Run `diff-impact` after making changes to verify the blast radius
|
|
249
|
-
|
|
250
|
-
#### "I need to write a concrete implementation plan"
|
|
251
|
-
1. Run `system <module>` to understand the target area
|
|
252
|
-
2. Run `symbols <file>` on each file you'll modify to get line ranges and signatures
|
|
253
|
-
3. Run `surface <module>` to identify the public contract you must preserve
|
|
254
|
-
4. Run `refs <symbol>` for any symbol you plan to change, rename, or remove
|
|
255
|
-
5. Run `affected <symbol>` for transitive impact on critical symbols
|
|
256
|
-
6. Run `fan-in <symbol>` to quantify blast radius for each change
|
|
257
|
-
7. Run `test-coverage <symbol>` to identify test gaps before you start
|
|
258
|
-
|
|
259
|
-
#### "I want to clean up and de-bloat a codebase"
|
|
260
|
-
1. Run `health` for the full prioritized report (start here)
|
|
261
|
-
2. Address dead code first: `dead --min-loc 10 --skip-barrels` → safe deletions
|
|
262
|
-
3. Address isolated symbols: `isolated --min-loc 5` → completely safe deletions
|
|
263
|
-
4. Break cycles: `cycles` → structural fixes
|
|
264
|
-
5. Reduce duplication: `similar --min-similarity 0.6` → consolidation candidates
|
|
265
|
-
6. For each similar pair, run `convergence <a> <b>` to get the refactoring prescription
|
|
266
|
-
7. Find extraction opportunities: `extract-candidates --min-loc 20`
|
|
267
|
-
8. Remove unnecessary indirection: `wrapper-candidates`, `passthrough-candidates`
|
|
268
|
-
9. Prune premature abstractions: `stale-abstractions`
|
|
269
|
-
10. Fix pattern drift: `drift` → bring outlier files into line with their neighbors
|
|
270
|
-
|
|
271
|
-
#### "I want to assess code quality and risk"
|
|
272
|
-
1. Run `health` for the overall score
|
|
273
|
-
2. Run `complexity-hotspots -n 20` for the riskiest symbols
|
|
274
|
-
3. Run `bottlenecks -n 20` for coupling pressure points
|
|
275
|
-
4. Run `deep-chains --min-depth 5` for architectural layering issues
|
|
276
|
-
5. Run `test-coverage` for the coverage percentage
|
|
277
|
-
6. Run `doc-coverage` for documentation gaps
|
|
278
|
-
|
|
279
|
-
#### "I want to understand the impact of a change I already made"
|
|
280
|
-
1. Run `diff-impact` to see what your changes affect
|
|
281
|
-
2. Run `affected <symbol>` for any symbol you modified
|
|
282
|
-
3. Run `test-coverage <symbol>` for each affected symbol to find test gaps
|
|
283
|
-
|
|
284
|
-
#### Command cheat sheet
|
|
285
|
-
Quick reference table: "If you want to know X, run Y."
|
|
286
|
-
|
|
287
|
-
### 4.2 Update `README.md`
|
|
288
|
-
|
|
289
|
-
- Add link to AGENT_GUIDE.md in the README
|
|
290
|
-
- Add a "Workflows" section that links to the guide
|
|
291
|
-
- Update command count and command table with new Phase 1-3 commands
|
|
292
|
-
|
|
293
|
-
### Phase 4 files to create:
|
|
294
|
-
- `docs/AGENT_GUIDE.md`
|
|
295
|
-
|
|
296
|
-
### Phase 4 files to modify:
|
|
297
|
-
- `README.md` — add workflows section, update command count
|
|
298
|
-
|
|
299
|
-
---
|
|
300
|
-
|
|
301
|
-
## Execution Order
|
|
302
|
-
|
|
303
|
-
```
|
|
304
|
-
Phase 1 (impact + planning) → 3 new commands, ~400 LOC
|
|
305
|
-
Phase 2 (de-bloating) → 5 new commands, ~500 LOC
|
|
306
|
-
Phase 3 (health + convergence) → 2 new commands, ~300 LOC
|
|
307
|
-
Phase 4 (documentation) → 1 new doc, README update
|
|
308
|
-
|
|
309
|
-
Total: 10 new commands, ~1200 LOC of query logic, 1 agent guide
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
Each phase is independently testable and committable. Phase 3 depends on Phase 2. Phase 4 depends on Phases 1-3 (references all commands). Phases 1 and 2 are independent and can be built in parallel.
|
|
313
|
-
|
|
314
|
-
### Shared infrastructure all phases will use:
|
|
315
|
-
- `query-support.ts` — `buildFileDepGraph()`, `findFirstSymbolMatch()`, `getCalleeRowsForSymbol()`
|
|
316
|
-
- `db.ts` — `pathExclusionsFor()`, `symbolNoiseFor()`, `symbolNoise`, `localSymbolPredicate`
|
|
317
|
-
- `clean-signature.ts` — for any command that displays signatures
|
|
318
|
-
- `symbol-parser.ts` — `shortenSymbol()` for all display output
|
|
319
|
-
|
|
320
|
-
No new shared infrastructure needed. The existing helpers cover all planned commands.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/extract-candidates.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { getCalleeRowsForSymbol } from '../query-support.js';\nimport type { ExtractCandidate } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find functions with natural extraction seams.\n *\n * A large function that references two distinct groups of symbols —\n * where group A's symbols are never co-referenced with group B's —\n * has a natural extraction boundary. The isolated cluster can likely\n * be pulled into its own function.\n *\n * We detect this by:\n * 1. Finding all callees of a function\n * 2. Building a co-occurrence graph (which callees appear in the same chunk)\n * 3. Finding connected components — disconnected components = extraction seams\n * 4. Scoring each cluster by how isolated it is from the rest\n */\nexport function extractCandidates(\n db: ScipDatabase,\n opts: { scope?: string; minLoc?: number; minCallees?: number; limit?: number } = {},\n): ExtractCandidate[] {\n const { scope, minLoc = 10, minCallees = 6, limit = 20 } = opts;\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Find functions large enough to consider\n const symbols = db.all<{\n id: number;\n symbol: string;\n document_id: number;\n start_line: number;\n end_line: number;\n relative_path: string;\n }>(\n `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n AND (der.end_line - der.start_line + 1) >= ?\n ${scopeFilter}\n ORDER BY (der.end_line - der.start_line + 1) DESC`,\n minLoc,\n );\n\n const results: ExtractCandidate[] = [];\n\n for (const sym of symbols) {\n if (db.isIgnored(sym.relative_path)) continue;\n\n // Skip pure type files — \"callees\" in a type file are just type references,\n // not function calls. Splitting type files is a cosmetic choice, not an\n // extraction opportunity.\n const basename = sym.relative_path.split('/').pop() ?? '';\n if (basename.includes('types')) continue;\n\n // Get callees with their chunk locations (to build co-occurrence)\n const calleeChunks = getCalleeRowsForSymbol(db, {\n documentId: sym.document_id,\n startLine: sym.start_line,\n endLine: sym.end_line,\n symbolId: sym.id,\n });\n\n // Collect unique callees\n const calleeSet = new Set(calleeChunks.map((c) => c.symbol));\n if (calleeSet.size < minCallees) continue;\n\n // Build co-occurrence graph: two callees are connected if they\n // appear in the same chunk (meaning they're used in proximity)\n const cooccurrence = new Map<string, Set<string>>();\n for (const callee of calleeSet) {\n cooccurrence.set(callee, new Set());\n }\n\n // Group by chunk\n const chunkToCallees = new Map<number, Set<string>>();\n for (const cc of calleeChunks) {\n if (!chunkToCallees.has(cc.chunkId)) chunkToCallees.set(cc.chunkId, new Set());\n chunkToCallees.get(cc.chunkId)!.add(cc.symbol);\n }\n\n // Callees in the same chunk are co-occurring\n for (const callees of chunkToCallees.values()) {\n const arr = [...callees];\n for (let i = 0; i < arr.length; i++) {\n for (let j = i + 1; j < arr.length; j++) {\n cooccurrence.get(arr[i]!)!.add(arr[j]!);\n cooccurrence.get(arr[j]!)!.add(arr[i]!);\n }\n }\n }\n\n // Find connected components via BFS\n const visited = new Set<string>();\n const clusters: Set<string>[] = [];\n\n for (const callee of calleeSet) {\n if (visited.has(callee)) continue;\n const cluster = new Set<string>();\n const queue = [callee];\n while (queue.length > 0) {\n const current = queue.pop()!;\n if (visited.has(current)) continue;\n visited.add(current);\n cluster.add(current);\n for (const neighbor of cooccurrence.get(current) ?? []) {\n if (!visited.has(neighbor)) queue.push(neighbor);\n }\n }\n clusters.push(cluster);\n }\n\n // Only interesting if there are multiple clusters (= extraction seams exist)\n if (clusters.length < 2) continue;\n\n // Score each cluster by isolation:\n // isolation = 1 - (edges to other clusters / total possible edges to other clusters)\n const scoredClusters = clusters\n .filter((c) => c.size >= 2) // single-callee clusters aren't interesting\n .map((cluster) => {\n const otherCallees = new Set<string>();\n for (const c of clusters) {\n if (c !== cluster) {\n for (const s of c) otherCallees.add(s);\n }\n }\n\n // Count cross-cluster edges\n let crossEdges = 0;\n for (const callee of cluster) {\n for (const neighbor of cooccurrence.get(callee) ?? []) {\n if (otherCallees.has(neighbor)) crossEdges++;\n }\n }\n\n const maxCrossEdges = cluster.size * otherCallees.size;\n const isolation = maxCrossEdges > 0 ? 1 - crossEdges / maxCrossEdges : 1;\n\n return {\n callees: [...cluster].map(shortenSymbol),\n isolation,\n };\n })\n .filter((c) => c.isolation > 0.5) // Only report well-isolated clusters\n .sort((a, b) => b.isolation - a.isolation);\n\n if (scoredClusters.length > 0) {\n results.push({\n symbol: sym.symbol,\n shortName: shortenSymbol(sym.symbol),\n relativePath: sym.relative_path,\n startLine: sym.start_line,\n endLine: sym.end_line,\n loc: sym.end_line - sym.start_line + 1,\n totalCallees: calleeSet.size,\n clusters: scoredClusters,\n });\n }\n }\n\n results.sort((a, b) => b.clusters.length - a.clusters.length || b.loc - a.loc);\n return results.slice(0, limit);\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-3UOUTZQT.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
|
@@ -1 +0,0 @@
|
|
|
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 != 1 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 != 1\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":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/wrapper-candidates.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { testFileExclusionSql } from '../query-support.js';\nimport type { WrapperCandidate } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find wrapper candidates: symbols called by only one other symbol.\n *\n * These are premature abstractions that add indirection without\n * providing reuse. A function with fan-in = 1 whose sole caller\n * is widely used is a strong signal of unnecessary wrapping.\n */\nexport function wrapperCandidates(\n db: ScipDatabase,\n opts?: { scope?: string; maxLoc?: number; limit?: number },\n): WrapperCandidate[] {\n const { scope, maxLoc = 15, limit = 30 } = opts ?? {};\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Find all symbols with exactly 1 cross-file consumer (fan-in = 1),\n // along with who that single caller is and the caller's own fan-in.\n const rows = db.all<{\n symbol: string;\n file: string;\n start_line: number;\n end_line: number;\n loc: number;\n caller_symbol: string;\n caller_fan_in: number;\n }>(\n `SELECT * FROM (\n SELECT\n gs.symbol,\n d.relative_path AS file,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n -- The single caller: the symbol whose definition range contains\n -- the chunk that references our target\n (SELECT caller_gs.symbol\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN defn_enclosing_ranges caller_der\n ON caller_der.document_id = ref_c.document_id\n AND ref_c.start_line >= caller_der.start_line\n AND ref_c.end_line <= caller_der.end_line\n JOIN global_symbols caller_gs ON caller_der.symbol_id = caller_gs.id\n WHERE ref_m.symbol_id = gs.id\n AND ref_m.role != 1\n AND ref_c.document_id != der.document_id\n LIMIT 1\n ) AS caller_symbol,\n -- Fan-in of that single caller\n (SELECT COUNT(DISTINCT caller_ref_c.document_id)\n FROM mentions caller_ref_m\n JOIN chunks caller_ref_c ON caller_ref_m.chunk_id = caller_ref_c.id\n WHERE caller_ref_m.symbol_id = (\n SELECT caller_der2.symbol_id\n FROM mentions ref_m2\n JOIN chunks ref_c2 ON ref_m2.chunk_id = ref_c2.id\n JOIN defn_enclosing_ranges caller_der2\n ON caller_der2.document_id = ref_c2.document_id\n AND ref_c2.start_line >= caller_der2.start_line\n AND ref_c2.end_line <= caller_der2.end_line\n WHERE ref_m2.symbol_id = gs.id\n AND ref_m2.role != 1\n AND ref_c2.document_id != der.document_id\n LIMIT 1\n )\n AND caller_ref_m.role != 1\n ) AS caller_fan_in\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n -- Only functions/terms, not type definitions (types with # are not wrappers)\n AND gs.symbol NOT LIKE '%#'\n AND (der.end_line - der.start_line + 1) <= ?\n AND (der.end_line - der.start_line + 1) >= 2\n ${scopeFilter}\n -- Exactly 1 cross-file consumer\n AND (\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\n AND ref_m.role != 1\n AND ref_c.document_id != der.document_id\n ) = 1\n ) WHERE caller_symbol IS NOT NULL AND caller_fan_in > 3\n ORDER BY caller_fan_in DESC, loc DESC\n LIMIT ?`,\n maxLoc, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.file))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n file: r.file,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n singleCaller: r.caller_symbol,\n singleCallerShort: shortenSymbol(r.caller_symbol),\n callerFanIn: r.caller_fan_in,\n }));\n}\n"],"mappings":";;;;;;;;AAYO,SAAS,kBACd,IACA,MACoB;AACpB,QAAM,EAAE,OAAO,SAAS,IAAI,QAAQ,GAAG,IAAI,QAAQ,CAAC;AACpD,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAItE,QAAM,OAAO,GAAG;AAAA,IASd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UA6CM,GAAG,kBAAkB,GAAG,CAAC;AAAA,cACrB,qBAAqB,GAAG,CAAC;AAAA,UAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,UAKvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAajB;AAAA,IAAQ;AAAA,EACV;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,MAAM,EAAE;AAAA,IACR,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,KAAK,EAAE;AAAA,IACP,cAAc,EAAE;AAAA,IAChB,mBAAmB,cAAc,EAAE,aAAa;AAAA,IAChD,aAAa,EAAE;AAAA,EACjB,EAAE;AACN;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
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":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/complexity.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { ComplexityResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Per-symbol complexity analysis combining source-level branch counting\n * with index-level metrics (fan-in, fan-out, callee count).\n *\n * Branch counting uses language-aware regex. The language is read from\n * the SCIP documents table, so it works for any indexed language.\n */\nexport function complexity(\n db: ScipDatabase,\n symbolPattern: string,\n): ComplexityResult | null {\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n // Get language\n const doc = db.get<{ language: string | null }>(\n `SELECT language FROM documents WHERE relative_path = ?`,\n match.relativePath,\n );\n const language = doc?.language ?? 'unknown';\n\n // Read source for branch counting\n const filePath = join(db.config.projectRoot, match.relativePath);\n let source = '';\n try {\n const lines = readFileSync(filePath, 'utf-8').split('\\n');\n source = lines.slice(match.startLine, match.endLine + 1).join('\\n');\n } catch {\n // If we can't read the file, just skip branch counting\n }\n\n const branches = countBranches(source, language);\n const loc = match.endLine - match.startLine + 1;\n\n // Callee count\n const callees = getCalleeRowsForSymbol(db, match);\n const uniqueCallees = new Set(callees.map((c) => c.symbol));\n\n // Fan-in\n const fanInRow = db.get<{ c: number }>(\n `SELECT COUNT(DISTINCT c.document_id) AS c\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = ? AND m.role != 1`,\n match.symbolId,\n );\n\n // Fan-out (callees in other files)\n const fanOut = new Set(\n callees.filter((c) => c.file !== match.relativePath).map((c) => c.symbol),\n ).size;\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n relativePath: match.relativePath,\n startLine: match.startLine,\n endLine: match.endLine,\n loc,\n branches,\n cyclomaticEstimate: branches + 1,\n calleeCount: uniqueCallees.size,\n fanIn: fanInRow?.c ?? 0,\n fanOut,\n };\n}\n\n/**\n * Count branch points in source code using language-aware regex.\n * Works across all SCIP-supported languages.\n */\nfunction countBranches(source: string, language: string): number {\n // Strip comments and strings to avoid false positives\n const stripped = stripCommentsAndStrings(source);\n let count = 0;\n\n // Universal branch keywords (work across most C-family languages)\n const universalPatterns = [\n /\\bif\\b/g,\n /\\belse\\s+if\\b/g,\n /\\belse\\b/g,\n /\\bfor\\b/g,\n /\\bwhile\\b/g,\n /\\bswitch\\b/g,\n /\\bcase\\b/g,\n /\\bcatch\\b/g,\n /\\?\\s*[^?]/g, // ternary (but not ??)\n /&&/g,\n /\\|\\|/g,\n ];\n\n for (const pattern of universalPatterns) {\n const matches = stripped.match(pattern);\n if (matches) count += matches.length;\n }\n\n // Language-specific patterns\n if (language === 'python') {\n const pyPatterns = [/\\belif\\b/g, /\\bexcept\\b/g, /\\bfinally\\b/g];\n for (const p of pyPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n } else if (language === 'rust') {\n const rustPatterns = [/\\bmatch\\b/g, /=>/g, /\\bloop\\b/g];\n for (const p of rustPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n } else if (language === 'ruby') {\n const rubyPatterns = [/\\belsif\\b/g, /\\bunless\\b/g, /\\brescue\\b/g, /\\bwhen\\b/g];\n for (const p of rubyPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n } else if (language === 'go') {\n const goPatterns = [/\\bselect\\b/g, /\\bdefer\\b/g];\n for (const p of goPatterns) {\n const m = stripped.match(p);\n if (m) count += m.length;\n }\n }\n\n return count;\n}\n\n/**\n * Rough strip of comments and string literals to reduce false positives\n * in branch counting. Not perfect but good enough for estimation.\n */\nfunction stripCommentsAndStrings(source: string): string {\n return source\n // Block comments\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n // Line comments\n .replace(/\\/\\/.*/g, '')\n // Python/Ruby line comments\n .replace(/#.*/g, '')\n // Double-quoted strings\n .replace(/\"(?:[^\"\\\\]|\\\\.)*\"/g, '\"\"')\n // Single-quoted strings\n .replace(/'(?:[^'\\\\]|\\\\.)*'/g, \"''\")\n // Template literals\n .replace(/`(?:[^`\\\\]|\\\\.)*`/g, '``');\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
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 != 1\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 != 1\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":[]}
|