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,193 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { loadRuntimeConfig, redactRuntimeConfig } from "../config/runtime-config.js";
|
|
4
|
+
import { RagCodeEngine } from "../core/engine.js";
|
|
5
|
+
import { createMcpServer } from "../mcp/server.js";
|
|
6
|
+
import { listToolDefinitions } from "../mcp/tools.js";
|
|
7
|
+
import { readWatcherLiveness } from "../watch/watcher-liveness.js";
|
|
8
|
+
export async function runDoctor(options = {}) {
|
|
9
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
10
|
+
const env = options.env ?? process.env;
|
|
11
|
+
const runtime = readCheck(() => loadRuntimeConfig({ cwd, env, overrides: options.repoRoot ? { repoRoot: path.resolve(cwd, options.repoRoot) } : undefined }));
|
|
12
|
+
const sqlite = await importCheck("node:sqlite", "node:sqlite import ok");
|
|
13
|
+
const lancedb = await importCheck("@lancedb/lancedb", "@lancedb/lancedb import ok");
|
|
14
|
+
const mcpSdk = await importCheck("@modelcontextprotocol/sdk/server/mcp.js", "@modelcontextprotocol/sdk import ok");
|
|
15
|
+
const mcp = createMcpCheck();
|
|
16
|
+
const report = {
|
|
17
|
+
ok: true,
|
|
18
|
+
cwd,
|
|
19
|
+
node: checkNodeVersion(),
|
|
20
|
+
runtime: {
|
|
21
|
+
graph: runtime.ok
|
|
22
|
+
? { ok: true, message: "graph runtime config ok", config: redactRuntimeConfig(runtime.value) }
|
|
23
|
+
: { ok: false, message: runtime.message },
|
|
24
|
+
semantic: runtime.ok
|
|
25
|
+
? { ok: true, message: "semantic runtime config ok", config: redactRuntimeConfig(runtime.value) }
|
|
26
|
+
: { ok: false, message: runtime.message }
|
|
27
|
+
},
|
|
28
|
+
dependencies: {
|
|
29
|
+
sqlite,
|
|
30
|
+
lancedb,
|
|
31
|
+
mcpSdk
|
|
32
|
+
},
|
|
33
|
+
mcp
|
|
34
|
+
};
|
|
35
|
+
if (options.repoRoot) {
|
|
36
|
+
report.watcher = await buildWatcherCheck(path.resolve(cwd, options.repoRoot));
|
|
37
|
+
report.smoke = await runRepoSmoke({
|
|
38
|
+
cwd,
|
|
39
|
+
env,
|
|
40
|
+
repoRoot: options.repoRoot,
|
|
41
|
+
searchQuery: options.searchQuery ?? "export function class import"
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
report.ok = [
|
|
45
|
+
report.node,
|
|
46
|
+
report.runtime.graph,
|
|
47
|
+
report.runtime.semantic,
|
|
48
|
+
report.dependencies.sqlite,
|
|
49
|
+
report.dependencies.lancedb,
|
|
50
|
+
report.dependencies.mcpSdk,
|
|
51
|
+
report.mcp,
|
|
52
|
+
report.smoke
|
|
53
|
+
].filter((check) => Boolean(check)).every((check) => check.ok);
|
|
54
|
+
return report;
|
|
55
|
+
}
|
|
56
|
+
// Watcher liveness is informational: a repo with no running watcher is a valid (if not-auto-fresh)
|
|
57
|
+
// state, so this never flips report.ok to false. It surfaces the gap the lifecycle work closes —
|
|
58
|
+
// "indexed but nothing keeps it fresh" — so `doctor <repo>` is where a user discovers they should
|
|
59
|
+
// run `ragcode service install`.
|
|
60
|
+
async function buildWatcherCheck(repoRoot) {
|
|
61
|
+
const liveness = await readWatcherLiveness(repoRoot);
|
|
62
|
+
const message = liveness.state === "running"
|
|
63
|
+
? `Watcher running (pid ${liveness.lock?.pid}); heartbeat ${liveness.heartbeatAgeMs}ms ago.`
|
|
64
|
+
: liveness.state === "stale"
|
|
65
|
+
? `Watcher process is alive but its heartbeat is stale (${liveness.heartbeatAgeMs}ms old); it may be hung.`
|
|
66
|
+
: liveness.state === "dead"
|
|
67
|
+
? "A watcher lock/heartbeat exists but no live process backs it; it likely crashed. Re-run `ragcode service install` or `ragcode watch`."
|
|
68
|
+
: "No background watcher running. The index will not auto-refresh. Run `ragcode service install <repo>` to keep it fresh across reboots.";
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
message,
|
|
72
|
+
state: liveness.state,
|
|
73
|
+
processAlive: liveness.processAlive,
|
|
74
|
+
heartbeatFresh: liveness.heartbeatFresh,
|
|
75
|
+
heartbeatAgeMs: liveness.heartbeatAgeMs,
|
|
76
|
+
pendingFiles: liveness.heartbeat?.pendingFiles,
|
|
77
|
+
indexingFiles: liveness.heartbeat?.indexingFiles
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function checkNodeVersion() {
|
|
81
|
+
const major = Number(process.versions.node.split(".")[0]);
|
|
82
|
+
return {
|
|
83
|
+
ok: Number.isFinite(major) && major >= 24,
|
|
84
|
+
message: `Node ${process.version}; required >=24.0.0 for node:sqlite graph storage`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function readCheck(read) {
|
|
88
|
+
try {
|
|
89
|
+
return { ok: true, value: read() };
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return { ok: false, message: errorMessage(error) };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function importCheck(specifier, okMessage) {
|
|
96
|
+
try {
|
|
97
|
+
await import(specifier);
|
|
98
|
+
return { ok: true, message: okMessage };
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
return { ok: false, message: errorMessage(error) };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function createMcpCheck() {
|
|
105
|
+
const tools = listToolDefinitions();
|
|
106
|
+
try {
|
|
107
|
+
const server = createMcpServer(createNoopEngine());
|
|
108
|
+
return {
|
|
109
|
+
ok: !server.isConnected() && tools.length > 0,
|
|
110
|
+
message: `MCP server factory ok with ${tools.length} tool(s)`,
|
|
111
|
+
toolCount: tools.length,
|
|
112
|
+
tools: tools.map((tool) => tool.name)
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
message: errorMessage(error),
|
|
119
|
+
toolCount: tools.length,
|
|
120
|
+
tools: tools.map((tool) => tool.name)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
async function runRepoSmoke(options) {
|
|
125
|
+
const repoRoot = path.resolve(options.cwd, options.repoRoot);
|
|
126
|
+
let engine;
|
|
127
|
+
try {
|
|
128
|
+
const stat = await fs.stat(repoRoot).catch((error) => {
|
|
129
|
+
throw new Error(`repoRoot is not readable: ${repoRoot}. ${errorMessage(error)}`);
|
|
130
|
+
});
|
|
131
|
+
if (!stat.isDirectory()) {
|
|
132
|
+
throw new Error(`repoRoot is not a directory: ${repoRoot}`);
|
|
133
|
+
}
|
|
134
|
+
engine = new RagCodeEngine({ cwd: options.cwd, env: options.env });
|
|
135
|
+
const index = await engine.indexRepo(repoRoot);
|
|
136
|
+
const status = await engine.indexStatus(repoRoot);
|
|
137
|
+
const hits = await engine.searchCode({ repoRoot, query: options.searchQuery, limit: 5 });
|
|
138
|
+
return {
|
|
139
|
+
ok: true,
|
|
140
|
+
message: "repo smoke indexed and searched successfully",
|
|
141
|
+
repoRoot,
|
|
142
|
+
indexed: {
|
|
143
|
+
projectId: index.projectId,
|
|
144
|
+
repoRoot: index.repoRoot,
|
|
145
|
+
indexedAtMs: index.indexedAtMs,
|
|
146
|
+
files: index.files.length,
|
|
147
|
+
chunks: index.chunks.length,
|
|
148
|
+
symbols: index.symbols.length,
|
|
149
|
+
edges: index.edges.length,
|
|
150
|
+
skippedFiles: index.skippedFiles.length
|
|
151
|
+
},
|
|
152
|
+
status: {
|
|
153
|
+
fileCount: status.fileCount,
|
|
154
|
+
chunkCount: status.chunkCount,
|
|
155
|
+
symbolCount: status.symbolCount,
|
|
156
|
+
edgeCount: status.edgeCount,
|
|
157
|
+
staleFileCount: status.staleFileCount,
|
|
158
|
+
pendingFileCount: status.pendingFileCount,
|
|
159
|
+
skippedFileCount: status.skippedFileCount
|
|
160
|
+
},
|
|
161
|
+
search: {
|
|
162
|
+
query: options.searchQuery,
|
|
163
|
+
hits: hits.map((hit) => ({
|
|
164
|
+
filePath: hit.chunk.filePath,
|
|
165
|
+
startLine: hit.chunk.startLine,
|
|
166
|
+
endLine: hit.chunk.endLine,
|
|
167
|
+
score: hit.score,
|
|
168
|
+
source: hit.source,
|
|
169
|
+
reason: hit.reason
|
|
170
|
+
}))
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
message: errorMessage(error),
|
|
178
|
+
repoRoot
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
engine?.close();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function createNoopEngine() {
|
|
186
|
+
const handler = async () => ({ ok: true });
|
|
187
|
+
return new Proxy({}, {
|
|
188
|
+
get: () => handler
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
function errorMessage(error) {
|
|
192
|
+
return error instanceof Error ? error.message : String(error);
|
|
193
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type RuntimeConfig, type RuntimeConfigOverrides } from "../config/runtime-config.js";
|
|
2
|
+
export type EmbeddingFailureKind = "missing_key" | "auth_failure" | "model_not_found" | "network_failure" | "dimensions_mismatch" | "unsupported_dimensions_request" | "unknown";
|
|
3
|
+
export interface EmbeddingTestResult {
|
|
4
|
+
ok: boolean;
|
|
5
|
+
provider: RuntimeConfig["semantic"]["embeddingProvider"];
|
|
6
|
+
model?: string;
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
requestedDimensions?: number;
|
|
9
|
+
dimensions?: number;
|
|
10
|
+
latencyMs?: number;
|
|
11
|
+
failure?: {
|
|
12
|
+
kind: EmbeddingFailureKind;
|
|
13
|
+
message: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface EmbeddingTestOptions {
|
|
17
|
+
cwd?: string;
|
|
18
|
+
env?: NodeJS.ProcessEnv;
|
|
19
|
+
overrides?: RuntimeConfigOverrides;
|
|
20
|
+
fetchImpl?: typeof fetch;
|
|
21
|
+
sampleText?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function runEmbeddingTest(options?: EmbeddingTestOptions): Promise<EmbeddingTestResult>;
|
|
24
|
+
export declare function testEmbeddingForConfig(config: RuntimeConfig, options?: Pick<EmbeddingTestOptions, "fetchImpl" | "sampleText">): Promise<EmbeddingTestResult>;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { loadRuntimeConfig } from "../config/runtime-config.js";
|
|
3
|
+
import { DeterministicEmbeddingProvider } from "../semantic/deterministic-embedding.js";
|
|
4
|
+
import { OpenAICompatibleEmbeddingProvider } from "../semantic/openai-compatible-embedding.js";
|
|
5
|
+
export async function runEmbeddingTest(options = {}) {
|
|
6
|
+
const config = loadRuntimeConfig({ cwd: options.cwd, env: options.env, overrides: options.overrides });
|
|
7
|
+
return testEmbeddingForConfig(config, options);
|
|
8
|
+
}
|
|
9
|
+
// Runs one small embedding request against the effective provider and classifies failures.
|
|
10
|
+
// Never includes the API key in the result; baseUrl/model are not secrets and stay visible
|
|
11
|
+
// so doctor/configure output remains actionable.
|
|
12
|
+
export async function testEmbeddingForConfig(config, options = {}) {
|
|
13
|
+
const semantic = config.semantic;
|
|
14
|
+
const base = {
|
|
15
|
+
ok: false,
|
|
16
|
+
provider: semantic.embeddingProvider,
|
|
17
|
+
model: semantic.embeddingModel,
|
|
18
|
+
baseUrl: semantic.embeddingProvider === "openai-compatible" ? semantic.embeddingBaseUrl : undefined,
|
|
19
|
+
requestedDimensions: semantic.embeddingDimensions
|
|
20
|
+
};
|
|
21
|
+
if (semantic.embeddingProvider === "openai-compatible" && !config.embeddingApiKey) {
|
|
22
|
+
return {
|
|
23
|
+
...base,
|
|
24
|
+
failure: {
|
|
25
|
+
kind: "missing_key",
|
|
26
|
+
message: "No embedding API key configured. Set RAGCODE_EMBEDDING_API_KEY (or OPENAI_API_KEY), or run `ragcode configure`."
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const provider = semantic.embeddingProvider === "deterministic"
|
|
31
|
+
? new DeterministicEmbeddingProvider(semantic.embeddingDimensions ?? 64)
|
|
32
|
+
: new OpenAICompatibleEmbeddingProvider({
|
|
33
|
+
apiKey: config.embeddingApiKey ?? "",
|
|
34
|
+
model: semantic.embeddingModel ?? "text-embedding-3-small",
|
|
35
|
+
baseUrl: semantic.embeddingBaseUrl,
|
|
36
|
+
dimensions: semantic.embeddingDimensions,
|
|
37
|
+
requestDimensions: semantic.embeddingRequestDimensions,
|
|
38
|
+
fetch: options.fetchImpl
|
|
39
|
+
});
|
|
40
|
+
const startedAt = performance.now();
|
|
41
|
+
try {
|
|
42
|
+
const vector = await provider.embed(options.sampleText ?? "ragcode embedding connectivity test");
|
|
43
|
+
const latencyMs = Math.round(performance.now() - startedAt);
|
|
44
|
+
if (semantic.embeddingDimensions !== undefined && vector.length !== semantic.embeddingDimensions) {
|
|
45
|
+
return {
|
|
46
|
+
...base,
|
|
47
|
+
dimensions: vector.length,
|
|
48
|
+
latencyMs,
|
|
49
|
+
failure: {
|
|
50
|
+
kind: "dimensions_mismatch",
|
|
51
|
+
message: `Provider returned ${vector.length} dimensions but ${semantic.embeddingDimensions} are configured. Align embeddingDimensions or enable embeddingRequestDimensions if the provider supports it.`
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { ...base, ok: true, dimensions: vector.length, latencyMs };
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
return {
|
|
59
|
+
...base,
|
|
60
|
+
latencyMs: Math.round(performance.now() - startedAt),
|
|
61
|
+
failure: classifyEmbeddingFailure(error)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function classifyEmbeddingFailure(error) {
|
|
66
|
+
const candidate = error;
|
|
67
|
+
const message = candidate?.message ?? String(error);
|
|
68
|
+
const lowered = message.toLowerCase();
|
|
69
|
+
const causeCode = candidate?.cause?.code ?? "";
|
|
70
|
+
if (candidate?.status === 401 || candidate?.status === 403 || /unauthorized|invalid api key|incorrect api key|forbidden/.test(lowered)) {
|
|
71
|
+
return { kind: "auth_failure", message: "Authentication failed. Check the embedding API key." };
|
|
72
|
+
}
|
|
73
|
+
if ((candidate?.status === 404 && /model/.test(lowered)) || /model.*(not found|does not exist)/.test(lowered)) {
|
|
74
|
+
return { kind: "model_not_found", message: `Embedding model rejected by the provider: ${message}` };
|
|
75
|
+
}
|
|
76
|
+
if (/dimensions?.*(unsupported|not supported|invalid)|unsupported.*dimensions?|does not support.*dimensions?/.test(lowered)) {
|
|
77
|
+
return { kind: "unsupported_dimensions_request", message: `Provider rejected the dimensions request: ${message}` };
|
|
78
|
+
}
|
|
79
|
+
if (/^(ECONNREFUSED|ENOTFOUND|ETIMEDOUT|ECONNRESET|EAI_AGAIN)$/.test(causeCode) || /fetch failed|network|econnrefused|enotfound|etimedout/.test(lowered)) {
|
|
80
|
+
return { kind: "network_failure", message: `Could not reach the embedding endpoint: ${message}` };
|
|
81
|
+
}
|
|
82
|
+
return { kind: "unknown", message };
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function extractChangedFiles(diff: string): string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import parseDiff from "parse-diff";
|
|
2
|
+
import { normalizeUserPath } from "../utils/path.js";
|
|
3
|
+
export function extractChangedFiles(diff) {
|
|
4
|
+
const files = new Set();
|
|
5
|
+
for (const file of parseDiff(diff)) {
|
|
6
|
+
if (file.deleted)
|
|
7
|
+
continue;
|
|
8
|
+
const target = file.to ?? file.from;
|
|
9
|
+
if (!target || target === "/dev/null")
|
|
10
|
+
continue;
|
|
11
|
+
files.add(normalizeUserPath(target));
|
|
12
|
+
}
|
|
13
|
+
return [...files].sort();
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { GraphEdge, ImpactAnalysis, ImpactReference, SymbolNode } from "../core/types.js";
|
|
2
|
+
export interface ImpactReportInput {
|
|
3
|
+
target: string;
|
|
4
|
+
matchedSymbols: SymbolNode[];
|
|
5
|
+
incomingEdges: GraphEdge[];
|
|
6
|
+
outgoingEdges: GraphEdge[];
|
|
7
|
+
symbols: SymbolNode[];
|
|
8
|
+
}
|
|
9
|
+
export declare function buildImpactAnalysis(input: ImpactReportInput): ImpactAnalysis;
|
|
10
|
+
export declare function impactReference(edge: GraphEdge, symbolsById: Map<string, SymbolNode>): ImpactReference;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
export function buildImpactAnalysis(input) {
|
|
2
|
+
const symbolsById = new Map(input.symbols.map((symbol) => [symbol.id, symbol]));
|
|
3
|
+
const minimalPackByFile = new Map();
|
|
4
|
+
const allEdges = [...input.incomingEdges, ...input.outgoingEdges];
|
|
5
|
+
for (const symbol of input.matchedSymbols) {
|
|
6
|
+
addPackItem(minimalPackByFile, symbol.filePath, "target", `Matched target symbol ${symbol.name}.`, [symbol]);
|
|
7
|
+
}
|
|
8
|
+
for (const edge of input.incomingEdges) {
|
|
9
|
+
const source = symbolsById.get(edge.sourceId);
|
|
10
|
+
const target = symbolsById.get(edge.targetId);
|
|
11
|
+
if (source)
|
|
12
|
+
addPackItem(minimalPackByFile, source.filePath, roleForIncoming(edge.kind), `Incoming ${edge.kind} reference to target.`, [source]);
|
|
13
|
+
if (target)
|
|
14
|
+
addPackItem(minimalPackByFile, target.filePath, "target", `Target side of incoming ${edge.kind} reference.`, [target]);
|
|
15
|
+
addMetadataFiles(minimalPackByFile, edge, roleForIncoming(edge.kind), `Incoming ${edge.kind} metadata reference.`);
|
|
16
|
+
}
|
|
17
|
+
for (const edge of input.outgoingEdges) {
|
|
18
|
+
const source = symbolsById.get(edge.sourceId);
|
|
19
|
+
const target = symbolsById.get(edge.targetId);
|
|
20
|
+
if (source)
|
|
21
|
+
addPackItem(minimalPackByFile, source.filePath, "target", `Source side of outgoing ${edge.kind} reference.`, [source]);
|
|
22
|
+
if (target)
|
|
23
|
+
addPackItem(minimalPackByFile, target.filePath, roleForOutgoing(edge.kind), `Outgoing ${edge.kind} reference from target.`, [target]);
|
|
24
|
+
addMetadataFiles(minimalPackByFile, edge, roleForOutgoing(edge.kind), `Outgoing ${edge.kind} metadata reference.`);
|
|
25
|
+
}
|
|
26
|
+
const minimalPack = [...minimalPackByFile.values()].sort((a, b) => rolePriority(a.role) - rolePriority(b.role) || a.filePath.localeCompare(b.filePath));
|
|
27
|
+
const references = uniqueReferences(allEdges.map((edge) => impactReference(edge, symbolsById)));
|
|
28
|
+
const impactedFiles = minimalPack.map((item) => item.filePath);
|
|
29
|
+
const riskLevel = riskFor(minimalPack.length, references.length);
|
|
30
|
+
return {
|
|
31
|
+
target: input.target,
|
|
32
|
+
minimalPack,
|
|
33
|
+
references,
|
|
34
|
+
nextQueries: nextQueriesFor(input.target, references),
|
|
35
|
+
matchedSymbols: input.matchedSymbols,
|
|
36
|
+
impactedFiles,
|
|
37
|
+
incomingEdges: input.incomingEdges,
|
|
38
|
+
outgoingEdges: input.outgoingEdges,
|
|
39
|
+
riskLevel
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function impactReference(edge, symbolsById) {
|
|
43
|
+
const source = symbolsById.get(edge.sourceId);
|
|
44
|
+
const target = symbolsById.get(edge.targetId);
|
|
45
|
+
return {
|
|
46
|
+
edge: edge.kind,
|
|
47
|
+
sourceFile: source?.filePath ?? stringMetadata(edge, "sourceFile"),
|
|
48
|
+
targetFile: target?.filePath ?? stringMetadata(edge, "targetFile"),
|
|
49
|
+
sourceSymbol: source?.name,
|
|
50
|
+
targetSymbol: target?.name,
|
|
51
|
+
targetName: stringMetadata(edge, "targetName"),
|
|
52
|
+
reason: reasonForEdge(edge.kind),
|
|
53
|
+
confidence: confidenceForEdge(edge)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function addMetadataFiles(items, edge, role, reason) {
|
|
57
|
+
const sourceFile = stringMetadata(edge, "sourceFile");
|
|
58
|
+
const targetFile = stringMetadata(edge, "targetFile");
|
|
59
|
+
if (sourceFile)
|
|
60
|
+
addPackItem(items, sourceFile, role === "test" ? "target" : role, reason, []);
|
|
61
|
+
if (targetFile)
|
|
62
|
+
addPackItem(items, targetFile, role, reason, []);
|
|
63
|
+
}
|
|
64
|
+
function addPackItem(items, filePath, role, reason, symbols) {
|
|
65
|
+
const existing = items.get(filePath);
|
|
66
|
+
if (!existing) {
|
|
67
|
+
items.set(filePath, {
|
|
68
|
+
filePath,
|
|
69
|
+
role,
|
|
70
|
+
reason,
|
|
71
|
+
symbols: symbols.map(symbolSummary)
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (rolePriority(role) < rolePriority(existing.role))
|
|
76
|
+
existing.role = role;
|
|
77
|
+
if (existing.reason.length > reason.length)
|
|
78
|
+
existing.reason = reason;
|
|
79
|
+
const byName = new Map(existing.symbols.map((symbol) => [`${symbol.kind}:${symbol.name}:${symbol.startLine}`, symbol]));
|
|
80
|
+
for (const symbol of symbols.map(symbolSummary))
|
|
81
|
+
byName.set(`${symbol.kind}:${symbol.name}:${symbol.startLine}`, symbol);
|
|
82
|
+
existing.symbols = [...byName.values()];
|
|
83
|
+
}
|
|
84
|
+
function symbolSummary(symbol) {
|
|
85
|
+
return {
|
|
86
|
+
name: symbol.name,
|
|
87
|
+
kind: symbol.kind,
|
|
88
|
+
startLine: symbol.startLine,
|
|
89
|
+
endLine: symbol.endLine
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function roleForIncoming(kind) {
|
|
93
|
+
if (kind === "calls_api" || kind === "routes_to")
|
|
94
|
+
return "route";
|
|
95
|
+
if (kind === "tested_by")
|
|
96
|
+
return "test";
|
|
97
|
+
if (kind === "uses_middleware")
|
|
98
|
+
return "middleware";
|
|
99
|
+
return "caller";
|
|
100
|
+
}
|
|
101
|
+
function roleForOutgoing(kind) {
|
|
102
|
+
if (kind === "tested_by")
|
|
103
|
+
return "test";
|
|
104
|
+
if (kind === "uses_middleware")
|
|
105
|
+
return "middleware";
|
|
106
|
+
if (kind === "reads_from" || kind === "writes_to")
|
|
107
|
+
return "resource_owner";
|
|
108
|
+
if (kind === "handles_event")
|
|
109
|
+
return "event_owner";
|
|
110
|
+
if (kind === "calls_api" || kind === "routes_to")
|
|
111
|
+
return "route";
|
|
112
|
+
return "callee";
|
|
113
|
+
}
|
|
114
|
+
function rolePriority(role) {
|
|
115
|
+
if (role === "target")
|
|
116
|
+
return 0;
|
|
117
|
+
if (role === "test")
|
|
118
|
+
return 1;
|
|
119
|
+
if (role === "caller" || role === "callee" || role === "route")
|
|
120
|
+
return 2;
|
|
121
|
+
return 3;
|
|
122
|
+
}
|
|
123
|
+
function reasonForEdge(kind) {
|
|
124
|
+
if (kind === "tested_by")
|
|
125
|
+
return "Test coverage edge from indexed graph.";
|
|
126
|
+
if (kind === "routes_to")
|
|
127
|
+
return "Route-to-service edge from indexed topology.";
|
|
128
|
+
if (kind === "calls_api")
|
|
129
|
+
return "Client-to-API edge from indexed topology.";
|
|
130
|
+
if (kind === "uses_middleware")
|
|
131
|
+
return "Middleware usage edge from indexed topology.";
|
|
132
|
+
if (kind === "reads_from")
|
|
133
|
+
return "Resource read edge from indexed topology.";
|
|
134
|
+
if (kind === "writes_to")
|
|
135
|
+
return "Resource write edge from indexed topology.";
|
|
136
|
+
if (kind === "handles_event")
|
|
137
|
+
return "Event handler edge from indexed topology.";
|
|
138
|
+
return `Graph ${kind} edge from indexed structure.`;
|
|
139
|
+
}
|
|
140
|
+
function confidenceForEdge(edge) {
|
|
141
|
+
if (edge.kind === "tested_by")
|
|
142
|
+
return "high";
|
|
143
|
+
if (edge.metadata?.resolution === "resolved" || edge.metadata?.resolution === "resolved_lsp")
|
|
144
|
+
return "high";
|
|
145
|
+
if (edge.metadata?.resolution === "framework_static" || edge.metadata?.resolution === "resource_static" || edge.metadata?.resolution === "event_static")
|
|
146
|
+
return "high";
|
|
147
|
+
if (edge.metadata?.targetName)
|
|
148
|
+
return "medium";
|
|
149
|
+
return "low";
|
|
150
|
+
}
|
|
151
|
+
function nextQueriesFor(target, references) {
|
|
152
|
+
const queries = [`get_context ${target}`, `trace_flow ${target}`];
|
|
153
|
+
if (references.some((reference) => reference.edge === "tested_by"))
|
|
154
|
+
queries.push(`related_tests ${target}`);
|
|
155
|
+
if (references.some((reference) => reference.edge === "reads_from" || reference.edge === "writes_to"))
|
|
156
|
+
queries.push(`topology_map ${target} data flow`);
|
|
157
|
+
return [...new Set(queries)];
|
|
158
|
+
}
|
|
159
|
+
function riskFor(fileCount, referenceCount) {
|
|
160
|
+
const score = fileCount + referenceCount;
|
|
161
|
+
return score > 12 ? "high" : score > 4 ? "medium" : "low";
|
|
162
|
+
}
|
|
163
|
+
function uniqueReferences(references) {
|
|
164
|
+
const byKey = new Map();
|
|
165
|
+
for (const reference of references) {
|
|
166
|
+
byKey.set([reference.edge, reference.sourceFile, reference.targetFile, reference.targetName].join("::"), reference);
|
|
167
|
+
}
|
|
168
|
+
return [...byKey.values()];
|
|
169
|
+
}
|
|
170
|
+
function stringMetadata(edge, key) {
|
|
171
|
+
const value = edge.metadata?.[key];
|
|
172
|
+
return typeof value === "string" ? value : undefined;
|
|
173
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { GraphStore } from "../core/contracts.js";
|
|
2
|
+
import type { CodeChunk, CodeFile, DiffReview, EdgeKind, GraphEdge, ImpactAnalysis, OwnerCandidate, RelatedTests, RepoIndex, SearchHit, SearchQuery, SymbolNode, TraceFlow, WatcherEventOptions, WatcherState } from "../core/types.js";
|
|
3
|
+
export declare class InMemoryGraphStore implements GraphStore {
|
|
4
|
+
private readonly repos;
|
|
5
|
+
resetRepo(repoRoot: string): Promise<void>;
|
|
6
|
+
upsertIndex(index: RepoIndex): Promise<void>;
|
|
7
|
+
getIndexGeneration(repoRoot: string): Promise<number>;
|
|
8
|
+
recordFileEvents(repoRoot: string, filePaths: string[], options?: WatcherEventOptions): Promise<WatcherState>;
|
|
9
|
+
getWatcherState(repoRoot: string): Promise<WatcherState>;
|
|
10
|
+
markDirtyFilesIndexing(repoRoot: string, filePaths: string[]): Promise<WatcherState>;
|
|
11
|
+
markDirtyFilesDeadLetter(repoRoot: string, filePaths: string[], reason: string): Promise<WatcherState>;
|
|
12
|
+
clearDirtyFiles(repoRoot: string, filePaths?: string[]): Promise<void>;
|
|
13
|
+
getFiles(repoRoot: string): Promise<CodeFile[]>;
|
|
14
|
+
getChunks(repoRoot: string): Promise<CodeChunk[]>;
|
|
15
|
+
getSkippedFiles(repoRoot: string): Promise<Array<{
|
|
16
|
+
filePath: string;
|
|
17
|
+
reason: string;
|
|
18
|
+
}>>;
|
|
19
|
+
getSymbols(repoRoot: string): Promise<SymbolNode[]>;
|
|
20
|
+
getEdges(repoRoot: string, kind?: EdgeKind): Promise<GraphEdge[]>;
|
|
21
|
+
findSymbol(repoRoot: string, name: string): Promise<SymbolNode[]>;
|
|
22
|
+
explainFile(repoRoot: string, filePath: string): Promise<{
|
|
23
|
+
file?: CodeFile;
|
|
24
|
+
chunks: CodeChunk[];
|
|
25
|
+
symbols: SymbolNode[];
|
|
26
|
+
}>;
|
|
27
|
+
searchText(query: SearchQuery): Promise<SearchHit[]>;
|
|
28
|
+
findOwner(repoRoot: string, query: string, limit?: number): Promise<OwnerCandidate[]>;
|
|
29
|
+
impactAnalysis(repoRoot: string, target: string): Promise<ImpactAnalysis>;
|
|
30
|
+
relatedTests(repoRoot: string, target: string): Promise<RelatedTests>;
|
|
31
|
+
traceFlow(repoRoot: string, entry: string, maxSteps?: number): Promise<TraceFlow>;
|
|
32
|
+
reviewDiff(repoRoot: string, diff?: string, changedFiles?: string[]): Promise<DiffReview>;
|
|
33
|
+
private ensureRepo;
|
|
34
|
+
private clearDirtyRows;
|
|
35
|
+
private deleteFileRows;
|
|
36
|
+
}
|