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,395 @@
|
|
|
1
|
+
import { buildImpactAnalysis, impactReference } from "./impact-report.js";
|
|
2
|
+
import { isIncomingImpactEdge, isOutgoingImpactEdge, matchesImpactTarget, parseImpactTarget } from "./target-matcher.js";
|
|
3
|
+
import { normalizeUserPath } from "../utils/path.js";
|
|
4
|
+
import { coalesceFileEvents } from "../watch/file-event-coalescer.js";
|
|
5
|
+
import { buildQueryMatchProfile, scoreChunkText, scoreSymbolText } from "../retrieval/query-matching.js";
|
|
6
|
+
import { extractChangedFiles } from "./diff-files.js";
|
|
7
|
+
import { applyOwnerPathIntent } from "./owner-ranking.js";
|
|
8
|
+
export class InMemoryGraphStore {
|
|
9
|
+
repos = new Map();
|
|
10
|
+
async resetRepo(repoRoot) {
|
|
11
|
+
this.repos.set(repoRoot, {
|
|
12
|
+
indexGeneration: 0,
|
|
13
|
+
files: new Map(),
|
|
14
|
+
chunks: new Map(),
|
|
15
|
+
symbols: new Map(),
|
|
16
|
+
edges: [],
|
|
17
|
+
skippedFiles: [],
|
|
18
|
+
dirtyFiles: new Map(),
|
|
19
|
+
burstMode: false,
|
|
20
|
+
droppedEvents: 0
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async upsertIndex(index) {
|
|
24
|
+
const state = index.fullReindex
|
|
25
|
+
? {
|
|
26
|
+
projectId: index.projectId,
|
|
27
|
+
indexGeneration: index.indexGeneration,
|
|
28
|
+
files: new Map(),
|
|
29
|
+
chunks: new Map(),
|
|
30
|
+
symbols: new Map(),
|
|
31
|
+
edges: [],
|
|
32
|
+
skippedFiles: index.skippedFiles,
|
|
33
|
+
dirtyFiles: new Map(),
|
|
34
|
+
burstMode: false,
|
|
35
|
+
droppedEvents: 0
|
|
36
|
+
}
|
|
37
|
+
: this.ensureRepo(index.repoRoot);
|
|
38
|
+
state.projectId = index.projectId;
|
|
39
|
+
state.indexGeneration = index.indexGeneration;
|
|
40
|
+
state.skippedFiles = index.skippedFiles;
|
|
41
|
+
const refreshedOrDeleted = refreshedOrDeletedFiles(index);
|
|
42
|
+
for (const filePath of refreshedOrDeleted)
|
|
43
|
+
this.deleteFileRows(state, filePath);
|
|
44
|
+
const filesToWrite = index.fullReindex ? index.files : index.files.filter((file) => refreshedOrDeleted.has(file.path));
|
|
45
|
+
const chunksToWrite = index.fullReindex ? index.chunks : index.chunks.filter((chunk) => refreshedOrDeleted.has(chunk.filePath));
|
|
46
|
+
const symbolsToWrite = index.fullReindex ? index.symbols : index.symbols.filter((symbol) => refreshedOrDeleted.has(symbol.filePath));
|
|
47
|
+
const edgesToWrite = index.fullReindex ? index.edges : index.edges.filter((edge) => edgeFilePath(edge) && refreshedOrDeleted.has(edgeFilePath(edge)));
|
|
48
|
+
for (const file of filesToWrite)
|
|
49
|
+
state.files.set(file.path, file);
|
|
50
|
+
for (const chunk of chunksToWrite)
|
|
51
|
+
state.chunks.set(chunk.id, chunk);
|
|
52
|
+
for (const symbol of symbolsToWrite)
|
|
53
|
+
state.symbols.set(symbol.id, symbol);
|
|
54
|
+
state.edges.push(...edgesToWrite);
|
|
55
|
+
this.clearDirtyRows(state, index.affectedFiles);
|
|
56
|
+
this.repos.set(index.repoRoot, state);
|
|
57
|
+
}
|
|
58
|
+
async getIndexGeneration(repoRoot) {
|
|
59
|
+
return this.ensureRepo(repoRoot).indexGeneration;
|
|
60
|
+
}
|
|
61
|
+
async recordFileEvents(repoRoot, filePaths, options) {
|
|
62
|
+
const state = this.ensureRepo(repoRoot);
|
|
63
|
+
const projectId = state.projectId ?? "__unindexed__";
|
|
64
|
+
const coalesced = coalesceFileEvents(repoRoot, filePaths, options);
|
|
65
|
+
for (const filePath of coalesced.dirtyFiles) {
|
|
66
|
+
const existing = state.dirtyFiles.get(filePath);
|
|
67
|
+
const now = coalesced.lastEventAtMs;
|
|
68
|
+
state.dirtyFiles.set(filePath, {
|
|
69
|
+
projectId,
|
|
70
|
+
filePath,
|
|
71
|
+
status: "pending",
|
|
72
|
+
reason: coalesced.burstMode ? "watcher burst event" : "watcher file event",
|
|
73
|
+
firstSeenAtMs: existing?.firstSeenAtMs ?? now,
|
|
74
|
+
lastSeenAtMs: now,
|
|
75
|
+
eventCount: (existing?.eventCount ?? 0) + (coalesced.eventCountByFile.get(filePath) ?? 1)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
state.burstMode = state.burstMode || coalesced.burstMode;
|
|
79
|
+
state.droppedEvents += coalesced.droppedEvents;
|
|
80
|
+
state.lastEventAtMs = coalesced.lastEventAtMs;
|
|
81
|
+
state.watcherUpdatedAtMs = coalesced.lastEventAtMs;
|
|
82
|
+
return watcherStateFromMemory(projectId, state);
|
|
83
|
+
}
|
|
84
|
+
async getWatcherState(repoRoot) {
|
|
85
|
+
const state = this.ensureRepo(repoRoot);
|
|
86
|
+
return watcherStateFromMemory(state.projectId ?? "__unindexed__", state);
|
|
87
|
+
}
|
|
88
|
+
async markDirtyFilesIndexing(repoRoot, filePaths) {
|
|
89
|
+
const state = this.ensureRepo(repoRoot);
|
|
90
|
+
const now = Date.now();
|
|
91
|
+
for (const filePath of filePaths) {
|
|
92
|
+
const existing = state.dirtyFiles.get(filePath);
|
|
93
|
+
if (!existing)
|
|
94
|
+
continue;
|
|
95
|
+
state.dirtyFiles.set(filePath, {
|
|
96
|
+
...existing,
|
|
97
|
+
status: "indexing",
|
|
98
|
+
reason: "background batch indexing",
|
|
99
|
+
lastSeenAtMs: now
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
state.watcherUpdatedAtMs = now;
|
|
103
|
+
return watcherStateFromMemory(state.projectId ?? "__unindexed__", state);
|
|
104
|
+
}
|
|
105
|
+
async markDirtyFilesDeadLetter(repoRoot, filePaths, reason) {
|
|
106
|
+
const state = this.ensureRepo(repoRoot);
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
for (const filePath of filePaths) {
|
|
109
|
+
const existing = state.dirtyFiles.get(filePath);
|
|
110
|
+
if (!existing)
|
|
111
|
+
continue;
|
|
112
|
+
state.dirtyFiles.set(filePath, {
|
|
113
|
+
...existing,
|
|
114
|
+
status: "dead_letter",
|
|
115
|
+
reason,
|
|
116
|
+
lastSeenAtMs: now
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
state.watcherUpdatedAtMs = now;
|
|
120
|
+
return watcherStateFromMemory(state.projectId ?? "__unindexed__", state);
|
|
121
|
+
}
|
|
122
|
+
async clearDirtyFiles(repoRoot, filePaths) {
|
|
123
|
+
this.clearDirtyRows(this.ensureRepo(repoRoot), filePaths);
|
|
124
|
+
}
|
|
125
|
+
async getFiles(repoRoot) {
|
|
126
|
+
return [...this.ensureRepo(repoRoot).files.values()];
|
|
127
|
+
}
|
|
128
|
+
async getChunks(repoRoot) {
|
|
129
|
+
return [...this.ensureRepo(repoRoot).chunks.values()];
|
|
130
|
+
}
|
|
131
|
+
async getSkippedFiles(repoRoot) {
|
|
132
|
+
return [...this.ensureRepo(repoRoot).skippedFiles];
|
|
133
|
+
}
|
|
134
|
+
async getSymbols(repoRoot) {
|
|
135
|
+
return [...this.ensureRepo(repoRoot).symbols.values()];
|
|
136
|
+
}
|
|
137
|
+
async getEdges(repoRoot, kind) {
|
|
138
|
+
const edges = this.ensureRepo(repoRoot).edges;
|
|
139
|
+
return kind ? edges.filter((edge) => edge.kind === kind) : [...edges];
|
|
140
|
+
}
|
|
141
|
+
async findSymbol(repoRoot, name) {
|
|
142
|
+
const needle = name.toLowerCase();
|
|
143
|
+
return [...this.ensureRepo(repoRoot).symbols.values()]
|
|
144
|
+
.filter((symbol) => symbol.name.toLowerCase().includes(needle))
|
|
145
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
146
|
+
}
|
|
147
|
+
async explainFile(repoRoot, filePath) {
|
|
148
|
+
const normalized = normalizeUserPath(filePath);
|
|
149
|
+
const state = this.ensureRepo(repoRoot);
|
|
150
|
+
return {
|
|
151
|
+
file: state.files.get(normalized),
|
|
152
|
+
chunks: [...state.chunks.values()].filter((chunk) => chunk.filePath === normalized),
|
|
153
|
+
symbols: [...state.symbols.values()].filter((symbol) => symbol.filePath === normalized)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
async searchText(query) {
|
|
157
|
+
const repoRoot = requireRepoRoot(query.repoRoot);
|
|
158
|
+
const state = this.ensureRepo(repoRoot);
|
|
159
|
+
const scopedSymbols = query.projectId ? [...state.symbols.values()].filter((symbol) => symbol.projectId === query.projectId) : [...state.symbols.values()];
|
|
160
|
+
const profile = buildQueryMatchProfile(query.query, scopedSymbols);
|
|
161
|
+
if (profile.queryTerms.length === 0)
|
|
162
|
+
return [];
|
|
163
|
+
const limit = query.limit ?? 20;
|
|
164
|
+
const hits = [];
|
|
165
|
+
for (const chunk of state.chunks.values()) {
|
|
166
|
+
if (query.projectId && chunk.projectId !== query.projectId)
|
|
167
|
+
continue;
|
|
168
|
+
const match = scoreChunkText(chunk, profile);
|
|
169
|
+
if (!match)
|
|
170
|
+
continue;
|
|
171
|
+
hits.push({
|
|
172
|
+
chunk,
|
|
173
|
+
score: match.score,
|
|
174
|
+
source: "keyword",
|
|
175
|
+
reason: match.reason
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return hits.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
179
|
+
}
|
|
180
|
+
async findOwner(repoRoot, query, limit = 5) {
|
|
181
|
+
const state = this.ensureRepo(repoRoot);
|
|
182
|
+
const profile = buildQueryMatchProfile(query, [...state.symbols.values()]);
|
|
183
|
+
const candidates = new Map();
|
|
184
|
+
for (const hit of await this.searchText({ repoRoot, query, limit: limit * 4 })) {
|
|
185
|
+
const existing = candidates.get(hit.chunk.filePath) ?? {
|
|
186
|
+
filePath: hit.chunk.filePath,
|
|
187
|
+
score: 0,
|
|
188
|
+
reasons: [],
|
|
189
|
+
symbols: []
|
|
190
|
+
};
|
|
191
|
+
existing.score += hit.score;
|
|
192
|
+
existing.reasons.push(hit.reason);
|
|
193
|
+
candidates.set(hit.chunk.filePath, existing);
|
|
194
|
+
}
|
|
195
|
+
for (const symbol of state.symbols.values()) {
|
|
196
|
+
const match = scoreSymbolText(symbol, profile);
|
|
197
|
+
if (!match)
|
|
198
|
+
continue;
|
|
199
|
+
const existing = candidates.get(symbol.filePath) ?? {
|
|
200
|
+
filePath: symbol.filePath,
|
|
201
|
+
score: 0,
|
|
202
|
+
reasons: [],
|
|
203
|
+
symbols: []
|
|
204
|
+
};
|
|
205
|
+
existing.score += 1 + match.score;
|
|
206
|
+
existing.reasons.push(match.reason);
|
|
207
|
+
existing.symbols.push(symbol);
|
|
208
|
+
candidates.set(symbol.filePath, existing);
|
|
209
|
+
}
|
|
210
|
+
const ranked = applyOwnerPathIntent([...candidates.values()]
|
|
211
|
+
.map((candidate) => ({
|
|
212
|
+
...candidate,
|
|
213
|
+
reasons: [...new Set(candidate.reasons)],
|
|
214
|
+
symbols: uniqueSymbols(candidate.symbols)
|
|
215
|
+
})), query);
|
|
216
|
+
return ranked
|
|
217
|
+
.sort((a, b) => b.score - a.score)
|
|
218
|
+
.slice(0, limit);
|
|
219
|
+
}
|
|
220
|
+
async impactAnalysis(repoRoot, target) {
|
|
221
|
+
const parsedTarget = parseImpactTarget(target);
|
|
222
|
+
const state = this.ensureRepo(repoRoot);
|
|
223
|
+
const matchedSymbols = [...state.symbols.values()].filter((symbol) => matchesImpactTarget(symbol, parsedTarget));
|
|
224
|
+
const matchedIds = new Set(matchedSymbols.map((symbol) => symbol.id));
|
|
225
|
+
const incomingEdges = state.edges.filter((edge) => isIncomingImpactEdge(edge, matchedIds, parsedTarget));
|
|
226
|
+
const outgoingEdges = state.edges.filter((edge) => isOutgoingImpactEdge(edge, matchedIds, parsedTarget));
|
|
227
|
+
return buildImpactAnalysis({
|
|
228
|
+
target,
|
|
229
|
+
matchedSymbols,
|
|
230
|
+
incomingEdges,
|
|
231
|
+
outgoingEdges,
|
|
232
|
+
symbols: [...state.symbols.values()]
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
async relatedTests(repoRoot, target) {
|
|
236
|
+
const state = this.ensureRepo(repoRoot);
|
|
237
|
+
const normalized = normalizeUserPath(target);
|
|
238
|
+
const basename = normalized.split("/").pop()?.replace(/\.[^.]+$/, "") ?? normalized;
|
|
239
|
+
const matchedIds = new Set([...state.symbols.values()]
|
|
240
|
+
.filter((symbol) => matchesTarget(symbol, normalized, target))
|
|
241
|
+
.map((symbol) => symbol.id));
|
|
242
|
+
const graphTestsByPath = new Map();
|
|
243
|
+
const references = [];
|
|
244
|
+
for (const edge of state.edges) {
|
|
245
|
+
if (edge.kind !== "tested_by")
|
|
246
|
+
continue;
|
|
247
|
+
const sourceFile = typeof edge.metadata?.sourceFile === "string" ? edge.metadata.sourceFile : undefined;
|
|
248
|
+
if (!matchedIds.has(edge.sourceId) && sourceFile !== normalized)
|
|
249
|
+
continue;
|
|
250
|
+
const targetSymbol = state.symbols.get(edge.targetId);
|
|
251
|
+
if (!targetSymbol || !isTestFile(targetSymbol.filePath))
|
|
252
|
+
continue;
|
|
253
|
+
const file = state.files.get(targetSymbol.filePath);
|
|
254
|
+
if (file)
|
|
255
|
+
graphTestsByPath.set(file.path, file);
|
|
256
|
+
references.push(impactReference(edge, state.symbols));
|
|
257
|
+
}
|
|
258
|
+
const testsByPath = graphTestsByPath.size > 0 ? graphTestsByPath : filenameTestMatches(state.files, basename, normalized, target);
|
|
259
|
+
const tests = [...testsByPath.values()].sort((a, b) => a.path.localeCompare(b.path));
|
|
260
|
+
return {
|
|
261
|
+
target,
|
|
262
|
+
tests,
|
|
263
|
+
references,
|
|
264
|
+
missingLikelyTests: tests.length === 0 ? [`No indexed test file matched ${basename}.`] : []
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
async traceFlow(repoRoot, entry, maxSteps = 20) {
|
|
268
|
+
const state = this.ensureRepo(repoRoot);
|
|
269
|
+
const starts = await this.findSymbol(repoRoot, entry);
|
|
270
|
+
const startIds = new Set(starts.map((symbol) => symbol.id));
|
|
271
|
+
const steps = state.edges
|
|
272
|
+
.filter((edge) => isTraceEdge(edge.kind) && (startIds.has(edge.sourceId) || String(edge.metadata?.sourceFile ?? "").toLowerCase().includes(entry.toLowerCase())))
|
|
273
|
+
.slice(0, maxSteps)
|
|
274
|
+
.map((edge) => {
|
|
275
|
+
const source = state.symbols.get(edge.sourceId);
|
|
276
|
+
return {
|
|
277
|
+
filePath: source?.filePath ?? String(edge.metadata?.sourceFile ?? "unknown"),
|
|
278
|
+
symbolName: source?.name ?? "unknown",
|
|
279
|
+
kind: edge.kind,
|
|
280
|
+
targetName: typeof edge.metadata?.targetName === "string" ? edge.metadata.targetName : undefined,
|
|
281
|
+
targetFile: typeof edge.metadata?.targetFile === "string" ? edge.metadata.targetFile : undefined,
|
|
282
|
+
line: typeof edge.metadata?.line === "number" ? edge.metadata.line : undefined
|
|
283
|
+
};
|
|
284
|
+
});
|
|
285
|
+
return { entry, steps, truncated: steps.length === maxSteps };
|
|
286
|
+
}
|
|
287
|
+
async reviewDiff(repoRoot, diff, changedFiles = []) {
|
|
288
|
+
const files = changedFiles.length > 0 ? changedFiles.map(normalizeUserPath) : extractChangedFiles(diff ?? "");
|
|
289
|
+
const tests = new Set();
|
|
290
|
+
const findings = [];
|
|
291
|
+
let riskScore = 0;
|
|
292
|
+
for (const file of files) {
|
|
293
|
+
const related = await this.relatedTests(repoRoot, file);
|
|
294
|
+
for (const test of related.tests)
|
|
295
|
+
tests.add(test.path);
|
|
296
|
+
if (related.tests.length === 0 && !isTestFile(file))
|
|
297
|
+
findings.push(`No directly related test file found for ${file}.`);
|
|
298
|
+
const impact = await this.impactAnalysis(repoRoot, file);
|
|
299
|
+
riskScore += impact.impactedFiles.length;
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
changedFiles: files,
|
|
303
|
+
relatedTests: [...tests].sort(),
|
|
304
|
+
riskLevel: riskScore > 12 ? "high" : riskScore > 4 ? "medium" : "low",
|
|
305
|
+
findings
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
ensureRepo(repoRoot) {
|
|
309
|
+
let state = this.repos.get(repoRoot);
|
|
310
|
+
if (!state) {
|
|
311
|
+
state = { indexGeneration: 0, files: new Map(), chunks: new Map(), symbols: new Map(), edges: [], skippedFiles: [], dirtyFiles: new Map(), burstMode: false, droppedEvents: 0 };
|
|
312
|
+
this.repos.set(repoRoot, state);
|
|
313
|
+
}
|
|
314
|
+
return state;
|
|
315
|
+
}
|
|
316
|
+
clearDirtyRows(state, filePaths) {
|
|
317
|
+
if (!filePaths) {
|
|
318
|
+
state.dirtyFiles.clear();
|
|
319
|
+
state.burstMode = false;
|
|
320
|
+
state.droppedEvents = 0;
|
|
321
|
+
state.watcherUpdatedAtMs = Date.now();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
for (const filePath of filePaths)
|
|
325
|
+
state.dirtyFiles.delete(filePath);
|
|
326
|
+
if (state.dirtyFiles.size === 0)
|
|
327
|
+
state.burstMode = false;
|
|
328
|
+
state.watcherUpdatedAtMs = Date.now();
|
|
329
|
+
}
|
|
330
|
+
deleteFileRows(state, filePath) {
|
|
331
|
+
state.files.delete(filePath);
|
|
332
|
+
for (const [chunkId, chunk] of state.chunks.entries()) {
|
|
333
|
+
if (chunk.filePath === filePath)
|
|
334
|
+
state.chunks.delete(chunkId);
|
|
335
|
+
}
|
|
336
|
+
for (const [symbolId, symbol] of state.symbols.entries()) {
|
|
337
|
+
if (symbol.filePath === filePath)
|
|
338
|
+
state.symbols.delete(symbolId);
|
|
339
|
+
}
|
|
340
|
+
state.edges = state.edges.filter((edge) => edgeFilePath(edge) !== filePath);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function requireRepoRoot(repoRoot) {
|
|
344
|
+
if (!repoRoot)
|
|
345
|
+
throw new Error("Internal error: graph search requires a resolved repoRoot.");
|
|
346
|
+
return repoRoot;
|
|
347
|
+
}
|
|
348
|
+
function uniqueSymbols(symbols) {
|
|
349
|
+
return [...new Map(symbols.map((symbol) => [symbol.id, symbol])).values()];
|
|
350
|
+
}
|
|
351
|
+
function isTestFile(filePath) {
|
|
352
|
+
return /(^|\/)(__tests__|tests?)(\/|$)|\.(test|spec)\.[jt]sx?$/.test(filePath);
|
|
353
|
+
}
|
|
354
|
+
function filenameTestMatches(files, basename, normalized, target) {
|
|
355
|
+
const tests = new Map();
|
|
356
|
+
for (const file of files.values()) {
|
|
357
|
+
if (isTestFile(file.path) && (file.path.toLowerCase().includes(basename.toLowerCase()) || normalized === target)) {
|
|
358
|
+
tests.set(file.path, file);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return tests;
|
|
362
|
+
}
|
|
363
|
+
function edgeFilePath(edge) {
|
|
364
|
+
return typeof edge.metadata?.sourceFile === "string" ? edge.metadata.sourceFile : undefined;
|
|
365
|
+
}
|
|
366
|
+
function refreshedOrDeletedFiles(index) {
|
|
367
|
+
return new Set(index.fullReindex ? index.files.map((file) => file.path) : [...(index.refreshedFiles ?? index.changedFiles), ...index.deletedFiles]);
|
|
368
|
+
}
|
|
369
|
+
function isTraceEdge(kind) {
|
|
370
|
+
return kind === "calls"
|
|
371
|
+
|| kind === "calls_api"
|
|
372
|
+
|| kind === "routes_to"
|
|
373
|
+
|| kind === "handles_webhook"
|
|
374
|
+
|| kind === "handles_event"
|
|
375
|
+
|| kind === "tested_by"
|
|
376
|
+
|| kind === "uses_middleware"
|
|
377
|
+
|| kind === "reads_from"
|
|
378
|
+
|| kind === "writes_to";
|
|
379
|
+
}
|
|
380
|
+
function matchesTarget(symbol, normalized, target) {
|
|
381
|
+
return matchesImpactTarget(symbol, parseImpactTarget(target || normalized));
|
|
382
|
+
}
|
|
383
|
+
function watcherStateFromMemory(projectId, state) {
|
|
384
|
+
const dirtyFiles = [...state.dirtyFiles.values()].sort((a, b) => a.filePath.localeCompare(b.filePath));
|
|
385
|
+
return {
|
|
386
|
+
projectId,
|
|
387
|
+
dirtyFiles,
|
|
388
|
+
pendingFiles: dirtyFiles.filter((file) => file.status === "pending").map((file) => file.filePath),
|
|
389
|
+
indexingFiles: dirtyFiles.filter((file) => file.status === "indexing").map((file) => file.filePath),
|
|
390
|
+
burstMode: state.burstMode,
|
|
391
|
+
droppedEvents: state.droppedEvents,
|
|
392
|
+
lastEventAtMs: state.lastEventAtMs,
|
|
393
|
+
updatedAtMs: state.watcherUpdatedAtMs
|
|
394
|
+
};
|
|
395
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { isExplicitTestQuery, isTestPath } from "../retrieval/path-classification.js";
|
|
2
|
+
const DEFAULT_TEST_OWNER_MULTIPLIER = 0.55;
|
|
3
|
+
const TEST_OWNER_CEILING_RATIO = 0.95;
|
|
4
|
+
const EXPLICIT_TEST_OWNER_BOOST = 1;
|
|
5
|
+
export function applyOwnerPathIntent(candidates, query) {
|
|
6
|
+
const explicitTestQuery = isExplicitTestQuery(query);
|
|
7
|
+
const normalizedQuery = query.replaceAll("\\", "/").toLowerCase();
|
|
8
|
+
const implementationScores = candidates
|
|
9
|
+
.filter((candidate) => !isTestPath(candidate.filePath))
|
|
10
|
+
.map((candidate) => candidate.score);
|
|
11
|
+
const strongestImplementationScore = implementationScores.length > 0
|
|
12
|
+
? Math.max(...implementationScores)
|
|
13
|
+
: undefined;
|
|
14
|
+
return candidates.map((candidate) => {
|
|
15
|
+
if (!isTestPath(candidate.filePath))
|
|
16
|
+
return candidate;
|
|
17
|
+
if (explicitTestQuery || queryNamesPath(normalizedQuery, candidate.filePath)) {
|
|
18
|
+
return {
|
|
19
|
+
...candidate,
|
|
20
|
+
score: candidate.score + EXPLICIT_TEST_OWNER_BOOST,
|
|
21
|
+
reasons: [...candidate.reasons, `test relevance boost (+${EXPLICIT_TEST_OWNER_BOOST.toFixed(2)}): explicit test query`]
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (strongestImplementationScore === undefined)
|
|
25
|
+
return candidate;
|
|
26
|
+
const adjustedScore = Math.min(candidate.score * DEFAULT_TEST_OWNER_MULTIPLIER, strongestImplementationScore * TEST_OWNER_CEILING_RATIO);
|
|
27
|
+
return {
|
|
28
|
+
...candidate,
|
|
29
|
+
score: adjustedScore,
|
|
30
|
+
reasons: [...candidate.reasons, `test default demotion (${formatScoreDelta(adjustedScore - candidate.score)}): owner query is implementation-oriented`]
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function formatScoreDelta(delta) {
|
|
35
|
+
return delta.toFixed(2);
|
|
36
|
+
}
|
|
37
|
+
function queryNamesPath(normalizedQuery, filePath) {
|
|
38
|
+
const normalizedPath = filePath.replaceAll("\\", "/").toLowerCase();
|
|
39
|
+
const basename = normalizedPath.split("/").pop();
|
|
40
|
+
return normalizedQuery.includes(normalizedPath) || Boolean(basename && normalizedQuery.includes(basename));
|
|
41
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { GraphStore } from "../core/contracts.js";
|
|
2
|
+
import type { CodeChunk, CodeFile, DiffReview, EdgeKind, GraphEdge, ImpactAnalysis, OwnerCandidate, ProjectIdentity, RelatedTests, RepoIndex, SearchHit, SearchQuery, SymbolNode, TraceFlow, WatcherEventOptions, WatcherState } from "../core/types.js";
|
|
3
|
+
export declare class SQLiteGraphStore implements GraphStore {
|
|
4
|
+
private readonly db;
|
|
5
|
+
private readonly sql;
|
|
6
|
+
constructor(dbPath: string);
|
|
7
|
+
close(): void;
|
|
8
|
+
getProjectByRoot(repoRoot: string): Promise<ProjectIdentity | undefined>;
|
|
9
|
+
listProjects(): Promise<ProjectIdentity[]>;
|
|
10
|
+
getIndexGeneration(repoRoot: string): Promise<number>;
|
|
11
|
+
recordFileEvents(repoRoot: string, filePaths: string[], options?: WatcherEventOptions): Promise<WatcherState>;
|
|
12
|
+
getWatcherState(repoRoot: string): Promise<WatcherState>;
|
|
13
|
+
markDirtyFilesIndexing(repoRoot: string, filePaths: string[]): Promise<WatcherState>;
|
|
14
|
+
markDirtyFilesDeadLetter(repoRoot: string, filePaths: string[], reason: string): Promise<WatcherState>;
|
|
15
|
+
clearDirtyFiles(repoRoot: string, filePaths?: string[]): Promise<void>;
|
|
16
|
+
resetRepo(repoRoot: string): Promise<void>;
|
|
17
|
+
upsertIndex(index: RepoIndex): Promise<void>;
|
|
18
|
+
getFiles(repoRoot: string): Promise<CodeFile[]>;
|
|
19
|
+
getChunks(repoRoot: string): Promise<CodeChunk[]>;
|
|
20
|
+
getSkippedFiles(repoRoot: string): Promise<Array<{
|
|
21
|
+
filePath: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
}>>;
|
|
24
|
+
getSymbols(repoRoot: string): Promise<SymbolNode[]>;
|
|
25
|
+
getEdges(repoRoot: string, kind?: EdgeKind): Promise<GraphEdge[]>;
|
|
26
|
+
findSymbol(repoRoot: string, name: string): Promise<SymbolNode[]>;
|
|
27
|
+
explainFile(repoRoot: string, filePath: string): Promise<{
|
|
28
|
+
file?: CodeFile;
|
|
29
|
+
chunks: CodeChunk[];
|
|
30
|
+
symbols: SymbolNode[];
|
|
31
|
+
}>;
|
|
32
|
+
searchText(query: SearchQuery): Promise<SearchHit[]>;
|
|
33
|
+
findOwner(repoRoot: string, query: string, limit?: number): Promise<OwnerCandidate[]>;
|
|
34
|
+
impactAnalysis(repoRoot: string, target: string): Promise<ImpactAnalysis>;
|
|
35
|
+
relatedTests(repoRoot: string, target: string): Promise<RelatedTests>;
|
|
36
|
+
traceFlow(repoRoot: string, entry: string, maxSteps?: number): Promise<TraceFlow>;
|
|
37
|
+
reviewDiff(repoRoot: string, diff?: string, changedFiles?: string[]): Promise<DiffReview>;
|
|
38
|
+
private migrate;
|
|
39
|
+
private ensureProjectColumns;
|
|
40
|
+
private deleteProjectRows;
|
|
41
|
+
private clearDirtyRows;
|
|
42
|
+
private watcherStateForProject;
|
|
43
|
+
private deleteFileRows;
|
|
44
|
+
private filePathsForProject;
|
|
45
|
+
private transaction;
|
|
46
|
+
private projectIdForRoot;
|
|
47
|
+
private requireProjectId;
|
|
48
|
+
private scopedProjectId;
|
|
49
|
+
private chunksForProject;
|
|
50
|
+
private symbolsForProject;
|
|
51
|
+
}
|