@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,11 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { getNodeId,
|
|
3
|
-
import { loadConfig } from '../infrastructure/config.js';
|
|
2
|
+
import { getNodeId, testFilterSQL } from '../db/index.js';
|
|
4
3
|
import { debug } from '../infrastructure/logger.js';
|
|
5
|
-
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
6
4
|
import { normalizePath } from '../shared/constants.js';
|
|
7
|
-
import {
|
|
8
|
-
import type { BetterSqlite3Database, CodegraphConfig } from '../types.js';
|
|
5
|
+
import type { BetterSqlite3Database } from '../types.js';
|
|
9
6
|
|
|
10
7
|
// ─── Build-time helpers ───────────────────────────────────────────────
|
|
11
8
|
|
|
@@ -367,7 +364,7 @@ export function buildStructure(
|
|
|
367
364
|
// Re-export from classifier for backward compatibility
|
|
368
365
|
export { FRAMEWORK_ENTRY_PREFIXES } from '../graph/classifiers/roles.js';
|
|
369
366
|
|
|
370
|
-
import { classifyRoles } from '../graph/classifiers/roles.js';
|
|
367
|
+
import { classifyRoles, median } from '../graph/classifiers/roles.js';
|
|
371
368
|
|
|
372
369
|
interface RoleSummary {
|
|
373
370
|
entry: number;
|
|
@@ -384,7 +381,53 @@ interface RoleSummary {
|
|
|
384
381
|
[key: string]: number;
|
|
385
382
|
}
|
|
386
383
|
|
|
387
|
-
|
|
384
|
+
/**
|
|
385
|
+
* Classify every node in the graph into a role (core, entry, utility, etc.).
|
|
386
|
+
*
|
|
387
|
+
* When `changedFiles` is provided, only nodes from those files (and their
|
|
388
|
+
* edge neighbours) are reclassified. The returned `RoleSummary` in that case
|
|
389
|
+
* reflects **only the affected subset**, not the entire graph. Callers that
|
|
390
|
+
* need graph-wide totals should perform a full classification (omit
|
|
391
|
+
* `changedFiles`) or query the DB directly.
|
|
392
|
+
*/
|
|
393
|
+
export function classifyNodeRoles(
|
|
394
|
+
db: BetterSqlite3Database,
|
|
395
|
+
changedFiles?: string[] | null,
|
|
396
|
+
): RoleSummary {
|
|
397
|
+
const emptySummary: RoleSummary = {
|
|
398
|
+
entry: 0,
|
|
399
|
+
core: 0,
|
|
400
|
+
utility: 0,
|
|
401
|
+
adapter: 0,
|
|
402
|
+
dead: 0,
|
|
403
|
+
'dead-leaf': 0,
|
|
404
|
+
'dead-entry': 0,
|
|
405
|
+
'dead-ffi': 0,
|
|
406
|
+
'dead-unresolved': 0,
|
|
407
|
+
'test-only': 0,
|
|
408
|
+
leaf: 0,
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// Incremental path: only reclassify nodes from affected files
|
|
412
|
+
if (changedFiles && changedFiles.length > 0) {
|
|
413
|
+
return classifyNodeRolesIncremental(db, changedFiles, emptySummary);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
return classifyNodeRolesFull(db, emptySummary);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function classifyNodeRolesFull(db: BetterSqlite3Database, emptySummary: RoleSummary): RoleSummary {
|
|
420
|
+
// Leaf kinds (parameter, property) can never have callers/callees.
|
|
421
|
+
// Classify them directly as dead-leaf without the expensive fan-in/fan-out JOINs.
|
|
422
|
+
const leafRows = db
|
|
423
|
+
.prepare(
|
|
424
|
+
`SELECT n.id
|
|
425
|
+
FROM nodes n
|
|
426
|
+
WHERE n.kind IN ('parameter', 'property')`,
|
|
427
|
+
)
|
|
428
|
+
.all() as { id: number }[];
|
|
429
|
+
|
|
430
|
+
// Only compute fan-in/fan-out for callable/classifiable nodes
|
|
388
431
|
const rows = db
|
|
389
432
|
.prepare(
|
|
390
433
|
`SELECT n.id, n.name, n.kind, n.file,
|
|
@@ -397,7 +440,7 @@ export function classifyNodeRoles(db: BetterSqlite3Database): RoleSummary {
|
|
|
397
440
|
LEFT JOIN (
|
|
398
441
|
SELECT source_id, COUNT(*) AS cnt FROM edges WHERE kind = 'calls' GROUP BY source_id
|
|
399
442
|
) fo ON n.id = fo.source_id
|
|
400
|
-
WHERE n.kind NOT IN ('file', 'directory')`,
|
|
443
|
+
WHERE n.kind NOT IN ('file', 'directory', 'parameter', 'property')`,
|
|
401
444
|
)
|
|
402
445
|
.all() as {
|
|
403
446
|
id: number;
|
|
@@ -408,21 +451,7 @@ export function classifyNodeRoles(db: BetterSqlite3Database): RoleSummary {
|
|
|
408
451
|
fan_out: number;
|
|
409
452
|
}[];
|
|
410
453
|
|
|
411
|
-
|
|
412
|
-
entry: 0,
|
|
413
|
-
core: 0,
|
|
414
|
-
utility: 0,
|
|
415
|
-
adapter: 0,
|
|
416
|
-
dead: 0,
|
|
417
|
-
'dead-leaf': 0,
|
|
418
|
-
'dead-entry': 0,
|
|
419
|
-
'dead-ffi': 0,
|
|
420
|
-
'dead-unresolved': 0,
|
|
421
|
-
'test-only': 0,
|
|
422
|
-
leaf: 0,
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
if (rows.length === 0) return emptySummary;
|
|
454
|
+
if (rows.length === 0 && leafRows.length === 0) return emptySummary;
|
|
426
455
|
|
|
427
456
|
const exportedIds = new Set(
|
|
428
457
|
(
|
|
@@ -471,6 +500,16 @@ export function classifyNodeRoles(db: BetterSqlite3Database): RoleSummary {
|
|
|
471
500
|
// Build summary and group updates by role for batch UPDATE
|
|
472
501
|
const summary: RoleSummary = { ...emptySummary };
|
|
473
502
|
const idsByRole = new Map<string, number[]>();
|
|
503
|
+
|
|
504
|
+
// Leaf kinds are always dead-leaf -- skip classifier
|
|
505
|
+
if (leafRows.length > 0) {
|
|
506
|
+
const leafIds: number[] = [];
|
|
507
|
+
for (const row of leafRows) leafIds.push(row.id);
|
|
508
|
+
idsByRole.set('dead-leaf', leafIds);
|
|
509
|
+
summary.dead += leafRows.length;
|
|
510
|
+
summary['dead-leaf'] += leafRows.length;
|
|
511
|
+
}
|
|
512
|
+
|
|
474
513
|
for (const row of rows) {
|
|
475
514
|
const role = roleMap.get(String(row.id)) || 'leaf';
|
|
476
515
|
if (role.startsWith('dead')) summary.dead++;
|
|
@@ -508,372 +547,188 @@ export function classifyNodeRoles(db: BetterSqlite3Database): RoleSummary {
|
|
|
508
547
|
return summary;
|
|
509
548
|
}
|
|
510
549
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
550
|
+
/**
|
|
551
|
+
* Incremental role classification: only reclassify nodes from changed files
|
|
552
|
+
* plus their immediate edge neighbours (callers and callees in other files).
|
|
553
|
+
*
|
|
554
|
+
* Uses indexed point lookups for fan-in/fan-out instead of full table scans.
|
|
555
|
+
* Global medians are computed from edge distribution (fast GROUP BY on index).
|
|
556
|
+
* Unchanged files not connected to changed files keep their roles from the
|
|
557
|
+
* previous build.
|
|
558
|
+
*/
|
|
559
|
+
function classifyNodeRolesIncremental(
|
|
560
|
+
db: BetterSqlite3Database,
|
|
561
|
+
changedFiles: string[],
|
|
562
|
+
emptySummary: RoleSummary,
|
|
563
|
+
): RoleSummary {
|
|
564
|
+
// Expand affected set: include files containing nodes that are edge neighbours
|
|
565
|
+
// of changed-file nodes. This ensures that removing a call from file A to a
|
|
566
|
+
// node in file B causes B's roles to be recalculated (fan_in changed).
|
|
567
|
+
const seedPlaceholders = changedFiles.map(() => '?').join(',');
|
|
568
|
+
const neighbourFiles = db
|
|
569
|
+
.prepare(
|
|
570
|
+
`SELECT DISTINCT n2.file FROM edges e
|
|
571
|
+
JOIN nodes n1 ON (e.source_id = n1.id OR e.target_id = n1.id)
|
|
572
|
+
JOIN nodes n2 ON (e.source_id = n2.id OR e.target_id = n2.id)
|
|
573
|
+
WHERE e.kind = 'calls'
|
|
574
|
+
AND n1.file IN (${seedPlaceholders})
|
|
575
|
+
AND n2.file NOT IN (${seedPlaceholders})
|
|
576
|
+
AND n2.kind NOT IN ('file', 'directory')`,
|
|
577
|
+
)
|
|
578
|
+
.all(...changedFiles, ...changedFiles) as { file: string }[];
|
|
579
|
+
const allAffectedFiles = [...changedFiles, ...neighbourFiles.map((r) => r.file)];
|
|
580
|
+
const placeholders = allAffectedFiles.map(() => '?').join(',');
|
|
581
|
+
|
|
582
|
+
// 1. Compute global medians from edge distribution (fast: scans edge index, no node join)
|
|
583
|
+
const fanInDist = (
|
|
584
|
+
db
|
|
585
|
+
.prepare(`SELECT COUNT(*) AS cnt FROM edges WHERE kind = 'calls' GROUP BY target_id`)
|
|
586
|
+
.all() as { cnt: number }[]
|
|
587
|
+
)
|
|
588
|
+
.map((r) => r.cnt)
|
|
589
|
+
.sort((a, b) => a - b);
|
|
590
|
+
const fanOutDist = (
|
|
591
|
+
db
|
|
592
|
+
.prepare(`SELECT COUNT(*) AS cnt FROM edges WHERE kind = 'calls' GROUP BY source_id`)
|
|
593
|
+
.all() as { cnt: number }[]
|
|
594
|
+
)
|
|
595
|
+
.map((r) => r.cnt)
|
|
596
|
+
.sort((a, b) => a - b);
|
|
597
|
+
|
|
598
|
+
const globalMedians = { fanIn: median(fanInDist), fanOut: median(fanOutDist) };
|
|
599
|
+
|
|
600
|
+
// 2a. Leaf kinds (parameter, property) in affected files — always dead-leaf
|
|
601
|
+
const leafRows = db
|
|
602
|
+
.prepare(
|
|
603
|
+
`SELECT n.id FROM nodes n
|
|
604
|
+
WHERE n.kind IN ('parameter', 'property')
|
|
605
|
+
AND n.file IN (${placeholders})`,
|
|
606
|
+
)
|
|
607
|
+
.all(...allAffectedFiles) as { id: number }[];
|
|
544
608
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
609
|
+
// 2b. Get callable nodes using indexed correlated subqueries (fast point lookups)
|
|
610
|
+
const rows = db
|
|
611
|
+
.prepare(
|
|
612
|
+
`SELECT n.id, n.name, n.kind, n.file,
|
|
613
|
+
(SELECT COUNT(*) FROM edges WHERE kind = 'calls' AND target_id = n.id) AS fan_in,
|
|
614
|
+
(SELECT COUNT(*) FROM edges WHERE kind = 'calls' AND source_id = n.id) AS fan_out
|
|
615
|
+
FROM nodes n
|
|
616
|
+
WHERE n.kind NOT IN ('file', 'directory', 'parameter', 'property')
|
|
617
|
+
AND n.file IN (${placeholders})`,
|
|
618
|
+
)
|
|
619
|
+
.all(...allAffectedFiles) as {
|
|
620
|
+
id: number;
|
|
621
|
+
name: string;
|
|
622
|
+
kind: string;
|
|
554
623
|
file: string;
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
importCount: number;
|
|
558
|
-
exportCount: number;
|
|
559
|
-
fanIn: number;
|
|
560
|
-
fanOut: number;
|
|
624
|
+
fan_in: number;
|
|
625
|
+
fan_out: number;
|
|
561
626
|
}[];
|
|
562
|
-
subdirectories: string[];
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
export function structureData(
|
|
566
|
-
customDbPath?: string,
|
|
567
|
-
opts: StructureDataOpts = {},
|
|
568
|
-
): {
|
|
569
|
-
directories: DirectoryEntry[];
|
|
570
|
-
count: number;
|
|
571
|
-
suppressed?: number;
|
|
572
|
-
warning?: string;
|
|
573
|
-
} {
|
|
574
|
-
const db = openReadonlyOrFail(customDbPath);
|
|
575
|
-
try {
|
|
576
|
-
const rawDir = opts.directory || null;
|
|
577
|
-
const filterDir = rawDir && normalizePath(rawDir) !== '.' ? rawDir : null;
|
|
578
|
-
const maxDepth = opts.depth || null;
|
|
579
|
-
const sortBy = opts.sort || 'files';
|
|
580
|
-
const noTests = opts.noTests || false;
|
|
581
|
-
const full = opts.full || false;
|
|
582
|
-
const fileLimit = opts.fileLimit || 25;
|
|
583
|
-
|
|
584
|
-
// Get all directory nodes with their metrics
|
|
585
|
-
let dirs = db
|
|
586
|
-
.prepare(`
|
|
587
|
-
SELECT n.id, n.name, n.file, nm.symbol_count, nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
588
|
-
FROM nodes n
|
|
589
|
-
LEFT JOIN node_metrics nm ON n.id = nm.node_id
|
|
590
|
-
WHERE n.kind = 'directory'
|
|
591
|
-
`)
|
|
592
|
-
.all() as DirRow[];
|
|
593
|
-
|
|
594
|
-
if (filterDir) {
|
|
595
|
-
const norm = normalizePath(filterDir);
|
|
596
|
-
dirs = dirs.filter((d) => d.name === norm || d.name.startsWith(`${norm}/`));
|
|
597
|
-
}
|
|
598
627
|
|
|
599
|
-
|
|
600
|
-
const baseDepth = filterDir ? normalizePath(filterDir).split('/').length : 0;
|
|
601
|
-
dirs = dirs.filter((d) => {
|
|
602
|
-
const depth = d.name.split('/').length - baseDepth;
|
|
603
|
-
return depth <= maxDepth;
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// Sort
|
|
608
|
-
const sortFn = getSortFn(sortBy);
|
|
609
|
-
dirs.sort(sortFn);
|
|
628
|
+
if (rows.length === 0 && leafRows.length === 0) return emptySummary;
|
|
610
629
|
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
JOIN nodes n ON e.target_id = n.id
|
|
618
|
-
LEFT JOIN node_metrics nm ON n.id = nm.node_id
|
|
619
|
-
WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'file'
|
|
620
|
-
`)
|
|
621
|
-
.all(d.id) as FileMetricRow[];
|
|
622
|
-
if (noTests) files = files.filter((f) => !isTestFile(f.name));
|
|
623
|
-
|
|
624
|
-
const subdirs = db
|
|
625
|
-
.prepare(`
|
|
626
|
-
SELECT n.name
|
|
630
|
+
// 3. Get exported status for affected nodes only (scoped to changed files)
|
|
631
|
+
const exportedIds = new Set(
|
|
632
|
+
(
|
|
633
|
+
db
|
|
634
|
+
.prepare(
|
|
635
|
+
`SELECT DISTINCT e.target_id
|
|
627
636
|
FROM edges e
|
|
628
|
-
JOIN nodes
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
fileCount,
|
|
637
|
-
symbolCount: d.symbol_count || 0,
|
|
638
|
-
fanIn: d.fan_in || 0,
|
|
639
|
-
fanOut: d.fan_out || 0,
|
|
640
|
-
cohesion: d.cohesion,
|
|
641
|
-
density: fileCount > 0 ? (d.symbol_count || 0) / fileCount : 0,
|
|
642
|
-
files: files.map((f) => ({
|
|
643
|
-
file: f.name,
|
|
644
|
-
lineCount: f.line_count || 0,
|
|
645
|
-
symbolCount: f.symbol_count || 0,
|
|
646
|
-
importCount: f.import_count || 0,
|
|
647
|
-
exportCount: f.export_count || 0,
|
|
648
|
-
fanIn: f.fan_in || 0,
|
|
649
|
-
fanOut: f.fan_out || 0,
|
|
650
|
-
})),
|
|
651
|
-
subdirectories: subdirs.map((s) => s.name),
|
|
652
|
-
};
|
|
653
|
-
});
|
|
654
|
-
|
|
655
|
-
// Apply global file limit unless full mode
|
|
656
|
-
if (!full) {
|
|
657
|
-
const totalFiles = result.reduce((sum, d) => sum + d.files.length, 0);
|
|
658
|
-
if (totalFiles > fileLimit) {
|
|
659
|
-
let shown = 0;
|
|
660
|
-
for (const d of result) {
|
|
661
|
-
const remaining = fileLimit - shown;
|
|
662
|
-
if (remaining <= 0) {
|
|
663
|
-
d.files = [];
|
|
664
|
-
} else if (d.files.length > remaining) {
|
|
665
|
-
d.files = d.files.slice(0, remaining);
|
|
666
|
-
shown = fileLimit;
|
|
667
|
-
} else {
|
|
668
|
-
shown += d.files.length;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
const suppressed = totalFiles - fileLimit;
|
|
672
|
-
return {
|
|
673
|
-
directories: result,
|
|
674
|
-
count: result.length,
|
|
675
|
-
suppressed,
|
|
676
|
-
warning: `${suppressed} files omitted (showing ${fileLimit}/${totalFiles}). Use --full to show all files, or narrow with --directory.`,
|
|
677
|
-
};
|
|
678
|
-
}
|
|
679
|
-
}
|
|
637
|
+
JOIN nodes caller ON e.source_id = caller.id
|
|
638
|
+
JOIN nodes target ON e.target_id = target.id
|
|
639
|
+
WHERE e.kind = 'calls' AND caller.file != target.file
|
|
640
|
+
AND target.file IN (${placeholders})`,
|
|
641
|
+
)
|
|
642
|
+
.all(...allAffectedFiles) as { target_id: number }[]
|
|
643
|
+
).map((r) => r.target_id),
|
|
644
|
+
);
|
|
680
645
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
646
|
+
// 4. Production fan-in for affected nodes only
|
|
647
|
+
const prodFanInMap = new Map<number, number>();
|
|
648
|
+
const prodRows = db
|
|
649
|
+
.prepare(
|
|
650
|
+
`SELECT e.target_id, COUNT(*) AS cnt
|
|
651
|
+
FROM edges e
|
|
652
|
+
JOIN nodes caller ON e.source_id = caller.id
|
|
653
|
+
JOIN nodes target ON e.target_id = target.id
|
|
654
|
+
WHERE e.kind = 'calls'
|
|
655
|
+
AND target.file IN (${placeholders})
|
|
656
|
+
${testFilterSQL('caller.file')}
|
|
657
|
+
GROUP BY e.target_id`,
|
|
658
|
+
)
|
|
659
|
+
.all(...allAffectedFiles) as { target_id: number; cnt: number }[];
|
|
660
|
+
for (const r of prodRows) {
|
|
661
|
+
prodFanInMap.set(r.target_id, r.cnt);
|
|
685
662
|
}
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
interface HotspotRow {
|
|
689
|
-
name: string;
|
|
690
|
-
kind: string;
|
|
691
|
-
line_count: number | null;
|
|
692
|
-
symbol_count: number | null;
|
|
693
|
-
import_count: number | null;
|
|
694
|
-
export_count: number | null;
|
|
695
|
-
fan_in: number | null;
|
|
696
|
-
fan_out: number | null;
|
|
697
|
-
cohesion: number | null;
|
|
698
|
-
file_count: number | null;
|
|
699
|
-
}
|
|
700
663
|
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
664
|
+
// 5. Classify affected nodes using global medians
|
|
665
|
+
const classifierInput = rows.map((r) => ({
|
|
666
|
+
id: String(r.id),
|
|
667
|
+
name: r.name,
|
|
668
|
+
kind: r.kind,
|
|
669
|
+
file: r.file,
|
|
670
|
+
fanIn: r.fan_in,
|
|
671
|
+
fanOut: r.fan_out,
|
|
672
|
+
isExported: exportedIds.has(r.id),
|
|
673
|
+
productionFanIn: prodFanInMap.get(r.id) || 0,
|
|
674
|
+
}));
|
|
708
675
|
|
|
709
|
-
|
|
710
|
-
customDbPath?: string,
|
|
711
|
-
opts: HotspotsDataOpts = {},
|
|
712
|
-
): {
|
|
713
|
-
metric: string;
|
|
714
|
-
level: string;
|
|
715
|
-
limit: number;
|
|
716
|
-
hotspots: unknown[];
|
|
717
|
-
} {
|
|
718
|
-
const db = openReadonlyOrFail(customDbPath);
|
|
719
|
-
try {
|
|
720
|
-
const metric = opts.metric || 'fan-in';
|
|
721
|
-
const level = opts.level || 'file';
|
|
722
|
-
const limit = opts.limit || 10;
|
|
723
|
-
const noTests = opts.noTests || false;
|
|
724
|
-
|
|
725
|
-
const kind = level === 'directory' ? 'directory' : 'file';
|
|
726
|
-
|
|
727
|
-
const testFilter = testFilterSQL('n.name', noTests && kind === 'file');
|
|
728
|
-
|
|
729
|
-
const HOTSPOT_QUERIES: Record<string, { all(...params: unknown[]): HotspotRow[] }> = {
|
|
730
|
-
'fan-in': db.prepare(`
|
|
731
|
-
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
732
|
-
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
733
|
-
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
734
|
-
WHERE n.kind = ? ${testFilter} ORDER BY nm.fan_in DESC NULLS LAST LIMIT ?`),
|
|
735
|
-
'fan-out': db.prepare(`
|
|
736
|
-
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
737
|
-
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
738
|
-
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
739
|
-
WHERE n.kind = ? ${testFilter} ORDER BY nm.fan_out DESC NULLS LAST LIMIT ?`),
|
|
740
|
-
density: db.prepare(`
|
|
741
|
-
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
742
|
-
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
743
|
-
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
744
|
-
WHERE n.kind = ? ${testFilter} ORDER BY nm.symbol_count DESC NULLS LAST LIMIT ?`),
|
|
745
|
-
coupling: db.prepare(`
|
|
746
|
-
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
747
|
-
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
748
|
-
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
749
|
-
WHERE n.kind = ? ${testFilter} ORDER BY (COALESCE(nm.fan_in, 0) + COALESCE(nm.fan_out, 0)) DESC NULLS LAST LIMIT ?`),
|
|
750
|
-
};
|
|
751
|
-
|
|
752
|
-
const stmt = HOTSPOT_QUERIES[metric] ?? HOTSPOT_QUERIES['fan-in'];
|
|
753
|
-
// stmt is always defined: metric is a valid key or the fallback is a concrete property
|
|
754
|
-
const rows = stmt!.all(kind, limit);
|
|
755
|
-
|
|
756
|
-
const hotspots = rows.map((r) => ({
|
|
757
|
-
name: r.name,
|
|
758
|
-
kind: r.kind,
|
|
759
|
-
lineCount: r.line_count,
|
|
760
|
-
symbolCount: r.symbol_count,
|
|
761
|
-
importCount: r.import_count,
|
|
762
|
-
exportCount: r.export_count,
|
|
763
|
-
fanIn: r.fan_in,
|
|
764
|
-
fanOut: r.fan_out,
|
|
765
|
-
cohesion: r.cohesion,
|
|
766
|
-
fileCount: r.file_count,
|
|
767
|
-
density:
|
|
768
|
-
(r.file_count ?? 0) > 0
|
|
769
|
-
? (r.symbol_count || 0) / (r.file_count ?? 1)
|
|
770
|
-
: (r.line_count ?? 0) > 0
|
|
771
|
-
? (r.symbol_count || 0) / (r.line_count ?? 1)
|
|
772
|
-
: 0,
|
|
773
|
-
coupling: (r.fan_in || 0) + (r.fan_out || 0),
|
|
774
|
-
}));
|
|
775
|
-
|
|
776
|
-
const base = { metric, level, limit, hotspots };
|
|
777
|
-
return paginateResult(base, 'hotspots', { limit: opts.limit, offset: opts.offset });
|
|
778
|
-
} finally {
|
|
779
|
-
db.close();
|
|
780
|
-
}
|
|
781
|
-
}
|
|
676
|
+
const roleMap = classifyRoles(classifierInput, globalMedians);
|
|
782
677
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
}
|
|
678
|
+
// 6. Build summary (only for affected nodes) and update only those nodes
|
|
679
|
+
const summary: RoleSummary = { ...emptySummary };
|
|
680
|
+
const idsByRole = new Map<string, number[]>();
|
|
787
681
|
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
)
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
cohesion: number | null;
|
|
796
|
-
fileCount: number;
|
|
797
|
-
symbolCount: number;
|
|
798
|
-
fanIn: number;
|
|
799
|
-
fanOut: number;
|
|
800
|
-
files: string[];
|
|
801
|
-
}[];
|
|
802
|
-
count: number;
|
|
803
|
-
} {
|
|
804
|
-
const db = openReadonlyOrFail(customDbPath);
|
|
805
|
-
try {
|
|
806
|
-
const config = opts.config || loadConfig();
|
|
807
|
-
const threshold =
|
|
808
|
-
opts.threshold ??
|
|
809
|
-
(config as unknown as { structure?: { cohesionThreshold?: number } }).structure
|
|
810
|
-
?.cohesionThreshold ??
|
|
811
|
-
0.3;
|
|
812
|
-
|
|
813
|
-
const dirs = db
|
|
814
|
-
.prepare(`
|
|
815
|
-
SELECT n.id, n.name, nm.symbol_count, nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
816
|
-
FROM nodes n
|
|
817
|
-
JOIN node_metrics nm ON n.id = nm.node_id
|
|
818
|
-
WHERE n.kind = 'directory' AND nm.cohesion IS NOT NULL AND nm.cohesion >= ?
|
|
819
|
-
ORDER BY nm.cohesion DESC
|
|
820
|
-
`)
|
|
821
|
-
.all(threshold) as {
|
|
822
|
-
id: number;
|
|
823
|
-
name: string;
|
|
824
|
-
symbol_count: number | null;
|
|
825
|
-
fan_in: number | null;
|
|
826
|
-
fan_out: number | null;
|
|
827
|
-
cohesion: number | null;
|
|
828
|
-
file_count: number | null;
|
|
829
|
-
}[];
|
|
830
|
-
|
|
831
|
-
const modules = dirs.map((d) => {
|
|
832
|
-
// Get files inside this directory
|
|
833
|
-
const files = (
|
|
834
|
-
db
|
|
835
|
-
.prepare(`
|
|
836
|
-
SELECT n.name FROM edges e
|
|
837
|
-
JOIN nodes n ON e.target_id = n.id
|
|
838
|
-
WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'file'
|
|
839
|
-
`)
|
|
840
|
-
.all(d.id) as { name: string }[]
|
|
841
|
-
).map((f) => f.name);
|
|
842
|
-
|
|
843
|
-
return {
|
|
844
|
-
directory: d.name,
|
|
845
|
-
cohesion: d.cohesion,
|
|
846
|
-
fileCount: d.file_count || 0,
|
|
847
|
-
symbolCount: d.symbol_count || 0,
|
|
848
|
-
fanIn: d.fan_in || 0,
|
|
849
|
-
fanOut: d.fan_out || 0,
|
|
850
|
-
files,
|
|
851
|
-
};
|
|
852
|
-
});
|
|
853
|
-
|
|
854
|
-
return { threshold, modules, count: modules.length };
|
|
855
|
-
} finally {
|
|
856
|
-
db.close();
|
|
682
|
+
// Leaf kinds are always dead-leaf -- skip classifier
|
|
683
|
+
if (leafRows.length > 0) {
|
|
684
|
+
const leafIds: number[] = [];
|
|
685
|
+
for (const row of leafRows) leafIds.push(row.id);
|
|
686
|
+
idsByRole.set('dead-leaf', leafIds);
|
|
687
|
+
summary.dead += leafRows.length;
|
|
688
|
+
summary['dead-leaf'] += leafRows.length;
|
|
857
689
|
}
|
|
858
|
-
}
|
|
859
690
|
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
case 'density':
|
|
871
|
-
return (a, b) => {
|
|
872
|
-
const da = (a.file_count ?? 0) > 0 ? (a.symbol_count || 0) / (a.file_count ?? 1) : 0;
|
|
873
|
-
const db_ = (b.file_count ?? 0) > 0 ? (b.symbol_count || 0) / (b.file_count ?? 1) : 0;
|
|
874
|
-
return db_ - da;
|
|
875
|
-
};
|
|
876
|
-
default:
|
|
877
|
-
return (a, b) => a.name.localeCompare(b.name);
|
|
691
|
+
for (const row of rows) {
|
|
692
|
+
const role = roleMap.get(String(row.id)) || 'leaf';
|
|
693
|
+
if (role.startsWith('dead')) summary.dead++;
|
|
694
|
+
summary[role] = (summary[role] || 0) + 1;
|
|
695
|
+
let ids = idsByRole.get(role);
|
|
696
|
+
if (!ids) {
|
|
697
|
+
ids = [];
|
|
698
|
+
idsByRole.set(role, ids);
|
|
699
|
+
}
|
|
700
|
+
ids.push(row.id);
|
|
878
701
|
}
|
|
702
|
+
|
|
703
|
+
// Only update affected nodes — no global NULL reset
|
|
704
|
+
const ROLE_CHUNK = 500;
|
|
705
|
+
const roleStmtCache = new Map<number, SqliteStatement>();
|
|
706
|
+
db.transaction(() => {
|
|
707
|
+
// Reset roles only for affected files' nodes
|
|
708
|
+
db.prepare(
|
|
709
|
+
`UPDATE nodes SET role = NULL WHERE file IN (${placeholders}) AND kind NOT IN ('file', 'directory')`,
|
|
710
|
+
).run(...allAffectedFiles);
|
|
711
|
+
for (const [role, ids] of idsByRole) {
|
|
712
|
+
for (let i = 0; i < ids.length; i += ROLE_CHUNK) {
|
|
713
|
+
const end = Math.min(i + ROLE_CHUNK, ids.length);
|
|
714
|
+
const chunkSize = end - i;
|
|
715
|
+
let stmt = roleStmtCache.get(chunkSize);
|
|
716
|
+
if (!stmt) {
|
|
717
|
+
const ph = Array.from({ length: chunkSize }, () => '?').join(',');
|
|
718
|
+
stmt = db.prepare(`UPDATE nodes SET role = ? WHERE id IN (${ph})`);
|
|
719
|
+
roleStmtCache.set(chunkSize, stmt);
|
|
720
|
+
}
|
|
721
|
+
const vals: unknown[] = [role];
|
|
722
|
+
for (let j = i; j < end; j++) vals.push(ids[j]);
|
|
723
|
+
stmt.run(...vals);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
})();
|
|
727
|
+
|
|
728
|
+
return summary;
|
|
879
729
|
}
|
|
730
|
+
|
|
731
|
+
// ─── Query functions (re-exported from structure-query.ts) ────────────
|
|
732
|
+
// Split to separate query-time concerns (DB reads, sorting, pagination)
|
|
733
|
+
// from build-time concerns (directory insertion, metrics computation, role classification).
|
|
734
|
+
export { hotspotsData, moduleBoundariesData, structureData } from './structure-query.js';
|
package/src/features/triage.ts
CHANGED
|
@@ -126,7 +126,7 @@ export function triageData(
|
|
|
126
126
|
const minScore = opts.minScore != null ? Number(opts.minScore) : null;
|
|
127
127
|
const sort = opts.sort || 'risk';
|
|
128
128
|
const config = opts.config || loadConfig();
|
|
129
|
-
const riskConfig = ((config as unknown as Record<string, unknown>)
|
|
129
|
+
const riskConfig = ((config as unknown as Record<string, unknown>).risk || {}) as {
|
|
130
130
|
weights?: Partial<RiskWeights>;
|
|
131
131
|
roleWeights?: Record<string, number>;
|
|
132
132
|
defaultRoleWeight?: number;
|
|
@@ -163,7 +163,7 @@ export function triageData(
|
|
|
163
163
|
const items = buildTriageItems(filtered, riskMetrics);
|
|
164
164
|
|
|
165
165
|
const scored = minScore != null ? items.filter((it) => it.riskScore >= minScore) : items;
|
|
166
|
-
scored.sort(SORT_FNS[sort] || SORT_FNS
|
|
166
|
+
scored.sort(SORT_FNS[sort] || SORT_FNS.risk!);
|
|
167
167
|
|
|
168
168
|
const result = {
|
|
169
169
|
items: scored,
|