@optave/codegraph 3.7.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 +28 -16
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +195 -30
- 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/rules/javascript.d.ts.map +1 -1
- package/dist/ast-analysis/rules/javascript.js +0 -1
- package/dist/ast-analysis/rules/javascript.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 +52 -129
- 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/ast.js +2 -2
- package/dist/cli/commands/ast.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 +315 -43
- 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 +106 -1
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +17 -5
- package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +99 -51
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +34 -7
- 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 +50 -26
- 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 +1 -6
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +101 -32
- 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/clojure.d.ts +12 -0
- package/dist/extractors/clojure.d.ts.map +1 -0
- package/dist/extractors/clojure.js +245 -0
- package/dist/extractors/clojure.js.map +1 -0
- package/dist/extractors/cuda.d.ts +11 -0
- package/dist/extractors/cuda.d.ts.map +1 -0
- package/dist/extractors/cuda.js +302 -0
- package/dist/extractors/cuda.js.map +1 -0
- package/dist/extractors/erlang.d.ts +14 -0
- package/dist/extractors/erlang.d.ts.map +1 -0
- package/dist/extractors/erlang.js +239 -0
- package/dist/extractors/erlang.js.map +1 -0
- package/dist/extractors/fsharp.d.ts +13 -0
- package/dist/extractors/fsharp.d.ts.map +1 -0
- package/dist/extractors/fsharp.js +218 -0
- package/dist/extractors/fsharp.js.map +1 -0
- package/dist/extractors/gleam.d.ts +14 -0
- package/dist/extractors/gleam.d.ts.map +1 -0
- package/dist/extractors/gleam.js +229 -0
- package/dist/extractors/gleam.js.map +1 -0
- package/dist/extractors/go.js +36 -33
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/groovy.d.ts +10 -0
- package/dist/extractors/groovy.d.ts.map +1 -0
- package/dist/extractors/groovy.js +304 -0
- package/dist/extractors/groovy.js.map +1 -0
- 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/index.d.ts +11 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +11 -0
- package/dist/extractors/index.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/julia.d.ts +16 -0
- package/dist/extractors/julia.d.ts.map +1 -0
- package/dist/extractors/julia.js +287 -0
- package/dist/extractors/julia.js.map +1 -0
- package/dist/extractors/kotlin.js +84 -78
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/objc.d.ts +9 -0
- package/dist/extractors/objc.d.ts.map +1 -0
- package/dist/extractors/objc.js +406 -0
- package/dist/extractors/objc.js.map +1 -0
- package/dist/extractors/ocaml.js +74 -0
- package/dist/extractors/ocaml.js.map +1 -1
- package/dist/extractors/python.js +29 -24
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/r.d.ts +13 -0
- package/dist/extractors/r.d.ts.map +1 -0
- package/dist/extractors/r.js +251 -0
- package/dist/extractors/r.js.map +1 -0
- package/dist/extractors/rust.js +41 -32
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/solidity.d.ts +9 -0
- package/dist/extractors/solidity.d.ts.map +1 -0
- package/dist/extractors/solidity.js +365 -0
- package/dist/extractors/solidity.js.map +1 -0
- package/dist/extractors/swift.js +83 -81
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/verilog.d.ts +9 -0
- package/dist/extractors/verilog.d.ts.map +1 -0
- package/dist/extractors/verilog.js +286 -0
- package/dist/extractors/verilog.js.map +1 -0
- 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 +84 -83
- 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/bfs.d.ts +2 -0
- package/dist/graph/algorithms/bfs.d.ts.map +1 -1
- package/dist/graph/algorithms/bfs.js +27 -0
- package/dist/graph/algorithms/bfs.js.map +1 -1
- package/dist/graph/algorithms/centrality.d.ts +2 -0
- package/dist/graph/algorithms/centrality.d.ts.map +1 -1
- package/dist/graph/algorithms/centrality.js +28 -0
- package/dist/graph/algorithms/centrality.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/algorithms/louvain.d.ts +3 -4
- package/dist/graph/algorithms/louvain.d.ts.map +1 -1
- package/dist/graph/algorithms/louvain.js +29 -0
- package/dist/graph/algorithms/louvain.js.map +1 -1
- package/dist/graph/algorithms/shortest-path.d.ts +2 -0
- package/dist/graph/algorithms/shortest-path.d.ts.map +1 -1
- package/dist/graph/algorithms/shortest-path.js +18 -1
- package/dist/graph/algorithms/shortest-path.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 +129 -3
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-clojure.wasm +0 -0
- package/grammars/tree-sitter-cuda.wasm +0 -0
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-fsharp.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/grammars/tree-sitter-groovy.wasm +0 -0
- package/grammars/tree-sitter-julia.wasm +0 -0
- package/grammars/tree-sitter-objc.wasm +0 -0
- package/grammars/tree-sitter-ocaml_interface.wasm +0 -0
- package/grammars/tree-sitter-r.wasm +0 -0
- package/grammars/tree-sitter-solidity.wasm +0 -0
- package/grammars/tree-sitter-verilog.wasm +0 -0
- package/package.json +18 -7
- package/src/ast-analysis/engine.ts +245 -42
- package/src/ast-analysis/metrics.ts +33 -11
- package/src/ast-analysis/rules/javascript.ts +0 -1
- 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 +49 -119
- package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- package/src/cli/commands/ast.ts +2 -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 +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 +366 -41
- package/src/domain/graph/builder/stages/build-edges.ts +162 -1
- package/src/domain/graph/builder/stages/collect-files.ts +18 -7
- package/src/domain/graph/builder/stages/detect-changes.ts +110 -56
- package/src/domain/graph/builder/stages/finalize.ts +41 -11
- package/src/domain/graph/builder/stages/insert-nodes.ts +75 -39
- 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 +122 -28
- 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/clojure.ts +273 -0
- package/src/extractors/cuda.ts +316 -0
- package/src/extractors/erlang.ts +252 -0
- package/src/extractors/fsharp.ts +253 -0
- package/src/extractors/gleam.ts +246 -0
- package/src/extractors/go.ts +43 -33
- package/src/extractors/groovy.ts +332 -0
- package/src/extractors/helpers.ts +37 -23
- package/src/extractors/index.ts +11 -0
- package/src/extractors/java.ts +66 -47
- package/src/extractors/javascript.ts +45 -46
- package/src/extractors/julia.ts +318 -0
- package/src/extractors/kotlin.ts +84 -77
- package/src/extractors/objc.ts +431 -0
- package/src/extractors/ocaml.ts +78 -0
- package/src/extractors/python.ts +31 -25
- package/src/extractors/r.ts +253 -0
- package/src/extractors/rust.ts +37 -29
- package/src/extractors/solidity.ts +394 -0
- package/src/extractors/swift.ts +81 -80
- package/src/extractors/verilog.ts +315 -0
- package/src/extractors/zig.ts +58 -61
- package/src/features/ast.ts +131 -112
- 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/bfs.ts +34 -0
- package/src/graph/algorithms/centrality.ts +30 -0
- package/src/graph/algorithms/leiden/optimiser.ts +99 -55
- package/src/graph/algorithms/leiden/partition.ts +359 -294
- package/src/graph/algorithms/louvain.ts +31 -4
- package/src/graph/algorithms/shortest-path.ts +20 -1
- 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 +138 -3
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Community detection via vendored Leiden algorithm.
|
|
2
|
+
* Community detection via native Rust Louvain or vendored Leiden algorithm.
|
|
3
3
|
* Maintains backward-compatible API: { assignments: Map<string, number>, modularity: number }
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* use `detectClusters` from `./leiden/index.js` directly.
|
|
5
|
+
* Native path: classic Louvain (Rust, undirected modularity optimization).
|
|
6
|
+
* JS fallback: Leiden algorithm via `detectClusters` (always undirected, `directed: false`).
|
|
8
7
|
*/
|
|
8
|
+
|
|
9
|
+
import { warn } from '../../infrastructure/logger.js';
|
|
10
|
+
import { loadNative } from '../../infrastructure/native.js';
|
|
9
11
|
import type { CodeGraph } from '../model.js';
|
|
10
12
|
import type { DetectClustersResult } from './leiden/index.js';
|
|
11
13
|
import { detectClusters } from './leiden/index.js';
|
|
@@ -28,6 +30,31 @@ export function louvainCommunities(graph: CodeGraph, opts: LouvainOptions = {}):
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
const resolution: number = opts.resolution ?? 1.0;
|
|
33
|
+
|
|
34
|
+
const native = loadNative();
|
|
35
|
+
if (native?.louvainCommunities) {
|
|
36
|
+
// maxLevels, maxLocalPasses, and refinementTheta are Leiden-specific tuning knobs
|
|
37
|
+
// not supported by the Rust Louvain implementation. Warn callers who set them.
|
|
38
|
+
if (opts.maxLevels != null || opts.maxLocalPasses != null || opts.refinementTheta != null) {
|
|
39
|
+
warn(
|
|
40
|
+
'louvainCommunities: maxLevels/maxLocalPasses/refinementTheta are ignored by the native Rust path',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
const edges = graph.toEdgeArray();
|
|
44
|
+
const nodeIds = graph.nodeIds();
|
|
45
|
+
const result = native.louvainCommunities(edges, nodeIds, resolution, 42);
|
|
46
|
+
const assignments = new Map<string, number>();
|
|
47
|
+
for (const entry of result.assignments) {
|
|
48
|
+
assignments.set(entry.node, entry.community);
|
|
49
|
+
}
|
|
50
|
+
return { assignments, modularity: result.modularity };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return louvainJS(graph, opts, resolution);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** JS fallback using the vendored Leiden algorithm. */
|
|
57
|
+
function louvainJS(graph: CodeGraph, opts: LouvainOptions, resolution: number): LouvainResult {
|
|
31
58
|
const result: DetectClustersResult = detectClusters(graph, {
|
|
32
59
|
resolution,
|
|
33
60
|
randomSeed: 42,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { loadNative } from '../../infrastructure/native.js';
|
|
1
2
|
import type { CodeGraph } from '../model.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* BFS-based shortest path on a CodeGraph.
|
|
5
6
|
*
|
|
7
|
+
* Tries the native Rust implementation first, falls back to JS.
|
|
8
|
+
*
|
|
6
9
|
* @returns Path from fromId to toId (inclusive), or null if unreachable
|
|
7
10
|
*/
|
|
8
11
|
export function shortestPath(graph: CodeGraph, fromId: string, toId: string): string[] | null {
|
|
@@ -12,6 +15,23 @@ export function shortestPath(graph: CodeGraph, fromId: string, toId: string): st
|
|
|
12
15
|
if (!graph.hasNode(from) || !graph.hasNode(to)) return null;
|
|
13
16
|
if (from === to) return [from];
|
|
14
17
|
|
|
18
|
+
const native = loadNative();
|
|
19
|
+
if (native?.shortestPath) {
|
|
20
|
+
let edges = graph.toEdgeArray();
|
|
21
|
+
if (!graph.directed) {
|
|
22
|
+
// Undirected: toEdgeArray() deduplicates to one canonical direction;
|
|
23
|
+
// mirror each edge so the Rust BFS can traverse in both directions.
|
|
24
|
+
edges = [...edges, ...edges.map((e) => ({ source: e.target, target: e.source }))];
|
|
25
|
+
}
|
|
26
|
+
const result = native.shortestPath(edges, from, to);
|
|
27
|
+
return result.length > 0 ? result : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return shortestPathJS(graph, from, to);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Pure JS fallback for shortest path. */
|
|
34
|
+
function shortestPathJS(graph: CodeGraph, from: string, to: string): string[] | null {
|
|
15
35
|
const parent = new Map<string, string | null>();
|
|
16
36
|
parent.set(from, null);
|
|
17
37
|
const queue = [from];
|
|
@@ -23,7 +43,6 @@ export function shortestPath(graph: CodeGraph, fromId: string, toId: string): st
|
|
|
23
43
|
if (parent.has(neighbor)) continue;
|
|
24
44
|
parent.set(neighbor, current);
|
|
25
45
|
if (neighbor === to) {
|
|
26
|
-
// Reconstruct path
|
|
27
46
|
const path: string[] = [];
|
|
28
47
|
let node: string | null = to;
|
|
29
48
|
while (node !== null) {
|
package/src/graph/model.ts
CHANGED
|
@@ -17,6 +17,11 @@ export interface CodeGraphOpts {
|
|
|
17
17
|
directed?: boolean;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/** Canonical key for an undirected edge — smallest ID first, null-byte separated. */
|
|
21
|
+
function undirectedEdgeKey(a: string, b: string): string {
|
|
22
|
+
return a < b ? `${a}\0${b}` : `${b}\0${a}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
20
25
|
export class CodeGraph {
|
|
21
26
|
private _directed: boolean;
|
|
22
27
|
private _nodes: Map<string, NodeAttrs>;
|
|
@@ -121,7 +126,7 @@ export class CodeGraph {
|
|
|
121
126
|
const seen = new Set<string>();
|
|
122
127
|
for (const [src, targets] of this._successors) {
|
|
123
128
|
for (const [tgt, attrs] of targets) {
|
|
124
|
-
const key = src
|
|
129
|
+
const key = undirectedEdgeKey(src, tgt);
|
|
125
130
|
if (seen.has(key)) continue;
|
|
126
131
|
seen.add(key);
|
|
127
132
|
yield [src, tgt, attrs];
|
|
@@ -178,7 +178,7 @@ export function loadConfig(cwd?: string): CodegraphConfig {
|
|
|
178
178
|
_configCache.set(cwd, structuredClone(result));
|
|
179
179
|
return result;
|
|
180
180
|
} catch (err: unknown) {
|
|
181
|
-
debug(`Failed to parse config ${filePath}: ${(err
|
|
181
|
+
debug(`Failed to parse config ${filePath}: ${toErrorMessage(err)}`);
|
|
182
182
|
}
|
|
183
183
|
}
|
|
184
184
|
}
|
|
@@ -229,7 +229,7 @@ export function resolveSecrets(config: CodegraphConfig): CodegraphConfig {
|
|
|
229
229
|
(config.llm as Record<string, unknown>).apiKey = result;
|
|
230
230
|
}
|
|
231
231
|
} catch (err: unknown) {
|
|
232
|
-
warn(`apiKeyCommand failed: ${(err
|
|
232
|
+
warn(`apiKeyCommand failed: ${toErrorMessage(err)}`);
|
|
233
233
|
}
|
|
234
234
|
return config;
|
|
235
235
|
}
|
|
@@ -252,7 +252,8 @@ function expandWorkspaceGlob(pattern: string, rootDir: string): string[] {
|
|
|
252
252
|
.filter((e) => e.isDirectory())
|
|
253
253
|
.map((e) => path.join(baseDir, e.name))
|
|
254
254
|
.filter((d) => fs.existsSync(path.join(d, 'package.json')));
|
|
255
|
-
} catch {
|
|
255
|
+
} catch (e) {
|
|
256
|
+
debug(`expandGlobDirs: failed to read ${baseDir}: ${toErrorMessage(e)}`);
|
|
256
257
|
return [];
|
|
257
258
|
}
|
|
258
259
|
}
|
|
@@ -265,7 +266,8 @@ function readPackageName(pkgDir: string): string | null {
|
|
|
265
266
|
const raw = fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf-8');
|
|
266
267
|
const pkg = JSON.parse(raw);
|
|
267
268
|
return pkg.name || null;
|
|
268
|
-
} catch {
|
|
269
|
+
} catch (e) {
|
|
270
|
+
debug(`readPackageName: failed for ${pkgDir}: ${toErrorMessage(e)}`);
|
|
269
271
|
return null;
|
|
270
272
|
}
|
|
271
273
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catch-suppression helpers for intentional error swallowing.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `infrastructure/` (not `shared/`) because it depends on the
|
|
5
|
+
* structured logger — keeping `shared/errors.ts` dependency-free.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { toErrorMessage } from '../shared/errors.js';
|
|
9
|
+
import { debug } from './logger.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Run `fn` and return its result. If it throws, log a debug message and
|
|
13
|
+
* return `fallback` instead. Use this for intentional catch suppression
|
|
14
|
+
* where the error is expected and non-fatal (e.g. optional file reads,
|
|
15
|
+
* graceful feature probes, cleanup that may fail).
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* const version = suppressError(() => readPkgVersion(), 'read package version', '');
|
|
19
|
+
*/
|
|
20
|
+
export function suppressError<T>(fn: () => T, context: string, fallback: T): T {
|
|
21
|
+
try {
|
|
22
|
+
return fn();
|
|
23
|
+
} catch (e: unknown) {
|
|
24
|
+
debug(`${context}: ${toErrorMessage(e)}`);
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Async variant of {@link suppressError}. Awaits `fn()` and returns `fallback`
|
|
31
|
+
* on rejection, logging the error via `debug()`.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* const data = await suppressErrorAsync(() => fetchOptionalData(), 'fetch data', null);
|
|
35
|
+
*/
|
|
36
|
+
export async function suppressErrorAsync<T>(
|
|
37
|
+
fn: () => Promise<T>,
|
|
38
|
+
context: string,
|
|
39
|
+
fallback: T,
|
|
40
|
+
): Promise<T> {
|
|
41
|
+
try {
|
|
42
|
+
return await fn();
|
|
43
|
+
} catch (e: unknown) {
|
|
44
|
+
debug(`${context}: ${toErrorMessage(e)}`);
|
|
45
|
+
return fallback;
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/mcp/server.ts
CHANGED
|
@@ -13,7 +13,8 @@ const { version: PKG_VERSION } = require('../../package.json') as { version: str
|
|
|
13
13
|
import { getDatabase } from '../db/better-sqlite3.js';
|
|
14
14
|
import { findDbPath } from '../db/index.js';
|
|
15
15
|
import { loadConfig } from '../infrastructure/config.js';
|
|
16
|
-
import {
|
|
16
|
+
import { debug } from '../infrastructure/logger.js';
|
|
17
|
+
import { CodegraphError, ConfigError, toErrorMessage } from '../shared/errors.js';
|
|
17
18
|
import { MCP_MAX_LIMIT } from '../shared/paginate.js';
|
|
18
19
|
import type { CodegraphConfig, MCPServerOptions } from '../types.js';
|
|
19
20
|
import { initMcpDefaults } from './middleware.js';
|
|
@@ -56,7 +57,8 @@ async function loadMCPSdk(): Promise<{
|
|
|
56
57
|
ListToolsRequestSchema: types.ListToolsRequestSchema,
|
|
57
58
|
CallToolRequestSchema: types.CallToolRequestSchema,
|
|
58
59
|
};
|
|
59
|
-
} catch {
|
|
60
|
+
} catch (e) {
|
|
61
|
+
debug(`MCP SDK import failed: ${toErrorMessage(e)}`);
|
|
60
62
|
throw new ConfigError(
|
|
61
63
|
'MCP server requires @modelcontextprotocol/sdk.\nInstall it with: npm install @modelcontextprotocol/sdk',
|
|
62
64
|
);
|
|
@@ -123,8 +125,10 @@ function registerShutdownHandlers(): void {
|
|
|
123
125
|
const shutdown = async () => {
|
|
124
126
|
try {
|
|
125
127
|
await _activeServer?.close();
|
|
126
|
-
} catch (
|
|
127
|
-
|
|
128
|
+
} catch (shutdownErr: unknown) {
|
|
129
|
+
debug(
|
|
130
|
+
`MCP shutdown close failed (transport may already be gone): ${toErrorMessage(shutdownErr)}`,
|
|
131
|
+
);
|
|
128
132
|
}
|
|
129
133
|
process.exit(0);
|
|
130
134
|
};
|
|
@@ -154,36 +158,13 @@ function registerShutdownHandlers(): void {
|
|
|
154
158
|
process.on('unhandledRejection', silentReject);
|
|
155
159
|
}
|
|
156
160
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
// Apply config-based MCP page-size overrides
|
|
165
|
-
const config = options.config || loadConfig();
|
|
166
|
-
initMcpDefaults(config.mcp?.defaults ? { ...config.mcp.defaults } : undefined);
|
|
167
|
-
|
|
168
|
-
const { Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema } =
|
|
169
|
-
await loadMCPSdk();
|
|
170
|
-
|
|
171
|
-
// Connect transport FIRST so the server can receive the client's
|
|
172
|
-
// `initialize` request while heavy modules (queries, better-sqlite3)
|
|
173
|
-
// are still loading. These are lazy-loaded on the first tool call
|
|
174
|
-
// and cached for subsequent calls.
|
|
175
|
-
const { getQueries } = createLazyLoaders();
|
|
176
|
-
|
|
177
|
-
const server = new (Server as any)(
|
|
178
|
-
{ name: 'codegraph', version: PKG_VERSION },
|
|
179
|
-
{ capabilities: { tools: {} } },
|
|
180
|
-
);
|
|
181
|
-
|
|
182
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
183
|
-
tools: buildToolList(multiRepo),
|
|
184
|
-
}));
|
|
185
|
-
|
|
186
|
-
server.setRequestHandler(CallToolRequestSchema, async (request: any) => {
|
|
161
|
+
function createCallToolHandler(
|
|
162
|
+
multiRepo: boolean,
|
|
163
|
+
customDbPath: string | undefined,
|
|
164
|
+
allowedRepos: string[] | undefined,
|
|
165
|
+
getQueries: () => Promise<unknown>,
|
|
166
|
+
) {
|
|
167
|
+
return async (request: any) => {
|
|
187
168
|
const { name, arguments: args } = request.params;
|
|
188
169
|
try {
|
|
189
170
|
validateMultiRepoAccess(multiRepo, name, args);
|
|
@@ -212,10 +193,45 @@ export async function startMCPServer(
|
|
|
212
193
|
const text =
|
|
213
194
|
err instanceof CodegraphError
|
|
214
195
|
? `[${code}] ${err.message}`
|
|
215
|
-
: `Error: ${(err
|
|
196
|
+
: `Error: ${toErrorMessage(err)}`;
|
|
216
197
|
return { content: [{ type: 'text', text }], isError: true };
|
|
217
198
|
}
|
|
218
|
-
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function startMCPServer(
|
|
203
|
+
customDbPath?: string,
|
|
204
|
+
options: MCPServerOptionsInternal = {},
|
|
205
|
+
): Promise<void> {
|
|
206
|
+
const { allowedRepos } = options;
|
|
207
|
+
const multiRepo = options.multiRepo || !!allowedRepos;
|
|
208
|
+
|
|
209
|
+
// Apply config-based MCP page-size overrides
|
|
210
|
+
const config = options.config || loadConfig();
|
|
211
|
+
initMcpDefaults(config.mcp?.defaults ? { ...config.mcp.defaults } : undefined);
|
|
212
|
+
|
|
213
|
+
const { Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema } =
|
|
214
|
+
await loadMCPSdk();
|
|
215
|
+
|
|
216
|
+
// Connect transport FIRST so the server can receive the client's
|
|
217
|
+
// `initialize` request while heavy modules (queries, better-sqlite3)
|
|
218
|
+
// are still loading. These are lazy-loaded on the first tool call
|
|
219
|
+
// and cached for subsequent calls.
|
|
220
|
+
const { getQueries } = createLazyLoaders();
|
|
221
|
+
|
|
222
|
+
const server = new (Server as any)(
|
|
223
|
+
{ name: 'codegraph', version: PKG_VERSION },
|
|
224
|
+
{ capabilities: { tools: {} } },
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
228
|
+
tools: buildToolList(multiRepo),
|
|
229
|
+
}));
|
|
230
|
+
|
|
231
|
+
server.setRequestHandler(
|
|
232
|
+
CallToolRequestSchema,
|
|
233
|
+
createCallToolHandler(multiRepo, customDbPath, allowedRepos, getQueries),
|
|
234
|
+
);
|
|
219
235
|
|
|
220
236
|
const transport = new (StdioServerTransport as any)();
|
|
221
237
|
|
|
@@ -235,7 +251,7 @@ export async function startMCPServer(
|
|
|
235
251
|
process.exit(0);
|
|
236
252
|
}
|
|
237
253
|
process.stderr.write(
|
|
238
|
-
`MCP transport connect failed: ${
|
|
254
|
+
`MCP transport connect failed: ${err instanceof Error ? (err.stack ?? err.message) : toErrorMessage(err)}\n`,
|
|
239
255
|
);
|
|
240
256
|
process.exit(1);
|
|
241
257
|
}
|
|
@@ -58,6 +58,53 @@ interface DataflowImpactEntry {
|
|
|
58
58
|
levels: Record<string, Array<{ name: string; file: string; line: number }>>;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
function printDataflowFlows(r: DataflowResultEntry): void {
|
|
62
|
+
if (r.flowsTo.length > 0) {
|
|
63
|
+
console.log('\n Data flows TO:');
|
|
64
|
+
for (const f of r.flowsTo) {
|
|
65
|
+
const conf = f.confidence < 1.0 ? ` [${(f.confidence * 100).toFixed(0)}%]` : '';
|
|
66
|
+
console.log(` \u2192 ${f.target} (${f.file}:${f.line}) arg[${f.paramIndex}]${conf}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (r.flowsFrom.length > 0) {
|
|
70
|
+
console.log('\n Data flows FROM:');
|
|
71
|
+
for (const f of r.flowsFrom) {
|
|
72
|
+
const conf = f.confidence < 1.0 ? ` [${(f.confidence * 100).toFixed(0)}%]` : '';
|
|
73
|
+
console.log(` \u2190 ${f.source} (${f.file}:${f.line}) arg[${f.paramIndex}]${conf}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function printDataflowReturns(r: DataflowResultEntry): void {
|
|
79
|
+
if (r.returns.length > 0) {
|
|
80
|
+
console.log('\n Return value consumed by:');
|
|
81
|
+
for (const c of r.returns) {
|
|
82
|
+
console.log(` \u2192 ${c.consumer} (${c.file}:${c.line}) ${c.expression}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (r.returnedBy.length > 0) {
|
|
86
|
+
console.log('\n Uses return value of:');
|
|
87
|
+
for (const p of r.returnedBy) {
|
|
88
|
+
console.log(` \u2190 ${p.producer} (${p.file}:${p.line}) ${p.expression}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function printDataflowMutations(r: DataflowResultEntry): void {
|
|
94
|
+
if (r.mutates.length > 0) {
|
|
95
|
+
console.log('\n Mutates:');
|
|
96
|
+
for (const m of r.mutates) {
|
|
97
|
+
console.log(` \u270E ${m.expression} (line ${m.line})`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (r.mutatedBy.length > 0) {
|
|
101
|
+
console.log('\n Mutated by:');
|
|
102
|
+
for (const m of r.mutatedBy) {
|
|
103
|
+
console.log(` \u270E ${m.source} \u2014 ${m.expression} (line ${m.line})`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
61
108
|
export function dataflow(
|
|
62
109
|
name: string,
|
|
63
110
|
customDbPath: string | undefined,
|
|
@@ -87,50 +134,9 @@ export function dataflow(
|
|
|
87
134
|
for (const r of data.results) {
|
|
88
135
|
console.log(`\n${r.kind} ${r.name} (${r.file}:${r.line})`);
|
|
89
136
|
console.log('\u2500'.repeat(60));
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
for (const f of r.flowsTo) {
|
|
94
|
-
const conf = f.confidence < 1.0 ? ` [${(f.confidence * 100).toFixed(0)}%]` : '';
|
|
95
|
-
console.log(` \u2192 ${f.target} (${f.file}:${f.line}) arg[${f.paramIndex}]${conf}`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (r.flowsFrom.length > 0) {
|
|
100
|
-
console.log('\n Data flows FROM:');
|
|
101
|
-
for (const f of r.flowsFrom) {
|
|
102
|
-
const conf = f.confidence < 1.0 ? ` [${(f.confidence * 100).toFixed(0)}%]` : '';
|
|
103
|
-
console.log(` \u2190 ${f.source} (${f.file}:${f.line}) arg[${f.paramIndex}]${conf}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (r.returns.length > 0) {
|
|
108
|
-
console.log('\n Return value consumed by:');
|
|
109
|
-
for (const c of r.returns) {
|
|
110
|
-
console.log(` \u2192 ${c.consumer} (${c.file}:${c.line}) ${c.expression}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (r.returnedBy.length > 0) {
|
|
115
|
-
console.log('\n Uses return value of:');
|
|
116
|
-
for (const p of r.returnedBy) {
|
|
117
|
-
console.log(` \u2190 ${p.producer} (${p.file}:${p.line}) ${p.expression}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (r.mutates.length > 0) {
|
|
122
|
-
console.log('\n Mutates:');
|
|
123
|
-
for (const m of r.mutates) {
|
|
124
|
-
console.log(` \u270E ${m.expression} (line ${m.line})`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (r.mutatedBy.length > 0) {
|
|
129
|
-
console.log('\n Mutated by:');
|
|
130
|
-
for (const m of r.mutatedBy) {
|
|
131
|
-
console.log(` \u270E ${m.source} \u2014 ${m.expression} (line ${m.line})`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
137
|
+
printDataflowFlows(r);
|
|
138
|
+
printDataflowReturns(r);
|
|
139
|
+
printDataflowMutations(r);
|
|
134
140
|
}
|
|
135
141
|
}
|
|
136
142
|
|
|
@@ -1,56 +1,48 @@
|
|
|
1
1
|
import { diffImpactData } from '../domain/analysis/diff-impact.js';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
staged?: boolean;
|
|
9
|
-
ref?: string;
|
|
10
|
-
includeImplementors?: boolean;
|
|
11
|
-
limit?: number;
|
|
12
|
-
offset?: number;
|
|
13
|
-
config?: any;
|
|
14
|
-
} = {},
|
|
15
|
-
): string {
|
|
16
|
-
const data: any = diffImpactData(customDbPath, opts);
|
|
17
|
-
if ('error' in data) return data.error as string;
|
|
18
|
-
if (data.changedFiles === 0 || data.affectedFunctions.length === 0) {
|
|
19
|
-
return 'flowchart TB\n none["No impacted functions detected"]';
|
|
20
|
-
}
|
|
3
|
+
interface MermaidNodeRegistry {
|
|
4
|
+
nodeIdMap: Map<string, string>;
|
|
5
|
+
nodeLabels: Map<string, string>;
|
|
6
|
+
counter: number;
|
|
7
|
+
}
|
|
21
8
|
|
|
22
|
-
|
|
23
|
-
|
|
9
|
+
interface ImpactEdgeSets {
|
|
10
|
+
allEdges: Set<string>;
|
|
11
|
+
edgeFromNodes: Set<string>;
|
|
12
|
+
edgeToNodes: Set<string>;
|
|
13
|
+
changedKeys: Set<string>;
|
|
14
|
+
}
|
|
24
15
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
return nodeIdMap.get(key)!;
|
|
16
|
+
function createNodeRegistry(): MermaidNodeRegistry {
|
|
17
|
+
return { nodeIdMap: new Map(), nodeLabels: new Map(), counter: 0 };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function registerNode(reg: MermaidNodeRegistry, key: string, label?: string): string {
|
|
21
|
+
if (!reg.nodeIdMap.has(key)) {
|
|
22
|
+
reg.nodeIdMap.set(key, `n${reg.counter++}`);
|
|
23
|
+
if (label) reg.nodeLabels.set(key, label);
|
|
35
24
|
}
|
|
25
|
+
return reg.nodeIdMap.get(key)!;
|
|
26
|
+
}
|
|
36
27
|
|
|
37
|
-
|
|
38
|
-
for (const fn of
|
|
39
|
-
|
|
28
|
+
function registerAllNodes(reg: MermaidNodeRegistry, affectedFunctions: any[]): void {
|
|
29
|
+
for (const fn of affectedFunctions) {
|
|
30
|
+
registerNode(reg, `${fn.file}::${fn.name}:${fn.line}`, fn.name);
|
|
40
31
|
for (const callers of Object.values(fn.levels || {})) {
|
|
41
32
|
for (const c of callers as Array<{ name: string; file: string; line: number }>) {
|
|
42
|
-
|
|
33
|
+
registerNode(reg, `${c.file}::${c.name}:${c.line}`, c.name);
|
|
43
34
|
}
|
|
44
35
|
}
|
|
45
36
|
}
|
|
37
|
+
}
|
|
46
38
|
|
|
47
|
-
|
|
39
|
+
function collectEdges(affectedFunctions: any[]): ImpactEdgeSets {
|
|
48
40
|
const allEdges = new Set<string>();
|
|
49
41
|
const edgeFromNodes = new Set<string>();
|
|
50
42
|
const edgeToNodes = new Set<string>();
|
|
51
43
|
const changedKeys = new Set<string>();
|
|
52
44
|
|
|
53
|
-
for (const fn of
|
|
45
|
+
for (const fn of affectedFunctions) {
|
|
54
46
|
changedKeys.add(`${fn.file}::${fn.name}:${fn.line}`);
|
|
55
47
|
for (const edge of fn.edges || []) {
|
|
56
48
|
const edgeKey = `${edge.from}|${edge.to}`;
|
|
@@ -62,30 +54,42 @@ export function diffImpactMermaid(
|
|
|
62
54
|
}
|
|
63
55
|
}
|
|
64
56
|
|
|
65
|
-
|
|
57
|
+
return { allEdges, edgeFromNodes, edgeToNodes, changedKeys };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function classifyCallerNodes(edges: ImpactEdgeSets): {
|
|
61
|
+
blastRadiusKeys: Set<string>;
|
|
62
|
+
intermediateKeys: Set<string>;
|
|
63
|
+
} {
|
|
66
64
|
const blastRadiusKeys = new Set<string>();
|
|
67
|
-
for (const key of edgeToNodes) {
|
|
68
|
-
if (!edgeFromNodes.has(key) && !changedKeys.has(key)) {
|
|
65
|
+
for (const key of edges.edgeToNodes) {
|
|
66
|
+
if (!edges.edgeFromNodes.has(key) && !edges.changedKeys.has(key)) {
|
|
69
67
|
blastRadiusKeys.add(key);
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
70
|
|
|
73
|
-
// Intermediate callers: not changed, not blast radius
|
|
74
71
|
const intermediateKeys = new Set<string>();
|
|
75
|
-
for (const key of edgeToNodes) {
|
|
76
|
-
if (!changedKeys.has(key) && !blastRadiusKeys.has(key)) {
|
|
72
|
+
for (const key of edges.edgeToNodes) {
|
|
73
|
+
if (!edges.changedKeys.has(key) && !blastRadiusKeys.has(key)) {
|
|
77
74
|
intermediateKeys.add(key);
|
|
78
75
|
}
|
|
79
76
|
}
|
|
80
77
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
return { blastRadiusKeys, intermediateKeys };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function emitFileSubgraphs(
|
|
82
|
+
lines: string[],
|
|
83
|
+
affectedFunctions: any[],
|
|
84
|
+
newFileSet: Set<string>,
|
|
85
|
+
reg: MermaidNodeRegistry,
|
|
86
|
+
): number {
|
|
87
|
+
const fileGroups = new Map<string, any[]>();
|
|
88
|
+
for (const fn of affectedFunctions) {
|
|
84
89
|
if (!fileGroups.has(fn.file)) fileGroups.set(fn.file, []);
|
|
85
90
|
fileGroups.get(fn.file)!.push(fn);
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
// Emit changed-file subgraphs
|
|
89
93
|
let sgCounter = 0;
|
|
90
94
|
for (const [file, fns] of fileGroups) {
|
|
91
95
|
const isNew = newFileSet.has(file);
|
|
@@ -94,33 +98,71 @@ export function diffImpactMermaid(
|
|
|
94
98
|
lines.push(` subgraph ${sgId}["${file} **(${tag})**"]`);
|
|
95
99
|
for (const fn of fns) {
|
|
96
100
|
const key = `${fn.file}::${fn.name}:${fn.line}`;
|
|
97
|
-
lines.push(` ${nodeIdMap.get(key)}["${fn.name}"]`);
|
|
101
|
+
lines.push(` ${reg.nodeIdMap.get(key)}["${fn.name}"]`);
|
|
98
102
|
}
|
|
99
103
|
lines.push(' end');
|
|
100
104
|
const style = isNew ? 'fill:#e8f5e9,stroke:#4caf50' : 'fill:#fff3e0,stroke:#ff9800';
|
|
101
105
|
lines.push(` style ${sgId} ${style}`);
|
|
102
106
|
}
|
|
103
107
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
return sgCounter;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function emitBlastRadiusSubgraph(
|
|
112
|
+
lines: string[],
|
|
113
|
+
blastRadiusKeys: Set<string>,
|
|
114
|
+
reg: MermaidNodeRegistry,
|
|
115
|
+
sgCounter: number,
|
|
116
|
+
): void {
|
|
117
|
+
if (blastRadiusKeys.size === 0) return;
|
|
118
|
+
const sgId = `sg${sgCounter}`;
|
|
119
|
+
lines.push(` subgraph ${sgId}["Callers **(blast radius)**"]`);
|
|
120
|
+
for (const key of blastRadiusKeys) {
|
|
121
|
+
lines.push(` ${reg.nodeIdMap.get(key)}["${reg.nodeLabels.get(key)}"]`);
|
|
107
122
|
}
|
|
123
|
+
lines.push(' end');
|
|
124
|
+
lines.push(` style ${sgId} fill:#f3e5f5,stroke:#9c27b0`);
|
|
125
|
+
}
|
|
108
126
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
127
|
+
export function diffImpactMermaid(
|
|
128
|
+
customDbPath: string,
|
|
129
|
+
opts: {
|
|
130
|
+
noTests?: boolean;
|
|
131
|
+
depth?: number;
|
|
132
|
+
staged?: boolean;
|
|
133
|
+
ref?: string;
|
|
134
|
+
includeImplementors?: boolean;
|
|
135
|
+
limit?: number;
|
|
136
|
+
offset?: number;
|
|
137
|
+
config?: any;
|
|
138
|
+
} = {},
|
|
139
|
+
): string {
|
|
140
|
+
const data: any = diffImpactData(customDbPath, opts);
|
|
141
|
+
if ('error' in data) return data.error as string;
|
|
142
|
+
if (data.changedFiles === 0 || data.affectedFunctions.length === 0) {
|
|
143
|
+
return 'flowchart TB\n none["No impacted functions detected"]';
|
|
118
144
|
}
|
|
119
145
|
|
|
120
|
-
|
|
121
|
-
|
|
146
|
+
const newFileSet = new Set<string>(data.newFiles || []);
|
|
147
|
+
const lines = ['flowchart TB'];
|
|
148
|
+
|
|
149
|
+
const reg = createNodeRegistry();
|
|
150
|
+
registerAllNodes(reg, data.affectedFunctions);
|
|
151
|
+
|
|
152
|
+
const edges = collectEdges(data.affectedFunctions);
|
|
153
|
+
const { blastRadiusKeys, intermediateKeys } = classifyCallerNodes(edges);
|
|
154
|
+
|
|
155
|
+
const sgCounter = emitFileSubgraphs(lines, data.affectedFunctions, newFileSet, reg);
|
|
156
|
+
|
|
157
|
+
for (const key of intermediateKeys) {
|
|
158
|
+
lines.push(` ${reg.nodeIdMap.get(key)}["${reg.nodeLabels.get(key)}"]`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
emitBlastRadiusSubgraph(lines, blastRadiusKeys, reg, sgCounter);
|
|
162
|
+
|
|
163
|
+
for (const edgeKey of edges.allEdges) {
|
|
122
164
|
const [from, to] = edgeKey.split('|') as [string, string];
|
|
123
|
-
lines.push(` ${nodeIdMap.get(from)} --> ${nodeIdMap.get(to)}`);
|
|
165
|
+
lines.push(` ${reg.nodeIdMap.get(from)} --> ${reg.nodeIdMap.get(to)}`);
|
|
124
166
|
}
|
|
125
167
|
|
|
126
168
|
return lines.join('\n');
|