@kodus/kodus-graph 0.2.7 → 0.2.8
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 +1 -1
- package/src/analysis/diff.ts +40 -2
- package/src/cli.ts +3 -0
- package/src/commands/context.ts +45 -2
- package/src/shared/logger.ts +3 -0
package/package.json
CHANGED
package/src/analysis/diff.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IndexedGraph } from '../graph/loader';
|
|
2
2
|
import type { GraphEdge, GraphNode } from '../graph/types';
|
|
3
|
+
import { log } from '../shared/logger';
|
|
3
4
|
|
|
4
5
|
export interface NodeChange {
|
|
5
6
|
qualified_name: string;
|
|
@@ -45,6 +46,12 @@ export function computeStructuralDiff(
|
|
|
45
46
|
if (changedSet.has(n.file_path)) newNodesMap.set(n.qualified_name, n);
|
|
46
47
|
}
|
|
47
48
|
|
|
49
|
+
log.debug('diff: input', {
|
|
50
|
+
oldNodesInChanged: oldNodesInChanged.size,
|
|
51
|
+
newNodesInChanged: newNodesMap.size,
|
|
52
|
+
changedFiles,
|
|
53
|
+
});
|
|
54
|
+
|
|
48
55
|
// Classify nodes
|
|
49
56
|
const added: NodeChange[] = [];
|
|
50
57
|
const removed: NodeChange[] = [];
|
|
@@ -79,12 +86,34 @@ export function computeStructuralDiff(
|
|
|
79
86
|
if (n.content_hash && newN.content_hash) {
|
|
80
87
|
// Definitive: hash comparison catches ALL content changes,
|
|
81
88
|
// even same-line-count edits (e.g. `return 1` → `return 2`).
|
|
82
|
-
if (n.content_hash !== newN.content_hash)
|
|
89
|
+
if (n.content_hash !== newN.content_hash) {
|
|
90
|
+
changes.push('body');
|
|
91
|
+
log.debug('diff: body change detected', {
|
|
92
|
+
node: qn,
|
|
93
|
+
oldHash: n.content_hash.substring(0, 8),
|
|
94
|
+
newHash: newN.content_hash.substring(0, 8),
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
log.debug('diff: hash match (displacement only)', {
|
|
98
|
+
node: qn,
|
|
99
|
+
oldLines: `${n.line_start}-${n.line_end}`,
|
|
100
|
+
newLines: `${newN.line_start}-${newN.line_end}`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
83
103
|
} else if (n.line_start !== newN.line_start || n.line_end !== newN.line_end) {
|
|
84
104
|
// Fallback (legacy data without content_hash): size heuristic.
|
|
85
105
|
const oldSize = n.line_end - n.line_start;
|
|
86
106
|
const newSize = newN.line_end - newN.line_start;
|
|
87
|
-
if (oldSize !== newSize)
|
|
107
|
+
if (oldSize !== newSize) {
|
|
108
|
+
changes.push('line_range');
|
|
109
|
+
log.debug('diff: line_range fallback (no content_hash)', {
|
|
110
|
+
node: qn,
|
|
111
|
+
hasOldHash: !!n.content_hash,
|
|
112
|
+
hasNewHash: !!newN.content_hash,
|
|
113
|
+
oldSize,
|
|
114
|
+
newSize,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
88
117
|
}
|
|
89
118
|
if ((n.params || '') !== (newN.params || '')) changes.push('params');
|
|
90
119
|
if ((n.return_type || '') !== (newN.return_type || '')) changes.push('return_type');
|
|
@@ -121,6 +150,15 @@ export function computeStructuralDiff(
|
|
|
121
150
|
riskByFile[file] = { dependents: count, risk };
|
|
122
151
|
}
|
|
123
152
|
|
|
153
|
+
log.info('diff: result', {
|
|
154
|
+
added: added.length,
|
|
155
|
+
removed: removed.length,
|
|
156
|
+
modified: modified.length,
|
|
157
|
+
edgesAdded: addedEdges.length,
|
|
158
|
+
edgesRemoved: removedEdges.length,
|
|
159
|
+
modifiedDetails: modified.map((m) => `${m.qualified_name} [${m.changes.join(',')}]`),
|
|
160
|
+
});
|
|
161
|
+
|
|
124
162
|
return {
|
|
125
163
|
changed_files: changedFiles,
|
|
126
164
|
summary: { added: added.length, removed: removed.length, modified: modified.length },
|
package/src/cli.ts
CHANGED
|
@@ -14,6 +14,9 @@ import { executeUpdate } from './commands/update';
|
|
|
14
14
|
const program = new Command();
|
|
15
15
|
|
|
16
16
|
import pkg from '../package.json';
|
|
17
|
+
import { log } from './shared/logger';
|
|
18
|
+
|
|
19
|
+
log.info(`kodus-graph v${pkg.version}`, { node: process.version, platform: process.platform });
|
|
17
20
|
program.name('kodus-graph').description('Code graph builder for Kodus code review').version(pkg.version);
|
|
18
21
|
|
|
19
22
|
program
|
package/src/commands/context.ts
CHANGED
|
@@ -22,6 +22,15 @@ interface ContextOptions {
|
|
|
22
22
|
export async function executeContext(opts: ContextOptions): Promise<void> {
|
|
23
23
|
const repoDir = resolve(opts.repoDir);
|
|
24
24
|
|
|
25
|
+
log.info('context: starting', {
|
|
26
|
+
files: opts.files,
|
|
27
|
+
repoDir,
|
|
28
|
+
graph: opts.graph ?? null,
|
|
29
|
+
format: opts.format,
|
|
30
|
+
minConfidence: opts.minConfidence,
|
|
31
|
+
maxDepth: opts.maxDepth,
|
|
32
|
+
});
|
|
33
|
+
|
|
25
34
|
// Parse changed files using secure temp
|
|
26
35
|
const tmp = createSecureTempFile('ctx');
|
|
27
36
|
try {
|
|
@@ -33,6 +42,11 @@ export async function executeContext(opts: ContextOptions): Promise<void> {
|
|
|
33
42
|
});
|
|
34
43
|
const parseResult = JSON.parse(readFileSync(tmp.filePath, 'utf-8'));
|
|
35
44
|
|
|
45
|
+
log.info('context: parse done', {
|
|
46
|
+
nodes: parseResult.nodes?.length ?? 0,
|
|
47
|
+
edges: parseResult.edges?.length ?? 0,
|
|
48
|
+
});
|
|
49
|
+
|
|
36
50
|
// Load and merge with main graph if provided
|
|
37
51
|
let mergedGraph: GraphData;
|
|
38
52
|
let oldGraph: GraphData | null = null;
|
|
@@ -53,6 +67,12 @@ export async function executeContext(opts: ContextOptions): Promise<void> {
|
|
|
53
67
|
const changedSet = new Set(opts.files);
|
|
54
68
|
const sameBranch = detectSameBranch(validated.data.nodes, parseResult.nodes, changedSet);
|
|
55
69
|
|
|
70
|
+
log.info('context: baseline graph loaded', {
|
|
71
|
+
graphNodes: validated.data.nodes.length,
|
|
72
|
+
graphEdges: validated.data.edges.length,
|
|
73
|
+
sameBranch,
|
|
74
|
+
});
|
|
75
|
+
|
|
56
76
|
if (sameBranch) {
|
|
57
77
|
// --graph was built from the same commit (e.g. kodus-ai's parse --all on PR branch).
|
|
58
78
|
// Exclude changed files from oldGraph so diff detects their functions as "added"
|
|
@@ -88,6 +108,16 @@ export async function executeContext(opts: ContextOptions): Promise<void> {
|
|
|
88
108
|
maxDepth: opts.maxDepth,
|
|
89
109
|
});
|
|
90
110
|
|
|
111
|
+
log.info('context: analysis done', {
|
|
112
|
+
changedFunctions: output.analysis.changed_functions.length,
|
|
113
|
+
diff: output.analysis.structural_diff.summary,
|
|
114
|
+
blastRadius: output.analysis.blast_radius.total_functions,
|
|
115
|
+
risk: `${output.analysis.risk.level} (${output.analysis.risk.score})`,
|
|
116
|
+
testGaps: output.analysis.test_gaps.length,
|
|
117
|
+
affectedFlows: output.analysis.affected_flows.length,
|
|
118
|
+
duration_ms: output.analysis.metadata.duration_ms,
|
|
119
|
+
});
|
|
120
|
+
|
|
91
121
|
if (opts.format === 'prompt') {
|
|
92
122
|
writeFileSync(opts.out, formatPrompt(output));
|
|
93
123
|
} else {
|
|
@@ -120,7 +150,10 @@ function detectSameBranch(
|
|
|
120
150
|
}
|
|
121
151
|
|
|
122
152
|
// No overlap means graph has no nodes for changed files — not same-branch scenario
|
|
123
|
-
if (graphHashes.size === 0)
|
|
153
|
+
if (graphHashes.size === 0) {
|
|
154
|
+
log.debug('detectSameBranch: no graph hashes for changed files');
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
124
157
|
|
|
125
158
|
const parseHashes = new Map<string, string>();
|
|
126
159
|
for (const n of parseNodes) {
|
|
@@ -132,8 +165,18 @@ function detectSameBranch(
|
|
|
132
165
|
// If any overlapping file has different hash → different branch
|
|
133
166
|
for (const [file, hash] of graphHashes) {
|
|
134
167
|
const parseHash = parseHashes.get(file);
|
|
135
|
-
if (parseHash && parseHash !== hash)
|
|
168
|
+
if (parseHash && parseHash !== hash) {
|
|
169
|
+
log.debug('detectSameBranch: hash mismatch → different branch', {
|
|
170
|
+
file,
|
|
171
|
+
graphHash: hash.substring(0, 8),
|
|
172
|
+
parseHash: parseHash.substring(0, 8),
|
|
173
|
+
});
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
136
176
|
}
|
|
137
177
|
|
|
178
|
+
log.debug('detectSameBranch: all hashes match → same branch', {
|
|
179
|
+
filesCompared: graphHashes.size,
|
|
180
|
+
});
|
|
138
181
|
return true;
|
|
139
182
|
}
|
package/src/shared/logger.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// src/shared/logger.ts
|
|
2
2
|
export const log = {
|
|
3
|
+
info(msg: string, ctx?: Record<string, unknown>): void {
|
|
4
|
+
process.stderr.write(`[INFO] ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : ''}\n`);
|
|
5
|
+
},
|
|
3
6
|
debug(msg: string, ctx?: Record<string, unknown>): void {
|
|
4
7
|
if (process.env.KODUS_GRAPH_DEBUG) {
|
|
5
8
|
process.stderr.write(`[DEBUG] ${msg}${ctx ? ` ${JSON.stringify(ctx)}` : ''}\n`);
|