@optave/codegraph 2.6.0 → 3.0.1

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/src/structure.js CHANGED
@@ -17,7 +17,7 @@ import { isTestFile } from './queries.js';
17
17
  * @param {Map<string, number>} lineCountMap - Map of relPath → line count
18
18
  * @param {Set<string>} directories - Set of relative directory paths
19
19
  */
20
- export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, directories) {
20
+ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, directories, changedFiles) {
21
21
  const insertNode = db.prepare(
22
22
  'INSERT OR IGNORE INTO nodes (name, kind, file, line, end_line) VALUES (?, ?, ?, ?, ?)',
23
23
  );
@@ -33,12 +33,49 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
33
33
  VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
34
34
  `);
35
35
 
36
- // Clean previous directory nodes/edges (idempotent rebuild)
37
- db.exec(`
38
- DELETE FROM edges WHERE kind = 'contains';
39
- DELETE FROM node_metrics;
40
- DELETE FROM nodes WHERE kind = 'directory';
41
- `);
36
+ const isIncremental = changedFiles != null && changedFiles.length > 0;
37
+
38
+ if (isIncremental) {
39
+ // Incremental: only clean up data for changed files and their ancestor directories
40
+ const affectedDirs = new Set();
41
+ for (const f of changedFiles) {
42
+ let d = normalizePath(path.dirname(f));
43
+ while (d && d !== '.') {
44
+ affectedDirs.add(d);
45
+ d = normalizePath(path.dirname(d));
46
+ }
47
+ }
48
+ const deleteContainsForDir = db.prepare(
49
+ "DELETE FROM edges WHERE kind = 'contains' AND source_id IN (SELECT id FROM nodes WHERE name = ? AND kind = 'directory')",
50
+ );
51
+ const deleteMetricForNode = db.prepare('DELETE FROM node_metrics WHERE node_id = ?');
52
+ db.transaction(() => {
53
+ // Delete contains edges only from affected directories
54
+ for (const dir of affectedDirs) {
55
+ deleteContainsForDir.run(dir);
56
+ }
57
+ // Delete metrics for changed files
58
+ for (const f of changedFiles) {
59
+ const fileRow = getNodeId.get(f, 'file', f, 0);
60
+ if (fileRow) deleteMetricForNode.run(fileRow.id);
61
+ }
62
+ // Delete metrics for affected directories
63
+ for (const dir of affectedDirs) {
64
+ const dirRow = getNodeId.get(dir, 'directory', dir, 0);
65
+ if (dirRow) deleteMetricForNode.run(dirRow.id);
66
+ }
67
+ })();
68
+ } else {
69
+ // Full rebuild: clean previous directory nodes/edges (idempotent)
70
+ // Scope contains-edge delete to directory-sourced edges only,
71
+ // preserving symbol-level contains edges (file→def, class→method, etc.)
72
+ db.exec(`
73
+ DELETE FROM edges WHERE kind = 'contains'
74
+ AND source_id IN (SELECT id FROM nodes WHERE kind = 'directory');
75
+ DELETE FROM node_metrics;
76
+ DELETE FROM nodes WHERE kind = 'directory';
77
+ `);
78
+ }
42
79
 
43
80
  // Step 1: Ensure all directories are represented (including intermediate parents)
44
81
  const allDirs = new Set();
@@ -58,7 +95,7 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
58
95
  }
59
96
  }
60
97
 
61
- // Step 2: Insert directory nodes
98
+ // Step 2: Insert directory nodes (INSERT OR IGNORE — safe for incremental)
62
99
  const insertDirs = db.transaction(() => {
63
100
  for (const dir of allDirs) {
64
101
  insertNode.run(dir, 'directory', dir, 0, null);
@@ -67,11 +104,28 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
67
104
  insertDirs();
68
105
 
69
106
  // Step 3: Insert 'contains' edges (dir → file, dir → subdirectory)
107
+ // On incremental, only re-insert for affected directories (others are intact)
108
+ const affectedDirs = isIncremental
109
+ ? (() => {
110
+ const dirs = new Set();
111
+ for (const f of changedFiles) {
112
+ let d = normalizePath(path.dirname(f));
113
+ while (d && d !== '.') {
114
+ dirs.add(d);
115
+ d = normalizePath(path.dirname(d));
116
+ }
117
+ }
118
+ return dirs;
119
+ })()
120
+ : null;
121
+
70
122
  const insertContains = db.transaction(() => {
71
123
  // dir → file
72
124
  for (const relPath of fileSymbols.keys()) {
73
125
  const dir = normalizePath(path.dirname(relPath));
74
126
  if (!dir || dir === '.') continue;
127
+ // On incremental, skip dirs whose contains edges are intact
128
+ if (affectedDirs && !affectedDirs.has(dir)) continue;
75
129
  const dirRow = getNodeId.get(dir, 'directory', dir, 0);
76
130
  const fileRow = getNodeId.get(relPath, 'file', relPath, 0);
77
131
  if (dirRow && fileRow) {
@@ -82,6 +136,8 @@ export function buildStructure(db, fileSymbols, _rootDir, lineCountMap, director
82
136
  for (const dir of allDirs) {
83
137
  const parent = normalizePath(path.dirname(dir));
84
138
  if (!parent || parent === '.' || parent === dir) continue;
139
+ // On incremental, skip parent dirs whose contains edges are intact
140
+ if (affectedDirs && !affectedDirs.has(parent)) continue;
85
141
  const parentRow = getNodeId.get(parent, 'directory', parent, 0);
86
142
  const childRow = getNodeId.get(dir, 'directory', dir, 0);
87
143
  if (parentRow && childRow) {