scip-query 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/IMPROVEMENTS.md +143 -0
- package/PLAN.md +320 -0
- package/README.md +1213 -0
- package/dist/chunk-2QZ23IBN.js +55 -0
- package/dist/chunk-2QZ23IBN.js.map +1 -0
- package/dist/chunk-36OMT7ZJ.js +144 -0
- package/dist/chunk-36OMT7ZJ.js.map +1 -0
- package/dist/chunk-3E2X7RIE.js +101 -0
- package/dist/chunk-3E2X7RIE.js.map +1 -0
- package/dist/chunk-3UOUTZQT.js +45 -0
- package/dist/chunk-3UOUTZQT.js.map +1 -0
- package/dist/chunk-3ZZJVBIO.js +88 -0
- package/dist/chunk-3ZZJVBIO.js.map +1 -0
- package/dist/chunk-4TYLS5XX.js +10 -0
- package/dist/chunk-4TYLS5XX.js.map +1 -0
- package/dist/chunk-5FGUEU7N.js +101 -0
- package/dist/chunk-5FGUEU7N.js.map +1 -0
- package/dist/chunk-5WTJAXY2.js +61 -0
- package/dist/chunk-5WTJAXY2.js.map +1 -0
- package/dist/chunk-6NBLIDF4.js +24 -0
- package/dist/chunk-6NBLIDF4.js.map +1 -0
- package/dist/chunk-6SXADWLW.js +43 -0
- package/dist/chunk-6SXADWLW.js.map +1 -0
- package/dist/chunk-6VJ6Q7IE.js +65 -0
- package/dist/chunk-6VJ6Q7IE.js.map +1 -0
- package/dist/chunk-7OZPA5OO.js +258 -0
- package/dist/chunk-7OZPA5OO.js.map +1 -0
- package/dist/chunk-BEPIEVLR.js +76 -0
- package/dist/chunk-BEPIEVLR.js.map +1 -0
- package/dist/chunk-BFSCMC22.js +42 -0
- package/dist/chunk-BFSCMC22.js.map +1 -0
- package/dist/chunk-BP2ATLK2.js +110 -0
- package/dist/chunk-BP2ATLK2.js.map +1 -0
- package/dist/chunk-CM454WL3.js +114 -0
- package/dist/chunk-CM454WL3.js.map +1 -0
- package/dist/chunk-DCKMSTJ4.js +74 -0
- package/dist/chunk-DCKMSTJ4.js.map +1 -0
- package/dist/chunk-DEZKCZXD.js +40 -0
- package/dist/chunk-DEZKCZXD.js.map +1 -0
- package/dist/chunk-DVWGWHFW.js +99 -0
- package/dist/chunk-DVWGWHFW.js.map +1 -0
- package/dist/chunk-EMDQWNYR.js +102 -0
- package/dist/chunk-EMDQWNYR.js.map +1 -0
- package/dist/chunk-FFSWWE5O.js +33 -0
- package/dist/chunk-FFSWWE5O.js.map +1 -0
- package/dist/chunk-FGXRVW7G.js +73 -0
- package/dist/chunk-FGXRVW7G.js.map +1 -0
- package/dist/chunk-FUHJCHS4.js +158 -0
- package/dist/chunk-FUHJCHS4.js.map +1 -0
- package/dist/chunk-GJFURBEW.js +64 -0
- package/dist/chunk-GJFURBEW.js.map +1 -0
- package/dist/chunk-GTILYBH6.js +102 -0
- package/dist/chunk-GTILYBH6.js.map +1 -0
- package/dist/chunk-JJP7KQND.js +1 -0
- package/dist/chunk-JJP7KQND.js.map +1 -0
- package/dist/chunk-JKP5GH6T.js +213 -0
- package/dist/chunk-JKP5GH6T.js.map +1 -0
- package/dist/chunk-KCBMVQL5.js +38 -0
- package/dist/chunk-KCBMVQL5.js.map +1 -0
- package/dist/chunk-KVSW5KYP.js +78 -0
- package/dist/chunk-KVSW5KYP.js.map +1 -0
- package/dist/chunk-LAWMH22O.js +172 -0
- package/dist/chunk-LAWMH22O.js.map +1 -0
- package/dist/chunk-LB7OS35Q.js +72 -0
- package/dist/chunk-LB7OS35Q.js.map +1 -0
- package/dist/chunk-LUSIFBXO.js +57 -0
- package/dist/chunk-LUSIFBXO.js.map +1 -0
- package/dist/chunk-MBVNHJVN.js +44 -0
- package/dist/chunk-MBVNHJVN.js.map +1 -0
- package/dist/chunk-MGNMHKX3.js +15 -0
- package/dist/chunk-MGNMHKX3.js.map +1 -0
- package/dist/chunk-N5KEREIA.js +41 -0
- package/dist/chunk-N5KEREIA.js.map +1 -0
- package/dist/chunk-NDSQYIWT.js +71 -0
- package/dist/chunk-NDSQYIWT.js.map +1 -0
- package/dist/chunk-NUZ4OMU3.js +28 -0
- package/dist/chunk-NUZ4OMU3.js.map +1 -0
- package/dist/chunk-QOV2R2WT.js +170 -0
- package/dist/chunk-QOV2R2WT.js.map +1 -0
- package/dist/chunk-SEFSL2GF.js +78 -0
- package/dist/chunk-SEFSL2GF.js.map +1 -0
- package/dist/chunk-T6ARFSBZ.js +103 -0
- package/dist/chunk-T6ARFSBZ.js.map +1 -0
- package/dist/chunk-TBP6BICL.js +46 -0
- package/dist/chunk-TBP6BICL.js.map +1 -0
- package/dist/chunk-TDNNOR6D.js +97 -0
- package/dist/chunk-TDNNOR6D.js.map +1 -0
- package/dist/chunk-TSPZOMHC.js +195 -0
- package/dist/chunk-TSPZOMHC.js.map +1 -0
- package/dist/chunk-UNTPVD36.js +55 -0
- package/dist/chunk-UNTPVD36.js.map +1 -0
- package/dist/chunk-VRUJH4BO.js +88 -0
- package/dist/chunk-VRUJH4BO.js.map +1 -0
- package/dist/chunk-VZ7AMAFL.js +76 -0
- package/dist/chunk-VZ7AMAFL.js.map +1 -0
- package/dist/chunk-XFXDXEUN.js +24 -0
- package/dist/chunk-XFXDXEUN.js.map +1 -0
- package/dist/chunk-YZAA4LYG.js +169 -0
- package/dist/chunk-YZAA4LYG.js.map +1 -0
- package/dist/chunk-Z73NYSBZ.js +92 -0
- package/dist/chunk-Z73NYSBZ.js.map +1 -0
- package/dist/chunk-ZJRYBOEE.js +125 -0
- package/dist/chunk-ZJRYBOEE.js.map +1 -0
- package/dist/cli.js +5798 -0
- package/dist/cli.js.map +1 -0
- package/dist/db-BxaevAyc.d.ts +683 -0
- package/dist/index.d.ts +254 -0
- package/dist/index.js +1271 -0
- package/dist/index.js.map +1 -0
- package/dist/postinstall.js +167 -0
- package/dist/postinstall.js.map +1 -0
- package/dist/queries/affected.d.ts +14 -0
- package/dist/queries/affected.js +9 -0
- package/dist/queries/affected.js.map +1 -0
- package/dist/queries/bottlenecks.d.ts +18 -0
- package/dist/queries/bottlenecks.js +8 -0
- package/dist/queries/bottlenecks.js.map +1 -0
- package/dist/queries/by-kind.d.ts +20 -0
- package/dist/queries/by-kind.js +10 -0
- package/dist/queries/by-kind.js.map +1 -0
- package/dist/queries/call-graph.d.ts +13 -0
- package/dist/queries/call-graph.js +9 -0
- package/dist/queries/call-graph.js.map +1 -0
- package/dist/queries/change-surface.d.ts +10 -0
- package/dist/queries/change-surface.js +9 -0
- package/dist/queries/change-surface.js.map +1 -0
- package/dist/queries/clean-signature.d.ts +9 -0
- package/dist/queries/clean-signature.js +7 -0
- package/dist/queries/clean-signature.js.map +1 -0
- package/dist/queries/code.d.ts +17 -0
- package/dist/queries/code.js +9 -0
- package/dist/queries/code.js.map +1 -0
- package/dist/queries/complexity-hotspots.d.ts +19 -0
- package/dist/queries/complexity-hotspots.js +9 -0
- package/dist/queries/complexity-hotspots.js.map +1 -0
- package/dist/queries/complexity.d.ts +13 -0
- package/dist/queries/complexity.js +9 -0
- package/dist/queries/complexity.js.map +1 -0
- package/dist/queries/convergence.d.ts +11 -0
- package/dist/queries/convergence.js +9 -0
- package/dist/queries/convergence.js.map +1 -0
- package/dist/queries/coupling.d.ts +17 -0
- package/dist/queries/coupling.js +9 -0
- package/dist/queries/coupling.js.map +1 -0
- package/dist/queries/cycles.d.ts +16 -0
- package/dist/queries/cycles.js +8 -0
- package/dist/queries/cycles.js.map +1 -0
- package/dist/queries/dataflow.d.ts +19 -0
- package/dist/queries/dataflow.js +9 -0
- package/dist/queries/dataflow.js.map +1 -0
- package/dist/queries/dead.d.ts +10 -0
- package/dist/queries/dead.js +9 -0
- package/dist/queries/dead.js.map +1 -0
- package/dist/queries/deep-chains.d.ts +16 -0
- package/dist/queries/deep-chains.js +8 -0
- package/dist/queries/deep-chains.js.map +1 -0
- package/dist/queries/deps.d.ts +9 -0
- package/dist/queries/deps.js +9 -0
- package/dist/queries/deps.js.map +1 -0
- package/dist/queries/diff-impact.d.ts +13 -0
- package/dist/queries/diff-impact.js +9 -0
- package/dist/queries/diff-impact.js.map +1 -0
- package/dist/queries/doc-coverage.d.ts +14 -0
- package/dist/queries/doc-coverage.js +8 -0
- package/dist/queries/doc-coverage.js.map +1 -0
- package/dist/queries/drift.d.ts +25 -0
- package/dist/queries/drift.js +8 -0
- package/dist/queries/drift.js.map +1 -0
- package/dist/queries/extract-candidates.d.ts +25 -0
- package/dist/queries/extract-candidates.js +9 -0
- package/dist/queries/extract-candidates.js.map +1 -0
- package/dist/queries/fan.d.ts +29 -0
- package/dist/queries/fan.js +14 -0
- package/dist/queries/fan.js.map +1 -0
- package/dist/queries/files.d.ts +6 -0
- package/dist/queries/files.js +7 -0
- package/dist/queries/files.js.map +1 -0
- package/dist/queries/health.d.ts +18 -0
- package/dist/queries/health.js +21 -0
- package/dist/queries/health.js.map +1 -0
- package/dist/queries/hierarchy.d.ts +13 -0
- package/dist/queries/hierarchy.js +8 -0
- package/dist/queries/hierarchy.js.map +1 -0
- package/dist/queries/hotspots.d.ts +13 -0
- package/dist/queries/hotspots.js +8 -0
- package/dist/queries/hotspots.js.map +1 -0
- package/dist/queries/imports.d.ts +19 -0
- package/dist/queries/imports.js +12 -0
- package/dist/queries/imports.js.map +1 -0
- package/dist/queries/index.d.ts +47 -0
- package/dist/queries/index.js +207 -0
- package/dist/queries/index.js.map +1 -0
- package/dist/queries/isolated.d.ts +14 -0
- package/dist/queries/isolated.js +9 -0
- package/dist/queries/isolated.js.map +1 -0
- package/dist/queries/members.d.ts +10 -0
- package/dist/queries/members.js +8 -0
- package/dist/queries/members.js.map +1 -0
- package/dist/queries/methods.d.ts +6 -0
- package/dist/queries/methods.js +8 -0
- package/dist/queries/methods.js.map +1 -0
- package/dist/queries/outline.d.ts +10 -0
- package/dist/queries/outline.js +8 -0
- package/dist/queries/outline.js.map +1 -0
- package/dist/queries/passthrough-candidates.d.ts +18 -0
- package/dist/queries/passthrough-candidates.js +9 -0
- package/dist/queries/passthrough-candidates.js.map +1 -0
- package/dist/queries/redundant-reexports.d.ts +22 -0
- package/dist/queries/redundant-reexports.js +8 -0
- package/dist/queries/redundant-reexports.js.map +1 -0
- package/dist/queries/refs.d.ts +6 -0
- package/dist/queries/refs.js +7 -0
- package/dist/queries/refs.js.map +1 -0
- package/dist/queries/similar-chains.d.ts +29 -0
- package/dist/queries/similar-chains.js +8 -0
- package/dist/queries/similar-chains.js.map +1 -0
- package/dist/queries/similar-files.d.ts +19 -0
- package/dist/queries/similar-files.js +8 -0
- package/dist/queries/similar-files.js.map +1 -0
- package/dist/queries/similar-signatures.d.ts +21 -0
- package/dist/queries/similar-signatures.js +8 -0
- package/dist/queries/similar-signatures.js.map +1 -0
- package/dist/queries/similar.d.ts +34 -0
- package/dist/queries/similar.js +11 -0
- package/dist/queries/similar.js.map +1 -0
- package/dist/queries/slice.d.ts +21 -0
- package/dist/queries/slice.js +9 -0
- package/dist/queries/slice.js.map +1 -0
- package/dist/queries/stale-abstractions.d.ts +18 -0
- package/dist/queries/stale-abstractions.js +9 -0
- package/dist/queries/stale-abstractions.js.map +1 -0
- package/dist/queries/stats.d.ts +6 -0
- package/dist/queries/stats.js +7 -0
- package/dist/queries/stats.js.map +1 -0
- package/dist/queries/surface.d.ts +7 -0
- package/dist/queries/surface.js +8 -0
- package/dist/queries/surface.js.map +1 -0
- package/dist/queries/symbols.d.ts +6 -0
- package/dist/queries/symbols.js +9 -0
- package/dist/queries/symbols.js.map +1 -0
- package/dist/queries/system.d.ts +7 -0
- package/dist/queries/system.js +9 -0
- package/dist/queries/system.js.map +1 -0
- package/dist/queries/test-coverage.d.ts +22 -0
- package/dist/queries/test-coverage.js +11 -0
- package/dist/queries/test-coverage.js.map +1 -0
- package/dist/queries/trace.d.ts +6 -0
- package/dist/queries/trace.js +8 -0
- package/dist/queries/trace.js.map +1 -0
- package/dist/queries/wrapper-candidates.d.ts +17 -0
- package/dist/queries/wrapper-candidates.js +9 -0
- package/dist/queries/wrapper-candidates.js.map +1 -0
- package/dist/reindex-worker.js +368 -0
- package/dist/reindex-worker.js.map +1 -0
- package/docs/AGENT_GUIDE.md +359 -0
- package/package.json +70 -0
- package/reports/debloat/2026-04-10-scip-query-self-audit.md +161 -0
- package/skills/concrete-plan/SKILL.md +318 -0
- package/skills/scip-debloat/SKILL.md +413 -0
- package/skills/scip-explore/SKILL.md +235 -0
- package/skills/scip-verify/SKILL.md +323 -0
- package/src/cli.ts +1480 -0
- package/src/config.ts +117 -0
- package/src/db.ts +127 -0
- package/src/gitignore-filter.ts +143 -0
- package/src/index.ts +11 -0
- package/src/postinstall.ts +8 -0
- package/src/queries/affected.ts +86 -0
- package/src/queries/bottlenecks.ts +67 -0
- package/src/queries/by-kind.ts +204 -0
- package/src/queries/call-graph.ts +66 -0
- package/src/queries/change-surface.ts +110 -0
- package/src/queries/clean-signature.ts +22 -0
- package/src/queries/code.ts +101 -0
- package/src/queries/complexity-hotspots.ts +119 -0
- package/src/queries/complexity.ts +152 -0
- package/src/queries/convergence.ts +82 -0
- package/src/queries/coupling.ts +99 -0
- package/src/queries/cycles.ts +78 -0
- package/src/queries/dataflow.ts +128 -0
- package/src/queries/dead.ts +122 -0
- package/src/queries/deep-chains.ts +59 -0
- package/src/queries/deps.ts +46 -0
- package/src/queries/diff-impact.ts +204 -0
- package/src/queries/doc-coverage.ts +86 -0
- package/src/queries/drift.ts +224 -0
- package/src/queries/extract-candidates.ts +167 -0
- package/src/queries/fan.ts +148 -0
- package/src/queries/files.ts +16 -0
- package/src/queries/health.ts +324 -0
- package/src/queries/hierarchy.ts +49 -0
- package/src/queries/hotspots.ts +53 -0
- package/src/queries/imports.ts +95 -0
- package/src/queries/index.ts +45 -0
- package/src/queries/isolated.ts +67 -0
- package/src/queries/members.ts +54 -0
- package/src/queries/methods.ts +27 -0
- package/src/queries/outline.ts +52 -0
- package/src/queries/passthrough-candidates.ts +94 -0
- package/src/queries/redundant-reexports.ts +170 -0
- package/src/queries/refs.ts +27 -0
- package/src/queries/similar-chains.ts +314 -0
- package/src/queries/similar-files.ts +140 -0
- package/src/queries/similar-signatures.ts +151 -0
- package/src/queries/similar.ts +305 -0
- package/src/queries/slice.ts +154 -0
- package/src/queries/stale-abstractions.ts +82 -0
- package/src/queries/stats.ts +22 -0
- package/src/queries/surface.ts +34 -0
- package/src/queries/symbols.ts +39 -0
- package/src/queries/system.ts +86 -0
- package/src/queries/test-coverage.ts +106 -0
- package/src/queries/trace.ts +55 -0
- package/src/queries/wrapper-candidates.ts +112 -0
- package/src/query-support.ts +226 -0
- package/src/reindex/detect.ts +58 -0
- package/src/reindex/index.ts +153 -0
- package/src/reindex/indexers.ts +220 -0
- package/src/reindex/install.ts +125 -0
- package/src/reindex-worker.ts +35 -0
- package/src/setup.ts +202 -0
- package/src/symbol-parser.ts +278 -0
- package/src/types.ts +654 -0
- package/src/watch.ts +274 -0
- package/tests/gitignore-filter.test.ts +48 -0
- package/tests/queries.test.ts +300 -0
- package/tests/symbol-parser.test.ts +157 -0
- package/tsconfig.json +20 -0
- package/tsup.config.ts +40 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1 @@
|
|
|
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 = 0\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 = 0\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":[]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findFirstSymbolMatch
|
|
3
|
+
} from "./chunk-FUHJCHS4.js";
|
|
4
|
+
import {
|
|
5
|
+
shortenSymbol
|
|
6
|
+
} from "./chunk-QOV2R2WT.js";
|
|
7
|
+
|
|
8
|
+
// src/queries/code.ts
|
|
9
|
+
import { readFileSync } from "fs";
|
|
10
|
+
import { join } from "path";
|
|
11
|
+
function code(db, symbolPattern, opts = {}) {
|
|
12
|
+
const { context = 0 } = opts;
|
|
13
|
+
const fileLineMatch = symbolPattern.match(/^(.+\.\w+):(\d+)-(\d+)$/);
|
|
14
|
+
if (fileLineMatch) {
|
|
15
|
+
return readFileRange(db, fileLineMatch[1], parseInt(fileLineMatch[2], 10), parseInt(fileLineMatch[3], 10), context);
|
|
16
|
+
}
|
|
17
|
+
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
18
|
+
if (!match) return null;
|
|
19
|
+
const doc = db.get(
|
|
20
|
+
`SELECT language FROM documents WHERE relative_path = ?`,
|
|
21
|
+
match.relativePath
|
|
22
|
+
);
|
|
23
|
+
const filePath = join(db.config.projectRoot, match.relativePath);
|
|
24
|
+
let fileContent;
|
|
25
|
+
try {
|
|
26
|
+
fileContent = readFileSync(filePath, "utf-8");
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const lines = fileContent.split("\n");
|
|
31
|
+
const startLine = Math.max(0, match.startLine - context);
|
|
32
|
+
const endLine = Math.min(lines.length - 1, match.endLine + context);
|
|
33
|
+
const source = lines.slice(startLine, endLine + 1).join("\n");
|
|
34
|
+
return {
|
|
35
|
+
symbol: match.symbol,
|
|
36
|
+
shortName: shortenSymbol(match.symbol),
|
|
37
|
+
relativePath: match.relativePath,
|
|
38
|
+
startLine: startLine + 1,
|
|
39
|
+
// 1-indexed for display
|
|
40
|
+
endLine: endLine + 1,
|
|
41
|
+
language: doc?.language ?? null,
|
|
42
|
+
source
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
function readFileRange(db, filePath, startLine, endLine, context) {
|
|
46
|
+
const doc = db.get(
|
|
47
|
+
`SELECT relative_path, language FROM documents WHERE relative_path LIKE ?`,
|
|
48
|
+
`%${filePath}%`
|
|
49
|
+
);
|
|
50
|
+
if (!doc) return null;
|
|
51
|
+
const fullPath = join(db.config.projectRoot, doc.relative_path);
|
|
52
|
+
let fileContent;
|
|
53
|
+
try {
|
|
54
|
+
fileContent = readFileSync(fullPath, "utf-8");
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const lines = fileContent.split("\n");
|
|
59
|
+
const start = Math.max(0, startLine - 1 - context);
|
|
60
|
+
const end = Math.min(lines.length - 1, endLine - 1 + context);
|
|
61
|
+
const source = lines.slice(start, end + 1).join("\n");
|
|
62
|
+
return {
|
|
63
|
+
symbol: `${doc.relative_path}:${startLine}-${endLine}`,
|
|
64
|
+
shortName: `${doc.relative_path}:${startLine}-${endLine}`,
|
|
65
|
+
relativePath: doc.relative_path,
|
|
66
|
+
startLine: start + 1,
|
|
67
|
+
endLine: end + 1,
|
|
68
|
+
language: doc.language,
|
|
69
|
+
source
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
code
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=chunk-VZ7AMAFL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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":[]}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// src/queries/stats.ts
|
|
2
|
+
function stats(db) {
|
|
3
|
+
const documents = db.get("SELECT COUNT(*) as c FROM documents").c;
|
|
4
|
+
const symbols = db.get("SELECT COUNT(*) as c FROM global_symbols").c;
|
|
5
|
+
const definitions = db.get(
|
|
6
|
+
"SELECT COUNT(*) as c FROM mentions WHERE role = 1"
|
|
7
|
+
).c;
|
|
8
|
+
const references = db.get(
|
|
9
|
+
"SELECT COUNT(*) as c FROM mentions WHERE role = 0"
|
|
10
|
+
).c;
|
|
11
|
+
return {
|
|
12
|
+
documents,
|
|
13
|
+
symbols,
|
|
14
|
+
definitions,
|
|
15
|
+
references,
|
|
16
|
+
indexSizeBytes: db.sizeBytes(),
|
|
17
|
+
lastBuilt: db.lastModified()
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
stats
|
|
23
|
+
};
|
|
24
|
+
//# sourceMappingURL=chunk-XFXDXEUN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/queries/stats.ts"],"sourcesContent":["import type { ScipDatabase } from '../db.js';\nimport type { StatsResult } from '../types.js';\n\nexport function stats(db: ScipDatabase): StatsResult {\n const documents = db.get<{ c: number }>('SELECT COUNT(*) as c FROM documents')!.c;\n const symbols = db.get<{ c: number }>('SELECT COUNT(*) as c FROM global_symbols')!.c;\n const definitions = db.get<{ c: number }>(\n 'SELECT COUNT(*) as c FROM mentions WHERE role = 1',\n )!.c;\n const references = db.get<{ c: number }>(\n 'SELECT COUNT(*) as c FROM mentions WHERE role = 0',\n )!.c;\n\n return {\n documents,\n symbols,\n definitions,\n references,\n indexSizeBytes: db.sizeBytes(),\n lastBuilt: db.lastModified(),\n };\n}\n"],"mappings":";AAGO,SAAS,MAAM,IAA+B;AACnD,QAAM,YAAY,GAAG,IAAmB,qCAAqC,EAAG;AAChF,QAAM,UAAU,GAAG,IAAmB,0CAA0C,EAAG;AACnF,QAAM,cAAc,GAAG;AAAA,IACrB;AAAA,EACF,EAAG;AACH,QAAM,aAAa,GAAG;AAAA,IACpB;AAAA,EACF,EAAG;AAEH,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB,GAAG,UAAU;AAAA,IAC7B,WAAW,GAAG,aAAa;AAAA,EAC7B;AACF;","names":[]}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TEST_FILE_PATTERNS,
|
|
3
|
+
testFileMatchSql
|
|
4
|
+
} from "./chunk-FUHJCHS4.js";
|
|
5
|
+
import {
|
|
6
|
+
shortenSymbol
|
|
7
|
+
} from "./chunk-QOV2R2WT.js";
|
|
8
|
+
|
|
9
|
+
// src/queries/diff-impact.ts
|
|
10
|
+
import { execFileSync } from "child_process";
|
|
11
|
+
function diffImpact(db, opts = {}) {
|
|
12
|
+
const { base = "HEAD" } = opts;
|
|
13
|
+
let changedFileLines;
|
|
14
|
+
try {
|
|
15
|
+
const stdout = execFileSync("git", ["diff", "--name-only", base], {
|
|
16
|
+
encoding: "utf-8",
|
|
17
|
+
cwd: db.config.projectRoot,
|
|
18
|
+
timeout: 1e4
|
|
19
|
+
});
|
|
20
|
+
changedFileLines = stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
21
|
+
} catch {
|
|
22
|
+
return {
|
|
23
|
+
changedFiles: [],
|
|
24
|
+
changedSymbols: [],
|
|
25
|
+
affectedConsumers: [],
|
|
26
|
+
uncoveredSymbols: [],
|
|
27
|
+
summary: {
|
|
28
|
+
totalChangedFiles: 0,
|
|
29
|
+
totalChangedSymbols: 0,
|
|
30
|
+
totalAffectedFiles: 0,
|
|
31
|
+
testCoveragePercent: 0
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (changedFileLines.length === 0) {
|
|
36
|
+
return {
|
|
37
|
+
changedFiles: [],
|
|
38
|
+
changedSymbols: [],
|
|
39
|
+
affectedConsumers: [],
|
|
40
|
+
uncoveredSymbols: [],
|
|
41
|
+
summary: {
|
|
42
|
+
totalChangedFiles: 0,
|
|
43
|
+
totalChangedSymbols: 0,
|
|
44
|
+
totalAffectedFiles: 0,
|
|
45
|
+
testCoveragePercent: 0
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const changedFiles = [];
|
|
50
|
+
const changedDocIds = [];
|
|
51
|
+
for (const file of changedFileLines) {
|
|
52
|
+
const doc = db.get(
|
|
53
|
+
`SELECT id, relative_path FROM documents
|
|
54
|
+
WHERE relative_path LIKE ?
|
|
55
|
+
LIMIT 1`,
|
|
56
|
+
`%${file}`
|
|
57
|
+
);
|
|
58
|
+
if (doc && !db.isIgnored(doc.relative_path)) {
|
|
59
|
+
changedFiles.push(doc.relative_path);
|
|
60
|
+
changedDocIds.push(doc.id);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (changedDocIds.length === 0) {
|
|
64
|
+
return {
|
|
65
|
+
changedFiles: changedFileLines,
|
|
66
|
+
changedSymbols: [],
|
|
67
|
+
affectedConsumers: [],
|
|
68
|
+
uncoveredSymbols: [],
|
|
69
|
+
summary: {
|
|
70
|
+
totalChangedFiles: changedFileLines.length,
|
|
71
|
+
totalChangedSymbols: 0,
|
|
72
|
+
totalAffectedFiles: 0,
|
|
73
|
+
testCoveragePercent: 0
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const docPlaceholders = changedDocIds.map(() => "?").join(",");
|
|
78
|
+
const syms = db.all(
|
|
79
|
+
`SELECT DISTINCT gs.id AS symbol_id, gs.symbol, d.relative_path
|
|
80
|
+
FROM defn_enclosing_ranges der
|
|
81
|
+
JOIN global_symbols gs ON der.symbol_id = gs.id
|
|
82
|
+
JOIN documents d ON der.document_id = d.id
|
|
83
|
+
WHERE der.document_id IN (${docPlaceholders})
|
|
84
|
+
${db.symbolNoiseFor("gs")}
|
|
85
|
+
ORDER BY d.relative_path`,
|
|
86
|
+
...changedDocIds
|
|
87
|
+
);
|
|
88
|
+
const testPatternSql = testFileMatchSql("ref_d", TEST_FILE_PATTERNS);
|
|
89
|
+
const changedSymbols = [];
|
|
90
|
+
const consumerMap = /* @__PURE__ */ new Map();
|
|
91
|
+
const uncoveredSymbols = [];
|
|
92
|
+
let coveredCount = 0;
|
|
93
|
+
for (const sym of syms) {
|
|
94
|
+
const fanInRow = db.get(
|
|
95
|
+
`SELECT COUNT(DISTINCT c.document_id) AS fan_in
|
|
96
|
+
FROM mentions m
|
|
97
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
98
|
+
WHERE m.symbol_id = ?
|
|
99
|
+
AND m.role = 0`,
|
|
100
|
+
sym.symbol_id
|
|
101
|
+
);
|
|
102
|
+
const fanIn = fanInRow?.fan_in ?? 0;
|
|
103
|
+
const shortName = shortenSymbol(sym.symbol);
|
|
104
|
+
changedSymbols.push({
|
|
105
|
+
symbol: sym.symbol,
|
|
106
|
+
shortName,
|
|
107
|
+
file: sym.relative_path,
|
|
108
|
+
fanIn
|
|
109
|
+
});
|
|
110
|
+
const consumers = db.all(
|
|
111
|
+
`SELECT DISTINCT ref_d.relative_path
|
|
112
|
+
FROM mentions m
|
|
113
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
114
|
+
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
115
|
+
WHERE m.symbol_id = ?
|
|
116
|
+
AND m.role = 0
|
|
117
|
+
AND ref_d.relative_path NOT IN (${changedFiles.map(() => "?").join(",")})
|
|
118
|
+
${db.pathExclusionsFor("ref_d")}`,
|
|
119
|
+
sym.symbol_id,
|
|
120
|
+
...changedFiles
|
|
121
|
+
);
|
|
122
|
+
for (const consumer of consumers) {
|
|
123
|
+
if (db.isIgnored(consumer.relative_path)) continue;
|
|
124
|
+
if (!consumerMap.has(consumer.relative_path)) {
|
|
125
|
+
consumerMap.set(consumer.relative_path, /* @__PURE__ */ new Set());
|
|
126
|
+
}
|
|
127
|
+
consumerMap.get(consumer.relative_path).add(shortName);
|
|
128
|
+
}
|
|
129
|
+
const hasTest = db.get(
|
|
130
|
+
`SELECT COUNT(*) AS c
|
|
131
|
+
FROM mentions m
|
|
132
|
+
JOIN chunks c ON m.chunk_id = c.id
|
|
133
|
+
JOIN documents ref_d ON c.document_id = ref_d.id
|
|
134
|
+
WHERE m.symbol_id = ?
|
|
135
|
+
AND m.role = 0
|
|
136
|
+
AND (${testPatternSql})`,
|
|
137
|
+
sym.symbol_id
|
|
138
|
+
);
|
|
139
|
+
if (hasTest && hasTest.c > 0) {
|
|
140
|
+
coveredCount++;
|
|
141
|
+
} else {
|
|
142
|
+
uncoveredSymbols.push({
|
|
143
|
+
symbol: sym.symbol,
|
|
144
|
+
shortName,
|
|
145
|
+
file: sym.relative_path
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const affectedConsumers = [...consumerMap.entries()].map(([file, symbols]) => ({ file, consumedSymbols: symbols.size })).sort((a, b) => b.consumedSymbols - a.consumedSymbols);
|
|
150
|
+
const totalSymbols = changedSymbols.length;
|
|
151
|
+
const testCoveragePercent = totalSymbols > 0 ? Math.round(coveredCount / totalSymbols * 100) : 0;
|
|
152
|
+
return {
|
|
153
|
+
changedFiles,
|
|
154
|
+
changedSymbols,
|
|
155
|
+
affectedConsumers,
|
|
156
|
+
uncoveredSymbols,
|
|
157
|
+
summary: {
|
|
158
|
+
totalChangedFiles: changedFiles.length,
|
|
159
|
+
totalChangedSymbols: totalSymbols,
|
|
160
|
+
totalAffectedFiles: affectedConsumers.length,
|
|
161
|
+
testCoveragePercent
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export {
|
|
167
|
+
diffImpact
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=chunk-YZAA4LYG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 = 0`,\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 = 0\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 = 0\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":[]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildFileDepGraph
|
|
3
|
+
} from "./chunk-FUHJCHS4.js";
|
|
4
|
+
|
|
5
|
+
// src/queries/similar-files.ts
|
|
6
|
+
function similarFiles(db, opts = {}) {
|
|
7
|
+
const { minSimilarity = 0.5, limit = 20, scope, minDeps = 3, filePattern } = opts;
|
|
8
|
+
const profiles = buildFileProfiles(db, { scope, minDeps });
|
|
9
|
+
const results = [];
|
|
10
|
+
if (filePattern) {
|
|
11
|
+
const target = profiles.find((p) => p.file.includes(filePattern));
|
|
12
|
+
if (!target) return [];
|
|
13
|
+
for (const candidate of profiles) {
|
|
14
|
+
if (candidate.file === target.file) continue;
|
|
15
|
+
const result = compareProfiles(target, candidate, minSimilarity);
|
|
16
|
+
if (result) results.push(result);
|
|
17
|
+
}
|
|
18
|
+
} else {
|
|
19
|
+
for (let i = 0; i < profiles.length; i++) {
|
|
20
|
+
for (let j = i + 1; j < profiles.length; j++) {
|
|
21
|
+
const result = compareProfiles(profiles[i], profiles[j], minSimilarity);
|
|
22
|
+
if (result) results.push(result);
|
|
23
|
+
}
|
|
24
|
+
if (results.length > limit * 5) break;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
results.sort((a, b) => b.similarity - a.similarity);
|
|
28
|
+
return results.slice(0, limit);
|
|
29
|
+
}
|
|
30
|
+
function buildFileProfiles(db, opts) {
|
|
31
|
+
const { scope, minDeps } = opts;
|
|
32
|
+
const depMap = buildFileDepGraph(db, scope);
|
|
33
|
+
const universalDeps = findUniversalDependencies(depMap);
|
|
34
|
+
const profiles = [];
|
|
35
|
+
for (const [file, deps] of depMap) {
|
|
36
|
+
if (deps.size >= minDeps) {
|
|
37
|
+
profiles.push({
|
|
38
|
+
file,
|
|
39
|
+
deps: new Set([...deps].filter((dep) => !universalDeps.has(dep)))
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return profiles;
|
|
44
|
+
}
|
|
45
|
+
function findUniversalDependencies(depMap) {
|
|
46
|
+
const universalDeps = /* @__PURE__ */ new Set();
|
|
47
|
+
const fileCount = depMap.size;
|
|
48
|
+
if (fileCount === 0) return universalDeps;
|
|
49
|
+
const depCounts = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const deps of depMap.values()) {
|
|
51
|
+
for (const dep of deps) {
|
|
52
|
+
depCounts.set(dep, (depCounts.get(dep) ?? 0) + 1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
for (const [dep, count] of depCounts) {
|
|
56
|
+
if (count / fileCount > 0.5) {
|
|
57
|
+
universalDeps.add(dep);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return universalDeps;
|
|
61
|
+
}
|
|
62
|
+
function compareProfiles(a, b, minSimilarity) {
|
|
63
|
+
const shared = /* @__PURE__ */ new Set();
|
|
64
|
+
for (const dep of a.deps) {
|
|
65
|
+
if (b.deps.has(dep)) shared.add(dep);
|
|
66
|
+
}
|
|
67
|
+
if (shared.size === 0) return null;
|
|
68
|
+
const unionSize = (/* @__PURE__ */ new Set([...a.deps, ...b.deps])).size;
|
|
69
|
+
const similarity = shared.size / unionSize;
|
|
70
|
+
if (similarity < minSimilarity) return null;
|
|
71
|
+
const uniqueA = [];
|
|
72
|
+
for (const dep of a.deps) {
|
|
73
|
+
if (!b.deps.has(dep)) uniqueA.push(dep);
|
|
74
|
+
}
|
|
75
|
+
const uniqueB = [];
|
|
76
|
+
for (const dep of b.deps) {
|
|
77
|
+
if (!a.deps.has(dep)) uniqueB.push(dep);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
fileA: a.file,
|
|
81
|
+
fileB: b.file,
|
|
82
|
+
similarity,
|
|
83
|
+
sharedDeps: [...shared],
|
|
84
|
+
uniqueToA: uniqueA,
|
|
85
|
+
uniqueToB: uniqueB
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
similarFiles
|
|
91
|
+
};
|
|
92
|
+
//# sourceMappingURL=chunk-Z73NYSBZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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":[]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findFirstSymbolMatch,
|
|
3
|
+
getCalleeRowsForSymbol
|
|
4
|
+
} from "./chunk-FUHJCHS4.js";
|
|
5
|
+
import {
|
|
6
|
+
shortenSymbol
|
|
7
|
+
} from "./chunk-QOV2R2WT.js";
|
|
8
|
+
|
|
9
|
+
// src/queries/slice.ts
|
|
10
|
+
function slice(db, symbolPattern, opts = {}) {
|
|
11
|
+
const { direction = "backward" } = opts;
|
|
12
|
+
const match = findFirstSymbolMatch(db, symbolPattern);
|
|
13
|
+
if (!match) return null;
|
|
14
|
+
if (direction === "backward") {
|
|
15
|
+
return backwardSlice(db, match);
|
|
16
|
+
} else {
|
|
17
|
+
return forwardSlice(db, match);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function backwardSlice(db, match) {
|
|
21
|
+
const callees = getCalleeRowsForSymbol(db, match);
|
|
22
|
+
const localPredecessors = db.all(
|
|
23
|
+
`SELECT DISTINCT gs.symbol, d.relative_path AS file
|
|
24
|
+
FROM defn_enclosing_ranges der
|
|
25
|
+
JOIN global_symbols gs ON der.symbol_id = gs.id
|
|
26
|
+
JOIN documents d ON der.document_id = d.id
|
|
27
|
+
WHERE der.document_id = ?
|
|
28
|
+
AND der.end_line < ?
|
|
29
|
+
AND gs.id != ?
|
|
30
|
+
${db.symbolNoiseFor("gs")}
|
|
31
|
+
ORDER BY der.start_line DESC
|
|
32
|
+
LIMIT 15`,
|
|
33
|
+
match.documentId,
|
|
34
|
+
match.startLine,
|
|
35
|
+
match.symbolId
|
|
36
|
+
);
|
|
37
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38
|
+
const connected = [];
|
|
39
|
+
for (const c of callees) {
|
|
40
|
+
if (seen.has(c.symbol)) continue;
|
|
41
|
+
seen.add(c.symbol);
|
|
42
|
+
connected.push({
|
|
43
|
+
symbol: c.symbol,
|
|
44
|
+
shortName: shortenSymbol(c.symbol),
|
|
45
|
+
file: c.file,
|
|
46
|
+
relationship: "referenced within definition (callee)"
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
for (const p of localPredecessors) {
|
|
50
|
+
if (seen.has(p.symbol) || db.isIgnored(p.file)) continue;
|
|
51
|
+
seen.add(p.symbol);
|
|
52
|
+
connected.push({
|
|
53
|
+
symbol: p.symbol,
|
|
54
|
+
shortName: shortenSymbol(p.symbol),
|
|
55
|
+
file: p.file,
|
|
56
|
+
relationship: "defined before target in same file (local predecessor)"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
symbol: match.symbol,
|
|
61
|
+
shortName: shortenSymbol(match.symbol),
|
|
62
|
+
direction: "backward",
|
|
63
|
+
connectedSymbols: connected
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function forwardSlice(db, match) {
|
|
67
|
+
const rows = db.all(
|
|
68
|
+
`SELECT DISTINCT
|
|
69
|
+
enc_gs.symbol AS enclosing_symbol,
|
|
70
|
+
enc_d.relative_path AS enclosing_file,
|
|
71
|
+
out_gs.symbol AS output_symbol,
|
|
72
|
+
out_d.relative_path AS output_file
|
|
73
|
+
FROM mentions ref_m
|
|
74
|
+
JOIN chunks ref_c ON ref_m.chunk_id = ref_c.id
|
|
75
|
+
JOIN documents ref_d ON ref_c.document_id = ref_d.id
|
|
76
|
+
-- Find enclosing function at each reference site
|
|
77
|
+
JOIN defn_enclosing_ranges enc_der
|
|
78
|
+
ON enc_der.document_id = ref_d.id
|
|
79
|
+
AND enc_der.start_line <= ref_c.start_line
|
|
80
|
+
AND enc_der.end_line >= ref_c.end_line
|
|
81
|
+
JOIN global_symbols enc_gs ON enc_der.symbol_id = enc_gs.id
|
|
82
|
+
JOIN documents enc_d ON enc_der.document_id = enc_d.id
|
|
83
|
+
-- Find other symbols referenced within that enclosing function
|
|
84
|
+
JOIN mentions out_m ON out_m.role = 0
|
|
85
|
+
JOIN chunks out_c ON out_m.chunk_id = out_c.id
|
|
86
|
+
AND out_c.document_id = enc_der.document_id
|
|
87
|
+
AND out_c.start_line >= enc_der.start_line
|
|
88
|
+
AND out_c.end_line <= enc_der.end_line
|
|
89
|
+
JOIN global_symbols out_gs ON out_m.symbol_id = out_gs.id
|
|
90
|
+
JOIN defn_enclosing_ranges out_der ON out_gs.id = out_der.symbol_id
|
|
91
|
+
JOIN documents out_d ON out_der.document_id = out_d.id
|
|
92
|
+
WHERE ref_m.symbol_id = ? AND ref_m.role = 0
|
|
93
|
+
AND out_gs.id != ? AND out_gs.id != enc_gs.id
|
|
94
|
+
AND out_d.id != ref_d.id
|
|
95
|
+
${db.symbolNoiseFor("out_gs")}
|
|
96
|
+
${db.pathExclusionsFor("out_d")}
|
|
97
|
+
ORDER BY out_d.relative_path
|
|
98
|
+
LIMIT 30`,
|
|
99
|
+
match.symbolId,
|
|
100
|
+
match.symbolId
|
|
101
|
+
);
|
|
102
|
+
const seen = /* @__PURE__ */ new Set();
|
|
103
|
+
const connected = [];
|
|
104
|
+
for (const r of rows) {
|
|
105
|
+
if (seen.has(r.output_symbol) || db.isIgnored(r.output_file)) continue;
|
|
106
|
+
seen.add(r.output_symbol);
|
|
107
|
+
connected.push({
|
|
108
|
+
symbol: r.output_symbol,
|
|
109
|
+
shortName: shortenSymbol(r.output_symbol),
|
|
110
|
+
file: r.output_file,
|
|
111
|
+
relationship: `used alongside target in ${shortenSymbol(r.enclosing_symbol)}`
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
symbol: match.symbol,
|
|
116
|
+
shortName: shortenSymbol(match.symbol),
|
|
117
|
+
direction: "forward",
|
|
118
|
+
connectedSymbols: connected
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export {
|
|
123
|
+
slice
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=chunk-ZJRYBOEE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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 = 0\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 = 0\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":[]}
|