scip-query 0.2.0 → 0.3.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-26DOJ63W.js +161 -0
- package/dist/chunk-2UELLEBI.js +1 -0
- package/dist/{chunk-4PDAL6IL.js → chunk-4JCSOF2O.js} +3 -3
- package/dist/{chunk-6SXADWLW.js → chunk-5OMVSV6E.js} +13 -5
- package/dist/{chunk-KPPHZCZJ.js → chunk-7HK5ZLOE.js} +28 -46
- package/dist/{chunk-7RLE5EWE.js → chunk-7KIMF5PV.js} +34 -13
- package/dist/chunk-AXQKUYKF.js +1442 -0
- package/dist/chunk-C7H5WBTJ.js +46 -0
- package/dist/{chunk-NHBZIL2J.js → chunk-CHDJXYBG.js} +3 -28
- package/dist/{chunk-KCBMVQL5.js → chunk-CPVAQJEC.js} +13 -5
- package/dist/{chunk-ZQIIPFD7.js → chunk-DH7G3DDV.js} +2 -2
- package/dist/{chunk-BOVXCR46.js → chunk-EOROMIFO.js} +14 -6
- package/dist/{chunk-2CKGIR6G.js → chunk-EPWLXXBL.js} +3 -3
- package/dist/{chunk-5RMYT5WH.js → chunk-F7XU27LU.js} +2 -2
- package/dist/{chunk-63G7IQTD.js → chunk-FYYOWQXK.js} +20 -40
- package/dist/chunk-GEXE2T6I.js +87 -0
- package/dist/{chunk-DGUPQSOR.js → chunk-GJDHTTR2.js} +11 -4
- package/dist/chunk-GSH2FPKV.js +87 -0
- package/dist/{chunk-NUZ4OMU3.js → chunk-GU2H5QRN.js} +2 -2
- package/dist/{chunk-Z6YZJ36C.js → chunk-HJZUSUPU.js} +8 -4
- package/dist/{chunk-LAWMH22O.js → chunk-HLKAFWWJ.js} +82 -3
- package/dist/{chunk-HPFZLISB.js → chunk-HLUS2HEB.js} +2 -2
- package/dist/{chunk-H2MDONBU.js → chunk-J3JSOSUO.js} +9 -6
- package/dist/{chunk-7PBOG4YE.js → chunk-KBOQX573.js} +2 -2
- package/dist/{chunk-HDSRORNV.js → chunk-KKCHYLVI.js} +17 -11
- package/dist/{chunk-HMLMH7VZ.js → chunk-LFJQVJYJ.js} +2 -2
- package/dist/{chunk-4EXL2CUA.js → chunk-LQJUPXQY.js} +16 -8
- package/dist/{chunk-ITZ3DDOG.js → chunk-MPGIHELS.js} +18 -3
- package/dist/chunk-NFS5W3PP.js +37 -0
- package/dist/chunk-NG5F43OU.js +200 -0
- package/dist/{chunk-DCKMSTJ4.js → chunk-O7Q7FDUJ.js} +22 -14
- package/dist/{chunk-UJQN5N3I.js → chunk-OIDHN6GD.js} +6 -3
- package/dist/{chunk-Z4GHE2HD.js → chunk-P3E6L7KW.js} +6 -2
- package/dist/{chunk-BNN2RKD2.js → chunk-P4WO3BBW.js} +3 -3
- package/dist/{chunk-QOV2R2WT.js → chunk-QIXNAB5K.js} +42 -2
- package/dist/{chunk-NWCE4CIC.js → chunk-SMDCNPMK.js} +11 -28
- package/dist/{chunk-SEFSL2GF.js → chunk-TOIEB3LG.js} +2 -2
- package/dist/{chunk-4XHWPRAX.js → chunk-UGQKAVCD.js} +3 -3
- package/dist/{chunk-OVPLOMPY.js → chunk-UQEQ6AHX.js} +7 -4
- package/dist/{chunk-ZK6GXM3J.js → chunk-VIYSWZCO.js} +3 -3
- package/dist/chunk-VJJKSGIX.js +121 -0
- package/dist/{chunk-N5KEREIA.js → chunk-VT4JBH6L.js} +19 -7
- package/dist/{chunk-7LLPRPR5.js → chunk-WGAD3GNR.js} +2 -2
- package/dist/chunk-YDBXNPYU.js +69 -0
- package/dist/chunk-YY4QGUQ5.js +84 -0
- package/dist/{chunk-FGXRVW7G.js → chunk-YZ6L7GFO.js} +2 -2
- package/dist/{chunk-W4ALF422.js → chunk-ZEUCXQBN.js} +3 -3
- package/dist/cli.js +2697 -1054
- package/dist/{db-BNVVZSfP.d.ts → db-ShvwGDKf.d.ts} +12 -19
- package/dist/index.d.ts +15 -15
- package/dist/index.js +263 -234
- package/dist/postinstall.js +5 -76
- package/dist/queries/affected.d.ts +1 -1
- package/dist/queries/affected.js +3 -3
- package/dist/queries/bottlenecks.d.ts +1 -1
- package/dist/queries/bottlenecks.js +2 -2
- package/dist/queries/by-kind.d.ts +1 -1
- package/dist/queries/by-kind.js +2 -2
- package/dist/queries/call-graph.d.ts +1 -1
- package/dist/queries/call-graph.js +3 -3
- package/dist/queries/change-surface.d.ts +2 -2
- package/dist/queries/change-surface.js +3 -3
- package/dist/queries/code.d.ts +1 -1
- package/dist/queries/code.js +3 -3
- package/dist/queries/complexity-hotspots.d.ts +1 -1
- package/dist/queries/complexity-hotspots.js +3 -3
- package/dist/queries/complexity.d.ts +1 -1
- package/dist/queries/complexity.js +3 -3
- package/dist/queries/convergence.d.ts +1 -1
- package/dist/queries/convergence.js +3 -3
- package/dist/queries/coupling.d.ts +1 -1
- package/dist/queries/coupling.js +3 -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/deps.js +3 -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 +3 -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 +46 -51
- package/dist/queries/isolated.d.ts +1 -1
- package/dist/queries/isolated.js +4 -3
- package/dist/queries/members.d.ts +2 -2
- package/dist/queries/members.js +3 -2
- package/dist/queries/methods.d.ts +1 -1
- package/dist/queries/methods.js +2 -2
- package/dist/queries/outline.d.ts +1 -1
- package/dist/queries/outline.js +3 -2
- package/dist/queries/passthrough-candidates.d.ts +1 -1
- package/dist/queries/passthrough-candidates.js +3 -3
- package/dist/queries/redundant-reexports.d.ts +1 -1
- package/dist/queries/redundant-reexports.js +4 -2
- package/dist/queries/refs.d.ts +1 -1
- package/dist/queries/refs.js +3 -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 +2 -2
- 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 +3 -2
- package/dist/queries/symbols.d.ts +1 -1
- package/dist/queries/symbols.js +3 -2
- package/dist/queries/system.d.ts +1 -1
- package/dist/queries/system.js +3 -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 +0 -55
- 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 +0 -65
- 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 +0 -46
- 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 +0 -103
- 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 +0 -158
- 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
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/code.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch } from '../query-support.js';\nimport type { CodeResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Read the source code for a symbol, bounded to its definition range.\n * Language-agnostic: just reads the file and extracts the relevant lines.\n *\n * Accepts:\n * - Symbol name pattern: \"processVegaMention\"\n * - Full short name: \"src:modules:chat:processVegaMention\"\n * - File:line-line syntax: \"src/chat/service.ts:100-200\"\n */\nexport function code(\n db: ScipDatabase,\n symbolPattern: string,\n opts: { context?: number } = {},\n): CodeResult | null {\n const { context = 0 } = opts;\n\n // Handle direct file:line-line syntax (bypass symbol lookup)\n const fileLineMatch = symbolPattern.match(/^(.+\\.\\w+):(\\d+)-(\\d+)$/);\n if (fileLineMatch) {\n return readFileRange(db, fileLineMatch[1]!, parseInt(fileLineMatch[2]!, 10), parseInt(fileLineMatch[3]!, 10), context);\n }\n\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n // Get the language from the documents table\n const doc = db.get<{ language: string | null }>(\n `SELECT language FROM documents WHERE relative_path = ?`,\n match.relativePath,\n );\n\n // Read the file\n const filePath = join(db.config.projectRoot, match.relativePath);\n let fileContent: string;\n try {\n fileContent = readFileSync(filePath, 'utf-8');\n } catch {\n return null;\n }\n\n const lines = fileContent.split('\\n');\n const startLine = Math.max(0, match.startLine - context);\n const endLine = Math.min(lines.length - 1, match.endLine + context);\n const source = lines.slice(startLine, endLine + 1).join('\\n');\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n relativePath: match.relativePath,\n startLine: startLine + 1, // 1-indexed for display\n endLine: endLine + 1,\n language: doc?.language ?? null,\n source,\n };\n}\n\n/** Read source by file path and line range directly (no symbol lookup) */\nfunction readFileRange(\n db: ScipDatabase,\n filePath: string,\n startLine: number,\n endLine: number,\n context: number,\n): CodeResult | null {\n // Find the file in the index\n const doc = db.get<{ relative_path: string; language: string | null }>(\n `SELECT relative_path, language FROM documents WHERE relative_path LIKE ?`,\n `%${filePath}%`,\n );\n if (!doc) return null;\n\n const fullPath = join(db.config.projectRoot, doc.relative_path);\n let fileContent: string;\n try {\n fileContent = readFileSync(fullPath, 'utf-8');\n } catch {\n return null;\n }\n\n const lines = fileContent.split('\\n');\n const start = Math.max(0, startLine - 1 - context); // convert to 0-indexed\n const end = Math.min(lines.length - 1, endLine - 1 + context);\n const source = lines.slice(start, end + 1).join('\\n');\n\n return {\n symbol: `${doc.relative_path}:${startLine}-${endLine}`,\n shortName: `${doc.relative_path}:${startLine}-${endLine}`,\n relativePath: doc.relative_path,\n startLine: start + 1,\n endLine: end + 1,\n language: doc.language,\n source,\n };\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,oBAAoB;AAC7B,SAAS,YAAY;AAed,SAAS,KACd,IACA,eACA,OAA6B,CAAC,GACX;AACnB,QAAM,EAAE,UAAU,EAAE,IAAI;AAGxB,QAAM,gBAAgB,cAAc,MAAM,yBAAyB;AACnE,MAAI,eAAe;AACjB,WAAO,cAAc,IAAI,cAAc,CAAC,GAAI,SAAS,cAAc,CAAC,GAAI,EAAE,GAAG,SAAS,cAAc,CAAC,GAAI,EAAE,GAAG,OAAO;AAAA,EACvH;AAEA,QAAM,QAAQ,qBAAqB,IAAI,aAAa;AACpD,MAAI,CAAC,MAAO,QAAO;AAGnB,QAAM,MAAM,GAAG;AAAA,IACb;AAAA,IACA,MAAM;AAAA,EACR;AAGA,QAAM,WAAW,KAAK,GAAG,OAAO,aAAa,MAAM,YAAY;AAC/D,MAAI;AACJ,MAAI;AACF,kBAAc,aAAa,UAAU,OAAO;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,YAAY,MAAM,IAAI;AACpC,QAAM,YAAY,KAAK,IAAI,GAAG,MAAM,YAAY,OAAO;AACvD,QAAM,UAAU,KAAK,IAAI,MAAM,SAAS,GAAG,MAAM,UAAU,OAAO;AAClE,QAAM,SAAS,MAAM,MAAM,WAAW,UAAU,CAAC,EAAE,KAAK,IAAI;AAE5D,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,cAAc,MAAM;AAAA,IACpB,WAAW,YAAY;AAAA;AAAA,IACvB,SAAS,UAAU;AAAA,IACnB,UAAU,KAAK,YAAY;AAAA,IAC3B;AAAA,EACF;AACF;AAGA,SAAS,cACP,IACA,UACA,WACA,SACA,SACmB;AAEnB,QAAM,MAAM,GAAG;AAAA,IACb;AAAA,IACA,IAAI,QAAQ;AAAA,EACd;AACA,MAAI,CAAC,IAAK,QAAO;AAEjB,QAAM,WAAW,KAAK,GAAG,OAAO,aAAa,IAAI,aAAa;AAC9D,MAAI;AACJ,MAAI;AACF,kBAAc,aAAa,UAAU,OAAO;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,QAAQ,YAAY,MAAM,IAAI;AACpC,QAAM,QAAQ,KAAK,IAAI,GAAG,YAAY,IAAI,OAAO;AACjD,QAAM,MAAM,KAAK,IAAI,MAAM,SAAS,GAAG,UAAU,IAAI,OAAO;AAC5D,QAAM,SAAS,MAAM,MAAM,OAAO,MAAM,CAAC,EAAE,KAAK,IAAI;AAEpD,SAAO;AAAA,IACL,QAAQ,GAAG,IAAI,aAAa,IAAI,SAAS,IAAI,OAAO;AAAA,IACpD,WAAW,GAAG,IAAI,aAAa,IAAI,SAAS,IAAI,OAAO;AAAA,IACvD,cAAc,IAAI;AAAA,IAClB,WAAW,QAAQ;AAAA,IACnB,SAAS,MAAM;AAAA,IACf,UAAU,IAAI;AAAA,IACd;AAAA,EACF;AACF;","names":[]}
|
package/dist/chunk-HB7MRLLL.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
shortenSymbol
|
|
3
|
-
} from "./chunk-QOV2R2WT.js";
|
|
4
|
-
|
|
5
|
-
// src/queries/imports.ts
|
|
6
|
-
function imports(db, filePattern) {
|
|
7
|
-
const rows = db.all(
|
|
8
|
-
`SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file
|
|
9
|
-
FROM mentions m
|
|
10
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
11
|
-
JOIN documents imp_d ON c.document_id = imp_d.id
|
|
12
|
-
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
13
|
-
LEFT JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
14
|
-
LEFT JOIN documents def_d ON der.document_id = def_d.id
|
|
15
|
-
WHERE imp_d.relative_path LIKE ?
|
|
16
|
-
AND m.role = 2
|
|
17
|
-
ORDER BY def_d.relative_path, gs.symbol`,
|
|
18
|
-
`%${filePattern}%`
|
|
19
|
-
);
|
|
20
|
-
return rows.map((r) => ({
|
|
21
|
-
symbol: r.symbol,
|
|
22
|
-
shortName: shortenSymbol(r.symbol),
|
|
23
|
-
fromFile: r.from_file ?? "(external)"
|
|
24
|
-
}));
|
|
25
|
-
}
|
|
26
|
-
function importedBy(db, symbolPattern) {
|
|
27
|
-
const rows = db.all(
|
|
28
|
-
`SELECT DISTINCT gs.symbol, d.relative_path AS importer
|
|
29
|
-
FROM mentions m
|
|
30
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
31
|
-
JOIN documents d ON c.document_id = d.id
|
|
32
|
-
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
33
|
-
WHERE gs.symbol LIKE ?
|
|
34
|
-
AND m.role = 2
|
|
35
|
-
ORDER BY d.relative_path`,
|
|
36
|
-
`%${symbolPattern}%`
|
|
37
|
-
);
|
|
38
|
-
return rows.filter((r) => !db.isIgnored(r.importer)).map((r) => ({
|
|
39
|
-
symbol: r.symbol,
|
|
40
|
-
shortName: shortenSymbol(r.symbol),
|
|
41
|
-
fromFile: r.importer
|
|
42
|
-
}));
|
|
43
|
-
}
|
|
44
|
-
function unusedImports(db, filePattern) {
|
|
45
|
-
const rows = db.all(
|
|
46
|
-
`SELECT gs.symbol, d.relative_path AS imported_in
|
|
47
|
-
FROM mentions m
|
|
48
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
49
|
-
JOIN documents d ON c.document_id = d.id
|
|
50
|
-
JOIN global_symbols gs ON m.symbol_id = gs.id
|
|
51
|
-
WHERE d.relative_path LIKE ?
|
|
52
|
-
AND m.role = 2
|
|
53
|
-
AND NOT EXISTS (
|
|
54
|
-
SELECT 1
|
|
55
|
-
FROM mentions ref_m
|
|
56
|
-
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
57
|
-
WHERE ref_m.symbol_id = gs.id
|
|
58
|
-
AND ref_m.role != 1
|
|
59
|
-
AND ref_c.document_id = d.id
|
|
60
|
-
)
|
|
61
|
-
ORDER BY d.relative_path, gs.symbol`,
|
|
62
|
-
`%${filePattern}%`
|
|
63
|
-
);
|
|
64
|
-
return rows.map((r) => ({
|
|
65
|
-
symbol: r.symbol,
|
|
66
|
-
shortName: shortenSymbol(r.symbol),
|
|
67
|
-
importedIn: r.imported_in
|
|
68
|
-
}));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export {
|
|
72
|
-
imports,
|
|
73
|
-
importedBy,
|
|
74
|
-
unusedImports
|
|
75
|
-
};
|
|
76
|
-
//# sourceMappingURL=chunk-HB7MRLLL.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/imports.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { ImportResult, UnusedImportResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * What symbols does this file import?\n * Uses role=2 (import) from the SCIP mentions table.\n */\nexport function imports(db: ScipDatabase, filePattern: string): ImportResult[] {\n const rows = db.all<{\n symbol: string;\n from_file: string;\n }>(\n `SELECT DISTINCT gs.symbol, def_d.relative_path AS from_file\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents imp_d ON c.document_id = imp_d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n LEFT JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n LEFT JOIN documents def_d ON der.document_id = def_d.id\n WHERE imp_d.relative_path LIKE ?\n AND m.role = 2\n ORDER BY def_d.relative_path, gs.symbol`,\n `%${filePattern}%`,\n );\n\n return rows.map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n fromFile: r.from_file ?? '(external)',\n }));\n}\n\n/**\n * Which files import this symbol?\n */\nexport function importedBy(db: ScipDatabase, symbolPattern: string): ImportResult[] {\n const rows = db.all<{\n symbol: string;\n importer: string;\n }>(\n `SELECT DISTINCT gs.symbol, d.relative_path AS importer\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n WHERE gs.symbol LIKE ?\n AND m.role = 2\n ORDER BY d.relative_path`,\n `%${symbolPattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.importer))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n fromFile: r.importer,\n }));\n}\n\n/**\n * Find imports in a file that are never referenced (role=0) in the same file.\n * These are likely unused imports.\n */\nexport function unusedImports(db: ScipDatabase, filePattern: string): UnusedImportResult[] {\n const rows = db.all<{\n symbol: string;\n imported_in: string;\n }>(\n `SELECT gs.symbol, d.relative_path AS imported_in\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n WHERE d.relative_path LIKE ?\n AND m.role = 2\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 WHERE ref_m.symbol_id = gs.id\n AND ref_m.role != 1\n AND ref_c.document_id = d.id\n )\n ORDER BY d.relative_path, gs.symbol`,\n `%${filePattern}%`,\n );\n\n return rows.map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n importedIn: r.imported_in,\n }));\n}\n"],"mappings":";;;;;AAQO,SAAS,QAAQ,IAAkB,aAAqC;AAC7E,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,UAAU,EAAE,aAAa;AAAA,EAC3B,EAAE;AACJ;AAKO,SAAS,WAAW,IAAkB,eAAuC;AAClF,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,QAAQ,CAAC,EACvC,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,UAAU,EAAE;AAAA,EACd,EAAE;AACN;AAMO,SAAS,cAAc,IAAkB,aAA2C;AACzF,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAgBA,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,YAAY,EAAE;AAAA,EAChB,EAAE;AACJ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/coupling.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { CouplingResult } from '../types.js';\n\n/**\n * Measure coupling between two files: how many symbols do they share\n * (symbols defined in one and referenced in the other, or vice versa).\n */\nexport function coupling(\n db: ScipDatabase,\n file1: string,\n file2: string,\n): CouplingResult {\n const row = db.get<{ shared: number }>(\n `SELECT COUNT(DISTINCT gs.id) AS shared\n FROM global_symbols gs\n WHERE (\n -- Defined in file1, referenced in file2\n EXISTS (\n SELECT 1 FROM defn_enclosing_ranges der\n JOIN documents d ON der.document_id = d.id\n WHERE der.symbol_id = gs.id AND d.relative_path LIKE ?\n )\n AND EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path LIKE ?\n )\n ) OR (\n -- Defined in file2, referenced in file1\n EXISTS (\n SELECT 1 FROM defn_enclosing_ranges der\n JOIN documents d ON der.document_id = d.id\n WHERE der.symbol_id = gs.id AND d.relative_path LIKE ?\n )\n AND EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n WHERE m.symbol_id = gs.id AND m.role != 1 AND d.relative_path LIKE ?\n )\n )`,\n `%${file1}%`, `%${file2}%`,\n `%${file2}%`, `%${file1}%`,\n );\n\n return {\n file1,\n file2,\n sharedSymbols: row?.shared ?? 0,\n };\n}\n\n/**\n * Find the most coupled file pairs in the codebase.\n */\nexport function topCoupling(\n db: ScipDatabase,\n opts: { limit?: number; scope?: string } = {},\n): CouplingResult[] {\n const { limit = 20, scope } = opts;\n const scopeFilter = scope\n ? `AND d1.relative_path LIKE '%${scope}%' AND d2.relative_path LIKE '%${scope}%'`\n : '';\n\n // Find file pairs that share the most symbols (one defines, other references)\n const rows = db.all<{\n file1: string;\n file2: string;\n shared: number;\n }>(\n `SELECT\n def_d.relative_path AS file1,\n ref_d.relative_path AS file2,\n COUNT(DISTINCT gs.id) AS shared\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 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 def_d ON der.document_id = def_d.id\n WHERE m.role != 1\n AND def_d.id != ref_d.id\n ${db.pathExclusionsFor('def_d', 'ref_d')}\n ${scopeFilter}\n GROUP BY def_d.id, ref_d.id\n ORDER BY shared DESC\n LIMIT ?`,\n limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.file1) && !db.isIgnored(r.file2))\n .map((r) => ({\n file1: r.file1,\n file2: r.file2,\n sharedSymbols: r.shared,\n }));\n}\n"],"mappings":";AAOO,SAAS,SACd,IACA,OACA,OACgB;AAChB,QAAM,MAAM,GAAG;AAAA,IACb;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,IA6BA,IAAI,KAAK;AAAA,IAAK,IAAI,KAAK;AAAA,IACvB,IAAI,KAAK;AAAA,IAAK,IAAI,KAAK;AAAA,EACzB;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,eAAe,KAAK,UAAU;AAAA,EAChC;AACF;AAKO,SAAS,YACd,IACA,OAA2C,CAAC,GAC1B;AAClB,QAAM,EAAE,QAAQ,IAAI,MAAM,IAAI;AAC9B,QAAM,cAAc,QAChB,+BAA+B,KAAK,kCAAkC,KAAK,OAC3E;AAGJ,QAAM,OAAO,GAAG;AAAA,IAKd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYI,GAAG,kBAAkB,SAAS,OAAO,CAAC;AAAA,QACtC,WAAW;AAAA;AAAA;AAAA;AAAA,IAIf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,KAAK,KAAK,CAAC,GAAG,UAAU,EAAE,KAAK,CAAC,EAC9D,IAAI,CAAC,OAAO;AAAA,IACX,OAAO,EAAE;AAAA,IACT,OAAO,EAAE;AAAA,IACT,eAAe,EAAE;AAAA,EACnB,EAAE;AACN;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/cycles.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { CycleResult } from '../types.js';\n\n/**\n * Detect circular dependency chains between files.\n * A cycle exists when file A depends on B, B depends on C, and C depends on A.\n *\n * Uses the same dependency edges as the `deps` command (symbol definitions\n * referenced across files), then runs DFS cycle detection.\n */\nexport function cycles(\n db: ScipDatabase,\n opts: { scope?: string; maxDepth?: number } = {},\n): CycleResult[] {\n const { scope, maxDepth = 10 } = opts;\n const graph = buildFileDepGraph(db, scope);\n\n // DFS cycle detection\n const allCycles: CycleResult[] = [];\n const visited = new Set<string>();\n const inStack = new Set<string>();\n const stack: string[] = [];\n\n function dfs(node: string, depth: number): void {\n if (depth > maxDepth) return;\n if (inStack.has(node)) {\n // Found a cycle — extract it from the stack\n const cycleStart = stack.indexOf(node);\n if (cycleStart !== -1) {\n const cyclePath = stack.slice(cycleStart).concat(node);\n // Normalize: start from the lexicographically smallest file\n const minIdx = cyclePath.indexOf(\n cyclePath.reduce((a, b) => (a < b ? a : b)),\n );\n const normalized = [\n ...cyclePath.slice(minIdx, -1),\n ...cyclePath.slice(0, minIdx),\n cyclePath[minIdx]!,\n ];\n // Deduplicate\n const key = normalized.join(' -> ');\n if (!seenCycles.has(key)) {\n seenCycles.add(key);\n allCycles.push({ path: normalized });\n }\n }\n return;\n }\n if (visited.has(node)) return;\n\n visited.add(node);\n inStack.add(node);\n stack.push(node);\n\n const neighbors = graph.get(node);\n if (neighbors) {\n for (const neighbor of neighbors) {\n dfs(neighbor, depth + 1);\n }\n }\n\n stack.pop();\n inStack.delete(node);\n }\n\n const seenCycles = new Set<string>();\n for (const node of graph.keys()) {\n if (!visited.has(node)) {\n dfs(node, 0);\n }\n }\n\n // Sort by cycle length (shorter cycles are more actionable)\n allCycles.sort((a, b) => a.path.length - b.path.length);\n\n return allCycles;\n}\n"],"mappings":";;;;;AAWO,SAAS,OACd,IACA,OAA8C,CAAC,GAChC;AACf,QAAM,EAAE,OAAO,WAAW,GAAG,IAAI;AACjC,QAAM,QAAQ,kBAAkB,IAAI,KAAK;AAGzC,QAAM,YAA2B,CAAC;AAClC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,UAAU,oBAAI,IAAY;AAChC,QAAM,QAAkB,CAAC;AAEzB,WAAS,IAAI,MAAc,OAAqB;AAC9C,QAAI,QAAQ,SAAU;AACtB,QAAI,QAAQ,IAAI,IAAI,GAAG;AAErB,YAAM,aAAa,MAAM,QAAQ,IAAI;AACrC,UAAI,eAAe,IAAI;AACrB,cAAM,YAAY,MAAM,MAAM,UAAU,EAAE,OAAO,IAAI;AAErD,cAAM,SAAS,UAAU;AAAA,UACvB,UAAU,OAAO,CAAC,GAAG,MAAO,IAAI,IAAI,IAAI,CAAE;AAAA,QAC5C;AACA,cAAM,aAAa;AAAA,UACjB,GAAG,UAAU,MAAM,QAAQ,EAAE;AAAA,UAC7B,GAAG,UAAU,MAAM,GAAG,MAAM;AAAA,UAC5B,UAAU,MAAM;AAAA,QAClB;AAEA,cAAM,MAAM,WAAW,KAAK,MAAM;AAClC,YAAI,CAAC,WAAW,IAAI,GAAG,GAAG;AACxB,qBAAW,IAAI,GAAG;AAClB,oBAAU,KAAK,EAAE,MAAM,WAAW,CAAC;AAAA,QACrC;AAAA,MACF;AACA;AAAA,IACF;AACA,QAAI,QAAQ,IAAI,IAAI,EAAG;AAEvB,YAAQ,IAAI,IAAI;AAChB,YAAQ,IAAI,IAAI;AAChB,UAAM,KAAK,IAAI;AAEf,UAAM,YAAY,MAAM,IAAI,IAAI;AAChC,QAAI,WAAW;AACb,iBAAW,YAAY,WAAW;AAChC,YAAI,UAAU,QAAQ,CAAC;AAAA,MACzB;AAAA,IACF;AAEA,UAAM,IAAI;AACV,YAAQ,OAAO,IAAI;AAAA,EACrB;AAEA,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,QAAQ,MAAM,KAAK,GAAG;AAC/B,QAAI,CAAC,QAAQ,IAAI,IAAI,GAAG;AACtB,UAAI,MAAM,CAAC;AAAA,IACb;AAAA,EACF;AAGA,YAAU,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,SAAS,EAAE,KAAK,MAAM;AAEtD,SAAO;AACT;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/similar-files.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { buildFileDepGraph } from '../query-support.js';\nimport type { SimilarFileResult } from '../types.js';\n\n/**\n * Find files with similar dependency profiles.\n *\n * Two files that depend on (import from) the same set of other files\n * are structurally doing similar work. High Jaccard similarity between\n * their dependency sets = likely copy-paste variants or consolidation candidates.\n */\nexport function similarFiles(\n db: ScipDatabase,\n opts: {\n minSimilarity?: number;\n limit?: number;\n scope?: string;\n minDeps?: number;\n filePattern?: string;\n } = {},\n): SimilarFileResult[] {\n const { minSimilarity = 0.5, limit = 20, scope, minDeps = 3, filePattern } = opts;\n\n // Build dependency profile for each file\n const profiles = buildFileProfiles(db, { scope, minDeps });\n\n const results: SimilarFileResult[] = [];\n\n if (filePattern) {\n // Compare one file against all others\n const target = profiles.find((p) => p.file.includes(filePattern));\n if (!target) return [];\n\n for (const candidate of profiles) {\n if (candidate.file === target.file) continue;\n const result = compareProfiles(target, candidate, minSimilarity);\n if (result) results.push(result);\n }\n } else {\n // Pairwise comparison across all files\n for (let i = 0; i < profiles.length; i++) {\n for (let j = i + 1; j < profiles.length; j++) {\n const result = compareProfiles(profiles[i]!, profiles[j]!, minSimilarity);\n if (result) results.push(result);\n }\n if (results.length > limit * 5) break;\n }\n }\n\n results.sort((a, b) => b.similarity - a.similarity);\n return results.slice(0, limit);\n}\n\n// ── Internal ───────────────────────────────────────────────\n\ninterface FileProfile {\n file: string;\n deps: Set<string>;\n}\n\nfunction buildFileProfiles(\n db: ScipDatabase,\n opts: { scope?: string; minDeps: number },\n): FileProfile[] {\n const { scope, minDeps } = opts;\n const depMap = buildFileDepGraph(db, scope);\n const universalDeps = findUniversalDependencies(depMap);\n\n // Filter to files with enough deps\n const profiles: FileProfile[] = [];\n for (const [file, deps] of depMap) {\n if (deps.size >= minDeps) {\n profiles.push({\n file,\n deps: new Set([...deps].filter((dep) => !universalDeps.has(dep))),\n });\n }\n }\n\n return profiles;\n}\n\nfunction findUniversalDependencies(\n depMap: Map<string, Set<string>>,\n): Set<string> {\n const universalDeps = new Set<string>();\n const fileCount = depMap.size;\n if (fileCount === 0) return universalDeps;\n\n const depCounts = new Map<string, number>();\n for (const deps of depMap.values()) {\n for (const dep of deps) {\n depCounts.set(dep, (depCounts.get(dep) ?? 0) + 1);\n }\n }\n\n for (const [dep, count] of depCounts) {\n if (count / fileCount > 0.5) {\n universalDeps.add(dep);\n }\n }\n\n return universalDeps;\n}\n\nfunction compareProfiles(\n a: FileProfile,\n b: FileProfile,\n minSimilarity: number,\n): SimilarFileResult | null {\n const shared = new Set<string>();\n for (const dep of a.deps) {\n if (b.deps.has(dep)) shared.add(dep);\n }\n\n if (shared.size === 0) return null;\n\n const unionSize = new Set([...a.deps, ...b.deps]).size;\n const similarity = shared.size / unionSize;\n\n if (similarity < minSimilarity) return null;\n\n const uniqueA: string[] = [];\n for (const dep of a.deps) {\n if (!b.deps.has(dep)) uniqueA.push(dep);\n }\n const uniqueB: string[] = [];\n for (const dep of b.deps) {\n if (!a.deps.has(dep)) uniqueB.push(dep);\n }\n\n return {\n fileA: a.file,\n fileB: b.file,\n similarity,\n sharedDeps: [...shared],\n uniqueToA: uniqueA,\n uniqueToB: uniqueB,\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-HZBC7PPD.js
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
TEST_FILE_PATTERNS,
|
|
3
|
-
testFileExclusionSql,
|
|
4
|
-
testFileMatchSql
|
|
5
|
-
} from "./chunk-ZOGY2V3N.js";
|
|
6
|
-
import {
|
|
7
|
-
shortenSymbol
|
|
8
|
-
} from "./chunk-QOV2R2WT.js";
|
|
9
|
-
|
|
10
|
-
// src/queries/test-coverage.ts
|
|
11
|
-
function testCoverage(db, symbolPattern) {
|
|
12
|
-
const syms = db.all(
|
|
13
|
-
`SELECT gs.id, gs.symbol, d.relative_path
|
|
14
|
-
FROM global_symbols gs
|
|
15
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
16
|
-
JOIN documents d ON der.document_id = d.id
|
|
17
|
-
WHERE gs.symbol LIKE ?
|
|
18
|
-
${db.pathExclusionsFor("d")}
|
|
19
|
-
${db.symbolNoiseFor("gs")}
|
|
20
|
-
ORDER BY d.relative_path`,
|
|
21
|
-
`%${symbolPattern}%`
|
|
22
|
-
);
|
|
23
|
-
const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
|
|
24
|
-
return syms.filter((s) => !db.isIgnored(s.relative_path)).map((s) => {
|
|
25
|
-
const testFiles = db.all(
|
|
26
|
-
`SELECT DISTINCT ref_d.relative_path
|
|
27
|
-
FROM mentions m
|
|
28
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
29
|
-
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
30
|
-
WHERE m.symbol_id = ?
|
|
31
|
-
AND m.role != 1
|
|
32
|
-
AND (${testPatternSql})
|
|
33
|
-
ORDER BY ref_d.relative_path`,
|
|
34
|
-
s.id
|
|
35
|
-
).map((r) => r.relative_path);
|
|
36
|
-
return {
|
|
37
|
-
symbol: s.symbol,
|
|
38
|
-
shortName: shortenSymbol(s.symbol),
|
|
39
|
-
definedIn: s.relative_path,
|
|
40
|
-
testFiles,
|
|
41
|
-
covered: testFiles.length > 0
|
|
42
|
-
};
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
function testCoverageSummary(db, opts = {}) {
|
|
46
|
-
const { scope, minLoc = 3 } = opts;
|
|
47
|
-
const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : "";
|
|
48
|
-
const testPatternSql = testFileExclusionSql("d");
|
|
49
|
-
const symbols = db.all(
|
|
50
|
-
`SELECT gs.id
|
|
51
|
-
FROM global_symbols gs
|
|
52
|
-
JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id
|
|
53
|
-
JOIN documents d ON der.document_id = d.id
|
|
54
|
-
WHERE 1 = 1
|
|
55
|
-
${db.pathExclusionsFor("d")}
|
|
56
|
-
AND ${testPatternSql}
|
|
57
|
-
${db.symbolNoiseFor("gs")}
|
|
58
|
-
AND gs.symbol NOT LIKE '%#%'
|
|
59
|
-
AND (der.end_line - der.start_line + 1) >= ?
|
|
60
|
-
${scopeFilter}`,
|
|
61
|
-
minLoc
|
|
62
|
-
);
|
|
63
|
-
const testRefSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
|
|
64
|
-
let covered = 0;
|
|
65
|
-
for (const s of symbols) {
|
|
66
|
-
const hasTest = db.get(
|
|
67
|
-
`SELECT COUNT(*) AS c FROM mentions m
|
|
68
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
69
|
-
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
70
|
-
WHERE m.symbol_id = ? AND m.role != 1 AND (${testRefSql})`,
|
|
71
|
-
s.id
|
|
72
|
-
);
|
|
73
|
-
if (hasTest && hasTest.c > 0) covered++;
|
|
74
|
-
}
|
|
75
|
-
const total = symbols.length;
|
|
76
|
-
return {
|
|
77
|
-
total,
|
|
78
|
-
covered,
|
|
79
|
-
uncovered: total - covered,
|
|
80
|
-
percent: total > 0 ? Math.round(covered / total * 100) : 0
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export {
|
|
85
|
-
testCoverage,
|
|
86
|
-
testCoverageSummary
|
|
87
|
-
};
|
|
88
|
-
//# sourceMappingURL=chunk-HZBC7PPD.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/test-coverage.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { TEST_FILE_PATTERNS, testFileExclusionSql, testFileMatchSql } from '../query-support.js';\nimport type { TestCoverageResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Check if a symbol is referenced by any test file.\n * Reports which test files cover (reference) each matching symbol.\n */\nexport function testCoverage(\n db: ScipDatabase,\n symbolPattern: string,\n): TestCoverageResult[] {\n // Find matching symbols\n const syms = db.all<{\n id: number;\n symbol: string;\n relative_path: string;\n }>(\n `SELECT gs.id, gs.symbol, d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.symbol LIKE ?\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n ORDER BY d.relative_path`,\n `%${symbolPattern}%`,\n );\n\n const testPatternSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);\n\n return syms\n .filter((s) => !db.isIgnored(s.relative_path))\n .map((s) => {\n // Find test files that reference this symbol\n const testFiles = db.all<{ relative_path: string }>(\n `SELECT DISTINCT ref_d.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND (${testPatternSql})\n ORDER BY ref_d.relative_path`,\n s.id,\n ).map((r) => r.relative_path);\n\n return {\n symbol: s.symbol,\n shortName: shortenSymbol(s.symbol),\n definedIn: s.relative_path,\n testFiles,\n covered: testFiles.length > 0,\n };\n });\n}\n\n/**\n * Summary: what percentage of symbols in scope are referenced by test files?\n */\nexport function testCoverageSummary(\n db: ScipDatabase,\n opts: { scope?: string; minLoc?: number } = {},\n): { total: number; covered: number; uncovered: number; percent: number } {\n const { scope, minLoc = 3 } = opts;\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n const testPatternSql = testFileExclusionSql('d');\n\n const symbols = db.all<{ id: number }>(\n `SELECT gs.id\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testPatternSql}\n ${db.symbolNoiseFor('gs')}\n AND gs.symbol NOT LIKE '%#%'\n AND (der.end_line - der.start_line + 1) >= ?\n ${scopeFilter}`,\n minLoc,\n );\n\n const testRefSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);\n\n let covered = 0;\n for (const s of symbols) {\n const hasTest = db.get<{ c: number }>(\n `SELECT COUNT(*) AS c FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n WHERE m.symbol_id = ? AND m.role != 1 AND (${testRefSql})`,\n s.id,\n );\n if (hasTest && hasTest.c > 0) covered++;\n }\n\n const total = symbols.length;\n return {\n total,\n covered,\n uncovered: total - covered,\n percent: total > 0 ? Math.round((covered / total) * 100) : 0,\n };\n}\n"],"mappings":";;;;;;;;;;AASO,SAAS,aACd,IACA,eACsB;AAEtB,QAAM,OAAO,GAAG;AAAA,IAKd;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA,IAE3B,IAAI,aAAa;AAAA,EACnB;AAEA,QAAM,iBAAiB,iBAAiB,SAAS,kBAAkB;AAEnE,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,MAAM;AAEV,UAAM,YAAY,GAAG;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,iBAMS,cAAc;AAAA;AAAA,MAEvB,EAAE;AAAA,IACJ,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa;AAE5B,WAAO;AAAA,MACL,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,WAAW,EAAE;AAAA,MACb;AAAA,MACA,SAAS,UAAU,SAAS;AAAA,IAC9B;AAAA,EACF,CAAC;AACL;AAKO,SAAS,oBACd,IACA,OAA4C,CAAC,GAC2B;AACxE,QAAM,EAAE,OAAO,SAAS,EAAE,IAAI;AAC9B,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AACtE,QAAM,iBAAiB,qBAAqB,GAAG;AAE/C,QAAM,UAAU,GAAG;AAAA,IACjB;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA,YACrB,cAAc;AAAA,QAClB,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,QAGvB,WAAW;AAAA,IACf;AAAA,EACF;AAEA,QAAM,aAAa,iBAAiB,SAAS,kBAAkB;AAE/D,MAAI,UAAU;AACd,aAAW,KAAK,SAAS;AACvB,UAAM,UAAU,GAAG;AAAA,MACjB;AAAA;AAAA;AAAA,oDAG8C,UAAU;AAAA,MACxD,EAAE;AAAA,IACJ;AACA,QAAI,WAAW,QAAQ,IAAI,EAAG;AAAA,EAChC;AAEA,QAAM,QAAQ,QAAQ;AACtB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW,QAAQ;AAAA,IACnB,SAAS,QAAQ,IAAI,KAAK,MAAO,UAAU,QAAS,GAAG,IAAI;AAAA,EAC7D;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/refs.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { RefResult } from '../types.js';\n\nexport function refs(db: ScipDatabase, symbolPattern: string): RefResult[] {\n const rows = db.all<{\n relative_path: string;\n start_line: number;\n }>(\n `SELECT DISTINCT d.relative_path, c.start_line\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n WHERE gs.symbol LIKE ?\n AND ${db.localSymbolPredicate}\n AND m.role != 1\n ORDER BY d.relative_path, c.start_line`,\n `%${symbolPattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n relativePath: r.relative_path,\n line: r.start_line,\n }));\n}\n"],"mappings":";AAGO,SAAS,KAAK,IAAkB,eAAoC;AACzE,QAAM,OAAO,GAAG;AAAA,IAId;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAMQ,GAAG,oBAAoB;AAAA;AAAA;AAAA,IAG/B,IAAI,aAAa;AAAA,EACnB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,cAAc,EAAE;AAAA,IAChB,MAAM,EAAE;AAAA,EACV,EAAE;AACN;","names":[]}
|
package/dist/chunk-JJP7KQND.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=chunk-JJP7KQND.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/symbols.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { SymbolResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\nimport { cleanSignature } from './clean-signature.js';\n\nexport function symbols(db: ScipDatabase, filePattern: string): SymbolResult[] {\n const rows = db.all<{\n start_line: number;\n end_line: number;\n sig: string | null;\n symbol: string;\n relative_path: string;\n }>(\n `SELECT\n der.start_line,\n der.end_line,\n REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig,\n gs.symbol,\n d.relative_path\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n JOIN documents d ON der.document_id = d.id\n WHERE d.relative_path LIKE ?\n AND ${db.localSymbolPredicate}\n ${db.symbolNoise}\n ORDER BY der.start_line`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n startLine: r.start_line,\n endLine: r.end_line,\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n signature: cleanSignature(r.sig),\n }));\n}\n"],"mappings":";;;;;;;;AAKO,SAAS,QAAQ,IAAkB,aAAqC;AAC7E,QAAM,OAAO,GAAG;AAAA,IAOd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAUQ,GAAG,oBAAoB;AAAA,QAC3B,GAAG,WAAW;AAAA;AAAA,IAElB,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,WAAW,eAAe,EAAE,GAAG;AAAA,EACjC,EAAE;AACN;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/diff-impact.ts"],"sourcesContent":["import { execFileSync } from 'node:child_process';\nimport type { ScipDatabase } from '../db.js';\nimport { TEST_FILE_PATTERNS, testFileMatchSql } from '../query-support.js';\nimport type { DiffImpactResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Given a git diff, compute the affected symbol set.\n * Finds all symbols defined in changed files, their fan-in,\n * the files that consume them, and test coverage gaps.\n */\nexport function diffImpact(\n db: ScipDatabase,\n opts: { base?: string } = {},\n): DiffImpactResult {\n const { base = 'HEAD' } = opts;\n\n // Get changed files from git\n let changedFileLines: string[];\n try {\n const stdout = execFileSync('git', ['diff', '--name-only', base], {\n encoding: 'utf-8',\n cwd: db.config.projectRoot,\n timeout: 10_000,\n });\n changedFileLines = stdout\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n } catch {\n // Not in a git repo or git not available — return empty result\n return {\n changedFiles: [],\n changedSymbols: [],\n affectedConsumers: [],\n uncoveredSymbols: [],\n summary: {\n totalChangedFiles: 0,\n totalChangedSymbols: 0,\n totalAffectedFiles: 0,\n testCoveragePercent: 0,\n },\n };\n }\n\n if (changedFileLines.length === 0) {\n return {\n changedFiles: [],\n changedSymbols: [],\n affectedConsumers: [],\n uncoveredSymbols: [],\n summary: {\n totalChangedFiles: 0,\n totalChangedSymbols: 0,\n totalAffectedFiles: 0,\n testCoveragePercent: 0,\n },\n };\n }\n\n // Match changed files against the index\n const changedFiles: string[] = [];\n const changedDocIds: number[] = [];\n\n for (const file of changedFileLines) {\n const doc = db.get<{ id: number; relative_path: string }>(\n `SELECT id, relative_path FROM documents\n WHERE relative_path LIKE ?\n LIMIT 1`,\n `%${file}`,\n );\n if (doc && !db.isIgnored(doc.relative_path)) {\n changedFiles.push(doc.relative_path);\n changedDocIds.push(doc.id);\n }\n }\n\n if (changedDocIds.length === 0) {\n return {\n changedFiles: changedFileLines,\n changedSymbols: [],\n affectedConsumers: [],\n uncoveredSymbols: [],\n summary: {\n totalChangedFiles: changedFileLines.length,\n totalChangedSymbols: 0,\n totalAffectedFiles: 0,\n testCoveragePercent: 0,\n },\n };\n }\n\n // Get all symbols defined in changed files\n const docPlaceholders = changedDocIds.map(() => '?').join(',');\n const syms = db.all<{\n symbol_id: number;\n symbol: string;\n relative_path: string;\n }>(\n `SELECT DISTINCT gs.id AS symbol_id, gs.symbol, d.relative_path\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n JOIN documents d ON der.document_id = d.id\n WHERE der.document_id IN (${docPlaceholders})\n ${db.symbolNoiseFor('gs')}\n ORDER BY d.relative_path`,\n ...changedDocIds,\n );\n\n // For each symbol, compute fan-in (distinct referencing documents)\n const testPatternSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);\n const changedSymbols: DiffImpactResult['changedSymbols'] = [];\n const consumerMap = new Map<string, Set<string>>(); // file -> set of consumed symbol shortNames\n const uncoveredSymbols: DiffImpactResult['uncoveredSymbols'] = [];\n let coveredCount = 0;\n\n for (const sym of syms) {\n // Fan-in: distinct files that reference this symbol\n const fanInRow = db.get<{ fan_in: number }>(\n `SELECT COUNT(DISTINCT c.document_id) AS fan_in\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = ?\n AND m.role != 1`,\n sym.symbol_id,\n );\n\n const fanIn = fanInRow?.fan_in ?? 0;\n const shortName = shortenSymbol(sym.symbol);\n\n changedSymbols.push({\n symbol: sym.symbol,\n shortName,\n file: sym.relative_path,\n fanIn,\n });\n\n // Collect consumer files (excluding the changed files themselves)\n const consumers = db.all<{ relative_path: string }>(\n `SELECT DISTINCT ref_d.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND ref_d.relative_path NOT IN (${changedFiles.map(() => '?').join(',')})\n ${db.pathExclusionsFor('ref_d')}`,\n sym.symbol_id,\n ...changedFiles,\n );\n\n for (const consumer of consumers) {\n if (db.isIgnored(consumer.relative_path)) continue;\n if (!consumerMap.has(consumer.relative_path)) {\n consumerMap.set(consumer.relative_path, new Set());\n }\n consumerMap.get(consumer.relative_path)!.add(shortName);\n }\n\n // Check test coverage\n const hasTest = db.get<{ c: number }>(\n `SELECT COUNT(*) AS c\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND (${testPatternSql})`,\n sym.symbol_id,\n );\n\n if (hasTest && hasTest.c > 0) {\n coveredCount++;\n } else {\n uncoveredSymbols.push({\n symbol: sym.symbol,\n shortName,\n file: sym.relative_path,\n });\n }\n }\n\n // Build affected consumers list\n const affectedConsumers = [...consumerMap.entries()]\n .map(([file, symbols]) => ({ file, consumedSymbols: symbols.size }))\n .sort((a, b) => b.consumedSymbols - a.consumedSymbols);\n\n const totalSymbols = changedSymbols.length;\n const testCoveragePercent =\n totalSymbols > 0 ? Math.round((coveredCount / totalSymbols) * 100) : 0;\n\n return {\n changedFiles,\n changedSymbols,\n affectedConsumers,\n uncoveredSymbols,\n summary: {\n totalChangedFiles: changedFiles.length,\n totalChangedSymbols: totalSymbols,\n totalAffectedFiles: affectedConsumers.length,\n testCoveragePercent,\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/by-kind.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { ByKindResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * SCIP SymbolInformation.Kind enum values.\n * From: https://github.com/sourcegraph/scip/blob/main/scip.proto\n */\nconst KIND_NAMES: Record<number, string> = {\n 0: 'UnspecifiedKind',\n 1: 'AbstractMethod',\n 2: 'Accessor',\n 3: 'Array',\n 4: 'Assertion',\n 5: 'AssociatedType',\n 6: 'Attribute',\n 7: 'Axiom',\n 8: 'Boolean',\n 9: 'Class',\n 10: 'Constant',\n 11: 'Constructor',\n 12: 'Contract',\n 13: 'DataFamily',\n 14: 'DefinitionMacro',\n 15: 'Delegate',\n 16: 'Enum',\n 17: 'EnumMember',\n 18: 'Error',\n 19: 'Event',\n 20: 'Fact',\n 21: 'Field',\n 22: 'File',\n 23: 'Function',\n 24: 'Getter',\n 25: 'Grammar',\n 26: 'Instance',\n 27: 'Interface',\n 28: 'Key',\n 29: 'Lang',\n 30: 'Lemma',\n 31: 'Library',\n 32: 'Macro',\n 33: 'Method',\n 34: 'MethodAlias',\n 35: 'MethodReceiver',\n 36: 'MethodSpecification',\n 37: 'Message',\n 38: 'Modifier',\n 39: 'Module',\n 40: 'Namespace',\n 41: 'Null',\n 42: 'Number',\n 43: 'Object',\n 44: 'Operator',\n 45: 'Package',\n 46: 'PackageObject',\n 47: 'Parameter',\n 48: 'ParameterLabel',\n 49: 'Pattern',\n 50: 'Predicate',\n 51: 'Property',\n 52: 'Protocol',\n 53: 'ProtocolMethod',\n 54: 'PureVirtualMethod',\n 55: 'Quasiquoter',\n 56: 'SelfParameter',\n 57: 'Setter',\n 58: 'Signature',\n 59: 'SingletonClass',\n 60: 'SingletonMethod',\n 61: 'StaticDataMember',\n 62: 'StaticEvent',\n 63: 'StaticField',\n 64: 'StaticMethod',\n 65: 'StaticProperty',\n 66: 'StaticVariable',\n 67: 'String',\n 68: 'Struct',\n 69: 'Subscript',\n 70: 'Tactic',\n 71: 'Theorem',\n 72: 'ThisParameter',\n 73: 'Trait',\n 74: 'TraitMethod',\n 75: 'Type',\n 76: 'TypeAlias',\n 77: 'TypeClass',\n 78: 'TypeClassMethod',\n 79: 'TypeFamily',\n 80: 'TypeParameter',\n 81: 'Union',\n 82: 'Value',\n 83: 'Variable',\n};\n\n/** Reverse lookup: name -> kind number */\nconst KIND_BY_NAME = new Map<string, number>();\nfor (const [k, v] of Object.entries(KIND_NAMES)) {\n KIND_BY_NAME.set(v.toLowerCase(), Number(k));\n}\n\n/**\n * Find symbols by SCIP kind (class, interface, enum, function, etc.)\n */\nexport function byKind(\n db: ScipDatabase,\n kindQuery: string,\n opts: { scope?: string; limit?: number } = {},\n): ByKindResult[] {\n const { scope, limit = 100 } = opts;\n\n // Resolve kind: accept number or name\n let kindNum: number | null = null;\n const asNum = parseInt(kindQuery, 10);\n if (!isNaN(asNum)) {\n kindNum = asNum;\n } else {\n kindNum = KIND_BY_NAME.get(kindQuery.toLowerCase()) ?? null;\n // Fuzzy match: try partial name\n if (kindNum === null) {\n for (const [name, num] of KIND_BY_NAME) {\n if (name.includes(kindQuery.toLowerCase())) {\n kindNum = num;\n break;\n }\n }\n }\n }\n\n if (kindNum === null) {\n return [];\n }\n\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Check if the index actually has kind data populated\n const hasKinds = db.get<{ c: number }>(\n `SELECT COUNT(*) AS c FROM global_symbols WHERE kind IS NOT NULL`,\n );\n if (!hasKinds || hasKinds.c === 0) {\n return []; // Indexer doesn't populate kind field\n }\n\n const rows = db.all<{\n symbol: string;\n kind: number;\n relative_path: string;\n start_line: number;\n end_line: number;\n }>(\n `SELECT gs.symbol, gs.kind, d.relative_path, der.start_line, der.end_line\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.kind = ?\n ${db.pathExclusionsFor('d')}\n ${scopeFilter}\n ORDER BY d.relative_path, der.start_line\n LIMIT ?`,\n kindNum, limit,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n kind: r.kind,\n kindName: KIND_NAMES[r.kind] ?? 'Unknown',\n relativePath: r.relative_path,\n startLine: r.start_line,\n endLine: r.end_line,\n }));\n}\n\n/** List all symbol kinds present in the index with counts */\nexport function kindCounts(\n db: ScipDatabase,\n opts: { scope?: string } = {},\n): Array<{ kind: number; kindName: string; count: number }> {\n const scopeFilter = opts.scope\n ? `AND d.relative_path LIKE '%${opts.scope}%'`\n : '';\n\n const rows = db.all<{ kind: number; cnt: number }>(\n `SELECT gs.kind, COUNT(*) AS cnt\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND gs.kind IS NOT NULL\n AND gs.kind != 0\n ${scopeFilter}\n GROUP BY gs.kind\n ORDER BY cnt DESC`,\n );\n\n return rows.map((r) => ({\n kind: r.kind,\n kindName: KIND_NAMES[r.kind] ?? 'Unknown',\n count: r.cnt,\n }));\n}\n"],"mappings":";;;;;AAQA,IAAM,aAAqC;AAAA,EACzC,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AAAA,EACH,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGA,IAAM,eAAe,oBAAI,IAAoB;AAC7C,WAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC/C,eAAa,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,CAAC;AAC7C;AAKO,SAAS,OACd,IACA,WACA,OAA2C,CAAC,GAC5B;AAChB,QAAM,EAAE,OAAO,QAAQ,IAAI,IAAI;AAG/B,MAAI,UAAyB;AAC7B,QAAM,QAAQ,SAAS,WAAW,EAAE;AACpC,MAAI,CAAC,MAAM,KAAK,GAAG;AACjB,cAAU;AAAA,EACZ,OAAO;AACL,cAAU,aAAa,IAAI,UAAU,YAAY,CAAC,KAAK;AAEvD,QAAI,YAAY,MAAM;AACpB,iBAAW,CAAC,MAAM,GAAG,KAAK,cAAc;AACtC,YAAI,KAAK,SAAS,UAAU,YAAY,CAAC,GAAG;AAC1C,oBAAU;AACV;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,MAAI,YAAY,MAAM;AACpB,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAGtE,QAAM,WAAW,GAAG;AAAA,IAClB;AAAA,EACF;AACA,MAAI,CAAC,YAAY,SAAS,MAAM,GAAG;AACjC,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,OAAO,GAAG;AAAA,IAOd;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,WAAW;AAAA;AAAA;AAAA,IAGf;AAAA,IAAS;AAAA,EACX;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,MAAM,EAAE;AAAA,IACR,UAAU,WAAW,EAAE,IAAI,KAAK;AAAA,IAChC,cAAc,EAAE;AAAA,IAChB,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,EACb,EAAE;AACN;AAGO,SAAS,WACd,IACA,OAA2B,CAAC,GAC8B;AAC1D,QAAM,cAAc,KAAK,QACrB,8BAA8B,KAAK,KAAK,OACxC;AAEJ,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA;AAAA;AAAA,QAKI,GAAG,kBAAkB,GAAG,CAAC;AAAA;AAAA;AAAA,QAGzB,WAAW;AAAA;AAAA;AAAA,EAGjB;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,MAAM,EAAE;AAAA,IACR,UAAU,WAAW,EAAE,IAAI,KAAK;AAAA,IAChC,OAAO,EAAE;AAAA,EACX,EAAE;AACJ;","names":[]}
|
package/dist/chunk-MCUX5LA7.js
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
findFirstSymbolMatch
|
|
3
|
-
} from "./chunk-ZOGY2V3N.js";
|
|
4
|
-
import {
|
|
5
|
-
shortenSymbol
|
|
6
|
-
} from "./chunk-QOV2R2WT.js";
|
|
7
|
-
|
|
8
|
-
// src/queries/dataflow.ts
|
|
9
|
-
function dataflow(db, symbolPattern) {
|
|
10
|
-
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
11
|
-
if (!match) return null;
|
|
12
|
-
const defSites = db.all(
|
|
13
|
-
`SELECT d.relative_path AS file, c.start_line AS line
|
|
14
|
-
FROM mentions m
|
|
15
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
16
|
-
JOIN documents d ON c.document_id = d.id
|
|
17
|
-
WHERE m.symbol_id = ? AND m.role = 1
|
|
18
|
-
ORDER BY d.relative_path, c.start_line`,
|
|
19
|
-
match.symbolId
|
|
20
|
-
);
|
|
21
|
-
const usageSites = db.all(
|
|
22
|
-
`SELECT d.relative_path AS file, c.start_line AS line,
|
|
23
|
-
(SELECT enc_gs.symbol
|
|
24
|
-
FROM defn_enclosing_ranges enc_der
|
|
25
|
-
JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
|
|
26
|
-
WHERE enc_der.document_id = d.id
|
|
27
|
-
AND enc_der.start_line <= c.start_line
|
|
28
|
-
AND enc_der.end_line >= c.end_line
|
|
29
|
-
ORDER BY (enc_der.end_line - enc_der.start_line) ASC
|
|
30
|
-
LIMIT 1
|
|
31
|
-
) AS enclosing_symbol
|
|
32
|
-
FROM mentions m
|
|
33
|
-
JOIN chunks c ON m.chunk_id = c.id
|
|
34
|
-
JOIN documents d ON c.document_id = d.id
|
|
35
|
-
WHERE m.symbol_id = ? AND m.role != 1
|
|
36
|
-
${db.pathExclusionsFor("d")}
|
|
37
|
-
ORDER BY d.relative_path, c.start_line`,
|
|
38
|
-
match.symbolId
|
|
39
|
-
);
|
|
40
|
-
const producers = db.all(
|
|
41
|
-
`SELECT DISTINCT other_gs.symbol, other_d.relative_path AS file
|
|
42
|
-
FROM mentions other_m
|
|
43
|
-
JOIN chunks other_c ON other_m.chunk_id = other_c.id
|
|
44
|
-
JOIN global_symbols other_gs ON other_m.symbol_id = other_gs.id
|
|
45
|
-
JOIN defn_enclosing_ranges other_der ON other_gs.id = other_der.symbol_id
|
|
46
|
-
JOIN documents other_d ON other_der.document_id = other_d.id
|
|
47
|
-
WHERE other_c.document_id = ?
|
|
48
|
-
AND other_c.start_line >= ? AND other_c.end_line <= ?
|
|
49
|
-
AND other_m.role != 1
|
|
50
|
-
AND other_gs.id != ?
|
|
51
|
-
${db.symbolNoiseFor("other_gs")}
|
|
52
|
-
${db.pathExclusionsFor("other_d")}
|
|
53
|
-
ORDER BY other_d.relative_path
|
|
54
|
-
LIMIT 30`,
|
|
55
|
-
match.documentId,
|
|
56
|
-
match.startLine,
|
|
57
|
-
match.endLine,
|
|
58
|
-
match.symbolId
|
|
59
|
-
);
|
|
60
|
-
const consumers = db.all(
|
|
61
|
-
`SELECT DISTINCT consumer_gs.symbol, consumer_d.relative_path AS file
|
|
62
|
-
FROM mentions ref_m
|
|
63
|
-
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
64
|
-
JOIN documents ref_d ON ref_c.document_id = ref_d.id
|
|
65
|
-
-- Find the enclosing function at each usage site
|
|
66
|
-
JOIN defn_enclosing_ranges enc_der
|
|
67
|
-
ON enc_der.document_id = ref_d.id
|
|
68
|
-
AND enc_der.start_line <= ref_c.start_line
|
|
69
|
-
AND enc_der.end_line >= ref_c.end_line
|
|
70
|
-
JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
|
|
71
|
-
-- Find other symbols defined by that enclosing function's file
|
|
72
|
-
JOIN mentions consumer_m ON consumer_m.symbol_id = enc_gs.id AND consumer_m.role != 1
|
|
73
|
-
JOIN chunks consumer_c ON consumer_m.chunk_id = consumer_c.id
|
|
74
|
-
JOIN documents consumer_d ON consumer_c.document_id = consumer_d.id
|
|
75
|
-
JOIN global_symbols consumer_gs ON consumer_m.symbol_id = consumer_gs.id
|
|
76
|
-
WHERE ref_m.symbol_id = ? AND ref_m.role != 1
|
|
77
|
-
AND consumer_d.id != ref_d.id
|
|
78
|
-
${db.symbolNoiseFor("consumer_gs")}
|
|
79
|
-
${db.pathExclusionsFor("consumer_d")}
|
|
80
|
-
ORDER BY consumer_d.relative_path
|
|
81
|
-
LIMIT 30`,
|
|
82
|
-
match.symbolId
|
|
83
|
-
);
|
|
84
|
-
return {
|
|
85
|
-
symbol: match.symbol,
|
|
86
|
-
shortName: shortenSymbol(match.symbol),
|
|
87
|
-
relativePath: match.relativePath,
|
|
88
|
-
definitionSites: defSites.filter((s) => !db.isIgnored(s.file)),
|
|
89
|
-
usageSites: usageSites.filter((s) => !db.isIgnored(s.file)).map((s) => ({
|
|
90
|
-
file: s.file,
|
|
91
|
-
line: s.line,
|
|
92
|
-
enclosingSymbol: s.enclosing_symbol ?? "(top-level)",
|
|
93
|
-
enclosingShort: s.enclosing_symbol ? shortenSymbol(s.enclosing_symbol) : "(top-level)"
|
|
94
|
-
})),
|
|
95
|
-
producers: producers.filter((p) => !db.isIgnored(p.file)).map((p) => ({ symbol: p.symbol, shortName: shortenSymbol(p.symbol), file: p.file })),
|
|
96
|
-
consumers: consumers.filter((c) => !db.isIgnored(c.file)).map((c) => ({ symbol: c.symbol, shortName: shortenSymbol(c.symbol), file: c.file }))
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export {
|
|
101
|
-
dataflow
|
|
102
|
-
};
|
|
103
|
-
//# sourceMappingURL=chunk-MCUX5LA7.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/dataflow.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch } from '../query-support.js';\nimport type { DataflowResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Reference-level dataflow analysis: where does data around this symbol\n * come from and where does it go?\n *\n * This is not value-level dataflow (we can't trace x = foo(); bar(x);\n * as a chain). Instead it shows:\n * - Where the symbol is defined and used\n * - What other symbols appear in the same enclosing scope (co-occurring data)\n * - What feeds into the function that defines it (producers)\n * - What consumes the function that uses it (consumers)\n *\n * Language-agnostic: works with any SCIP index.\n */\nexport function dataflow(\n db: ScipDatabase,\n symbolPattern: string,\n): DataflowResult | null {\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n // Definition sites (role=1)\n const defSites = db.all<{ file: string; line: number }>(\n `SELECT d.relative_path AS file, c.start_line AS line\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n WHERE m.symbol_id = ? AND m.role = 1\n ORDER BY d.relative_path, c.start_line`,\n match.symbolId,\n );\n\n // Usage sites (role=0) with enclosing symbol\n const usageSites = db.all<{\n file: string;\n line: number;\n enclosing_symbol: string | null;\n }>(\n `SELECT d.relative_path AS file, c.start_line AS line,\n (SELECT enc_gs.symbol\n FROM defn_enclosing_ranges enc_der\n JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id\n WHERE enc_der.document_id = d.id\n AND enc_der.start_line <= c.start_line\n AND enc_der.end_line >= c.end_line\n ORDER BY (enc_der.end_line - enc_der.start_line) ASC\n LIMIT 1\n ) AS enclosing_symbol\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d ON c.document_id = d.id\n WHERE m.symbol_id = ? AND m.role != 1\n ${db.pathExclusionsFor('d')}\n ORDER BY d.relative_path, c.start_line`,\n match.symbolId,\n );\n\n // Producers: other symbols referenced within the same function that defines our target\n const producers = db.all<{ symbol: string; file: string }>(\n `SELECT DISTINCT other_gs.symbol, other_d.relative_path AS file\n FROM mentions other_m\n JOIN chunks other_c ON other_m.chunk_id = other_c.id\n JOIN global_symbols other_gs ON other_m.symbol_id = other_gs.id\n JOIN defn_enclosing_ranges other_der ON other_gs.id = other_der.symbol_id\n JOIN documents other_d ON other_der.document_id = other_d.id\n WHERE other_c.document_id = ?\n AND other_c.start_line >= ? AND other_c.end_line <= ?\n AND other_m.role != 1\n AND other_gs.id != ?\n ${db.symbolNoiseFor('other_gs')}\n ${db.pathExclusionsFor('other_d')}\n ORDER BY other_d.relative_path\n LIMIT 30`,\n match.documentId, match.startLine, match.endLine, match.symbolId,\n );\n\n // Consumers: symbols exported/defined by functions that reference our target\n // (what does the data flow into after being used)\n const consumers = db.all<{ symbol: string; file: string }>(\n `SELECT DISTINCT consumer_gs.symbol, consumer_d.relative_path AS file\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN documents ref_d ON ref_c.document_id = ref_d.id\n -- Find the enclosing function at each usage site\n JOIN defn_enclosing_ranges enc_der\n ON enc_der.document_id = ref_d.id\n AND enc_der.start_line <= ref_c.start_line\n AND enc_der.end_line >= ref_c.end_line\n JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id\n -- Find other symbols defined by that enclosing function's file\n JOIN mentions consumer_m ON consumer_m.symbol_id = enc_gs.id AND consumer_m.role != 1\n JOIN chunks consumer_c ON consumer_m.chunk_id = consumer_c.id\n JOIN documents consumer_d ON consumer_c.document_id = consumer_d.id\n JOIN global_symbols consumer_gs ON consumer_m.symbol_id = consumer_gs.id\n WHERE ref_m.symbol_id = ? AND ref_m.role != 1\n AND consumer_d.id != ref_d.id\n ${db.symbolNoiseFor('consumer_gs')}\n ${db.pathExclusionsFor('consumer_d')}\n ORDER BY consumer_d.relative_path\n LIMIT 30`,\n match.symbolId,\n );\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n relativePath: match.relativePath,\n definitionSites: defSites.filter((s) => !db.isIgnored(s.file)),\n usageSites: usageSites\n .filter((s) => !db.isIgnored(s.file))\n .map((s) => ({\n file: s.file,\n line: s.line,\n enclosingSymbol: s.enclosing_symbol ?? '(top-level)',\n enclosingShort: s.enclosing_symbol ? shortenSymbol(s.enclosing_symbol) : '(top-level)',\n })),\n producers: producers\n .filter((p) => !db.isIgnored(p.file))\n .map((p) => ({ symbol: p.symbol, shortName: shortenSymbol(p.symbol), file: p.file })),\n consumers: consumers\n .filter((c) => !db.isIgnored(c.file))\n .map((c) => ({ symbol: c.symbol, shortName: shortenSymbol(c.symbol), file: c.file })),\n };\n}\n"],"mappings":";;;;;;;;AAkBO,SAAS,SACd,IACA,eACuB;AACvB,QAAM,QAAQ,qBAAqB,IAAI,aAAa;AACpD,MAAI,CAAC,MAAO,QAAO;AAGnB,QAAM,WAAW,GAAG;AAAA,IAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,MAAM;AAAA,EACR;AAGA,QAAM,aAAa,GAAG;AAAA,IAKpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcI,GAAG,kBAAkB,GAAG,CAAC;AAAA;AAAA,IAE7B,MAAM;AAAA,EACR;AAGA,QAAM,YAAY,GAAG;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUI,GAAG,eAAe,UAAU,CAAC;AAAA,QAC7B,GAAG,kBAAkB,SAAS,CAAC;AAAA;AAAA;AAAA,IAGnC,MAAM;AAAA,IAAY,MAAM;AAAA,IAAW,MAAM;AAAA,IAAS,MAAM;AAAA,EAC1D;AAIA,QAAM,YAAY,GAAG;AAAA,IACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAiBI,GAAG,eAAe,aAAa,CAAC;AAAA,QAChC,GAAG,kBAAkB,YAAY,CAAC;AAAA;AAAA;AAAA,IAGtC,MAAM;AAAA,EACR;AAEA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,cAAc,MAAM;AAAA,IACpB,iBAAiB,SAAS,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC;AAAA,IAC7D,YAAY,WACT,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO;AAAA,MACX,MAAM,EAAE;AAAA,MACR,MAAM,EAAE;AAAA,MACR,iBAAiB,EAAE,oBAAoB;AAAA,MACvC,gBAAgB,EAAE,mBAAmB,cAAc,EAAE,gBAAgB,IAAI;AAAA,IAC3E,EAAE;AAAA,IACJ,WAAW,UACR,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,WAAW,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE;AAAA,IACtF,WAAW,UACR,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,IAAI,CAAC,EACnC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,WAAW,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE;AAAA,EACxF;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/files.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { FileResult } from '../types.js';\n\nexport function files(db: ScipDatabase, pattern: string): FileResult[] {\n const rows = db.all<{ relative_path: string }>(\n `SELECT relative_path FROM documents\n WHERE relative_path LIKE ?\n ORDER BY relative_path`,\n `%${pattern}%`,\n );\n\n // Apply gitignore filtering\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({ relativePath: r.relative_path }));\n}\n"],"mappings":";AAGO,SAAS,MAAM,IAAkB,SAA+B;AACrE,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA,IAGA,IAAI,OAAO;AAAA,EACb;AAGA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE;AACnD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/deps.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { DepResult } from '../types.js';\n\n/** What internal files does this file depend on? (forward dependencies) */\nexport function deps(db: ScipDatabase, filePattern: string): DepResult[] {\n const rows = db.all<{ relative_path: string }>(\n `SELECT DISTINCT d2.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d1.relative_path LIKE ?\n AND d2.relative_path <> d1.relative_path\n AND ${db.localSymbolPredicate}\n ORDER BY d2.relative_path`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({ relativePath: r.relative_path }));\n}\n\n/** What files depend on this file/module? (reverse dependencies) */\nexport function rdeps(db: ScipDatabase, filePattern: string): DepResult[] {\n const rows = db.all<{ relative_path: string }>(\n `SELECT DISTINCT d1.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents d1 ON c.document_id = d1.id\n JOIN global_symbols gs ON m.symbol_id = gs.id\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d2 ON der.document_id = d2.id\n WHERE d2.relative_path LIKE ?\n AND d1.relative_path NOT LIKE ?\n ORDER BY d1.relative_path`,\n `%${filePattern}%`,\n `%${filePattern}%`,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({ relativePath: r.relative_path }));\n}\n"],"mappings":";AAIO,SAAS,KAAK,IAAkB,aAAkC;AACvE,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YASQ,GAAG,oBAAoB;AAAA;AAAA,IAE/B,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE;AACnD;AAGO,SAAS,MAAM,IAAkB,aAAkC;AACxE,QAAM,OAAO,GAAG;AAAA,IACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAUA,IAAI,WAAW;AAAA,IACf,IAAI,WAAW;AAAA,EACjB;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE;AACnD;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/slice.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol, type SymbolMatch } from '../query-support.js';\nimport type { SliceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Reference-level program slicing: track what affects a symbol (backward)\n * or what a symbol affects (forward).\n *\n * Backward slice: \"What feeds into this?\" — symbols referenced in the same\n * function that defines the target. These are the inputs/dependencies.\n *\n * Forward slice: \"What does this feed into?\" — at each site where the target\n * is referenced, find the enclosing function, then find what that function\n * exports/defines. These are the outputs/consumers.\n *\n * Language-agnostic: works with any SCIP index.\n */\nexport function slice(\n db: ScipDatabase,\n symbolPattern: string,\n opts: { direction?: 'backward' | 'forward' } = {},\n): SliceResult | null {\n const { direction = 'backward' } = opts;\n\n const match = findFirstSymbolMatch(db, symbolPattern);\n if (!match) return null;\n\n if (direction === 'backward') {\n return backwardSlice(db, match);\n } else {\n return forwardSlice(db, match);\n }\n}\n\n\nfunction backwardSlice(db: ScipDatabase, match: SymbolMatch): SliceResult {\n // Find all symbols referenced within the definition range of the target.\n // These are what \"feeds into\" the target — the inputs.\n const callees = getCalleeRowsForSymbol(db, match);\n\n // Also find symbols whose definitions are in the same file and whose\n // ranges overlap or precede the target — local variables, parameters, etc.\n const localPredecessors = db.all<{ symbol: string; file: string }>(\n `SELECT DISTINCT gs.symbol, d.relative_path AS file\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n JOIN documents d ON der.document_id = d.id\n WHERE der.document_id = ?\n AND der.end_line < ?\n AND gs.id != ?\n ${db.symbolNoiseFor('gs')}\n ORDER BY der.start_line DESC\n LIMIT 15`,\n match.documentId, match.startLine, match.symbolId,\n );\n\n const seen = new Set<string>();\n const connected: SliceResult['connectedSymbols'] = [];\n\n for (const c of callees) {\n if (seen.has(c.symbol)) continue;\n seen.add(c.symbol);\n connected.push({\n symbol: c.symbol,\n shortName: shortenSymbol(c.symbol),\n file: c.file,\n relationship: 'referenced within definition (callee)',\n });\n }\n\n for (const p of localPredecessors) {\n if (seen.has(p.symbol) || db.isIgnored(p.file)) continue;\n seen.add(p.symbol);\n connected.push({\n symbol: p.symbol,\n shortName: shortenSymbol(p.symbol),\n file: p.file,\n relationship: 'defined before target in same file (local predecessor)',\n });\n }\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n direction: 'backward',\n connectedSymbols: connected,\n };\n}\n\nfunction forwardSlice(db: ScipDatabase, match: SymbolMatch): SliceResult {\n // Find where the target is referenced, then at each reference site,\n // find what else the enclosing function defines/exports.\n const rows = db.all<{\n enclosing_symbol: string;\n enclosing_file: string;\n output_symbol: string;\n output_file: string;\n }>(\n `SELECT DISTINCT\n enc_gs.symbol AS enclosing_symbol,\n enc_d.relative_path AS enclosing_file,\n out_gs.symbol AS output_symbol,\n out_d.relative_path AS output_file\n FROM mentions ref_m\n JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id\n JOIN documents ref_d ON ref_c.document_id = ref_d.id\n -- Find enclosing function at each reference site\n JOIN defn_enclosing_ranges enc_der\n ON enc_der.document_id = ref_d.id\n AND enc_der.start_line <= ref_c.start_line\n AND enc_der.end_line >= ref_c.end_line\n JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id\n JOIN documents enc_d ON enc_der.document_id = enc_d.id\n -- Find other symbols referenced within that enclosing function\n JOIN mentions out_m ON out_m.role != 1\n JOIN chunks out_c ON out_m.chunk_id = out_c.id\n AND out_c.document_id = enc_der.document_id\n AND out_c.start_line >= enc_der.start_line\n AND out_c.end_line <= enc_der.end_line\n JOIN global_symbols out_gs ON out_m.symbol_id = out_gs.id\n JOIN defn_enclosing_ranges out_der ON out_gs.id = out_der.symbol_id\n JOIN documents out_d ON out_der.document_id = out_d.id\n WHERE ref_m.symbol_id = ? AND ref_m.role != 1\n AND out_gs.id != ? AND out_gs.id != enc_gs.id\n AND out_d.id != ref_d.id\n ${db.symbolNoiseFor('out_gs')}\n ${db.pathExclusionsFor('out_d')}\n ORDER BY out_d.relative_path\n LIMIT 30`,\n match.symbolId, match.symbolId,\n );\n\n const seen = new Set<string>();\n const connected: SliceResult['connectedSymbols'] = [];\n\n for (const r of rows) {\n if (seen.has(r.output_symbol) || db.isIgnored(r.output_file)) continue;\n seen.add(r.output_symbol);\n connected.push({\n symbol: r.output_symbol,\n shortName: shortenSymbol(r.output_symbol),\n file: r.output_file,\n relationship: `used alongside target in ${shortenSymbol(r.enclosing_symbol)}`,\n });\n }\n\n return {\n symbol: match.symbol,\n shortName: shortenSymbol(match.symbol),\n direction: 'forward',\n connectedSymbols: connected,\n };\n}\n"],"mappings":";;;;;;;;;AAkBO,SAAS,MACd,IACA,eACA,OAA+C,CAAC,GAC5B;AACpB,QAAM,EAAE,YAAY,WAAW,IAAI;AAEnC,QAAM,QAAQ,qBAAqB,IAAI,aAAa;AACpD,MAAI,CAAC,MAAO,QAAO;AAEnB,MAAI,cAAc,YAAY;AAC5B,WAAO,cAAc,IAAI,KAAK;AAAA,EAChC,OAAO;AACL,WAAO,aAAa,IAAI,KAAK;AAAA,EAC/B;AACF;AAGA,SAAS,cAAc,IAAkB,OAAiC;AAGxE,QAAM,UAAU,uBAAuB,IAAI,KAAK;AAIhD,QAAM,oBAAoB,GAAG;AAAA,IAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOI,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,IAG3B,MAAM;AAAA,IAAY,MAAM;AAAA,IAAW,MAAM;AAAA,EAC3C;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,YAA6C,CAAC;AAEpD,aAAW,KAAK,SAAS;AACvB,QAAI,KAAK,IAAI,EAAE,MAAM,EAAG;AACxB,SAAK,IAAI,EAAE,MAAM;AACjB,cAAU,KAAK;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,MAAM,EAAE;AAAA,MACR,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAEA,aAAW,KAAK,mBAAmB;AACjC,QAAI,KAAK,IAAI,EAAE,MAAM,KAAK,GAAG,UAAU,EAAE,IAAI,EAAG;AAChD,SAAK,IAAI,EAAE,MAAM;AACjB,cAAU,KAAK;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,MAAM;AAAA,MACjC,MAAM,EAAE;AAAA,MACR,cAAc;AAAA,IAChB,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,WAAW;AAAA,IACX,kBAAkB;AAAA,EACpB;AACF;AAEA,SAAS,aAAa,IAAkB,OAAiC;AAGvE,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QA2BI,GAAG,eAAe,QAAQ,CAAC;AAAA,QAC3B,GAAG,kBAAkB,OAAO,CAAC;AAAA;AAAA;AAAA,IAGjC,MAAM;AAAA,IAAU,MAAM;AAAA,EACxB;AAEA,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,YAA6C,CAAC;AAEpD,aAAW,KAAK,MAAM;AACpB,QAAI,KAAK,IAAI,EAAE,aAAa,KAAK,GAAG,UAAU,EAAE,WAAW,EAAG;AAC9D,SAAK,IAAI,EAAE,aAAa;AACxB,cAAU,KAAK;AAAA,MACb,QAAQ,EAAE;AAAA,MACV,WAAW,cAAc,EAAE,aAAa;AAAA,MACxC,MAAM,EAAE;AAAA,MACR,cAAc,4BAA4B,cAAc,EAAE,gBAAgB,CAAC;AAAA,IAC7E,CAAC;AAAA,EACH;AAEA,SAAO;AAAA,IACL,QAAQ,MAAM;AAAA,IACd,WAAW,cAAc,MAAM,MAAM;AAAA,IACrC,WAAW;AAAA,IACX,kBAAkB;AAAA,EACpB;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/methods.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { MethodResult } from '../types.js';\nimport { leafName } from '../symbol-parser.js';\n\nexport function methods(db: ScipDatabase, className: string): MethodResult[] {\n const rows = db.all<{\n start_line: number;\n end_line: number;\n symbol: string;\n }>(\n `SELECT der.start_line, der.end_line, gs.symbol\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n WHERE gs.symbol LIKE ?\n AND ${db.localSymbolPredicate}\n AND gs.symbol LIKE '%().%'\n ${db.symbolNoise}\n ORDER BY der.start_line`,\n `%${className}#%`,\n );\n\n return rows.map((r) => ({\n startLine: r.start_line,\n endLine: r.end_line,\n name: leafName(r.symbol),\n }));\n}\n"],"mappings":";;;;;AAIO,SAAS,QAAQ,IAAkB,WAAmC;AAC3E,QAAM,OAAO,GAAG;AAAA,IAKd;AAAA;AAAA;AAAA;AAAA,YAIQ,GAAG,oBAAoB;AAAA;AAAA,QAE3B,GAAG,WAAW;AAAA;AAAA,IAElB,IAAI,SAAS;AAAA,EACf;AAEA,SAAO,KAAK,IAAI,CAAC,OAAO;AAAA,IACtB,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,MAAM,SAAS,EAAE,MAAM;AAAA,EACzB,EAAE;AACJ;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/change-surface.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { TEST_FILE_PATTERNS, testFileMatchSql } from '../query-support.js';\nimport type { ChangeSurfaceEntry, ChangeSurfaceResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Pre-change briefing for a file. For each symbol defined in the file,\n * reports external consumer count, test coverage, and risk level.\n */\nexport function changeSurface(\n db: ScipDatabase,\n filePattern: string,\n): ChangeSurfaceResult | null {\n // Find the file\n const doc = db.get<{ id: number; relative_path: string }>(\n `SELECT id, relative_path FROM documents\n WHERE relative_path LIKE ?\n ${db.pathExclusionsFor('documents')}\n LIMIT 1`,\n `%${filePattern}%`,\n );\n\n if (!doc || db.isIgnored(doc.relative_path)) return null;\n\n // Get all symbols defined in this file, excluding typeLiterals\n const syms = db.all<{\n symbol_id: number;\n symbol: string;\n start_line: number;\n end_line: number;\n }>(\n `SELECT DISTINCT gs.id AS symbol_id, gs.symbol, der.start_line, der.end_line\n FROM defn_enclosing_ranges der\n JOIN global_symbols gs ON der.symbol_id = gs.id\n WHERE der.document_id = ?\n ${db.symbolNoiseFor('gs')}\n ORDER BY der.start_line`,\n doc.id,\n );\n\n const testPatternSql = testFileMatchSql('ref_d', TEST_FILE_PATTERNS);\n\n const symbols: ChangeSurfaceEntry[] = [];\n let totalExternalConsumers = 0;\n let coveredCount = 0;\n\n for (const sym of syms) {\n // Count external consumers: mentions with role=0 from different documents\n const consumerRow = db.get<{ consumer_count: number }>(\n `SELECT COUNT(DISTINCT c.document_id) AS consumer_count\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND c.document_id != ?`,\n sym.symbol_id,\n doc.id,\n );\n\n const externalConsumers = consumerRow?.consumer_count ?? 0;\n\n // Find test files that reference this symbol\n const testFiles = db.all<{ relative_path: string }>(\n `SELECT DISTINCT ref_d.relative_path\n FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n JOIN documents ref_d ON c.document_id = ref_d.id\n WHERE m.symbol_id = ?\n AND m.role != 1\n AND (${testPatternSql})\n ORDER BY ref_d.relative_path`,\n sym.symbol_id,\n ).map((r) => r.relative_path);\n\n const hasTests = testFiles.length > 0;\n if (hasTests) coveredCount++;\n\n // Risk level determination\n let riskLevel: 'low' | 'medium' | 'high';\n if (externalConsumers > 10 && !hasTests) {\n riskLevel = 'high';\n } else if (externalConsumers > 5 || !hasTests) {\n riskLevel = 'medium';\n } else {\n riskLevel = 'low';\n }\n\n totalExternalConsumers += externalConsumers;\n\n symbols.push({\n symbol: sym.symbol,\n shortName: shortenSymbol(sym.symbol),\n startLine: sym.start_line,\n endLine: sym.end_line,\n externalConsumers,\n testFiles,\n riskLevel,\n });\n }\n\n const testCoveragePercent =\n symbols.length > 0 ? Math.round((coveredCount / symbols.length) * 100) : 0;\n\n return {\n file: doc.relative_path,\n symbols,\n totalExternalConsumers,\n testCoveragePercent,\n };\n}\n"],"mappings":";;;;;;;;;AASO,SAAS,cACd,IACA,aAC4B;AAE5B,QAAM,MAAM,GAAG;AAAA,IACb;AAAA;AAAA,SAEK,GAAG,kBAAkB,WAAW,CAAC;AAAA;AAAA,IAEtC,IAAI,WAAW;AAAA,EACjB;AAEA,MAAI,CAAC,OAAO,GAAG,UAAU,IAAI,aAAa,EAAG,QAAO;AAGpD,QAAM,OAAO,GAAG;AAAA,IAMd;AAAA;AAAA;AAAA;AAAA,QAII,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA,IAE3B,IAAI;AAAA,EACN;AAEA,QAAM,iBAAiB,iBAAiB,SAAS,kBAAkB;AAEnE,QAAM,UAAgC,CAAC;AACvC,MAAI,yBAAyB;AAC7B,MAAI,eAAe;AAEnB,aAAW,OAAO,MAAM;AAEtB,UAAM,cAAc,GAAG;AAAA,MACrB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,IAAI;AAAA,MACJ,IAAI;AAAA,IACN;AAEA,UAAM,oBAAoB,aAAa,kBAAkB;AAGzD,UAAM,YAAY,GAAG;AAAA,MACnB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAMS,cAAc;AAAA;AAAA,MAEvB,IAAI;AAAA,IACN,EAAE,IAAI,CAAC,MAAM,EAAE,aAAa;AAE5B,UAAM,WAAW,UAAU,SAAS;AACpC,QAAI,SAAU;AAGd,QAAI;AACJ,QAAI,oBAAoB,MAAM,CAAC,UAAU;AACvC,kBAAY;AAAA,IACd,WAAW,oBAAoB,KAAK,CAAC,UAAU;AAC7C,kBAAY;AAAA,IACd,OAAO;AACL,kBAAY;AAAA,IACd;AAEA,8BAA0B;AAE1B,YAAQ,KAAK;AAAA,MACX,QAAQ,IAAI;AAAA,MACZ,WAAW,cAAc,IAAI,MAAM;AAAA,MACnC,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,sBACJ,QAAQ,SAAS,IAAI,KAAK,MAAO,eAAe,QAAQ,SAAU,GAAG,IAAI;AAE3E,SAAO;AAAA,IACL,MAAM,IAAI;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/isolated.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { testFileExclusionSql } from '../query-support.js';\nimport type { IsolatedResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find isolated symbols: defined locally, referenced by nothing,\n * and referencing nothing external. These are truly orphaned code —\n * not just unused exports, but completely disconnected from the graph.\n */\nexport function isolated(\n db: ScipDatabase,\n opts: { scope?: string; minLoc?: number } = {},\n): IsolatedResult[] {\n const { scope, minLoc = 3 } = opts;\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n const rows = db.all<{\n symbol: string;\n relative_path: string;\n start_line: number;\n end_line: number;\n loc: number;\n }>(\n `SELECT\n gs.symbol,\n d.relative_path,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n AND gs.symbol NOT LIKE '%#%'\n AND (der.end_line - der.start_line + 1) >= ?\n ${scopeFilter}\n -- No cross-file references TO this symbol\n AND NOT EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id != d.id\n )\n -- No same-file references either\n AND NOT EXISTS (\n SELECT 1 FROM mentions m\n JOIN chunks c ON m.chunk_id = c.id\n WHERE m.symbol_id = gs.id AND m.role != 1 AND c.document_id = d.id\n )\n ORDER BY loc DESC, d.relative_path`,\n minLoc,\n );\n\n return rows\n .filter((r) => !db.isIgnored(r.relative_path))\n .map((r) => ({\n symbol: r.symbol,\n shortName: shortenSymbol(r.symbol),\n relativePath: r.relative_path,\n startLine: r.start_line,\n endLine: r.end_line,\n loc: r.loc,\n }));\n}\n"],"mappings":";;;;;;;;AAUO,SAAS,SACd,IACA,OAA4C,CAAC,GAC3B;AAClB,QAAM,EAAE,OAAO,SAAS,EAAE,IAAI;AAC9B,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAEtE,QAAM,OAAO,GAAG;AAAA,IAOd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAUI,GAAG,kBAAkB,GAAG,CAAC;AAAA,YACrB,qBAAqB,GAAG,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,QAGvB,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAcf;AAAA,EACF;AAEA,SAAO,KACJ,OAAO,CAAC,MAAM,CAAC,GAAG,UAAU,EAAE,aAAa,CAAC,EAC5C,IAAI,CAAC,OAAO;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,WAAW,cAAc,EAAE,MAAM;AAAA,IACjC,cAAc,EAAE;AAAA,IAChB,WAAW,EAAE;AAAA,IACb,SAAS,EAAE;AAAA,IACX,KAAK,EAAE;AAAA,EACT,EAAE;AACN;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/symbol-parser.ts"],"sourcesContent":["import type { ScipSymbol, ScipDescriptor, ScipLocalSymbol, DescriptorSuffix } from './types.js';\n\n/**\n * SCIP Symbol Grammar (from the SCIP spec):\n *\n * <symbol> ::= <scheme> ' ' <package> ' ' <descriptor>+ | 'local ' <local-id>\n * <package> ::= <manager> ' ' <package-name> ' ' <version> ' '\n * <descriptor> ::= <name> <suffix>\n *\n * Suffix characters:\n * / namespace\n * # type (class, interface, enum)\n * . term (variable, field, property)\n * (). method\n * [ type parameter\n * () parameter\n * : meta\n * ! macro\n *\n * Names may be backtick-escaped: `some.weird/name`\n */\n\nconst SUFFIX_MAP: Record<string, DescriptorSuffix> = {\n '/': 'namespace',\n '#': 'type',\n '.': 'term',\n '[': 'type-param',\n ':': 'meta',\n '!': 'macro',\n};\n\n/**\n * Parse a SCIP symbol string into its structured components.\n * Works for any SCIP-indexed language (TypeScript, Java, Rust, Python, etc.)\n */\nexport function parseSymbol(raw: string): ScipSymbol | ScipLocalSymbol {\n if (raw.startsWith('local ')) {\n return { kind: 'local', id: raw.slice(6), raw };\n }\n\n // Split: <scheme> <manager> <package-name> <version> <descriptors...>\n // The tricky part: package-name can contain spaces if backtick-escaped\n const parts = raw.split(' ');\n if (parts.length < 4) {\n // Malformed — return a best-effort parse\n return {\n scheme: parts[0] ?? '',\n manager: parts[1] ?? '',\n packageName: parts[2] ?? '',\n version: '',\n descriptors: [],\n raw,\n };\n }\n\n const scheme = parts[0]!;\n const manager = parts[1]!;\n\n // Package name and version: package name might be backtick-escaped\n // After scheme + manager, we need to find package + version + descriptor string\n let restAfterManager = raw.slice(scheme.length + 1 + manager.length + 1);\n\n // Parse package name (may be backtick-escaped)\n let packageName: string;\n if (restAfterManager.startsWith('`')) {\n const closingTick = restAfterManager.indexOf('`', 1);\n if (closingTick === -1) {\n packageName = restAfterManager.slice(1);\n restAfterManager = '';\n } else {\n packageName = restAfterManager.slice(1, closingTick);\n restAfterManager = restAfterManager.slice(closingTick + 2); // skip ` and space\n }\n } else {\n const spaceIdx = restAfterManager.indexOf(' ');\n if (spaceIdx === -1) {\n packageName = restAfterManager;\n restAfterManager = '';\n } else {\n packageName = restAfterManager.slice(0, spaceIdx);\n restAfterManager = restAfterManager.slice(spaceIdx + 1);\n }\n }\n\n // Parse version\n let version: string;\n const versionSpaceIdx = restAfterManager.indexOf(' ');\n if (versionSpaceIdx === -1) {\n version = restAfterManager;\n restAfterManager = '';\n } else {\n version = restAfterManager.slice(0, versionSpaceIdx);\n restAfterManager = restAfterManager.slice(versionSpaceIdx + 1);\n }\n\n // Parse descriptors from the remaining string\n const descriptors = parseDescriptors(restAfterManager);\n\n return { scheme, manager, packageName, version, descriptors, raw };\n}\n\n/**\n * Parse the descriptor chain from a SCIP symbol.\n *\n * SCIP descriptor grammar:\n * namespace: name/\n * type: name#\n * term: name.\n * method: name(disambiguator).\n * type-param: [name] (bracket-wrapped, prefix syntax)\n * parameter: (name) (paren-wrapped, prefix syntax)\n * meta: name:\n * macro: name!\n *\n * Names can be backtick-escaped: `some/name.with.dots`\n */\nfunction parseDescriptors(input: string): ScipDescriptor[] {\n const descriptors: ScipDescriptor[] = [];\n let i = 0;\n\n while (i < input.length) {\n // Type parameter: [name]\n if (input[i] === '[') {\n const closeBracket = input.indexOf(']', i + 1);\n if (closeBracket === -1) {\n descriptors.push({ name: input.slice(i + 1), suffix: 'type-param' });\n break;\n }\n descriptors.push({ name: input.slice(i + 1, closeBracket), suffix: 'type-param' });\n i = closeBracket + 1;\n continue;\n }\n\n // Parameter: (name) — only when ( appears at descriptor start with no preceding name\n if (input[i] === '(' && (descriptors.length === 0 || i === 0 || isSuffixChar(input[i - 1]!))) {\n const closeParen = input.indexOf(')', i + 1);\n if (closeParen !== -1 && input[closeParen + 1] !== '.') {\n // This is a parameter (name), not a method disambiguator\n descriptors.push({ name: input.slice(i + 1, closeParen), suffix: 'parameter' });\n i = closeParen + 1;\n continue;\n }\n }\n\n let name: string;\n\n // Backtick-escaped name\n if (input[i] === '`') {\n const closingTick = input.indexOf('`', i + 1);\n if (closingTick === -1) {\n name = input.slice(i + 1);\n i = input.length;\n descriptors.push({ name, suffix: 'term' });\n break;\n }\n name = input.slice(i + 1, closingTick);\n i = closingTick + 1;\n } else {\n // Read name until we hit a suffix character\n const start = i;\n while (i < input.length && !isSuffixChar(input[i]!)) {\n i++;\n }\n name = input.slice(start, i);\n }\n\n // Parse suffix after name\n if (i >= input.length) {\n if (name) descriptors.push({ name, suffix: 'term' });\n break;\n }\n\n const char = input[i]!;\n\n // Method: name(disambiguator).\n if (char === '(') {\n const closeParen = input.indexOf(')', i + 1);\n if (closeParen !== -1 && input[closeParen + 1] === '.') {\n descriptors.push({ name, suffix: 'method' });\n i = closeParen + 2; // skip past ).\n } else if (closeParen !== -1) {\n // Bare (disambiguator) without . — treat as method anyway (common in practice)\n descriptors.push({ name, suffix: 'method' });\n i = closeParen + 1;\n } else {\n descriptors.push({ name, suffix: 'term' });\n i++;\n }\n } else {\n const suffix = SUFFIX_MAP[char];\n if (suffix) {\n descriptors.push({ name, suffix });\n i += 1;\n } else {\n i += 1; // Unknown suffix — skip\n }\n }\n }\n\n return descriptors;\n}\n\nfunction isSuffixChar(c: string): boolean {\n return c === '/' || c === '#' || c === '.' || c === '(' || c === '[' || c === ':' || c === '!';\n}\n\n/**\n * Convert a parsed SCIP symbol to a short, human-readable name.\n * Language-agnostic: works for any SCIP-indexed language.\n *\n * Examples:\n * \"scip-typescript npm @vega/api 0.1.3 src/modules/auth/auth.service.ts/AuthService#login().\"\n * → \"auth.service:AuthService:login()\"\n *\n * \"scip-java maven com.example/mylib 1.0.0 com/example/MyClass#doStuff().\"\n * → \"MyClass:doStuff()\"\n *\n * \"rust-analyzer cargo my-crate 0.1.0 src/lib.rs/MyStruct#new().\"\n * → \"lib:MyStruct:new()\"\n */\nexport function shortenSymbol(raw: string): string {\n const parsed = parseSymbol(raw);\n if ('kind' in parsed && parsed.kind === 'local') {\n return `local:${parsed.id}`;\n }\n\n const sym = parsed as ScipSymbol;\n if (sym.descriptors.length === 0) return sym.raw;\n\n const parts: string[] = [];\n for (const desc of sym.descriptors) {\n // Strip file extensions from namespace descriptors (the file path parts)\n let name = desc.name;\n if (desc.suffix === 'namespace') {\n // Remove common file extensions\n name = name\n .replace(/\\.(ts|tsx|js|jsx|mjs|cjs)$/, '')\n .replace(/\\.(py|pyi)$/, '')\n .replace(/\\.(rs)$/, '')\n .replace(/\\.(java|scala|kt|kts)$/, '')\n .replace(/\\.(rb)$/, '')\n .replace(/\\.(go)$/, '')\n .replace(/\\.(cs|vb)$/, '')\n .replace(/\\.(dart)$/, '')\n .replace(/\\.(php)$/, '')\n .replace(/\\.(c|cc|cpp|cxx|h|hpp)$/, '');\n }\n\n // Skip empty names (can happen with trailing suffixes)\n if (!name) continue;\n\n // For methods, append () for clarity\n if (desc.suffix === 'method') {\n parts.push(`${name}()`);\n } else {\n parts.push(name);\n }\n }\n\n return parts.join(':');\n}\n\n/**\n * Extract just the leaf name from a SCIP symbol.\n * Useful when you only need the function/class/variable name without path context.\n */\nexport function leafName(raw: string): string {\n const parsed = parseSymbol(raw);\n if ('kind' in parsed && parsed.kind === 'local') {\n return parsed.id;\n }\n\n const sym = parsed as ScipSymbol;\n if (sym.descriptors.length === 0) return '';\n\n const last = sym.descriptors[sym.descriptors.length - 1]!;\n return last.name;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/similar-signatures.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { SimilarSignatureGroup } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find functions with near-identical type signatures (same parameter types\n * and return type) but different names. These are \"same shape\" functions\n * that may be doing similar work even if their internal implementation differs.\n *\n * The SCIP `documentation` field often contains the full type signature\n * after a `|` delimiter. We parse it, normalize it (strip the function name,\n * whitespace, and case), then group by normalized signature.\n *\n * Groups with 2+ functions = same-shape candidates.\n */\nexport function similarSignatures(\n db: ScipDatabase,\n opts: { scope?: string; minLoc?: number; limit?: number } = {},\n): SimilarSignatureGroup[] {\n const { scope, minLoc = 1, limit } = opts;\n\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // Get all function-level symbols with their documentation/signature strings.\n // We use the same signature extraction pattern as symbols.ts / trace.ts.\n // Filter to symbols that have a documentation field containing '|' (the sig delimiter)\n // and whose signature contains '(' (indicating a callable).\n const rows = db.all<{\n symbol: string;\n relative_path: string;\n start_line: number;\n end_line: number;\n loc: number;\n sig: string;\n }>(\n `SELECT\n gs.symbol,\n d.relative_path,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n REPLACE(SUBSTR(gs.documentation, INSTR(gs.documentation, '|') + 1), char(10), ' ') AS sig\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE gs.documentation IS NOT NULL\n AND gs.documentation != ''\n AND INSTR(gs.documentation, '|') > 0\n AND (der.end_line - der.start_line + 1) >= ?\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n ${scopeFilter}\n ORDER BY d.relative_path, der.start_line`,\n minLoc,\n );\n\n // Group by normalized signature\n const sigGroups = new Map<string, Array<{\n symbol: string;\n shortName: string;\n file: string;\n startLine: number;\n endLine: number;\n loc: number;\n }>>();\n\n for (const row of rows) {\n if (db.isIgnored(row.relative_path)) continue;\n\n const normalized = normalizeSignature(row.sig);\n if (!normalized) continue;\n\n const entry = {\n symbol: row.symbol,\n shortName: shortenSymbol(row.symbol),\n file: row.relative_path,\n startLine: row.start_line,\n endLine: row.end_line,\n loc: row.loc,\n };\n\n const existing = sigGroups.get(normalized);\n if (existing) {\n existing.push(entry);\n } else {\n sigGroups.set(normalized, [entry]);\n }\n }\n\n // Collect groups with 2+ functions\n const results: SimilarSignatureGroup[] = [];\n\n for (const [signature, functions] of sigGroups) {\n if (functions.length < 2) continue;\n\n results.push({ signature, functions });\n }\n\n // Sort by group size descending (largest groups = most duplication),\n // then by total LOC in the group\n results.sort((a, b) => {\n const sizeDiff = b.functions.length - a.functions.length;\n if (sizeDiff !== 0) return sizeDiff;\n const locA = a.functions.reduce((sum, f) => sum + f.loc, 0);\n const locB = b.functions.reduce((sum, f) => sum + f.loc, 0);\n return locB - locA;\n });\n\n return limit ? results.slice(0, limit) : results;\n}\n\n/**\n * Normalize a signature for comparison:\n * 1. Clean markdown fences and SCIP prefixes\n * 2. Strip everything before the first '(' (removes the function name)\n * 3. Strip whitespace and lowercase\n *\n * Returns null if the signature doesn't contain a callable form.\n */\nfunction normalizeSignature(raw: string): string | null {\n if (!raw || !raw.trim()) return null;\n\n // Clean markdown and SCIP decoration (same as cleanSignature)\n let sig = raw\n .replace(/^```\\w*\\s*/, '')\n .replace(/\\s*```$/, '')\n .replace(/^\\(method\\)\\s*/, '')\n .replace(/^\\(property\\)\\s*/, '')\n .replace(/^\\(function\\)\\s*/, '')\n .replace(/^\\(class\\)\\s*/, '')\n .replace(/^\\(interface\\)\\s*/, '')\n .replace(/^\\(enum\\)\\s*/, '')\n .replace(/^\\(type alias\\)\\s*/, '')\n .replace(/^\\(const\\)\\s*/, '')\n .replace(/^\\(var\\)\\s*/, '')\n .trim();\n\n // Find the first '(' — everything from there is the parameter/return signature\n const parenIdx = sig.indexOf('(');\n if (parenIdx === -1) return null;\n\n sig = sig.slice(parenIdx);\n\n // Normalize: strip all whitespace, lowercase\n sig = sig.replace(/\\s+/g, '').toLowerCase();\n\n // Must have meaningful content after normalization\n if (sig.length < 3) return null; // e.g. \"()\" alone is too generic\n\n return sig;\n}\n"],"mappings":";;;;;AAeO,SAAS,kBACd,IACA,OAA4D,CAAC,GACpC;AACzB,QAAM,EAAE,OAAO,SAAS,GAAG,MAAM,IAAI;AAErC,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAMtE,QAAM,OAAO,GAAG;AAAA,IAQd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAcI,GAAG,kBAAkB,GAAG,CAAC;AAAA,QACzB,GAAG,eAAe,IAAI,CAAC;AAAA,QACvB,WAAW;AAAA;AAAA,IAEf;AAAA,EACF;AAGA,QAAM,YAAY,oBAAI,IAOlB;AAEJ,aAAW,OAAO,MAAM;AACtB,QAAI,GAAG,UAAU,IAAI,aAAa,EAAG;AAErC,UAAM,aAAa,mBAAmB,IAAI,GAAG;AAC7C,QAAI,CAAC,WAAY;AAEjB,UAAM,QAAQ;AAAA,MACZ,QAAQ,IAAI;AAAA,MACZ,WAAW,cAAc,IAAI,MAAM;AAAA,MACnC,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,KAAK,IAAI;AAAA,IACX;AAEA,UAAM,WAAW,UAAU,IAAI,UAAU;AACzC,QAAI,UAAU;AACZ,eAAS,KAAK,KAAK;AAAA,IACrB,OAAO;AACL,gBAAU,IAAI,YAAY,CAAC,KAAK,CAAC;AAAA,IACnC;AAAA,EACF;AAGA,QAAM,UAAmC,CAAC;AAE1C,aAAW,CAAC,WAAW,SAAS,KAAK,WAAW;AAC9C,QAAI,UAAU,SAAS,EAAG;AAE1B,YAAQ,KAAK,EAAE,WAAW,UAAU,CAAC;AAAA,EACvC;AAIA,UAAQ,KAAK,CAAC,GAAG,MAAM;AACrB,UAAM,WAAW,EAAE,UAAU,SAAS,EAAE,UAAU;AAClD,QAAI,aAAa,EAAG,QAAO;AAC3B,UAAM,OAAO,EAAE,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,CAAC;AAC1D,UAAM,OAAO,EAAE,UAAU,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,KAAK,CAAC;AAC1D,WAAO,OAAO;AAAA,EAChB,CAAC;AAED,SAAO,QAAQ,QAAQ,MAAM,GAAG,KAAK,IAAI;AAC3C;AAUA,SAAS,mBAAmB,KAA4B;AACtD,MAAI,CAAC,OAAO,CAAC,IAAI,KAAK,EAAG,QAAO;AAGhC,MAAI,MAAM,IACP,QAAQ,cAAc,EAAE,EACxB,QAAQ,WAAW,EAAE,EACrB,QAAQ,kBAAkB,EAAE,EAC5B,QAAQ,oBAAoB,EAAE,EAC9B,QAAQ,oBAAoB,EAAE,EAC9B,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,qBAAqB,EAAE,EAC/B,QAAQ,gBAAgB,EAAE,EAC1B,QAAQ,sBAAsB,EAAE,EAChC,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,eAAe,EAAE,EACzB,KAAK;AAGR,QAAM,WAAW,IAAI,QAAQ,GAAG;AAChC,MAAI,aAAa,GAAI,QAAO;AAE5B,QAAM,IAAI,MAAM,QAAQ;AAGxB,QAAM,IAAI,QAAQ,QAAQ,EAAE,EAAE,YAAY;AAG1C,MAAI,IAAI,SAAS,EAAG,QAAO;AAE3B,SAAO;AACT;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/similar.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { findFirstSymbolMatch, getCalleeRowsForSymbol } from '../query-support.js';\nimport type { SimilarSymbolResult } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find functions with similar callee fingerprints using TF-IDF weighted\n * cosine similarity.\n *\n * Plain Jaccard similarity inflates scores when functions share ubiquitous\n * infrastructure imports (db, types, shortenSymbol). TF-IDF fixes this by\n * weighting each shared callee by how rare it is:\n *\n * - A callee used by 2 functions (rare) gets high weight → strong signal\n * - A callee used by 30 functions (common) gets low weight → noise\n *\n * This means two functions sharing `sendWelcomeEmail()` (rare) score\n * much higher than two functions sharing `db.all()` (ubiquitous), even\n * though both are \"shared callees.\"\n */\nexport function similar(\n db: ScipDatabase,\n symbolPattern: string,\n opts: { minSimilarity?: number; limit?: number } = {},\n): SimilarSymbolResult[] {\n const { minSimilarity = 0.4, limit = 20 } = opts;\n\n const target = findCallees(db, symbolPattern);\n if (!target || target.callees.size === 0) return [];\n\n const candidates = getAllCalleeFingerprints(db, {\n minCallees: 3,\n excludeSymbol: target.symbol,\n });\n\n // Compute IDF weights across all fingerprints + target\n const allFingerprints = [target, ...candidates];\n const idfWeights = computeIdf(allFingerprints);\n\n const results: SimilarSymbolResult[] = [];\n\n for (const candidate of candidates) {\n if (candidate.callees.size < 3) continue;\n\n const { similarity, significantShared, trivialShared } = weightedSimilarity(\n target.callees, candidate.callees, idfWeights,\n );\n\n if (similarity < minSimilarity) continue;\n if (significantShared.length < 1) continue; // no real overlap\n\n results.push({\n symbolA: target.symbol,\n shortNameA: shortenSymbol(target.symbol),\n fileA: target.file,\n symbolB: candidate.symbol,\n shortNameB: shortenSymbol(candidate.symbol),\n fileB: candidate.file,\n similarity,\n sharedCallees: significantShared.map(shortenSymbol),\n uniqueToA: [...difference(target.callees, candidate.callees)].map(shortenSymbol),\n uniqueToB: [...difference(candidate.callees, target.callees)].map(shortenSymbol),\n });\n }\n\n results.sort((a, b) => b.similarity - a.similarity);\n return results.slice(0, limit);\n}\n\n/**\n * Find similar symbols across the entire codebase.\n * Uses TF-IDF weighted similarity to filter out infrastructure noise.\n */\nexport function similarAll(\n db: ScipDatabase,\n opts: { minSimilarity?: number; limit?: number; scope?: string; minCallees?: number } = {},\n): SimilarSymbolResult[] {\n const { minSimilarity = 0.5, limit = 20, scope, minCallees = 4 } = opts;\n\n const all = getAllCalleeFingerprints(db, { minCallees, scope });\n const idfWeights = computeIdf(all);\n\n const results: SimilarSymbolResult[] = [];\n\n for (let i = 0; i < all.length; i++) {\n for (let j = i + 1; j < all.length; j++) {\n const a = all[i]!;\n const b = all[j]!;\n\n if (a.file === b.file) continue;\n\n const { similarity, significantShared } = weightedSimilarity(\n a.callees, b.callees, idfWeights,\n );\n\n if (similarity < minSimilarity) continue;\n if (significantShared.length < 2) continue;\n\n results.push({\n symbolA: a.symbol,\n shortNameA: shortenSymbol(a.symbol),\n fileA: a.file,\n symbolB: b.symbol,\n shortNameB: shortenSymbol(b.symbol),\n fileB: b.file,\n similarity,\n sharedCallees: significantShared.map(shortenSymbol),\n uniqueToA: [...difference(a.callees, b.callees)].map(shortenSymbol),\n uniqueToB: [...difference(b.callees, a.callees)].map(shortenSymbol),\n });\n }\n\n if (results.length > limit * 5) break;\n }\n\n results.sort((a, b) => b.similarity - a.similarity);\n return results.slice(0, limit);\n}\n\n// ── TF-IDF Engine ──────────────────────────────────────────\n\n/**\n * Compute inverse document frequency for each callee.\n * IDF(callee) = log(N / df(callee)) where N is total functions\n * and df is how many functions reference that callee.\n *\n * High IDF = rare callee = strong similarity signal.\n * Low IDF = ubiquitous callee = noise.\n */\nfunction computeIdf(fingerprints: SymbolFingerprint[]): Map<string, number> {\n const n = fingerprints.length;\n if (n === 0) return new Map();\n\n // Count how many functions reference each callee\n const docFreq = new Map<string, number>();\n for (const fp of fingerprints) {\n for (const callee of fp.callees) {\n docFreq.set(callee, (docFreq.get(callee) ?? 0) + 1);\n }\n }\n\n // Compute IDF\n const idf = new Map<string, number>();\n for (const [callee, df] of docFreq) {\n idf.set(callee, Math.log(n / df));\n }\n\n return idf;\n}\n\n/**\n * Compute TF-IDF weighted cosine similarity between two callee sets.\n *\n * Each callee is a dimension. Its weight is its IDF score.\n * Cosine similarity of the weighted vectors gives a similarity\n * that ignores ubiquitous callees and emphasizes rare shared ones.\n *\n * Also returns which shared callees are \"significant\" (above-median IDF)\n * vs \"trivial\" (below-median IDF, i.e., infrastructure).\n */\nfunction weightedSimilarity(\n a: Set<string>,\n b: Set<string>,\n idf: Map<string, number>,\n): { similarity: number; significantShared: string[]; trivialShared: string[] } {\n const shared = intersection(a, b);\n if (shared.size === 0) return { similarity: 0, significantShared: [], trivialShared: [] };\n\n // Compute weighted dot product and magnitudes\n let dotProduct = 0;\n let magA = 0;\n let magB = 0;\n\n const allCallees = new Set([...a, ...b]);\n for (const callee of allCallees) {\n const weight = idf.get(callee) ?? 0;\n const inA = a.has(callee) ? weight : 0;\n const inB = b.has(callee) ? weight : 0;\n dotProduct += inA * inB;\n magA += inA * inA;\n magB += inB * inB;\n }\n\n const magnitude = Math.sqrt(magA) * Math.sqrt(magB);\n const similarity = magnitude > 0 ? dotProduct / magnitude : 0;\n\n // Split shared callees into significant (high IDF) and trivial (low IDF)\n const medianIdf = getMedianIdf(idf);\n const significantShared: string[] = [];\n const trivialShared: string[] = [];\n\n for (const callee of shared) {\n const weight = idf.get(callee) ?? 0;\n if (weight >= medianIdf) {\n significantShared.push(callee);\n } else {\n trivialShared.push(callee);\n }\n }\n\n // Sort significant callees by IDF descending (most distinctive first)\n significantShared.sort((x, y) => (idf.get(y) ?? 0) - (idf.get(x) ?? 0));\n\n return { similarity, significantShared, trivialShared };\n}\n\nfunction getMedianIdf(idf: Map<string, number>): number {\n const values = [...idf.values()].sort((a, b) => a - b);\n if (values.length === 0) return 0;\n const mid = Math.floor(values.length / 2);\n return values.length % 2 === 0\n ? (values[mid - 1]! + values[mid]!) / 2\n : values[mid]!;\n}\n\n// ── Internal helpers ───────────────────────────────────────\n\ninterface SymbolFingerprint {\n symbol: string;\n file: string;\n callees: Set<string>;\n}\n\nfunction findCallees(\n db: ScipDatabase,\n symbolPattern: string,\n): SymbolFingerprint | null {\n const target = findFirstSymbolMatch(db, symbolPattern);\n\n if (!target) return null;\n\n const calleeRows = getCalleeRowsForSymbol(db, target);\n\n return {\n symbol: target.symbol,\n file: target.relativePath,\n callees: new Set(calleeRows.map((r) => r.symbol)),\n };\n}\n\nfunction getAllCalleeFingerprints(\n db: ScipDatabase,\n opts: { minCallees: number; scope?: string; excludeSymbol?: string },\n): SymbolFingerprint[] {\n const { minCallees, scope, excludeSymbol } = opts;\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n const excludeFilter = excludeSymbol ? `AND gs.symbol != '${excludeSymbol.replace(/'/g, \"''\")}'` : '';\n\n const symbols = db.all<{\n id: number;\n symbol: string;\n document_id: number;\n start_line: number;\n end_line: number;\n relative_path: string;\n }>(\n `SELECT gs.id, gs.symbol, der.document_id, der.start_line, der.end_line, d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n ${db.symbolNoiseFor('gs')}\n AND (der.end_line - der.start_line + 1) >= 5\n ${scopeFilter}\n ${excludeFilter}\n ORDER BY d.relative_path`,\n );\n\n const fingerprints: SymbolFingerprint[] = [];\n\n for (const sym of symbols) {\n if (db.isIgnored(sym.relative_path)) continue;\n\n const calleeRows = getCalleeRowsForSymbol(db, {\n documentId: sym.document_id,\n startLine: sym.start_line,\n endLine: sym.end_line,\n symbolId: sym.id,\n });\n\n const callees = new Set(calleeRows.map((r) => r.symbol));\n if (callees.size >= minCallees) {\n fingerprints.push({ symbol: sym.symbol, file: sym.relative_path, callees });\n }\n }\n\n return fingerprints;\n}\n\nfunction intersection<T>(a: Set<T>, b: Set<T>): Set<T> {\n const result = new Set<T>();\n for (const item of a) {\n if (b.has(item)) result.add(item);\n }\n return result;\n}\n\nfunction difference<T>(a: Set<T>, b: Set<T>): Set<T> {\n const result = new Set<T>();\n for (const item of a) {\n if (!b.has(item)) result.add(item);\n }\n return result;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/queries/passthrough-candidates.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport { getCalleeRowsForSymbol, testFileExclusionSql } from '../query-support.js';\nimport type { PassthroughCandidate } from '../types.js';\nimport { shortenSymbol } from '../symbol-parser.js';\n\n/**\n * Find passthrough candidates: functions that just forward to one\n * other function.\n *\n * A function with exactly 1 callee and small LOC is likely a thin\n * wrapper that adds no value — it just passes arguments through to\n * the real implementation.\n */\nexport function passthroughCandidates(\n db: ScipDatabase,\n opts?: { scope?: string; maxLoc?: number; limit?: number },\n): PassthroughCandidate[] {\n const { scope, maxLoc = 15, limit = 30 } = opts ?? {};\n const scopeFilter = scope ? `AND d.relative_path LIKE '%${scope}%'` : '';\n\n // 1. Find all non-trivial symbols with definition ranges (>= 3 LOC)\n const symbols = db.all<{\n id: number;\n symbol: string;\n document_id: number;\n start_line: number;\n end_line: number;\n loc: number;\n relative_path: string;\n }>(\n `SELECT\n gs.id,\n gs.symbol,\n der.document_id,\n der.start_line,\n der.end_line,\n (der.end_line - der.start_line + 1) AS loc,\n d.relative_path\n FROM global_symbols gs\n JOIN defn_enclosing_ranges der ON gs.id = der.symbol_id\n JOIN documents d ON der.document_id = d.id\n WHERE 1 = 1\n ${db.pathExclusionsFor('d')}\n AND ${testFileExclusionSql('d')}\n ${db.symbolNoiseFor('gs')}\n AND (der.end_line - der.start_line + 1) >= 3\n AND (der.end_line - der.start_line + 1) <= ?\n ${scopeFilter}\n ORDER BY d.relative_path`,\n maxLoc,\n );\n\n const results: PassthroughCandidate[] = [];\n\n for (const sym of symbols) {\n if (db.isIgnored(sym.relative_path)) continue;\n\n // 2. Count callees for this symbol\n const callees = getCalleeRowsForSymbol(db, {\n documentId: sym.document_id,\n startLine: sym.start_line,\n endLine: sym.end_line,\n symbolId: sym.id,\n });\n\n // Deduplicate by symbol (same callee may appear in multiple chunks)\n const uniqueCallees = new Map<string, { symbol: string; file: string }>();\n for (const c of callees) {\n if (!uniqueCallees.has(c.symbol)) {\n uniqueCallees.set(c.symbol, { symbol: c.symbol, file: c.file });\n }\n }\n\n // 3. Passthrough = exactly 1 unique callee\n if (uniqueCallees.size !== 1) continue;\n\n const [, callee] = [...uniqueCallees.entries()][0]!;\n\n results.push({\n symbol: sym.symbol,\n shortName: shortenSymbol(sym.symbol),\n file: sym.relative_path,\n startLine: sym.start_line,\n endLine: sym.end_line,\n loc: sym.loc,\n forwardsTo: callee.symbol,\n forwardsToShort: shortenSymbol(callee.symbol),\n forwardsToFile: callee.file,\n });\n }\n\n results.sort((a, b) => a.loc - b.loc || a.file.localeCompare(b.file));\n return results.slice(0, limit);\n}\n"],"mappings":";;;;;;;;;AAaO,SAAS,sBACd,IACA,MACwB;AACxB,QAAM,EAAE,OAAO,SAAS,IAAI,QAAQ,GAAG,IAAI,QAAQ,CAAC;AACpD,QAAM,cAAc,QAAQ,8BAA8B,KAAK,OAAO;AAGtE,QAAM,UAAU,GAAG;AAAA,IASjB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYI,GAAG,kBAAkB,GAAG,CAAC;AAAA,YACrB,qBAAqB,GAAG,CAAC;AAAA,QAC7B,GAAG,eAAe,IAAI,CAAC;AAAA;AAAA;AAAA,QAGvB,WAAW;AAAA;AAAA,IAEf;AAAA,EACF;AAEA,QAAM,UAAkC,CAAC;AAEzC,aAAW,OAAO,SAAS;AACzB,QAAI,GAAG,UAAU,IAAI,aAAa,EAAG;AAGrC,UAAM,UAAU,uBAAuB,IAAI;AAAA,MACzC,YAAY,IAAI;AAAA,MAChB,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,UAAU,IAAI;AAAA,IAChB,CAAC;AAGD,UAAM,gBAAgB,oBAAI,IAA8C;AACxE,eAAW,KAAK,SAAS;AACvB,UAAI,CAAC,cAAc,IAAI,EAAE,MAAM,GAAG;AAChC,sBAAc,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,MAAM,EAAE,KAAK,CAAC;AAAA,MAChE;AAAA,IACF;AAGA,QAAI,cAAc,SAAS,EAAG;AAE9B,UAAM,CAAC,EAAE,MAAM,IAAI,CAAC,GAAG,cAAc,QAAQ,CAAC,EAAE,CAAC;AAEjD,YAAQ,KAAK;AAAA,MACX,QAAQ,IAAI;AAAA,MACZ,WAAW,cAAc,IAAI,MAAM;AAAA,MACnC,MAAM,IAAI;AAAA,MACV,WAAW,IAAI;AAAA,MACf,SAAS,IAAI;AAAA,MACb,KAAK,IAAI;AAAA,MACT,YAAY,OAAO;AAAA,MACnB,iBAAiB,cAAc,OAAO,MAAM;AAAA,MAC5C,gBAAgB,OAAO;AAAA,IACzB,CAAC;AAAA,EACH;AAEA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACpE,SAAO,QAAQ,MAAM,GAAG,KAAK;AAC/B;","names":[]}
|