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.
Files changed (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +366 -0
  3. package/README.zh-CN.md +363 -0
  4. package/dist/src/cli/configure/app.d.ts +6 -0
  5. package/dist/src/cli/configure/app.js +81 -0
  6. package/dist/src/cli/configure/run.d.ts +5 -0
  7. package/dist/src/cli/configure/run.js +85 -0
  8. package/dist/src/cli/configure/state.d.ts +42 -0
  9. package/dist/src/cli/configure/state.js +174 -0
  10. package/dist/src/cli/configure.d.ts +31 -0
  11. package/dist/src/cli/configure.js +101 -0
  12. package/dist/src/cli/index.d.ts +2 -0
  13. package/dist/src/cli/index.js +503 -0
  14. package/dist/src/cli/tui/index-progress.d.ts +12 -0
  15. package/dist/src/cli/tui/index-progress.js +49 -0
  16. package/dist/src/cli/tui/watch-status.d.ts +10 -0
  17. package/dist/src/cli/tui/watch-status.js +27 -0
  18. package/dist/src/cli/update.d.ts +18 -0
  19. package/dist/src/cli/update.js +111 -0
  20. package/dist/src/config/dotenv.d.ts +1 -0
  21. package/dist/src/config/dotenv.js +14 -0
  22. package/dist/src/config/graph-runtime.d.ts +13 -0
  23. package/dist/src/config/graph-runtime.js +29 -0
  24. package/dist/src/config/runtime-config.d.ts +87 -0
  25. package/dist/src/config/runtime-config.js +215 -0
  26. package/dist/src/config/semantic-runtime.d.ts +24 -0
  27. package/dist/src/config/semantic-runtime.js +89 -0
  28. package/dist/src/context/context-builder.d.ts +20 -0
  29. package/dist/src/context/context-builder.js +277 -0
  30. package/dist/src/context/expansion-policy.d.ts +6 -0
  31. package/dist/src/context/expansion-policy.js +49 -0
  32. package/dist/src/context/skeletonizer.d.ts +2 -0
  33. package/dist/src/context/skeletonizer.js +79 -0
  34. package/dist/src/context/snippet-renderer.d.ts +2 -0
  35. package/dist/src/context/snippet-renderer.js +67 -0
  36. package/dist/src/core/contracts.d.ts +74 -0
  37. package/dist/src/core/contracts.js +1 -0
  38. package/dist/src/core/engine.d.ts +64 -0
  39. package/dist/src/core/engine.js +442 -0
  40. package/dist/src/core/types.d.ts +490 -0
  41. package/dist/src/core/types.js +1 -0
  42. package/dist/src/diagnostics/doctor.d.ts +66 -0
  43. package/dist/src/diagnostics/doctor.js +193 -0
  44. package/dist/src/diagnostics/embedding-test.d.ts +24 -0
  45. package/dist/src/diagnostics/embedding-test.js +83 -0
  46. package/dist/src/graph/diff-files.d.ts +1 -0
  47. package/dist/src/graph/diff-files.js +14 -0
  48. package/dist/src/graph/impact-report.d.ts +10 -0
  49. package/dist/src/graph/impact-report.js +173 -0
  50. package/dist/src/graph/in-memory-graph-store.d.ts +36 -0
  51. package/dist/src/graph/in-memory-graph-store.js +395 -0
  52. package/dist/src/graph/owner-ranking.d.ts +2 -0
  53. package/dist/src/graph/owner-ranking.js +41 -0
  54. package/dist/src/graph/sqlite-graph-store.d.ts +51 -0
  55. package/dist/src/graph/sqlite-graph-store.js +724 -0
  56. package/dist/src/graph/sqlite-statements.d.ts +36 -0
  57. package/dist/src/graph/sqlite-statements.js +105 -0
  58. package/dist/src/graph/target-matcher.d.ts +13 -0
  59. package/dist/src/graph/target-matcher.js +64 -0
  60. package/dist/src/index.d.ts +32 -0
  61. package/dist/src/index.js +32 -0
  62. package/dist/src/indexing/analyzers/fallback-analyzer.d.ts +6 -0
  63. package/dist/src/indexing/analyzers/fallback-analyzer.js +45 -0
  64. package/dist/src/indexing/analyzers/go-treesitter-analyzer.d.ts +2 -0
  65. package/dist/src/indexing/analyzers/go-treesitter-analyzer.js +87 -0
  66. package/dist/src/indexing/analyzers/java-treesitter-analyzer.d.ts +2 -0
  67. package/dist/src/indexing/analyzers/java-treesitter-analyzer.js +88 -0
  68. package/dist/src/indexing/analyzers/python-treesitter-analyzer.d.ts +2 -0
  69. package/dist/src/indexing/analyzers/python-treesitter-analyzer.js +96 -0
  70. package/dist/src/indexing/analyzers/registry.d.ts +5 -0
  71. package/dist/src/indexing/analyzers/registry.js +23 -0
  72. package/dist/src/indexing/analyzers/rust-treesitter-analyzer.d.ts +2 -0
  73. package/dist/src/indexing/analyzers/rust-treesitter-analyzer.js +96 -0
  74. package/dist/src/indexing/analyzers/tree-sitter-base.d.ts +30 -0
  75. package/dist/src/indexing/analyzers/tree-sitter-base.js +163 -0
  76. package/dist/src/indexing/analyzers/types.d.ts +17 -0
  77. package/dist/src/indexing/analyzers/types.js +1 -0
  78. package/dist/src/indexing/analyzers/typescript-analyzer.d.ts +5 -0
  79. package/dist/src/indexing/analyzers/typescript-analyzer.js +199 -0
  80. package/dist/src/indexing/ast-analyzer.d.ts +11 -0
  81. package/dist/src/indexing/ast-analyzer.js +11 -0
  82. package/dist/src/indexing/chunker.d.ts +11 -0
  83. package/dist/src/indexing/chunker.js +157 -0
  84. package/dist/src/indexing/ignore-policy.d.ts +6 -0
  85. package/dist/src/indexing/ignore-policy.js +40 -0
  86. package/dist/src/indexing/indexer.d.ts +13 -0
  87. package/dist/src/indexing/indexer.js +189 -0
  88. package/dist/src/indexing/language.d.ts +3 -0
  89. package/dist/src/indexing/language.js +24 -0
  90. package/dist/src/indexing/scanner.d.ts +13 -0
  91. package/dist/src/indexing/scanner.js +87 -0
  92. package/dist/src/lsp/definition-resolver.d.ts +6 -0
  93. package/dist/src/lsp/definition-resolver.js +60 -0
  94. package/dist/src/lsp/typescript-language-service.d.ts +21 -0
  95. package/dist/src/lsp/typescript-language-service.js +82 -0
  96. package/dist/src/mcp/server.d.ts +11 -0
  97. package/dist/src/mcp/server.js +64 -0
  98. package/dist/src/mcp/tools.d.ts +266 -0
  99. package/dist/src/mcp/tools.js +309 -0
  100. package/dist/src/project/project-identity.d.ts +2 -0
  101. package/dist/src/project/project-identity.js +24 -0
  102. package/dist/src/project/project-registry.d.ts +12 -0
  103. package/dist/src/project/project-registry.js +49 -0
  104. package/dist/src/project/workspace-resolver.d.ts +20 -0
  105. package/dist/src/project/workspace-resolver.js +62 -0
  106. package/dist/src/retrieval/graph-reranker.d.ts +11 -0
  107. package/dist/src/retrieval/graph-reranker.js +0 -0
  108. package/dist/src/retrieval/hybrid-retriever.d.ts +31 -0
  109. package/dist/src/retrieval/hybrid-retriever.js +111 -0
  110. package/dist/src/retrieval/path-classification.d.ts +6 -0
  111. package/dist/src/retrieval/path-classification.js +22 -0
  112. package/dist/src/retrieval/query-matching.d.ts +22 -0
  113. package/dist/src/retrieval/query-matching.js +166 -0
  114. package/dist/src/retrieval/query-planner.d.ts +5 -0
  115. package/dist/src/retrieval/query-planner.js +77 -0
  116. package/dist/src/retrieval/ranking-signals.d.ts +19 -0
  117. package/dist/src/retrieval/ranking-signals.js +97 -0
  118. package/dist/src/retrieval/topology-distance.d.ts +21 -0
  119. package/dist/src/retrieval/topology-distance.js +116 -0
  120. package/dist/src/reuse/reuse-detector.d.ts +12 -0
  121. package/dist/src/reuse/reuse-detector.js +564 -0
  122. package/dist/src/semantic/deterministic-embedding.d.ts +7 -0
  123. package/dist/src/semantic/deterministic-embedding.js +31 -0
  124. package/dist/src/semantic/in-memory-semantic-store.d.ts +11 -0
  125. package/dist/src/semantic/in-memory-semantic-store.js +65 -0
  126. package/dist/src/semantic/lance-semantic-store.d.ts +131 -0
  127. package/dist/src/semantic/lance-semantic-store.js +623 -0
  128. package/dist/src/semantic/openai-compatible-embedding.d.ts +19 -0
  129. package/dist/src/semantic/openai-compatible-embedding.js +75 -0
  130. package/dist/src/service/service-identity.d.ts +13 -0
  131. package/dist/src/service/service-identity.js +48 -0
  132. package/dist/src/service/service-manager.d.ts +29 -0
  133. package/dist/src/service/service-manager.js +231 -0
  134. package/dist/src/service/service-templates.d.ts +22 -0
  135. package/dist/src/service/service-templates.js +101 -0
  136. package/dist/src/subgraph/impact-explainer.d.ts +2 -0
  137. package/dist/src/subgraph/impact-explainer.js +54 -0
  138. package/dist/src/subgraph/node-expander.d.ts +13 -0
  139. package/dist/src/subgraph/node-expander.js +139 -0
  140. package/dist/src/subgraph/output-preset.d.ts +3 -0
  141. package/dist/src/subgraph/output-preset.js +102 -0
  142. package/dist/src/subgraph/subgraph-builder.d.ts +17 -0
  143. package/dist/src/subgraph/subgraph-builder.js +688 -0
  144. package/dist/src/topology/export-index.d.ts +7 -0
  145. package/dist/src/topology/export-index.js +14 -0
  146. package/dist/src/topology/framework-topology.d.ts +3 -0
  147. package/dist/src/topology/framework-topology.js +460 -0
  148. package/dist/src/topology/import-resolver.d.ts +2 -0
  149. package/dist/src/topology/import-resolver.js +29 -0
  150. package/dist/src/topology/orm-topology.d.ts +3 -0
  151. package/dist/src/topology/orm-topology.js +200 -0
  152. package/dist/src/topology/runtime-topology.d.ts +3 -0
  153. package/dist/src/topology/runtime-topology.js +204 -0
  154. package/dist/src/topology/symbol-resolver.d.ts +6 -0
  155. package/dist/src/topology/symbol-resolver.js +74 -0
  156. package/dist/src/topology/test-topology.d.ts +2 -0
  157. package/dist/src/topology/test-topology.js +82 -0
  158. package/dist/src/utils/hash.d.ts +2 -0
  159. package/dist/src/utils/hash.js +7 -0
  160. package/dist/src/utils/path.d.ts +2 -0
  161. package/dist/src/utils/path.js +7 -0
  162. package/dist/src/watch/event-journal.d.ts +17 -0
  163. package/dist/src/watch/event-journal.js +81 -0
  164. package/dist/src/watch/file-event-coalescer.d.ts +9 -0
  165. package/dist/src/watch/file-event-coalescer.js +39 -0
  166. package/dist/src/watch/index-scheduler.d.ts +52 -0
  167. package/dist/src/watch/index-scheduler.js +190 -0
  168. package/dist/src/watch/watch-daemon.d.ts +73 -0
  169. package/dist/src/watch/watch-daemon.js +368 -0
  170. package/dist/src/watch/watcher-liveness.d.ts +47 -0
  171. package/dist/src/watch/watcher-liveness.js +168 -0
  172. package/dist/src/web/server.d.ts +1 -0
  173. package/dist/src/web/server.js +375 -0
  174. package/package.json +94 -0
@@ -0,0 +1,200 @@
1
+ import ts from "typescript";
2
+ import { stableId } from "../utils/hash.js";
3
+ const PRISMA_READ_OPERATIONS = new Set(["aggregate", "count", "findFirst", "findMany", "findUnique"]);
4
+ const PRISMA_WRITE_OPERATIONS = new Set(["create", "createMany", "delete", "deleteMany", "update", "updateMany", "upsert"]);
5
+ const DRIZZLE_READ_OPERATIONS = new Set(["from", "select"]);
6
+ const DRIZZLE_WRITE_OPERATIONS = new Set(["delete", "insert", "update"]);
7
+ // Bounded ORM resolver: recognizes the conventional client identifiers `prisma.<model>.<op>()`
8
+ // and `db.<op>(<model>)` / `db.select().from(<model>)` only. Renamed clients (e.g. `database`,
9
+ // `drizzleDb`) are intentionally out of scope — broadening detection needs a resolver that
10
+ // tracks the import binding rather than the literal name, which is deferred (see todo.md D/L4).
11
+ export function buildOrmTopologyEdges(repoRoot, _files, sources, symbols) {
12
+ const edges = [];
13
+ for (const source of sources) {
14
+ const sourceFile = parseSourceFile(source);
15
+ const requestPayloadBindings = collectRequestPayloadBindings(sourceFile);
16
+ function visit(node) {
17
+ if (ts.isCallExpression(node)) {
18
+ const access = annotateDataflow(prismaAccessFromCall(node) ?? drizzleAccessFromCall(node), node, sourceFile, requestPayloadBindings);
19
+ if (access) {
20
+ const line = lineAt(sourceFile, node);
21
+ const sourceSymbol = containingSymbol(symbols, source.filePath, line) ?? fileSymbol(symbols, source.filePath);
22
+ if (sourceSymbol)
23
+ edges.push(ormEdge(repoRoot, source.filePath, sourceSymbol, access, line));
24
+ }
25
+ }
26
+ ts.forEachChild(node, visit);
27
+ }
28
+ ts.forEachChild(sourceFile, visit);
29
+ }
30
+ return dedupeEdges(edges);
31
+ }
32
+ function prismaAccessFromCall(node) {
33
+ if (!ts.isPropertyAccessExpression(node.expression))
34
+ return undefined;
35
+ const operation = node.expression.name.text;
36
+ const kind = PRISMA_READ_OPERATIONS.has(operation)
37
+ ? "reads_from"
38
+ : PRISMA_WRITE_OPERATIONS.has(operation)
39
+ ? "writes_to"
40
+ : undefined;
41
+ if (!kind || !ts.isPropertyAccessExpression(node.expression.expression))
42
+ return undefined;
43
+ const modelExpression = node.expression.expression;
44
+ if (!ts.isIdentifier(modelExpression.expression) || modelExpression.expression.text !== "prisma")
45
+ return undefined;
46
+ const model = modelExpression.name.text;
47
+ return { orm: "prisma", kind, operation, model, resource: `prisma.${model}` };
48
+ }
49
+ function drizzleAccessFromCall(node) {
50
+ if (!ts.isPropertyAccessExpression(node.expression))
51
+ return undefined;
52
+ const operation = node.expression.name.text;
53
+ if (operation === "values" || operation === "set") {
54
+ const writeCall = ts.isCallExpression(node.expression.expression) ? node.expression.expression : undefined;
55
+ const writeAccess = writeCall ? drizzleAccessFromCall(writeCall) : undefined;
56
+ if (writeAccess?.kind === "writes_to")
57
+ return writeAccess;
58
+ }
59
+ if (DRIZZLE_WRITE_OPERATIONS.has(operation) && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "db") {
60
+ const model = identifierText(node.arguments[0]);
61
+ if (model)
62
+ return { orm: "drizzle", kind: "writes_to", operation, model, resource: `drizzle.${model}` };
63
+ }
64
+ if (operation === "from") {
65
+ const model = identifierText(node.arguments[0]);
66
+ const selectCall = ts.isCallExpression(node.expression.expression) ? node.expression.expression : undefined;
67
+ if (model && selectCall && isDbSelectCall(selectCall)) {
68
+ return { orm: "drizzle", kind: "reads_from", operation: "select", model, resource: `drizzle.${model}` };
69
+ }
70
+ }
71
+ if (DRIZZLE_READ_OPERATIONS.has(operation) && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === "db") {
72
+ return { orm: "drizzle", kind: "reads_from", operation, model: "unknown", resource: "drizzle.unknown" };
73
+ }
74
+ return undefined;
75
+ }
76
+ function isDbSelectCall(node) {
77
+ return ts.isPropertyAccessExpression(node.expression)
78
+ && ts.isIdentifier(node.expression.expression)
79
+ && node.expression.expression.text === "db"
80
+ && node.expression.name.text === "select";
81
+ }
82
+ function identifierText(node) {
83
+ if (!node)
84
+ return undefined;
85
+ if (ts.isIdentifier(node))
86
+ return node.text;
87
+ if (ts.isPropertyAccessExpression(node))
88
+ return node.name.text;
89
+ return undefined;
90
+ }
91
+ function ormEdge(repoRoot, sourceFile, sourceSymbol, access, line) {
92
+ return {
93
+ projectId: sourceSymbol.projectId,
94
+ sourceId: sourceSymbol.id,
95
+ targetId: stableId([repoRoot, "orm", access.orm, access.model]),
96
+ kind: access.kind,
97
+ metadata: {
98
+ orm: access.orm,
99
+ sourceFile,
100
+ targetName: access.resource,
101
+ resource: access.resource,
102
+ model: access.model,
103
+ operation: access.operation,
104
+ line,
105
+ dataflowSource: access.dataflowSource,
106
+ dataflowKind: access.dataflowKind,
107
+ resolution: access.dataflowSource ? "orm_dataflow" : "orm_static",
108
+ producer: `${access.orm}_resolver`
109
+ }
110
+ };
111
+ }
112
+ function annotateDataflow(access, node, sourceFile, bindings) {
113
+ if (!access || access.kind !== "writes_to")
114
+ return access;
115
+ const source = dataflowSourceForCall(node, sourceFile, bindings);
116
+ return source ? { ...access, dataflowSource: source, dataflowKind: "request_payload" } : access;
117
+ }
118
+ function collectRequestPayloadBindings(sourceFile) {
119
+ const bindings = new Set();
120
+ function visit(node) {
121
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
122
+ const initializer = node.initializer.getText(sourceFile);
123
+ if (/\.(body|params|query)\b|\.json\s*\(/.test(initializer))
124
+ bindings.add(node.name.text);
125
+ }
126
+ ts.forEachChild(node, visit);
127
+ }
128
+ ts.forEachChild(sourceFile, visit);
129
+ return bindings;
130
+ }
131
+ function dataflowSourceForCall(node, sourceFile, bindings) {
132
+ const direct = directRequestPayloadSource(node, sourceFile);
133
+ if (direct)
134
+ return direct;
135
+ if (bindings.size === 0)
136
+ return undefined;
137
+ const text = node.getText(sourceFile);
138
+ return [...bindings].find((binding) => new RegExp(`\\b${escapeRegExp(binding)}\\b`).test(text));
139
+ }
140
+ function directRequestPayloadSource(node, sourceFile) {
141
+ for (const argument of node.arguments) {
142
+ const source = requestPayloadExpression(argument, sourceFile);
143
+ if (source)
144
+ return source;
145
+ }
146
+ return undefined;
147
+ }
148
+ function requestPayloadExpression(node, sourceFile) {
149
+ if (ts.isPropertyAccessExpression(node) && ts.isIdentifier(node.expression) && ["body", "params", "query"].includes(node.name.text)) {
150
+ return `${node.expression.text}.${node.name.text}`;
151
+ }
152
+ if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && ts.isIdentifier(node.expression.expression) && node.expression.name.text === "json") {
153
+ return `${node.expression.expression.text}.json()`;
154
+ }
155
+ let found;
156
+ node.forEachChild((child) => {
157
+ if (found)
158
+ return;
159
+ found = requestPayloadExpression(child, sourceFile);
160
+ });
161
+ return found;
162
+ }
163
+ function escapeRegExp(value) {
164
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
165
+ }
166
+ function parseSourceFile(source) {
167
+ return ts.createSourceFile(source.filePath, source.content, ts.ScriptTarget.Latest, true, scriptKindForPath(source.filePath));
168
+ }
169
+ function containingSymbol(symbols, filePath, line) {
170
+ return symbols
171
+ .filter((symbol) => symbol.filePath === filePath && symbol.kind !== "file" && symbol.startLine <= line && symbol.endLine >= line)
172
+ .sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
173
+ }
174
+ function fileSymbol(symbols, filePath) {
175
+ return symbols.find((symbol) => symbol.filePath === filePath && symbol.kind === "file");
176
+ }
177
+ function lineAt(sourceFile, node) {
178
+ return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
179
+ }
180
+ function scriptKindForPath(filePath) {
181
+ if (filePath.endsWith(".tsx"))
182
+ return ts.ScriptKind.TSX;
183
+ if (filePath.endsWith(".jsx"))
184
+ return ts.ScriptKind.JSX;
185
+ if (filePath.endsWith(".js") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs"))
186
+ return ts.ScriptKind.JS;
187
+ return ts.ScriptKind.TS;
188
+ }
189
+ function dedupeEdges(edges) {
190
+ const seen = new Set();
191
+ const deduped = [];
192
+ for (const edge of edges) {
193
+ const key = [edge.kind, edge.sourceId, edge.targetId, edge.metadata?.operation, edge.metadata?.sourceFile, edge.metadata?.line].join("::");
194
+ if (seen.has(key))
195
+ continue;
196
+ seen.add(key);
197
+ deduped.push(edge);
198
+ }
199
+ return deduped;
200
+ }
@@ -0,0 +1,3 @@
1
+ import type { CodeFile, GraphEdge, SymbolNode } from "../core/types.js";
2
+ import type { TypeScriptSourceFile } from "../lsp/typescript-language-service.js";
3
+ export declare function buildRuntimeTopologyEdges(repoRoot: string, files: CodeFile[], sources: TypeScriptSourceFile[], symbols: SymbolNode[]): GraphEdge[];
@@ -0,0 +1,204 @@
1
+ import ts from "typescript";
2
+ import { stableId } from "../utils/hash.js";
3
+ const READ_OPERATIONS = new Set(["aggregate", "count", "findFirst", "findMany", "findUnique", "get", "query", "read", "select"]);
4
+ const WRITE_OPERATIONS = new Set(["create", "createMany", "delete", "deleteMany", "insert", "save", "set", "update", "updateMany", "upsert", "write"]);
5
+ const EVENT_OPERATIONS = new Set(["addEventListener", "handle", "listen", "on", "once", "subscribe"]);
6
+ const RESOURCE_ROOTS = new Set(["client", "database", "db", "prisma"]);
7
+ export function buildRuntimeTopologyEdges(repoRoot, files, sources, symbols) {
8
+ return dedupeEdges([
9
+ ...resourceAccessEdges(repoRoot, sources, symbols),
10
+ ...eventHandlerEdges(repoRoot, sources, symbols),
11
+ ...middlewareUsageEdges(files, symbols)
12
+ ]);
13
+ }
14
+ function resourceAccessEdges(repoRoot, sources, symbols) {
15
+ const edges = [];
16
+ for (const source of sources) {
17
+ const sourceFile = parseSourceFile(source);
18
+ function visit(node) {
19
+ if (ts.isCallExpression(node)) {
20
+ const access = resourceAccessFromCall(node);
21
+ if (access) {
22
+ const line = lineAt(sourceFile, node);
23
+ const sourceSymbol = containingSymbol(symbols, source.filePath, line) ?? fileSymbol(symbols, source.filePath);
24
+ if (sourceSymbol) {
25
+ edges.push({
26
+ projectId: sourceSymbol.projectId,
27
+ sourceId: sourceSymbol.id,
28
+ targetId: stableId([repoRoot, "resource", access.resource]),
29
+ kind: access.kind,
30
+ metadata: {
31
+ sourceFile: source.filePath,
32
+ targetName: access.resource,
33
+ resource: access.resource,
34
+ operation: access.operation,
35
+ line,
36
+ resolution: "resource_static",
37
+ producer: "typescript_runtime_topology"
38
+ }
39
+ });
40
+ }
41
+ }
42
+ }
43
+ ts.forEachChild(node, visit);
44
+ }
45
+ ts.forEachChild(sourceFile, visit);
46
+ }
47
+ return edges;
48
+ }
49
+ function eventHandlerEdges(repoRoot, sources, symbols) {
50
+ const edges = [];
51
+ for (const source of sources) {
52
+ const sourceFile = parseSourceFile(source);
53
+ function visit(node) {
54
+ if (ts.isCallExpression(node)) {
55
+ const event = eventFromCall(node);
56
+ if (event) {
57
+ const line = lineAt(sourceFile, node);
58
+ const sourceSymbol = containingSymbol(symbols, source.filePath, line) ?? fileSymbol(symbols, source.filePath);
59
+ if (sourceSymbol) {
60
+ edges.push({
61
+ projectId: sourceSymbol.projectId,
62
+ sourceId: sourceSymbol.id,
63
+ targetId: stableId([repoRoot, "event", event.name]),
64
+ kind: "handles_event",
65
+ metadata: {
66
+ sourceFile: source.filePath,
67
+ targetName: event.name,
68
+ event: event.name,
69
+ operation: event.operation,
70
+ line,
71
+ resolution: "event_static",
72
+ producer: "typescript_runtime_topology"
73
+ }
74
+ });
75
+ }
76
+ }
77
+ }
78
+ ts.forEachChild(node, visit);
79
+ }
80
+ ts.forEachChild(sourceFile, visit);
81
+ }
82
+ return edges;
83
+ }
84
+ function middlewareUsageEdges(files, symbols) {
85
+ const middlewareFile = files.find((file) => /^(src\/)?middleware\.[jt]s$/.test(file.path));
86
+ if (!middlewareFile)
87
+ return [];
88
+ const targetSymbol = symbols.find((symbol) => symbol.filePath === middlewareFile.path && symbol.name === "middleware")
89
+ ?? fileSymbol(symbols, middlewareFile.path);
90
+ if (!targetSymbol)
91
+ return [];
92
+ const edges = [];
93
+ for (const file of files) {
94
+ if (!isRouteFile(file.path))
95
+ continue;
96
+ const sourceSymbol = routeHandlerSymbol(symbols, file.path) ?? fileSymbol(symbols, file.path);
97
+ if (!sourceSymbol)
98
+ continue;
99
+ edges.push({
100
+ projectId: sourceSymbol.projectId,
101
+ sourceId: sourceSymbol.id,
102
+ targetId: targetSymbol.id,
103
+ kind: "uses_middleware",
104
+ metadata: {
105
+ framework: "nextjs",
106
+ sourceFile: file.path,
107
+ targetFile: middlewareFile.path,
108
+ targetName: targetSymbol.name,
109
+ resolution: "framework_static",
110
+ producer: "typescript_runtime_topology"
111
+ }
112
+ });
113
+ }
114
+ return edges;
115
+ }
116
+ function resourceAccessFromCall(node) {
117
+ if (!ts.isPropertyAccessExpression(node.expression))
118
+ return undefined;
119
+ const operation = node.expression.name.text;
120
+ const kind = READ_OPERATIONS.has(operation)
121
+ ? "reads_from"
122
+ : WRITE_OPERATIONS.has(operation)
123
+ ? "writes_to"
124
+ : undefined;
125
+ if (!kind || !ts.isPropertyAccessExpression(node.expression.expression))
126
+ return undefined;
127
+ const resourceExpression = node.expression.expression;
128
+ if (!ts.isIdentifier(resourceExpression.expression))
129
+ return undefined;
130
+ const root = resourceExpression.expression.text;
131
+ if (!RESOURCE_ROOTS.has(root))
132
+ return undefined;
133
+ return {
134
+ kind,
135
+ operation,
136
+ resource: `${root}.${resourceExpression.name.text}`
137
+ };
138
+ }
139
+ function eventFromCall(node) {
140
+ if (!ts.isPropertyAccessExpression(node.expression))
141
+ return undefined;
142
+ const operation = node.expression.name.text;
143
+ if (!EVENT_OPERATIONS.has(operation))
144
+ return undefined;
145
+ const eventName = stringLiteralValue(node.arguments[0]);
146
+ if (!eventName)
147
+ return undefined;
148
+ if (!looksLikeEventReceiver(node.expression.expression) && operation !== "addEventListener")
149
+ return undefined;
150
+ return { operation, name: eventName };
151
+ }
152
+ function looksLikeEventReceiver(expression) {
153
+ const text = expression.getText();
154
+ return /event|emitter|bus|queue|stream|socket|document|window/i.test(text);
155
+ }
156
+ function stringLiteralValue(node) {
157
+ if (!node)
158
+ return undefined;
159
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
160
+ return node.text;
161
+ return undefined;
162
+ }
163
+ function parseSourceFile(source) {
164
+ return ts.createSourceFile(source.filePath, source.content, ts.ScriptTarget.Latest, true, scriptKindForPath(source.filePath));
165
+ }
166
+ function routeHandlerSymbol(symbols, filePath) {
167
+ return symbols.find((symbol) => symbol.filePath === filePath && ["GET", "POST", "PUT", "PATCH", "DELETE"].includes(symbol.name))
168
+ ?? symbols.find((symbol) => symbol.filePath === filePath && symbol.exported && symbol.kind !== "file");
169
+ }
170
+ function fileSymbol(symbols, filePath) {
171
+ return symbols.find((symbol) => symbol.filePath === filePath && symbol.kind === "file");
172
+ }
173
+ function containingSymbol(symbols, filePath, line) {
174
+ return symbols
175
+ .filter((symbol) => symbol.filePath === filePath && symbol.kind !== "file" && symbol.startLine <= line && symbol.endLine >= line)
176
+ .sort((a, b) => (a.endLine - a.startLine) - (b.endLine - b.startLine))[0];
177
+ }
178
+ function isRouteFile(filePath) {
179
+ return /(?:^|\/)(?:app|pages)\/api\/.+(?:\/route)?\.[jt]sx?$/.test(filePath.replaceAll("\\", "/"));
180
+ }
181
+ function lineAt(sourceFile, node) {
182
+ return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
183
+ }
184
+ function scriptKindForPath(filePath) {
185
+ if (filePath.endsWith(".tsx"))
186
+ return ts.ScriptKind.TSX;
187
+ if (filePath.endsWith(".jsx"))
188
+ return ts.ScriptKind.JSX;
189
+ if (filePath.endsWith(".js") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs"))
190
+ return ts.ScriptKind.JS;
191
+ return ts.ScriptKind.TS;
192
+ }
193
+ function dedupeEdges(edges) {
194
+ const seen = new Set();
195
+ const deduped = [];
196
+ for (const edge of edges) {
197
+ const key = [edge.kind, edge.sourceId, edge.targetId, edge.metadata?.targetName, edge.metadata?.sourceFile].join("::");
198
+ if (seen.has(key))
199
+ continue;
200
+ seen.add(key);
201
+ deduped.push(edge);
202
+ }
203
+ return deduped;
204
+ }
@@ -0,0 +1,6 @@
1
+ import type { CodeFile, GraphEdge, SymbolNode } from "../core/types.js";
2
+ export interface ImportBinding {
3
+ imported: string;
4
+ local: string;
5
+ }
6
+ export declare function resolveGraphEdges(files: CodeFile[], symbols: SymbolNode[], edges: GraphEdge[]): GraphEdge[];
@@ -0,0 +1,74 @@
1
+ import { createExportIndex, exportKey } from "./export-index.js";
2
+ import { resolveImportPath } from "./import-resolver.js";
3
+ export function resolveGraphEdges(files, symbols, edges) {
4
+ const exportIndex = createExportIndex(symbols);
5
+ const bindingsByFile = new Map();
6
+ const resolvedEdges = [];
7
+ for (const edge of edges) {
8
+ if (edge.kind !== "imports") {
9
+ resolvedEdges.push(edge);
10
+ continue;
11
+ }
12
+ const sourceFile = stringMetadata(edge, "sourceFile");
13
+ const importSource = stringMetadata(edge, "source");
14
+ const targetFile = sourceFile && importSource ? resolveImportPath(sourceFile, importSource, files) : undefined;
15
+ if (!sourceFile || !targetFile) {
16
+ resolvedEdges.push({ ...edge, metadata: { ...edge.metadata, resolution: "unresolved" } });
17
+ continue;
18
+ }
19
+ const fileSymbol = exportIndex.fileSymbols.get(targetFile);
20
+ resolvedEdges.push({
21
+ ...edge,
22
+ targetId: fileSymbol?.id ?? edge.targetId,
23
+ metadata: { ...edge.metadata, resolution: "resolved", targetFile }
24
+ });
25
+ const bindings = importBindings(edge);
26
+ if (bindings.length === 0)
27
+ continue;
28
+ const fileBindings = bindingsByFile.get(sourceFile) ?? new Map();
29
+ for (const binding of bindings) {
30
+ fileBindings.set(binding.local, { ...binding, targetFile });
31
+ }
32
+ bindingsByFile.set(sourceFile, fileBindings);
33
+ }
34
+ return resolvedEdges.map((edge) => {
35
+ if (edge.kind !== "calls")
36
+ return edge;
37
+ const sourceFile = stringMetadata(edge, "sourceFile");
38
+ const targetName = stringMetadata(edge, "targetName");
39
+ if (!sourceFile || !targetName)
40
+ return edge;
41
+ const binding = bindingsByFile.get(sourceFile)?.get(targetName);
42
+ if (!binding)
43
+ return { ...edge, metadata: { ...edge.metadata, resolution: "unresolved" } };
44
+ const targetSymbol = exportIndex.exportedSymbols.get(exportKey(binding.targetFile, binding.imported));
45
+ if (!targetSymbol)
46
+ return { ...edge, metadata: { ...edge.metadata, resolution: "unresolved", targetFile: binding.targetFile } };
47
+ return {
48
+ ...edge,
49
+ targetId: targetSymbol.id,
50
+ metadata: {
51
+ ...edge.metadata,
52
+ resolution: "resolved",
53
+ targetFile: targetSymbol.filePath,
54
+ importedName: binding.imported
55
+ }
56
+ };
57
+ });
58
+ }
59
+ function importBindings(edge) {
60
+ const raw = edge.metadata?.bindings;
61
+ if (!Array.isArray(raw))
62
+ return [];
63
+ return raw.filter(isImportBinding);
64
+ }
65
+ function isImportBinding(value) {
66
+ if (!value || typeof value !== "object")
67
+ return false;
68
+ const candidate = value;
69
+ return typeof candidate.imported === "string" && typeof candidate.local === "string";
70
+ }
71
+ function stringMetadata(edge, key) {
72
+ const value = edge.metadata?.[key];
73
+ return typeof value === "string" ? value : undefined;
74
+ }
@@ -0,0 +1,2 @@
1
+ import type { GraphEdge, SymbolNode } from "../core/types.js";
2
+ export declare function buildTestTopologyEdges(symbols: SymbolNode[], edges: GraphEdge[]): GraphEdge[];
@@ -0,0 +1,82 @@
1
+ import { createExportIndex, exportKey } from "./export-index.js";
2
+ export function buildTestTopologyEdges(symbols, edges) {
3
+ const exportIndex = createExportIndex(symbols);
4
+ const testEdges = [];
5
+ for (const edge of edges) {
6
+ if (edge.kind !== "imports")
7
+ continue;
8
+ const testFile = stringMetadata(edge, "sourceFile");
9
+ const sourceFile = stringMetadata(edge, "targetFile");
10
+ if (!testFile || !sourceFile || !isTestFile(testFile) || isTestFile(sourceFile))
11
+ continue;
12
+ const testFileSymbol = exportIndex.fileSymbols.get(testFile);
13
+ if (!testFileSymbol)
14
+ continue;
15
+ const bindings = importBindings(edge);
16
+ const sourceSymbols = bindings.length > 0
17
+ ? bindings.map((binding) => ({
18
+ symbol: binding.imported === "default"
19
+ ? undefined
20
+ : exportIndex.exportedSymbols.get(exportKey(sourceFile, binding.imported)),
21
+ binding
22
+ }))
23
+ : [{ symbol: undefined, binding: undefined }];
24
+ for (const entry of sourceSymbols) {
25
+ const sourceSymbol = entry.symbol ?? exportIndex.fileSymbols.get(sourceFile);
26
+ if (!sourceSymbol)
27
+ continue;
28
+ testEdges.push({
29
+ projectId: edge.projectId,
30
+ sourceId: sourceSymbol.id,
31
+ targetId: testFileSymbol.id,
32
+ kind: "tested_by",
33
+ metadata: {
34
+ sourceFile,
35
+ targetFile: testFile,
36
+ testFile,
37
+ targetName: testFile,
38
+ importedName: entry.binding?.imported,
39
+ localName: entry.binding?.local,
40
+ line: edge.metadata?.line,
41
+ resolution: "test_import",
42
+ colocated: sameDirectory(sourceFile, testFile)
43
+ }
44
+ });
45
+ }
46
+ }
47
+ return dedupeEdges(testEdges);
48
+ }
49
+ function importBindings(edge) {
50
+ const raw = edge.metadata?.bindings;
51
+ if (!Array.isArray(raw))
52
+ return [];
53
+ return raw.filter(isImportBinding);
54
+ }
55
+ function isImportBinding(value) {
56
+ if (!value || typeof value !== "object")
57
+ return false;
58
+ const candidate = value;
59
+ return typeof candidate.imported === "string" && typeof candidate.local === "string";
60
+ }
61
+ function stringMetadata(edge, key) {
62
+ const value = edge.metadata?.[key];
63
+ return typeof value === "string" ? value : undefined;
64
+ }
65
+ function isTestFile(filePath) {
66
+ return /(^|\/)(__tests__|tests?)(\/|$)|\.(test|spec)\.[jt]sx?$/.test(filePath);
67
+ }
68
+ function sameDirectory(left, right) {
69
+ return left.split("/").slice(0, -1).join("/") === right.split("/").slice(0, -1).join("/");
70
+ }
71
+ function dedupeEdges(edges) {
72
+ const seen = new Set();
73
+ const deduped = [];
74
+ for (const edge of edges) {
75
+ const key = [edge.kind, edge.sourceId, edge.targetId, edge.metadata?.importedName].join("::");
76
+ if (seen.has(key))
77
+ continue;
78
+ seen.add(key);
79
+ deduped.push(edge);
80
+ }
81
+ return deduped;
82
+ }
@@ -0,0 +1,2 @@
1
+ export declare function sha256(input: string): string;
2
+ export declare function stableId(parts: Array<string | number | undefined>): string;
@@ -0,0 +1,7 @@
1
+ import { createHash } from "node:crypto";
2
+ export function sha256(input) {
3
+ return createHash("sha256").update(input).digest("hex");
4
+ }
5
+ export function stableId(parts) {
6
+ return sha256(parts.filter((part) => part !== undefined).join("::")).slice(0, 24);
7
+ }
@@ -0,0 +1,2 @@
1
+ export declare function normalizeRepoPath(repoRoot: string, candidate: string): string;
2
+ export declare function normalizeUserPath(candidate: string): string;
@@ -0,0 +1,7 @@
1
+ import path from "node:path";
2
+ export function normalizeRepoPath(repoRoot, candidate) {
3
+ return path.relative(repoRoot, candidate).split(path.sep).join("/");
4
+ }
5
+ export function normalizeUserPath(candidate) {
6
+ return candidate.split(path.sep).join("/");
7
+ }
@@ -0,0 +1,17 @@
1
+ export interface WatchEventJournalEntry {
2
+ event: "add" | "change" | "unlink" | "addDir" | "unlinkDir";
3
+ filePath: string;
4
+ observedAtMs: number;
5
+ }
6
+ export declare class FileEventJournal {
7
+ readonly journalPath: string;
8
+ private readonly repoRoot?;
9
+ constructor(journalPath: string, repoRoot?: string | undefined);
10
+ static forRepo(repoRoot: string, fileName?: string): FileEventJournal;
11
+ append(entry: WatchEventJournalEntry): Promise<void>;
12
+ appendBatch(entries: WatchEventJournalEntry[]): Promise<void>;
13
+ replay(): Promise<WatchEventJournalEntry[]>;
14
+ replayPaths(): Promise<string[]>;
15
+ truncate(): Promise<void>;
16
+ private normalizeJournalPath;
17
+ }