ragcode-context-engine 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/LICENSE +21 -0
- package/README.md +366 -0
- package/README.zh-CN.md +363 -0
- package/dist/src/cli/configure/app.d.ts +6 -0
- package/dist/src/cli/configure/app.js +81 -0
- package/dist/src/cli/configure/run.d.ts +5 -0
- package/dist/src/cli/configure/run.js +85 -0
- package/dist/src/cli/configure/state.d.ts +42 -0
- package/dist/src/cli/configure/state.js +174 -0
- package/dist/src/cli/configure.d.ts +31 -0
- package/dist/src/cli/configure.js +101 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +503 -0
- package/dist/src/cli/tui/index-progress.d.ts +12 -0
- package/dist/src/cli/tui/index-progress.js +49 -0
- package/dist/src/cli/tui/watch-status.d.ts +10 -0
- package/dist/src/cli/tui/watch-status.js +27 -0
- package/dist/src/cli/update.d.ts +18 -0
- package/dist/src/cli/update.js +111 -0
- package/dist/src/config/dotenv.d.ts +1 -0
- package/dist/src/config/dotenv.js +14 -0
- package/dist/src/config/graph-runtime.d.ts +13 -0
- package/dist/src/config/graph-runtime.js +29 -0
- package/dist/src/config/runtime-config.d.ts +87 -0
- package/dist/src/config/runtime-config.js +215 -0
- package/dist/src/config/semantic-runtime.d.ts +24 -0
- package/dist/src/config/semantic-runtime.js +89 -0
- package/dist/src/context/context-builder.d.ts +20 -0
- package/dist/src/context/context-builder.js +277 -0
- package/dist/src/context/expansion-policy.d.ts +6 -0
- package/dist/src/context/expansion-policy.js +49 -0
- package/dist/src/context/skeletonizer.d.ts +2 -0
- package/dist/src/context/skeletonizer.js +79 -0
- package/dist/src/context/snippet-renderer.d.ts +2 -0
- package/dist/src/context/snippet-renderer.js +67 -0
- package/dist/src/core/contracts.d.ts +74 -0
- package/dist/src/core/contracts.js +1 -0
- package/dist/src/core/engine.d.ts +64 -0
- package/dist/src/core/engine.js +442 -0
- package/dist/src/core/types.d.ts +490 -0
- package/dist/src/core/types.js +1 -0
- package/dist/src/diagnostics/doctor.d.ts +66 -0
- package/dist/src/diagnostics/doctor.js +193 -0
- package/dist/src/diagnostics/embedding-test.d.ts +24 -0
- package/dist/src/diagnostics/embedding-test.js +83 -0
- package/dist/src/graph/diff-files.d.ts +1 -0
- package/dist/src/graph/diff-files.js +14 -0
- package/dist/src/graph/impact-report.d.ts +10 -0
- package/dist/src/graph/impact-report.js +173 -0
- package/dist/src/graph/in-memory-graph-store.d.ts +36 -0
- package/dist/src/graph/in-memory-graph-store.js +395 -0
- package/dist/src/graph/owner-ranking.d.ts +2 -0
- package/dist/src/graph/owner-ranking.js +41 -0
- package/dist/src/graph/sqlite-graph-store.d.ts +51 -0
- package/dist/src/graph/sqlite-graph-store.js +724 -0
- package/dist/src/graph/sqlite-statements.d.ts +36 -0
- package/dist/src/graph/sqlite-statements.js +105 -0
- package/dist/src/graph/target-matcher.d.ts +13 -0
- package/dist/src/graph/target-matcher.js +64 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.js +32 -0
- package/dist/src/indexing/analyzers/fallback-analyzer.d.ts +6 -0
- package/dist/src/indexing/analyzers/fallback-analyzer.js +45 -0
- package/dist/src/indexing/analyzers/go-treesitter-analyzer.d.ts +2 -0
- package/dist/src/indexing/analyzers/go-treesitter-analyzer.js +87 -0
- package/dist/src/indexing/analyzers/java-treesitter-analyzer.d.ts +2 -0
- package/dist/src/indexing/analyzers/java-treesitter-analyzer.js +88 -0
- package/dist/src/indexing/analyzers/python-treesitter-analyzer.d.ts +2 -0
- package/dist/src/indexing/analyzers/python-treesitter-analyzer.js +96 -0
- package/dist/src/indexing/analyzers/registry.d.ts +5 -0
- package/dist/src/indexing/analyzers/registry.js +23 -0
- package/dist/src/indexing/analyzers/rust-treesitter-analyzer.d.ts +2 -0
- package/dist/src/indexing/analyzers/rust-treesitter-analyzer.js +96 -0
- package/dist/src/indexing/analyzers/tree-sitter-base.d.ts +30 -0
- package/dist/src/indexing/analyzers/tree-sitter-base.js +163 -0
- package/dist/src/indexing/analyzers/types.d.ts +17 -0
- package/dist/src/indexing/analyzers/types.js +1 -0
- package/dist/src/indexing/analyzers/typescript-analyzer.d.ts +5 -0
- package/dist/src/indexing/analyzers/typescript-analyzer.js +199 -0
- package/dist/src/indexing/ast-analyzer.d.ts +11 -0
- package/dist/src/indexing/ast-analyzer.js +11 -0
- package/dist/src/indexing/chunker.d.ts +11 -0
- package/dist/src/indexing/chunker.js +157 -0
- package/dist/src/indexing/ignore-policy.d.ts +6 -0
- package/dist/src/indexing/ignore-policy.js +40 -0
- package/dist/src/indexing/indexer.d.ts +13 -0
- package/dist/src/indexing/indexer.js +189 -0
- package/dist/src/indexing/language.d.ts +3 -0
- package/dist/src/indexing/language.js +24 -0
- package/dist/src/indexing/scanner.d.ts +13 -0
- package/dist/src/indexing/scanner.js +87 -0
- package/dist/src/lsp/definition-resolver.d.ts +6 -0
- package/dist/src/lsp/definition-resolver.js +60 -0
- package/dist/src/lsp/typescript-language-service.d.ts +21 -0
- package/dist/src/lsp/typescript-language-service.js +82 -0
- package/dist/src/mcp/server.d.ts +11 -0
- package/dist/src/mcp/server.js +64 -0
- package/dist/src/mcp/tools.d.ts +266 -0
- package/dist/src/mcp/tools.js +309 -0
- package/dist/src/project/project-identity.d.ts +2 -0
- package/dist/src/project/project-identity.js +24 -0
- package/dist/src/project/project-registry.d.ts +12 -0
- package/dist/src/project/project-registry.js +49 -0
- package/dist/src/project/workspace-resolver.d.ts +20 -0
- package/dist/src/project/workspace-resolver.js +62 -0
- package/dist/src/retrieval/graph-reranker.d.ts +11 -0
- package/dist/src/retrieval/graph-reranker.js +0 -0
- package/dist/src/retrieval/hybrid-retriever.d.ts +31 -0
- package/dist/src/retrieval/hybrid-retriever.js +111 -0
- package/dist/src/retrieval/path-classification.d.ts +6 -0
- package/dist/src/retrieval/path-classification.js +22 -0
- package/dist/src/retrieval/query-matching.d.ts +22 -0
- package/dist/src/retrieval/query-matching.js +166 -0
- package/dist/src/retrieval/query-planner.d.ts +5 -0
- package/dist/src/retrieval/query-planner.js +77 -0
- package/dist/src/retrieval/ranking-signals.d.ts +19 -0
- package/dist/src/retrieval/ranking-signals.js +97 -0
- package/dist/src/retrieval/topology-distance.d.ts +21 -0
- package/dist/src/retrieval/topology-distance.js +116 -0
- package/dist/src/reuse/reuse-detector.d.ts +12 -0
- package/dist/src/reuse/reuse-detector.js +564 -0
- package/dist/src/semantic/deterministic-embedding.d.ts +7 -0
- package/dist/src/semantic/deterministic-embedding.js +31 -0
- package/dist/src/semantic/in-memory-semantic-store.d.ts +11 -0
- package/dist/src/semantic/in-memory-semantic-store.js +65 -0
- package/dist/src/semantic/lance-semantic-store.d.ts +131 -0
- package/dist/src/semantic/lance-semantic-store.js +623 -0
- package/dist/src/semantic/openai-compatible-embedding.d.ts +19 -0
- package/dist/src/semantic/openai-compatible-embedding.js +75 -0
- package/dist/src/service/service-identity.d.ts +13 -0
- package/dist/src/service/service-identity.js +48 -0
- package/dist/src/service/service-manager.d.ts +29 -0
- package/dist/src/service/service-manager.js +231 -0
- package/dist/src/service/service-templates.d.ts +22 -0
- package/dist/src/service/service-templates.js +101 -0
- package/dist/src/subgraph/impact-explainer.d.ts +2 -0
- package/dist/src/subgraph/impact-explainer.js +54 -0
- package/dist/src/subgraph/node-expander.d.ts +13 -0
- package/dist/src/subgraph/node-expander.js +139 -0
- package/dist/src/subgraph/output-preset.d.ts +3 -0
- package/dist/src/subgraph/output-preset.js +102 -0
- package/dist/src/subgraph/subgraph-builder.d.ts +17 -0
- package/dist/src/subgraph/subgraph-builder.js +688 -0
- package/dist/src/topology/export-index.d.ts +7 -0
- package/dist/src/topology/export-index.js +14 -0
- package/dist/src/topology/framework-topology.d.ts +3 -0
- package/dist/src/topology/framework-topology.js +460 -0
- package/dist/src/topology/import-resolver.d.ts +2 -0
- package/dist/src/topology/import-resolver.js +29 -0
- package/dist/src/topology/orm-topology.d.ts +3 -0
- package/dist/src/topology/orm-topology.js +200 -0
- package/dist/src/topology/runtime-topology.d.ts +3 -0
- package/dist/src/topology/runtime-topology.js +204 -0
- package/dist/src/topology/symbol-resolver.d.ts +6 -0
- package/dist/src/topology/symbol-resolver.js +74 -0
- package/dist/src/topology/test-topology.d.ts +2 -0
- package/dist/src/topology/test-topology.js +82 -0
- package/dist/src/utils/hash.d.ts +2 -0
- package/dist/src/utils/hash.js +7 -0
- package/dist/src/utils/path.d.ts +2 -0
- package/dist/src/utils/path.js +7 -0
- package/dist/src/watch/event-journal.d.ts +17 -0
- package/dist/src/watch/event-journal.js +81 -0
- package/dist/src/watch/file-event-coalescer.d.ts +9 -0
- package/dist/src/watch/file-event-coalescer.js +39 -0
- package/dist/src/watch/index-scheduler.d.ts +52 -0
- package/dist/src/watch/index-scheduler.js +190 -0
- package/dist/src/watch/watch-daemon.d.ts +73 -0
- package/dist/src/watch/watch-daemon.js +368 -0
- package/dist/src/watch/watcher-liveness.d.ts +47 -0
- package/dist/src/watch/watcher-liveness.js +168 -0
- package/dist/src/web/server.d.ts +1 -0
- package/dist/src/web/server.js +375 -0
- package/package.json +94 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { resolveCallDefinitionsWithTypeScript } from "../lsp/definition-resolver.js";
|
|
3
|
+
import { buildFrameworkTopologyEdges } from "../topology/framework-topology.js";
|
|
4
|
+
import { buildOrmTopologyEdges } from "../topology/orm-topology.js";
|
|
5
|
+
import { buildRuntimeTopologyEdges } from "../topology/runtime-topology.js";
|
|
6
|
+
import { resolveGraphEdges } from "../topology/symbol-resolver.js";
|
|
7
|
+
import { buildTestTopologyEdges } from "../topology/test-topology.js";
|
|
8
|
+
import { analyzeFile } from "./ast-analyzer.js";
|
|
9
|
+
export async function chunkFiles(repoRoot, files, options = {}) {
|
|
10
|
+
const analyzed = await analyzeFiles(repoRoot, files);
|
|
11
|
+
return {
|
|
12
|
+
chunks: analyzed.chunks,
|
|
13
|
+
symbols: analyzed.symbols,
|
|
14
|
+
edges: resolveChunkEdges(repoRoot, files, analyzed.sources, analyzed.symbols, analyzed.edges)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export async function chunkFilesIncremental(repoRoot, files, filesToAnalyze, cached, options = {}) {
|
|
18
|
+
const analyzedPaths = new Set(filesToAnalyze.map((file) => file.path));
|
|
19
|
+
const currentPaths = new Set(files.map((file) => file.path));
|
|
20
|
+
const currentCached = filterCachedChunking(cached, currentPaths, analyzedPaths);
|
|
21
|
+
if (analyzedPaths.size === 0)
|
|
22
|
+
return currentCached;
|
|
23
|
+
const analyzed = await analyzeFiles(repoRoot, filesToAnalyze);
|
|
24
|
+
const chunks = [
|
|
25
|
+
...currentCached.chunks,
|
|
26
|
+
...analyzed.chunks
|
|
27
|
+
];
|
|
28
|
+
const symbols = [
|
|
29
|
+
...currentCached.symbols,
|
|
30
|
+
...analyzed.symbols
|
|
31
|
+
];
|
|
32
|
+
const refreshedEdges = resolveChunkEdges(repoRoot, files, analyzed.sources, symbols, analyzed.edges, routeCatalogEdges(cached.edges, currentPaths, analyzedPaths))
|
|
33
|
+
.filter((edge) => {
|
|
34
|
+
const sourceFile = edgeSourceFile(edge);
|
|
35
|
+
return sourceFile ? analyzedPaths.has(sourceFile) : false;
|
|
36
|
+
});
|
|
37
|
+
return { chunks, symbols, edges: dedupeEdges([...currentCached.edges, ...refreshedEdges]) };
|
|
38
|
+
}
|
|
39
|
+
async function analyzeFiles(repoRoot, files) {
|
|
40
|
+
const chunks = [];
|
|
41
|
+
const symbols = [];
|
|
42
|
+
const edges = [];
|
|
43
|
+
const sources = [];
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
const content = await fs.readFile(file.absolutePath, "utf8");
|
|
46
|
+
if (file.language === "typescript" || file.language === "javascript") {
|
|
47
|
+
sources.push({ filePath: file.path, absolutePath: file.absolutePath, content });
|
|
48
|
+
}
|
|
49
|
+
const analysis = analyzeFile(repoRoot, file, content);
|
|
50
|
+
chunks.push(...analysis.chunks);
|
|
51
|
+
symbols.push(...analysis.symbols);
|
|
52
|
+
edges.push(...analysis.edges);
|
|
53
|
+
}
|
|
54
|
+
return { chunks, symbols, edges, sources };
|
|
55
|
+
}
|
|
56
|
+
function resolveChunkEdges(repoRoot, files, sources, symbols, edges, priorEdges = []) {
|
|
57
|
+
const importResolvedEdges = resolveGraphEdges(files, symbols, edges);
|
|
58
|
+
const lspResolvedEdges = resolveCallDefinitionsWithTypeScript(repoRoot, sources, symbols, importResolvedEdges);
|
|
59
|
+
const testEdges = buildTestTopologyEdges(symbols, lspResolvedEdges);
|
|
60
|
+
const frameworkEdges = buildFrameworkTopologyEdges(files, sources, symbols, lspResolvedEdges, priorEdges);
|
|
61
|
+
const runtimeEdges = buildRuntimeTopologyEdges(repoRoot, files, sources, symbols);
|
|
62
|
+
const ormEdges = buildOrmTopologyEdges(repoRoot, files, sources, symbols);
|
|
63
|
+
return [...lspResolvedEdges, ...testEdges, ...frameworkEdges, ...runtimeEdges, ...ormEdges];
|
|
64
|
+
}
|
|
65
|
+
function edgeSourceFile(edge) {
|
|
66
|
+
return typeof edge.metadata?.sourceFile === "string" ? edge.metadata.sourceFile : undefined;
|
|
67
|
+
}
|
|
68
|
+
function routeCatalogEdges(edges, currentPaths, analyzedPaths) {
|
|
69
|
+
return edges.filter((edge) => {
|
|
70
|
+
if (edge.kind !== "calls_api" && edge.kind !== "routes_to" && edge.kind !== "handles_webhook")
|
|
71
|
+
return false;
|
|
72
|
+
const routeFile = stringMetadata(edge, "routeFile") ?? stringMetadata(edge, "targetFile");
|
|
73
|
+
if (!routeFile || !currentPaths.has(routeFile))
|
|
74
|
+
return false;
|
|
75
|
+
return !analyzedPaths.has(routeFile);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function filterCachedChunking(cached, currentPaths, analyzedPaths) {
|
|
79
|
+
return {
|
|
80
|
+
chunks: cached.chunks.filter((chunk) => currentPaths.has(chunk.filePath) && !analyzedPaths.has(chunk.filePath)),
|
|
81
|
+
symbols: cached.symbols.filter((symbol) => currentPaths.has(symbol.filePath) && !analyzedPaths.has(symbol.filePath)),
|
|
82
|
+
edges: cached.edges.filter((edge) => {
|
|
83
|
+
const sourceFile = edgeSourceFile(edge);
|
|
84
|
+
return Boolean(sourceFile && currentPaths.has(sourceFile) && !analyzedPaths.has(sourceFile));
|
|
85
|
+
})
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function dedupeEdges(edges) {
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const unique = [];
|
|
91
|
+
for (const edge of edges) {
|
|
92
|
+
const key = edgeDedupeKey(edge);
|
|
93
|
+
if (seen.has(key))
|
|
94
|
+
continue;
|
|
95
|
+
seen.add(key);
|
|
96
|
+
unique.push(edge);
|
|
97
|
+
}
|
|
98
|
+
return unique;
|
|
99
|
+
}
|
|
100
|
+
function edgeDedupeKey(edge) {
|
|
101
|
+
const metadata = edge.metadata;
|
|
102
|
+
return [
|
|
103
|
+
edge.projectId,
|
|
104
|
+
edge.sourceId,
|
|
105
|
+
edge.targetId,
|
|
106
|
+
edge.kind,
|
|
107
|
+
scalarMetadata(metadata, "sourceFile"),
|
|
108
|
+
scalarMetadata(metadata, "targetFile"),
|
|
109
|
+
scalarMetadata(metadata, "targetName"),
|
|
110
|
+
scalarMetadata(metadata, "source"),
|
|
111
|
+
scalarMetadata(metadata, "name"),
|
|
112
|
+
scalarMetadata(metadata, "line"),
|
|
113
|
+
scalarMetadata(metadata, "position"),
|
|
114
|
+
scalarMetadata(metadata, "resolution"),
|
|
115
|
+
scalarMetadata(metadata, "importedName"),
|
|
116
|
+
scalarMetadata(metadata, "localName"),
|
|
117
|
+
scalarMetadata(metadata, "route"),
|
|
118
|
+
scalarMetadata(metadata, "requestPath"),
|
|
119
|
+
scalarMetadata(metadata, "framework"),
|
|
120
|
+
scalarMetadata(metadata, "resource"),
|
|
121
|
+
scalarMetadata(metadata, "operation"),
|
|
122
|
+
scalarMetadata(metadata, "event"),
|
|
123
|
+
scalarMetadata(metadata, "handler"),
|
|
124
|
+
scalarMetadata(metadata, "testFile"),
|
|
125
|
+
scalarMetadata(metadata, "colocated"),
|
|
126
|
+
bindingsMetadata(metadata)
|
|
127
|
+
].map(escapeKeyPart).join("\u001f");
|
|
128
|
+
}
|
|
129
|
+
function scalarMetadata(metadata, key) {
|
|
130
|
+
const value = metadata?.[key];
|
|
131
|
+
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean")
|
|
132
|
+
return String(value);
|
|
133
|
+
return "";
|
|
134
|
+
}
|
|
135
|
+
function stringMetadata(edge, key) {
|
|
136
|
+
const value = edge.metadata?.[key];
|
|
137
|
+
return typeof value === "string" ? value : undefined;
|
|
138
|
+
}
|
|
139
|
+
function bindingsMetadata(metadata) {
|
|
140
|
+
const value = metadata?.bindings;
|
|
141
|
+
if (!Array.isArray(value))
|
|
142
|
+
return "";
|
|
143
|
+
return value
|
|
144
|
+
.map((entry) => {
|
|
145
|
+
if (!entry || typeof entry !== "object")
|
|
146
|
+
return "";
|
|
147
|
+
const binding = entry;
|
|
148
|
+
const imported = typeof binding.imported === "string" ? binding.imported : "";
|
|
149
|
+
const local = typeof binding.local === "string" ? binding.local : "";
|
|
150
|
+
return `${imported}->${local}`;
|
|
151
|
+
})
|
|
152
|
+
.sort()
|
|
153
|
+
.join("|");
|
|
154
|
+
}
|
|
155
|
+
function escapeKeyPart(value) {
|
|
156
|
+
return value.replaceAll("\\", "\\\\").replaceAll("\u001f", "\\u001f");
|
|
157
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface IgnoreDecision {
|
|
2
|
+
ignored: boolean;
|
|
3
|
+
reason?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function shouldIgnoreDirectory(name: string): IgnoreDecision;
|
|
6
|
+
export declare function shouldIgnoreFile(relativePath: string, maxFileBytes: number, sizeBytes: number): IgnoreDecision;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const DEFAULT_IGNORED_DIRS = new Set([
|
|
2
|
+
".git",
|
|
3
|
+
".ragcode",
|
|
4
|
+
".codegraph",
|
|
5
|
+
".understand-anything",
|
|
6
|
+
".omx",
|
|
7
|
+
"node_modules",
|
|
8
|
+
"dist",
|
|
9
|
+
"build",
|
|
10
|
+
"coverage",
|
|
11
|
+
"target",
|
|
12
|
+
".next",
|
|
13
|
+
".turbo"
|
|
14
|
+
]);
|
|
15
|
+
const SENSITIVE_FILE_PATTERNS = [
|
|
16
|
+
/^\.env(\..*)?$/i,
|
|
17
|
+
/(^|[\\/])id_rsa$/i,
|
|
18
|
+
/(^|[\\/])id_dsa$/i,
|
|
19
|
+
/(^|[\\/])credentials?(\..*)?$/i,
|
|
20
|
+
/(^|[\\/])secrets?(\..*)?$/i,
|
|
21
|
+
/\.pem$/i,
|
|
22
|
+
/\.key$/i,
|
|
23
|
+
/\.p12$/i,
|
|
24
|
+
/\.pfx$/i
|
|
25
|
+
];
|
|
26
|
+
export function shouldIgnoreDirectory(name) {
|
|
27
|
+
if (DEFAULT_IGNORED_DIRS.has(name))
|
|
28
|
+
return { ignored: true, reason: `ignored directory: ${name}` };
|
|
29
|
+
return { ignored: false };
|
|
30
|
+
}
|
|
31
|
+
export function shouldIgnoreFile(relativePath, maxFileBytes, sizeBytes) {
|
|
32
|
+
const normalized = relativePath.replaceAll("\\", "/");
|
|
33
|
+
const basename = normalized.split("/").pop() ?? normalized;
|
|
34
|
+
if (SENSITIVE_FILE_PATTERNS.some((pattern) => pattern.test(basename) || pattern.test(normalized))) {
|
|
35
|
+
return { ignored: true, reason: "sensitive file policy" };
|
|
36
|
+
}
|
|
37
|
+
if (sizeBytes > maxFileBytes)
|
|
38
|
+
return { ignored: true, reason: `file exceeds ${maxFileBytes} bytes` };
|
|
39
|
+
return { ignored: false };
|
|
40
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { GraphStore, Indexer, SemanticStore, EmbeddingProvider } from "../core/contracts.js";
|
|
2
|
+
import type { IndexRefreshOptions, ProjectIdentity, RepoIndex } from "../core/types.js";
|
|
3
|
+
export interface RepoIndexerOptions {
|
|
4
|
+
graphStore: GraphStore;
|
|
5
|
+
semanticStore: SemanticStore;
|
|
6
|
+
embeddingProvider: EmbeddingProvider;
|
|
7
|
+
}
|
|
8
|
+
export declare class RepoIndexer implements Indexer {
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(options: RepoIndexerOptions);
|
|
11
|
+
indexRepo(repoRoot: string, projectId: string, project?: ProjectIdentity, options?: IndexRefreshOptions): Promise<RepoIndex>;
|
|
12
|
+
private updateSemanticIndex;
|
|
13
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { resolveImportPath } from "../topology/import-resolver.js";
|
|
3
|
+
import { chunkFiles, chunkFilesIncremental } from "./chunker.js";
|
|
4
|
+
import { scanRepo } from "./scanner.js";
|
|
5
|
+
export class RepoIndexer {
|
|
6
|
+
options;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
async indexRepo(repoRoot, projectId, project, options = {}) {
|
|
11
|
+
const absoluteRoot = path.resolve(repoRoot);
|
|
12
|
+
options.onProgress?.({ phase: "loading_existing_index", message: "Loading existing index" });
|
|
13
|
+
const existingFiles = await this.options.graphStore.getFiles(absoluteRoot).catch(() => []);
|
|
14
|
+
const fullReindex = existingFiles.length === 0;
|
|
15
|
+
const affectedPaths = fullReindex ? undefined : normalizedAffectedFiles(options.affectedFiles);
|
|
16
|
+
options.onProgress?.({ phase: "scanning", message: affectedPaths ? "Scanning affected files" : "Scanning repository" });
|
|
17
|
+
const scan = await scanRepo(absoluteRoot, projectId, affectedPaths ? { filePaths: [...affectedPaths] } : {});
|
|
18
|
+
const files = affectedPaths ? mergeAffectedScan(existingFiles, scan.files, affectedPaths) : scan.files;
|
|
19
|
+
const skippedFiles = affectedPaths
|
|
20
|
+
? mergeSkippedFiles(await this.options.graphStore.getSkippedFiles(absoluteRoot).catch(() => []), scan.skippedFiles, affectedPaths)
|
|
21
|
+
: scan.skippedFiles;
|
|
22
|
+
const existingByPath = new Map(existingFiles.map((file) => [file.path, file]));
|
|
23
|
+
const currentPaths = new Set(files.map((file) => file.path));
|
|
24
|
+
const changedFiles = files.filter((file) => {
|
|
25
|
+
if (affectedPaths && !affectedPaths.has(file.path))
|
|
26
|
+
return false;
|
|
27
|
+
return existingByPath.get(file.path)?.contentHash !== file.contentHash;
|
|
28
|
+
});
|
|
29
|
+
const deletedFiles = existingFiles
|
|
30
|
+
.filter((file) => (!affectedPaths || affectedPaths.has(file.path)) && !currentPaths.has(file.path))
|
|
31
|
+
.map((file) => file.path)
|
|
32
|
+
.sort();
|
|
33
|
+
const currentGeneration = this.options.graphStore.getIndexGeneration
|
|
34
|
+
? await this.options.graphStore.getIndexGeneration(absoluteRoot).catch(() => 0)
|
|
35
|
+
: 0;
|
|
36
|
+
const indexGeneration = currentGeneration + 1;
|
|
37
|
+
const changedFilePaths = changedFiles.map((file) => file.path);
|
|
38
|
+
// Incremental refresh loads the full prior graph into memory: chunkFilesIncremental needs the
|
|
39
|
+
// whole symbol/edge set to re-resolve cross-file references for the changed files. Peak memory
|
|
40
|
+
// therefore scales with total graph size, not the changed slice — an accepted trade-off for
|
|
41
|
+
// correctness; revisit with windowed loading if it becomes a ceiling (see todo.md D/L6).
|
|
42
|
+
const cached = fullReindex
|
|
43
|
+
? undefined
|
|
44
|
+
: {
|
|
45
|
+
chunks: await this.options.graphStore.getChunks(absoluteRoot),
|
|
46
|
+
symbols: await this.options.graphStore.getSymbols(absoluteRoot),
|
|
47
|
+
edges: await this.options.graphStore.getEdges(absoluteRoot)
|
|
48
|
+
};
|
|
49
|
+
const refreshedFiles = cached
|
|
50
|
+
? refreshedFilePaths(files, cached.edges, changedFilePaths, deletedFiles)
|
|
51
|
+
: changedFilePaths;
|
|
52
|
+
const filesToAnalyze = files.filter((file) => refreshedFiles.includes(file.path));
|
|
53
|
+
options.onProgress?.({
|
|
54
|
+
phase: "analyzing",
|
|
55
|
+
message: cached ? "Analyzing changed graph slice" : "Analyzing full repository",
|
|
56
|
+
scannedFiles: scan.files.length,
|
|
57
|
+
changedFiles: changedFilePaths.length,
|
|
58
|
+
deletedFiles: deletedFiles.length,
|
|
59
|
+
refreshedFiles: refreshedFiles.length
|
|
60
|
+
});
|
|
61
|
+
const { chunks, symbols, edges } = cached
|
|
62
|
+
? await chunkFilesIncremental(absoluteRoot, files, filesToAnalyze, cached)
|
|
63
|
+
: await chunkFiles(absoluteRoot, files);
|
|
64
|
+
const indexedAtMs = Date.now();
|
|
65
|
+
const index = {
|
|
66
|
+
projectId,
|
|
67
|
+
project: project ? { ...project, lastIndexedAtMs: indexedAtMs } : undefined,
|
|
68
|
+
repoRoot: absoluteRoot,
|
|
69
|
+
indexedAtMs,
|
|
70
|
+
indexGeneration,
|
|
71
|
+
changedFiles: changedFilePaths,
|
|
72
|
+
deletedFiles,
|
|
73
|
+
affectedFiles: affectedPaths ? [...affectedPaths].sort() : undefined,
|
|
74
|
+
scannedFiles: scan.files.map((file) => file.path).sort(),
|
|
75
|
+
refreshedFiles,
|
|
76
|
+
fullReindex,
|
|
77
|
+
files,
|
|
78
|
+
chunks,
|
|
79
|
+
symbols,
|
|
80
|
+
edges,
|
|
81
|
+
skippedFiles
|
|
82
|
+
};
|
|
83
|
+
options.onProgress?.({
|
|
84
|
+
phase: "writing_graph",
|
|
85
|
+
message: "Writing graph index",
|
|
86
|
+
scannedFiles: index.scannedFiles?.length,
|
|
87
|
+
changedFiles: index.changedFiles.length,
|
|
88
|
+
deletedFiles: index.deletedFiles.length,
|
|
89
|
+
refreshedFiles: index.refreshedFiles?.length,
|
|
90
|
+
chunks: index.chunks.length,
|
|
91
|
+
symbols: index.symbols.length,
|
|
92
|
+
edges: index.edges.length
|
|
93
|
+
});
|
|
94
|
+
await this.options.graphStore.upsertIndex(index);
|
|
95
|
+
options.onProgress?.({
|
|
96
|
+
phase: "writing_semantic",
|
|
97
|
+
message: "Writing semantic index",
|
|
98
|
+
chunks: index.chunks.length,
|
|
99
|
+
symbols: index.symbols.length,
|
|
100
|
+
edges: index.edges.length
|
|
101
|
+
});
|
|
102
|
+
await this.updateSemanticIndex(absoluteRoot, projectId, index);
|
|
103
|
+
options.onProgress?.({
|
|
104
|
+
phase: "complete",
|
|
105
|
+
message: "Index complete",
|
|
106
|
+
scannedFiles: index.scannedFiles?.length,
|
|
107
|
+
changedFiles: index.changedFiles.length,
|
|
108
|
+
deletedFiles: index.deletedFiles.length,
|
|
109
|
+
refreshedFiles: index.refreshedFiles?.length,
|
|
110
|
+
chunks: index.chunks.length,
|
|
111
|
+
symbols: index.symbols.length,
|
|
112
|
+
edges: index.edges.length
|
|
113
|
+
});
|
|
114
|
+
return index;
|
|
115
|
+
}
|
|
116
|
+
async updateSemanticIndex(repoRoot, projectId, index) {
|
|
117
|
+
try {
|
|
118
|
+
const semanticNeedsRebuild = await this.options.semanticStore.needsRebuild?.(repoRoot, projectId) ?? false;
|
|
119
|
+
if (index.fullReindex || semanticNeedsRebuild || !this.options.semanticStore.deleteFile) {
|
|
120
|
+
await this.options.semanticStore.resetRepo(repoRoot);
|
|
121
|
+
await this.options.semanticStore.upsertChunks(index.chunks, this.options.embeddingProvider, index.indexGeneration);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const changedOrDeleted = new Set([...index.changedFiles, ...index.deletedFiles]);
|
|
125
|
+
for (const filePath of changedOrDeleted) {
|
|
126
|
+
await this.options.semanticStore.deleteFile(repoRoot, projectId, filePath);
|
|
127
|
+
}
|
|
128
|
+
const changedChunks = index.chunks.filter((chunk) => changedOrDeleted.has(chunk.filePath) && !index.deletedFiles.includes(chunk.filePath));
|
|
129
|
+
await this.options.semanticStore.upsertChunks(changedChunks, this.options.embeddingProvider, index.indexGeneration);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
133
|
+
console.error(`[ragcode] semantic index skipped: ${message}`);
|
|
134
|
+
// Semantic recall is optional cache acceleration. Graph rows remain the source of truth.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function normalizedAffectedFiles(filePaths) {
|
|
139
|
+
if (!filePaths?.length)
|
|
140
|
+
return undefined;
|
|
141
|
+
return new Set(filePaths.map((filePath) => filePath.replaceAll("\\", "/")).filter(Boolean));
|
|
142
|
+
}
|
|
143
|
+
function mergeAffectedScan(existingFiles, scannedFiles, affectedPaths) {
|
|
144
|
+
const merged = new Map();
|
|
145
|
+
for (const file of existingFiles) {
|
|
146
|
+
if (!affectedPaths.has(file.path))
|
|
147
|
+
merged.set(file.path, file);
|
|
148
|
+
}
|
|
149
|
+
for (const file of scannedFiles)
|
|
150
|
+
merged.set(file.path, file);
|
|
151
|
+
return [...merged.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
152
|
+
}
|
|
153
|
+
function mergeSkippedFiles(existingSkippedFiles, scannedSkippedFiles, affectedPaths) {
|
|
154
|
+
const merged = new Map();
|
|
155
|
+
for (const skipped of existingSkippedFiles) {
|
|
156
|
+
if (!affectedPaths.has(skipped.filePath))
|
|
157
|
+
merged.set(skipped.filePath, skipped);
|
|
158
|
+
}
|
|
159
|
+
for (const skipped of scannedSkippedFiles)
|
|
160
|
+
merged.set(skipped.filePath, skipped);
|
|
161
|
+
return [...merged.values()].sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
162
|
+
}
|
|
163
|
+
function refreshedFilePaths(files, previousEdges, changedFiles, deletedFiles) {
|
|
164
|
+
const currentPaths = new Set(files.map((file) => file.path));
|
|
165
|
+
const touchedPaths = new Set([...changedFiles, ...deletedFiles]);
|
|
166
|
+
const refreshed = new Set(changedFiles.filter((filePath) => currentPaths.has(filePath)));
|
|
167
|
+
for (const edge of previousEdges) {
|
|
168
|
+
const sourceFile = stringMetadata(edge, "sourceFile");
|
|
169
|
+
if (!sourceFile || !currentPaths.has(sourceFile))
|
|
170
|
+
continue;
|
|
171
|
+
// Any edge whose recorded target file was touched invalidates its source. This is uniform
|
|
172
|
+
// across imports, resolved calls, and framework edges (calls_api/routes_to/uses_middleware):
|
|
173
|
+
// they all carry targetFile metadata, so this single pass covers them — no per-kind reverse pass.
|
|
174
|
+
const previousTargetFile = stringMetadata(edge, "targetFile");
|
|
175
|
+
if (previousTargetFile && touchedPaths.has(previousTargetFile)) {
|
|
176
|
+
refreshed.add(sourceFile);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const importSource = edge.kind === "imports" ? stringMetadata(edge, "source") : undefined;
|
|
180
|
+
const nextTargetFile = importSource ? resolveImportPath(sourceFile, importSource, files) : undefined;
|
|
181
|
+
if (nextTargetFile && touchedPaths.has(nextTargetFile))
|
|
182
|
+
refreshed.add(sourceFile);
|
|
183
|
+
}
|
|
184
|
+
return [...refreshed].sort();
|
|
185
|
+
}
|
|
186
|
+
function stringMetadata(edge, key) {
|
|
187
|
+
const value = edge.metadata?.[key];
|
|
188
|
+
return typeof value === "string" ? value : undefined;
|
|
189
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const EXTENSION_LANGUAGE = {
|
|
2
|
+
".ts": "typescript",
|
|
3
|
+
".tsx": "typescript",
|
|
4
|
+
".js": "javascript",
|
|
5
|
+
".jsx": "javascript",
|
|
6
|
+
".mjs": "javascript",
|
|
7
|
+
".cjs": "javascript",
|
|
8
|
+
".py": "python",
|
|
9
|
+
".rs": "rust",
|
|
10
|
+
".go": "go",
|
|
11
|
+
".java": "java",
|
|
12
|
+
".md": "markdown",
|
|
13
|
+
".mdx": "markdown",
|
|
14
|
+
".json": "json"
|
|
15
|
+
};
|
|
16
|
+
export function detectLanguage(filePath) {
|
|
17
|
+
const dot = filePath.lastIndexOf(".");
|
|
18
|
+
if (dot === -1)
|
|
19
|
+
return "unknown";
|
|
20
|
+
return EXTENSION_LANGUAGE[filePath.slice(dot).toLowerCase()] ?? "unknown";
|
|
21
|
+
}
|
|
22
|
+
export function isIndexableLanguage(language) {
|
|
23
|
+
return language !== "unknown";
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { CodeFile } from "../core/types.js";
|
|
2
|
+
export interface ScanOptions {
|
|
3
|
+
maxFileBytes?: number;
|
|
4
|
+
filePaths?: string[];
|
|
5
|
+
}
|
|
6
|
+
export interface ScanResult {
|
|
7
|
+
files: CodeFile[];
|
|
8
|
+
skippedFiles: Array<{
|
|
9
|
+
filePath: string;
|
|
10
|
+
reason: string;
|
|
11
|
+
}>;
|
|
12
|
+
}
|
|
13
|
+
export declare function scanRepo(repoRoot: string, projectId: string, options?: ScanOptions): Promise<ScanResult>;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { sha256 } from "../utils/hash.js";
|
|
4
|
+
import { normalizeRepoPath } from "../utils/path.js";
|
|
5
|
+
import { detectLanguage, isIndexableLanguage } from "./language.js";
|
|
6
|
+
import { shouldIgnoreDirectory, shouldIgnoreFile } from "./ignore-policy.js";
|
|
7
|
+
export async function scanRepo(repoRoot, projectId, options = {}) {
|
|
8
|
+
const absoluteRoot = path.resolve(repoRoot);
|
|
9
|
+
const maxFileBytes = options.maxFileBytes ?? 512_000;
|
|
10
|
+
const files = [];
|
|
11
|
+
const skippedFiles = [];
|
|
12
|
+
if (options.filePaths?.length) {
|
|
13
|
+
for (const filePath of [...new Set(options.filePaths.map((candidate) => candidate.replaceAll("\\", "/")))].sort()) {
|
|
14
|
+
await scanOne(filePath);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
files: files.sort((a, b) => a.path.localeCompare(b.path)),
|
|
18
|
+
skippedFiles: skippedFiles.sort((a, b) => a.filePath.localeCompare(b.filePath))
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async function walk(dir) {
|
|
22
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const absolutePath = path.join(dir, entry.name);
|
|
25
|
+
if (entry.isDirectory()) {
|
|
26
|
+
const decision = shouldIgnoreDirectory(entry.name);
|
|
27
|
+
if (decision.ignored) {
|
|
28
|
+
skippedFiles.push({ filePath: normalizeRepoPath(absoluteRoot, absolutePath), reason: decision.reason ?? "ignored directory" });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
await walk(absolutePath);
|
|
32
|
+
}
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (!entry.isFile())
|
|
36
|
+
continue;
|
|
37
|
+
const relativePath = normalizeRepoPath(absoluteRoot, absolutePath);
|
|
38
|
+
await scanFile(absolutePath, relativePath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
await walk(absoluteRoot);
|
|
42
|
+
return {
|
|
43
|
+
files: files.sort((a, b) => a.path.localeCompare(b.path)),
|
|
44
|
+
skippedFiles: skippedFiles.sort((a, b) => a.filePath.localeCompare(b.filePath))
|
|
45
|
+
};
|
|
46
|
+
async function scanOne(relativePath) {
|
|
47
|
+
if (!relativePath || relativePath === "." || relativePath.startsWith("..") || path.isAbsolute(relativePath))
|
|
48
|
+
return;
|
|
49
|
+
const parts = relativePath.split("/");
|
|
50
|
+
const ignoredDirectory = parts.find((part) => shouldIgnoreDirectory(part).ignored);
|
|
51
|
+
if (ignoredDirectory) {
|
|
52
|
+
skippedFiles.push({ filePath: relativePath, reason: shouldIgnoreDirectory(ignoredDirectory).reason ?? "ignored directory" });
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
await scanFile(path.join(absoluteRoot, relativePath), relativePath);
|
|
56
|
+
}
|
|
57
|
+
async function scanFile(absolutePath, relativePath) {
|
|
58
|
+
const stat = await fs.stat(absolutePath).catch((error) => {
|
|
59
|
+
if (isNotFound(error))
|
|
60
|
+
return undefined;
|
|
61
|
+
throw error;
|
|
62
|
+
});
|
|
63
|
+
if (!stat?.isFile())
|
|
64
|
+
return;
|
|
65
|
+
const fileDecision = shouldIgnoreFile(relativePath, maxFileBytes, stat.size);
|
|
66
|
+
if (fileDecision.ignored) {
|
|
67
|
+
skippedFiles.push({ filePath: relativePath, reason: fileDecision.reason ?? "ignored file" });
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const language = detectLanguage(path.basename(relativePath));
|
|
71
|
+
if (!isIndexableLanguage(language))
|
|
72
|
+
return;
|
|
73
|
+
const content = await fs.readFile(absolutePath, "utf8");
|
|
74
|
+
files.push({
|
|
75
|
+
projectId,
|
|
76
|
+
path: relativePath,
|
|
77
|
+
absolutePath,
|
|
78
|
+
language,
|
|
79
|
+
sizeBytes: stat.size,
|
|
80
|
+
contentHash: sha256(content),
|
|
81
|
+
modifiedAtMs: stat.mtimeMs
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function isNotFound(error) {
|
|
86
|
+
return Boolean(error && typeof error === "object" && "code" in error && error.code === "ENOENT");
|
|
87
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { GraphEdge, SymbolNode } from "../core/types.js";
|
|
2
|
+
import { TypeScriptLanguageService, type TypeScriptSourceFile } from "./typescript-language-service.js";
|
|
3
|
+
export interface TypeScriptDefinitionResolverOptions {
|
|
4
|
+
createService?: (repoRoot: string, sources: TypeScriptSourceFile[]) => Pick<TypeScriptLanguageService, "getDefinitionAt">;
|
|
5
|
+
}
|
|
6
|
+
export declare function resolveCallDefinitionsWithTypeScript(repoRoot: string, sources: TypeScriptSourceFile[], symbols: SymbolNode[], edges: GraphEdge[], options?: TypeScriptDefinitionResolverOptions): GraphEdge[];
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { TypeScriptLanguageService } from "./typescript-language-service.js";
|
|
2
|
+
export function resolveCallDefinitionsWithTypeScript(repoRoot, sources, symbols, edges, options = {}) {
|
|
3
|
+
if (sources.length === 0)
|
|
4
|
+
return edges;
|
|
5
|
+
try {
|
|
6
|
+
const service = options.createService?.(repoRoot, sources) ?? new TypeScriptLanguageService(repoRoot, sources);
|
|
7
|
+
return edges.map((edge) => resolveCallEdge(service, symbols, edge));
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return edges;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function resolveCallEdge(service, symbols, edge) {
|
|
14
|
+
if (edge.kind !== "calls" || edge.metadata?.resolution === "resolved")
|
|
15
|
+
return edge;
|
|
16
|
+
const sourceFile = stringMetadata(edge, "sourceFile");
|
|
17
|
+
const position = numberMetadata(edge, "position");
|
|
18
|
+
if (!sourceFile || position === undefined)
|
|
19
|
+
return unresolved(edge);
|
|
20
|
+
const definitions = service.getDefinitionAt(sourceFile, position);
|
|
21
|
+
const targetSymbol = findTargetSymbol(symbols, definitions, stringMetadata(edge, "targetName"));
|
|
22
|
+
if (!targetSymbol)
|
|
23
|
+
return unresolved(edge);
|
|
24
|
+
return {
|
|
25
|
+
...edge,
|
|
26
|
+
targetId: targetSymbol.id,
|
|
27
|
+
metadata: {
|
|
28
|
+
...edge.metadata,
|
|
29
|
+
resolution: "resolved_lsp",
|
|
30
|
+
targetFile: targetSymbol.filePath,
|
|
31
|
+
targetSymbol: targetSymbol.name
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function findTargetSymbol(symbols, definitions, targetName) {
|
|
36
|
+
const candidates = definitions.flatMap((definition) => {
|
|
37
|
+
const containing = symbols.filter((symbol) => symbol.kind !== "file" &&
|
|
38
|
+
symbol.filePath === definition.filePath &&
|
|
39
|
+
symbol.startLine <= definition.startLine &&
|
|
40
|
+
symbol.endLine >= definition.startLine);
|
|
41
|
+
return containing.map((symbol) => ({ symbol, definition }));
|
|
42
|
+
});
|
|
43
|
+
return candidates.find(({ symbol }) => targetName && symbol.name === targetName)?.symbol
|
|
44
|
+
?? candidates.find(({ symbol, definition }) => definition.name && symbol.name === definition.name)?.symbol
|
|
45
|
+
?? candidates.sort((a, b) => symbolSpan(a.symbol) - symbolSpan(b.symbol))[0]?.symbol;
|
|
46
|
+
}
|
|
47
|
+
function unresolved(edge) {
|
|
48
|
+
return { ...edge, metadata: { ...edge.metadata, resolution: "unresolved" } };
|
|
49
|
+
}
|
|
50
|
+
function symbolSpan(symbol) {
|
|
51
|
+
return symbol.endLine - symbol.startLine;
|
|
52
|
+
}
|
|
53
|
+
function stringMetadata(edge, key) {
|
|
54
|
+
const value = edge.metadata?.[key];
|
|
55
|
+
return typeof value === "string" ? value : undefined;
|
|
56
|
+
}
|
|
57
|
+
function numberMetadata(edge, key) {
|
|
58
|
+
const value = edge.metadata?.[key];
|
|
59
|
+
return typeof value === "number" ? value : undefined;
|
|
60
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface TypeScriptSourceFile {
|
|
2
|
+
filePath: string;
|
|
3
|
+
absolutePath: string;
|
|
4
|
+
content: string;
|
|
5
|
+
}
|
|
6
|
+
export interface TypeScriptDefinitionLocation {
|
|
7
|
+
filePath: string;
|
|
8
|
+
startLine: number;
|
|
9
|
+
endLine: number;
|
|
10
|
+
name?: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class TypeScriptLanguageService {
|
|
13
|
+
private readonly repoRoot;
|
|
14
|
+
private readonly service;
|
|
15
|
+
private readonly absoluteByRepoPath;
|
|
16
|
+
private readonly contentByAbsolutePath;
|
|
17
|
+
constructor(repoRoot: string, sources: TypeScriptSourceFile[]);
|
|
18
|
+
getDefinitionAt(repoFilePath: string, position: number): TypeScriptDefinitionLocation[];
|
|
19
|
+
private createHost;
|
|
20
|
+
private toDefinitionLocation;
|
|
21
|
+
}
|