@optave/codegraph 3.8.0 → 3.8.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/README.md +9 -8
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +95 -87
- 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 +32 -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/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 +10 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +17 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +6 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +77 -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 +3 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +26 -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 +53 -52
- 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 +255 -105
- 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 +22 -17
- 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 +0 -5
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +13 -28
- 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 +18 -5
- 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 +46 -45
- 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 +10 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/ast-analysis/engine.ts +126 -105
- 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 +35 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- 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 +20 -0
- package/src/db/repository/native-repository.ts +79 -1
- package/src/db/repository/nodes.ts +13 -8
- package/src/db/repository/sqlite-repository.ts +29 -0
- package/src/domain/analysis/brief.ts +15 -25
- package/src/domain/analysis/context.ts +17 -10
- package/src/domain/analysis/dependencies.ts +67 -76
- 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 +286 -97
- package/src/domain/graph/builder/stages/build-edges.ts +22 -18
- 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 +14 -26
- package/src/domain/search/generator.ts +1 -1
- package/src/domain/search/models.ts +17 -4
- 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 +45 -46
- 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 +24 -4
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { performance } from 'node:perf_hooks';
|
|
13
13
|
import { bulkNodeIdsByFile } from '../../../../db/index.js';
|
|
14
|
+
import { debug } from '../../../../infrastructure/logger.js';
|
|
15
|
+
import { toErrorMessage } from '../../../../shared/errors.js';
|
|
14
16
|
import type {
|
|
15
17
|
BetterSqlite3Database,
|
|
16
18
|
ExtractorOutput,
|
|
@@ -36,35 +38,31 @@ interface PrecomputedFileData {
|
|
|
36
38
|
_reverseDepOnly?: boolean;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
|
-
// ── Native fast-path
|
|
41
|
+
// ── Native fast-path helpers ─────────────────────────────────────────
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
definitions: Array<{
|
|
43
|
+
/** Shape of a marshaled batch for native bulk insert. */
|
|
44
|
+
interface InsertNodesBatch {
|
|
45
|
+
file: string;
|
|
46
|
+
definitions: Array<{
|
|
47
|
+
name: string;
|
|
48
|
+
kind: string;
|
|
49
|
+
line: number;
|
|
50
|
+
endLine?: number;
|
|
51
|
+
visibility?: string;
|
|
52
|
+
children: Array<{
|
|
52
53
|
name: string;
|
|
53
54
|
kind: string;
|
|
54
55
|
line: number;
|
|
55
56
|
endLine?: number;
|
|
56
57
|
visibility?: string;
|
|
57
|
-
children: Array<{
|
|
58
|
-
name: string;
|
|
59
|
-
kind: string;
|
|
60
|
-
line: number;
|
|
61
|
-
endLine?: number;
|
|
62
|
-
visibility?: string;
|
|
63
|
-
}>;
|
|
64
58
|
}>;
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
}>;
|
|
60
|
+
exports: Array<{ name: string; kind: string; line: number }>;
|
|
61
|
+
}
|
|
67
62
|
|
|
63
|
+
/** Marshal allSymbols into the batch format expected by native bulkInsertNodes. */
|
|
64
|
+
function marshalSymbolBatches(allSymbols: Map<string, ExtractorOutput>): InsertNodesBatch[] {
|
|
65
|
+
const batches: InsertNodesBatch[] = [];
|
|
68
66
|
for (const [relPath, symbols] of allSymbols) {
|
|
69
67
|
batches.push({
|
|
70
68
|
file: relPath,
|
|
@@ -89,14 +87,18 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
|
|
|
89
87
|
})),
|
|
90
88
|
});
|
|
91
89
|
}
|
|
90
|
+
return batches;
|
|
91
|
+
}
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
/** Build file hash entries from parsed symbols and precomputed/metadata sources. */
|
|
94
|
+
function buildFileHashes(
|
|
95
|
+
allSymbols: Map<string, ExtractorOutput>,
|
|
96
|
+
precomputedData: Map<string, PrecomputedFileData>,
|
|
97
|
+
metadataUpdates: MetadataUpdate[],
|
|
98
|
+
rootDir: string,
|
|
99
|
+
): Array<{ file: string; hash: string; mtime: number; size: number }> {
|
|
99
100
|
const fileHashes: Array<{ file: string; hash: string; mtime: number; size: number }> = [];
|
|
101
|
+
|
|
100
102
|
for (const [relPath] of allSymbols) {
|
|
101
103
|
const precomputed = precomputedData.get(relPath);
|
|
102
104
|
if (precomputed?._reverseDepOnly) {
|
|
@@ -119,7 +121,8 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
|
|
|
119
121
|
let code: string | null;
|
|
120
122
|
try {
|
|
121
123
|
code = readFileSafe(absPath);
|
|
122
|
-
} catch {
|
|
124
|
+
} catch (e) {
|
|
125
|
+
debug(`buildFileHashes: readFileSafe failed for ${relPath}: ${toErrorMessage(e)}`);
|
|
123
126
|
code = null;
|
|
124
127
|
}
|
|
125
128
|
if (code !== null) {
|
|
@@ -138,6 +141,24 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
|
|
|
138
141
|
fileHashes.push({ file: item.relPath, hash: item.hash, mtime, size });
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
return fileHashes;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Native fast-path ─────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
function tryNativeInsert(ctx: PipelineContext): boolean {
|
|
150
|
+
if (!ctx.nativeDb?.bulkInsertNodes) return false;
|
|
151
|
+
|
|
152
|
+
const { allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
|
|
153
|
+
|
|
154
|
+
const batches = marshalSymbolBatches(allSymbols);
|
|
155
|
+
|
|
156
|
+
const precomputedData = new Map<string, PrecomputedFileData>();
|
|
157
|
+
for (const item of filesToParse) {
|
|
158
|
+
if (item.relPath) precomputedData.set(item.relPath, item as PrecomputedFileData);
|
|
159
|
+
}
|
|
160
|
+
const fileHashes = buildFileHashes(allSymbols, precomputedData, metadataUpdates, rootDir);
|
|
161
|
+
|
|
141
162
|
// WAL guard: same suspendJsDb/resumeJsDb pattern used by feature modules
|
|
142
163
|
// (ast, cfg, complexity, dataflow). Checkpoint JS side before native write,
|
|
143
164
|
// then checkpoint native side after, so neither library reads WAL frames
|
|
@@ -151,8 +172,10 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
|
|
|
151
172
|
} finally {
|
|
152
173
|
try {
|
|
153
174
|
ctx.nativeDb?.exec('PRAGMA wal_checkpoint(TRUNCATE)');
|
|
154
|
-
} catch {
|
|
155
|
-
|
|
175
|
+
} catch (e) {
|
|
176
|
+
debug(
|
|
177
|
+
`tryNativeInsert: WAL checkpoint failed (nativeDb may already be closed): ${toErrorMessage(e)}`,
|
|
178
|
+
);
|
|
156
179
|
}
|
|
157
180
|
}
|
|
158
181
|
return result;
|
|
@@ -324,7 +347,8 @@ function updateFileHashes(
|
|
|
324
347
|
let code: string | null;
|
|
325
348
|
try {
|
|
326
349
|
code = readFileSafe(absPath);
|
|
327
|
-
} catch {
|
|
350
|
+
} catch (e) {
|
|
351
|
+
debug(`updateFileHashes: readFileSafe failed for ${relPath}: ${toErrorMessage(e)}`);
|
|
328
352
|
code = null;
|
|
329
353
|
}
|
|
330
354
|
if (code !== null) {
|
|
@@ -364,8 +388,8 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
|
|
|
364
388
|
// Removed-file hash cleanup is handled inside the native call
|
|
365
389
|
return;
|
|
366
390
|
}
|
|
367
|
-
} catch {
|
|
368
|
-
|
|
391
|
+
} catch (e) {
|
|
392
|
+
debug(`insertNodes: native insert failed, falling back to JS: ${toErrorMessage(e)}`);
|
|
369
393
|
}
|
|
370
394
|
}
|
|
371
395
|
|
|
@@ -380,7 +404,8 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
|
|
|
380
404
|
upsertHash = ctx.db.prepare(
|
|
381
405
|
'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
|
|
382
406
|
);
|
|
383
|
-
} catch {
|
|
407
|
+
} catch (e) {
|
|
408
|
+
debug(`insertNodes: file_hashes prepare failed (table may not exist): ${toErrorMessage(e)}`);
|
|
384
409
|
upsertHash = null;
|
|
385
410
|
}
|
|
386
411
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { performance } from 'node:perf_hooks';
|
|
3
3
|
import { debug } from '../../../../infrastructure/logger.js';
|
|
4
|
+
import { normalizePath } from '../../../../shared/constants.js';
|
|
4
5
|
import type { Import } from '../../../../types.js';
|
|
5
6
|
import { parseFilesAuto } from '../../../parser.js';
|
|
6
7
|
import { resolveImportPath, resolveImportsBatch } from '../../resolve.js';
|
|
@@ -12,20 +13,10 @@ interface ReexportEntry {
|
|
|
12
13
|
wildcardReexport: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const t0 = performance.now();
|
|
18
|
-
const batchInputs: Array<{ fromFile: string; importSource: string }> = [];
|
|
19
|
-
for (const [relPath, symbols] of fileSymbols) {
|
|
20
|
-
const absFile = path.join(rootDir, relPath);
|
|
21
|
-
for (const imp of symbols.imports) {
|
|
22
|
-
batchInputs.push({ fromFile: absFile, importSource: imp.source });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
ctx.batchResolved = resolveImportsBatch(batchInputs, rootDir, aliases, allFiles);
|
|
26
|
-
ctx.timing.resolveMs = performance.now() - t0;
|
|
27
|
-
|
|
16
|
+
/** Collect reexport entries from fileSymbols into the reexportMap. */
|
|
17
|
+
function buildReexportMap(ctx: PipelineContext): void {
|
|
28
18
|
ctx.reexportMap = new Map<string, ReexportEntry[]>();
|
|
19
|
+
const { fileSymbols, rootDir } = ctx;
|
|
29
20
|
for (const [relPath, symbols] of fileSymbols) {
|
|
30
21
|
const reexports = symbols.imports.filter((imp) => imp.reexport);
|
|
31
22
|
if (reexports.length > 0) {
|
|
@@ -39,110 +30,141 @@ export async function resolveImports(ctx: PipelineContext): Promise<void> {
|
|
|
39
30
|
);
|
|
40
31
|
}
|
|
41
32
|
}
|
|
33
|
+
}
|
|
42
34
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
let barrelCandidates: Array<{ file: string }>;
|
|
52
|
-
if (changedRelPaths.size <= 5) {
|
|
53
|
-
// All known barrel files (has at least one reexport edge)
|
|
54
|
-
const allBarrelFiles = new Set(
|
|
55
|
-
(
|
|
56
|
-
db
|
|
57
|
-
.prepare(
|
|
58
|
-
`SELECT DISTINCT n1.file FROM edges e
|
|
59
|
-
JOIN nodes n1 ON e.source_id = n1.id
|
|
60
|
-
WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
|
|
61
|
-
)
|
|
62
|
-
.all() as Array<{ file: string }>
|
|
63
|
-
).map((r) => r.file),
|
|
64
|
-
);
|
|
35
|
+
/**
|
|
36
|
+
* Find barrel files related to changed files for scoped re-parsing.
|
|
37
|
+
* For small incremental builds (<=5 files), only barrels that re-export from
|
|
38
|
+
* or are imported by the changed files. For larger changes, all barrels.
|
|
39
|
+
*/
|
|
40
|
+
function findBarrelCandidates(ctx: PipelineContext): Array<{ file: string }> {
|
|
41
|
+
const { db, fileSymbols, rootDir, aliases } = ctx;
|
|
42
|
+
const changedRelPaths = new Set<string>(fileSymbols.keys());
|
|
65
43
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
}
|
|
44
|
+
const SMALL_CHANGE_THRESHOLD = 5;
|
|
45
|
+
if (changedRelPaths.size <= SMALL_CHANGE_THRESHOLD) {
|
|
46
|
+
const allBarrelFiles = new Set(
|
|
47
|
+
(
|
|
48
|
+
db
|
|
49
|
+
.prepare(
|
|
50
|
+
`SELECT DISTINCT n1.file FROM edges e
|
|
51
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
52
|
+
WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
|
|
53
|
+
)
|
|
54
|
+
.all() as Array<{ file: string }>
|
|
55
|
+
).map((r) => r.file),
|
|
56
|
+
);
|
|
81
57
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
);
|
|
89
|
-
for (const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
58
|
+
const barrels = new Set<string>();
|
|
59
|
+
|
|
60
|
+
// Find barrels imported by changed files using parsed import data
|
|
61
|
+
// (can't query DB edges -- they were purged for the changed files).
|
|
62
|
+
for (const relPath of changedRelPaths) {
|
|
63
|
+
const symbols = fileSymbols.get(relPath);
|
|
64
|
+
if (!symbols) continue;
|
|
65
|
+
for (const imp of symbols.imports) {
|
|
66
|
+
const resolved = ctx.batchResolved?.get(
|
|
67
|
+
`${normalizePath(path.join(rootDir, relPath))}|${imp.source}`,
|
|
68
|
+
);
|
|
69
|
+
const target =
|
|
70
|
+
resolved ?? resolveImportPath(path.join(rootDir, relPath), imp.source, rootDir, aliases);
|
|
71
|
+
if (allBarrelFiles.has(target)) barrels.add(target);
|
|
93
72
|
}
|
|
94
|
-
barrelCandidates = [...barrels].map((file) => ({ file }));
|
|
95
|
-
} else {
|
|
96
|
-
barrelCandidates = db
|
|
97
|
-
.prepare(
|
|
98
|
-
`SELECT DISTINCT n1.file FROM edges e
|
|
99
|
-
JOIN nodes n1 ON e.source_id = n1.id
|
|
100
|
-
WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
|
|
101
|
-
)
|
|
102
|
-
.all() as Array<{ file: string }>;
|
|
103
73
|
}
|
|
104
74
|
|
|
105
|
-
//
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
75
|
+
// Also find barrels that re-export from the changed files
|
|
76
|
+
const reexportSourceStmt = db.prepare(
|
|
77
|
+
`SELECT DISTINCT n1.file FROM edges e
|
|
78
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
79
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
80
|
+
WHERE e.kind = 'reexports' AND n1.kind = 'file' AND n2.file = ?`,
|
|
81
|
+
);
|
|
82
|
+
for (const relPath of changedRelPaths) {
|
|
83
|
+
for (const row of reexportSourceStmt.all(relPath) as Array<{ file: string }>) {
|
|
84
|
+
barrels.add(row.file);
|
|
110
85
|
}
|
|
111
86
|
}
|
|
87
|
+
return [...barrels].map((file) => ({ file }));
|
|
88
|
+
}
|
|
112
89
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
90
|
+
return db
|
|
91
|
+
.prepare(
|
|
92
|
+
`SELECT DISTINCT n1.file FROM edges e
|
|
93
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
94
|
+
WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
|
|
95
|
+
)
|
|
96
|
+
.all() as Array<{ file: string }>;
|
|
97
|
+
}
|
|
117
98
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
99
|
+
/** Re-parse barrel files and update fileSymbols/reexportMap with fresh data. */
|
|
100
|
+
async function reparseBarrelFiles(
|
|
101
|
+
ctx: PipelineContext,
|
|
102
|
+
barrelCandidates: Array<{ file: string }>,
|
|
103
|
+
): Promise<void> {
|
|
104
|
+
const { db, fileSymbols, rootDir, engineOpts } = ctx;
|
|
105
|
+
|
|
106
|
+
const barrelPaths: string[] = [];
|
|
107
|
+
for (const { file: relPath } of barrelCandidates) {
|
|
108
|
+
if (!fileSymbols.has(relPath)) {
|
|
109
|
+
barrelPaths.push(path.join(rootDir, relPath));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (barrelPaths.length === 0) return;
|
|
114
|
+
|
|
115
|
+
const deleteOutgoingEdges = db.prepare(
|
|
116
|
+
'DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const barrelSymbols = await parseFilesAuto(barrelPaths, rootDir, engineOpts);
|
|
121
|
+
for (const [relPath, fileSym] of barrelSymbols) {
|
|
122
|
+
deleteOutgoingEdges.run(relPath);
|
|
123
|
+
fileSymbols.set(relPath, fileSym);
|
|
124
|
+
ctx.barrelOnlyFiles.add(relPath);
|
|
125
|
+
const reexports = fileSym.imports.filter((imp: Import) => imp.reexport);
|
|
126
|
+
if (reexports.length > 0) {
|
|
127
|
+
ctx.reexportMap.set(
|
|
128
|
+
relPath,
|
|
129
|
+
reexports.map((imp: Import) => ({
|
|
130
|
+
source: getResolved(ctx, path.join(rootDir, relPath), imp.source),
|
|
131
|
+
names: imp.names,
|
|
132
|
+
wildcardReexport: imp.wildcardReexport || false,
|
|
133
|
+
})),
|
|
134
|
+
);
|
|
138
135
|
}
|
|
139
136
|
}
|
|
137
|
+
} catch (e: unknown) {
|
|
138
|
+
debug(`Barrel re-parse failed (non-fatal): ${(e as Error).message}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function resolveImports(ctx: PipelineContext): Promise<void> {
|
|
143
|
+
const { fileSymbols, rootDir, aliases, allFiles, isFullBuild } = ctx;
|
|
144
|
+
const t0 = performance.now();
|
|
145
|
+
|
|
146
|
+
const batchInputs: Array<{ fromFile: string; importSource: string }> = [];
|
|
147
|
+
for (const [relPath, symbols] of fileSymbols) {
|
|
148
|
+
const absFile = path.join(rootDir, relPath);
|
|
149
|
+
for (const imp of symbols.imports) {
|
|
150
|
+
batchInputs.push({ fromFile: absFile, importSource: imp.source });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
ctx.batchResolved = resolveImportsBatch(batchInputs, rootDir, aliases, allFiles);
|
|
154
|
+
ctx.timing.resolveMs = performance.now() - t0;
|
|
155
|
+
|
|
156
|
+
buildReexportMap(ctx);
|
|
157
|
+
|
|
158
|
+
ctx.barrelOnlyFiles = new Set<string>();
|
|
159
|
+
if (!isFullBuild) {
|
|
160
|
+
const barrelCandidates = findBarrelCandidates(ctx);
|
|
161
|
+
await reparseBarrelFiles(ctx, barrelCandidates);
|
|
140
162
|
}
|
|
141
163
|
}
|
|
142
164
|
|
|
143
165
|
export function getResolved(ctx: PipelineContext, absFile: string, importSource: string): string {
|
|
144
166
|
if (ctx.batchResolved) {
|
|
145
|
-
const key = `${absFile}|${importSource}`;
|
|
167
|
+
const key = `${normalizePath(absFile)}|${importSource}`;
|
|
146
168
|
const hit = ctx.batchResolved.get(key);
|
|
147
169
|
if (hit !== undefined) return hit;
|
|
148
170
|
}
|
|
@@ -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) {
|