@monoes/monograph 1.2.0 → 1.2.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/__tests__/mcp-tools/cypher.test.ts +10 -6
- package/__tests__/mcp-tools/detect-changes.test.ts +14 -11
- package/__tests__/pipeline/phases/wildcard-synthesis.test.ts +4 -4
- package/__tests__/search/embed-batch-config.test.ts +7 -7
- package/__tests__/search/hybrid-query.test.ts +3 -1
- package/__tests__/web/api.test.ts +1 -1
- package/dist/src/analysis/churn-cache.d.ts.map +1 -1
- package/dist/src/analysis/churn-cache.js +19 -6
- package/dist/src/analysis/churn-cache.js.map +1 -1
- package/dist/src/analysis/churn-trend.d.ts.map +1 -1
- package/dist/src/analysis/churn-trend.js +50 -6
- package/dist/src/analysis/churn-trend.js.map +1 -1
- package/dist/src/analysis/cluster-quality.d.ts +3 -0
- package/dist/src/analysis/cluster-quality.d.ts.map +1 -1
- package/dist/src/analysis/cluster-quality.js +18 -12
- package/dist/src/analysis/cluster-quality.js.map +1 -1
- package/dist/src/analysis/coverage-gaps.d.ts +3 -0
- package/dist/src/analysis/coverage-gaps.d.ts.map +1 -1
- package/dist/src/analysis/coverage-gaps.js +40 -1
- package/dist/src/analysis/coverage-gaps.js.map +1 -1
- package/dist/src/analysis/dep-classification.d.ts +2 -0
- package/dist/src/analysis/dep-classification.d.ts.map +1 -1
- package/dist/src/analysis/dep-classification.js +30 -0
- package/dist/src/analysis/dep-classification.js.map +1 -1
- package/dist/src/analysis/dependency-health.d.ts +4 -0
- package/dist/src/analysis/dependency-health.d.ts.map +1 -1
- package/dist/src/analysis/dependency-health.js +28 -0
- package/dist/src/analysis/dependency-health.js.map +1 -1
- package/dist/src/analysis/duplicate-exports.d.ts +9 -5
- package/dist/src/analysis/duplicate-exports.d.ts.map +1 -1
- package/dist/src/analysis/duplicate-exports.js +26 -2
- package/dist/src/analysis/duplicate-exports.js.map +1 -1
- package/dist/src/analysis/feature-flags.d.ts.map +1 -1
- package/dist/src/analysis/feature-flags.js +36 -17
- package/dist/src/analysis/feature-flags.js.map +1 -1
- package/dist/src/analysis/node-similarity.d.ts +19 -0
- package/dist/src/analysis/node-similarity.d.ts.map +1 -1
- package/dist/src/analysis/node-similarity.js +34 -3
- package/dist/src/analysis/node-similarity.js.map +1 -1
- package/dist/src/analysis/private-type-leaks.d.ts +4 -0
- package/dist/src/analysis/private-type-leaks.d.ts.map +1 -1
- package/dist/src/analysis/private-type-leaks.js +43 -2
- package/dist/src/analysis/private-type-leaks.js.map +1 -1
- package/dist/src/analysis/project-state.d.ts +2 -0
- package/dist/src/analysis/project-state.d.ts.map +1 -1
- package/dist/src/analysis/project-state.js +18 -7
- package/dist/src/analysis/project-state.js.map +1 -1
- package/dist/src/analysis/suppressions.d.ts.map +1 -1
- package/dist/src/analysis/suppressions.js +3 -1
- package/dist/src/analysis/suppressions.js.map +1 -1
- package/dist/src/analysis/trace.d.ts +5 -3
- package/dist/src/analysis/trace.d.ts.map +1 -1
- package/dist/src/analysis/trace.js +59 -53
- package/dist/src/analysis/trace.js.map +1 -1
- package/dist/src/analysis/unused-class-members.d.ts.map +1 -1
- package/dist/src/analysis/unused-class-members.js +7 -1
- package/dist/src/analysis/unused-class-members.js.map +1 -1
- package/dist/src/analysis/worker-pool.d.ts +3 -0
- package/dist/src/analysis/worker-pool.d.ts.map +1 -1
- package/dist/src/analysis/worker-pool.js +30 -8
- package/dist/src/analysis/worker-pool.js.map +1 -1
- package/dist/src/cli/augment.d.ts.map +1 -1
- package/dist/src/cli/augment.js +10 -3
- package/dist/src/cli/augment.js.map +1 -1
- package/dist/src/export/sarif.js +5 -5
- package/dist/src/export/sarif.js.map +1 -1
- package/dist/src/graph/analyzer.d.ts.map +1 -1
- package/dist/src/graph/analyzer.js +17 -2
- package/dist/src/graph/analyzer.js.map +1 -1
- package/dist/src/graph/author-analytics.d.ts +2 -0
- package/dist/src/graph/author-analytics.d.ts.map +1 -1
- package/dist/src/graph/author-analytics.js +39 -5
- package/dist/src/graph/author-analytics.js.map +1 -1
- package/dist/src/graph/baseline.d.ts +5 -0
- package/dist/src/graph/baseline.d.ts.map +1 -1
- package/dist/src/graph/baseline.js +65 -18
- package/dist/src/graph/baseline.js.map +1 -1
- package/dist/src/graph/clone-detector.d.ts.map +1 -1
- package/dist/src/graph/clone-detector.js +19 -33
- package/dist/src/graph/clone-detector.js.map +1 -1
- package/dist/src/graph/clone-families.d.ts +2 -0
- package/dist/src/graph/clone-families.d.ts.map +1 -1
- package/dist/src/graph/clone-families.js +38 -10
- package/dist/src/graph/clone-families.js.map +1 -1
- package/dist/src/graph/codeowners-extended.d.ts +17 -0
- package/dist/src/graph/codeowners-extended.d.ts.map +1 -1
- package/dist/src/graph/codeowners-extended.js +42 -0
- package/dist/src/graph/codeowners-extended.js.map +1 -1
- package/dist/src/graph/codeowners-gitlab.d.ts +21 -0
- package/dist/src/graph/codeowners-gitlab.d.ts.map +1 -1
- package/dist/src/graph/codeowners-gitlab.js +37 -0
- package/dist/src/graph/codeowners-gitlab.js.map +1 -1
- package/dist/src/graph/codeowners.d.ts +10 -0
- package/dist/src/graph/codeowners.d.ts.map +1 -1
- package/dist/src/graph/codeowners.js +24 -2
- package/dist/src/graph/codeowners.js.map +1 -1
- package/dist/src/graph/complexity.d.ts +10 -0
- package/dist/src/graph/complexity.d.ts.map +1 -1
- package/dist/src/graph/complexity.js +93 -43
- package/dist/src/graph/complexity.js.map +1 -1
- package/dist/src/graph/cross-reference-findings.d.ts.map +1 -1
- package/dist/src/graph/cross-reference-findings.js +19 -2
- package/dist/src/graph/cross-reference-findings.js.map +1 -1
- package/dist/src/graph/cross-reference.d.ts +4 -0
- package/dist/src/graph/cross-reference.d.ts.map +1 -1
- package/dist/src/graph/cross-reference.js +23 -0
- package/dist/src/graph/cross-reference.js.map +1 -1
- package/dist/src/graph/cycle-detection.d.ts +2 -0
- package/dist/src/graph/cycle-detection.d.ts.map +1 -1
- package/dist/src/graph/cycle-detection.js +26 -3
- package/dist/src/graph/cycle-detection.js.map +1 -1
- package/dist/src/graph/cycles.d.ts +11 -3
- package/dist/src/graph/cycles.d.ts.map +1 -1
- package/dist/src/graph/cycles.js +121 -83
- package/dist/src/graph/cycles.js.map +1 -1
- package/dist/src/graph/dead-code.d.ts +23 -0
- package/dist/src/graph/dead-code.d.ts.map +1 -1
- package/dist/src/graph/dead-code.js +53 -13
- package/dist/src/graph/dead-code.js.map +1 -1
- package/dist/src/graph/dependency-closure.d.ts.map +1 -1
- package/dist/src/graph/dependency-closure.js +20 -4
- package/dist/src/graph/dependency-closure.js.map +1 -1
- package/dist/src/graph/dfs.d.ts +14 -0
- package/dist/src/graph/dfs.d.ts.map +1 -1
- package/dist/src/graph/dfs.js +61 -0
- package/dist/src/graph/dfs.js.map +1 -1
- package/dist/src/graph/duplication-stats.d.ts.map +1 -1
- package/dist/src/graph/duplication-stats.js +19 -3
- package/dist/src/graph/duplication-stats.js.map +1 -1
- package/dist/src/graph/edge-filter.d.ts +2 -0
- package/dist/src/graph/edge-filter.d.ts.map +1 -1
- package/dist/src/graph/edge-filter.js +26 -12
- package/dist/src/graph/edge-filter.js.map +1 -1
- package/dist/src/graph/health-score.d.ts +8 -0
- package/dist/src/graph/health-score.d.ts.map +1 -1
- package/dist/src/graph/health-score.js +48 -17
- package/dist/src/graph/health-score.js.map +1 -1
- package/dist/src/graph/hotspots.d.ts +7 -0
- package/dist/src/graph/hotspots.d.ts.map +1 -1
- package/dist/src/graph/hotspots.js +50 -5
- package/dist/src/graph/hotspots.js.map +1 -1
- package/dist/src/graph/import-chain.d.ts +17 -1
- package/dist/src/graph/import-chain.d.ts.map +1 -1
- package/dist/src/graph/import-chain.js +68 -19
- package/dist/src/graph/import-chain.js.map +1 -1
- package/dist/src/graph/loader.d.ts.map +1 -1
- package/dist/src/graph/loader.js +9 -10
- package/dist/src/graph/loader.js.map +1 -1
- package/dist/src/graph/maintainability.d.ts +8 -0
- package/dist/src/graph/maintainability.d.ts.map +1 -1
- package/dist/src/graph/maintainability.js +65 -10
- package/dist/src/graph/maintainability.js.map +1 -1
- package/dist/src/graph/mirrored-dirs.d.ts +4 -0
- package/dist/src/graph/mirrored-dirs.d.ts.map +1 -1
- package/dist/src/graph/mirrored-dirs.js +48 -13
- package/dist/src/graph/mirrored-dirs.js.map +1 -1
- package/dist/src/graph/narrowing.d.ts +15 -0
- package/dist/src/graph/narrowing.d.ts.map +1 -1
- package/dist/src/graph/narrowing.js +33 -0
- package/dist/src/graph/narrowing.js.map +1 -1
- package/dist/src/graph/node-search.d.ts +3 -0
- package/dist/src/graph/node-search.d.ts.map +1 -1
- package/dist/src/graph/node-search.js +37 -4
- package/dist/src/graph/node-search.js.map +1 -1
- package/dist/src/graph/normalize-id.d.ts +4 -0
- package/dist/src/graph/normalize-id.d.ts.map +1 -1
- package/dist/src/graph/normalize-id.js +25 -2
- package/dist/src/graph/normalize-id.js.map +1 -1
- package/dist/src/graph/pagerank.d.ts +7 -0
- package/dist/src/graph/pagerank.d.ts.map +1 -1
- package/dist/src/graph/pagerank.js +49 -2
- package/dist/src/graph/pagerank.js.map +1 -1
- package/dist/src/graph/prune.d.ts +31 -0
- package/dist/src/graph/prune.d.ts.map +1 -1
- package/dist/src/graph/prune.js +65 -0
- package/dist/src/graph/prune.js.map +1 -1
- package/dist/src/graph/regex-search.d.ts +10 -2
- package/dist/src/graph/regex-search.d.ts.map +1 -1
- package/dist/src/graph/regex-search.js +33 -4
- package/dist/src/graph/regex-search.js.map +1 -1
- package/dist/src/graph/regression.d.ts +5 -0
- package/dist/src/graph/regression.d.ts.map +1 -1
- package/dist/src/graph/regression.js +45 -23
- package/dist/src/graph/regression.js.map +1 -1
- package/dist/src/graph/ripple-impact.d.ts +27 -0
- package/dist/src/graph/ripple-impact.d.ts.map +1 -1
- package/dist/src/graph/ripple-impact.js +55 -7
- package/dist/src/graph/ripple-impact.js.map +1 -1
- package/dist/src/graph/risk-profile.d.ts +7 -0
- package/dist/src/graph/risk-profile.d.ts.map +1 -1
- package/dist/src/graph/risk-profile.js +82 -48
- package/dist/src/graph/risk-profile.js.map +1 -1
- package/dist/src/graph/shingle-filter.d.ts.map +1 -1
- package/dist/src/graph/shingle-filter.js +25 -26
- package/dist/src/graph/shingle-filter.js.map +1 -1
- package/dist/src/graph/statistics.d.ts +19 -0
- package/dist/src/graph/statistics.d.ts.map +1 -1
- package/dist/src/graph/statistics.js +53 -40
- package/dist/src/graph/statistics.js.map +1 -1
- package/dist/src/graph/stats.d.ts +2 -0
- package/dist/src/graph/stats.d.ts.map +1 -1
- package/dist/src/graph/stats.js +54 -8
- package/dist/src/graph/stats.js.map +1 -1
- package/dist/src/graph/subgraph.d.ts +5 -0
- package/dist/src/graph/subgraph.d.ts.map +1 -1
- package/dist/src/graph/subgraph.js +73 -9
- package/dist/src/graph/subgraph.js.map +1 -1
- package/dist/src/graph/topo-sort.d.ts +15 -0
- package/dist/src/graph/topo-sort.d.ts.map +1 -1
- package/dist/src/graph/topo-sort.js +83 -10
- package/dist/src/graph/topo-sort.js.map +1 -1
- package/dist/src/graph/wcc.d.ts +29 -0
- package/dist/src/graph/wcc.d.ts.map +1 -1
- package/dist/src/graph/wcc.js +122 -39
- package/dist/src/graph/wcc.js.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp-tools/api-impact.d.ts.map +1 -1
- package/dist/src/mcp-tools/api-impact.js +13 -6
- package/dist/src/mcp-tools/api-impact.js.map +1 -1
- package/dist/src/mcp-tools/context.d.ts.map +1 -1
- package/dist/src/mcp-tools/context.js +19 -29
- package/dist/src/mcp-tools/context.js.map +1 -1
- package/dist/src/mcp-tools/cypher.d.ts +11 -0
- package/dist/src/mcp-tools/cypher.d.ts.map +1 -1
- package/dist/src/mcp-tools/cypher.js +13 -1
- package/dist/src/mcp-tools/cypher.js.map +1 -1
- package/dist/src/mcp-tools/detect-changes.d.ts.map +1 -1
- package/dist/src/mcp-tools/detect-changes.js +35 -20
- package/dist/src/mcp-tools/detect-changes.js.map +1 -1
- package/dist/src/mcp-tools/explain.d.ts.map +1 -1
- package/dist/src/mcp-tools/explain.js +4 -1
- package/dist/src/mcp-tools/explain.js.map +1 -1
- package/dist/src/mcp-tools/graph-query.d.ts +2 -0
- package/dist/src/mcp-tools/graph-query.d.ts.map +1 -1
- package/dist/src/mcp-tools/graph-query.js +20 -3
- package/dist/src/mcp-tools/graph-query.js.map +1 -1
- package/dist/src/mcp-tools/group-list.d.ts +1 -0
- package/dist/src/mcp-tools/group-list.d.ts.map +1 -1
- package/dist/src/mcp-tools/group-list.js +7 -9
- package/dist/src/mcp-tools/group-list.js.map +1 -1
- package/dist/src/mcp-tools/impact.d.ts.map +1 -1
- package/dist/src/mcp-tools/impact.js +20 -59
- package/dist/src/mcp-tools/impact.js.map +1 -1
- package/dist/src/mcp-tools/neighbors.d.ts.map +1 -1
- package/dist/src/mcp-tools/neighbors.js +24 -33
- package/dist/src/mcp-tools/neighbors.js.map +1 -1
- package/dist/src/mcp-tools/query.d.ts +2 -0
- package/dist/src/mcp-tools/query.d.ts.map +1 -1
- package/dist/src/mcp-tools/query.js +1 -0
- package/dist/src/mcp-tools/query.js.map +1 -1
- package/dist/src/mcp-tools/rename.d.ts.map +1 -1
- package/dist/src/mcp-tools/rename.js +12 -7
- package/dist/src/mcp-tools/rename.js.map +1 -1
- package/dist/src/mcp-tools/route-map.d.ts.map +1 -1
- package/dist/src/mcp-tools/route-map.js +19 -23
- package/dist/src/mcp-tools/route-map.js.map +1 -1
- package/dist/src/mcp-tools/shape-check.d.ts.map +1 -1
- package/dist/src/mcp-tools/shape-check.js +17 -13
- package/dist/src/mcp-tools/shape-check.js.map +1 -1
- package/dist/src/parsers/loader.js +6 -5
- package/dist/src/parsers/loader.js.map +1 -1
- package/dist/src/pipeline/cluster-only.d.ts.map +1 -1
- package/dist/src/pipeline/cluster-only.js +8 -4
- package/dist/src/pipeline/cluster-only.js.map +1 -1
- package/dist/src/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/src/pipeline/orchestrator.js +19 -0
- package/dist/src/pipeline/orchestrator.js.map +1 -1
- package/dist/src/pipeline/phases/boundary.d.ts +2 -0
- package/dist/src/pipeline/phases/boundary.d.ts.map +1 -1
- package/dist/src/pipeline/phases/boundary.js +31 -2
- package/dist/src/pipeline/phases/boundary.js.map +1 -1
- package/dist/src/pipeline/phases/communities.d.ts +14 -0
- package/dist/src/pipeline/phases/communities.d.ts.map +1 -1
- package/dist/src/pipeline/phases/communities.js +58 -5
- package/dist/src/pipeline/phases/communities.js.map +1 -1
- package/dist/src/pipeline/phases/contextual-proximity.d.ts.map +1 -1
- package/dist/src/pipeline/phases/contextual-proximity.js +21 -14
- package/dist/src/pipeline/phases/contextual-proximity.js.map +1 -1
- package/dist/src/pipeline/phases/cross-file.d.ts.map +1 -1
- package/dist/src/pipeline/phases/cross-file.js +35 -14
- package/dist/src/pipeline/phases/cross-file.js.map +1 -1
- package/dist/src/pipeline/phases/field-access.d.ts.map +1 -1
- package/dist/src/pipeline/phases/field-access.js +9 -4
- package/dist/src/pipeline/phases/field-access.js.map +1 -1
- package/dist/src/pipeline/phases/god-nodes.d.ts +4 -0
- package/dist/src/pipeline/phases/god-nodes.d.ts.map +1 -1
- package/dist/src/pipeline/phases/god-nodes.js +43 -9
- package/dist/src/pipeline/phases/god-nodes.js.map +1 -1
- package/dist/src/pipeline/phases/import-resolver.d.ts +11 -0
- package/dist/src/pipeline/phases/import-resolver.d.ts.map +1 -1
- package/dist/src/pipeline/phases/import-resolver.js +34 -7
- package/dist/src/pipeline/phases/import-resolver.js.map +1 -1
- package/dist/src/pipeline/phases/leiden.d.ts.map +1 -1
- package/dist/src/pipeline/phases/leiden.js +27 -19
- package/dist/src/pipeline/phases/leiden.js.map +1 -1
- package/dist/src/pipeline/phases/markdown.d.ts.map +1 -1
- package/dist/src/pipeline/phases/markdown.js +10 -5
- package/dist/src/pipeline/phases/markdown.js.map +1 -1
- package/dist/src/pipeline/phases/pdf-parse.d.ts.map +1 -1
- package/dist/src/pipeline/phases/pdf-parse.js +18 -3
- package/dist/src/pipeline/phases/pdf-parse.js.map +1 -1
- package/dist/src/pipeline/phases/processes.d.ts.map +1 -1
- package/dist/src/pipeline/phases/processes.js +26 -9
- package/dist/src/pipeline/phases/processes.js.map +1 -1
- package/dist/src/pipeline/phases/re-export-propagation.d.ts.map +1 -1
- package/dist/src/pipeline/phases/re-export-propagation.js +33 -14
- package/dist/src/pipeline/phases/re-export-propagation.js.map +1 -1
- package/dist/src/pipeline/phases/reachability.d.ts.map +1 -1
- package/dist/src/pipeline/phases/reachability.js +42 -26
- package/dist/src/pipeline/phases/reachability.js.map +1 -1
- package/dist/src/pipeline/phases/routes.d.ts.map +1 -1
- package/dist/src/pipeline/phases/routes.js +30 -21
- package/dist/src/pipeline/phases/routes.js.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.d.ts.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.js +93 -63
- package/dist/src/pipeline/phases/scope-resolution.js.map +1 -1
- package/dist/src/pipeline/runner.d.ts +4 -0
- package/dist/src/pipeline/runner.d.ts.map +1 -1
- package/dist/src/pipeline/runner.js +30 -11
- package/dist/src/pipeline/runner.js.map +1 -1
- package/dist/src/query/cypher-parser.d.ts +20 -0
- package/dist/src/query/cypher-parser.d.ts.map +1 -1
- package/dist/src/query/cypher-parser.js +73 -0
- package/dist/src/query/cypher-parser.js.map +1 -1
- package/dist/src/search/embed-batch.d.ts.map +1 -1
- package/dist/src/search/embed-batch.js +4 -1
- package/dist/src/search/embed-batch.js.map +1 -1
- package/dist/src/search/hybrid-query.d.ts +4 -0
- package/dist/src/search/hybrid-query.d.ts.map +1 -1
- package/dist/src/search/hybrid-query.js +5 -1
- package/dist/src/search/hybrid-query.js.map +1 -1
- package/dist/src/staleness/git-staleness.js +3 -3
- package/dist/src/staleness/git-staleness.js.map +1 -1
- package/dist/src/storage/db.d.ts.map +1 -1
- package/dist/src/storage/db.js +12 -0
- package/dist/src/storage/db.js.map +1 -1
- package/dist/src/storage/edge-store.d.ts +2 -0
- package/dist/src/storage/edge-store.d.ts.map +1 -1
- package/dist/src/storage/edge-store.js +26 -1
- package/dist/src/storage/edge-store.js.map +1 -1
- package/dist/src/storage/embedding-store.d.ts +16 -0
- package/dist/src/storage/embedding-store.d.ts.map +1 -1
- package/dist/src/storage/embedding-store.js +26 -2
- package/dist/src/storage/embedding-store.js.map +1 -1
- package/dist/src/storage/file-cache.d.ts +5 -0
- package/dist/src/storage/file-cache.d.ts.map +1 -1
- package/dist/src/storage/file-cache.js +35 -9
- package/dist/src/storage/file-cache.js.map +1 -1
- package/dist/src/storage/fts-store.d.ts +4 -0
- package/dist/src/storage/fts-store.d.ts.map +1 -1
- package/dist/src/storage/fts-store.js +8 -3
- package/dist/src/storage/fts-store.js.map +1 -1
- package/dist/src/storage/node-store.d.ts +2 -0
- package/dist/src/storage/node-store.d.ts.map +1 -1
- package/dist/src/storage/node-store.js +28 -1
- package/dist/src/storage/node-store.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/__tests__/pipeline/runner.parallel.test.ts +2 -2
- package/src/analysis/churn-cache.ts +18 -7
- package/src/analysis/churn-trend.ts +35 -6
- package/src/analysis/cluster-quality.ts +20 -11
- package/src/analysis/coverage-gaps.ts +44 -1
- package/src/analysis/dep-classification.ts +34 -0
- package/src/analysis/dependency-health.ts +25 -0
- package/src/analysis/duplicate-exports.ts +40 -4
- package/src/analysis/feature-flags.ts +37 -17
- package/src/analysis/node-similarity.ts +45 -4
- package/src/analysis/private-type-leaks.ts +49 -2
- package/src/analysis/project-state.ts +19 -9
- package/src/analysis/suppressions.ts +4 -1
- package/src/analysis/trace.ts +73 -54
- package/src/analysis/unused-class-members.ts +8 -1
- package/src/analysis/worker-pool.ts +40 -8
- package/src/cli/augment.ts +10 -3
- package/src/export/sarif.ts +5 -5
- package/src/graph/analyzer.ts +26 -2
- package/src/graph/author-analytics.ts +45 -5
- package/src/graph/baseline.ts +78 -24
- package/src/graph/clone-detector.ts +22 -36
- package/src/graph/clone-families.ts +34 -10
- package/src/graph/codeowners-extended.ts +54 -0
- package/src/graph/codeowners-gitlab.ts +46 -0
- package/src/graph/codeowners.ts +33 -2
- package/src/graph/complexity.ts +102 -47
- package/src/graph/cross-reference-findings.ts +23 -2
- package/src/graph/cross-reference.ts +26 -0
- package/src/graph/cycle-detection.ts +22 -3
- package/src/graph/cycles.ts +155 -96
- package/src/graph/dead-code.ts +76 -17
- package/src/graph/dependency-closure.ts +24 -7
- package/src/graph/dfs.ts +78 -0
- package/src/graph/duplication-stats.ts +17 -6
- package/src/graph/edge-filter.ts +26 -12
- package/src/graph/health-score.ts +47 -17
- package/src/graph/hotspots.ts +57 -8
- package/src/graph/import-chain.ts +89 -19
- package/src/graph/loader.ts +10 -7
- package/src/graph/maintainability.ts +72 -22
- package/src/graph/mirrored-dirs.ts +45 -14
- package/src/graph/narrowing.ts +44 -0
- package/src/graph/node-search.ts +49 -4
- package/src/graph/normalize-id.ts +26 -2
- package/src/graph/pagerank.ts +69 -2
- package/src/graph/prune.ts +76 -0
- package/src/graph/regex-search.ts +38 -4
- package/src/graph/regression.ts +48 -23
- package/src/graph/ripple-impact.ts +71 -11
- package/src/graph/risk-profile.ts +98 -53
- package/src/graph/shingle-filter.ts +33 -27
- package/src/graph/statistics.ts +73 -45
- package/src/graph/stats.ts +50 -16
- package/src/graph/subgraph.ts +96 -25
- package/src/graph/topo-sort.ts +101 -16
- package/src/graph/wcc.ts +146 -37
- package/src/index.ts +1 -1
- package/src/mcp-tools/api-impact.ts +13 -6
- package/src/mcp-tools/context.ts +31 -40
- package/src/mcp-tools/cypher.ts +14 -1
- package/src/mcp-tools/detect-changes.ts +38 -23
- package/src/mcp-tools/explain.ts +4 -1
- package/src/mcp-tools/graph-query.ts +24 -3
- package/src/mcp-tools/group-list.ts +9 -11
- package/src/mcp-tools/impact.ts +26 -66
- package/src/mcp-tools/neighbors.ts +34 -39
- package/src/mcp-tools/query.ts +3 -0
- package/src/mcp-tools/rename.ts +9 -8
- package/src/mcp-tools/route-map.ts +21 -23
- package/src/mcp-tools/shape-check.ts +18 -14
- package/src/parsers/loader.ts +5 -5
- package/src/pipeline/cluster-only.ts +8 -4
- package/src/pipeline/orchestrator.ts +22 -0
- package/src/pipeline/phases/boundary.ts +40 -2
- package/src/pipeline/phases/communities.ts +66 -5
- package/src/pipeline/phases/contextual-proximity.ts +15 -14
- package/src/pipeline/phases/cross-file.ts +35 -14
- package/src/pipeline/phases/field-access.ts +11 -4
- package/src/pipeline/phases/god-nodes.ts +47 -10
- package/src/pipeline/phases/import-resolver.ts +37 -5
- package/src/pipeline/phases/leiden.ts +25 -18
- package/src/pipeline/phases/markdown.ts +12 -7
- package/src/pipeline/phases/pdf-parse.ts +19 -2
- package/src/pipeline/phases/processes.ts +27 -15
- package/src/pipeline/phases/re-export-propagation.ts +40 -18
- package/src/pipeline/phases/reachability.ts +39 -24
- package/src/pipeline/phases/routes.ts +32 -21
- package/src/pipeline/phases/scope-resolution.ts +110 -71
- package/src/pipeline/runner.ts +28 -11
- package/src/query/cypher-parser.ts +83 -0
- package/src/search/embed-batch.ts +4 -1
- package/src/search/hybrid-query.ts +11 -1
- package/src/staleness/git-staleness.ts +3 -3
- package/src/storage/db.ts +11 -0
- package/src/storage/edge-store.ts +34 -1
- package/src/storage/embedding-store.ts +38 -6
- package/src/storage/file-cache.ts +35 -21
- package/src/storage/fts-store.ts +12 -3
- package/src/storage/node-store.ts +39 -1
- package/src/coverage/runtime.ts +0 -249
package/src/graph/baseline.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface BaselineFinding {
|
|
|
14
14
|
nodeId: string;
|
|
15
15
|
nodeName: string;
|
|
16
16
|
filePath: string | null;
|
|
17
|
+
startLine: number | null;
|
|
17
18
|
savedAt: string; // ISO timestamp
|
|
18
19
|
/** 16-hex SHA-256 fingerprint for stable deduplication across runs */
|
|
19
20
|
fingerprint?: string;
|
|
@@ -103,12 +104,12 @@ export function extractFindingsFromDb(
|
|
|
103
104
|
|
|
104
105
|
// Isolated nodes (no incoming or outgoing edges)
|
|
105
106
|
const isolated = db.prepare(`
|
|
106
|
-
SELECT n.id, n.name, n.file_path, n.label
|
|
107
|
+
SELECT n.id, n.name, n.file_path, n.start_line, n.label
|
|
107
108
|
FROM nodes n
|
|
108
109
|
WHERE NOT EXISTS (SELECT 1 FROM edges e WHERE e.source_id = n.id OR e.target_id = n.id)
|
|
109
110
|
AND n.label IN ('Function', 'Class', 'Method', 'Interface', 'Variable', 'Module', 'File')
|
|
110
111
|
LIMIT 500
|
|
111
|
-
`).all() as { id: string; name: string; file_path: string | null; label: string }[];
|
|
112
|
+
`).all() as { id: string; name: string; file_path: string | null; start_line: number | null; label: string }[];
|
|
112
113
|
|
|
113
114
|
for (const n of isolated) {
|
|
114
115
|
findings.push({
|
|
@@ -117,6 +118,7 @@ export function extractFindingsFromDb(
|
|
|
117
118
|
nodeId: n.id,
|
|
118
119
|
nodeName: n.name,
|
|
119
120
|
filePath: n.file_path,
|
|
121
|
+
startLine: n.start_line ?? null,
|
|
120
122
|
savedAt: new Date().toISOString(),
|
|
121
123
|
fingerprint: fingerprintFinding('isolated_node', n.file_path ?? '', n.id),
|
|
122
124
|
});
|
|
@@ -124,7 +126,7 @@ export function extractFindingsFromDb(
|
|
|
124
126
|
|
|
125
127
|
// God nodes (degree > 50)
|
|
126
128
|
const gods = db.prepare(`
|
|
127
|
-
SELECT n.id, n.name, n.file_path,
|
|
129
|
+
SELECT n.id, n.name, n.file_path, n.start_line,
|
|
128
130
|
COUNT(DISTINCT e1.id) + COUNT(DISTINCT e2.id) as degree
|
|
129
131
|
FROM nodes n
|
|
130
132
|
LEFT JOIN edges e1 ON e1.source_id = n.id
|
|
@@ -133,7 +135,7 @@ export function extractFindingsFromDb(
|
|
|
133
135
|
HAVING degree > 50
|
|
134
136
|
ORDER BY degree DESC
|
|
135
137
|
LIMIT 100
|
|
136
|
-
`).all() as { id: string; name: string; file_path: string | null; degree: number }[];
|
|
138
|
+
`).all() as { id: string; name: string; file_path: string | null; start_line: number | null; degree: number }[];
|
|
137
139
|
|
|
138
140
|
for (const n of gods) {
|
|
139
141
|
findings.push({
|
|
@@ -142,6 +144,7 @@ export function extractFindingsFromDb(
|
|
|
142
144
|
nodeId: n.id,
|
|
143
145
|
nodeName: n.name,
|
|
144
146
|
filePath: n.file_path,
|
|
147
|
+
startLine: n.start_line ?? null,
|
|
145
148
|
savedAt: new Date().toISOString(),
|
|
146
149
|
fingerprint: fingerprintFinding('god_node', n.file_path ?? '', n.id),
|
|
147
150
|
});
|
|
@@ -150,12 +153,12 @@ export function extractFindingsFromDb(
|
|
|
150
153
|
// Surprise edges: non-EXTRACTED confidence (cross-community low-confidence edges)
|
|
151
154
|
const surprises = db.prepare(`
|
|
152
155
|
SELECT DISTINCT e.id as edge_id, e.source_id, e.target_id, e.confidence,
|
|
153
|
-
n.name as src_name, n.file_path as src_file
|
|
156
|
+
n.name as src_name, n.file_path as src_file, n.start_line as src_line
|
|
154
157
|
FROM edges e
|
|
155
158
|
JOIN nodes n ON n.id = e.source_id
|
|
156
159
|
WHERE e.confidence != 'EXTRACTED'
|
|
157
160
|
LIMIT 200
|
|
158
|
-
`).all() as { edge_id: string; source_id: string; target_id: string; confidence: string; src_name: string; src_file: string | null }[];
|
|
161
|
+
`).all() as { edge_id: string; source_id: string; target_id: string; confidence: string; src_name: string; src_file: string | null; src_line: number | null }[];
|
|
159
162
|
|
|
160
163
|
for (const e of surprises) {
|
|
161
164
|
findings.push({
|
|
@@ -164,30 +167,28 @@ export function extractFindingsFromDb(
|
|
|
164
167
|
nodeId: e.source_id,
|
|
165
168
|
nodeName: e.src_name,
|
|
166
169
|
filePath: e.src_file,
|
|
170
|
+
startLine: e.src_line ?? null,
|
|
167
171
|
savedAt: new Date().toISOString(),
|
|
168
172
|
fingerprint: fingerprintFinding('surprise', e.source_id, e.target_id),
|
|
169
173
|
});
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
// Bridge nodes: nodes whose edges span more than one community_id
|
|
177
|
+
// Use JOIN+GROUP BY instead of correlated subqueries to avoid N*2 subquery executions
|
|
173
178
|
const bridges = db.prepare(`
|
|
174
|
-
SELECT n.id, n.name, n.file_path
|
|
179
|
+
SELECT n.id, n.name, n.file_path, n.start_line,
|
|
180
|
+
COUNT(DISTINCT CASE WHEN e_out.source_id = n.id AND n_tgt.community_id != n.community_id THEN n_tgt.community_id END) +
|
|
181
|
+
COUNT(DISTINCT CASE WHEN e_in.target_id = n.id AND n_src.community_id != n.community_id THEN n_src.community_id END) AS cross_communities
|
|
175
182
|
FROM nodes n
|
|
183
|
+
LEFT JOIN edges e_out ON e_out.source_id = n.id
|
|
184
|
+
LEFT JOIN nodes n_tgt ON n_tgt.id = e_out.target_id AND n_tgt.community_id IS NOT NULL
|
|
185
|
+
LEFT JOIN edges e_in ON e_in.target_id = n.id
|
|
186
|
+
LEFT JOIN nodes n_src ON n_src.id = e_in.source_id AND n_src.community_id IS NOT NULL
|
|
176
187
|
WHERE n.community_id IS NOT NULL
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
FROM edges e_out
|
|
180
|
-
JOIN nodes n_tgt ON n_tgt.id = e_out.target_id
|
|
181
|
-
WHERE e_out.source_id = n.id AND n_tgt.community_id != n.community_id
|
|
182
|
-
) +
|
|
183
|
-
(
|
|
184
|
-
SELECT COUNT(DISTINCT n_src.community_id)
|
|
185
|
-
FROM edges e_in
|
|
186
|
-
JOIN nodes n_src ON n_src.id = e_in.source_id
|
|
187
|
-
WHERE e_in.target_id = n.id AND n_src.community_id != n.community_id
|
|
188
|
-
) >= 2
|
|
188
|
+
GROUP BY n.id
|
|
189
|
+
HAVING cross_communities >= 2
|
|
189
190
|
LIMIT 100
|
|
190
|
-
`).all() as { id: string; name: string; file_path: string | null }[];
|
|
191
|
+
`).all() as { id: string; name: string; file_path: string | null; start_line: number | null }[];
|
|
191
192
|
|
|
192
193
|
for (const n of bridges) {
|
|
193
194
|
findings.push({
|
|
@@ -196,6 +197,7 @@ export function extractFindingsFromDb(
|
|
|
196
197
|
nodeId: n.id,
|
|
197
198
|
nodeName: n.name,
|
|
198
199
|
filePath: n.file_path,
|
|
200
|
+
startLine: n.start_line ?? null,
|
|
199
201
|
savedAt: new Date().toISOString(),
|
|
200
202
|
fingerprint: fingerprintFinding('bridge_node', n.file_path ?? '', n.id),
|
|
201
203
|
});
|
|
@@ -203,13 +205,13 @@ export function extractFindingsFromDb(
|
|
|
203
205
|
|
|
204
206
|
// Unreachable exports: exported nodes with no incoming edges
|
|
205
207
|
const unreachableExports = db.prepare(`
|
|
206
|
-
SELECT n.id, n.name, n.file_path
|
|
208
|
+
SELECT n.id, n.name, n.file_path, n.start_line
|
|
207
209
|
FROM nodes n
|
|
208
210
|
WHERE n.is_exported = 1
|
|
209
211
|
AND n.label IN ('Function', 'Class', 'Method', 'Interface', 'Variable')
|
|
210
212
|
AND NOT EXISTS (SELECT 1 FROM edges e WHERE e.target_id = n.id)
|
|
211
213
|
LIMIT 300
|
|
212
|
-
`).all() as { id: string; name: string; file_path: string | null }[];
|
|
214
|
+
`).all() as { id: string; name: string; file_path: string | null; start_line: number | null }[];
|
|
213
215
|
|
|
214
216
|
for (const n of unreachableExports) {
|
|
215
217
|
findings.push({
|
|
@@ -218,6 +220,7 @@ export function extractFindingsFromDb(
|
|
|
218
220
|
nodeId: n.id,
|
|
219
221
|
nodeName: n.name,
|
|
220
222
|
filePath: n.file_path,
|
|
223
|
+
startLine: n.start_line ?? null,
|
|
221
224
|
savedAt: new Date().toISOString(),
|
|
222
225
|
fingerprint: fingerprintFinding('unreachable_export', n.file_path ?? '', n.id),
|
|
223
226
|
});
|
|
@@ -226,12 +229,12 @@ export function extractFindingsFromDb(
|
|
|
226
229
|
// Ambiguous edges: edges with AMBIGUOUS confidence
|
|
227
230
|
const ambiguousEdges = db.prepare(`
|
|
228
231
|
SELECT DISTINCT e.id as edge_id, e.source_id, e.target_id,
|
|
229
|
-
n.name as src_name, n.file_path as src_file
|
|
232
|
+
n.name as src_name, n.file_path as src_file, n.start_line as src_line
|
|
230
233
|
FROM edges e
|
|
231
234
|
JOIN nodes n ON n.id = e.source_id
|
|
232
235
|
WHERE e.confidence = 'AMBIGUOUS'
|
|
233
236
|
LIMIT 200
|
|
234
|
-
`).all() as { edge_id: string; source_id: string; target_id: string; src_name: string; src_file: string | null }[];
|
|
237
|
+
`).all() as { edge_id: string; source_id: string; target_id: string; src_name: string; src_file: string | null; src_line: number | null }[];
|
|
235
238
|
|
|
236
239
|
for (const e of ambiguousEdges) {
|
|
237
240
|
findings.push({
|
|
@@ -240,6 +243,7 @@ export function extractFindingsFromDb(
|
|
|
240
243
|
nodeId: e.source_id,
|
|
241
244
|
nodeName: e.src_name,
|
|
242
245
|
filePath: e.src_file,
|
|
246
|
+
startLine: e.src_line ?? null,
|
|
243
247
|
savedAt: new Date().toISOString(),
|
|
244
248
|
fingerprint: fingerprintFinding('ambiguous_edge', e.source_id, e.target_id),
|
|
245
249
|
});
|
|
@@ -367,3 +371,53 @@ export function computeTrend(
|
|
|
367
371
|
|
|
368
372
|
return { metrics, overallDirection };
|
|
369
373
|
}
|
|
374
|
+
|
|
375
|
+
/** Format a list of ComparedFindings as structured text with file:line hints. */
|
|
376
|
+
export function formatComparedFindings(findings: ComparedFinding[], showAll = false): string {
|
|
377
|
+
if (findings.length === 0) return 'Baseline comparison: no findings.';
|
|
378
|
+
|
|
379
|
+
const newFindings = findings.filter(f => f.introduced);
|
|
380
|
+
const existing = findings.filter(f => !f.introduced);
|
|
381
|
+
|
|
382
|
+
const lines: string[] = [
|
|
383
|
+
`Baseline comparison: ${findings.length} total finding(s) — ${newFindings.length} new, ${existing.length} known.`,
|
|
384
|
+
'',
|
|
385
|
+
];
|
|
386
|
+
|
|
387
|
+
if (newFindings.length > 0) {
|
|
388
|
+
lines.push(`New findings (${newFindings.length}):`);
|
|
389
|
+
for (const f of newFindings) {
|
|
390
|
+
const ref = f.filePath
|
|
391
|
+
? f.startLine != null ? `${f.filePath}:${f.startLine}` : f.filePath
|
|
392
|
+
: f.nodeId;
|
|
393
|
+
lines.push(` [${f.type}] ${f.nodeName} ${ref}`);
|
|
394
|
+
}
|
|
395
|
+
lines.push('');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (showAll && existing.length > 0) {
|
|
399
|
+
lines.push(`Known findings (${existing.length}):`);
|
|
400
|
+
for (const f of existing) {
|
|
401
|
+
const ref = f.filePath
|
|
402
|
+
? f.startLine != null ? `${f.filePath}:${f.startLine}` : f.filePath
|
|
403
|
+
: f.nodeId;
|
|
404
|
+
lines.push(` [${f.type}] ${f.nodeName} ${ref}`);
|
|
405
|
+
}
|
|
406
|
+
lines.push('');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
return lines.join('\n').trimEnd();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** Format a TrendReport as structured text for LLM consumption. */
|
|
413
|
+
export function formatTrendReport(report: TrendReport): string {
|
|
414
|
+
const lines: string[] = [
|
|
415
|
+
`Graph trend: ${report.overallDirection}`,
|
|
416
|
+
'',
|
|
417
|
+
];
|
|
418
|
+
for (const m of report.metrics) {
|
|
419
|
+
const deltaStr = m.delta > 0 ? `+${m.delta}` : `${m.delta}`;
|
|
420
|
+
lines.push(` ${m.symbol} ${m.metric}: ${m.previous} → ${m.current} (${deltaStr}) [${m.direction}]`);
|
|
421
|
+
}
|
|
422
|
+
return lines.join('\n');
|
|
423
|
+
}
|
|
@@ -29,39 +29,29 @@ function baseName(filePath: string): string {
|
|
|
29
29
|
return filePath.replace(/^.*[\\/]/, '');
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Compute both Jaccard similarity and shared token count in a single pass.
|
|
34
|
+
* Avoids building the countA Map twice (previously done separately in
|
|
35
|
+
* jaccardSimilarity and sharedTokenCount).
|
|
36
|
+
*/
|
|
37
|
+
function computeSimilarityAndShared(a: string[], b: string[]): { similarity: number; shared: number } {
|
|
34
38
|
const countA = new Map<string, number>();
|
|
35
|
-
const countB = new Map<string, number>();
|
|
36
39
|
for (const t of a) countA.set(t, (countA.get(t) ?? 0) + 1);
|
|
37
|
-
for (const t of b) countB.set(t, (countB.get(t) ?? 0) + 1);
|
|
38
40
|
|
|
39
41
|
let intersection = 0;
|
|
40
|
-
for (const [token, ca] of countA) {
|
|
41
|
-
const cb = countB.get(token) ?? 0;
|
|
42
|
-
intersection += Math.min(ca, cb);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const union = a.length + b.length - intersection;
|
|
46
|
-
return union === 0 ? 1 : intersection / union;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/** Count tokens shared between two multisets. */
|
|
50
|
-
function sharedTokenCount(a: string[], b: string[]): number {
|
|
51
|
-
const countA = new Map<string, number>();
|
|
52
|
-
for (const t of a) countA.set(t, (countA.get(t) ?? 0) + 1);
|
|
53
|
-
|
|
54
|
-
let shared = 0;
|
|
55
42
|
const seen = new Map<string, number>();
|
|
56
43
|
for (const t of b) {
|
|
57
44
|
const limit = countA.get(t) ?? 0;
|
|
58
45
|
const used = seen.get(t) ?? 0;
|
|
59
46
|
if (used < limit) {
|
|
60
|
-
|
|
47
|
+
intersection++;
|
|
61
48
|
seen.set(t, used + 1);
|
|
62
49
|
}
|
|
63
50
|
}
|
|
64
|
-
|
|
51
|
+
|
|
52
|
+
const union = a.length + b.length - intersection;
|
|
53
|
+
const similarity = union === 0 ? 1 : intersection / union;
|
|
54
|
+
return { similarity, shared: intersection };
|
|
65
55
|
}
|
|
66
56
|
|
|
67
57
|
export function detectClones(
|
|
@@ -78,10 +68,12 @@ export function detectClones(
|
|
|
78
68
|
const pairs: ClonePair[] = [];
|
|
79
69
|
const involvedFiles = new Set<string>();
|
|
80
70
|
|
|
81
|
-
// Pre-compute tokens for each file
|
|
82
|
-
const tokenMap = new Map<string, string[]>();
|
|
71
|
+
// Pre-compute path tokens and name tokens for each file
|
|
72
|
+
const tokenMap = new Map<string, { pathTokens: string[]; nameTokens: string[] }>();
|
|
83
73
|
for (const row of rows) {
|
|
84
|
-
|
|
74
|
+
const pathTokens = tokenizePath(row.file_path);
|
|
75
|
+
const nameTokens = tokenizePath(baseName(row.file_path));
|
|
76
|
+
tokenMap.set(row.file_path, { pathTokens, nameTokens });
|
|
85
77
|
}
|
|
86
78
|
|
|
87
79
|
// Compare all pairs
|
|
@@ -89,22 +81,16 @@ export function detectClones(
|
|
|
89
81
|
for (let j = i + 1; j < rows.length; j++) {
|
|
90
82
|
const pathA = rows[i].file_path;
|
|
91
83
|
const pathB = rows[j].file_path;
|
|
92
|
-
const
|
|
93
|
-
const
|
|
84
|
+
const entryA = tokenMap.get(pathA)!;
|
|
85
|
+
const entryB = tokenMap.get(pathB)!;
|
|
94
86
|
|
|
95
|
-
const sim =
|
|
96
|
-
const shared = sharedTokenCount(tokensA, tokensB);
|
|
87
|
+
const { similarity: sim, shared } = computeSimilarityAndShared(entryA.pathTokens, entryB.pathTokens);
|
|
97
88
|
|
|
98
89
|
if (sim < minSimilarity || shared < minTokens) {
|
|
99
90
|
// Also check near-duplicate file names (same name, different dir)
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const nameTokensB = tokenizePath(nameB);
|
|
104
|
-
const nameSim = jaccardSimilarity(nameTokensA, nameTokensB);
|
|
105
|
-
const nameShared = sharedTokenCount(nameTokensA, nameTokensB);
|
|
106
|
-
|
|
107
|
-
if (nameSim >= minSimilarity && nameShared >= Math.min(minTokens, nameTokensA.length)) {
|
|
91
|
+
const { similarity: nameSim, shared: nameShared } = computeSimilarityAndShared(entryA.nameTokens, entryB.nameTokens);
|
|
92
|
+
|
|
93
|
+
if (nameSim >= minSimilarity && nameShared >= Math.min(minTokens, entryA.nameTokens.length)) {
|
|
108
94
|
// Same-name files in different directories → structural similarity
|
|
109
95
|
const pair: ClonePair = {
|
|
110
96
|
fileA: pathA,
|
|
@@ -30,7 +30,8 @@ export interface CloneFamily {
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
function fileSetKey(files: string[]): string {
|
|
33
|
-
|
|
33
|
+
// Avoid spread allocation: sort a copy directly
|
|
34
|
+
return files.slice().sort().join('|');
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
function generateSuggestions(family: Omit<CloneFamily, 'suggestions'>): RefactoringSuggestion[] {
|
|
@@ -46,10 +47,14 @@ function generateSuggestions(family: Omit<CloneFamily, 'suggestions'>): Refactor
|
|
|
46
47
|
});
|
|
47
48
|
} else {
|
|
48
49
|
// Check if all files are in the same directory → MergeDirectories
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
// Use Set directly to avoid spread+map intermediate array
|
|
51
|
+
const dirSet = new Set<string>();
|
|
52
|
+
for (const f of family.files) {
|
|
53
|
+
const slash = f.replace(/\\/g, '/');
|
|
54
|
+
const lastSlash = slash.lastIndexOf('/');
|
|
55
|
+
dirSet.add(lastSlash >= 0 ? slash.slice(0, lastSlash) : '.');
|
|
56
|
+
}
|
|
57
|
+
const dirs = [...dirSet];
|
|
53
58
|
if (dirs.length > 1 && family.files.length >= 3) {
|
|
54
59
|
suggestions.push({
|
|
55
60
|
kind: 'MergeDirectories',
|
|
@@ -81,12 +86,29 @@ export function groupIntoFamilies(groups: CloneGroup[]): CloneFamily[] {
|
|
|
81
86
|
familyMap.get(key)!.groups.push(group);
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
|
|
89
|
+
// Build output array without intermediate spread + map allocation
|
|
90
|
+
const result: CloneFamily[] = [];
|
|
91
|
+
for (const { files, groups } of familyMap.values()) {
|
|
85
92
|
const fileList = [...files].sort();
|
|
86
|
-
|
|
93
|
+
let totalDuplicatedLines = 0;
|
|
94
|
+
for (const g of groups) totalDuplicatedLines += g.duplicatedLines;
|
|
87
95
|
const base = { files: fileList, groups, totalDuplicatedLines };
|
|
88
|
-
|
|
89
|
-
}
|
|
96
|
+
result.push({ ...base, suggestions: generateSuggestions(base) });
|
|
97
|
+
}
|
|
98
|
+
return result.sort((a, b) => b.totalDuplicatedLines - a.totalDuplicatedLines);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Format clone families as structured text for LLM consumption. */
|
|
102
|
+
export function formatCloneFamilies(families: CloneFamily[]): string {
|
|
103
|
+
if (families.length === 0) return 'No clone families detected.';
|
|
104
|
+
const lines: string[] = [`Clone Families (${families.length} total):`];
|
|
105
|
+
for (const f of families.slice(0, 20)) {
|
|
106
|
+
lines.push(`\n ${f.totalDuplicatedLines} duplicated lines across ${f.files.length} files:`);
|
|
107
|
+
for (const file of f.files) lines.push(` ${file}`);
|
|
108
|
+
for (const s of f.suggestions) lines.push(` -> [${s.kind}] ${s.description}`);
|
|
109
|
+
}
|
|
110
|
+
if (families.length > 20) lines.push(`\n ... and ${families.length - 20} more families`);
|
|
111
|
+
return lines.join('\n');
|
|
90
112
|
}
|
|
91
113
|
|
|
92
114
|
export function cloneFamilySummary(families: CloneFamily[]): {
|
|
@@ -97,12 +119,14 @@ export function cloneFamilySummary(families: CloneFamily[]): {
|
|
|
97
119
|
const byKind: Record<RefactoringKind, number> = {
|
|
98
120
|
ExtractFunction: 0, ExtractModule: 0, MergeDirectories: 0,
|
|
99
121
|
};
|
|
122
|
+
let totalDuplicatedLines = 0;
|
|
100
123
|
for (const f of families) {
|
|
124
|
+
totalDuplicatedLines += f.totalDuplicatedLines;
|
|
101
125
|
for (const s of f.suggestions) byKind[s.kind]++;
|
|
102
126
|
}
|
|
103
127
|
return {
|
|
104
128
|
totalFamilies: families.length,
|
|
105
|
-
totalDuplicatedLines
|
|
129
|
+
totalDuplicatedLines,
|
|
106
130
|
byKind,
|
|
107
131
|
};
|
|
108
132
|
}
|
|
@@ -58,3 +58,57 @@ export function ownerLabel(co: CodeOwnersLike, relativePath: string): string {
|
|
|
58
58
|
}
|
|
59
59
|
return owners[0];
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
export interface OwnershipAggregate {
|
|
63
|
+
/** Paths that have no owner. */
|
|
64
|
+
unowned: string[];
|
|
65
|
+
/** Map from owner handle → list of owned file paths. */
|
|
66
|
+
byOwner: Map<string, string[]>;
|
|
67
|
+
/** Total files analyzed. */
|
|
68
|
+
totalFiles: number;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Aggregate ownership across a list of relative paths in a single pass.
|
|
73
|
+
* Each file is attributed to its primary owner (first in the owners list).
|
|
74
|
+
*/
|
|
75
|
+
export function aggregateOwnership(
|
|
76
|
+
co: CodeOwnersLike,
|
|
77
|
+
relativePaths: string[],
|
|
78
|
+
): OwnershipAggregate {
|
|
79
|
+
const unowned: string[] = [];
|
|
80
|
+
const byOwner = new Map<string, string[]>();
|
|
81
|
+
|
|
82
|
+
for (const path of relativePaths) {
|
|
83
|
+
const owners = co.ownersOf(path);
|
|
84
|
+
if (!owners || owners.length === 0) {
|
|
85
|
+
unowned.push(path);
|
|
86
|
+
} else {
|
|
87
|
+
const primary = owners[0];
|
|
88
|
+
let bucket = byOwner.get(primary);
|
|
89
|
+
if (!bucket) { bucket = []; byOwner.set(primary, bucket); }
|
|
90
|
+
bucket.push(path);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return { unowned, byOwner, totalFiles: relativePaths.length };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Format an OwnershipAggregate as structured text for LLM consumption.
|
|
99
|
+
*/
|
|
100
|
+
export function formatOwnershipReport(agg: OwnershipAggregate): string {
|
|
101
|
+
const lines: string[] = [
|
|
102
|
+
`Ownership report: ${agg.totalFiles} files, ${agg.byOwner.size} owner(s), ${agg.unowned.length} unowned`,
|
|
103
|
+
'',
|
|
104
|
+
];
|
|
105
|
+
// Sort owners by file count descending
|
|
106
|
+
const sorted = Array.from(agg.byOwner.entries()).sort((a, b) => b[1].length - a[1].length);
|
|
107
|
+
for (const [owner, files] of sorted) {
|
|
108
|
+
lines.push(` ${owner}: ${files.length} file(s)`);
|
|
109
|
+
}
|
|
110
|
+
if (agg.unowned.length > 0) {
|
|
111
|
+
lines.push(` ${UNOWNED_LABEL}: ${agg.unowned.length} file(s)`);
|
|
112
|
+
}
|
|
113
|
+
return lines.join('\n');
|
|
114
|
+
}
|
|
@@ -86,6 +86,12 @@ function pathMatchesPattern(filePath: string, pattern: string): boolean {
|
|
|
86
86
|
return norm === pat || norm.endsWith('/' + pat) || norm.startsWith(pat + '/');
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/** Single-call helper — avoids triple matchOwners invocations when callers need all three fields. */
|
|
90
|
+
export function singleFileOwnership(entries: CodeownersEntry[], filePath: string): { section: string; owners: string[]; ownerCount: number } {
|
|
91
|
+
const { owners, section } = matchOwners(entries, filePath);
|
|
92
|
+
return { section: section ?? NO_SECTION_LABEL, owners, ownerCount: owners.length };
|
|
93
|
+
}
|
|
94
|
+
|
|
89
95
|
export function ownerCountOf(entries: CodeownersEntry[], filePath: string): number {
|
|
90
96
|
return matchOwners(entries, filePath).owners.length;
|
|
91
97
|
}
|
|
@@ -102,3 +108,43 @@ export function sectionAndOwnersOf(entries: CodeownersEntry[], filePath: string)
|
|
|
102
108
|
export function hasSections(entries: CodeownersEntry[]): boolean {
|
|
103
109
|
return entries.some(e => e.section !== undefined);
|
|
104
110
|
}
|
|
111
|
+
|
|
112
|
+
export interface CodeownersGitlabReport {
|
|
113
|
+
totalEntries: number;
|
|
114
|
+
hasSections: boolean;
|
|
115
|
+
sectionNames: string[];
|
|
116
|
+
ownershipSummary: Array<{ file: string; section: string; owners: string[] }>;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Format a batch file-ownership report for LLM consumption.
|
|
121
|
+
* Uses singleFileOwnership() to avoid N*3 matchOwners scans.
|
|
122
|
+
*/
|
|
123
|
+
export function formatCodeownersGitlab(entries: CodeownersEntry[], filePaths: string[]): string {
|
|
124
|
+
if (entries.length === 0) return 'No CODEOWNERS entries found.';
|
|
125
|
+
|
|
126
|
+
const sections = new Set<string>();
|
|
127
|
+
const rows: Array<{ file: string; section: string; owners: string[] }> = [];
|
|
128
|
+
for (const fp of filePaths) {
|
|
129
|
+
const { section, owners } = singleFileOwnership(entries, fp);
|
|
130
|
+
sections.add(section);
|
|
131
|
+
rows.push({ file: fp, section, owners });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const unowned = rows.filter(r => r.owners.length === 0);
|
|
135
|
+
const lines: string[] = [
|
|
136
|
+
`CODEOWNERS (GitLab) — ${entries.length} entries, ${sections.size} section(s)`,
|
|
137
|
+
`Sections: ${[...sections].join(', ') || '(none)'}`,
|
|
138
|
+
`Files scanned: ${filePaths.length} Unowned: ${unowned.length}`,
|
|
139
|
+
'',
|
|
140
|
+
];
|
|
141
|
+
for (const r of rows) {
|
|
142
|
+
const ownerStr = r.owners.length > 0 ? r.owners.join(' ') : UNOWNED_LABEL;
|
|
143
|
+
lines.push(` ${r.file} [${r.section}] ${ownerStr}`);
|
|
144
|
+
}
|
|
145
|
+
if (unowned.length > 0) {
|
|
146
|
+
lines.push('', `Unowned files (${unowned.length}):`);
|
|
147
|
+
for (const r of unowned) lines.push(` ${r.file}`);
|
|
148
|
+
}
|
|
149
|
+
return lines.join('\n');
|
|
150
|
+
}
|
package/src/graph/codeowners.ts
CHANGED
|
@@ -98,6 +98,32 @@ export function parseCodeowners(repoRoot: string): CodeownersEntry[] {
|
|
|
98
98
|
return entries;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
// ── Compiled entry (regex precompiled once) ────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
export interface CompiledEntry {
|
|
104
|
+
owners: string[];
|
|
105
|
+
re: RegExp;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Compile an array of CodeownersEntry into CompiledEntry objects so that
|
|
110
|
+
* globToRegex() runs exactly once per pattern instead of once per file lookup.
|
|
111
|
+
*/
|
|
112
|
+
export function compileEntries(entries: CodeownersEntry[]): CompiledEntry[] {
|
|
113
|
+
return entries.map(e => ({ owners: e.owners, re: globToRegex(e.pattern) }));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Resolve owner for a single file path using precompiled regexes.
|
|
117
|
+
// Last matching entry wins (GitHub semantics).
|
|
118
|
+
export function resolveOwnerCompiled(compiled: CompiledEntry[], filePath: string): string[] {
|
|
119
|
+
const normalised = filePath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
120
|
+
let lastMatch: CompiledEntry | null = null;
|
|
121
|
+
for (const entry of compiled) {
|
|
122
|
+
if (entry.re.test(normalised)) lastMatch = entry;
|
|
123
|
+
}
|
|
124
|
+
return lastMatch ? lastMatch.owners : [];
|
|
125
|
+
}
|
|
126
|
+
|
|
101
127
|
// Resolve owner for a single file path (relative to repoRoot)
|
|
102
128
|
// Last matching entry wins
|
|
103
129
|
export function resolveOwner(entries: CodeownersEntry[], filePath: string): string[] {
|
|
@@ -121,6 +147,9 @@ export function annotateOwnership(
|
|
|
121
147
|
repoRoot: string,
|
|
122
148
|
): { annotated: number; unowned: number } {
|
|
123
149
|
const entries = parseCodeowners(repoRoot);
|
|
150
|
+
// Compile regexes once for all file nodes — avoids O(files * patterns) re-compilations.
|
|
151
|
+
const compiled = compileEntries(entries);
|
|
152
|
+
|
|
124
153
|
const fileNodes = db
|
|
125
154
|
.prepare(`SELECT id, file_path, properties FROM nodes WHERE label = 'File' AND file_path IS NOT NULL`)
|
|
126
155
|
.all() as { id: string; file_path: string; properties: string | null }[];
|
|
@@ -133,7 +162,7 @@ export function annotateOwnership(
|
|
|
133
162
|
const updateAll = db.transaction(() => {
|
|
134
163
|
for (const node of fileNodes) {
|
|
135
164
|
const relPath = relative(repoRoot, node.file_path).replace(/\\/g, '/');
|
|
136
|
-
const owners =
|
|
165
|
+
const owners = resolveOwnerCompiled(compiled, relPath);
|
|
137
166
|
const existing = node.properties ? JSON.parse(node.properties) : {};
|
|
138
167
|
const updated = { ...existing, codeowners: owners };
|
|
139
168
|
update.run(JSON.stringify(updated), node.id);
|
|
@@ -158,6 +187,8 @@ export function groupByOwner<T extends { filePath?: string | null }>(
|
|
|
158
187
|
findings: T[],
|
|
159
188
|
entries: CodeownersEntry[],
|
|
160
189
|
): Map<string, T[]> {
|
|
190
|
+
// Compile patterns once for all findings to avoid O(findings * patterns) regex constructions.
|
|
191
|
+
const compiled = compileEntries(entries);
|
|
161
192
|
const result = new Map<string, T[]>();
|
|
162
193
|
|
|
163
194
|
for (const finding of findings) {
|
|
@@ -168,7 +199,7 @@ export function groupByOwner<T extends { filePath?: string | null }>(
|
|
|
168
199
|
continue;
|
|
169
200
|
}
|
|
170
201
|
|
|
171
|
-
const owners =
|
|
202
|
+
const owners = resolveOwnerCompiled(compiled, finding.filePath);
|
|
172
203
|
if (owners.length === 0) {
|
|
173
204
|
const bucket = result.get('unowned') ?? [];
|
|
174
205
|
bucket.push(finding);
|