@optave/codegraph 3.4.0 → 3.4.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 +7 -7
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +3 -9
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/shared.d.ts.map +1 -1
- package/dist/ast-analysis/shared.js +0 -1
- package/dist/ast-analysis/shared.js.map +1 -1
- package/dist/ast-analysis/visitors/cfg-conditionals.d.ts +5 -0
- package/dist/ast-analysis/visitors/cfg-conditionals.d.ts.map +1 -0
- package/dist/ast-analysis/visitors/cfg-conditionals.js +166 -0
- package/dist/ast-analysis/visitors/cfg-conditionals.js.map +1 -0
- package/dist/ast-analysis/visitors/cfg-loops.d.ts +7 -0
- package/dist/ast-analysis/visitors/cfg-loops.d.ts.map +1 -0
- package/dist/ast-analysis/visitors/cfg-loops.js +73 -0
- package/dist/ast-analysis/visitors/cfg-loops.js.map +1 -0
- package/dist/ast-analysis/visitors/cfg-shared.d.ts +56 -0
- package/dist/ast-analysis/visitors/cfg-shared.d.ts.map +1 -0
- package/dist/ast-analysis/visitors/cfg-shared.js +107 -0
- package/dist/ast-analysis/visitors/cfg-shared.js.map +1 -0
- package/dist/ast-analysis/visitors/cfg-try-catch.d.ts +4 -0
- package/dist/ast-analysis/visitors/cfg-try-catch.d.ts.map +1 -0
- package/dist/ast-analysis/visitors/cfg-try-catch.js +100 -0
- package/dist/ast-analysis/visitors/cfg-try-catch.js.map +1 -0
- package/dist/ast-analysis/visitors/cfg-visitor.d.ts +2 -2
- package/dist/ast-analysis/visitors/cfg-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/cfg-visitor.js +11 -445
- 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.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/batch.d.ts.map +1 -1
- package/dist/cli/commands/batch.js +4 -3
- package/dist/cli/commands/batch.js.map +1 -1
- package/dist/cli/commands/branch-compare.js +1 -1
- package/dist/cli/commands/branch-compare.js.map +1 -1
- package/dist/cli/commands/build.js +1 -1
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +1 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/path.d.ts.map +1 -1
- package/dist/cli/commands/path.js +7 -2
- package/dist/cli/commands/path.js.map +1 -1
- package/dist/cli/commands/plot.d.ts.map +1 -1
- package/dist/cli/commands/plot.js +2 -2
- package/dist/cli/commands/plot.js.map +1 -1
- package/dist/cli/commands/watch.js +1 -1
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/shared/open-graph.d.ts +2 -2
- package/dist/cli/shared/open-graph.d.ts.map +1 -1
- package/dist/cli/shared/open-graph.js.map +1 -1
- package/dist/cli/types.d.ts +1 -1
- package/dist/cli/types.d.ts.map +1 -1
- package/dist/cli.js +2 -3
- package/dist/cli.js.map +1 -1
- package/dist/db/connection.d.ts +17 -0
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +91 -2
- 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 +7 -0
- package/dist/db/migrations.js.map +1 -1
- package/dist/domain/analysis/brief.d.ts.map +1 -1
- package/dist/domain/analysis/brief.js +1 -3
- 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 +2 -4
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts +49 -0
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +145 -0
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/diff-impact.d.ts +76 -0
- package/dist/domain/analysis/diff-impact.d.ts.map +1 -0
- package/dist/domain/analysis/diff-impact.js +282 -0
- package/dist/domain/analysis/diff-impact.js.map +1 -0
- package/dist/domain/analysis/exports.d.ts.map +1 -1
- package/dist/domain/analysis/exports.js +0 -1
- package/dist/domain/analysis/exports.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +66 -0
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -0
- package/dist/domain/analysis/fn-impact.js +189 -0
- package/dist/domain/analysis/fn-impact.js.map +1 -0
- package/dist/domain/analysis/impact.d.ts +8 -148
- package/dist/domain/analysis/impact.d.ts.map +1 -1
- package/dist/domain/analysis/impact.js +8 -568
- package/dist/domain/analysis/impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +1 -3
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/context.d.ts +2 -3
- 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 +4 -5
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +1 -2
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +2 -3
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- 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 +6 -0
- 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 +12 -2
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +155 -59
- package/dist/domain/graph/builder/stages/build-structure.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 +6 -6
- 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 +85 -61
- 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.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 +58 -11
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.js +2 -2
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/resolve.d.ts.map +1 -1
- package/dist/domain/graph/resolve.js +10 -8
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +1 -3
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +11 -12
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/queries.d.ts +3 -2
- package/dist/domain/queries.d.ts.map +1 -1
- package/dist/domain/queries.js +3 -2
- package/dist/domain/queries.js.map +1 -1
- package/dist/domain/search/generator.d.ts.map +1 -1
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/extractors/csharp.js +2 -2
- package/dist/extractors/csharp.js.map +1 -1
- package/dist/extractors/go.js +2 -2
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/helpers.d.ts +5 -0
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +5 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/javascript.js +58 -60
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/php.js +2 -2
- package/dist/extractors/php.js.map +1 -1
- package/dist/extractors/python.js +2 -2
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/rust.js +2 -2
- package/dist/extractors/rust.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +1 -2
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +2 -3
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +2 -4
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/cochange.js +4 -4
- package/dist/features/cochange.js.map +1 -1
- package/dist/features/communities.js +4 -4
- package/dist/features/communities.js.map +1 -1
- package/dist/features/complexity-query.d.ts +37 -0
- package/dist/features/complexity-query.d.ts.map +1 -0
- package/dist/features/complexity-query.js +263 -0
- package/dist/features/complexity-query.js.map +1 -0
- package/dist/features/complexity.d.ts +2 -30
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +7 -261
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +8 -24
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/export.d.ts +7 -8
- package/dist/features/export.d.ts.map +1 -1
- package/dist/features/export.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +1 -3
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/manifesto.js +8 -8
- package/dist/features/manifesto.js.map +1 -1
- package/dist/features/snapshot.d.ts.map +1 -1
- package/dist/features/snapshot.js +0 -1
- package/dist/features/snapshot.js.map +1 -1
- package/dist/features/structure-query.d.ts +76 -0
- package/dist/features/structure-query.d.ts.map +1 -0
- package/dist/features/structure-query.js +245 -0
- package/dist/features/structure-query.js.map +1 -0
- package/dist/features/structure.d.ts +12 -67
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +188 -244
- package/dist/features/structure.js.map +1 -1
- package/dist/features/triage.js +2 -2
- package/dist/features/triage.js.map +1 -1
- package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/adapter.js +2 -9
- package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts +5 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +20 -12
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +12 -11
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/native.d.ts.map +1 -1
- package/dist/infrastructure/native.js +7 -3
- package/dist/infrastructure/native.js.map +1 -1
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +1 -1
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/infrastructure/update-check.js +3 -3
- package/dist/infrastructure/update-check.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +2 -8
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +9 -4
- package/dist/mcp/tool-registry.js.map +1 -1
- package/dist/mcp/tools/audit.js +1 -1
- package/dist/mcp/tools/audit.js.map +1 -1
- package/dist/mcp/tools/cfg.js +1 -1
- package/dist/mcp/tools/cfg.js.map +1 -1
- package/dist/mcp/tools/check.js +2 -2
- package/dist/mcp/tools/check.js.map +1 -1
- package/dist/mcp/tools/dataflow.js +2 -2
- package/dist/mcp/tools/dataflow.js.map +1 -1
- package/dist/mcp/tools/export-graph.js +1 -1
- package/dist/mcp/tools/export-graph.js.map +1 -1
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/path.d.ts +1 -0
- package/dist/mcp/tools/path.d.ts.map +1 -1
- package/dist/mcp/tools/path.js +9 -0
- package/dist/mcp/tools/path.js.map +1 -1
- package/dist/mcp/tools/query.js +1 -1
- package/dist/mcp/tools/query.js.map +1 -1
- package/dist/mcp/tools/semantic-search.js +1 -1
- package/dist/mcp/tools/semantic-search.js.map +1 -1
- package/dist/mcp/tools/sequence.js +1 -1
- package/dist/mcp/tools/sequence.js.map +1 -1
- package/dist/mcp/tools/symbol-children.js +1 -1
- package/dist/mcp/tools/symbol-children.js.map +1 -1
- package/dist/mcp/tools/triage.js +1 -1
- package/dist/mcp/tools/triage.js.map +1 -1
- package/dist/presentation/audit.d.ts.map +1 -1
- package/dist/presentation/audit.js +0 -1
- package/dist/presentation/audit.js.map +1 -1
- package/dist/presentation/diff-impact-mermaid.d.ts +11 -0
- package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -0
- package/dist/presentation/diff-impact-mermaid.js +105 -0
- package/dist/presentation/diff-impact-mermaid.js.map +1 -0
- package/dist/presentation/flow.d.ts.map +1 -1
- package/dist/presentation/flow.js +0 -2
- package/dist/presentation/flow.js.map +1 -1
- package/dist/presentation/manifesto.d.ts.map +1 -1
- package/dist/presentation/manifesto.js +0 -1
- package/dist/presentation/manifesto.js.map +1 -1
- package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
- package/dist/presentation/queries-cli/inspect.js.map +1 -1
- package/dist/presentation/queries-cli/path.d.ts.map +1 -1
- package/dist/presentation/queries-cli/path.js +45 -1
- package/dist/presentation/queries-cli/path.js.map +1 -1
- package/dist/presentation/result-formatter.d.ts.map +1 -1
- package/dist/presentation/result-formatter.js +1 -3
- package/dist/presentation/result-formatter.js.map +1 -1
- package/dist/presentation/sequence.d.ts.map +1 -1
- package/dist/presentation/sequence.js +0 -1
- package/dist/presentation/sequence.js.map +1 -1
- package/dist/presentation/structure.d.ts.map +1 -1
- package/dist/presentation/structure.js.map +1 -1
- package/dist/presentation/triage.d.ts.map +1 -1
- package/dist/presentation/triage.js +0 -1
- package/dist/presentation/triage.js.map +1 -1
- package/dist/shared/constants.d.ts +9 -3
- package/dist/shared/constants.d.ts.map +1 -1
- package/dist/shared/constants.js +6 -3
- package/dist/shared/constants.js.map +1 -1
- package/dist/shared/errors.d.ts +2 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +4 -0
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/version.d.ts +2 -0
- package/dist/shared/version.d.ts.map +1 -0
- package/dist/shared/version.js +5 -0
- package/dist/shared/version.js.map +1 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/ast-analysis/engine.ts +3 -9
- package/src/ast-analysis/shared.ts +0 -1
- package/src/ast-analysis/visitors/cfg-conditionals.ts +227 -0
- package/src/ast-analysis/visitors/cfg-loops.ts +136 -0
- package/src/ast-analysis/visitors/cfg-shared.ts +196 -0
- package/src/ast-analysis/visitors/cfg-try-catch.ts +142 -0
- package/src/ast-analysis/visitors/cfg-visitor.ts +34 -655
- package/src/ast-analysis/visitors/complexity-visitor.ts +0 -1
- package/src/ast-analysis/visitors/dataflow-visitor.ts +0 -1
- package/src/cli/commands/batch.ts +4 -3
- package/src/cli/commands/branch-compare.ts +1 -1
- package/src/cli/commands/build.ts +1 -1
- package/src/cli/commands/info.ts +1 -2
- package/src/cli/commands/path.ts +7 -2
- package/src/cli/commands/plot.ts +2 -2
- package/src/cli/commands/watch.ts +1 -1
- package/src/cli/index.ts +2 -2
- package/src/cli/shared/open-graph.ts +2 -2
- package/src/cli/types.ts +1 -1
- package/src/cli.ts +2 -3
- package/src/db/connection.ts +97 -13
- package/src/db/index.ts +2 -0
- package/src/db/migrations.ts +7 -0
- package/src/domain/analysis/brief.ts +0 -1
- package/src/domain/analysis/context.ts +2 -6
- package/src/domain/analysis/dependencies.ts +165 -0
- package/src/domain/analysis/diff-impact.ts +354 -0
- package/src/domain/analysis/exports.ts +0 -2
- package/src/domain/analysis/fn-impact.ts +241 -0
- package/src/domain/analysis/impact.ts +8 -718
- package/src/domain/analysis/module-map.ts +1 -5
- package/src/domain/graph/builder/context.ts +2 -2
- package/src/domain/graph/builder/helpers.ts +14 -11
- package/src/domain/graph/builder/incremental.ts +33 -28
- package/src/domain/graph/builder/pipeline.ts +8 -0
- package/src/domain/graph/builder/stages/build-edges.ts +17 -4
- package/src/domain/graph/builder/stages/build-structure.ts +205 -76
- package/src/domain/graph/builder/stages/detect-changes.ts +11 -12
- package/src/domain/graph/builder/stages/finalize.ts +100 -81
- package/src/domain/graph/builder/stages/insert-nodes.ts +12 -8
- package/src/domain/graph/builder/stages/resolve-imports.ts +75 -10
- package/src/domain/graph/cycles.ts +2 -2
- package/src/domain/graph/resolve.ts +14 -8
- package/src/domain/graph/watcher.ts +2 -4
- package/src/domain/parser.ts +11 -13
- package/src/domain/queries.ts +2 -2
- package/src/domain/search/generator.ts +3 -4
- package/src/extractors/csharp.ts +2 -2
- package/src/extractors/go.ts +2 -2
- package/src/extractors/helpers.ts +6 -0
- package/src/extractors/javascript.ts +58 -61
- package/src/extractors/php.ts +2 -2
- package/src/extractors/python.ts +2 -2
- package/src/extractors/rust.ts +2 -2
- package/src/features/audit.ts +1 -2
- package/src/features/branch-compare.ts +3 -9
- package/src/features/cfg.ts +2 -4
- package/src/features/cochange.ts +4 -4
- package/src/features/communities.ts +4 -4
- package/src/features/complexity-query.ts +370 -0
- package/src/features/complexity.ts +6 -365
- package/src/features/dataflow.ts +48 -70
- package/src/features/export.ts +12 -16
- package/src/features/flow.ts +0 -1
- package/src/features/graph-enrichment.ts +1 -3
- package/src/features/manifesto.ts +8 -8
- package/src/features/snapshot.ts +1 -2
- package/src/features/structure-query.ts +387 -0
- package/src/features/structure.ts +231 -376
- package/src/features/triage.ts +2 -2
- package/src/graph/algorithms/leiden/adapter.ts +2 -9
- package/src/graph/classifiers/roles.ts +22 -13
- package/src/index.ts +1 -0
- package/src/infrastructure/config.ts +12 -13
- package/src/infrastructure/native.ts +7 -3
- package/src/infrastructure/registry.ts +1 -1
- package/src/infrastructure/update-check.ts +3 -3
- package/src/mcp/server.ts +2 -10
- package/src/mcp/tool-registry.ts +11 -4
- package/src/mcp/tools/audit.ts +1 -1
- package/src/mcp/tools/cfg.ts +1 -1
- package/src/mcp/tools/check.ts +2 -2
- package/src/mcp/tools/dataflow.ts +2 -2
- package/src/mcp/tools/export-graph.ts +1 -1
- package/src/mcp/tools/index.ts +0 -1
- package/src/mcp/tools/path.ts +10 -0
- 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/symbol-children.ts +1 -1
- package/src/mcp/tools/triage.ts +1 -1
- package/src/presentation/audit.ts +0 -1
- package/src/presentation/diff-impact-mermaid.ts +127 -0
- package/src/presentation/flow.ts +0 -2
- package/src/presentation/manifesto.ts +0 -1
- package/src/presentation/queries-cli/inspect.ts +0 -1
- package/src/presentation/queries-cli/path.ts +71 -1
- package/src/presentation/result-formatter.ts +0 -1
- package/src/presentation/sequence.ts +0 -1
- package/src/presentation/structure.ts +0 -12
- package/src/presentation/triage.ts +0 -1
- package/src/shared/constants.ts +33 -19
- package/src/shared/errors.ts +5 -0
- package/src/shared/version.ts +10 -0
- package/src/types.ts +4 -10
- package/src/vendor.d.ts +0 -39
|
@@ -1,721 +1,11 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import {
|
|
5
|
-
findDbPath,
|
|
6
|
-
findDistinctCallers,
|
|
7
|
-
findFileNodes,
|
|
8
|
-
findImplementors,
|
|
9
|
-
findImportDependents,
|
|
10
|
-
findNodeById,
|
|
11
|
-
openReadonlyOrFail,
|
|
12
|
-
} from '../../db/index.js';
|
|
13
|
-
import { cachedStmt } from '../../db/repository/cached-stmt.js';
|
|
14
|
-
import { evaluateBoundaries } from '../../features/boundaries.js';
|
|
15
|
-
import { coChangeForFiles } from '../../features/cochange.js';
|
|
16
|
-
import { ownersForFiles } from '../../features/owners.js';
|
|
17
|
-
import { loadConfig } from '../../infrastructure/config.js';
|
|
18
|
-
import { debug } from '../../infrastructure/logger.js';
|
|
19
|
-
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
20
|
-
import { normalizeSymbol } from '../../shared/normalize.js';
|
|
21
|
-
import { paginateResult } from '../../shared/paginate.js';
|
|
22
|
-
import type { BetterSqlite3Database, NodeRow, RelatedNodeRow, StmtCache } from '../../types.js';
|
|
23
|
-
import { findMatchingNodes } from './symbol-lookup.js';
|
|
24
|
-
|
|
25
|
-
const _defsStmtCache: StmtCache<NodeRow> = new WeakMap();
|
|
26
|
-
|
|
27
|
-
// --- Shared BFS: transitive callers ---
|
|
28
|
-
|
|
29
|
-
const INTERFACE_LIKE_KINDS = new Set(['interface', 'trait']);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Check whether the graph contains any 'implements' edges.
|
|
33
|
-
* Cached per db handle so the query runs at most once per connection.
|
|
34
|
-
*/
|
|
35
|
-
const _hasImplementsCache: WeakMap<BetterSqlite3Database, boolean> = new WeakMap();
|
|
36
|
-
function hasImplementsEdges(db: BetterSqlite3Database): boolean {
|
|
37
|
-
if (_hasImplementsCache.has(db)) return _hasImplementsCache.get(db)!;
|
|
38
|
-
const row = db.prepare("SELECT 1 FROM edges WHERE kind = 'implements' LIMIT 1").get();
|
|
39
|
-
const result = !!row;
|
|
40
|
-
_hasImplementsCache.set(db, result);
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* BFS traversal to find transitive callers of a node.
|
|
46
|
-
* When an interface/trait node is encountered (either as the start node or
|
|
47
|
-
* during traversal), its concrete implementors are also added to the frontier
|
|
48
|
-
* so that changes to an interface signature propagate to all implementors.
|
|
49
|
-
*/
|
|
50
|
-
export function bfsTransitiveCallers(
|
|
51
|
-
db: BetterSqlite3Database,
|
|
52
|
-
startId: number,
|
|
53
|
-
{
|
|
54
|
-
noTests = false,
|
|
55
|
-
maxDepth = 3,
|
|
56
|
-
includeImplementors = true,
|
|
57
|
-
onVisit,
|
|
58
|
-
}: {
|
|
59
|
-
noTests?: boolean;
|
|
60
|
-
maxDepth?: number;
|
|
61
|
-
includeImplementors?: boolean;
|
|
62
|
-
onVisit?: (
|
|
63
|
-
caller: RelatedNodeRow & { viaImplements?: boolean },
|
|
64
|
-
parentId: number,
|
|
65
|
-
depth: number,
|
|
66
|
-
) => void;
|
|
67
|
-
} = {},
|
|
68
|
-
) {
|
|
69
|
-
// Skip all implementor lookups when the graph has no implements edges
|
|
70
|
-
const resolveImplementors = includeImplementors && hasImplementsEdges(db);
|
|
71
|
-
|
|
72
|
-
const visited = new Set([startId]);
|
|
73
|
-
const levels: Record<
|
|
74
|
-
number,
|
|
75
|
-
Array<{ name: string; kind: string; file: string; line: number; viaImplements?: boolean }>
|
|
76
|
-
> = {};
|
|
77
|
-
let frontier = [startId];
|
|
78
|
-
|
|
79
|
-
// Seed: if start node is an interface/trait, include its implementors at depth 1.
|
|
80
|
-
// Implementors go into a separate list so their callers appear at depth 2, not depth 1.
|
|
81
|
-
const implNextFrontier: number[] = [];
|
|
82
|
-
if (resolveImplementors) {
|
|
83
|
-
const startNode = findNodeById(db, startId) as NodeRow | undefined;
|
|
84
|
-
if (startNode && INTERFACE_LIKE_KINDS.has(startNode.kind)) {
|
|
85
|
-
const impls = findImplementors(db, startId) as RelatedNodeRow[];
|
|
86
|
-
for (const impl of impls) {
|
|
87
|
-
if (!visited.has(impl.id) && (!noTests || !isTestFile(impl.file))) {
|
|
88
|
-
visited.add(impl.id);
|
|
89
|
-
implNextFrontier.push(impl.id);
|
|
90
|
-
if (!levels[1]) levels[1] = [];
|
|
91
|
-
levels[1].push({
|
|
92
|
-
name: impl.name,
|
|
93
|
-
kind: impl.kind,
|
|
94
|
-
file: impl.file,
|
|
95
|
-
line: impl.line,
|
|
96
|
-
viaImplements: true,
|
|
97
|
-
});
|
|
98
|
-
if (onVisit) onVisit({ ...impl, viaImplements: true }, startId, 1);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
for (let d = 1; d <= maxDepth; d++) {
|
|
105
|
-
// On the first wave, merge seeded implementors so their callers appear at d=2
|
|
106
|
-
if (d === 1 && implNextFrontier.length > 0) {
|
|
107
|
-
frontier = [...frontier, ...implNextFrontier];
|
|
108
|
-
}
|
|
109
|
-
const nextFrontier: number[] = [];
|
|
110
|
-
for (const fid of frontier) {
|
|
111
|
-
const callers = findDistinctCallers(db, fid) as RelatedNodeRow[];
|
|
112
|
-
for (const c of callers) {
|
|
113
|
-
if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
|
|
114
|
-
visited.add(c.id);
|
|
115
|
-
nextFrontier.push(c.id);
|
|
116
|
-
if (!levels[d]) levels[d] = [];
|
|
117
|
-
levels[d]!.push({ name: c.name, kind: c.kind, file: c.file, line: c.line });
|
|
118
|
-
if (onVisit) onVisit(c, fid, d);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// If a caller is an interface/trait, also pull in its implementors
|
|
122
|
-
// Implementors are one extra hop away, so record at d+1
|
|
123
|
-
if (resolveImplementors && INTERFACE_LIKE_KINDS.has(c.kind)) {
|
|
124
|
-
const impls = findImplementors(db, c.id) as RelatedNodeRow[];
|
|
125
|
-
for (const impl of impls) {
|
|
126
|
-
if (!visited.has(impl.id) && (!noTests || !isTestFile(impl.file))) {
|
|
127
|
-
visited.add(impl.id);
|
|
128
|
-
nextFrontier.push(impl.id);
|
|
129
|
-
const implDepth = d + 1;
|
|
130
|
-
if (!levels[implDepth]) levels[implDepth] = [];
|
|
131
|
-
levels[implDepth].push({
|
|
132
|
-
name: impl.name,
|
|
133
|
-
kind: impl.kind,
|
|
134
|
-
file: impl.file,
|
|
135
|
-
line: impl.line,
|
|
136
|
-
viaImplements: true,
|
|
137
|
-
});
|
|
138
|
-
if (onVisit) onVisit({ ...impl, viaImplements: true }, c.id, implDepth);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
frontier = nextFrontier;
|
|
145
|
-
if (frontier.length === 0) break;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return { totalDependents: visited.size - 1, levels };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function impactAnalysisData(
|
|
152
|
-
file: string,
|
|
153
|
-
customDbPath: string,
|
|
154
|
-
opts: { noTests?: boolean } = {},
|
|
155
|
-
) {
|
|
156
|
-
const db = openReadonlyOrFail(customDbPath);
|
|
157
|
-
try {
|
|
158
|
-
const noTests = opts.noTests || false;
|
|
159
|
-
const fileNodes = findFileNodes(db, `%${file}%`) as NodeRow[];
|
|
160
|
-
if (fileNodes.length === 0) {
|
|
161
|
-
return { file, sources: [], levels: {}, totalDependents: 0 };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const visited = new Set<number>();
|
|
165
|
-
const queue: number[] = [];
|
|
166
|
-
const levels = new Map<number, number>();
|
|
167
|
-
|
|
168
|
-
for (const fn of fileNodes) {
|
|
169
|
-
visited.add(fn.id);
|
|
170
|
-
queue.push(fn.id);
|
|
171
|
-
levels.set(fn.id, 0);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
while (queue.length > 0) {
|
|
175
|
-
const current = queue.shift()!;
|
|
176
|
-
const level = levels.get(current)!;
|
|
177
|
-
const dependents = findImportDependents(db, current) as RelatedNodeRow[];
|
|
178
|
-
for (const dep of dependents) {
|
|
179
|
-
if (!visited.has(dep.id) && (!noTests || !isTestFile(dep.file))) {
|
|
180
|
-
visited.add(dep.id);
|
|
181
|
-
queue.push(dep.id);
|
|
182
|
-
levels.set(dep.id, level + 1);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const byLevel: Record<number, Array<{ file: string }>> = {};
|
|
188
|
-
for (const [id, level] of levels) {
|
|
189
|
-
if (level === 0) continue;
|
|
190
|
-
if (!byLevel[level]) byLevel[level] = [];
|
|
191
|
-
const node = findNodeById(db, id) as NodeRow | undefined;
|
|
192
|
-
if (node) byLevel[level].push({ file: node.file });
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
file,
|
|
197
|
-
sources: fileNodes.map((f) => f.file),
|
|
198
|
-
levels: byLevel,
|
|
199
|
-
totalDependents: visited.size - fileNodes.length,
|
|
200
|
-
};
|
|
201
|
-
} finally {
|
|
202
|
-
db.close();
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
export function fnImpactData(
|
|
207
|
-
name: string,
|
|
208
|
-
customDbPath: string,
|
|
209
|
-
opts: {
|
|
210
|
-
depth?: number;
|
|
211
|
-
noTests?: boolean;
|
|
212
|
-
file?: string;
|
|
213
|
-
kind?: string;
|
|
214
|
-
includeImplementors?: boolean;
|
|
215
|
-
limit?: number;
|
|
216
|
-
offset?: number;
|
|
217
|
-
// biome-ignore lint/suspicious/noExplicitAny: config shape is dynamic
|
|
218
|
-
config?: any;
|
|
219
|
-
} = {},
|
|
220
|
-
) {
|
|
221
|
-
const db = openReadonlyOrFail(customDbPath);
|
|
222
|
-
try {
|
|
223
|
-
const config = opts.config || loadConfig();
|
|
224
|
-
const maxDepth = opts.depth || config.analysis?.fnImpactDepth || 5;
|
|
225
|
-
const noTests = opts.noTests || false;
|
|
226
|
-
const hc = new Map();
|
|
227
|
-
|
|
228
|
-
const nodes = findMatchingNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
|
|
229
|
-
if (nodes.length === 0) {
|
|
230
|
-
return { name, results: [] };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const includeImplementors = opts.includeImplementors !== false;
|
|
234
|
-
|
|
235
|
-
const results = nodes.map((node) => {
|
|
236
|
-
const { levels, totalDependents } = bfsTransitiveCallers(db, node.id, {
|
|
237
|
-
noTests,
|
|
238
|
-
maxDepth,
|
|
239
|
-
includeImplementors,
|
|
240
|
-
});
|
|
241
|
-
return {
|
|
242
|
-
...normalizeSymbol(node, db, hc),
|
|
243
|
-
levels,
|
|
244
|
-
totalDependents,
|
|
245
|
-
};
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
const base = { name, results };
|
|
249
|
-
return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
|
|
250
|
-
} finally {
|
|
251
|
-
db.close();
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// --- diffImpactData helpers ---
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* Walk up from repoRoot until a .git directory is found.
|
|
259
|
-
* Returns true if a git root exists, false otherwise.
|
|
260
|
-
*/
|
|
261
|
-
function findGitRoot(repoRoot: string): boolean {
|
|
262
|
-
let checkDir = repoRoot;
|
|
263
|
-
while (checkDir) {
|
|
264
|
-
if (fs.existsSync(path.join(checkDir, '.git'))) {
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
|
-
const parent = path.dirname(checkDir);
|
|
268
|
-
if (parent === checkDir) break;
|
|
269
|
-
checkDir = parent;
|
|
270
|
-
}
|
|
271
|
-
return false;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Execute git diff and return the raw output string.
|
|
276
|
-
* Returns `{ output: string }` on success or `{ error: string }` on failure.
|
|
277
|
-
*/
|
|
278
|
-
function runGitDiff(
|
|
279
|
-
repoRoot: string,
|
|
280
|
-
opts: { staged?: boolean; ref?: string },
|
|
281
|
-
): { output: string; error?: never } | { error: string; output?: never } {
|
|
282
|
-
try {
|
|
283
|
-
const args = opts.staged
|
|
284
|
-
? ['diff', '--cached', '--unified=0', '--no-color']
|
|
285
|
-
: ['diff', opts.ref || 'HEAD', '--unified=0', '--no-color'];
|
|
286
|
-
const output = execFileSync('git', args, {
|
|
287
|
-
cwd: repoRoot,
|
|
288
|
-
encoding: 'utf-8',
|
|
289
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
290
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
291
|
-
});
|
|
292
|
-
return { output };
|
|
293
|
-
} catch (e: unknown) {
|
|
294
|
-
return { error: `Failed to run git diff: ${(e as Error).message}` };
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Parse raw git diff output into a changedRanges map and newFiles set.
|
|
300
|
-
*/
|
|
301
|
-
function parseGitDiff(diffOutput: string) {
|
|
302
|
-
const changedRanges = new Map<string, Array<{ start: number; end: number }>>();
|
|
303
|
-
const newFiles = new Set<string>();
|
|
304
|
-
let currentFile: string | null = null;
|
|
305
|
-
let prevIsDevNull = false;
|
|
306
|
-
|
|
307
|
-
for (const line of diffOutput.split('\n')) {
|
|
308
|
-
if (line.startsWith('--- /dev/null')) {
|
|
309
|
-
prevIsDevNull = true;
|
|
310
|
-
continue;
|
|
311
|
-
}
|
|
312
|
-
if (line.startsWith('--- ')) {
|
|
313
|
-
prevIsDevNull = false;
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
const fileMatch = line.match(/^\+\+\+ b\/(.+)/);
|
|
317
|
-
if (fileMatch) {
|
|
318
|
-
currentFile = fileMatch[1]!;
|
|
319
|
-
if (!changedRanges.has(currentFile)) changedRanges.set(currentFile, []);
|
|
320
|
-
if (prevIsDevNull) newFiles.add(currentFile!);
|
|
321
|
-
prevIsDevNull = false;
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
const hunkMatch = line.match(/^@@ .+ \+(\d+)(?:,(\d+))? @@/);
|
|
325
|
-
if (hunkMatch && currentFile) {
|
|
326
|
-
const start = parseInt(hunkMatch[1]!, 10);
|
|
327
|
-
const count = parseInt(hunkMatch[2] || '1', 10);
|
|
328
|
-
changedRanges.get(currentFile)!.push({ start, end: start + count - 1 });
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
return { changedRanges, newFiles };
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* Find all function/method/class nodes whose line ranges overlap any changed range.
|
|
337
|
-
*/
|
|
338
|
-
function findAffectedFunctions(
|
|
339
|
-
db: BetterSqlite3Database,
|
|
340
|
-
changedRanges: Map<string, Array<{ start: number; end: number }>>,
|
|
341
|
-
noTests: boolean,
|
|
342
|
-
): NodeRow[] {
|
|
343
|
-
const affectedFunctions: NodeRow[] = [];
|
|
344
|
-
const defsStmt = cachedStmt(
|
|
345
|
-
_defsStmtCache,
|
|
346
|
-
db,
|
|
347
|
-
`SELECT * FROM nodes WHERE file = ? AND kind IN ('function', 'method', 'class') ORDER BY line`,
|
|
348
|
-
);
|
|
349
|
-
for (const [file, ranges] of changedRanges) {
|
|
350
|
-
if (noTests && isTestFile(file)) continue;
|
|
351
|
-
const defs = defsStmt.all(file) as NodeRow[];
|
|
352
|
-
for (let i = 0; i < defs.length; i++) {
|
|
353
|
-
const def = defs[i]!;
|
|
354
|
-
const endLine = def.end_line || (defs[i + 1] ? defs[i + 1]!.line - 1 : 999999);
|
|
355
|
-
for (const range of ranges) {
|
|
356
|
-
if (range.start <= endLine && range.end >= def.line) {
|
|
357
|
-
affectedFunctions.push(def);
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
return affectedFunctions;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Run BFS per affected function, collecting per-function results and the full affected set.
|
|
368
|
-
*/
|
|
369
|
-
function buildFunctionImpactResults(
|
|
370
|
-
db: BetterSqlite3Database,
|
|
371
|
-
affectedFunctions: NodeRow[],
|
|
372
|
-
noTests: boolean,
|
|
373
|
-
maxDepth: number,
|
|
374
|
-
includeImplementors = true,
|
|
375
|
-
) {
|
|
376
|
-
const allAffected = new Set<string>();
|
|
377
|
-
const functionResults = affectedFunctions.map((fn) => {
|
|
378
|
-
const edges: Array<{ from: string; to: string }> = [];
|
|
379
|
-
const idToKey = new Map<number, string>();
|
|
380
|
-
idToKey.set(fn.id, `${fn.file}::${fn.name}:${fn.line}`);
|
|
381
|
-
|
|
382
|
-
const { levels, totalDependents } = bfsTransitiveCallers(db, fn.id, {
|
|
383
|
-
noTests,
|
|
384
|
-
maxDepth,
|
|
385
|
-
includeImplementors,
|
|
386
|
-
onVisit(c, parentId) {
|
|
387
|
-
allAffected.add(`${c.file}:${c.name}`);
|
|
388
|
-
const callerKey = `${c.file}::${c.name}:${c.line}`;
|
|
389
|
-
idToKey.set(c.id, callerKey);
|
|
390
|
-
edges.push({ from: idToKey.get(parentId)!, to: callerKey });
|
|
391
|
-
},
|
|
392
|
-
});
|
|
393
|
-
|
|
394
|
-
return {
|
|
395
|
-
name: fn.name,
|
|
396
|
-
kind: fn.kind,
|
|
397
|
-
file: fn.file,
|
|
398
|
-
line: fn.line,
|
|
399
|
-
transitiveCallers: totalDependents,
|
|
400
|
-
levels,
|
|
401
|
-
edges,
|
|
402
|
-
};
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
return { functionResults, allAffected };
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Look up historically co-changed files for the set of changed files.
|
|
410
|
-
* Returns an empty array if the co_changes table is unavailable.
|
|
411
|
-
*/
|
|
412
|
-
function lookupCoChanges(
|
|
413
|
-
db: BetterSqlite3Database,
|
|
414
|
-
changedRanges: Map<string, unknown>,
|
|
415
|
-
affectedFiles: Set<string>,
|
|
416
|
-
noTests: boolean,
|
|
417
|
-
) {
|
|
418
|
-
try {
|
|
419
|
-
db.prepare('SELECT 1 FROM co_changes LIMIT 1').get();
|
|
420
|
-
const changedFilesList = [...changedRanges.keys()];
|
|
421
|
-
const coResults = coChangeForFiles(changedFilesList, db, {
|
|
422
|
-
minJaccard: 0.3,
|
|
423
|
-
limit: 20,
|
|
424
|
-
noTests,
|
|
425
|
-
});
|
|
426
|
-
return coResults.filter((r: { file: string }) => !affectedFiles.has(r.file));
|
|
427
|
-
} catch (e: unknown) {
|
|
428
|
-
debug(`co_changes lookup skipped: ${(e as Error).message}`);
|
|
429
|
-
return [];
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
1
|
/**
|
|
434
|
-
*
|
|
435
|
-
*
|
|
2
|
+
* impact.ts — Re-export barrel for backward compatibility.
|
|
3
|
+
*
|
|
4
|
+
* The implementation has been split into focused modules:
|
|
5
|
+
* - fn-impact.ts: bfsTransitiveCallers, impactAnalysisData, fnImpactData
|
|
6
|
+
* - diff-impact.ts: diffImpactData and git diff analysis helpers
|
|
7
|
+
* - presentation/diff-impact-mermaid.ts: diffImpactMermaid (Mermaid diagram generation)
|
|
436
8
|
*/
|
|
437
|
-
function lookupOwnership(
|
|
438
|
-
changedRanges: Map<string, unknown>,
|
|
439
|
-
affectedFiles: Set<string>,
|
|
440
|
-
repoRoot: string,
|
|
441
|
-
) {
|
|
442
|
-
try {
|
|
443
|
-
const allFilePaths = [...new Set([...changedRanges.keys(), ...affectedFiles])];
|
|
444
|
-
const ownerResult = ownersForFiles(allFilePaths, repoRoot);
|
|
445
|
-
if (ownerResult.affectedOwners.length > 0) {
|
|
446
|
-
return {
|
|
447
|
-
owners: Object.fromEntries(ownerResult.owners),
|
|
448
|
-
affectedOwners: ownerResult.affectedOwners,
|
|
449
|
-
suggestedReviewers: ownerResult.suggestedReviewers,
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
return null;
|
|
453
|
-
} catch (e: unknown) {
|
|
454
|
-
debug(`CODEOWNERS lookup skipped: ${(e as Error).message}`);
|
|
455
|
-
return null;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Check manifesto boundary violations scoped to the changed files.
|
|
461
|
-
* Returns `{ boundaryViolations, boundaryViolationCount }`.
|
|
462
|
-
*/
|
|
463
|
-
function checkBoundaryViolations(
|
|
464
|
-
db: BetterSqlite3Database,
|
|
465
|
-
changedRanges: Map<string, unknown>,
|
|
466
|
-
noTests: boolean,
|
|
467
|
-
// biome-ignore lint/suspicious/noExplicitAny: opts shape varies by caller
|
|
468
|
-
opts: any,
|
|
469
|
-
repoRoot: string,
|
|
470
|
-
) {
|
|
471
|
-
try {
|
|
472
|
-
const cfg = opts.config || loadConfig(repoRoot);
|
|
473
|
-
const boundaryConfig = cfg.manifesto?.boundaries;
|
|
474
|
-
if (boundaryConfig) {
|
|
475
|
-
const result = evaluateBoundaries(db, boundaryConfig, {
|
|
476
|
-
scopeFiles: [...changedRanges.keys()],
|
|
477
|
-
noTests,
|
|
478
|
-
});
|
|
479
|
-
return {
|
|
480
|
-
boundaryViolations: result.violations,
|
|
481
|
-
boundaryViolationCount: result.violationCount,
|
|
482
|
-
};
|
|
483
|
-
}
|
|
484
|
-
} catch (e: unknown) {
|
|
485
|
-
debug(`boundary check skipped: ${(e as Error).message}`);
|
|
486
|
-
}
|
|
487
|
-
return { boundaryViolations: [], boundaryViolationCount: 0 };
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// --- diffImpactData ---
|
|
491
|
-
|
|
492
|
-
/**
|
|
493
|
-
* Fix #2: Shell injection vulnerability.
|
|
494
|
-
* Uses execFileSync instead of execSync to prevent shell interpretation of user input.
|
|
495
|
-
*/
|
|
496
|
-
export function diffImpactData(
|
|
497
|
-
customDbPath: string,
|
|
498
|
-
opts: {
|
|
499
|
-
noTests?: boolean;
|
|
500
|
-
depth?: number;
|
|
501
|
-
staged?: boolean;
|
|
502
|
-
ref?: string;
|
|
503
|
-
includeImplementors?: boolean;
|
|
504
|
-
limit?: number;
|
|
505
|
-
offset?: number;
|
|
506
|
-
// biome-ignore lint/suspicious/noExplicitAny: config shape is dynamic
|
|
507
|
-
config?: any;
|
|
508
|
-
} = {},
|
|
509
|
-
) {
|
|
510
|
-
const db = openReadonlyOrFail(customDbPath);
|
|
511
|
-
try {
|
|
512
|
-
const noTests = opts.noTests || false;
|
|
513
|
-
const config = opts.config || loadConfig();
|
|
514
|
-
const maxDepth = opts.depth || config.analysis?.impactDepth || 3;
|
|
515
|
-
|
|
516
|
-
const dbPath = findDbPath(customDbPath);
|
|
517
|
-
const repoRoot = path.resolve(path.dirname(dbPath), '..');
|
|
518
|
-
|
|
519
|
-
if (!findGitRoot(repoRoot)) {
|
|
520
|
-
return { error: `Not a git repository: ${repoRoot}` };
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const gitResult = runGitDiff(repoRoot, opts);
|
|
524
|
-
if ('error' in gitResult) return { error: gitResult.error };
|
|
525
|
-
|
|
526
|
-
if (!gitResult.output.trim()) {
|
|
527
|
-
return {
|
|
528
|
-
changedFiles: 0,
|
|
529
|
-
newFiles: [],
|
|
530
|
-
affectedFunctions: [],
|
|
531
|
-
affectedFiles: [],
|
|
532
|
-
summary: null,
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
const { changedRanges, newFiles } = parseGitDiff(gitResult.output);
|
|
537
|
-
|
|
538
|
-
if (changedRanges.size === 0) {
|
|
539
|
-
return {
|
|
540
|
-
changedFiles: 0,
|
|
541
|
-
newFiles: [],
|
|
542
|
-
affectedFunctions: [],
|
|
543
|
-
affectedFiles: [],
|
|
544
|
-
summary: null,
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
const affectedFunctions = findAffectedFunctions(db, changedRanges, noTests);
|
|
549
|
-
const includeImplementors = opts.includeImplementors !== false;
|
|
550
|
-
const { functionResults, allAffected } = buildFunctionImpactResults(
|
|
551
|
-
db,
|
|
552
|
-
affectedFunctions,
|
|
553
|
-
noTests,
|
|
554
|
-
maxDepth,
|
|
555
|
-
includeImplementors,
|
|
556
|
-
);
|
|
557
|
-
|
|
558
|
-
const affectedFiles = new Set<string>();
|
|
559
|
-
for (const key of allAffected) affectedFiles.add(key.split(':')[0]!);
|
|
560
|
-
|
|
561
|
-
const historicallyCoupled = lookupCoChanges(db, changedRanges, affectedFiles, noTests);
|
|
562
|
-
const ownership = lookupOwnership(changedRanges, affectedFiles, repoRoot);
|
|
563
|
-
const { boundaryViolations, boundaryViolationCount } = checkBoundaryViolations(
|
|
564
|
-
db,
|
|
565
|
-
changedRanges,
|
|
566
|
-
noTests,
|
|
567
|
-
opts,
|
|
568
|
-
repoRoot,
|
|
569
|
-
);
|
|
570
|
-
|
|
571
|
-
const base = {
|
|
572
|
-
changedFiles: changedRanges.size,
|
|
573
|
-
newFiles: [...newFiles],
|
|
574
|
-
affectedFunctions: functionResults,
|
|
575
|
-
affectedFiles: [...affectedFiles],
|
|
576
|
-
historicallyCoupled,
|
|
577
|
-
ownership,
|
|
578
|
-
boundaryViolations,
|
|
579
|
-
boundaryViolationCount,
|
|
580
|
-
summary: {
|
|
581
|
-
functionsChanged: affectedFunctions.length,
|
|
582
|
-
callersAffected: allAffected.size,
|
|
583
|
-
filesAffected: affectedFiles.size,
|
|
584
|
-
historicallyCoupledCount: historicallyCoupled.length,
|
|
585
|
-
ownersAffected: ownership ? ownership.affectedOwners.length : 0,
|
|
586
|
-
boundaryViolationCount,
|
|
587
|
-
},
|
|
588
|
-
};
|
|
589
|
-
return paginateResult(base, 'affectedFunctions', { limit: opts.limit, offset: opts.offset });
|
|
590
|
-
} finally {
|
|
591
|
-
db.close();
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
export function diffImpactMermaid(
|
|
596
|
-
customDbPath: string,
|
|
597
|
-
opts: {
|
|
598
|
-
noTests?: boolean;
|
|
599
|
-
depth?: number;
|
|
600
|
-
staged?: boolean;
|
|
601
|
-
ref?: string;
|
|
602
|
-
includeImplementors?: boolean;
|
|
603
|
-
limit?: number;
|
|
604
|
-
offset?: number;
|
|
605
|
-
// biome-ignore lint/suspicious/noExplicitAny: config shape is dynamic
|
|
606
|
-
config?: any;
|
|
607
|
-
} = {},
|
|
608
|
-
): string {
|
|
609
|
-
// biome-ignore lint/suspicious/noExplicitAny: paginateResult returns dynamic shape
|
|
610
|
-
const data: any = diffImpactData(customDbPath, opts);
|
|
611
|
-
if ('error' in data) return data.error as string;
|
|
612
|
-
if (data.changedFiles === 0 || data.affectedFunctions.length === 0) {
|
|
613
|
-
return 'flowchart TB\n none["No impacted functions detected"]';
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
const newFileSet = new Set(data.newFiles || []);
|
|
617
|
-
const lines = ['flowchart TB'];
|
|
618
|
-
|
|
619
|
-
// Assign stable Mermaid node IDs
|
|
620
|
-
let nodeCounter = 0;
|
|
621
|
-
const nodeIdMap = new Map<string, string>();
|
|
622
|
-
const nodeLabels = new Map<string, string>();
|
|
623
|
-
function nodeId(key: string, label?: string): string {
|
|
624
|
-
if (!nodeIdMap.has(key)) {
|
|
625
|
-
nodeIdMap.set(key, `n${nodeCounter++}`);
|
|
626
|
-
if (label) nodeLabels.set(key, label);
|
|
627
|
-
}
|
|
628
|
-
return nodeIdMap.get(key)!;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Register all nodes (changed functions + their callers)
|
|
632
|
-
for (const fn of data.affectedFunctions) {
|
|
633
|
-
nodeId(`${fn.file}::${fn.name}:${fn.line}`, fn.name);
|
|
634
|
-
for (const callers of Object.values(fn.levels || {})) {
|
|
635
|
-
for (const c of callers as Array<{ name: string; file: string; line: number }>) {
|
|
636
|
-
nodeId(`${c.file}::${c.name}:${c.line}`, c.name);
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// Collect all edges and determine blast radius
|
|
642
|
-
const allEdges = new Set<string>();
|
|
643
|
-
const edgeFromNodes = new Set<string>();
|
|
644
|
-
const edgeToNodes = new Set<string>();
|
|
645
|
-
const changedKeys = new Set<string>();
|
|
646
|
-
|
|
647
|
-
for (const fn of data.affectedFunctions) {
|
|
648
|
-
changedKeys.add(`${fn.file}::${fn.name}:${fn.line}`);
|
|
649
|
-
for (const edge of fn.edges || []) {
|
|
650
|
-
const edgeKey = `${edge.from}|${edge.to}`;
|
|
651
|
-
if (!allEdges.has(edgeKey)) {
|
|
652
|
-
allEdges.add(edgeKey);
|
|
653
|
-
edgeFromNodes.add(edge.from);
|
|
654
|
-
edgeToNodes.add(edge.to);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
// Blast radius: caller nodes that are never a source (leaf nodes of the impact tree)
|
|
660
|
-
const blastRadiusKeys = new Set<string>();
|
|
661
|
-
for (const key of edgeToNodes) {
|
|
662
|
-
if (!edgeFromNodes.has(key) && !changedKeys.has(key)) {
|
|
663
|
-
blastRadiusKeys.add(key);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// Intermediate callers: not changed, not blast radius
|
|
668
|
-
const intermediateKeys = new Set<string>();
|
|
669
|
-
for (const key of edgeToNodes) {
|
|
670
|
-
if (!changedKeys.has(key) && !blastRadiusKeys.has(key)) {
|
|
671
|
-
intermediateKeys.add(key);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// Group changed functions by file
|
|
676
|
-
const fileGroups = new Map<string, typeof data.affectedFunctions>();
|
|
677
|
-
for (const fn of data.affectedFunctions) {
|
|
678
|
-
if (!fileGroups.has(fn.file)) fileGroups.set(fn.file, []);
|
|
679
|
-
fileGroups.get(fn.file)!.push(fn);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Emit changed-file subgraphs
|
|
683
|
-
let sgCounter = 0;
|
|
684
|
-
for (const [file, fns] of fileGroups) {
|
|
685
|
-
const isNew = newFileSet.has(file);
|
|
686
|
-
const tag = isNew ? 'new' : 'modified';
|
|
687
|
-
const sgId = `sg${sgCounter++}`;
|
|
688
|
-
lines.push(` subgraph ${sgId}["${file} **(${tag})**"]`);
|
|
689
|
-
for (const fn of fns) {
|
|
690
|
-
const key = `${fn.file}::${fn.name}:${fn.line}`;
|
|
691
|
-
lines.push(` ${nodeIdMap.get(key)}["${fn.name}"]`);
|
|
692
|
-
}
|
|
693
|
-
lines.push(' end');
|
|
694
|
-
const style = isNew ? 'fill:#e8f5e9,stroke:#4caf50' : 'fill:#fff3e0,stroke:#ff9800';
|
|
695
|
-
lines.push(` style ${sgId} ${style}`);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
// Emit intermediate caller nodes (outside subgraphs)
|
|
699
|
-
for (const key of intermediateKeys) {
|
|
700
|
-
lines.push(` ${nodeIdMap.get(key)}["${nodeLabels.get(key)}"]`);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// Emit blast radius subgraph
|
|
704
|
-
if (blastRadiusKeys.size > 0) {
|
|
705
|
-
const sgId = `sg${sgCounter++}`;
|
|
706
|
-
lines.push(` subgraph ${sgId}["Callers **(blast radius)**"]`);
|
|
707
|
-
for (const key of blastRadiusKeys) {
|
|
708
|
-
lines.push(` ${nodeIdMap.get(key)}["${nodeLabels.get(key)}"]`);
|
|
709
|
-
}
|
|
710
|
-
lines.push(' end');
|
|
711
|
-
lines.push(` style ${sgId} fill:#f3e5f5,stroke:#9c27b0`);
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// Emit edges (impact flows from changed fn toward callers)
|
|
715
|
-
for (const edgeKey of allEdges) {
|
|
716
|
-
const [from, to] = edgeKey.split('|') as [string, string];
|
|
717
|
-
lines.push(` ${nodeIdMap.get(from)} --> ${nodeIdMap.get(to)}`);
|
|
718
|
-
}
|
|
719
9
|
|
|
720
|
-
|
|
721
|
-
}
|
|
10
|
+
export { diffImpactData } from './diff-impact.js';
|
|
11
|
+
export { bfsTransitiveCallers, fnImpactData, impactAnalysisData } from './fn-impact.js';
|