@kodus/kodus-graph 0.2.8 → 0.2.10

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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +252 -0
  3. package/dist/analysis/blast-radius.d.ts +2 -0
  4. package/dist/analysis/blast-radius.js +55 -0
  5. package/dist/analysis/communities.d.ts +28 -0
  6. package/dist/analysis/communities.js +100 -0
  7. package/dist/analysis/context-builder.d.ts +34 -0
  8. package/dist/analysis/context-builder.js +92 -0
  9. package/dist/analysis/diff.d.ts +41 -0
  10. package/dist/analysis/diff.js +155 -0
  11. package/dist/analysis/enrich.d.ts +5 -0
  12. package/dist/analysis/enrich.js +126 -0
  13. package/dist/analysis/flows.d.ts +27 -0
  14. package/dist/analysis/flows.js +86 -0
  15. package/dist/analysis/inheritance.d.ts +3 -0
  16. package/dist/analysis/inheritance.js +31 -0
  17. package/dist/analysis/prompt-formatter.d.ts +2 -0
  18. package/dist/analysis/prompt-formatter.js +173 -0
  19. package/dist/analysis/risk-score.d.ts +4 -0
  20. package/dist/analysis/risk-score.js +51 -0
  21. package/dist/analysis/search.d.ts +11 -0
  22. package/dist/analysis/search.js +64 -0
  23. package/dist/analysis/test-gaps.d.ts +2 -0
  24. package/dist/analysis/test-gaps.js +14 -0
  25. package/dist/cli.d.ts +2 -0
  26. package/dist/cli.js +210 -0
  27. package/dist/commands/analyze.d.ts +9 -0
  28. package/dist/commands/analyze.js +116 -0
  29. package/dist/commands/communities.d.ts +8 -0
  30. package/dist/commands/communities.js +9 -0
  31. package/dist/commands/context.d.ts +12 -0
  32. package/dist/commands/context.js +130 -0
  33. package/dist/commands/diff.d.ts +9 -0
  34. package/dist/commands/diff.js +89 -0
  35. package/dist/commands/flows.d.ts +8 -0
  36. package/dist/commands/flows.js +9 -0
  37. package/dist/commands/parse.d.ts +11 -0
  38. package/dist/commands/parse.js +101 -0
  39. package/dist/commands/search.d.ts +12 -0
  40. package/dist/commands/search.js +27 -0
  41. package/dist/commands/update.d.ts +7 -0
  42. package/dist/commands/update.js +154 -0
  43. package/dist/graph/builder.d.ts +6 -0
  44. package/dist/graph/builder.js +248 -0
  45. package/dist/graph/edges.d.ts +23 -0
  46. package/dist/graph/edges.js +159 -0
  47. package/dist/graph/json-writer.d.ts +9 -0
  48. package/dist/graph/json-writer.js +38 -0
  49. package/dist/graph/loader.d.ts +13 -0
  50. package/dist/graph/loader.js +101 -0
  51. package/dist/graph/merger.d.ts +7 -0
  52. package/dist/graph/merger.js +18 -0
  53. package/dist/graph/types.d.ts +252 -0
  54. package/dist/graph/types.js +1 -0
  55. package/dist/parser/batch.d.ts +5 -0
  56. package/dist/parser/batch.js +93 -0
  57. package/dist/parser/discovery.d.ts +7 -0
  58. package/dist/parser/discovery.js +61 -0
  59. package/dist/parser/extractor.d.ts +4 -0
  60. package/dist/parser/extractor.js +33 -0
  61. package/dist/parser/extractors/generic.d.ts +8 -0
  62. package/dist/parser/extractors/generic.js +471 -0
  63. package/dist/parser/extractors/python.d.ts +8 -0
  64. package/dist/parser/extractors/python.js +133 -0
  65. package/dist/parser/extractors/ruby.d.ts +8 -0
  66. package/dist/parser/extractors/ruby.js +153 -0
  67. package/dist/parser/extractors/typescript.d.ts +10 -0
  68. package/dist/parser/extractors/typescript.js +365 -0
  69. package/dist/parser/languages.d.ts +32 -0
  70. package/dist/parser/languages.js +304 -0
  71. package/dist/resolver/call-resolver.d.ts +36 -0
  72. package/dist/resolver/call-resolver.js +178 -0
  73. package/dist/resolver/external-detector.d.ts +11 -0
  74. package/dist/resolver/external-detector.js +820 -0
  75. package/dist/resolver/fs-cache.d.ts +8 -0
  76. package/dist/resolver/fs-cache.js +36 -0
  77. package/dist/resolver/import-map.d.ts +12 -0
  78. package/dist/resolver/import-map.js +21 -0
  79. package/dist/resolver/import-resolver.d.ts +19 -0
  80. package/dist/resolver/import-resolver.js +310 -0
  81. package/dist/resolver/languages/csharp.d.ts +3 -0
  82. package/dist/resolver/languages/csharp.js +94 -0
  83. package/dist/resolver/languages/go.d.ts +3 -0
  84. package/dist/resolver/languages/go.js +197 -0
  85. package/dist/resolver/languages/java.d.ts +1 -0
  86. package/dist/resolver/languages/java.js +193 -0
  87. package/dist/resolver/languages/php.d.ts +3 -0
  88. package/dist/resolver/languages/php.js +75 -0
  89. package/dist/resolver/languages/python.d.ts +11 -0
  90. package/dist/resolver/languages/python.js +127 -0
  91. package/dist/resolver/languages/ruby.d.ts +24 -0
  92. package/dist/resolver/languages/ruby.js +110 -0
  93. package/dist/resolver/languages/rust.d.ts +1 -0
  94. package/dist/resolver/languages/rust.js +197 -0
  95. package/dist/resolver/languages/typescript.d.ts +35 -0
  96. package/dist/resolver/languages/typescript.js +416 -0
  97. package/dist/resolver/re-export-resolver.d.ts +24 -0
  98. package/dist/resolver/re-export-resolver.js +57 -0
  99. package/dist/resolver/symbol-table.d.ts +17 -0
  100. package/dist/resolver/symbol-table.js +60 -0
  101. package/dist/shared/extract-calls.d.ts +26 -0
  102. package/dist/shared/extract-calls.js +57 -0
  103. package/dist/shared/file-hash.d.ts +3 -0
  104. package/dist/shared/file-hash.js +10 -0
  105. package/dist/shared/filters.d.ts +3 -0
  106. package/dist/shared/filters.js +240 -0
  107. package/dist/shared/logger.d.ts +6 -0
  108. package/dist/shared/logger.js +17 -0
  109. package/dist/shared/qualified-name.d.ts +1 -0
  110. package/dist/shared/qualified-name.js +9 -0
  111. package/dist/shared/safe-path.d.ts +6 -0
  112. package/dist/shared/safe-path.js +29 -0
  113. package/dist/shared/schemas.d.ts +43 -0
  114. package/dist/shared/schemas.js +30 -0
  115. package/dist/shared/temp.d.ts +11 -0
  116. package/{src/shared/temp.ts → dist/shared/temp.js} +4 -5
  117. package/package.json +20 -6
  118. package/src/analysis/blast-radius.ts +0 -54
  119. package/src/analysis/communities.ts +0 -135
  120. package/src/analysis/context-builder.ts +0 -130
  121. package/src/analysis/diff.ts +0 -169
  122. package/src/analysis/enrich.ts +0 -110
  123. package/src/analysis/flows.ts +0 -112
  124. package/src/analysis/inheritance.ts +0 -34
  125. package/src/analysis/prompt-formatter.ts +0 -175
  126. package/src/analysis/risk-score.ts +0 -62
  127. package/src/analysis/search.ts +0 -76
  128. package/src/analysis/test-gaps.ts +0 -21
  129. package/src/cli.ts +0 -210
  130. package/src/commands/analyze.ts +0 -128
  131. package/src/commands/communities.ts +0 -19
  132. package/src/commands/context.ts +0 -182
  133. package/src/commands/diff.ts +0 -96
  134. package/src/commands/flows.ts +0 -19
  135. package/src/commands/parse.ts +0 -124
  136. package/src/commands/search.ts +0 -41
  137. package/src/commands/update.ts +0 -166
  138. package/src/graph/builder.ts +0 -209
  139. package/src/graph/edges.ts +0 -101
  140. package/src/graph/json-writer.ts +0 -43
  141. package/src/graph/loader.ts +0 -113
  142. package/src/graph/merger.ts +0 -25
  143. package/src/graph/types.ts +0 -283
  144. package/src/parser/batch.ts +0 -82
  145. package/src/parser/discovery.ts +0 -75
  146. package/src/parser/extractor.ts +0 -37
  147. package/src/parser/extractors/generic.ts +0 -132
  148. package/src/parser/extractors/python.ts +0 -133
  149. package/src/parser/extractors/ruby.ts +0 -147
  150. package/src/parser/extractors/typescript.ts +0 -350
  151. package/src/parser/languages.ts +0 -122
  152. package/src/resolver/call-resolver.ts +0 -244
  153. package/src/resolver/import-map.ts +0 -27
  154. package/src/resolver/import-resolver.ts +0 -72
  155. package/src/resolver/languages/csharp.ts +0 -7
  156. package/src/resolver/languages/go.ts +0 -7
  157. package/src/resolver/languages/java.ts +0 -7
  158. package/src/resolver/languages/php.ts +0 -7
  159. package/src/resolver/languages/python.ts +0 -35
  160. package/src/resolver/languages/ruby.ts +0 -21
  161. package/src/resolver/languages/rust.ts +0 -7
  162. package/src/resolver/languages/typescript.ts +0 -168
  163. package/src/resolver/re-export-resolver.ts +0 -66
  164. package/src/resolver/symbol-table.ts +0 -67
  165. package/src/shared/extract-calls.ts +0 -75
  166. package/src/shared/file-hash.ts +0 -12
  167. package/src/shared/filters.ts +0 -243
  168. package/src/shared/logger.ts +0 -17
  169. package/src/shared/qualified-name.ts +0 -5
  170. package/src/shared/safe-path.ts +0 -31
  171. package/src/shared/schemas.ts +0 -32
@@ -1,209 +0,0 @@
1
- import { deriveEdges } from './edges';
2
- import type { GraphData, GraphEdge, GraphNode, ImportEdge, RawCallEdge, RawGraph } from './types';
3
-
4
- export function buildGraphData(
5
- raw: RawGraph,
6
- callEdges: RawCallEdge[],
7
- importEdges: ImportEdge[],
8
- _repoDir: string,
9
- fileHashes: Map<string, string>,
10
- ): GraphData {
11
- const nodes: GraphNode[] = [];
12
- const edges: GraphEdge[] = [];
13
-
14
- // Functions -> nodes
15
- for (const f of raw.functions) {
16
- nodes.push({
17
- kind: f.kind,
18
- name: f.name,
19
- qualified_name: f.qualified,
20
- file_path: f.file,
21
- line_start: f.line_start,
22
- line_end: f.line_end,
23
- language: detectLang(f.file),
24
- parent_name: f.className || undefined,
25
- params: f.params || undefined,
26
- return_type: f.returnType || undefined,
27
- is_test: false,
28
- file_hash: fileHashes.get(f.file) || '',
29
- content_hash: f.content_hash,
30
- });
31
- }
32
-
33
- // Classes -> nodes
34
- for (const c of raw.classes) {
35
- nodes.push({
36
- kind: 'Class',
37
- name: c.name,
38
- qualified_name: c.qualified,
39
- file_path: c.file,
40
- line_start: c.line_start,
41
- line_end: c.line_end,
42
- language: detectLang(c.file),
43
- is_test: false,
44
- file_hash: fileHashes.get(c.file) || '',
45
- content_hash: c.content_hash,
46
- });
47
- }
48
-
49
- // Interfaces -> nodes
50
- for (const i of raw.interfaces) {
51
- nodes.push({
52
- kind: 'Interface',
53
- name: i.name,
54
- qualified_name: i.qualified,
55
- file_path: i.file,
56
- line_start: i.line_start,
57
- line_end: i.line_end,
58
- language: detectLang(i.file),
59
- is_test: false,
60
- file_hash: fileHashes.get(i.file) || '',
61
- content_hash: i.content_hash,
62
- });
63
- }
64
-
65
- // Enums -> nodes
66
- for (const e of raw.enums) {
67
- nodes.push({
68
- kind: 'Enum',
69
- name: e.name,
70
- qualified_name: e.qualified,
71
- file_path: e.file,
72
- line_start: e.line_start,
73
- line_end: e.line_end,
74
- language: detectLang(e.file),
75
- is_test: false,
76
- file_hash: fileHashes.get(e.file) || '',
77
- content_hash: e.content_hash,
78
- });
79
- }
80
-
81
- // Tests -> nodes
82
- for (const t of raw.tests) {
83
- nodes.push({
84
- kind: 'Test',
85
- name: t.name,
86
- qualified_name: t.qualified,
87
- file_path: t.file,
88
- line_start: t.line_start,
89
- line_end: t.line_end,
90
- language: detectLang(t.file),
91
- is_test: true,
92
- file_hash: fileHashes.get(t.file) || '',
93
- content_hash: t.content_hash,
94
- });
95
- }
96
-
97
- // Build file→functions index to resolve caller from line number
98
- const functionsByFile = new Map<string, Array<{ qualified_name: string; line_start: number; line_end: number }>>();
99
- for (const node of nodes) {
100
- if (node.kind === 'Class' || node.kind === 'Interface' || node.kind === 'Enum') continue;
101
- const entry = { qualified_name: node.qualified_name, line_start: node.line_start, line_end: node.line_end };
102
- const list = functionsByFile.get(node.file_path);
103
- if (list) list.push(entry);
104
- else functionsByFile.set(node.file_path, [entry]);
105
- }
106
- // Sort descending by line_start so inner/nested functions match first
107
- for (const list of functionsByFile.values()) {
108
- list.sort((a, b) => b.line_start - a.line_start);
109
- }
110
-
111
- // CALLS edges — resolve caller function from call line number
112
- for (const ce of callEdges) {
113
- const sourceFile = ce.source.includes('::') ? ce.source.split('::')[0] : ce.source;
114
- let sourceQualified: string;
115
-
116
- if (ce.source.includes('::')) {
117
- sourceQualified = ce.source;
118
- } else {
119
- // Find the innermost function containing this call line
120
- const fns = functionsByFile.get(ce.source);
121
- let resolved: string | undefined;
122
- if (fns) {
123
- for (const fn of fns) {
124
- if (ce.line >= fn.line_start && ce.line <= fn.line_end) {
125
- resolved = fn.qualified_name;
126
- break;
127
- }
128
- }
129
- }
130
- sourceQualified = resolved || `${ce.source}::unknown`;
131
- }
132
-
133
- edges.push({
134
- kind: 'CALLS',
135
- source_qualified: sourceQualified,
136
- target_qualified: ce.target,
137
- file_path: sourceFile,
138
- line: ce.line,
139
- confidence: ce.confidence,
140
- });
141
- }
142
-
143
- // IMPORTS edges
144
- for (const ie of importEdges) {
145
- edges.push({
146
- kind: 'IMPORTS',
147
- source_qualified: ie.source,
148
- target_qualified: ie.target,
149
- file_path: ie.source,
150
- line: ie.line,
151
- });
152
- }
153
-
154
- // Derived edges
155
- const derived = deriveEdges(raw, importEdges);
156
-
157
- for (const e of derived.inherits) {
158
- edges.push({
159
- kind: 'INHERITS',
160
- source_qualified: e.source,
161
- target_qualified: e.target,
162
- file_path: e.file || '',
163
- line: 0,
164
- });
165
- }
166
- for (const e of derived.implements) {
167
- edges.push({
168
- kind: 'IMPLEMENTS',
169
- source_qualified: e.source,
170
- target_qualified: e.target,
171
- file_path: e.file || '',
172
- line: 0,
173
- });
174
- }
175
- for (const e of derived.testedBy) {
176
- edges.push({
177
- kind: 'TESTED_BY',
178
- source_qualified: e.source,
179
- target_qualified: e.target,
180
- file_path: e.target || '',
181
- line: 0,
182
- });
183
- }
184
- for (const e of derived.contains) {
185
- edges.push({
186
- kind: 'CONTAINS',
187
- source_qualified: e.source,
188
- target_qualified: e.target,
189
- file_path: e.source,
190
- line: 0,
191
- });
192
- }
193
-
194
- return { nodes, edges };
195
- }
196
-
197
- function detectLang(file: string): string {
198
- if (file.endsWith('.ts') || file.endsWith('.tsx')) return 'typescript';
199
- if (file.endsWith('.js') || file.endsWith('.jsx') || file.endsWith('.mjs') || file.endsWith('.cjs'))
200
- return 'javascript';
201
- if (file.endsWith('.py')) return 'python';
202
- if (file.endsWith('.rb')) return 'ruby';
203
- if (file.endsWith('.go')) return 'go';
204
- if (file.endsWith('.java')) return 'java';
205
- if (file.endsWith('.rs')) return 'rust';
206
- if (file.endsWith('.cs')) return 'csharp';
207
- if (file.endsWith('.php')) return 'php';
208
- return 'unknown';
209
- }
@@ -1,101 +0,0 @@
1
- import { basename, extname } from 'path';
2
- import type { ImportEdge, RawGraph } from './types';
3
-
4
- interface DerivedEdge {
5
- source: string;
6
- target: string;
7
- file?: string;
8
- }
9
-
10
- export interface DerivedEdges {
11
- inherits: DerivedEdge[];
12
- implements: DerivedEdge[];
13
- testedBy: DerivedEdge[];
14
- contains: DerivedEdge[];
15
- }
16
-
17
- /**
18
- * Extract the "stem" from a test file name by stripping test-related
19
- * prefixes/suffixes. Returns null if no test pattern was found.
20
- */
21
- export function extractTestStem(testFile: string): string | null {
22
- const base = basename(testFile, extname(testFile));
23
- const cleaned = base
24
- .replace(/_spec$/, '') // user_spec → user (Ruby/RSpec)
25
- .replace(/_test$/, '') // user_test → user (Python/Go)
26
- .replace(/^test_/, '') // test_user → user (Python)
27
- .replace(/\.test$/, '') // user.test → user (JS/TS)
28
- .replace(/\.spec$/, '') // user.spec → user (JS/TS)
29
- .replace(/-test$/, '') // user-test → user
30
- .replace(/-spec$/, '') // user-spec → user
31
- .replace(/^spec_/, '') // spec_user → user
32
- .replace(/Test$/, '') // UserTest → User (Java)
33
- .replace(/Spec$/, ''); // UserSpec → User (Scala)
34
- if (!cleaned || cleaned === base) return null;
35
- return cleaned;
36
- }
37
-
38
- export function deriveEdges(graph: RawGraph, importEdges: ImportEdge[]): DerivedEdges {
39
- // INHERITS: class extends another class
40
- const inherits = graph.classes
41
- .filter((c) => c.extends)
42
- .map((c) => ({ source: c.qualified, target: c.extends, file: c.file }));
43
-
44
- // IMPLEMENTS: class implements interface
45
- const implements_ = graph.classes
46
- .filter((c) => c.implements)
47
- .map((c) => ({ source: c.qualified, target: c.implements, file: c.file }));
48
-
49
- // TESTED_BY: two heuristics, deduplicated
50
- const testFiles = new Set(graph.tests.map((t) => t.file));
51
- const testedBySet = new Set<string>();
52
- const testedBy: DerivedEdge[] = [];
53
-
54
- const addTestedBy = (source: string, target: string) => {
55
- const key = `${source}|${target}`;
56
- if (testedBySet.has(key)) return;
57
- testedBySet.add(key);
58
- testedBy.push({ source, target });
59
- };
60
-
61
- // Heuristic 1: Resolved imports from test files (high signal)
62
- for (const e of importEdges) {
63
- if (testFiles.has(e.source) && e.resolved) {
64
- addTestedBy(e.target, e.source);
65
- }
66
- }
67
-
68
- // Heuristic 2: File-name matching (catches Ruby, Python, and any
69
- // language where imports don't resolve)
70
- const allSourceFiles = new Set<string>();
71
- for (const f of graph.functions) allSourceFiles.add(f.file);
72
- for (const c of graph.classes) allSourceFiles.add(c.file);
73
- for (const i of graph.interfaces) allSourceFiles.add(i.file);
74
- for (const e of graph.enums) allSourceFiles.add(e.file);
75
- for (const tf of testFiles) allSourceFiles.delete(tf);
76
-
77
- const sourceByBase = new Map<string, string[]>();
78
- for (const file of allSourceFiles) {
79
- const base = basename(file, extname(file));
80
- const list = sourceByBase.get(base);
81
- if (list) list.push(file);
82
- else sourceByBase.set(base, [file]);
83
- }
84
-
85
- for (const testFile of testFiles) {
86
- const stem = extractTestStem(testFile);
87
- if (!stem) continue;
88
- const matches = sourceByBase.get(stem);
89
- if (!matches) continue;
90
- for (const sourceFile of matches) {
91
- addTestedBy(sourceFile, testFile);
92
- }
93
- }
94
-
95
- // CONTAINS: file contains function/class
96
- const contains: DerivedEdge[] = [];
97
- for (const f of graph.functions) contains.push({ source: f.file, target: f.qualified });
98
- for (const c of graph.classes) contains.push({ source: c.file, target: c.qualified });
99
-
100
- return { inherits, implements: implements_, testedBy, contains };
101
- }
@@ -1,43 +0,0 @@
1
- import { openSync, writeSync, closeSync } from 'fs';
2
- import type { GraphEdge, GraphNode, ParseMetadata } from './types';
3
-
4
- /**
5
- * Write graph output as JSON to disk using incremental serialization.
6
- *
7
- * Instead of JSON.stringify on the full output (which creates a ~100-300 MB
8
- * string for large repos), this writes each node/edge individually.
9
- * Peak memory: only one JSON.stringify(singleNode) string at a time (~1 KB).
10
- */
11
- export function writeGraphJSON(
12
- out: string,
13
- metadata: ParseMetadata,
14
- nodes: GraphNode[],
15
- edges: GraphEdge[],
16
- ): void {
17
- const fd = openSync(out, 'w');
18
-
19
- try {
20
- writeSync(fd, '{"metadata":');
21
- writeSync(fd, JSON.stringify(metadata));
22
-
23
- // Nodes
24
- writeSync(fd, ',"nodes":[');
25
- for (let i = 0; i < nodes.length; i++) {
26
- if (i > 0) writeSync(fd, ',');
27
- writeSync(fd, '\n');
28
- writeSync(fd, JSON.stringify(nodes[i]));
29
- }
30
- writeSync(fd, '\n]');
31
-
32
- // Edges
33
- writeSync(fd, ',"edges":[');
34
- for (let i = 0; i < edges.length; i++) {
35
- if (i > 0) writeSync(fd, ',');
36
- writeSync(fd, '\n');
37
- writeSync(fd, JSON.stringify(edges[i]));
38
- }
39
- writeSync(fd, '\n]}');
40
- } finally {
41
- closeSync(fd);
42
- }
43
- }
@@ -1,113 +0,0 @@
1
- // src/graph/loader.ts
2
- import { readFileSync } from 'fs';
3
- import { z } from 'zod';
4
- import type { GraphData, GraphEdge, GraphNode, ParseMetadata } from './types';
5
-
6
- const ParseOutputSchema = z.object({
7
- metadata: z.object({
8
- repo_dir: z.string(),
9
- files_parsed: z.number(),
10
- total_nodes: z.number(),
11
- total_edges: z.number(),
12
- duration_ms: z.number(),
13
- parse_errors: z.number(),
14
- extract_errors: z.number(),
15
- files_unchanged: z.number().optional(),
16
- incremental: z.boolean().optional(),
17
- }),
18
- nodes: z.array(
19
- z.object({
20
- kind: z.enum(['Function', 'Method', 'Constructor', 'Class', 'Interface', 'Enum', 'Test']),
21
- name: z.string(),
22
- qualified_name: z.string(),
23
- file_path: z.string(),
24
- line_start: z.number(),
25
- line_end: z.number(),
26
- language: z.string(),
27
- is_test: z.boolean(),
28
- file_hash: z.string(),
29
- parent_name: z.string().optional(),
30
- params: z.string().optional(),
31
- return_type: z.string().optional(),
32
- modifiers: z.string().optional(),
33
- }),
34
- ),
35
- edges: z.array(
36
- z.object({
37
- kind: z.enum(['CALLS', 'IMPORTS', 'INHERITS', 'IMPLEMENTS', 'TESTED_BY', 'CONTAINS']),
38
- source_qualified: z.string(),
39
- target_qualified: z.string(),
40
- file_path: z.string(),
41
- line: z.number(),
42
- confidence: z.number().optional(),
43
- }),
44
- ),
45
- });
46
-
47
- export interface IndexedGraph {
48
- nodes: GraphNode[];
49
- edges: GraphEdge[];
50
- byQualified: Map<string, GraphNode>;
51
- byFile: Map<string, GraphNode[]>;
52
- adjacency: Map<string, GraphEdge[]>;
53
- reverseAdjacency: Map<string, GraphEdge[]>;
54
- edgesByKind: Map<string, GraphEdge[]>;
55
- metadata: ParseMetadata;
56
- }
57
-
58
- export function indexGraph(data: GraphData, metadata?: ParseMetadata): IndexedGraph {
59
- const { nodes, edges } = data;
60
- const meta: ParseMetadata = metadata ?? {
61
- repo_dir: '',
62
- files_parsed: 0,
63
- total_nodes: nodes.length,
64
- total_edges: edges.length,
65
- duration_ms: 0,
66
- parse_errors: 0,
67
- extract_errors: 0,
68
- };
69
-
70
- const byQualified = new Map<string, GraphNode>();
71
- const byFile = new Map<string, GraphNode[]>();
72
- const adjacency = new Map<string, GraphEdge[]>();
73
- const reverseAdjacency = new Map<string, GraphEdge[]>();
74
- const edgesByKind = new Map<string, GraphEdge[]>();
75
-
76
- for (const node of nodes) {
77
- byQualified.set(node.qualified_name, node);
78
- const list = byFile.get(node.file_path);
79
- if (list) list.push(node);
80
- else byFile.set(node.file_path, [node]);
81
- }
82
-
83
- for (const edge of edges) {
84
- const fwd = adjacency.get(edge.source_qualified);
85
- if (fwd) fwd.push(edge);
86
- else adjacency.set(edge.source_qualified, [edge]);
87
-
88
- const rev = reverseAdjacency.get(edge.target_qualified);
89
- if (rev) rev.push(edge);
90
- else reverseAdjacency.set(edge.target_qualified, [edge]);
91
-
92
- const byKind = edgesByKind.get(edge.kind);
93
- if (byKind) byKind.push(edge);
94
- else edgesByKind.set(edge.kind, [edge]);
95
- }
96
-
97
- return { nodes, edges, byQualified, byFile, adjacency, reverseAdjacency, edgesByKind, metadata: meta };
98
- }
99
-
100
- export function loadGraph(path: string): IndexedGraph {
101
- let raw: unknown;
102
- try {
103
- raw = JSON.parse(readFileSync(path, 'utf-8'));
104
- } catch (err) {
105
- throw new Error(`Failed to read graph file: ${path} — ${String(err)}`);
106
- }
107
-
108
- const parsed = ParseOutputSchema.parse(raw);
109
- return indexGraph(
110
- { nodes: parsed.nodes as GraphNode[], edges: parsed.edges as GraphEdge[] },
111
- parsed.metadata as ParseMetadata,
112
- );
113
- }
@@ -1,25 +0,0 @@
1
- import type { GraphData, MainGraphInput } from './types';
2
-
3
- /**
4
- * Merge local parse (PR changed files) with the main graph (from Postgres).
5
- * Replaces all nodes/edges from changed files with the local parse.
6
- * Keeps everything else from the main graph intact.
7
- */
8
- export function mergeGraphs(
9
- mainGraph: MainGraphInput | null,
10
- localParse: GraphData,
11
- changedFiles: string[],
12
- ): GraphData {
13
- if (!mainGraph) return localParse;
14
-
15
- const changedSet = new Set(changedFiles);
16
-
17
- // Keep main graph nodes/edges NOT in changed files
18
- const mainNodes = mainGraph.nodes.filter((n) => !changedSet.has(n.file_path));
19
- const mainEdges = mainGraph.edges.filter((e) => !changedSet.has(e.file_path));
20
-
21
- return {
22
- nodes: [...mainNodes, ...localParse.nodes],
23
- edges: [...mainEdges, ...localParse.edges],
24
- };
25
- }