@kodus/kodus-graph 0.2.8 → 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.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/dist/analysis/blast-radius.d.ts +2 -0
- package/dist/analysis/blast-radius.js +57 -0
- package/dist/analysis/communities.d.ts +28 -0
- package/dist/analysis/communities.js +100 -0
- package/dist/analysis/context-builder.d.ts +34 -0
- package/dist/analysis/context-builder.js +83 -0
- package/dist/analysis/diff.d.ts +35 -0
- package/dist/analysis/diff.js +140 -0
- package/dist/analysis/enrich.d.ts +5 -0
- package/dist/analysis/enrich.js +98 -0
- package/dist/analysis/flows.d.ts +27 -0
- package/dist/analysis/flows.js +86 -0
- package/dist/analysis/inheritance.d.ts +3 -0
- package/dist/analysis/inheritance.js +31 -0
- package/dist/analysis/prompt-formatter.d.ts +2 -0
- package/dist/analysis/prompt-formatter.js +166 -0
- package/dist/analysis/risk-score.d.ts +4 -0
- package/dist/analysis/risk-score.js +51 -0
- package/dist/analysis/search.d.ts +11 -0
- package/dist/analysis/search.js +64 -0
- package/dist/analysis/test-gaps.d.ts +2 -0
- package/dist/analysis/test-gaps.js +14 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +208 -0
- package/dist/commands/analyze.d.ts +9 -0
- package/dist/commands/analyze.js +114 -0
- package/dist/commands/communities.d.ts +8 -0
- package/dist/commands/communities.js +9 -0
- package/dist/commands/context.d.ts +12 -0
- package/dist/commands/context.js +130 -0
- package/dist/commands/diff.d.ts +9 -0
- package/dist/commands/diff.js +89 -0
- package/dist/commands/flows.d.ts +8 -0
- package/dist/commands/flows.js +9 -0
- package/dist/commands/parse.d.ts +10 -0
- package/dist/commands/parse.js +101 -0
- package/dist/commands/search.d.ts +12 -0
- package/dist/commands/search.js +27 -0
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +154 -0
- package/dist/graph/builder.d.ts +2 -0
- package/dist/graph/builder.js +216 -0
- package/dist/graph/edges.d.ts +19 -0
- package/dist/graph/edges.js +105 -0
- package/dist/graph/json-writer.d.ts +9 -0
- package/dist/graph/json-writer.js +38 -0
- package/dist/graph/loader.d.ts +13 -0
- package/dist/graph/loader.js +101 -0
- package/dist/graph/merger.d.ts +7 -0
- package/dist/graph/merger.js +18 -0
- package/dist/graph/types.d.ts +249 -0
- package/dist/graph/types.js +1 -0
- package/dist/parser/batch.d.ts +4 -0
- package/dist/parser/batch.js +78 -0
- package/dist/parser/discovery.d.ts +7 -0
- package/dist/parser/discovery.js +61 -0
- package/dist/parser/extractor.d.ts +4 -0
- package/dist/parser/extractor.js +33 -0
- package/dist/parser/extractors/generic.d.ts +8 -0
- package/dist/parser/extractors/generic.js +471 -0
- package/dist/parser/extractors/python.d.ts +8 -0
- package/dist/parser/extractors/python.js +133 -0
- package/dist/parser/extractors/ruby.d.ts +8 -0
- package/dist/parser/extractors/ruby.js +153 -0
- package/dist/parser/extractors/typescript.d.ts +10 -0
- package/dist/parser/extractors/typescript.js +365 -0
- package/dist/parser/languages.d.ts +32 -0
- package/dist/parser/languages.js +303 -0
- package/dist/resolver/call-resolver.d.ts +36 -0
- package/dist/resolver/call-resolver.js +178 -0
- package/dist/resolver/import-map.d.ts +12 -0
- package/dist/resolver/import-map.js +21 -0
- package/dist/resolver/import-resolver.d.ts +19 -0
- package/dist/resolver/import-resolver.js +212 -0
- package/dist/resolver/languages/csharp.d.ts +1 -0
- package/dist/resolver/languages/csharp.js +31 -0
- package/dist/resolver/languages/go.d.ts +3 -0
- package/dist/resolver/languages/go.js +196 -0
- package/dist/resolver/languages/java.d.ts +1 -0
- package/dist/resolver/languages/java.js +108 -0
- package/dist/resolver/languages/php.d.ts +3 -0
- package/dist/resolver/languages/php.js +54 -0
- package/dist/resolver/languages/python.d.ts +11 -0
- package/dist/resolver/languages/python.js +51 -0
- package/dist/resolver/languages/ruby.d.ts +9 -0
- package/dist/resolver/languages/ruby.js +59 -0
- package/dist/resolver/languages/rust.d.ts +1 -0
- package/dist/resolver/languages/rust.js +196 -0
- package/dist/resolver/languages/typescript.d.ts +27 -0
- package/dist/resolver/languages/typescript.js +240 -0
- package/dist/resolver/re-export-resolver.d.ts +24 -0
- package/dist/resolver/re-export-resolver.js +57 -0
- package/dist/resolver/symbol-table.d.ts +17 -0
- package/dist/resolver/symbol-table.js +60 -0
- package/dist/shared/extract-calls.d.ts +26 -0
- package/dist/shared/extract-calls.js +57 -0
- package/dist/shared/file-hash.d.ts +3 -0
- package/dist/shared/file-hash.js +10 -0
- package/dist/shared/filters.d.ts +3 -0
- package/dist/shared/filters.js +240 -0
- package/dist/shared/logger.d.ts +6 -0
- package/dist/shared/logger.js +17 -0
- package/dist/shared/qualified-name.d.ts +1 -0
- package/dist/shared/qualified-name.js +9 -0
- package/dist/shared/safe-path.d.ts +6 -0
- package/dist/shared/safe-path.js +29 -0
- package/dist/shared/schemas.d.ts +43 -0
- package/dist/shared/schemas.js +30 -0
- package/dist/shared/temp.d.ts +11 -0
- package/{src/shared/temp.ts → dist/shared/temp.js} +4 -5
- package/package.json +20 -6
- package/src/analysis/blast-radius.ts +0 -54
- package/src/analysis/communities.ts +0 -135
- package/src/analysis/context-builder.ts +0 -130
- package/src/analysis/diff.ts +0 -169
- package/src/analysis/enrich.ts +0 -110
- package/src/analysis/flows.ts +0 -112
- package/src/analysis/inheritance.ts +0 -34
- package/src/analysis/prompt-formatter.ts +0 -175
- package/src/analysis/risk-score.ts +0 -62
- package/src/analysis/search.ts +0 -76
- package/src/analysis/test-gaps.ts +0 -21
- package/src/cli.ts +0 -210
- package/src/commands/analyze.ts +0 -128
- package/src/commands/communities.ts +0 -19
- package/src/commands/context.ts +0 -182
- package/src/commands/diff.ts +0 -96
- package/src/commands/flows.ts +0 -19
- package/src/commands/parse.ts +0 -124
- package/src/commands/search.ts +0 -41
- package/src/commands/update.ts +0 -166
- package/src/graph/builder.ts +0 -209
- package/src/graph/edges.ts +0 -101
- package/src/graph/json-writer.ts +0 -43
- package/src/graph/loader.ts +0 -113
- package/src/graph/merger.ts +0 -25
- package/src/graph/types.ts +0 -283
- package/src/parser/batch.ts +0 -82
- package/src/parser/discovery.ts +0 -75
- package/src/parser/extractor.ts +0 -37
- package/src/parser/extractors/generic.ts +0 -132
- package/src/parser/extractors/python.ts +0 -133
- package/src/parser/extractors/ruby.ts +0 -147
- package/src/parser/extractors/typescript.ts +0 -350
- package/src/parser/languages.ts +0 -122
- package/src/resolver/call-resolver.ts +0 -244
- package/src/resolver/import-map.ts +0 -27
- package/src/resolver/import-resolver.ts +0 -72
- package/src/resolver/languages/csharp.ts +0 -7
- package/src/resolver/languages/go.ts +0 -7
- package/src/resolver/languages/java.ts +0 -7
- package/src/resolver/languages/php.ts +0 -7
- package/src/resolver/languages/python.ts +0 -35
- package/src/resolver/languages/ruby.ts +0 -21
- package/src/resolver/languages/rust.ts +0 -7
- package/src/resolver/languages/typescript.ts +0 -168
- package/src/resolver/re-export-resolver.ts +0 -66
- package/src/resolver/symbol-table.ts +0 -67
- package/src/shared/extract-calls.ts +0 -75
- package/src/shared/file-hash.ts +0 -12
- package/src/shared/filters.ts +0 -243
- package/src/shared/logger.ts +0 -17
- package/src/shared/qualified-name.ts +0 -5
- package/src/shared/safe-path.ts +0 -31
- 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,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,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,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,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
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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.
|
|
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": "./
|
|
15
|
+
"kodus-graph": "./dist/cli.js"
|
|
8
16
|
},
|
|
9
17
|
"files": [
|
|
10
|
-
"
|
|
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": "
|
|
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.
|
|
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": "
|
|
73
|
+
"@types/bun": "^1.3.11",
|
|
60
74
|
"typescript": "^6.0.2"
|
|
61
75
|
}
|
|
62
76
|
}
|