@kodus/kodus-graph 0.2.7 → 0.2.9

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 (167) 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 +57 -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 +83 -0
  9. package/dist/analysis/diff.d.ts +35 -0
  10. package/dist/analysis/diff.js +140 -0
  11. package/dist/analysis/enrich.d.ts +5 -0
  12. package/dist/analysis/enrich.js +98 -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 +166 -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 +208 -0
  27. package/dist/commands/analyze.d.ts +9 -0
  28. package/dist/commands/analyze.js +114 -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 +10 -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 +2 -0
  44. package/dist/graph/builder.js +216 -0
  45. package/dist/graph/edges.d.ts +19 -0
  46. package/dist/graph/edges.js +105 -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 +249 -0
  54. package/dist/graph/types.js +1 -0
  55. package/dist/parser/batch.d.ts +4 -0
  56. package/dist/parser/batch.js +78 -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 +303 -0
  71. package/dist/resolver/call-resolver.d.ts +36 -0
  72. package/dist/resolver/call-resolver.js +178 -0
  73. package/dist/resolver/import-map.d.ts +12 -0
  74. package/dist/resolver/import-map.js +21 -0
  75. package/dist/resolver/import-resolver.d.ts +19 -0
  76. package/dist/resolver/import-resolver.js +212 -0
  77. package/dist/resolver/languages/csharp.d.ts +1 -0
  78. package/dist/resolver/languages/csharp.js +31 -0
  79. package/dist/resolver/languages/go.d.ts +3 -0
  80. package/dist/resolver/languages/go.js +196 -0
  81. package/dist/resolver/languages/java.d.ts +1 -0
  82. package/dist/resolver/languages/java.js +108 -0
  83. package/dist/resolver/languages/php.d.ts +3 -0
  84. package/dist/resolver/languages/php.js +54 -0
  85. package/dist/resolver/languages/python.d.ts +11 -0
  86. package/dist/resolver/languages/python.js +51 -0
  87. package/dist/resolver/languages/ruby.d.ts +9 -0
  88. package/dist/resolver/languages/ruby.js +59 -0
  89. package/dist/resolver/languages/rust.d.ts +1 -0
  90. package/dist/resolver/languages/rust.js +196 -0
  91. package/dist/resolver/languages/typescript.d.ts +27 -0
  92. package/dist/resolver/languages/typescript.js +240 -0
  93. package/dist/resolver/re-export-resolver.d.ts +24 -0
  94. package/dist/resolver/re-export-resolver.js +57 -0
  95. package/dist/resolver/symbol-table.d.ts +17 -0
  96. package/dist/resolver/symbol-table.js +60 -0
  97. package/dist/shared/extract-calls.d.ts +26 -0
  98. package/dist/shared/extract-calls.js +57 -0
  99. package/dist/shared/file-hash.d.ts +3 -0
  100. package/dist/shared/file-hash.js +10 -0
  101. package/dist/shared/filters.d.ts +3 -0
  102. package/dist/shared/filters.js +240 -0
  103. package/dist/shared/logger.d.ts +6 -0
  104. package/dist/shared/logger.js +17 -0
  105. package/dist/shared/qualified-name.d.ts +1 -0
  106. package/dist/shared/qualified-name.js +9 -0
  107. package/dist/shared/safe-path.d.ts +6 -0
  108. package/dist/shared/safe-path.js +29 -0
  109. package/dist/shared/schemas.d.ts +43 -0
  110. package/dist/shared/schemas.js +30 -0
  111. package/dist/shared/temp.d.ts +11 -0
  112. package/{src/shared/temp.ts → dist/shared/temp.js} +4 -5
  113. package/package.json +20 -6
  114. package/src/analysis/blast-radius.ts +0 -54
  115. package/src/analysis/communities.ts +0 -135
  116. package/src/analysis/context-builder.ts +0 -130
  117. package/src/analysis/diff.ts +0 -131
  118. package/src/analysis/enrich.ts +0 -110
  119. package/src/analysis/flows.ts +0 -112
  120. package/src/analysis/inheritance.ts +0 -34
  121. package/src/analysis/prompt-formatter.ts +0 -175
  122. package/src/analysis/risk-score.ts +0 -62
  123. package/src/analysis/search.ts +0 -76
  124. package/src/analysis/test-gaps.ts +0 -21
  125. package/src/cli.ts +0 -207
  126. package/src/commands/analyze.ts +0 -128
  127. package/src/commands/communities.ts +0 -19
  128. package/src/commands/context.ts +0 -139
  129. package/src/commands/diff.ts +0 -96
  130. package/src/commands/flows.ts +0 -19
  131. package/src/commands/parse.ts +0 -124
  132. package/src/commands/search.ts +0 -41
  133. package/src/commands/update.ts +0 -166
  134. package/src/graph/builder.ts +0 -209
  135. package/src/graph/edges.ts +0 -101
  136. package/src/graph/json-writer.ts +0 -43
  137. package/src/graph/loader.ts +0 -113
  138. package/src/graph/merger.ts +0 -25
  139. package/src/graph/types.ts +0 -283
  140. package/src/parser/batch.ts +0 -82
  141. package/src/parser/discovery.ts +0 -75
  142. package/src/parser/extractor.ts +0 -37
  143. package/src/parser/extractors/generic.ts +0 -132
  144. package/src/parser/extractors/python.ts +0 -133
  145. package/src/parser/extractors/ruby.ts +0 -147
  146. package/src/parser/extractors/typescript.ts +0 -350
  147. package/src/parser/languages.ts +0 -122
  148. package/src/resolver/call-resolver.ts +0 -244
  149. package/src/resolver/import-map.ts +0 -27
  150. package/src/resolver/import-resolver.ts +0 -72
  151. package/src/resolver/languages/csharp.ts +0 -7
  152. package/src/resolver/languages/go.ts +0 -7
  153. package/src/resolver/languages/java.ts +0 -7
  154. package/src/resolver/languages/php.ts +0 -7
  155. package/src/resolver/languages/python.ts +0 -35
  156. package/src/resolver/languages/ruby.ts +0 -21
  157. package/src/resolver/languages/rust.ts +0 -7
  158. package/src/resolver/languages/typescript.ts +0 -168
  159. package/src/resolver/re-export-resolver.ts +0 -66
  160. package/src/resolver/symbol-table.ts +0 -67
  161. package/src/shared/extract-calls.ts +0 -75
  162. package/src/shared/file-hash.ts +0 -12
  163. package/src/shared/filters.ts +0 -243
  164. package/src/shared/logger.ts +0 -14
  165. package/src/shared/qualified-name.ts +0 -5
  166. package/src/shared/safe-path.ts +0 -31
  167. package/src/shared/schemas.ts +0 -32
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Symbol table with dual-index lookup (GitNexus pattern).
3
+ *
4
+ * Provides both exact (file + name) and global (name-only) lookups.
5
+ * The exact lookup is high-confidence; global lookup is lower-confidence
6
+ * but useful when import resolution fails.
7
+ */
8
+ export function createSymbolTable() {
9
+ const byFile = new Map();
10
+ const byName = new Map();
11
+ return {
12
+ add(file, name, qualified) {
13
+ if (!byFile.has(file)) {
14
+ byFile.set(file, new Map());
15
+ }
16
+ const fileMap = byFile.get(file);
17
+ if (!fileMap.has(name)) {
18
+ fileMap.set(name, []);
19
+ }
20
+ fileMap.get(name).push(qualified);
21
+ if (!byName.has(name)) {
22
+ byName.set(name, []);
23
+ }
24
+ byName.get(name).push(qualified);
25
+ },
26
+ lookupExact(file, name) {
27
+ const candidates = byFile.get(file)?.get(name);
28
+ if (!candidates || candidates.length === 0) {
29
+ return null;
30
+ }
31
+ // Only return if unambiguous within this file
32
+ return candidates.length === 1 ? candidates[0] : null;
33
+ },
34
+ lookupInFile(file, name, className) {
35
+ const candidates = byFile.get(file)?.get(name);
36
+ if (!candidates || candidates.length === 0) {
37
+ return null;
38
+ }
39
+ return candidates.find((q) => q.includes(`::${className}.${name}`)) ?? null;
40
+ },
41
+ isUnique(name) {
42
+ return (byName.get(name)?.length ?? 0) === 1;
43
+ },
44
+ lookupGlobal(name) {
45
+ return byName.get(name) ?? [];
46
+ },
47
+ get size() {
48
+ let count = 0;
49
+ for (const m of byFile.values()) {
50
+ for (const arr of m.values()) {
51
+ count += arr.length;
52
+ }
53
+ }
54
+ return count;
55
+ },
56
+ get fileCount() {
57
+ return byFile.size;
58
+ },
59
+ };
60
+ }
@@ -0,0 +1,26 @@
1
+ import type { SgNode } from '@ast-grep/napi';
2
+ import type { RawCallSite } from '../graph/types';
3
+ /**
4
+ * Language-specific configuration for call extraction.
5
+ * Each language provides its self/super patterns and how to find class context in the AST.
6
+ */
7
+ export interface CallExtractionConfig {
8
+ /** Prefixes indicating a self-reference (e.g., 'self.', 'this.') */
9
+ selfPrefixes: string[];
10
+ /** Prefixes indicating a super-reference (e.g., 'super().', 'super.', 'base.') */
11
+ superPrefixes: string[];
12
+ /** Find the enclosing class/module/impl node from a call site */
13
+ findEnclosingClass: (node: SgNode) => SgNode | null;
14
+ /** Extract the parent class name from a class node (for super resolution) */
15
+ getParentClass?: (classNode: SgNode) => string | undefined;
16
+ /** Skip this callee entirely (e.g., TS skips this.field.method — handled by DI) */
17
+ skipCallee?: (callee: string) => boolean;
18
+ }
19
+ /**
20
+ * Shared call extraction for all languages.
21
+ *
22
+ * Parses `$CALLEE($$$ARGS)` pattern, detects self/super references
23
+ * based on language config, and populates resolveInClass for
24
+ * class-aware resolution downstream.
25
+ */
26
+ export declare function extractCalls(rootNode: SgNode, fp: string, config: CallExtractionConfig, calls: RawCallSite[]): void;
@@ -0,0 +1,57 @@
1
+ import { NOISE } from './filters';
2
+ /**
3
+ * Shared call extraction for all languages.
4
+ *
5
+ * Parses `$CALLEE($$$ARGS)` pattern, detects self/super references
6
+ * based on language config, and populates resolveInClass for
7
+ * class-aware resolution downstream.
8
+ */
9
+ export function extractCalls(rootNode, fp, config, calls) {
10
+ for (const m of rootNode.findAll('$CALLEE($$$ARGS)')) {
11
+ const callee = m.getMatch('CALLEE')?.text();
12
+ if (!callee) {
13
+ continue;
14
+ }
15
+ if (config.skipCallee?.(callee)) {
16
+ continue;
17
+ }
18
+ const callName = callee.includes('.') ? callee.split('.').pop() : callee;
19
+ if (NOISE.has(callName)) {
20
+ continue;
21
+ }
22
+ let resolveInClass;
23
+ // Check self-reference: callee must be exactly `prefix + methodName` (no further chaining)
24
+ for (const prefix of config.selfPrefixes) {
25
+ if (!callee.startsWith(prefix)) {
26
+ continue;
27
+ }
28
+ const rest = callee.substring(prefix.length);
29
+ if (rest.includes('.')) {
30
+ break; // chained access (e.g., this.field.method) — not a self call
31
+ }
32
+ const classNode = config.findEnclosingClass(m);
33
+ resolveInClass = classNode?.field('name')?.text();
34
+ break;
35
+ }
36
+ // Check super-reference if no self match
37
+ if (!resolveInClass) {
38
+ for (const prefix of config.superPrefixes) {
39
+ const matches = callee === prefix || (callee.startsWith(prefix) && !callee.substring(prefix.length).includes('.'));
40
+ if (!matches) {
41
+ continue;
42
+ }
43
+ const classNode = config.findEnclosingClass(m);
44
+ if (classNode && config.getParentClass) {
45
+ resolveInClass = config.getParentClass(classNode);
46
+ }
47
+ break;
48
+ }
49
+ }
50
+ calls.push({
51
+ source: fp,
52
+ callName,
53
+ line: m.range().start.line,
54
+ ...(resolveInClass ? { resolveInClass } : {}),
55
+ });
56
+ }
57
+ }
@@ -0,0 +1,3 @@
1
+ export declare function computeFileHash(filePath: string): string;
2
+ /** Hash a node's source text (function body, class body, etc.) */
3
+ export declare function computeContentHash(sourceText: string): string;
@@ -0,0 +1,10 @@
1
+ import { createHash } from 'crypto';
2
+ import { readFileSync } from 'fs';
3
+ export function computeFileHash(filePath) {
4
+ const content = readFileSync(filePath);
5
+ return createHash('sha256').update(content).digest('hex');
6
+ }
7
+ /** Hash a node's source text (function body, class body, etc.) */
8
+ export function computeContentHash(sourceText) {
9
+ return createHash('sha256').update(sourceText).digest('hex');
10
+ }
@@ -0,0 +1,3 @@
1
+ export declare const SKIP_DIRS: Set<string>;
2
+ export declare function isSkippableFile(fileName: string): boolean;
3
+ export declare const NOISE: Set<string>;
@@ -0,0 +1,240 @@
1
+ export const SKIP_DIRS = new Set([
2
+ 'node_modules',
3
+ '.git',
4
+ 'dist',
5
+ 'build',
6
+ '.next',
7
+ 'coverage',
8
+ 'vendor',
9
+ '__pycache__',
10
+ '.venv',
11
+ 'venv',
12
+ 'target',
13
+ '.turbo',
14
+ '.cache',
15
+ '.output',
16
+ 'out',
17
+ '.nuxt',
18
+ '.svelte-kit',
19
+ '.idea',
20
+ '.mypy_cache',
21
+ '.tox',
22
+ '.pytest_cache',
23
+ '.eggs',
24
+ 'bower_components',
25
+ ]);
26
+ /** File name patterns to skip during discovery (minified, bundled, vendored) */
27
+ const SKIP_FILE_PATTERNS = [
28
+ /\.min\.\w+$/, // *.min.js, *.min.css
29
+ /[.-]bundle\.\w+$/, // *.bundle.js, *-bundle.js
30
+ /\.chunk\.\w+$/, // *.chunk.js (webpack)
31
+ /\.packed\.\w+$/, // *.packed.js
32
+ ];
33
+ export function isSkippableFile(fileName) {
34
+ return SKIP_FILE_PATTERNS.some((p) => p.test(fileName));
35
+ }
36
+ export const NOISE = new Set([
37
+ // JS/TS builtins
38
+ 'log',
39
+ 'error',
40
+ 'warn',
41
+ 'info',
42
+ 'debug',
43
+ 'trace',
44
+ 'push',
45
+ 'pop',
46
+ 'shift',
47
+ 'unshift',
48
+ 'splice',
49
+ 'slice',
50
+ 'map',
51
+ 'filter',
52
+ 'reduce',
53
+ 'forEach',
54
+ 'find',
55
+ 'findIndex',
56
+ 'some',
57
+ 'every',
58
+ 'flat',
59
+ 'flatMap',
60
+ 'sort',
61
+ 'reverse',
62
+ 'join',
63
+ 'split',
64
+ 'trim',
65
+ 'replace',
66
+ 'match',
67
+ 'test',
68
+ 'includes',
69
+ 'indexOf',
70
+ 'lastIndexOf',
71
+ 'startsWith',
72
+ 'endsWith',
73
+ 'keys',
74
+ 'values',
75
+ 'entries',
76
+ 'assign',
77
+ 'freeze',
78
+ 'create',
79
+ 'stringify',
80
+ 'parse',
81
+ 'toString',
82
+ 'toLowerCase',
83
+ 'toUpperCase',
84
+ 'concat',
85
+ 'charAt',
86
+ 'substring',
87
+ 'parseInt',
88
+ 'parseFloat',
89
+ 'isNaN',
90
+ 'isFinite',
91
+ 'isArray',
92
+ 'resolve',
93
+ 'reject',
94
+ 'all',
95
+ 'allSettled',
96
+ 'race',
97
+ 'any',
98
+ 'then',
99
+ 'catch',
100
+ 'finally',
101
+ 'get',
102
+ 'set',
103
+ 'has',
104
+ 'delete',
105
+ 'clear',
106
+ 'add',
107
+ 'next',
108
+ 'return',
109
+ 'throw',
110
+ 'setTimeout',
111
+ 'clearTimeout',
112
+ 'setInterval',
113
+ 'clearInterval',
114
+ 'require',
115
+ 'length',
116
+ 'call',
117
+ 'apply',
118
+ 'bind',
119
+ 'createElement',
120
+ 'useState',
121
+ 'useEffect',
122
+ 'useRef',
123
+ 'useCallback',
124
+ 'useMemo',
125
+ 'useContext',
126
+ 'useReducer',
127
+ 'render',
128
+ // Test helpers
129
+ 'expect',
130
+ 'toBe',
131
+ 'toEqual',
132
+ 'toBeDefined',
133
+ 'toBeNull',
134
+ 'toBeUndefined',
135
+ 'toBeTruthy',
136
+ 'toBeFalsy',
137
+ 'toContain',
138
+ 'toHaveLength',
139
+ 'toThrow',
140
+ 'toHaveBeenCalled',
141
+ 'toHaveBeenCalledWith',
142
+ 'toMatchObject',
143
+ 'toHaveBeenCalledTimes',
144
+ 'toHaveProperty',
145
+ 'describe',
146
+ 'it',
147
+ 'test',
148
+ 'beforeEach',
149
+ 'afterEach',
150
+ 'beforeAll',
151
+ 'afterAll',
152
+ 'fn',
153
+ 'spyOn',
154
+ 'mock',
155
+ 'mockResolvedValue',
156
+ 'mockReturnValue',
157
+ 'mockImplementation',
158
+ 'mockReturnThis',
159
+ 'now',
160
+ 'toISOString',
161
+ 'getTime',
162
+ // Globals
163
+ 'console',
164
+ 'Math',
165
+ 'Date',
166
+ 'JSON',
167
+ 'Object',
168
+ 'Array',
169
+ 'String',
170
+ 'Number',
171
+ 'Boolean',
172
+ 'Promise',
173
+ 'Error',
174
+ 'Map',
175
+ 'Set',
176
+ 'RegExp',
177
+ 'Buffer',
178
+ 'process',
179
+ // Python builtins
180
+ 'print',
181
+ 'len',
182
+ 'range',
183
+ 'enumerate',
184
+ 'zip',
185
+ 'isinstance',
186
+ 'type',
187
+ 'super',
188
+ 'self',
189
+ 'cls',
190
+ 'None',
191
+ 'True',
192
+ 'False',
193
+ 'append',
194
+ 'extend',
195
+ 'insert',
196
+ 'remove',
197
+ 'update',
198
+ 'items',
199
+ 'format',
200
+ 'strip',
201
+ 'upper',
202
+ 'lower',
203
+ // Ruby builtins
204
+ 'puts',
205
+ 'raise',
206
+ 'yield',
207
+ 'each',
208
+ 'do',
209
+ 'end',
210
+ 'attr_accessor',
211
+ 'attr_reader',
212
+ 'attr_writer',
213
+ 'respond_to',
214
+ 'render',
215
+ 'redirect_to',
216
+ 'before_action',
217
+ 'after_action',
218
+ 'validates',
219
+ 'has_many',
220
+ 'belongs_to',
221
+ 'has_one',
222
+ 'new',
223
+ 'initialize',
224
+ // Go builtins
225
+ 'fmt',
226
+ 'Println',
227
+ 'Printf',
228
+ 'Sprintf',
229
+ 'Errorf',
230
+ 'make',
231
+ 'panic',
232
+ 'recover',
233
+ 'defer',
234
+ // Java builtins
235
+ 'System',
236
+ 'println',
237
+ 'equals',
238
+ 'hashCode',
239
+ 'getClass',
240
+ ]);
@@ -0,0 +1,6 @@
1
+ export declare const log: {
2
+ info(msg: string, ctx?: Record<string, unknown>): void;
3
+ debug(msg: string, ctx?: Record<string, unknown>): void;
4
+ warn(msg: string, ctx?: Record<string, unknown>): void;
5
+ error(msg: string, ctx?: Record<string, unknown>): void;
6
+ };
@@ -0,0 +1,17 @@
1
+ // src/shared/logger.ts
2
+ export const log = {
3
+ info(msg, ctx) {
4
+ process.stderr.write(`[INFO] ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : ''}\n`);
5
+ },
6
+ debug(msg, ctx) {
7
+ if (process.env.KODUS_GRAPH_DEBUG) {
8
+ process.stderr.write(`[DEBUG] ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : ''}\n`);
9
+ }
10
+ },
11
+ warn(msg, ctx) {
12
+ process.stderr.write(`[WARN] ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : ''}\n`);
13
+ },
14
+ error(msg, ctx) {
15
+ process.stderr.write(`[ERROR] ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : ''}\n`);
16
+ },
17
+ };
@@ -0,0 +1 @@
1
+ export declare function qualifiedName(filePath: string, name: string, className?: string, isTest?: boolean): string;
@@ -0,0 +1,9 @@
1
+ export function qualifiedName(filePath, name, className, isTest) {
2
+ if (isTest) {
3
+ return `${filePath}::test:${name}`;
4
+ }
5
+ if (className) {
6
+ return `${filePath}::${className}.${name}`;
7
+ }
8
+ return `${filePath}::${name}`;
9
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Validate that a resolved path is within the repository root.
3
+ * Returns the validated absolute path.
4
+ * Throws if the path escapes the root.
5
+ */
6
+ export declare function ensureWithinRoot(filePath: string, repoRoot: string): string;
@@ -0,0 +1,29 @@
1
+ import { realpathSync } from 'fs';
2
+ import { relative, resolve } from 'path';
3
+ /**
4
+ * Validate that a resolved path is within the repository root.
5
+ * Returns the validated absolute path.
6
+ * Throws if the path escapes the root.
7
+ */
8
+ export function ensureWithinRoot(filePath, repoRoot) {
9
+ let absRoot;
10
+ try {
11
+ absRoot = realpathSync(resolve(repoRoot));
12
+ }
13
+ catch {
14
+ absRoot = resolve(repoRoot);
15
+ }
16
+ let absPath;
17
+ try {
18
+ absPath = realpathSync(resolve(absRoot, filePath));
19
+ }
20
+ catch {
21
+ // File doesn't exist yet or is unreadable — use resolve without symlink follow
22
+ absPath = resolve(absRoot, filePath);
23
+ }
24
+ const rel = relative(absRoot, absPath);
25
+ if (rel.startsWith('..') || resolve(absRoot, rel) !== absPath) {
26
+ throw new Error(`Path escapes repository root: ${filePath}`);
27
+ }
28
+ return absPath;
29
+ }
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ export declare const GraphInputSchema: z.ZodObject<{
3
+ sha: z.ZodOptional<z.ZodString>;
4
+ nodes: z.ZodArray<z.ZodObject<{
5
+ kind: z.ZodEnum<{
6
+ Function: "Function";
7
+ Method: "Method";
8
+ Constructor: "Constructor";
9
+ Class: "Class";
10
+ Interface: "Interface";
11
+ Enum: "Enum";
12
+ Test: "Test";
13
+ }>;
14
+ name: z.ZodString;
15
+ qualified_name: z.ZodString;
16
+ file_path: z.ZodString;
17
+ line_start: z.ZodNumber;
18
+ line_end: z.ZodNumber;
19
+ language: z.ZodString;
20
+ is_test: z.ZodBoolean;
21
+ file_hash: z.ZodOptional<z.ZodString>;
22
+ content_hash: z.ZodOptional<z.ZodString>;
23
+ parent_name: z.ZodOptional<z.ZodString>;
24
+ params: z.ZodOptional<z.ZodString>;
25
+ return_type: z.ZodOptional<z.ZodString>;
26
+ modifiers: z.ZodOptional<z.ZodString>;
27
+ }, z.core.$strip>>;
28
+ edges: z.ZodArray<z.ZodObject<{
29
+ kind: z.ZodEnum<{
30
+ CALLS: "CALLS";
31
+ IMPORTS: "IMPORTS";
32
+ INHERITS: "INHERITS";
33
+ IMPLEMENTS: "IMPLEMENTS";
34
+ TESTED_BY: "TESTED_BY";
35
+ CONTAINS: "CONTAINS";
36
+ }>;
37
+ source_qualified: z.ZodString;
38
+ target_qualified: z.ZodString;
39
+ file_path: z.ZodString;
40
+ line: z.ZodNumber;
41
+ confidence: z.ZodOptional<z.ZodNumber>;
42
+ }, z.core.$strip>>;
43
+ }, z.core.$strip>;
@@ -0,0 +1,30 @@
1
+ import { z } from 'zod';
2
+ const GraphNodeSchema = z.object({
3
+ kind: z.enum(['Function', 'Method', 'Constructor', 'Class', 'Interface', 'Enum', 'Test']),
4
+ name: z.string(),
5
+ qualified_name: z.string(),
6
+ file_path: z.string(),
7
+ line_start: z.number(),
8
+ line_end: z.number(),
9
+ language: z.string(),
10
+ is_test: z.boolean(),
11
+ file_hash: z.string().optional(),
12
+ content_hash: z.string().optional(),
13
+ parent_name: z.string().optional(),
14
+ params: z.string().optional(),
15
+ return_type: z.string().optional(),
16
+ modifiers: z.string().optional(),
17
+ });
18
+ const GraphEdgeSchema = z.object({
19
+ kind: z.enum(['CALLS', 'IMPORTS', 'INHERITS', 'IMPLEMENTS', 'TESTED_BY', 'CONTAINS']),
20
+ source_qualified: z.string(),
21
+ target_qualified: z.string(),
22
+ file_path: z.string(),
23
+ line: z.number(),
24
+ confidence: z.number().optional(),
25
+ });
26
+ export const GraphInputSchema = z.object({
27
+ sha: z.string().optional(),
28
+ nodes: z.array(GraphNodeSchema),
29
+ edges: z.array(GraphEdgeSchema),
30
+ });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Create a secure temp directory + file path.
3
+ * Directory is created with 0700 permissions via mkdtempSync.
4
+ * File name uses crypto.randomBytes for unpredictability.
5
+ *
6
+ * Caller is responsible for cleanup (rmSync(dir, { recursive: true, force: true })).
7
+ */
8
+ export declare function createSecureTempFile(prefix: string): {
9
+ dir: string;
10
+ filePath: string;
11
+ };
@@ -2,7 +2,6 @@ import { randomBytes } from 'crypto';
2
2
  import { mkdtempSync } from 'fs';
3
3
  import { tmpdir } from 'os';
4
4
  import { join } from 'path';
5
-
6
5
  /**
7
6
  * Create a secure temp directory + file path.
8
7
  * Directory is created with 0700 permissions via mkdtempSync.
@@ -10,8 +9,8 @@ import { join } from 'path';
10
9
  *
11
10
  * Caller is responsible for cleanup (rmSync(dir, { recursive: true, force: true })).
12
11
  */
13
- export function createSecureTempFile(prefix: string): { dir: string; filePath: string } {
14
- const dir = mkdtempSync(join(tmpdir(), `kodus-graph-${prefix}-`));
15
- const filePath = join(dir, `${randomBytes(8).toString('hex')}.json`);
16
- return { dir, filePath };
12
+ export function createSecureTempFile(prefix) {
13
+ const dir = mkdtempSync(join(tmpdir(), `kodus-graph-${prefix}-`));
14
+ const filePath = join(dir, `${randomBytes(8).toString('hex')}.json`);
15
+ return { dir, filePath };
17
16
  }
package/package.json CHANGED
@@ -1,13 +1,22 @@
1
1
  {
2
2
  "name": "@kodus/kodus-graph",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
4
4
  "description": "Code graph builder for Kodus code review — parses source code into structural graphs with nodes, edges, and analysis",
5
5
  "type": "module",
6
+ "main": "./dist/cli.js",
7
+ "types": "./dist/cli.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/cli.d.ts",
11
+ "import": "./dist/cli.js"
12
+ }
13
+ },
6
14
  "bin": {
7
- "kodus-graph": "./src/cli.ts"
15
+ "kodus-graph": "./dist/cli.js"
8
16
  },
9
17
  "files": [
10
- "src/**/*.ts",
18
+ "dist/**/*.js",
19
+ "dist/**/*.d.ts",
11
20
  "README.md",
12
21
  "LICENSE"
13
22
  ],
@@ -19,7 +28,9 @@
19
28
  "format": "biome format --write src/ tests/",
20
29
  "typecheck": "tsc --noEmit",
21
30
  "check": "bun run typecheck && bun run lint && bun test",
22
- "build": "bun build src/cli.ts --compile --outfile kodus-graph",
31
+ "build:dist": "tsc -p tsconfig.build.json",
32
+ "prepublishOnly": "bun run build:dist",
33
+ "build": "bun build src/cli.ts --compile --outfile dist/kodus-graph",
23
34
  "build:all": "bun run build:darwin-arm64 && bun run build:darwin-x64 && bun run build:linux-x64 && bun run build:linux-arm64",
24
35
  "build:darwin-arm64": "bun build src/cli.ts --compile --target=bun-darwin-arm64 --outfile dist/kodus-graph-darwin-arm64",
25
36
  "build:darwin-x64": "bun build src/cli.ts --compile --target=bun-darwin-x64 --outfile dist/kodus-graph-darwin-x64",
@@ -39,8 +50,11 @@
39
50
  "type": "git",
40
51
  "url": "https://github.com/kodustech/kodus-graph.git"
41
52
  },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ },
42
56
  "engines": {
43
- "bun": ">=1.0.0"
57
+ "bun": ">=1.3.0"
44
58
  },
45
59
  "dependencies": {
46
60
  "@ast-grep/lang-csharp": "^0.0.6",
@@ -56,7 +70,7 @@
56
70
  },
57
71
  "devDependencies": {
58
72
  "@biomejs/biome": "^2.4.10",
59
- "@types/bun": "latest",
73
+ "@types/bun": "^1.3.11",
60
74
  "typescript": "^6.0.2"
61
75
  }
62
76
  }