@optave/codegraph 3.8.0 → 3.9.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/README.md +13 -8
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +137 -86
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/metrics.d.ts +0 -3
- package/dist/ast-analysis/metrics.d.ts.map +1 -1
- package/dist/ast-analysis/metrics.js +30 -13
- package/dist/ast-analysis/metrics.js.map +1 -1
- package/dist/ast-analysis/shared.d.ts.map +1 -1
- package/dist/ast-analysis/shared.js +24 -19
- package/dist/ast-analysis/shared.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +55 -39
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitor.js +91 -70
- package/dist/ast-analysis/visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +81 -39
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/branch-compare.d.ts.map +1 -1
- package/dist/cli/commands/branch-compare.js +4 -0
- package/dist/cli/commands/branch-compare.js.map +1 -1
- package/dist/cli/commands/diff-impact.d.ts.map +1 -1
- package/dist/cli/commands/diff-impact.js +2 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +3 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +16 -2
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +29 -26
- package/dist/db/connection.js.map +1 -1
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +16 -5
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/repository/base.d.ts +16 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +31 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +7 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +100 -1
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/db/repository/nodes.d.ts.map +1 -1
- package/dist/db/repository/nodes.js +8 -4
- package/dist/db/repository/nodes.js.map +1 -1
- package/dist/db/repository/sqlite-repository.d.ts +4 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +51 -0
- package/dist/db/repository/sqlite-repository.js.map +1 -1
- package/dist/domain/analysis/brief.d.ts.map +1 -1
- package/dist/domain/analysis/brief.js +13 -17
- package/dist/domain/analysis/brief.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +14 -11
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +64 -59
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +2 -7
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +33 -31
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/implementations.d.ts.map +1 -1
- package/dist/domain/analysis/implementations.js +11 -19
- package/dist/domain/analysis/implementations.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +55 -76
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +7 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
- package/dist/domain/analysis/query-helpers.js +15 -1
- package/dist/domain/analysis/query-helpers.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +352 -107
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +49 -18
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +2 -2
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -0
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +114 -22
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/resolve.js +1 -1
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts +2 -0
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +170 -75
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +3 -4
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +141 -89
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.js +1 -1
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +4 -3
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +23 -8
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +29 -18
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/extractors/go.js +36 -33
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -29
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.js +58 -46
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.js +65 -54
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +84 -78
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/python.js +29 -24
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/rust.js +41 -32
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/solidity.js +58 -67
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/swift.js +83 -81
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/zig.js +58 -60
- package/dist/extractors/zig.js.map +1 -1
- package/dist/features/ast.d.ts +16 -14
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +83 -81
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +8 -6
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +69 -72
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/communities.d.ts.map +1 -1
- package/dist/features/communities.js +19 -7
- package/dist/features/communities.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +120 -125
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +136 -137
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +84 -79
- package/dist/features/flow.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +69 -65
- package/dist/features/structure-query.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +70 -55
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/partition.js +288 -266
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +5 -1
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +6 -4
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/suppress.d.ts +25 -0
- package/dist/infrastructure/suppress.d.ts.map +1 -0
- package/dist/infrastructure/suppress.js +43 -0
- package/dist/infrastructure/suppress.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +29 -24
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/dataflow.d.ts.map +1 -1
- package/dist/presentation/dataflow.js +47 -38
- package/dist/presentation/dataflow.js.map +1 -1
- package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
- package/dist/presentation/diff-impact-mermaid.js +60 -51
- package/dist/presentation/diff-impact-mermaid.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +20 -14
- package/dist/presentation/queries-cli/exports.js.map +1 -1
- package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
- package/dist/presentation/queries-cli/impact.js +15 -13
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
- package/dist/presentation/queries-cli/inspect.js +101 -79
- package/dist/presentation/queries-cli/inspect.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +25 -16
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/queries-cli/path.js +26 -20
- package/dist/presentation/queries-cli/path.js.map +1 -1
- package/dist/presentation/result-formatter.d.ts +10 -0
- package/dist/presentation/result-formatter.d.ts.map +1 -1
- package/dist/presentation/result-formatter.js +16 -1
- package/dist/presentation/result-formatter.js.map +1 -1
- package/dist/presentation/viewer.d.ts.map +1 -1
- package/dist/presentation/viewer.js +18 -12
- package/dist/presentation/viewer.js.map +1 -1
- package/dist/shared/errors.d.ts +5 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +5 -0
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/hierarchy.d.ts +8 -2
- package/dist/shared/hierarchy.d.ts.map +1 -1
- package/dist/shared/hierarchy.js +42 -1
- package/dist/shared/hierarchy.js.map +1 -1
- package/dist/shared/normalize.d.ts +6 -1
- package/dist/shared/normalize.d.ts.map +1 -1
- package/dist/shared/normalize.js +20 -12
- package/dist/shared/normalize.js.map +1 -1
- package/dist/shared/paginate.d.ts +0 -9
- package/dist/shared/paginate.d.ts.map +1 -1
- package/dist/shared/paginate.js +0 -15
- package/dist/shared/paginate.js.map +1 -1
- package/dist/types.d.ts +12 -5
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +9 -9
- package/src/ast-analysis/engine.ts +176 -104
- package/src/ast-analysis/metrics.ts +33 -11
- package/src/ast-analysis/shared.ts +33 -24
- package/src/ast-analysis/visitor-utils.ts +52 -32
- package/src/ast-analysis/visitor.ts +132 -71
- package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
- package/src/ast-analysis/visitors/complexity-visitor.ts +89 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- package/src/cli/commands/branch-compare.ts +4 -0
- package/src/cli/commands/diff-impact.ts +2 -1
- package/src/cli/commands/info.ts +3 -2
- package/src/cli/commands/watch.ts +16 -2
- package/src/db/connection.ts +29 -28
- package/src/db/query-builder.ts +15 -3
- package/src/db/repository/base.ts +34 -0
- package/src/db/repository/native-repository.ts +104 -1
- package/src/db/repository/nodes.ts +13 -8
- package/src/db/repository/sqlite-repository.ts +55 -0
- package/src/domain/analysis/brief.ts +15 -25
- package/src/domain/analysis/context.ts +17 -10
- package/src/domain/analysis/dependencies.ts +77 -81
- package/src/domain/analysis/fn-impact.ts +36 -43
- package/src/domain/analysis/implementations.ts +11 -17
- package/src/domain/analysis/module-map.ts +58 -92
- package/src/domain/analysis/query-helpers.ts +18 -1
- package/src/domain/graph/builder/pipeline.ts +409 -99
- package/src/domain/graph/builder/stages/build-edges.ts +45 -19
- package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
- package/src/domain/graph/builder/stages/finalize.ts +2 -2
- package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
- package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
- package/src/domain/graph/cycles.ts +110 -23
- package/src/domain/graph/resolve.ts +1 -1
- package/src/domain/graph/watcher.ts +202 -96
- package/src/domain/parser.ts +143 -89
- package/src/domain/search/generator.ts +1 -1
- package/src/domain/search/models.ts +26 -7
- package/src/domain/search/search/hybrid.ts +69 -51
- package/src/extractors/go.ts +43 -33
- package/src/extractors/helpers.ts +37 -23
- package/src/extractors/java.ts +66 -47
- package/src/extractors/javascript.ts +66 -54
- package/src/extractors/kotlin.ts +84 -77
- package/src/extractors/python.ts +31 -25
- package/src/extractors/rust.ts +37 -29
- package/src/extractors/solidity.ts +57 -61
- package/src/extractors/swift.ts +81 -80
- package/src/extractors/zig.ts +58 -61
- package/src/features/ast.ts +130 -110
- package/src/features/audit.ts +8 -6
- package/src/features/branch-compare.ts +105 -79
- package/src/features/communities.ts +25 -10
- package/src/features/complexity.ts +171 -134
- package/src/features/dataflow.ts +165 -175
- package/src/features/flow.ts +129 -92
- package/src/features/structure-query.ts +79 -64
- package/src/graph/algorithms/leiden/optimiser.ts +99 -55
- package/src/graph/algorithms/leiden/partition.ts +359 -294
- package/src/graph/model.ts +6 -1
- package/src/infrastructure/config.ts +6 -4
- package/src/infrastructure/suppress.ts +47 -0
- package/src/mcp/server.ts +53 -37
- package/src/presentation/dataflow.ts +50 -44
- package/src/presentation/diff-impact-mermaid.ts +104 -62
- package/src/presentation/queries-cli/exports.ts +21 -13
- package/src/presentation/queries-cli/impact.ts +15 -13
- package/src/presentation/queries-cli/inspect.ts +100 -81
- package/src/presentation/queries-cli/overview.ts +26 -16
- package/src/presentation/queries-cli/path.ts +33 -25
- package/src/presentation/result-formatter.ts +19 -1
- package/src/presentation/viewer.ts +42 -14
- package/src/shared/errors.ts +6 -0
- package/src/shared/hierarchy.ts +50 -2
- package/src/shared/normalize.ts +31 -12
- package/src/shared/paginate.ts +0 -17
- package/src/types.ts +26 -5
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { buildDependencyGraph } from '../../graph/builders/dependency.js';
|
|
3
|
-
import { CodeGraph } from '../../graph/model.js';
|
|
1
|
+
import { getCallableNodes, getCallEdges, getFileNodesAll, getImportEdges } from '../../db/index.js';
|
|
4
2
|
import { loadNative } from '../../infrastructure/native.js';
|
|
3
|
+
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
5
4
|
import type { BetterSqlite3Database } from '../../types.js';
|
|
6
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Find cycles using Tarjan's SCC algorithm.
|
|
8
|
+
*
|
|
9
|
+
* Builds a label-based adjacency list directly from DB rows — no intermediate
|
|
10
|
+
* CodeGraph construction. This is O(V + E) with minimal memory overhead.
|
|
11
|
+
*/
|
|
7
12
|
export function findCycles(
|
|
8
13
|
db: BetterSqlite3Database,
|
|
9
14
|
opts: { fileLevel?: boolean; noTests?: boolean } = {},
|
|
@@ -11,40 +16,122 @@ export function findCycles(
|
|
|
11
16
|
const fileLevel = opts.fileLevel !== false;
|
|
12
17
|
const noTests = opts.noTests || false;
|
|
13
18
|
|
|
14
|
-
const
|
|
19
|
+
const edges: Array<{ source: string; target: string }> = [];
|
|
20
|
+
const seen = new Set<string>();
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
if (fileLevel) {
|
|
23
|
+
let nodes = getFileNodesAll(db);
|
|
24
|
+
if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
|
|
25
|
+
const nodeIds = new Set<number>();
|
|
26
|
+
const idToFile = new Map<number, string>();
|
|
27
|
+
for (const n of nodes) {
|
|
28
|
+
nodeIds.add(n.id);
|
|
29
|
+
idToFile.set(n.id, n.file);
|
|
30
|
+
}
|
|
31
|
+
for (const e of getImportEdges(db)) {
|
|
32
|
+
if (!nodeIds.has(e.source_id) || !nodeIds.has(e.target_id)) continue;
|
|
33
|
+
if (e.source_id === e.target_id) continue;
|
|
34
|
+
const src = idToFile.get(e.source_id)!;
|
|
35
|
+
const tgt = idToFile.get(e.target_id)!;
|
|
36
|
+
const key = `${src}\0${tgt}`;
|
|
37
|
+
if (seen.has(key)) continue;
|
|
38
|
+
seen.add(key);
|
|
39
|
+
edges.push({ source: src, target: tgt });
|
|
40
|
+
}
|
|
41
|
+
} else {
|
|
42
|
+
let nodes = getCallableNodes(db);
|
|
43
|
+
if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
|
|
44
|
+
const nodeIds = new Set<number>();
|
|
45
|
+
const idToLabel = new Map<number, string>();
|
|
46
|
+
for (const n of nodes) {
|
|
47
|
+
nodeIds.add(n.id);
|
|
48
|
+
idToLabel.set(n.id, `${n.name}|${n.file}`);
|
|
49
|
+
}
|
|
50
|
+
for (const e of getCallEdges(db)) {
|
|
51
|
+
if (!nodeIds.has(e.source_id) || !nodeIds.has(e.target_id)) continue;
|
|
52
|
+
if (e.source_id === e.target_id) continue;
|
|
53
|
+
const src = idToLabel.get(e.source_id)!;
|
|
54
|
+
const tgt = idToLabel.get(e.target_id)!;
|
|
55
|
+
const key = `${src}\0${tgt}`;
|
|
56
|
+
if (seen.has(key)) continue;
|
|
57
|
+
seen.add(key);
|
|
58
|
+
edges.push({ source: src, target: tgt });
|
|
22
59
|
}
|
|
23
60
|
}
|
|
24
61
|
|
|
25
|
-
const edges = graph.toEdgeArray().map((e) => ({
|
|
26
|
-
source: idToLabel.get(e.source) ?? e.source,
|
|
27
|
-
target: idToLabel.get(e.target) ?? e.target,
|
|
28
|
-
}));
|
|
29
|
-
|
|
30
62
|
const native = loadNative();
|
|
31
63
|
if (native) {
|
|
32
64
|
return native.detectCycles(edges) as string[][];
|
|
33
65
|
}
|
|
34
66
|
|
|
35
|
-
|
|
36
|
-
for (const { source, target } of edges) {
|
|
37
|
-
labelGraph.addEdge(source, target);
|
|
38
|
-
}
|
|
39
|
-
return tarjan(labelGraph);
|
|
67
|
+
return tarjanFromEdges(edges);
|
|
40
68
|
}
|
|
41
69
|
|
|
42
70
|
export function findCyclesJS(edges: Array<{ source: string; target: string }>): string[][] {
|
|
43
|
-
|
|
71
|
+
return tarjanFromEdges(edges);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Run Tarjan's SCC on a flat edge list. Returns SCCs with length > 1 (cycles).
|
|
76
|
+
* Uses a simple adjacency-list Map instead of a full CodeGraph.
|
|
77
|
+
*/
|
|
78
|
+
function tarjanFromEdges(edges: Array<{ source: string; target: string }>): string[][] {
|
|
79
|
+
const adj = new Map<string, string[]>();
|
|
80
|
+
const allNodes = new Set<string>();
|
|
44
81
|
for (const { source, target } of edges) {
|
|
45
|
-
|
|
82
|
+
allNodes.add(source);
|
|
83
|
+
allNodes.add(target);
|
|
84
|
+
let list = adj.get(source);
|
|
85
|
+
if (!list) {
|
|
86
|
+
list = [];
|
|
87
|
+
adj.set(source, list);
|
|
88
|
+
}
|
|
89
|
+
list.push(target);
|
|
46
90
|
}
|
|
47
|
-
|
|
91
|
+
|
|
92
|
+
let index = 0;
|
|
93
|
+
const stack: string[] = [];
|
|
94
|
+
const onStack = new Set<string>();
|
|
95
|
+
const indices = new Map<string, number>();
|
|
96
|
+
const lowlinks = new Map<string, number>();
|
|
97
|
+
const sccs: string[][] = [];
|
|
98
|
+
|
|
99
|
+
function strongconnect(v: string): void {
|
|
100
|
+
indices.set(v, index);
|
|
101
|
+
lowlinks.set(v, index);
|
|
102
|
+
index++;
|
|
103
|
+
stack.push(v);
|
|
104
|
+
onStack.add(v);
|
|
105
|
+
|
|
106
|
+
const successors = adj.get(v);
|
|
107
|
+
if (successors) {
|
|
108
|
+
for (const w of successors) {
|
|
109
|
+
if (!indices.has(w)) {
|
|
110
|
+
strongconnect(w);
|
|
111
|
+
lowlinks.set(v, Math.min(lowlinks.get(v)!, lowlinks.get(w)!));
|
|
112
|
+
} else if (onStack.has(w)) {
|
|
113
|
+
lowlinks.set(v, Math.min(lowlinks.get(v)!, indices.get(w)!));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (lowlinks.get(v) === indices.get(v)) {
|
|
119
|
+
const scc: string[] = [];
|
|
120
|
+
let w: string | undefined;
|
|
121
|
+
do {
|
|
122
|
+
w = stack.pop()!;
|
|
123
|
+
onStack.delete(w);
|
|
124
|
+
scc.push(w);
|
|
125
|
+
} while (w !== v);
|
|
126
|
+
if (scc.length > 1) sccs.push(scc);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const id of allNodes) {
|
|
131
|
+
if (!indices.has(id)) strongconnect(id);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return sccs;
|
|
48
135
|
}
|
|
49
136
|
|
|
50
137
|
export function formatCycles(cycles: string[][]): string {
|
|
@@ -565,7 +565,7 @@ export function resolveImportsBatch(
|
|
|
565
565
|
// Native resolver's .js → .ts remap fails on unnormalized paths —
|
|
566
566
|
// apply JS-side fallback (same fix as resolveImportPath).
|
|
567
567
|
const resolved = remapJsToTs(normalized, rootDir);
|
|
568
|
-
map.set(`${r.fromFile}|${r.importSource}`, resolved);
|
|
568
|
+
map.set(`${normalizePath(r.fromFile)}|${r.importSource}`, resolved);
|
|
569
569
|
}
|
|
570
570
|
return map;
|
|
571
571
|
} catch (e) {
|
|
@@ -18,29 +18,8 @@ function isTrackedExt(filePath: string): boolean {
|
|
|
18
18
|
return EXTENSIONS.has(path.extname(filePath));
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
if (!fs.existsSync(dbPath)) {
|
|
24
|
-
throw new DbError('No graph.db found. Run `codegraph build` first.', { file: dbPath });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const db = openDb(dbPath);
|
|
28
|
-
initSchema(db);
|
|
29
|
-
const engineOpts: import('../../types.js').EngineOpts = {
|
|
30
|
-
engine: (opts.engine || 'auto') as import('../../types.js').EngineMode,
|
|
31
|
-
dataflow: false,
|
|
32
|
-
ast: false,
|
|
33
|
-
};
|
|
34
|
-
const { name: engineName, version: engineVersion } = getActiveEngine(engineOpts);
|
|
35
|
-
info(`Watch mode using ${engineName} engine${engineVersion ? ` (v${engineVersion})` : ''}`);
|
|
36
|
-
|
|
37
|
-
const cache = createParseTreeCache();
|
|
38
|
-
info(
|
|
39
|
-
cache
|
|
40
|
-
? 'Incremental parsing enabled (native tree cache)'
|
|
41
|
-
: 'Incremental parsing unavailable (full re-parse)',
|
|
42
|
-
);
|
|
43
|
-
|
|
21
|
+
/** Prepare all SQL statements needed by the watcher's incremental rebuild. */
|
|
22
|
+
function prepareWatcherStatements(db: ReturnType<typeof openDb>): IncrementalStmts {
|
|
44
23
|
const stmts = {
|
|
45
24
|
insertNode: db.prepare(
|
|
46
25
|
'INSERT OR IGNORE INTO nodes (name, kind, file, line, end_line) VALUES (?, ?, ?, ?, ?)',
|
|
@@ -67,7 +46,6 @@ export async function watchProject(rootDir: string, opts: { engine?: string } =
|
|
|
67
46
|
listSymbols: db.prepare("SELECT name, kind, line FROM nodes WHERE file = ? AND kind != 'file'"),
|
|
68
47
|
};
|
|
69
48
|
|
|
70
|
-
// Use named params for statements needing the same value twice
|
|
71
49
|
const origDeleteEdges = db.prepare(
|
|
72
50
|
`DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = @f) OR target_id IN (SELECT id FROM nodes WHERE file = @f)`,
|
|
73
51
|
);
|
|
@@ -79,96 +57,224 @@ export async function watchProject(rootDir: string, opts: { engine?: string } =
|
|
|
79
57
|
get: (f: string) => origCountEdges.get({ f }) as { c: number } | undefined,
|
|
80
58
|
};
|
|
81
59
|
|
|
60
|
+
return stmts as IncrementalStmts;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Rebuild result shape from rebuildFile. */
|
|
64
|
+
interface RebuildResult {
|
|
65
|
+
file: string;
|
|
66
|
+
deleted?: boolean;
|
|
67
|
+
event: string;
|
|
68
|
+
symbolDiff: unknown;
|
|
69
|
+
nodesBefore: number;
|
|
70
|
+
nodesAfter: number;
|
|
71
|
+
nodesAdded: number;
|
|
72
|
+
nodesRemoved: number;
|
|
73
|
+
edgesAdded: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Process a batch of pending file changes: rebuild, journal, and log. */
|
|
77
|
+
async function processPendingFiles(
|
|
78
|
+
files: string[],
|
|
79
|
+
db: ReturnType<typeof openDb>,
|
|
80
|
+
rootDir: string,
|
|
81
|
+
stmts: IncrementalStmts,
|
|
82
|
+
engineOpts: import('../../types.js').EngineOpts,
|
|
83
|
+
cache: ReturnType<typeof createParseTreeCache>,
|
|
84
|
+
): Promise<void> {
|
|
85
|
+
const results: RebuildResult[] = [];
|
|
86
|
+
for (const filePath of files) {
|
|
87
|
+
const result = (await rebuildFile(db, rootDir, filePath, stmts, engineOpts, cache, {
|
|
88
|
+
diffSymbols: diffSymbols as (old: unknown[], new_: unknown[]) => unknown,
|
|
89
|
+
})) as RebuildResult | null;
|
|
90
|
+
if (result) results.push(result);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (results.length > 0) {
|
|
94
|
+
writeJournalAndChangeEvents(rootDir, results);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
logRebuildResults(results);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Write journal entries and change events for processed files. */
|
|
101
|
+
function writeJournalAndChangeEvents(rootDir: string, updates: RebuildResult[]): void {
|
|
102
|
+
const entries = updates.map((r) => ({
|
|
103
|
+
file: r.file,
|
|
104
|
+
deleted: r.deleted || false,
|
|
105
|
+
}));
|
|
106
|
+
try {
|
|
107
|
+
appendJournalEntries(rootDir, entries);
|
|
108
|
+
} catch (e: unknown) {
|
|
109
|
+
debug(`Journal write failed (non-fatal): ${(e as Error).message}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const changeEvents = updates.map((r) =>
|
|
113
|
+
buildChangeEvent(r.file, r.event, r.symbolDiff, {
|
|
114
|
+
nodesBefore: r.nodesBefore,
|
|
115
|
+
nodesAfter: r.nodesAfter,
|
|
116
|
+
edgesAdded: r.edgesAdded,
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
try {
|
|
120
|
+
appendChangeEvents(rootDir, changeEvents);
|
|
121
|
+
} catch (e: unknown) {
|
|
122
|
+
debug(`Change event write failed (non-fatal): ${(e as Error).message}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Log rebuild results to the user. */
|
|
127
|
+
function logRebuildResults(updates: RebuildResult[]): void {
|
|
128
|
+
for (const r of updates) {
|
|
129
|
+
const nodeDelta = r.nodesAdded - r.nodesRemoved;
|
|
130
|
+
const nodeStr = nodeDelta >= 0 ? `+${nodeDelta}` : `${nodeDelta}`;
|
|
131
|
+
if (r.deleted) {
|
|
132
|
+
info(`Removed: ${r.file} (-${r.nodesRemoved} nodes)`);
|
|
133
|
+
} else {
|
|
134
|
+
info(`Updated: ${r.file} (${nodeStr} nodes, +${r.edgesAdded} edges)`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Recursively collect tracked source files for stat-based polling. */
|
|
140
|
+
function collectTrackedFiles(dir: string, result: string[]): void {
|
|
141
|
+
let entries: fs.Dirent[];
|
|
142
|
+
try {
|
|
143
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
144
|
+
} catch {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
for (const entry of entries) {
|
|
148
|
+
if (IGNORE_DIRS.has(entry.name) || entry.name.startsWith('.')) continue;
|
|
149
|
+
const full = path.join(dir, entry.name);
|
|
150
|
+
if (entry.isDirectory()) {
|
|
151
|
+
collectTrackedFiles(full, result);
|
|
152
|
+
} else if (EXTENSIONS.has(path.extname(entry.name))) {
|
|
153
|
+
result.push(full);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function watchProject(
|
|
159
|
+
rootDir: string,
|
|
160
|
+
opts: { engine?: string; poll?: boolean; pollInterval?: number } = {},
|
|
161
|
+
): Promise<void> {
|
|
162
|
+
const dbPath = path.join(rootDir, '.codegraph', 'graph.db');
|
|
163
|
+
if (!fs.existsSync(dbPath)) {
|
|
164
|
+
throw new DbError('No graph.db found. Run `codegraph build` first.', { file: dbPath });
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const db = openDb(dbPath);
|
|
168
|
+
initSchema(db);
|
|
169
|
+
const engineOpts: import('../../types.js').EngineOpts = {
|
|
170
|
+
engine: (opts.engine || 'auto') as import('../../types.js').EngineMode,
|
|
171
|
+
dataflow: false,
|
|
172
|
+
ast: false,
|
|
173
|
+
};
|
|
174
|
+
const { name: engineName, version: engineVersion } = getActiveEngine(engineOpts);
|
|
175
|
+
info(`Watch mode using ${engineName} engine${engineVersion ? ` (v${engineVersion})` : ''}`);
|
|
176
|
+
|
|
177
|
+
const cache = createParseTreeCache();
|
|
178
|
+
info(
|
|
179
|
+
cache
|
|
180
|
+
? 'Incremental parsing enabled (native tree cache)'
|
|
181
|
+
: 'Incremental parsing unavailable (full re-parse)',
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const stmts = prepareWatcherStatements(db);
|
|
185
|
+
|
|
82
186
|
const pending = new Set<string>();
|
|
83
187
|
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
84
188
|
const DEBOUNCE_MS = 300;
|
|
85
189
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
pending.clear();
|
|
89
|
-
|
|
90
|
-
const results: Array<{
|
|
91
|
-
file: string;
|
|
92
|
-
deleted?: boolean;
|
|
93
|
-
event: string;
|
|
94
|
-
symbolDiff: unknown;
|
|
95
|
-
nodesBefore: number;
|
|
96
|
-
nodesAfter: number;
|
|
97
|
-
nodesAdded: number;
|
|
98
|
-
nodesRemoved: number;
|
|
99
|
-
edgesAdded: number;
|
|
100
|
-
}> = [];
|
|
101
|
-
for (const filePath of files) {
|
|
102
|
-
const result = (await rebuildFile(
|
|
103
|
-
db,
|
|
104
|
-
rootDir,
|
|
105
|
-
filePath,
|
|
106
|
-
stmts as IncrementalStmts,
|
|
107
|
-
engineOpts,
|
|
108
|
-
cache,
|
|
109
|
-
{
|
|
110
|
-
diffSymbols: diffSymbols as (old: unknown[], new_: unknown[]) => unknown,
|
|
111
|
-
},
|
|
112
|
-
)) as (typeof results)[number] | null;
|
|
113
|
-
if (result) results.push(result);
|
|
114
|
-
}
|
|
115
|
-
const updates = results;
|
|
190
|
+
const usePoll = opts.poll ?? process.platform === 'win32';
|
|
191
|
+
const POLL_INTERVAL_MS = opts.pollInterval ?? 2000;
|
|
116
192
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
debug(`Journal write failed (non-fatal): ${(e as Error).message}`);
|
|
127
|
-
}
|
|
193
|
+
info(`Watching ${rootDir} for changes${usePoll ? ' (polling mode)' : ''}...`);
|
|
194
|
+
info('Press Ctrl+C to stop.');
|
|
195
|
+
|
|
196
|
+
let cleanup: () => void;
|
|
197
|
+
|
|
198
|
+
if (usePoll) {
|
|
199
|
+
// Polling mode: avoids native OS file watchers (NtNotifyChangeDirectoryFileEx)
|
|
200
|
+
// which can crash ReFS drivers on Windows Dev Drives.
|
|
201
|
+
const mtimeMap = new Map<string, number>();
|
|
128
202
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
edgesAdded: r.edgesAdded,
|
|
134
|
-
}),
|
|
135
|
-
);
|
|
203
|
+
// Seed initial mtimes
|
|
204
|
+
const initial: string[] = [];
|
|
205
|
+
collectTrackedFiles(rootDir, initial);
|
|
206
|
+
for (const f of initial) {
|
|
136
207
|
try {
|
|
137
|
-
|
|
138
|
-
} catch
|
|
139
|
-
|
|
208
|
+
mtimeMap.set(f, fs.statSync(f).mtimeMs);
|
|
209
|
+
} catch {
|
|
210
|
+
/* deleted between collect and stat */
|
|
140
211
|
}
|
|
141
212
|
}
|
|
213
|
+
info(`Polling ${initial.length} tracked files every ${POLL_INTERVAL_MS}ms`);
|
|
214
|
+
|
|
215
|
+
const pollTimer = setInterval(() => {
|
|
216
|
+
const current: string[] = [];
|
|
217
|
+
collectTrackedFiles(rootDir, current);
|
|
218
|
+
const currentSet = new Set(current);
|
|
142
219
|
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
220
|
+
// Detect modified or new files
|
|
221
|
+
for (const f of current) {
|
|
222
|
+
try {
|
|
223
|
+
const mtime = fs.statSync(f).mtimeMs;
|
|
224
|
+
const prev = mtimeMap.get(f);
|
|
225
|
+
if (prev === undefined || mtime !== prev) {
|
|
226
|
+
mtimeMap.set(f, mtime);
|
|
227
|
+
pending.add(f);
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
/* deleted between collect and stat */
|
|
231
|
+
}
|
|
150
232
|
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
233
|
|
|
154
|
-
|
|
155
|
-
|
|
234
|
+
// Detect deleted files
|
|
235
|
+
for (const f of mtimeMap.keys()) {
|
|
236
|
+
if (!currentSet.has(f)) {
|
|
237
|
+
mtimeMap.delete(f);
|
|
238
|
+
pending.add(f);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
156
241
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
242
|
+
if (pending.size > 0) {
|
|
243
|
+
if (timer) clearTimeout(timer);
|
|
244
|
+
timer = setTimeout(async () => {
|
|
245
|
+
const files = [...pending];
|
|
246
|
+
pending.clear();
|
|
247
|
+
await processPendingFiles(files, db, rootDir, stmts, engineOpts, cache);
|
|
248
|
+
}, DEBOUNCE_MS);
|
|
249
|
+
}
|
|
250
|
+
}, POLL_INTERVAL_MS);
|
|
161
251
|
|
|
162
|
-
|
|
163
|
-
|
|
252
|
+
cleanup = () => clearInterval(pollTimer);
|
|
253
|
+
} else {
|
|
254
|
+
// Native OS watcher — efficient but can trigger ReFS crashes on Windows Dev Drives.
|
|
255
|
+
// Use --poll if you experience BSOD/HYPERVISOR_ERROR on ReFS volumes.
|
|
256
|
+
const watcher = fs.watch(rootDir, { recursive: true }, (_eventType, filename) => {
|
|
257
|
+
if (!filename) return;
|
|
258
|
+
if (shouldIgnore(filename)) return;
|
|
259
|
+
if (!isTrackedExt(filename)) return;
|
|
164
260
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
261
|
+
const fullPath = path.join(rootDir, filename);
|
|
262
|
+
pending.add(fullPath);
|
|
263
|
+
|
|
264
|
+
if (timer) clearTimeout(timer);
|
|
265
|
+
timer = setTimeout(async () => {
|
|
266
|
+
const files = [...pending];
|
|
267
|
+
pending.clear();
|
|
268
|
+
await processPendingFiles(files, db, rootDir, stmts, engineOpts, cache);
|
|
269
|
+
}, DEBOUNCE_MS);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
cleanup = () => watcher.close();
|
|
273
|
+
}
|
|
168
274
|
|
|
169
275
|
process.on('SIGINT', () => {
|
|
170
276
|
info('Stopping watcher...');
|
|
171
|
-
|
|
277
|
+
cleanup();
|
|
172
278
|
// Flush any pending file paths to journal before exit
|
|
173
279
|
if (pending.size > 0) {
|
|
174
280
|
const entries = [...pending].map((filePath) => ({
|