@optave/codegraph 3.12.0 → 3.15.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 +83 -46
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +38 -40
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/rules/b2.d.ts +7 -0
- package/dist/ast-analysis/rules/b2.d.ts.map +1 -0
- package/dist/ast-analysis/rules/b2.js +240 -0
- package/dist/ast-analysis/rules/b2.js.map +1 -0
- package/dist/ast-analysis/rules/b3.d.ts +6 -0
- package/dist/ast-analysis/rules/b3.d.ts.map +1 -0
- package/dist/ast-analysis/rules/b3.js +105 -0
- package/dist/ast-analysis/rules/b3.js.map +1 -0
- package/dist/ast-analysis/rules/b4.d.ts +9 -0
- package/dist/ast-analysis/rules/b4.d.ts.map +1 -0
- package/dist/ast-analysis/rules/b4.js +361 -0
- package/dist/ast-analysis/rules/b4.js.map +1 -0
- package/dist/ast-analysis/rules/b5.d.ts +4 -0
- package/dist/ast-analysis/rules/b5.d.ts.map +1 -0
- package/dist/ast-analysis/rules/b5.js +52 -0
- package/dist/ast-analysis/rules/b5.js.map +1 -0
- package/dist/ast-analysis/rules/c.d.ts +4 -0
- package/dist/ast-analysis/rules/c.d.ts.map +1 -0
- package/dist/ast-analysis/rules/c.js +143 -0
- package/dist/ast-analysis/rules/c.js.map +1 -0
- package/dist/ast-analysis/rules/index.d.ts.map +1 -1
- package/dist/ast-analysis/rules/index.js +34 -0
- package/dist/ast-analysis/rules/index.js.map +1 -1
- package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
- package/dist/ast-analysis/rules/javascript.js +3 -0
- 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 +2 -0
- package/dist/ast-analysis/shared.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts +1 -0
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +5 -0
- 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 +60 -47
- package/dist/ast-analysis/visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/cfg-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/cfg-visitor.js +126 -76
- package/dist/ast-analysis/visitors/cfg-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 +27 -15
- 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 +54 -21
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/audit.js +2 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/batch.d.ts.map +1 -1
- package/dist/cli/commands/batch.js +1 -0
- package/dist/cli/commands/batch.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +6 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/config.js +275 -0
- package/dist/cli/commands/config.js.map +1 -0
- package/dist/cli/commands/roles.d.ts.map +1 -1
- package/dist/cli/commands/roles.js +6 -1
- package/dist/cli/commands/roles.js.map +1 -1
- package/dist/cli/commands/triage.js +1 -1
- package/dist/cli/commands/triage.js.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +10 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/shared/options.d.ts +2 -1
- package/dist/cli/shared/options.d.ts.map +1 -1
- package/dist/cli/shared/options.js +11 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/cli/types.d.ts +2 -0
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/db/better-sqlite3.d.ts +2 -1
- package/dist/db/better-sqlite3.d.ts.map +1 -1
- package/dist/db/better-sqlite3.js.map +1 -1
- package/dist/db/connection.d.ts +7 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +20 -5
- package/dist/db/connection.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +69 -1
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/repository/build-stmts.d.ts.map +1 -1
- package/dist/db/repository/build-stmts.js +18 -0
- package/dist/db/repository/build-stmts.js.map +1 -1
- package/dist/db/repository/dataflow.d.ts +5 -0
- package/dist/db/repository/dataflow.d.ts.map +1 -1
- package/dist/db/repository/dataflow.js +14 -0
- package/dist/db/repository/dataflow.js.map +1 -1
- package/dist/db/repository/index.d.ts +1 -1
- package/dist/db/repository/index.d.ts.map +1 -1
- package/dist/db/repository/index.js +1 -1
- package/dist/db/repository/index.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +47 -34
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/domain/analysis/context.d.ts +2 -2
- package/dist/domain/analysis/dependencies.d.ts +2 -2
- package/dist/domain/analysis/diff-impact.d.ts +2 -2
- package/dist/domain/analysis/fn-impact.d.ts +3 -1
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +4 -0
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/implementations.d.ts +2 -2
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +32 -5
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/roles.d.ts +7 -1
- package/dist/domain/analysis/roles.d.ts.map +1 -1
- package/dist/domain/analysis/roles.js +16 -0
- package/dist/domain/analysis/roles.js.map +1 -1
- package/dist/domain/analysis/symbol-lookup.d.ts +4 -4
- package/dist/domain/graph/builder/call-resolver.d.ts +29 -13
- package/dist/domain/graph/builder/call-resolver.d.ts.map +1 -1
- package/dist/domain/graph/builder/call-resolver.js +125 -205
- package/dist/domain/graph/builder/call-resolver.js.map +1 -1
- package/dist/domain/graph/builder/cha.d.ts +9 -1
- package/dist/domain/graph/builder/cha.d.ts.map +1 -1
- package/dist/domain/graph/builder/cha.js +17 -2
- package/dist/domain/graph/builder/cha.js.map +1 -1
- package/dist/domain/graph/builder/context.d.ts +1 -0
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +24 -1
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +174 -65
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +166 -97
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +46 -5
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts +0 -2
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +554 -538
- 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 +10 -7
- 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 +3 -2
- 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 +4 -0
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/native-orchestrator.js +952 -343
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/resolver/points-to.d.ts.map +1 -1
- package/dist/domain/graph/resolver/points-to.js +105 -57
- package/dist/domain/graph/resolver/points-to.js.map +1 -1
- package/dist/domain/graph/resolver/strategy.d.ts +61 -0
- package/dist/domain/graph/resolver/strategy.d.ts.map +1 -0
- package/dist/domain/graph/resolver/strategy.js +222 -0
- package/dist/domain/graph/resolver/strategy.js.map +1 -0
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +16 -9
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +16 -5
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +58 -17
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/queries.d.ts +1 -1
- package/dist/domain/queries.d.ts.map +1 -1
- package/dist/domain/queries.js +1 -1
- package/dist/domain/queries.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +13 -2
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/domain/wasm-worker-pool.d.ts.map +1 -1
- package/dist/domain/wasm-worker-pool.js +26 -5
- package/dist/domain/wasm-worker-pool.js.map +1 -1
- package/dist/domain/wasm-worker-protocol.d.ts +8 -0
- package/dist/domain/wasm-worker-protocol.d.ts.map +1 -1
- package/dist/extractors/cpp.d.ts.map +1 -1
- package/dist/extractors/cpp.js +42 -1
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/cuda.d.ts.map +1 -1
- package/dist/extractors/cuda.js +42 -1
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/dart.js +48 -3
- package/dist/extractors/dart.js.map +1 -1
- package/dist/extractors/groovy.js +62 -3
- package/dist/extractors/groovy.js.map +1 -1
- package/dist/extractors/helpers.d.ts +15 -2
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +45 -1
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +85 -8
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +686 -169
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +58 -3
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/objc.js +25 -2
- package/dist/extractors/objc.js.map +1 -1
- package/dist/extractors/scala.js +62 -2
- package/dist/extractors/scala.js.map +1 -1
- package/dist/extractors/swift.js +52 -3
- package/dist/extractors/swift.js.map +1 -1
- package/dist/features/audit.js +26 -23
- package/dist/features/audit.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +12 -9
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +25 -18
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +18 -5
- package/dist/features/check.js.map +1 -1
- package/dist/features/communities.d.ts +4 -2
- package/dist/features/communities.d.ts.map +1 -1
- package/dist/features/communities.js +6 -4
- package/dist/features/communities.js.map +1 -1
- package/dist/features/dataflow.d.ts +60 -0
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +530 -6
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/manifesto.d.ts.map +1 -1
- package/dist/features/manifesto.js +59 -72
- package/dist/features/manifesto.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +27 -22
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/snapshot.d.ts.map +1 -1
- package/dist/features/snapshot.js +36 -28
- package/dist/features/snapshot.js.map +1 -1
- package/dist/features/structure-query.d.ts +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +6 -6
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +150 -62
- package/dist/features/structure.js.map +1 -1
- package/dist/features/triage.d.ts.map +1 -1
- package/dist/features/triage.js +18 -11
- package/dist/features/triage.js.map +1 -1
- package/dist/graph/algorithms/bfs.d.ts +1 -1
- package/dist/graph/algorithms/bfs.d.ts.map +1 -1
- package/dist/graph/algorithms/bfs.js +14 -13
- package/dist/graph/algorithms/bfs.js.map +1 -1
- package/dist/graph/algorithms/tarjan.d.ts.map +1 -1
- package/dist/graph/algorithms/tarjan.js +5 -0
- package/dist/graph/algorithms/tarjan.js.map +1 -1
- package/dist/graph/builders/dependency.js +28 -22
- package/dist/graph/builders/dependency.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts +10 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +60 -6
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/config.d.ts +87 -4
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +424 -22
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +27 -7
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +79 -5
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/infrastructure/update-check.d.ts.map +1 -1
- package/dist/infrastructure/update-check.js +49 -31
- package/dist/infrastructure/update-check.js.map +1 -1
- package/dist/mcp/server.d.ts +2 -10
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/ast-query.d.ts +1 -1
- package/dist/mcp/tools/ast-query.d.ts.map +1 -1
- package/dist/mcp/tools/audit.d.ts +1 -1
- package/dist/mcp/tools/audit.d.ts.map +1 -1
- package/dist/mcp/tools/batch-query.d.ts +1 -1
- package/dist/mcp/tools/batch-query.d.ts.map +1 -1
- package/dist/mcp/tools/branch-compare.d.ts +1 -1
- package/dist/mcp/tools/branch-compare.d.ts.map +1 -1
- package/dist/mcp/tools/brief.d.ts +1 -1
- package/dist/mcp/tools/brief.d.ts.map +1 -1
- package/dist/mcp/tools/cfg.d.ts +1 -1
- package/dist/mcp/tools/cfg.d.ts.map +1 -1
- package/dist/mcp/tools/check.d.ts +1 -1
- package/dist/mcp/tools/check.d.ts.map +1 -1
- package/dist/mcp/tools/co-changes.d.ts +1 -1
- package/dist/mcp/tools/co-changes.d.ts.map +1 -1
- package/dist/mcp/tools/code-owners.d.ts +1 -1
- package/dist/mcp/tools/code-owners.d.ts.map +1 -1
- package/dist/mcp/tools/communities.d.ts +1 -1
- package/dist/mcp/tools/communities.d.ts.map +1 -1
- package/dist/mcp/tools/complexity.d.ts +1 -1
- package/dist/mcp/tools/complexity.d.ts.map +1 -1
- package/dist/mcp/tools/context.d.ts +1 -1
- package/dist/mcp/tools/context.d.ts.map +1 -1
- package/dist/mcp/tools/dataflow.d.ts +1 -1
- package/dist/mcp/tools/dataflow.d.ts.map +1 -1
- package/dist/mcp/tools/diff-impact.d.ts +1 -1
- package/dist/mcp/tools/diff-impact.d.ts.map +1 -1
- package/dist/mcp/tools/execution-flow.d.ts +1 -1
- package/dist/mcp/tools/execution-flow.d.ts.map +1 -1
- package/dist/mcp/tools/export-graph.d.ts +1 -1
- package/dist/mcp/tools/export-graph.d.ts.map +1 -1
- package/dist/mcp/tools/file-deps.d.ts +1 -1
- package/dist/mcp/tools/file-deps.d.ts.map +1 -1
- package/dist/mcp/tools/file-exports.d.ts +1 -1
- package/dist/mcp/tools/file-exports.d.ts.map +1 -1
- package/dist/mcp/tools/find-cycles.d.ts +1 -1
- package/dist/mcp/tools/find-cycles.d.ts.map +1 -1
- package/dist/mcp/tools/fn-impact.d.ts +1 -1
- package/dist/mcp/tools/fn-impact.d.ts.map +1 -1
- package/dist/mcp/tools/impact-analysis.d.ts +1 -1
- package/dist/mcp/tools/impact-analysis.d.ts.map +1 -1
- package/dist/mcp/tools/implementations.d.ts +1 -1
- package/dist/mcp/tools/implementations.d.ts.map +1 -1
- package/dist/mcp/tools/index.d.ts +2 -5
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/interfaces.d.ts +1 -1
- package/dist/mcp/tools/interfaces.d.ts.map +1 -1
- package/dist/mcp/tools/list-functions.d.ts +1 -1
- package/dist/mcp/tools/list-functions.d.ts.map +1 -1
- package/dist/mcp/tools/list-repos.d.ts +1 -1
- package/dist/mcp/tools/list-repos.d.ts.map +1 -1
- package/dist/mcp/tools/module-map.d.ts +1 -1
- package/dist/mcp/tools/module-map.d.ts.map +1 -1
- package/dist/mcp/tools/node-roles.d.ts +1 -1
- package/dist/mcp/tools/node-roles.d.ts.map +1 -1
- package/dist/mcp/tools/path.d.ts +1 -1
- package/dist/mcp/tools/path.d.ts.map +1 -1
- package/dist/mcp/tools/query.d.ts +1 -1
- package/dist/mcp/tools/query.d.ts.map +1 -1
- package/dist/mcp/tools/semantic-search.d.ts +1 -1
- package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
- package/dist/mcp/tools/sequence.d.ts +1 -1
- package/dist/mcp/tools/sequence.d.ts.map +1 -1
- package/dist/mcp/tools/structure.d.ts +1 -1
- package/dist/mcp/tools/structure.d.ts.map +1 -1
- package/dist/mcp/tools/symbol-children.d.ts +1 -1
- package/dist/mcp/tools/symbol-children.d.ts.map +1 -1
- package/dist/mcp/tools/triage.d.ts +1 -1
- package/dist/mcp/tools/triage.d.ts.map +1 -1
- package/dist/mcp/tools/where.d.ts +1 -1
- package/dist/mcp/tools/where.d.ts.map +1 -1
- package/dist/mcp/types.d.ts +19 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +6 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/presentation/queries-cli/index.d.ts +1 -1
- package/dist/presentation/queries-cli/index.d.ts.map +1 -1
- package/dist/presentation/queries-cli/index.js +1 -1
- package/dist/presentation/queries-cli/index.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts +1 -0
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +20 -1
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/queries-cli.d.ts +1 -1
- package/dist/presentation/queries-cli.d.ts.map +1 -1
- package/dist/presentation/queries-cli.js +1 -1
- package/dist/presentation/queries-cli.js.map +1 -1
- package/dist/presentation/structure.d.ts +1 -1
- package/dist/presentation/structure.d.ts.map +1 -1
- package/dist/presentation/structure.js +2 -2
- package/dist/presentation/structure.js.map +1 -1
- package/dist/presentation/viewer.d.ts.map +1 -1
- package/dist/presentation/viewer.js +45 -32
- package/dist/presentation/viewer.js.map +1 -1
- package/dist/shared/constants.d.ts +21 -0
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +25 -0
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/normalize.d.ts.map +1 -1
- package/dist/shared/normalize.js +12 -22
- package/dist/shared/normalize.js.map +1 -1
- package/dist/shared/paginate.d.ts +4 -17
- package/dist/shared/paginate.d.ts.map +1 -1
- package/dist/shared/paginate.js.map +1 -1
- package/dist/types.d.ts +113 -1
- 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 +7 -8
- package/src/ast-analysis/engine.ts +43 -63
- package/src/ast-analysis/rules/b2.ts +263 -0
- package/src/ast-analysis/rules/b3.ts +127 -0
- package/src/ast-analysis/rules/b4.ts +378 -0
- package/src/ast-analysis/rules/b5.ts +65 -0
- package/src/ast-analysis/rules/c.ts +157 -0
- package/src/ast-analysis/rules/index.ts +34 -0
- package/src/ast-analysis/rules/javascript.ts +3 -0
- package/src/ast-analysis/shared.ts +2 -0
- package/src/ast-analysis/visitor-utils.ts +5 -0
- package/src/ast-analysis/visitor.ts +82 -52
- package/src/ast-analysis/visitors/cfg-visitor.ts +198 -84
- package/src/ast-analysis/visitors/complexity-visitor.ts +44 -16
- package/src/ast-analysis/visitors/dataflow-visitor.ts +68 -29
- package/src/cli/commands/audit.ts +2 -1
- package/src/cli/commands/batch.ts +1 -0
- package/src/cli/commands/build.ts +6 -1
- package/src/cli/commands/config.ts +353 -0
- package/src/cli/commands/roles.ts +6 -1
- package/src/cli/commands/triage.ts +1 -1
- package/src/cli/index.ts +10 -0
- package/src/cli/shared/options.ts +11 -1
- package/src/cli/types.ts +2 -0
- package/src/db/better-sqlite3.ts +5 -4
- package/src/db/connection.ts +23 -5
- package/src/db/index.ts +1 -0
- package/src/db/migrations.ts +69 -1
- package/src/db/repository/build-stmts.ts +30 -0
- package/src/db/repository/dataflow.ts +16 -0
- package/src/db/repository/index.ts +1 -1
- package/src/db/repository/native-repository.ts +56 -40
- package/src/domain/analysis/fn-impact.ts +4 -0
- package/src/domain/analysis/module-map.ts +38 -6
- package/src/domain/analysis/roles.ts +23 -0
- package/src/domain/graph/builder/call-resolver.ts +156 -218
- package/src/domain/graph/builder/cha.ts +18 -1
- package/src/domain/graph/builder/context.ts +1 -0
- package/src/domain/graph/builder/helpers.ts +205 -67
- package/src/domain/graph/builder/incremental.ts +249 -119
- package/src/domain/graph/builder/pipeline.ts +59 -6
- package/src/domain/graph/builder/stages/build-edges.ts +783 -652
- package/src/domain/graph/builder/stages/collect-files.ts +12 -6
- package/src/domain/graph/builder/stages/detect-changes.ts +4 -2
- package/src/domain/graph/builder/stages/finalize.ts +4 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +1214 -398
- package/src/domain/graph/builder/stages/resolve-imports.ts +1 -1
- package/src/domain/graph/resolver/points-to.ts +182 -59
- package/src/domain/graph/resolver/strategy.ts +265 -0
- package/src/domain/graph/watcher.ts +19 -9
- package/src/domain/parser.ts +57 -16
- package/src/domain/queries.ts +1 -1
- package/src/domain/wasm-worker-entry.ts +13 -2
- package/src/domain/wasm-worker-pool.ts +29 -4
- package/src/domain/wasm-worker-protocol.ts +5 -0
- package/src/extractors/cpp.ts +44 -1
- package/src/extractors/cuda.ts +44 -1
- package/src/extractors/dart.ts +48 -3
- package/src/extractors/groovy.ts +62 -2
- package/src/extractors/helpers.ts +48 -2
- package/src/extractors/java.ts +88 -8
- package/src/extractors/javascript.ts +693 -167
- package/src/extractors/kotlin.ts +57 -3
- package/src/extractors/objc.ts +25 -1
- package/src/extractors/scala.ts +63 -1
- package/src/extractors/swift.ts +46 -3
- package/src/features/audit.ts +43 -34
- package/src/features/boundaries.ts +17 -9
- package/src/features/cfg.ts +31 -22
- package/src/features/check.ts +21 -5
- package/src/features/communities.ts +28 -19
- package/src/features/dataflow.ts +755 -6
- package/src/features/manifesto.ts +76 -75
- package/src/features/sequence.ts +29 -23
- package/src/features/snapshot.ts +36 -25
- package/src/features/structure-query.ts +7 -7
- package/src/features/structure.ts +185 -55
- package/src/features/triage.ts +28 -15
- package/src/graph/algorithms/bfs.ts +13 -12
- package/src/graph/algorithms/tarjan.ts +5 -0
- package/src/graph/builders/dependency.ts +35 -23
- package/src/graph/classifiers/roles.ts +74 -7
- package/src/index.ts +5 -1
- package/src/infrastructure/config.ts +511 -23
- package/src/infrastructure/registry.ts +117 -12
- package/src/infrastructure/update-check.ts +55 -33
- package/src/mcp/server.ts +2 -8
- package/src/mcp/tools/ast-query.ts +1 -1
- package/src/mcp/tools/audit.ts +1 -1
- package/src/mcp/tools/batch-query.ts +1 -1
- package/src/mcp/tools/branch-compare.ts +1 -1
- package/src/mcp/tools/brief.ts +1 -1
- package/src/mcp/tools/cfg.ts +1 -1
- package/src/mcp/tools/check.ts +1 -1
- package/src/mcp/tools/co-changes.ts +1 -1
- package/src/mcp/tools/code-owners.ts +1 -1
- package/src/mcp/tools/communities.ts +1 -1
- package/src/mcp/tools/complexity.ts +1 -1
- package/src/mcp/tools/context.ts +1 -1
- package/src/mcp/tools/dataflow.ts +1 -1
- package/src/mcp/tools/diff-impact.ts +1 -1
- package/src/mcp/tools/execution-flow.ts +1 -1
- package/src/mcp/tools/export-graph.ts +1 -1
- package/src/mcp/tools/file-deps.ts +1 -1
- package/src/mcp/tools/file-exports.ts +1 -1
- package/src/mcp/tools/find-cycles.ts +1 -1
- package/src/mcp/tools/fn-impact.ts +1 -1
- package/src/mcp/tools/impact-analysis.ts +1 -1
- package/src/mcp/tools/implementations.ts +1 -1
- package/src/mcp/tools/index.ts +2 -5
- package/src/mcp/tools/interfaces.ts +1 -1
- package/src/mcp/tools/list-functions.ts +1 -1
- package/src/mcp/tools/list-repos.ts +1 -1
- package/src/mcp/tools/module-map.ts +1 -1
- package/src/mcp/tools/node-roles.ts +1 -1
- package/src/mcp/tools/path.ts +1 -1
- package/src/mcp/tools/query.ts +1 -1
- package/src/mcp/tools/semantic-search.ts +1 -1
- package/src/mcp/tools/sequence.ts +1 -1
- package/src/mcp/tools/structure.ts +1 -1
- package/src/mcp/tools/symbol-children.ts +1 -1
- package/src/mcp/tools/triage.ts +1 -1
- package/src/mcp/tools/where.ts +1 -1
- package/src/mcp/types.ts +21 -0
- package/src/presentation/queries-cli/index.ts +1 -1
- package/src/presentation/queries-cli/overview.ts +35 -1
- package/src/presentation/queries-cli.ts +1 -0
- package/src/presentation/structure.ts +3 -3
- package/src/presentation/viewer.ts +98 -87
- package/src/shared/constants.ts +26 -0
- package/src/shared/normalize.ts +13 -22
- package/src/shared/paginate.ts +4 -18
- package/src/types.ts +127 -1
package/src/features/dataflow.ts
CHANGED
|
@@ -184,6 +184,70 @@ interface Mutation {
|
|
|
184
184
|
line: number;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
+
// ── P1: Visitor internal shapes ───────────────────────────────────────────────
|
|
188
|
+
// The visitor's finish() emits DataflowResultInternal with richer types than
|
|
189
|
+
// the public DataflowResult in types.ts. We cast here to access paramName,
|
|
190
|
+
// paramIndex, and referencedNames which the public type omits.
|
|
191
|
+
|
|
192
|
+
interface VisitorParam {
|
|
193
|
+
funcName: string;
|
|
194
|
+
paramName: string;
|
|
195
|
+
paramIndex: number;
|
|
196
|
+
line: number;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
interface VisitorReturn {
|
|
200
|
+
funcName: string;
|
|
201
|
+
expression: string;
|
|
202
|
+
referencedNames: string[];
|
|
203
|
+
line: number;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
interface VisitorAssignment {
|
|
207
|
+
varName: string;
|
|
208
|
+
callerFunc: string;
|
|
209
|
+
sourceCallName: string;
|
|
210
|
+
line: number;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
interface VisitorArgFlow {
|
|
214
|
+
callerFunc: string;
|
|
215
|
+
calleeName: string;
|
|
216
|
+
argIndex: number;
|
|
217
|
+
argName: string;
|
|
218
|
+
binding: { type: string; index?: number };
|
|
219
|
+
confidence: number;
|
|
220
|
+
expression: string;
|
|
221
|
+
line: number;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
interface VisitorMutation {
|
|
225
|
+
funcName: string;
|
|
226
|
+
binding: { type: string; index?: number };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── P2: interprocedural stitch data collected during per-file processing ──
|
|
230
|
+
|
|
231
|
+
/** A resolved argFlow candidate for the inter-procedural stitch post-pass. */
|
|
232
|
+
interface StitchCandidate {
|
|
233
|
+
callerFuncId: number;
|
|
234
|
+
calleeFuncId: number;
|
|
235
|
+
argIndex: number;
|
|
236
|
+
bindingType: string;
|
|
237
|
+
bindingIndex?: number;
|
|
238
|
+
argName: string;
|
|
239
|
+
expression: string;
|
|
240
|
+
line: number;
|
|
241
|
+
confidence: number;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** An assignment that captures a function's return value into a local. */
|
|
245
|
+
interface ReturnCapture {
|
|
246
|
+
callerFuncId: number;
|
|
247
|
+
calleeFuncId: number;
|
|
248
|
+
varName: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
187
251
|
function insertDataflowEdges(
|
|
188
252
|
insert: { run(...params: unknown[]): unknown },
|
|
189
253
|
data: DataflowResult,
|
|
@@ -236,8 +300,426 @@ function insertDataflowEdges(
|
|
|
236
300
|
return edgeCount;
|
|
237
301
|
}
|
|
238
302
|
|
|
303
|
+
// ── P1: dataflow_vertices + intra def_use edges ───────────────────────────────
|
|
304
|
+
|
|
305
|
+
function prepareVertexStmts(db: BetterSqlite3Database): {
|
|
306
|
+
insertVertex: ReturnType<BetterSqlite3Database['prepare']>;
|
|
307
|
+
insertIntraEdge: ReturnType<BetterSqlite3Database['prepare']>;
|
|
308
|
+
available: boolean;
|
|
309
|
+
} {
|
|
310
|
+
try {
|
|
311
|
+
return {
|
|
312
|
+
insertVertex: db.prepare(
|
|
313
|
+
`INSERT INTO dataflow_vertices (func_id, kind, name, param_index, line, node_id)
|
|
314
|
+
VALUES (?, ?, ?, ?, ?, ?)`,
|
|
315
|
+
),
|
|
316
|
+
insertIntraEdge: db.prepare(
|
|
317
|
+
`INSERT INTO dataflow
|
|
318
|
+
(source_id, target_id, kind, source_vertex, target_vertex, scope, expression, line, confidence)
|
|
319
|
+
VALUES (?, ?, 'def_use', ?, ?, 'intra', ?, ?, 1.0)`,
|
|
320
|
+
),
|
|
321
|
+
available: true,
|
|
322
|
+
};
|
|
323
|
+
} catch {
|
|
324
|
+
return {
|
|
325
|
+
insertVertex: db.prepare('SELECT 1'),
|
|
326
|
+
insertIntraEdge: db.prepare('SELECT 1'),
|
|
327
|
+
available: false,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Build dataflow_vertices, intra def_use edges, and summaries for one file.
|
|
334
|
+
* Called alongside insertDataflowEdges in the same transaction.
|
|
335
|
+
*
|
|
336
|
+
* Returns stitch candidates and return captures for the P2 inter-procedural
|
|
337
|
+
* post-pass (run after all files are processed).
|
|
338
|
+
*/
|
|
339
|
+
function buildDataflowVerticesAndEdges(
|
|
340
|
+
db: BetterSqlite3Database,
|
|
341
|
+
vstmts: ReturnType<typeof prepareVertexStmts>,
|
|
342
|
+
data: DataflowResult,
|
|
343
|
+
resolveNode: (name: string) => { id: number } | null,
|
|
344
|
+
): { candidates: StitchCandidate[]; captures: ReturnCapture[] } {
|
|
345
|
+
const empty: { candidates: StitchCandidate[]; captures: ReturnCapture[] } = {
|
|
346
|
+
candidates: [],
|
|
347
|
+
captures: [],
|
|
348
|
+
};
|
|
349
|
+
if (!vstmts.available) return empty;
|
|
350
|
+
|
|
351
|
+
const params = data.parameters as unknown as VisitorParam[];
|
|
352
|
+
const returns = data.returns as unknown as VisitorReturn[];
|
|
353
|
+
const assignments = data.assignments as unknown as VisitorAssignment[];
|
|
354
|
+
const argFlows = data.argFlows as unknown as VisitorArgFlow[];
|
|
355
|
+
const mutations = data.mutations as unknown as VisitorMutation[];
|
|
356
|
+
|
|
357
|
+
// 1. param vertices
|
|
358
|
+
const paramVertexIds = new Map<string, number>(); // "funcName:paramName" → vertex id
|
|
359
|
+
const paramIndexByFuncAndIndex = new Map<string, number>(); // "funcId:paramIndex" → vertex id
|
|
360
|
+
for (const p of params) {
|
|
361
|
+
const fn = resolveNode(p.funcName);
|
|
362
|
+
if (!fn) continue;
|
|
363
|
+
const result = vstmts.insertVertex.run(fn.id, 'param', p.paramName, p.paramIndex, p.line, null);
|
|
364
|
+
const vid = (result as { lastInsertRowid: number }).lastInsertRowid;
|
|
365
|
+
paramVertexIds.set(`${p.funcName}:${p.paramName}`, vid);
|
|
366
|
+
paramIndexByFuncAndIndex.set(`${fn.id}:${p.paramIndex}`, vid);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// 2. return vertices (one per function that has a return statement)
|
|
370
|
+
const returnVertexIds = new Map<string, number>(); // funcName → vertex id
|
|
371
|
+
const returnFuncsSeen = new Set<string>();
|
|
372
|
+
for (const r of returns) {
|
|
373
|
+
if (returnFuncsSeen.has(r.funcName)) continue;
|
|
374
|
+
returnFuncsSeen.add(r.funcName);
|
|
375
|
+
const fn = resolveNode(r.funcName);
|
|
376
|
+
if (!fn) continue;
|
|
377
|
+
const result = vstmts.insertVertex.run(fn.id, 'return', null, null, r.line, null);
|
|
378
|
+
returnVertexIds.set(r.funcName, (result as { lastInsertRowid: number }).lastInsertRowid);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// 3. local vertices (from call-return assignments)
|
|
382
|
+
const localVertexIds = new Map<string, number>(); // "funcName:varName" → vertex id
|
|
383
|
+
const localsSeen = new Set<string>();
|
|
384
|
+
for (const a of assignments) {
|
|
385
|
+
const key = `${a.callerFunc}:${a.varName}`;
|
|
386
|
+
if (localsSeen.has(key)) continue;
|
|
387
|
+
localsSeen.add(key);
|
|
388
|
+
const fn = resolveNode(a.callerFunc);
|
|
389
|
+
if (!fn) continue;
|
|
390
|
+
const result = vstmts.insertVertex.run(fn.id, 'local', a.varName, null, a.line, null);
|
|
391
|
+
localVertexIds.set(key, (result as { lastInsertRowid: number }).lastInsertRowid);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// 4. intra def_use edges: param/local → return
|
|
395
|
+
for (const r of returns) {
|
|
396
|
+
const fn = resolveNode(r.funcName);
|
|
397
|
+
if (!fn) continue;
|
|
398
|
+
const returnVid = returnVertexIds.get(r.funcName);
|
|
399
|
+
if (!returnVid) continue;
|
|
400
|
+
for (const name of r.referencedNames) {
|
|
401
|
+
const paramVid = paramVertexIds.get(`${r.funcName}:${name}`);
|
|
402
|
+
if (paramVid) {
|
|
403
|
+
vstmts.insertIntraEdge.run(fn.id, fn.id, paramVid, returnVid, r.expression, r.line);
|
|
404
|
+
}
|
|
405
|
+
const localVid = localVertexIds.get(`${r.funcName}:${name}`);
|
|
406
|
+
if (localVid) {
|
|
407
|
+
vstmts.insertIntraEdge.run(fn.id, fn.id, localVid, returnVid, r.expression, r.line);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// 5. summaries: flows_to_return = direct def_use from param to function's return
|
|
413
|
+
const checkDefUse = db.prepare(
|
|
414
|
+
`SELECT 1 FROM dataflow WHERE source_vertex = ? AND target_vertex = ? AND kind = 'def_use' LIMIT 1`,
|
|
415
|
+
);
|
|
416
|
+
const insertSummary = db.prepare(
|
|
417
|
+
`INSERT OR REPLACE INTO dataflow_summary (func_id, param_index, flows_to_return, is_mutated) VALUES (?, ?, ?, ?)`,
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
for (const p of params) {
|
|
421
|
+
const fn = resolveNode(p.funcName);
|
|
422
|
+
if (!fn) continue;
|
|
423
|
+
const paramVid = paramVertexIds.get(`${p.funcName}:${p.paramName}`);
|
|
424
|
+
if (!paramVid) continue;
|
|
425
|
+
const returnVid = returnVertexIds.get(p.funcName);
|
|
426
|
+
const flowsToReturn = returnVid ? (checkDefUse.get(paramVid, returnVid) ? 1 : 0) : 0;
|
|
427
|
+
const isMutated = mutations.some(
|
|
428
|
+
(m) =>
|
|
429
|
+
m.funcName === p.funcName &&
|
|
430
|
+
m.binding?.type === 'param' &&
|
|
431
|
+
m.binding?.index === p.paramIndex,
|
|
432
|
+
)
|
|
433
|
+
? 1
|
|
434
|
+
: 0;
|
|
435
|
+
insertSummary.run(fn.id, p.paramIndex, flowsToReturn, isMutated);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// 6. collect stitch candidates for P2 inter-procedural post-pass
|
|
439
|
+
const candidates: StitchCandidate[] = [];
|
|
440
|
+
for (const af of argFlows) {
|
|
441
|
+
const callerFn = resolveNode(af.callerFunc);
|
|
442
|
+
const calleeFn = resolveNode(af.calleeName);
|
|
443
|
+
if (!callerFn || !calleeFn) continue;
|
|
444
|
+
candidates.push({
|
|
445
|
+
callerFuncId: callerFn.id,
|
|
446
|
+
calleeFuncId: calleeFn.id,
|
|
447
|
+
argIndex: af.argIndex,
|
|
448
|
+
bindingType: af.binding.type,
|
|
449
|
+
bindingIndex: af.binding.index,
|
|
450
|
+
argName: af.argName,
|
|
451
|
+
expression: af.expression,
|
|
452
|
+
line: af.line,
|
|
453
|
+
confidence: af.confidence,
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// 7. collect return captures (locals that hold a callee's return value)
|
|
458
|
+
const captures: ReturnCapture[] = [];
|
|
459
|
+
for (const a of assignments) {
|
|
460
|
+
const callerFn = resolveNode(a.callerFunc);
|
|
461
|
+
const calleeFn = resolveNode(a.sourceCallName);
|
|
462
|
+
if (!callerFn || !calleeFn) continue;
|
|
463
|
+
captures.push({ callerFuncId: callerFn.id, calleeFuncId: calleeFn.id, varName: a.varName });
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
return { candidates, captures };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// ── P2: interprocedural stitching ─────────────────────────────────────────────
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Core stitch logic — must be called inside an already-open transaction.
|
|
473
|
+
*
|
|
474
|
+
* All callers (buildDataflowVerticesFromMap, buildDataflowEdges,
|
|
475
|
+
* buildDataflowP4ForNative) manage their own outer transaction and call this
|
|
476
|
+
* directly to avoid nested transactions, which better-sqlite3 does not support.
|
|
477
|
+
*/
|
|
478
|
+
function runInterproceduralStitch(
|
|
479
|
+
db: BetterSqlite3Database,
|
|
480
|
+
candidates: StitchCandidate[],
|
|
481
|
+
captures: ReturnCapture[],
|
|
482
|
+
): number {
|
|
483
|
+
if (candidates.length === 0) return 0;
|
|
484
|
+
|
|
485
|
+
const getParamVertex = db.prepare(
|
|
486
|
+
`SELECT id FROM dataflow_vertices WHERE func_id = ? AND kind = 'param' AND param_index = ? LIMIT 1`,
|
|
487
|
+
);
|
|
488
|
+
const getLocalVertex = db.prepare(
|
|
489
|
+
`SELECT id FROM dataflow_vertices WHERE func_id = ? AND kind = 'local' AND name = ? LIMIT 1`,
|
|
490
|
+
);
|
|
491
|
+
const getReturnVertex = db.prepare(
|
|
492
|
+
`SELECT id FROM dataflow_vertices WHERE func_id = ? AND kind = 'return' LIMIT 1`,
|
|
493
|
+
);
|
|
494
|
+
const getCallEdge = db.prepare(
|
|
495
|
+
`SELECT id FROM edges WHERE source_id = ? AND target_id = ? AND kind = 'calls' LIMIT 1`,
|
|
496
|
+
);
|
|
497
|
+
const getSummary = db.prepare(
|
|
498
|
+
`SELECT flows_to_return FROM dataflow_summary WHERE func_id = ? AND param_index = ?`,
|
|
499
|
+
);
|
|
500
|
+
const insertInterEdge = db.prepare(
|
|
501
|
+
`INSERT INTO dataflow
|
|
502
|
+
(source_id, target_id, kind, source_vertex, target_vertex, scope, call_edge_id, expression, line, confidence)
|
|
503
|
+
VALUES (?, ?, ?, ?, ?, 'inter', ?, ?, ?, ?)`,
|
|
504
|
+
);
|
|
505
|
+
|
|
506
|
+
// Build capture map: "callerFuncId:calleeFuncId" → varName (first match wins)
|
|
507
|
+
const captureMap = new Map<string, string>();
|
|
508
|
+
for (const cap of captures) {
|
|
509
|
+
const key = `${cap.callerFuncId}:${cap.calleeFuncId}`;
|
|
510
|
+
if (!captureMap.has(key)) captureMap.set(key, cap.varName);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
let count = 0;
|
|
514
|
+
for (const cand of candidates) {
|
|
515
|
+
// Resolve call edge for this site
|
|
516
|
+
const callEdge = getCallEdge.get(cand.callerFuncId, cand.calleeFuncId) as {
|
|
517
|
+
id: number;
|
|
518
|
+
} | null;
|
|
519
|
+
const callEdgeId = callEdge?.id ?? null;
|
|
520
|
+
|
|
521
|
+
// Find source vertex x in caller
|
|
522
|
+
let srcVertexId: number | null = null;
|
|
523
|
+
if (cand.bindingType === 'param' && cand.bindingIndex != null) {
|
|
524
|
+
const v = getParamVertex.get(cand.callerFuncId, cand.bindingIndex) as { id: number } | null;
|
|
525
|
+
srcVertexId = v?.id ?? null;
|
|
526
|
+
} else if (cand.bindingType === 'local') {
|
|
527
|
+
const v = getLocalVertex.get(cand.callerFuncId, cand.argName) as { id: number } | null;
|
|
528
|
+
srcVertexId = v?.id ?? null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (!srcVertexId) continue;
|
|
532
|
+
|
|
533
|
+
// Find callee's param[argIndex] vertex
|
|
534
|
+
const calleeParam = getParamVertex.get(cand.calleeFuncId, cand.argIndex) as {
|
|
535
|
+
id: number;
|
|
536
|
+
} | null;
|
|
537
|
+
if (!calleeParam) continue;
|
|
538
|
+
|
|
539
|
+
// arg_in: A's source → B.param[j]
|
|
540
|
+
insertInterEdge.run(
|
|
541
|
+
cand.callerFuncId,
|
|
542
|
+
cand.calleeFuncId,
|
|
543
|
+
'arg_in',
|
|
544
|
+
srcVertexId,
|
|
545
|
+
calleeParam.id,
|
|
546
|
+
callEdgeId,
|
|
547
|
+
cand.expression,
|
|
548
|
+
cand.line,
|
|
549
|
+
cand.confidence,
|
|
550
|
+
);
|
|
551
|
+
count++;
|
|
552
|
+
|
|
553
|
+
// return_out: if B.param[j] reaches B's return, emit B.return → A's capture
|
|
554
|
+
const summary = getSummary.get(cand.calleeFuncId, cand.argIndex) as {
|
|
555
|
+
flows_to_return: number;
|
|
556
|
+
} | null;
|
|
557
|
+
if (summary?.flows_to_return) {
|
|
558
|
+
const calleeReturn = getReturnVertex.get(cand.calleeFuncId) as { id: number } | null;
|
|
559
|
+
if (calleeReturn) {
|
|
560
|
+
const captureVarName = captureMap.get(`${cand.callerFuncId}:${cand.calleeFuncId}`);
|
|
561
|
+
const captureVertex = captureVarName
|
|
562
|
+
? (getLocalVertex.get(cand.callerFuncId, captureVarName) as { id: number } | null)
|
|
563
|
+
: null;
|
|
564
|
+
if (captureVertex) {
|
|
565
|
+
insertInterEdge.run(
|
|
566
|
+
cand.calleeFuncId,
|
|
567
|
+
cand.callerFuncId,
|
|
568
|
+
'return_out',
|
|
569
|
+
calleeReturn.id,
|
|
570
|
+
captureVertex.id,
|
|
571
|
+
callEdgeId,
|
|
572
|
+
cand.expression,
|
|
573
|
+
cand.line,
|
|
574
|
+
cand.confidence,
|
|
575
|
+
);
|
|
576
|
+
count++;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return count;
|
|
583
|
+
}
|
|
584
|
+
|
|
239
585
|
// ── buildDataflowEdges ──────────────────────────────────────────────────────
|
|
240
586
|
|
|
587
|
+
// ── P4 helpers ───────────────────────────────────────────────────────────────
|
|
588
|
+
|
|
589
|
+
/** Return IDs of all function/method nodes in the given relative file paths. */
|
|
590
|
+
export function collectFuncIdsForFiles(
|
|
591
|
+
db: BetterSqlite3Database,
|
|
592
|
+
relPaths: Iterable<string>,
|
|
593
|
+
): number[] {
|
|
594
|
+
const stmt = db.prepare(`SELECT id FROM nodes WHERE file = ? AND kind IN ('function', 'method')`);
|
|
595
|
+
const ids: number[] = [];
|
|
596
|
+
for (const p of relPaths) {
|
|
597
|
+
for (const row of stmt.all(p) as { id: number }[]) ids.push(row.id);
|
|
598
|
+
}
|
|
599
|
+
return ids;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* P4: Re-collect stitch candidates from caller files that were NOT in the
|
|
604
|
+
* changed set but contain calls to functions that WERE changed.
|
|
605
|
+
*
|
|
606
|
+
* During an incremental build the changed files' param vertices are purged
|
|
607
|
+
* and recreated, but the callers' files are never re-parsed — so their
|
|
608
|
+
* arg_in edges (pointing to the old param vertices) are deleted and never
|
|
609
|
+
* replaced. This function reads those caller files from disk and rebuilds
|
|
610
|
+
* the StitchCandidate list so runInterproceduralStitch can reconnect them.
|
|
611
|
+
*/
|
|
612
|
+
export async function collectCallerStitchCandidates(
|
|
613
|
+
db: BetterSqlite3Database,
|
|
614
|
+
changedFuncIds: number[],
|
|
615
|
+
changedRelPaths: Set<string>,
|
|
616
|
+
rootDir: string,
|
|
617
|
+
extToLang: Map<string, string>,
|
|
618
|
+
parsers: unknown,
|
|
619
|
+
getParserFn: ((parsers: any, absPath: string) => any) | null,
|
|
620
|
+
): Promise<{ candidates: StitchCandidate[]; captures: ReturnCapture[] }> {
|
|
621
|
+
if (changedFuncIds.length === 0) return { candidates: [], captures: [] };
|
|
622
|
+
|
|
623
|
+
// Find distinct caller files that have flows_to edges targeting any changed
|
|
624
|
+
// function and are NOT already in the changed file set (those are handled by
|
|
625
|
+
// the main per-file loop).
|
|
626
|
+
//
|
|
627
|
+
// Chunk the query to avoid exceeding SQLite's SQLITE_MAX_VARIABLE_NUMBER
|
|
628
|
+
// (999 on older builds, 32766 on SQLite ≥ 3.32). 500 is a safe batch size
|
|
629
|
+
// that works across all SQLite versions.
|
|
630
|
+
const CHUNK_SIZE = 500;
|
|
631
|
+
const callerFileSet = new Set<string>();
|
|
632
|
+
for (let i = 0; i < changedFuncIds.length; i += CHUNK_SIZE) {
|
|
633
|
+
const chunk = changedFuncIds.slice(i, i + CHUNK_SIZE);
|
|
634
|
+
const placeholders = chunk.map(() => '?').join(',');
|
|
635
|
+
const rows = db
|
|
636
|
+
.prepare(
|
|
637
|
+
`SELECT DISTINCT n.file AS caller_file
|
|
638
|
+
FROM dataflow d
|
|
639
|
+
JOIN nodes n ON n.id = d.source_id
|
|
640
|
+
WHERE d.target_id IN (${placeholders})
|
|
641
|
+
AND d.kind = 'flows_to'`,
|
|
642
|
+
)
|
|
643
|
+
.all(...chunk) as { caller_file: string }[];
|
|
644
|
+
for (const r of rows) callerFileSet.add(r.caller_file);
|
|
645
|
+
}
|
|
646
|
+
const callerFileRows = [...callerFileSet].map((f) => ({ caller_file: f }));
|
|
647
|
+
|
|
648
|
+
const callerFiles = callerFileRows
|
|
649
|
+
.map((r) => r.caller_file)
|
|
650
|
+
.filter((f) => !changedRelPaths.has(f));
|
|
651
|
+
|
|
652
|
+
if (callerFiles.length === 0) return { candidates: [], captures: [] };
|
|
653
|
+
|
|
654
|
+
// Ensure parsers are available — the main loop may have skipped loading them
|
|
655
|
+
// if all changed files came through the native bulk-insert path.
|
|
656
|
+
let activeParsers = parsers;
|
|
657
|
+
let activeGetParserFn = getParserFn;
|
|
658
|
+
if (!activeGetParserFn) {
|
|
659
|
+
const { createParsers, getParser } = await import('../domain/parser.js');
|
|
660
|
+
activeParsers = await createParsers();
|
|
661
|
+
activeGetParserFn = getParser;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const changedFuncIdSet = new Set(changedFuncIds);
|
|
665
|
+
const stmts = prepareNodeResolvers(db);
|
|
666
|
+
const candidates: StitchCandidate[] = [];
|
|
667
|
+
const captures: ReturnCapture[] = [];
|
|
668
|
+
|
|
669
|
+
for (const callerFile of callerFiles) {
|
|
670
|
+
// Read the caller file from disk without touching its existing DB rows.
|
|
671
|
+
// definitions: [] is an intentional stub — P4 only needs argFlow/assignment
|
|
672
|
+
// data from the visitor, not pre-loaded symbol definitions. extractDataflow
|
|
673
|
+
// does not currently use _definitions, so this is safe. If that changes,
|
|
674
|
+
// the stub must be replaced with the actual symbol list for the caller file.
|
|
675
|
+
const stub: FileSymbolsDataflow = { definitions: [], _langId: null, _tree: null };
|
|
676
|
+
const data = getDataflowForFile(
|
|
677
|
+
stub,
|
|
678
|
+
callerFile,
|
|
679
|
+
rootDir,
|
|
680
|
+
extToLang,
|
|
681
|
+
activeParsers,
|
|
682
|
+
activeGetParserFn,
|
|
683
|
+
);
|
|
684
|
+
if (!data) continue;
|
|
685
|
+
|
|
686
|
+
const resolver = makeNodeResolver(stmts, callerFile);
|
|
687
|
+
const argFlows = data.argFlows as unknown as VisitorArgFlow[];
|
|
688
|
+
const assignments = data.assignments as unknown as VisitorAssignment[];
|
|
689
|
+
|
|
690
|
+
for (const af of argFlows) {
|
|
691
|
+
const callerFn = resolver(af.callerFunc);
|
|
692
|
+
const calleeFn = resolver(af.calleeName);
|
|
693
|
+
if (!callerFn || !calleeFn) continue;
|
|
694
|
+
if (!changedFuncIdSet.has(calleeFn.id)) continue; // only re-stitch calls to changed callees
|
|
695
|
+
candidates.push({
|
|
696
|
+
callerFuncId: callerFn.id,
|
|
697
|
+
calleeFuncId: calleeFn.id,
|
|
698
|
+
argIndex: af.argIndex,
|
|
699
|
+
bindingType: af.binding.type,
|
|
700
|
+
bindingIndex: af.binding.index,
|
|
701
|
+
argName: af.argName,
|
|
702
|
+
expression: af.expression,
|
|
703
|
+
line: af.line,
|
|
704
|
+
confidence: af.confidence,
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
for (const a of assignments) {
|
|
709
|
+
const callerFn = resolver(a.callerFunc);
|
|
710
|
+
const calleeFn = resolver(a.sourceCallName);
|
|
711
|
+
if (!callerFn || !calleeFn) continue;
|
|
712
|
+
if (!changedFuncIdSet.has(calleeFn.id)) continue;
|
|
713
|
+
captures.push({ callerFuncId: callerFn.id, calleeFuncId: calleeFn.id, varName: a.varName });
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
debug(
|
|
718
|
+
`Dataflow P4: re-stitched ${candidates.length} candidate(s) from ${callerFiles.length} caller file(s)`,
|
|
719
|
+
);
|
|
720
|
+
return { candidates, captures };
|
|
721
|
+
}
|
|
722
|
+
|
|
241
723
|
function prepareNodeResolvers(db: BetterSqlite3Database): {
|
|
242
724
|
getNodeByNameAndFile: ReturnType<BetterSqlite3Database['prepare']>;
|
|
243
725
|
getNodeByName: ReturnType<BetterSqlite3Database['prepare']>;
|
|
@@ -259,11 +741,24 @@ function makeNodeResolver(
|
|
|
259
741
|
stmts: ReturnType<typeof prepareNodeResolvers>,
|
|
260
742
|
relPath: string,
|
|
261
743
|
): (funcName: string) => { id: number } | null {
|
|
744
|
+
// Memoise per (relPath, funcName). buildDataflowVerticesAndEdges resolves the
|
|
745
|
+
// same handful of function names many times per file — once per param, return,
|
|
746
|
+
// assignment, argFlow, summary row, and capture — and each miss costs one or
|
|
747
|
+
// two `nodes` table queries. The nodes table is never mutated during the P6
|
|
748
|
+
// vertex pass (only dataflow* tables are written), so the lookup is stable for
|
|
749
|
+
// the lifetime of the resolver; caching collapses tens of thousands of
|
|
750
|
+
// redundant queries on a full build into one per distinct name (#perf).
|
|
751
|
+
const cache = new Map<string, { id: number } | null>();
|
|
262
752
|
return (funcName: string): { id: number } | null => {
|
|
753
|
+
const cached = cache.get(funcName);
|
|
754
|
+
if (cached !== undefined) return cached;
|
|
263
755
|
const local = stmts.getNodeByNameAndFile.all(funcName, relPath) as { id: number }[];
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
756
|
+
const resolved =
|
|
757
|
+
local.length > 0
|
|
758
|
+
? local[0]!
|
|
759
|
+
: ((stmts.getNodeByName.all(funcName) as { id: number }[])[0] ?? null);
|
|
760
|
+
cache.set(funcName, resolved);
|
|
761
|
+
return resolved;
|
|
267
762
|
};
|
|
268
763
|
}
|
|
269
764
|
|
|
@@ -318,6 +813,128 @@ function collectNativeEdges(
|
|
|
318
813
|
}
|
|
319
814
|
}
|
|
320
815
|
|
|
816
|
+
/**
|
|
817
|
+
* P6 vertex-only pass for the native orchestrator path.
|
|
818
|
+
*
|
|
819
|
+
* When the Rust orchestrator runs with analysisComplete=true it inserts
|
|
820
|
+
* flows_to/returns/mutates edges directly into the DB but never writes to
|
|
821
|
+
* dataflow_vertices or dataflow_summary. This function takes pre-extracted
|
|
822
|
+
* DataflowResult objects (from native.extractDataflowAnalysis) and builds
|
|
823
|
+
* the missing vertex rows and inter-procedural edges — without touching the
|
|
824
|
+
* already-correct function-level edges.
|
|
825
|
+
*/
|
|
826
|
+
export function buildDataflowVerticesFromMap(
|
|
827
|
+
db: BetterSqlite3Database,
|
|
828
|
+
dataflowMap: Map<string, DataflowResult>,
|
|
829
|
+
extraCandidates?: StitchCandidate[],
|
|
830
|
+
extraCaptures?: ReturnCapture[],
|
|
831
|
+
): number {
|
|
832
|
+
const vstmts = prepareVertexStmts(db);
|
|
833
|
+
if (!vstmts.available || dataflowMap.size === 0) return 0;
|
|
834
|
+
|
|
835
|
+
const stmts = prepareNodeResolvers(db);
|
|
836
|
+
|
|
837
|
+
// Vertex writes and inter-procedural stitch are a single atomic unit: if the
|
|
838
|
+
// stitch throws or the process is killed between the two, the DB would be left
|
|
839
|
+
// with dataflow_vertices rows but no arg_in/return_out edges. Wrapping both
|
|
840
|
+
// under one transaction boundary prevents that half-written state.
|
|
841
|
+
let stitchCount = 0;
|
|
842
|
+
const tx = db.transaction(() => {
|
|
843
|
+
const allCandidates: StitchCandidate[] = [];
|
|
844
|
+
const allCaptures: ReturnCapture[] = [];
|
|
845
|
+
|
|
846
|
+
for (const [relPath, data] of dataflowMap) {
|
|
847
|
+
const resolver = makeNodeResolver(stmts, relPath);
|
|
848
|
+
const { candidates, captures } = buildDataflowVerticesAndEdges(db, vstmts, data, resolver);
|
|
849
|
+
allCandidates.push(...candidates);
|
|
850
|
+
allCaptures.push(...captures);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// P4: merge in stitch candidates from unchanged caller files if provided.
|
|
854
|
+
if (extraCandidates && extraCandidates.length > 0) allCandidates.push(...extraCandidates);
|
|
855
|
+
if (extraCaptures && extraCaptures.length > 0) allCaptures.push(...extraCaptures);
|
|
856
|
+
|
|
857
|
+
stitchCount = runInterproceduralStitch(db, allCandidates, allCaptures);
|
|
858
|
+
});
|
|
859
|
+
tx();
|
|
860
|
+
|
|
861
|
+
return stitchCount;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* P4 re-stitch pass for the native engine path.
|
|
866
|
+
*
|
|
867
|
+
* On incremental builds the changed files' param vertices are purged and
|
|
868
|
+
* recreated by the P6 vertex pass, but unchanged caller files are never
|
|
869
|
+
* re-parsed — so their arg_in edges (which pointed to the old param vertex
|
|
870
|
+
* IDs) are deleted and not replaced. This function re-parses those caller
|
|
871
|
+
* files and rebuilds the arg_in edges for any call that targets a function
|
|
872
|
+
* in one of the changed callee files.
|
|
873
|
+
*
|
|
874
|
+
* Called by the native orchestrator after buildDataflowVerticesFromMap.
|
|
875
|
+
* Safe to call on full builds — the guard below exits early when
|
|
876
|
+
* changedFiles covers all distinct files in the DB.
|
|
877
|
+
*/
|
|
878
|
+
export async function buildDataflowP4ForNative(
|
|
879
|
+
db: BetterSqlite3Database,
|
|
880
|
+
changedFiles: string[],
|
|
881
|
+
rootDir: string,
|
|
882
|
+
): Promise<void> {
|
|
883
|
+
if (changedFiles.length === 0) return;
|
|
884
|
+
|
|
885
|
+
// Deduplicate upfront so the full-build guard uses unique-file count, not
|
|
886
|
+
// raw array length (the orchestrator may emit the same path more than once).
|
|
887
|
+
const changedRelPaths = new Set(changedFiles);
|
|
888
|
+
|
|
889
|
+
// Skip on full builds — all files were in the changed set, so there are no
|
|
890
|
+
// unchanged callers to re-stitch.
|
|
891
|
+
const totalFilesInDb = (
|
|
892
|
+
db.prepare(`SELECT COUNT(DISTINCT file) AS n FROM nodes`).get() as { n: number }
|
|
893
|
+
).n;
|
|
894
|
+
if (changedRelPaths.size >= totalFilesInDb) return;
|
|
895
|
+
|
|
896
|
+
const extToLang = buildExtToLangMap();
|
|
897
|
+
const changedFuncIds = collectFuncIdsForFiles(db, changedRelPaths);
|
|
898
|
+
if (changedFuncIds.length === 0) return;
|
|
899
|
+
|
|
900
|
+
// parsers=null, getParserFn=null → collectCallerStitchCandidates initialises
|
|
901
|
+
// WASM parsers lazily for the caller files it needs to re-parse.
|
|
902
|
+
const { candidates, captures } = await collectCallerStitchCandidates(
|
|
903
|
+
db,
|
|
904
|
+
changedFuncIds,
|
|
905
|
+
changedRelPaths,
|
|
906
|
+
rootDir,
|
|
907
|
+
extToLang,
|
|
908
|
+
null,
|
|
909
|
+
null,
|
|
910
|
+
);
|
|
911
|
+
|
|
912
|
+
if (candidates.length > 0) {
|
|
913
|
+
let count = 0;
|
|
914
|
+
// Wrap the DELETE + stitch in a single transaction so that a crash between
|
|
915
|
+
// the purge and the re-insert cannot leave arg_in edges permanently removed
|
|
916
|
+
// but never replaced.
|
|
917
|
+
db.transaction(() => {
|
|
918
|
+
// Purge any existing arg_in edges targeting the changed callees from unchanged
|
|
919
|
+
// caller files. These edges were inserted by a previous P4 pass. Without this
|
|
920
|
+
// guard, repeated calls to buildDataflowP4ForNative insert duplicates because
|
|
921
|
+
// the dataflow table has no UNIQUE constraint on (source_vertex, target_vertex).
|
|
922
|
+
const placeholders = changedFuncIds.map(() => '?').join(',');
|
|
923
|
+
db.prepare(
|
|
924
|
+
`DELETE FROM dataflow
|
|
925
|
+
WHERE kind = 'arg_in'
|
|
926
|
+
AND target_id IN (${placeholders})
|
|
927
|
+
AND source_id NOT IN (${placeholders})`,
|
|
928
|
+
).run(...changedFuncIds, ...changedFuncIds);
|
|
929
|
+
|
|
930
|
+
count = runInterproceduralStitch(db, candidates, captures);
|
|
931
|
+
})();
|
|
932
|
+
if (count > 0) {
|
|
933
|
+
info(`Dataflow (native P4): ${count} inter-procedural edges re-stitched`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
|
|
321
938
|
export async function buildDataflowEdges(
|
|
322
939
|
db: BetterSqlite3Database,
|
|
323
940
|
fileSymbols: Map<string, FileSymbolsDataflow>,
|
|
@@ -359,6 +976,83 @@ export async function buildDataflowEdges(
|
|
|
359
976
|
}
|
|
360
977
|
info(`Dataflow (native bulk): ${inserted} edges inserted`);
|
|
361
978
|
}
|
|
979
|
+
|
|
980
|
+
// P6: vertex extraction on the native path.
|
|
981
|
+
// Rust DataflowResult already contains parameters/returns — no re-parse needed.
|
|
982
|
+
const vstmts = prepareVertexStmts(db);
|
|
983
|
+
if (vstmts.available) {
|
|
984
|
+
// P4: Incremental re-stitch — unchanged caller files are not in
|
|
985
|
+
// fileSymbols so their arg_in edges to the old param vertices were
|
|
986
|
+
// deleted by the purge and never recreated. Re-collect stitch
|
|
987
|
+
// candidates from those caller files by parsing them from disk.
|
|
988
|
+
//
|
|
989
|
+
// Skip on full builds: fileSymbols covers every file in the DB, so
|
|
990
|
+
// there are no unchanged callers to re-stitch.
|
|
991
|
+
//
|
|
992
|
+
// This async disk-read step must happen BEFORE the transaction opens
|
|
993
|
+
// (SQLite transactions are synchronous; async work inside them is not
|
|
994
|
+
// supported by better-sqlite3).
|
|
995
|
+
const totalFilesInDb = (
|
|
996
|
+
db.prepare(`SELECT COUNT(DISTINCT file) AS n FROM nodes`).get() as { n: number }
|
|
997
|
+
).n;
|
|
998
|
+
let p4CallerCount = 0;
|
|
999
|
+
const p4Candidates: StitchCandidate[] = [];
|
|
1000
|
+
const p4Captures: ReturnCapture[] = [];
|
|
1001
|
+
if (fileSymbols.size < totalFilesInDb) {
|
|
1002
|
+
const changedRelPaths = new Set<string>(fileSymbols.keys());
|
|
1003
|
+
const changedFuncIds = collectFuncIdsForFiles(db, changedRelPaths);
|
|
1004
|
+
const extra = await collectCallerStitchCandidates(
|
|
1005
|
+
db,
|
|
1006
|
+
changedFuncIds,
|
|
1007
|
+
changedRelPaths,
|
|
1008
|
+
rootDir,
|
|
1009
|
+
extToLang,
|
|
1010
|
+
null,
|
|
1011
|
+
null,
|
|
1012
|
+
);
|
|
1013
|
+
p4Candidates.push(...extra.candidates);
|
|
1014
|
+
p4Captures.push(...extra.captures);
|
|
1015
|
+
p4CallerCount = extra.candidates.length;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
// Vertex writes and inter-procedural stitch are a single atomic unit:
|
|
1019
|
+
// if the stitch throws or the process is killed between the two, the DB
|
|
1020
|
+
// would be left with dataflow_vertices rows but no arg_in/return_out
|
|
1021
|
+
// edges. Wrapping both under one transaction boundary prevents that
|
|
1022
|
+
// half-written state.
|
|
1023
|
+
let interCount = 0;
|
|
1024
|
+
const txVertex = db.transaction(() => {
|
|
1025
|
+
const allCandidates: StitchCandidate[] = [];
|
|
1026
|
+
const allCaptures: ReturnCapture[] = [];
|
|
1027
|
+
|
|
1028
|
+
for (const [relPath, symbols] of fileSymbols) {
|
|
1029
|
+
if (!symbols.dataflow) continue;
|
|
1030
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
1031
|
+
if (!DATAFLOW_EXTENSIONS.has(ext)) continue;
|
|
1032
|
+
const resolver = makeNodeResolver(stmts, relPath);
|
|
1033
|
+
const { candidates, captures } = buildDataflowVerticesAndEdges(
|
|
1034
|
+
db,
|
|
1035
|
+
vstmts,
|
|
1036
|
+
symbols.dataflow,
|
|
1037
|
+
resolver,
|
|
1038
|
+
);
|
|
1039
|
+
allCandidates.push(...candidates);
|
|
1040
|
+
allCaptures.push(...captures);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Merge in the P4 candidates collected above before opening the tx.
|
|
1044
|
+
allCandidates.push(...p4Candidates);
|
|
1045
|
+
allCaptures.push(...p4Captures);
|
|
1046
|
+
|
|
1047
|
+
interCount = runInterproceduralStitch(db, allCandidates, allCaptures);
|
|
1048
|
+
});
|
|
1049
|
+
txVertex();
|
|
1050
|
+
|
|
1051
|
+
info(
|
|
1052
|
+
`Dataflow (native): ${interCount} inter-procedural edges inserted${p4CallerCount > 0 ? ` (P4: ${p4CallerCount} re-stitch candidate(s) from unchanged callers)` : ''}`,
|
|
1053
|
+
);
|
|
1054
|
+
}
|
|
1055
|
+
|
|
362
1056
|
return;
|
|
363
1057
|
}
|
|
364
1058
|
debug('Dataflow: some files lack pre-computed data — falling back to JS');
|
|
@@ -373,9 +1067,52 @@ export async function buildDataflowEdges(
|
|
|
373
1067
|
);
|
|
374
1068
|
|
|
375
1069
|
const stmts = prepareNodeResolvers(db);
|
|
376
|
-
|
|
1070
|
+
const vstmts = prepareVertexStmts(db);
|
|
1071
|
+
|
|
1072
|
+
// P4: Incremental re-stitch — if only a subset of files changed, callers of
|
|
1073
|
+
// the changed functions were not in fileSymbols, so their arg_in edges were
|
|
1074
|
+
// deleted by the purge but never reconstructed. Re-collect stitch candidates
|
|
1075
|
+
// from those caller files now (read from disk, no DB writes).
|
|
1076
|
+
//
|
|
1077
|
+
// Skip P4 on full builds: when fileSymbols covers every file in the DB there
|
|
1078
|
+
// are no unchanged callers, and collectFuncIdsForFiles would issue one SELECT
|
|
1079
|
+
// per file for nothing. A single COUNT query is cheaper than N per-file SELECTs.
|
|
1080
|
+
//
|
|
1081
|
+
// This async disk-read step must happen BEFORE the transaction opens
|
|
1082
|
+
// (SQLite transactions are synchronous; async work inside them is not
|
|
1083
|
+
// supported by better-sqlite3).
|
|
1084
|
+
const totalFilesInDb = (
|
|
1085
|
+
db.prepare(`SELECT COUNT(DISTINCT file) AS n FROM nodes`).get() as { n: number }
|
|
1086
|
+
).n;
|
|
1087
|
+
const p4Candidates: StitchCandidate[] = [];
|
|
1088
|
+
const p4Captures: ReturnCapture[] = [];
|
|
1089
|
+
if (vstmts.available && fileSymbols.size < totalFilesInDb) {
|
|
1090
|
+
const changedRelPaths = new Set<string>(fileSymbols.keys());
|
|
1091
|
+
const changedFuncIds = collectFuncIdsForFiles(db, changedRelPaths);
|
|
1092
|
+
const extra = await collectCallerStitchCandidates(
|
|
1093
|
+
db,
|
|
1094
|
+
changedFuncIds,
|
|
1095
|
+
changedRelPaths,
|
|
1096
|
+
rootDir,
|
|
1097
|
+
extToLang,
|
|
1098
|
+
parsers,
|
|
1099
|
+
getParserFn,
|
|
1100
|
+
);
|
|
1101
|
+
p4Candidates.push(...extra.candidates);
|
|
1102
|
+
p4Captures.push(...extra.captures);
|
|
1103
|
+
}
|
|
377
1104
|
|
|
1105
|
+
// Edge writes, vertex writes, and inter-procedural stitch are a single
|
|
1106
|
+
// atomic unit: if the stitch throws or the process is killed between vertex
|
|
1107
|
+
// writes and the stitch, the DB would be left with dataflow_vertices rows
|
|
1108
|
+
// but no arg_in/return_out edges. Wrapping all three under one transaction
|
|
1109
|
+
// boundary prevents that half-written state.
|
|
1110
|
+
let totalEdges = 0;
|
|
1111
|
+
let interCount = 0;
|
|
378
1112
|
const tx = db.transaction(() => {
|
|
1113
|
+
const allCandidates: StitchCandidate[] = [];
|
|
1114
|
+
const allCaptures: ReturnCapture[] = [];
|
|
1115
|
+
|
|
379
1116
|
for (const [relPath, symbols] of fileSymbols) {
|
|
380
1117
|
const ext = path.extname(relPath).toLowerCase();
|
|
381
1118
|
if (!DATAFLOW_EXTENSIONS.has(ext)) continue;
|
|
@@ -383,12 +1120,24 @@ export async function buildDataflowEdges(
|
|
|
383
1120
|
const data = getDataflowForFile(symbols, relPath, rootDir, extToLang, parsers, getParserFn);
|
|
384
1121
|
if (!data) continue;
|
|
385
1122
|
|
|
386
|
-
|
|
1123
|
+
const resolver = makeNodeResolver(stmts, relPath);
|
|
1124
|
+
totalEdges += insertDataflowEdges(insert, data, resolver);
|
|
1125
|
+
const { candidates, captures } = buildDataflowVerticesAndEdges(db, vstmts, data, resolver);
|
|
1126
|
+
allCandidates.push(...candidates);
|
|
1127
|
+
allCaptures.push(...captures);
|
|
387
1128
|
}
|
|
1129
|
+
|
|
1130
|
+
// Merge in the P4 candidates collected above before opening the tx.
|
|
1131
|
+
allCandidates.push(...p4Candidates);
|
|
1132
|
+
allCaptures.push(...p4Captures);
|
|
1133
|
+
|
|
1134
|
+
// P2: inter-procedural stitch — runs after all per-file vertices + summaries written
|
|
1135
|
+
interCount = vstmts.available ? runInterproceduralStitch(db, allCandidates, allCaptures) : 0;
|
|
388
1136
|
});
|
|
389
1137
|
|
|
390
1138
|
tx();
|
|
391
|
-
|
|
1139
|
+
|
|
1140
|
+
info(`Dataflow: ${totalEdges} fn-level edges, ${interCount} inter-procedural edges inserted`);
|
|
392
1141
|
}
|
|
393
1142
|
|
|
394
1143
|
// ── Query functions ─────────────────────────────────────────────────────────
|