@kodus/kodus-graph 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/package.json +62 -0
- package/src/analysis/blast-radius.ts +54 -0
- package/src/analysis/communities.ts +135 -0
- package/src/analysis/diff.ts +120 -0
- package/src/analysis/flows.ts +112 -0
- package/src/analysis/review-context.ts +141 -0
- package/src/analysis/risk-score.ts +62 -0
- package/src/analysis/search.ts +76 -0
- package/src/analysis/test-gaps.ts +21 -0
- package/src/cli.ts +192 -0
- package/src/commands/analyze.ts +66 -0
- package/src/commands/communities.ts +19 -0
- package/src/commands/context.ts +69 -0
- package/src/commands/diff.ts +96 -0
- package/src/commands/flows.ts +19 -0
- package/src/commands/parse.ts +100 -0
- package/src/commands/search.ts +41 -0
- package/src/commands/update.ts +166 -0
- package/src/graph/builder.ts +170 -0
- package/src/graph/edges.ts +101 -0
- package/src/graph/loader.ts +100 -0
- package/src/graph/merger.ts +25 -0
- package/src/graph/types.ts +218 -0
- package/src/parser/batch.ts +74 -0
- package/src/parser/discovery.ts +42 -0
- package/src/parser/extractor.ts +37 -0
- package/src/parser/extractors/generic.ts +87 -0
- package/src/parser/extractors/python.ts +127 -0
- package/src/parser/extractors/ruby.ts +142 -0
- package/src/parser/extractors/typescript.ts +329 -0
- package/src/parser/languages.ts +122 -0
- package/src/resolver/call-resolver.ts +179 -0
- package/src/resolver/import-map.ts +27 -0
- package/src/resolver/import-resolver.ts +72 -0
- package/src/resolver/languages/csharp.ts +7 -0
- package/src/resolver/languages/go.ts +7 -0
- package/src/resolver/languages/java.ts +7 -0
- package/src/resolver/languages/php.ts +7 -0
- package/src/resolver/languages/python.ts +35 -0
- package/src/resolver/languages/ruby.ts +21 -0
- package/src/resolver/languages/rust.ts +7 -0
- package/src/resolver/languages/typescript.ts +168 -0
- package/src/resolver/symbol-table.ts +53 -0
- package/src/shared/file-hash.ts +7 -0
- package/src/shared/filters.ts +243 -0
- package/src/shared/logger.ts +14 -0
- package/src/shared/qualified-name.ts +5 -0
- package/src/shared/safe-path.ts +31 -0
- package/src/shared/schemas.ts +31 -0
- package/src/shared/temp.ts +17 -0
|
@@ -0,0 +1,243 @@
|
|
|
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
|
+
|
|
27
|
+
/** File name patterns to skip during discovery (minified, bundled, vendored) */
|
|
28
|
+
const SKIP_FILE_PATTERNS: RegExp[] = [
|
|
29
|
+
/\.min\.\w+$/, // *.min.js, *.min.css
|
|
30
|
+
/[.-]bundle\.\w+$/, // *.bundle.js, *-bundle.js
|
|
31
|
+
/\.chunk\.\w+$/, // *.chunk.js (webpack)
|
|
32
|
+
/\.packed\.\w+$/, // *.packed.js
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
export function isSkippableFile(fileName: string): boolean {
|
|
36
|
+
return SKIP_FILE_PATTERNS.some((p) => p.test(fileName));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const NOISE = new Set([
|
|
40
|
+
// JS/TS builtins
|
|
41
|
+
'log',
|
|
42
|
+
'error',
|
|
43
|
+
'warn',
|
|
44
|
+
'info',
|
|
45
|
+
'debug',
|
|
46
|
+
'trace',
|
|
47
|
+
'push',
|
|
48
|
+
'pop',
|
|
49
|
+
'shift',
|
|
50
|
+
'unshift',
|
|
51
|
+
'splice',
|
|
52
|
+
'slice',
|
|
53
|
+
'map',
|
|
54
|
+
'filter',
|
|
55
|
+
'reduce',
|
|
56
|
+
'forEach',
|
|
57
|
+
'find',
|
|
58
|
+
'findIndex',
|
|
59
|
+
'some',
|
|
60
|
+
'every',
|
|
61
|
+
'flat',
|
|
62
|
+
'flatMap',
|
|
63
|
+
'sort',
|
|
64
|
+
'reverse',
|
|
65
|
+
'join',
|
|
66
|
+
'split',
|
|
67
|
+
'trim',
|
|
68
|
+
'replace',
|
|
69
|
+
'match',
|
|
70
|
+
'test',
|
|
71
|
+
'includes',
|
|
72
|
+
'indexOf',
|
|
73
|
+
'lastIndexOf',
|
|
74
|
+
'startsWith',
|
|
75
|
+
'endsWith',
|
|
76
|
+
'keys',
|
|
77
|
+
'values',
|
|
78
|
+
'entries',
|
|
79
|
+
'assign',
|
|
80
|
+
'freeze',
|
|
81
|
+
'create',
|
|
82
|
+
'stringify',
|
|
83
|
+
'parse',
|
|
84
|
+
'toString',
|
|
85
|
+
'toLowerCase',
|
|
86
|
+
'toUpperCase',
|
|
87
|
+
'concat',
|
|
88
|
+
'charAt',
|
|
89
|
+
'substring',
|
|
90
|
+
'parseInt',
|
|
91
|
+
'parseFloat',
|
|
92
|
+
'isNaN',
|
|
93
|
+
'isFinite',
|
|
94
|
+
'isArray',
|
|
95
|
+
'resolve',
|
|
96
|
+
'reject',
|
|
97
|
+
'all',
|
|
98
|
+
'allSettled',
|
|
99
|
+
'race',
|
|
100
|
+
'any',
|
|
101
|
+
'then',
|
|
102
|
+
'catch',
|
|
103
|
+
'finally',
|
|
104
|
+
'get',
|
|
105
|
+
'set',
|
|
106
|
+
'has',
|
|
107
|
+
'delete',
|
|
108
|
+
'clear',
|
|
109
|
+
'add',
|
|
110
|
+
'next',
|
|
111
|
+
'return',
|
|
112
|
+
'throw',
|
|
113
|
+
'setTimeout',
|
|
114
|
+
'clearTimeout',
|
|
115
|
+
'setInterval',
|
|
116
|
+
'clearInterval',
|
|
117
|
+
'require',
|
|
118
|
+
'length',
|
|
119
|
+
'call',
|
|
120
|
+
'apply',
|
|
121
|
+
'bind',
|
|
122
|
+
'createElement',
|
|
123
|
+
'useState',
|
|
124
|
+
'useEffect',
|
|
125
|
+
'useRef',
|
|
126
|
+
'useCallback',
|
|
127
|
+
'useMemo',
|
|
128
|
+
'useContext',
|
|
129
|
+
'useReducer',
|
|
130
|
+
'render',
|
|
131
|
+
// Test helpers
|
|
132
|
+
'expect',
|
|
133
|
+
'toBe',
|
|
134
|
+
'toEqual',
|
|
135
|
+
'toBeDefined',
|
|
136
|
+
'toBeNull',
|
|
137
|
+
'toBeUndefined',
|
|
138
|
+
'toBeTruthy',
|
|
139
|
+
'toBeFalsy',
|
|
140
|
+
'toContain',
|
|
141
|
+
'toHaveLength',
|
|
142
|
+
'toThrow',
|
|
143
|
+
'toHaveBeenCalled',
|
|
144
|
+
'toHaveBeenCalledWith',
|
|
145
|
+
'toMatchObject',
|
|
146
|
+
'toHaveBeenCalledTimes',
|
|
147
|
+
'toHaveProperty',
|
|
148
|
+
'describe',
|
|
149
|
+
'it',
|
|
150
|
+
'test',
|
|
151
|
+
'beforeEach',
|
|
152
|
+
'afterEach',
|
|
153
|
+
'beforeAll',
|
|
154
|
+
'afterAll',
|
|
155
|
+
'fn',
|
|
156
|
+
'spyOn',
|
|
157
|
+
'mock',
|
|
158
|
+
'mockResolvedValue',
|
|
159
|
+
'mockReturnValue',
|
|
160
|
+
'mockImplementation',
|
|
161
|
+
'mockReturnThis',
|
|
162
|
+
'now',
|
|
163
|
+
'toISOString',
|
|
164
|
+
'getTime',
|
|
165
|
+
// Globals
|
|
166
|
+
'console',
|
|
167
|
+
'Math',
|
|
168
|
+
'Date',
|
|
169
|
+
'JSON',
|
|
170
|
+
'Object',
|
|
171
|
+
'Array',
|
|
172
|
+
'String',
|
|
173
|
+
'Number',
|
|
174
|
+
'Boolean',
|
|
175
|
+
'Promise',
|
|
176
|
+
'Error',
|
|
177
|
+
'Map',
|
|
178
|
+
'Set',
|
|
179
|
+
'RegExp',
|
|
180
|
+
'Buffer',
|
|
181
|
+
'process',
|
|
182
|
+
// Python builtins
|
|
183
|
+
'print',
|
|
184
|
+
'len',
|
|
185
|
+
'range',
|
|
186
|
+
'enumerate',
|
|
187
|
+
'zip',
|
|
188
|
+
'isinstance',
|
|
189
|
+
'type',
|
|
190
|
+
'super',
|
|
191
|
+
'self',
|
|
192
|
+
'cls',
|
|
193
|
+
'None',
|
|
194
|
+
'True',
|
|
195
|
+
'False',
|
|
196
|
+
'append',
|
|
197
|
+
'extend',
|
|
198
|
+
'insert',
|
|
199
|
+
'remove',
|
|
200
|
+
'update',
|
|
201
|
+
'items',
|
|
202
|
+
'format',
|
|
203
|
+
'strip',
|
|
204
|
+
'upper',
|
|
205
|
+
'lower',
|
|
206
|
+
// Ruby builtins
|
|
207
|
+
'puts',
|
|
208
|
+
'raise',
|
|
209
|
+
'yield',
|
|
210
|
+
'each',
|
|
211
|
+
'do',
|
|
212
|
+
'end',
|
|
213
|
+
'attr_accessor',
|
|
214
|
+
'attr_reader',
|
|
215
|
+
'attr_writer',
|
|
216
|
+
'respond_to',
|
|
217
|
+
'render',
|
|
218
|
+
'redirect_to',
|
|
219
|
+
'before_action',
|
|
220
|
+
'after_action',
|
|
221
|
+
'validates',
|
|
222
|
+
'has_many',
|
|
223
|
+
'belongs_to',
|
|
224
|
+
'has_one',
|
|
225
|
+
'new',
|
|
226
|
+
'initialize',
|
|
227
|
+
// Go builtins
|
|
228
|
+
'fmt',
|
|
229
|
+
'Println',
|
|
230
|
+
'Printf',
|
|
231
|
+
'Sprintf',
|
|
232
|
+
'Errorf',
|
|
233
|
+
'make',
|
|
234
|
+
'panic',
|
|
235
|
+
'recover',
|
|
236
|
+
'defer',
|
|
237
|
+
// Java builtins
|
|
238
|
+
'System',
|
|
239
|
+
'println',
|
|
240
|
+
'equals',
|
|
241
|
+
'hashCode',
|
|
242
|
+
'getClass',
|
|
243
|
+
]);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/shared/logger.ts
|
|
2
|
+
export const log = {
|
|
3
|
+
debug(msg: string, ctx?: Record<string, unknown>): void {
|
|
4
|
+
if (process.env.KODUS_GRAPH_DEBUG) {
|
|
5
|
+
process.stderr.write(`[DEBUG] ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : ''}\n`);
|
|
6
|
+
}
|
|
7
|
+
},
|
|
8
|
+
warn(msg: string, ctx?: Record<string, unknown>): void {
|
|
9
|
+
process.stderr.write(`[WARN] ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : ''}\n`);
|
|
10
|
+
},
|
|
11
|
+
error(msg: string, ctx?: Record<string, unknown>): void {
|
|
12
|
+
process.stderr.write(`[ERROR] ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : ''}\n`);
|
|
13
|
+
},
|
|
14
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { realpathSync } from 'fs';
|
|
2
|
+
import { relative, resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Validate that a resolved path is within the repository root.
|
|
6
|
+
* Returns the validated absolute path.
|
|
7
|
+
* Throws if the path escapes the root.
|
|
8
|
+
*/
|
|
9
|
+
export function ensureWithinRoot(filePath: string, repoRoot: string): string {
|
|
10
|
+
let absRoot: string;
|
|
11
|
+
try {
|
|
12
|
+
absRoot = realpathSync(resolve(repoRoot));
|
|
13
|
+
} catch {
|
|
14
|
+
absRoot = resolve(repoRoot);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let absPath: string;
|
|
18
|
+
try {
|
|
19
|
+
absPath = realpathSync(resolve(absRoot, filePath));
|
|
20
|
+
} catch {
|
|
21
|
+
// File doesn't exist yet or is unreadable — use resolve without symlink follow
|
|
22
|
+
absPath = resolve(absRoot, filePath);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const rel = relative(absRoot, absPath);
|
|
26
|
+
if (rel.startsWith('..') || resolve(absRoot, rel) !== absPath) {
|
|
27
|
+
throw new Error(`Path escapes repository root: ${filePath}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return absPath;
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
const GraphNodeSchema = z.object({
|
|
4
|
+
kind: z.enum(['Function', 'Method', 'Constructor', 'Class', 'Interface', 'Enum', 'Test']),
|
|
5
|
+
name: z.string(),
|
|
6
|
+
qualified_name: z.string(),
|
|
7
|
+
file_path: z.string(),
|
|
8
|
+
line_start: z.number(),
|
|
9
|
+
line_end: z.number(),
|
|
10
|
+
language: z.string(),
|
|
11
|
+
is_test: z.boolean(),
|
|
12
|
+
file_hash: z.string(),
|
|
13
|
+
parent_name: z.string().optional(),
|
|
14
|
+
params: z.string().optional(),
|
|
15
|
+
return_type: z.string().optional(),
|
|
16
|
+
modifiers: z.string().optional(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const GraphEdgeSchema = z.object({
|
|
20
|
+
kind: z.enum(['CALLS', 'IMPORTS', 'INHERITS', 'IMPLEMENTS', 'TESTED_BY', 'CONTAINS']),
|
|
21
|
+
source_qualified: z.string(),
|
|
22
|
+
target_qualified: z.string(),
|
|
23
|
+
file_path: z.string(),
|
|
24
|
+
line: z.number(),
|
|
25
|
+
confidence: z.number().optional(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export const GraphInputSchema = z.object({
|
|
29
|
+
nodes: z.array(GraphNodeSchema),
|
|
30
|
+
edges: z.array(GraphEdgeSchema),
|
|
31
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
import { mkdtempSync } from 'fs';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Create a secure temp directory + file path.
|
|
8
|
+
* Directory is created with 0700 permissions via mkdtempSync.
|
|
9
|
+
* File name uses crypto.randomBytes for unpredictability.
|
|
10
|
+
*
|
|
11
|
+
* Caller is responsible for cleanup (rmSync(dir, { recursive: true, force: true })).
|
|
12
|
+
*/
|
|
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 };
|
|
17
|
+
}
|