@monoes/monograph 1.2.0 → 1.2.2
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/.monomind/loops/mastermind-review-1782162423447.json +16 -0
- 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/cli/skill-gen.js +3 -0
- package/dist/src/cli/skill-gen.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 +2 -0
- 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/src/wiki/wiki-generator.d.ts.map +1 -1
- package/dist/src/wiki/wiki-generator.js +5 -1
- package/dist/src/wiki/wiki-generator.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/cli/skill-gen.ts +3 -0
- 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 +50 -4
- package/src/graph/normalize-id.ts +26 -2
- package/src/graph/pagerank.ts +70 -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 +97 -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/wiki/wiki-generator.ts +5 -1
- package/src/coverage/runtime.ts +0 -249
package/package.json
CHANGED
|
@@ -36,8 +36,8 @@ describe('PipelineRunner parallel execution', () => {
|
|
|
36
36
|
await runner.run(makeCtx());
|
|
37
37
|
const elapsed = Date.now() - t0;
|
|
38
38
|
|
|
39
|
-
// Sequential would take 110ms+; parallel
|
|
40
|
-
expect(elapsed).toBeLessThan(
|
|
39
|
+
// Sequential would take 110ms+; parallel finishes in ~65ms (200ms budget for CI/load variance)
|
|
40
|
+
expect(elapsed).toBeLessThan(200);
|
|
41
41
|
});
|
|
42
42
|
|
|
43
43
|
it('respects deps — c starts only after a and b finish', async () => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
1
|
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
2
|
import { join } from 'node:path';
|
|
4
3
|
import { execSync } from 'node:child_process';
|
|
@@ -107,7 +106,11 @@ function parseGitLog(stdout: string): ChurnCache {
|
|
|
107
106
|
files.get(path)!.push({ timestamp: currentTs, linesAdded: added, linesDeleted: deleted, authorIdx: currentAuthorIdx });
|
|
108
107
|
}
|
|
109
108
|
|
|
110
|
-
|
|
109
|
+
// Build files array without intermediate Array.from allocation
|
|
110
|
+
const filesArr: CachedFileChurn[] = [];
|
|
111
|
+
for (const [path, events] of files) filesArr.push({ path, events });
|
|
112
|
+
|
|
113
|
+
return { version: CHURN_CACHE_VERSION, lastIndexedSha: '', gitAfter: '', shallowClone: false, authorPool, files: filesArr };
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
function mergeChurnCaches(base: ChurnCache, delta: ChurnCache): ChurnCache {
|
|
@@ -118,25 +121,33 @@ function mergeChurnCaches(base: ChurnCache, delta: ChurnCache): ChurnCache {
|
|
|
118
121
|
if (!baseIndex.has(email)) { baseIndex.set(email, base.authorPool.length); base.authorPool.push(email); }
|
|
119
122
|
authorMap.set(i, baseIndex.get(email)!);
|
|
120
123
|
}
|
|
121
|
-
|
|
124
|
+
// Build fileMap without intermediate array from base.files.map()
|
|
125
|
+
const fileMap = new Map<string, CachedFileChurn>();
|
|
126
|
+
for (const f of base.files) fileMap.set(f.path, f);
|
|
122
127
|
for (const df of delta.files) {
|
|
123
128
|
const remapped = df.events.map(e => ({ ...e, authorIdx: e.authorIdx !== null ? (authorMap.get(e.authorIdx) ?? null) : null }));
|
|
124
129
|
if (fileMap.has(df.path)) fileMap.get(df.path)!.events.push(...remapped);
|
|
125
130
|
else fileMap.set(df.path, { path: df.path, events: remapped });
|
|
126
131
|
}
|
|
127
|
-
|
|
132
|
+
// Build result array without intermediate Array.from allocation
|
|
133
|
+
const mergedFiles: CachedFileChurn[] = [];
|
|
134
|
+
for (const f of fileMap.values()) mergedFiles.push(f);
|
|
135
|
+
return { ...base, files: mergedFiles };
|
|
128
136
|
}
|
|
129
137
|
|
|
138
|
+
// Precomputed constant: -Math.LN2 / HALF_LIFE_DAYS avoids per-call division in hot loop
|
|
139
|
+
const DECAY_FACTOR = -Math.LN2 / 90;
|
|
140
|
+
const SECS_PER_DAY = 86400;
|
|
141
|
+
|
|
130
142
|
function buildChurnResult(cache: ChurnCache): Omit<CachedChurnResult, 'cacheHit'> {
|
|
131
|
-
const HALF_LIFE_DAYS = 90;
|
|
132
|
-
const SECS_PER_DAY = 86400;
|
|
133
143
|
const now = Math.floor(Date.now() / 1000);
|
|
134
144
|
const files = new Map<string, { commits: number; linesAdded: number; linesDeleted: number; weightedCommits: number }>();
|
|
135
145
|
for (const { path, events } of cache.files) {
|
|
136
146
|
let commits = 0, linesAdded = 0, linesDeleted = 0, weightedCommits = 0;
|
|
137
147
|
for (const e of events) {
|
|
138
148
|
const ageDays = (now - e.timestamp) / SECS_PER_DAY;
|
|
139
|
-
|
|
149
|
+
// Math.exp is faster than Math.pow(0.5, x) for recency decay in hot loop
|
|
150
|
+
weightedCommits += Math.exp(DECAY_FACTOR * ageDays);
|
|
140
151
|
commits++; linesAdded += e.linesAdded; linesDeleted += e.linesDeleted;
|
|
141
152
|
}
|
|
142
153
|
files.set(path, { commits, linesAdded, linesDeleted, weightedCommits: Math.round(weightedCommits * 100) / 100 });
|
|
@@ -5,12 +5,20 @@ const COOLING_RATIO = 0.67;
|
|
|
5
5
|
|
|
6
6
|
export function computeChurnTrend(timestampsEpochSec: number[]): ChurnTrend {
|
|
7
7
|
if (timestampsEpochSec.length < 2) return 'stable';
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// Single pass: compute min, max, and partition counts simultaneously (O(N) vs O(4N) before)
|
|
9
|
+
let minTs = timestampsEpochSec[0];
|
|
10
|
+
let maxTs = timestampsEpochSec[0];
|
|
11
|
+
for (let i = 1; i < timestampsEpochSec.length; i++) {
|
|
12
|
+
const ts = timestampsEpochSec[i];
|
|
13
|
+
if (ts < minTs) minTs = ts;
|
|
14
|
+
if (ts > maxTs) maxTs = ts;
|
|
15
|
+
}
|
|
10
16
|
if (maxTs === minTs) return 'stable';
|
|
11
17
|
const midpoint = minTs + (maxTs - minTs) / 2;
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
let recent = 0, older = 0;
|
|
19
|
+
for (const ts of timestampsEpochSec) {
|
|
20
|
+
if (ts > midpoint) recent++; else older++;
|
|
21
|
+
}
|
|
14
22
|
if (older < 1) return 'stable';
|
|
15
23
|
const ratio = recent / older;
|
|
16
24
|
if (ratio > ACCELERATING_RATIO) return 'accelerating';
|
|
@@ -23,6 +31,27 @@ export function churnTrendLabel(trend: ChurnTrend): string {
|
|
|
23
31
|
}
|
|
24
32
|
|
|
25
33
|
export function churnTrendFromFileSeries(fileTimestamps: number[][]): ChurnTrend {
|
|
26
|
-
|
|
27
|
-
|
|
34
|
+
// Avoid allocating an intermediate flat array — inline the min/max/count logic
|
|
35
|
+
if (fileTimestamps.length === 0) return 'stable';
|
|
36
|
+
let minTs = Infinity, maxTs = -Infinity, total = 0;
|
|
37
|
+
for (const series of fileTimestamps) {
|
|
38
|
+
for (const ts of series) {
|
|
39
|
+
if (ts < minTs) minTs = ts;
|
|
40
|
+
if (ts > maxTs) maxTs = ts;
|
|
41
|
+
total++;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (total < 2 || maxTs === minTs) return 'stable';
|
|
45
|
+
const midpoint = minTs + (maxTs - minTs) / 2;
|
|
46
|
+
let recent = 0, older = 0;
|
|
47
|
+
for (const series of fileTimestamps) {
|
|
48
|
+
for (const ts of series) {
|
|
49
|
+
if (ts > midpoint) recent++; else older++;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (older < 1) return 'stable';
|
|
53
|
+
const ratio = recent / older;
|
|
54
|
+
if (ratio > ACCELERATING_RATIO) return 'accelerating';
|
|
55
|
+
if (ratio < COOLING_RATIO) return 'cooling';
|
|
56
|
+
return 'stable';
|
|
28
57
|
}
|
|
@@ -30,24 +30,18 @@ function buildAdjacency(edges: Edge[]): Map<string, Set<string>> {
|
|
|
30
30
|
* a(i) = mean distance to other nodes in same community
|
|
31
31
|
* b(i) = min mean distance to any other community
|
|
32
32
|
* silhouette(i) = (b(i) - a(i)) / max(a(i), b(i))
|
|
33
|
+
*
|
|
34
|
+
* @param communityMembers - precomputed communityId → nodeId[] map (excludes self)
|
|
33
35
|
*/
|
|
34
36
|
function nodeSilhouette(
|
|
35
37
|
nodeId: string,
|
|
36
38
|
community: number,
|
|
37
|
-
|
|
39
|
+
communityMembers: Map<number, string[]>,
|
|
38
40
|
adj: Map<string, Set<string>>,
|
|
39
41
|
): number {
|
|
40
42
|
const neighbors = adj.get(nodeId) ?? new Set<string>();
|
|
41
43
|
|
|
42
|
-
|
|
43
|
-
const communityMembers = new Map<number, string[]>();
|
|
44
|
-
for (const [nid, cid] of memberships) {
|
|
45
|
-
if (nid === nodeId) continue;
|
|
46
|
-
if (!communityMembers.has(cid)) communityMembers.set(cid, []);
|
|
47
|
-
communityMembers.get(cid)!.push(nid);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const sameMembers = communityMembers.get(community) ?? [];
|
|
44
|
+
const sameMembers = (communityMembers.get(community) ?? []).filter(n => n !== nodeId);
|
|
51
45
|
if (sameMembers.length === 0) return 0; // singleton community
|
|
52
46
|
|
|
53
47
|
// a(i): mean distance to same community (1 = not neighbor, 0 = neighbor)
|
|
@@ -74,6 +68,9 @@ function nodeSilhouette(
|
|
|
74
68
|
/**
|
|
75
69
|
* Compute the average silhouette score for the partitioning.
|
|
76
70
|
* Returns a value in [-1, 1] where higher is better.
|
|
71
|
+
*
|
|
72
|
+
* Precomputes the communityMembers map once (O(N)) before the per-node loop,
|
|
73
|
+
* reducing overall complexity from O(N²) to O(N + K*N) where K = community count.
|
|
77
74
|
*/
|
|
78
75
|
export function silhouetteScore(
|
|
79
76
|
memberships: Map<string, number>,
|
|
@@ -81,9 +78,21 @@ export function silhouetteScore(
|
|
|
81
78
|
): number {
|
|
82
79
|
if (memberships.size === 0) return 0;
|
|
83
80
|
const adj = buildAdjacency(edges);
|
|
81
|
+
|
|
82
|
+
// Precompute communityId → nodeId[] once to avoid O(N) re-scan per node
|
|
83
|
+
const communityMembers = new Map<number, string[]>();
|
|
84
|
+
for (const [nid, cid] of memberships) {
|
|
85
|
+
let members = communityMembers.get(cid);
|
|
86
|
+
if (!members) {
|
|
87
|
+
members = [];
|
|
88
|
+
communityMembers.set(cid, members);
|
|
89
|
+
}
|
|
90
|
+
members.push(nid);
|
|
91
|
+
}
|
|
92
|
+
|
|
84
93
|
let total = 0;
|
|
85
94
|
for (const [nodeId, community] of memberships) {
|
|
86
|
-
total += nodeSilhouette(nodeId, community,
|
|
95
|
+
total += nodeSilhouette(nodeId, community, communityMembers, adj);
|
|
87
96
|
}
|
|
88
97
|
return total / memberships.size;
|
|
89
98
|
}
|
|
@@ -12,6 +12,7 @@ export interface UntestedExport {
|
|
|
12
12
|
nodeId: string;
|
|
13
13
|
name: string;
|
|
14
14
|
filePath: string | null;
|
|
15
|
+
startLine: number | null;
|
|
15
16
|
exportType: string; // label
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -37,6 +38,7 @@ interface ExportRow {
|
|
|
37
38
|
id: string;
|
|
38
39
|
name: string;
|
|
39
40
|
file_path: string | null;
|
|
41
|
+
start_line: number | null;
|
|
40
42
|
label: string;
|
|
41
43
|
}
|
|
42
44
|
|
|
@@ -128,7 +130,7 @@ export function computeCoverageGaps(db: MonographDb): CoverageGapsResult {
|
|
|
128
130
|
|
|
129
131
|
// All exported symbols
|
|
130
132
|
const allExportedRows = db.prepare(
|
|
131
|
-
`SELECT n.id, n.name, n.file_path, n.label
|
|
133
|
+
`SELECT n.id, n.name, n.file_path, n.start_line, n.label
|
|
132
134
|
FROM nodes n
|
|
133
135
|
WHERE n.is_exported = 1
|
|
134
136
|
AND n.label IN ('Function','Class','Method','Interface','Const','TypeAlias','Enum','Variable')`
|
|
@@ -168,6 +170,7 @@ export function computeCoverageGaps(db: MonographDb): CoverageGapsResult {
|
|
|
168
170
|
nodeId: exp.id,
|
|
169
171
|
name: exp.name,
|
|
170
172
|
filePath: exp.file_path,
|
|
173
|
+
startLine: exp.start_line ?? null,
|
|
171
174
|
exportType: exp.label,
|
|
172
175
|
});
|
|
173
176
|
}
|
|
@@ -201,3 +204,43 @@ export function computeCoverageGaps(db: MonographDb): CoverageGapsResult {
|
|
|
201
204
|
summary,
|
|
202
205
|
};
|
|
203
206
|
}
|
|
207
|
+
|
|
208
|
+
/** Format CoverageGapsResult as structured text with file:line hints for LLM navigation. */
|
|
209
|
+
export function formatCoverageGaps(result: CoverageGapsResult, topN = 20): string {
|
|
210
|
+
const lines: string[] = [result.summary, ''];
|
|
211
|
+
|
|
212
|
+
if (result.untestedFiles.length > 0) {
|
|
213
|
+
// Sort untested files by inDegree descending — highest-impact first
|
|
214
|
+
const sorted = [...result.untestedFiles].sort((a, b) => b.inDegree - a.inDegree);
|
|
215
|
+
const shown = sorted.slice(0, topN);
|
|
216
|
+
lines.push(`Top untested files by import-count (${shown.length}/${result.untestedFiles.length} shown):`);
|
|
217
|
+
for (const f of shown) {
|
|
218
|
+
lines.push(` ${f.filePath} in-degree:${f.inDegree}`);
|
|
219
|
+
}
|
|
220
|
+
lines.push('');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (result.untestedExports.length > 0) {
|
|
224
|
+
// Group untested exports by file
|
|
225
|
+
const byFile = new Map<string, UntestedExport[]>();
|
|
226
|
+
for (const exp of result.untestedExports) {
|
|
227
|
+
const key = exp.filePath ?? '(unknown)';
|
|
228
|
+
let group = byFile.get(key);
|
|
229
|
+
if (!group) { group = []; byFile.set(key, group); }
|
|
230
|
+
group.push(exp);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
lines.push(`Untested exported symbols (${result.untestedExports.length}) grouped by file:`);
|
|
234
|
+
for (const [filePath, exports] of byFile) {
|
|
235
|
+
lines.push(` ${filePath}`);
|
|
236
|
+
for (const exp of exports) {
|
|
237
|
+
const ref = exp.startLine != null ? `${filePath}:${exp.startLine}` : filePath;
|
|
238
|
+
lines.push(` ${exp.exportType} ${exp.name} (${ref})`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
lines.push('');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
lines.push('Fix: add tests importing the untested files, or mark files as non-runtime if they are build artifacts.');
|
|
245
|
+
return lines.join('\n').trimEnd();
|
|
246
|
+
}
|
|
@@ -131,3 +131,37 @@ export function classifyDependencies(db: MonographDb): DepClassificationResult {
|
|
|
131
131
|
valueOnlyCount,
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
|
+
|
|
135
|
+
/** Format DepClassificationResult as structured text for LLM navigation and actionable guidance. */
|
|
136
|
+
export function formatDepClassification(result: DepClassificationResult): string {
|
|
137
|
+
if (result.packages.length === 0) {
|
|
138
|
+
return 'Dependency classification: no external packages detected.';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const lines: string[] = [
|
|
142
|
+
`Dependency classification: ${result.packages.length} external package(s).`,
|
|
143
|
+
` type-only: ${result.typeOnlyCount} mixed: ${result.mixedCount} value-only: ${result.valueOnlyCount}`,
|
|
144
|
+
'',
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
const groups: Array<[PackageDepClassification['recommendation'], string, string]> = [
|
|
148
|
+
['type-only', 'Type-only (move to devDependencies or use @types)', 'Move to devDependencies — not needed at runtime.'],
|
|
149
|
+
['unused', 'Unused', 'Remove from dependencies — no imports detected.'],
|
|
150
|
+
['move-to-devdeps', 'Move to devDependencies', 'Only used in non-production code paths.'],
|
|
151
|
+
['keep-as-dep', 'Keep as dependency', 'Used as runtime value imports — keep in dependencies.'],
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
for (const [rec, heading, advice] of groups) {
|
|
155
|
+
const pkgs = result.packages.filter(p => p.recommendation === rec);
|
|
156
|
+
if (pkgs.length === 0) continue;
|
|
157
|
+
lines.push(`${heading} (${pkgs.length}):`);
|
|
158
|
+
for (const p of pkgs) {
|
|
159
|
+
const typeNote = p.typeOnlyImportCount > 0 ? ` (${p.typeOnlyImportCount} type-only)` : '';
|
|
160
|
+
lines.push(` ${p.packageName} imports:${p.importCount}${typeNote}`);
|
|
161
|
+
}
|
|
162
|
+
lines.push(` → ${advice}`);
|
|
163
|
+
lines.push('');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return lines.join('\n').trimEnd();
|
|
167
|
+
}
|
|
@@ -119,3 +119,28 @@ export function dependencyHealth(input: DependencyHealthInput): DependencyHealth
|
|
|
119
119
|
details: { cyclePenalty, deadCodeRatio, fanSkew, godNodeConcentration },
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Format dependency health results as structured text for LLM consumption.
|
|
125
|
+
*/
|
|
126
|
+
export function formatDependencyHealth(result: DependencyHealthResult): string {
|
|
127
|
+
const { score, details } = result;
|
|
128
|
+
const pct = (v: number) => `${(v * 100).toFixed(1)}%`;
|
|
129
|
+
const grade = score >= 0.9 ? 'Excellent' : score >= 0.75 ? 'Good' : score >= 0.5 ? 'Fair' : 'Poor';
|
|
130
|
+
const lines: string[] = [
|
|
131
|
+
`Dependency health score: ${pct(score)} (${grade})`,
|
|
132
|
+
` Cycle penalty : ${pct(details.cyclePenalty)} (fraction of nodes in cycles; weight 35%)`,
|
|
133
|
+
` Dead-code ratio : ${pct(details.deadCodeRatio)} (fraction of unreachable nodes; weight 25%)`,
|
|
134
|
+
` Fan-out skew : ${pct(details.fanSkew)} (out-degree coefficient of variation / 4; weight 20%)`,
|
|
135
|
+
` God-node density : ${pct(details.godNodeConcentration)} (max in-degree / total edges; weight 20%)`,
|
|
136
|
+
];
|
|
137
|
+
const advice: string[] = [];
|
|
138
|
+
if (details.cyclePenalty > 0.1) advice.push('reduce circular imports (cyclePenalty high)');
|
|
139
|
+
if (details.deadCodeRatio > 0.1) advice.push('remove unreachable exports (deadCodeRatio high)');
|
|
140
|
+
if (details.fanSkew > 0.25) advice.push('break up high-fan-out modules (fanSkew high)');
|
|
141
|
+
if (details.godNodeConcentration > 0.1) advice.push('distribute hub dependencies (godNodeConcentration high)');
|
|
142
|
+
if (advice.length > 0) {
|
|
143
|
+
lines.push(' Recommendations: ' + advice.join('; '));
|
|
144
|
+
}
|
|
145
|
+
return lines.join('\n');
|
|
146
|
+
}
|
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
import type { MonographDb } from '../storage/db.js';
|
|
2
2
|
|
|
3
|
+
export interface DuplicateExportLocation {
|
|
4
|
+
nodeId: string;
|
|
5
|
+
filePath: string | null;
|
|
6
|
+
startLine: number | null;
|
|
7
|
+
label: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
export interface DuplicateExportGroup {
|
|
4
11
|
exportName: string;
|
|
5
|
-
locations:
|
|
12
|
+
locations: DuplicateExportLocation[];
|
|
6
13
|
count: number;
|
|
7
14
|
}
|
|
8
15
|
|
|
@@ -16,6 +23,7 @@ interface ExportedRow {
|
|
|
16
23
|
id: string;
|
|
17
24
|
name: string;
|
|
18
25
|
file_path: string | null;
|
|
26
|
+
start_line: number | null;
|
|
19
27
|
label: string;
|
|
20
28
|
}
|
|
21
29
|
|
|
@@ -24,14 +32,14 @@ const GENERIC_NAMES = new Set(['default', 'index', 'module']);
|
|
|
24
32
|
|
|
25
33
|
export function detectDuplicateExports(db: MonographDb): DuplicateExportsResult {
|
|
26
34
|
const rows = db.prepare(
|
|
27
|
-
`SELECT id, name, file_path, label
|
|
35
|
+
`SELECT id, name, file_path, start_line, label
|
|
28
36
|
FROM nodes
|
|
29
37
|
WHERE is_exported = 1
|
|
30
38
|
AND label IN ('Function','Class','Method','Interface','Const','TypeAlias','Enum','Variable')`
|
|
31
39
|
).all() as ExportedRow[];
|
|
32
40
|
|
|
33
41
|
// Group by normalized name
|
|
34
|
-
const groups = new Map<string,
|
|
42
|
+
const groups = new Map<string, DuplicateExportLocation[]>();
|
|
35
43
|
|
|
36
44
|
for (const row of rows) {
|
|
37
45
|
const normalized = row.name.toLowerCase().trim();
|
|
@@ -42,7 +50,7 @@ export function detectDuplicateExports(db: MonographDb): DuplicateExportsResult
|
|
|
42
50
|
list = [];
|
|
43
51
|
groups.set(normalized, list);
|
|
44
52
|
}
|
|
45
|
-
list.push({ nodeId: row.id, filePath: row.file_path, label: row.label });
|
|
53
|
+
list.push({ nodeId: row.id, filePath: row.file_path, startLine: row.start_line ?? null, label: row.label });
|
|
46
54
|
}
|
|
47
55
|
|
|
48
56
|
// Filter to duplicates only (count > 1)
|
|
@@ -72,3 +80,31 @@ export function detectDuplicateExports(db: MonographDb): DuplicateExportsResult
|
|
|
72
80
|
affectedFiles: affectedFileSet.size,
|
|
73
81
|
};
|
|
74
82
|
}
|
|
83
|
+
|
|
84
|
+
/** Format DuplicateExportsResult as structured text with file:line hints for LLM navigation. */
|
|
85
|
+
export function formatDuplicateExports(result: DuplicateExportsResult): string {
|
|
86
|
+
if (result.totalDuplicates === 0) {
|
|
87
|
+
return 'Duplicate exports: none detected.';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const lines: string[] = [
|
|
91
|
+
`Duplicate exports: ${result.totalDuplicates} name(s) exported from multiple files (${result.affectedFiles} file(s) affected).`,
|
|
92
|
+
'',
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
for (const group of result.groups) {
|
|
96
|
+
lines.push(` ${group.exportName} (${group.count} locations):`);
|
|
97
|
+
for (const loc of group.locations) {
|
|
98
|
+
const ref = loc.filePath
|
|
99
|
+
? loc.startLine != null
|
|
100
|
+
? `${loc.filePath}:${loc.startLine}`
|
|
101
|
+
: loc.filePath
|
|
102
|
+
: '(unknown)';
|
|
103
|
+
lines.push(` ${loc.label} ${ref}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
lines.push('');
|
|
108
|
+
lines.push('Fix: consolidate duplicate exports into a single canonical location or rename to avoid conflicts.');
|
|
109
|
+
return lines.join('\n').trimEnd();
|
|
110
|
+
}
|
|
@@ -145,12 +145,25 @@ export function crossReferenceWithDeadCode(
|
|
|
145
145
|
flags: FeatureFlag[],
|
|
146
146
|
deadExports: Array<{ filePath: string; name: string; line: number }>,
|
|
147
147
|
): FeatureFlag[] {
|
|
148
|
+
// Pregroup dead exports by filePath to avoid O(F*D) nested scan
|
|
149
|
+
const deadByFile = new Map<string, Array<{ name: string; line: number }>>();
|
|
150
|
+
for (const e of deadExports) {
|
|
151
|
+
let arr = deadByFile.get(e.filePath);
|
|
152
|
+
if (!arr) {
|
|
153
|
+
arr = [];
|
|
154
|
+
deadByFile.set(e.filePath, arr);
|
|
155
|
+
}
|
|
156
|
+
arr.push({ name: e.name, line: e.line });
|
|
157
|
+
}
|
|
158
|
+
|
|
148
159
|
return flags.map(flag => {
|
|
149
160
|
if (flag.guardLineStart == null || flag.guardLineEnd == null) return flag;
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
161
|
+
const fileDeads = deadByFile.get(flag.filePath);
|
|
162
|
+
if (!fileDeads) return flag;
|
|
163
|
+
const start = flag.guardLineStart ?? 0;
|
|
164
|
+
const end = flag.guardLineEnd ?? Infinity;
|
|
165
|
+
const guarded = fileDeads
|
|
166
|
+
.filter(e => e.line >= start && e.line <= end)
|
|
154
167
|
.map(e => e.name);
|
|
155
168
|
return guarded.length > 0 ? { ...flag, guardedDeadExports: guarded } : flag;
|
|
156
169
|
});
|
|
@@ -179,20 +192,27 @@ export interface FlagsSummary {
|
|
|
179
192
|
}
|
|
180
193
|
|
|
181
194
|
export function summarizeFlags(flags: FeatureFlag[]): FlagsSummary {
|
|
195
|
+
// Single pass over flags instead of 7 separate filter/map passes
|
|
196
|
+
const byKind: Record<FlagKind, number> = { EnvironmentVariable: 0, SdkCall: 0, ConfigObject: 0 };
|
|
197
|
+
const byConfidence: Record<FlagConfidence, number> = { High: 0, Medium: 0, Low: 0 };
|
|
198
|
+
const flagNames = new Set<string>();
|
|
199
|
+
const filePaths = new Set<string>();
|
|
200
|
+
let deadCodeOverlaps = 0;
|
|
201
|
+
|
|
202
|
+
for (const f of flags) {
|
|
203
|
+
byKind[f.kind]++;
|
|
204
|
+
byConfidence[f.confidence]++;
|
|
205
|
+
flagNames.add(f.flagName);
|
|
206
|
+
filePaths.add(f.filePath);
|
|
207
|
+
if ((f.guardedDeadExports?.length ?? 0) > 0) deadCodeOverlaps++;
|
|
208
|
+
}
|
|
209
|
+
|
|
182
210
|
return {
|
|
183
211
|
totalFlags: flags.length,
|
|
184
|
-
byKind
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
byConfidence: {
|
|
190
|
-
High: flags.filter(f => f.confidence === 'High').length,
|
|
191
|
-
Medium: flags.filter(f => f.confidence === 'Medium').length,
|
|
192
|
-
Low: flags.filter(f => f.confidence === 'Low').length,
|
|
193
|
-
},
|
|
194
|
-
uniqueFlagNames: new Set(flags.map(f => f.flagName)).size,
|
|
195
|
-
filesWithFlags: new Set(flags.map(f => f.filePath)).size,
|
|
196
|
-
deadCodeOverlaps: flags.filter(f => (f.guardedDeadExports?.length ?? 0) > 0).length,
|
|
212
|
+
byKind,
|
|
213
|
+
byConfidence,
|
|
214
|
+
uniqueFlagNames: flagNames.size,
|
|
215
|
+
filesWithFlags: filePaths.size,
|
|
216
|
+
deadCodeOverlaps,
|
|
197
217
|
};
|
|
198
218
|
}
|
|
@@ -17,6 +17,8 @@ export interface SimilarNode {
|
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
19
|
* Build an undirected neighbor map from an edge list.
|
|
20
|
+
* Callers that invoke findSimilarNodes for multiple nodes on the same edge set
|
|
21
|
+
* should build once and pass the map directly to findSimilarNodesFromMap.
|
|
20
22
|
*/
|
|
21
23
|
export function buildNeighborMap(
|
|
22
24
|
edges: SimilarityEdge[],
|
|
@@ -46,17 +48,19 @@ export function jaccardSimilarity(a: Set<string>, b: Set<string>): number {
|
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
|
-
* Find the k most similar nodes to `nodeId`
|
|
51
|
+
* Find the k most similar nodes to `nodeId` using a pre-built neighbor map.
|
|
52
|
+
*
|
|
53
|
+
* Use this variant when querying multiple nodes against the same edge set —
|
|
54
|
+
* build the map once with `buildNeighborMap` and reuse it across calls.
|
|
50
55
|
*
|
|
51
56
|
* Only nodes with score > 0 are returned (no shared neighbors → excluded).
|
|
52
57
|
* Results are sorted descending by score.
|
|
53
58
|
*/
|
|
54
|
-
export function
|
|
59
|
+
export function findSimilarNodesFromMap(
|
|
55
60
|
nodeId: string,
|
|
56
|
-
|
|
61
|
+
neighborMap: Map<string, Set<string>>,
|
|
57
62
|
k: number,
|
|
58
63
|
): SimilarNode[] {
|
|
59
|
-
const neighborMap = buildNeighborMap(edges);
|
|
60
64
|
const targetNeighbors = neighborMap.get(nodeId);
|
|
61
65
|
if (!targetNeighbors || targetNeighbors.size === 0) return [];
|
|
62
66
|
|
|
@@ -72,3 +76,40 @@ export function findSimilarNodes(
|
|
|
72
76
|
results.sort((a, b) => b.score - a.score);
|
|
73
77
|
return results.slice(0, k);
|
|
74
78
|
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Find the k most similar nodes to `nodeId` by Jaccard neighbor overlap.
|
|
82
|
+
*
|
|
83
|
+
* Builds the neighbor map from `edges` on each call. For repeated queries
|
|
84
|
+
* over the same edge set, prefer `buildNeighborMap` + `findSimilarNodesFromMap`.
|
|
85
|
+
*
|
|
86
|
+
* Only nodes with score > 0 are returned (no shared neighbors → excluded).
|
|
87
|
+
* Results are sorted descending by score.
|
|
88
|
+
*/
|
|
89
|
+
export function findSimilarNodes(
|
|
90
|
+
nodeId: string,
|
|
91
|
+
edges: SimilarityEdge[],
|
|
92
|
+
k: number,
|
|
93
|
+
): SimilarNode[] {
|
|
94
|
+
return findSimilarNodesFromMap(nodeId, buildNeighborMap(edges), k);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Format similar-node results as structured text for LLM consumption.
|
|
99
|
+
*/
|
|
100
|
+
export function formatSimilarNodes(
|
|
101
|
+
nodeId: string,
|
|
102
|
+
results: SimilarNode[],
|
|
103
|
+
nodeLabels?: Map<string, string>,
|
|
104
|
+
): string {
|
|
105
|
+
if (results.length === 0) {
|
|
106
|
+
return `No structurally similar nodes found for: ${nodeId}`;
|
|
107
|
+
}
|
|
108
|
+
const lines: string[] = [`Similar nodes to ${nodeId} (Jaccard neighbor overlap):`];
|
|
109
|
+
for (const { nodeId: id, score } of results) {
|
|
110
|
+
const label = nodeLabels?.get(id);
|
|
111
|
+
const suffix = label ? ` [${label}]` : '';
|
|
112
|
+
lines.push(` ${(score * 100).toFixed(1)}% ${id}${suffix}`);
|
|
113
|
+
}
|
|
114
|
+
return lines.join('\n');
|
|
115
|
+
}
|
|
@@ -4,9 +4,11 @@ export interface PrivateTypeLeak {
|
|
|
4
4
|
exportNodeId: string;
|
|
5
5
|
exportName: string;
|
|
6
6
|
exportFilePath: string | null;
|
|
7
|
+
exportStartLine: number | null;
|
|
7
8
|
leakedTypeNodeId: string;
|
|
8
9
|
leakedTypeName: string;
|
|
9
10
|
leakedTypeFilePath: string | null;
|
|
11
|
+
leakedTypeStartLine: number | null;
|
|
10
12
|
reason: string;
|
|
11
13
|
}
|
|
12
14
|
|
|
@@ -21,14 +23,17 @@ interface LeakRow {
|
|
|
21
23
|
target_id: string;
|
|
22
24
|
src_name: string;
|
|
23
25
|
src_path: string | null;
|
|
26
|
+
src_line: number | null;
|
|
24
27
|
tgt_name: string;
|
|
25
28
|
tgt_path: string | null;
|
|
29
|
+
tgt_line: number | null;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
export function detectPrivateTypeLeaks(db: MonographDb): PrivateTypeLeaksResult {
|
|
29
33
|
const rows = db.prepare(`
|
|
30
|
-
SELECT e.source_id, e.target_id,
|
|
31
|
-
|
|
34
|
+
SELECT e.source_id, e.target_id,
|
|
35
|
+
n1.name as src_name, n1.file_path as src_path, n1.start_line as src_line,
|
|
36
|
+
n2.name as tgt_name, n2.file_path as tgt_path, n2.start_line as tgt_line
|
|
32
37
|
FROM edges e
|
|
33
38
|
JOIN nodes n1 ON n1.id = e.source_id
|
|
34
39
|
JOIN nodes n2 ON n2.id = e.target_id
|
|
@@ -57,9 +62,11 @@ export function detectPrivateTypeLeaks(db: MonographDb): PrivateTypeLeaksResult
|
|
|
57
62
|
exportNodeId: row.source_id,
|
|
58
63
|
exportName: row.src_name,
|
|
59
64
|
exportFilePath: row.src_path,
|
|
65
|
+
exportStartLine: row.src_line ?? null,
|
|
60
66
|
leakedTypeNodeId: row.target_id,
|
|
61
67
|
leakedTypeName: row.tgt_name,
|
|
62
68
|
leakedTypeFilePath: row.tgt_path,
|
|
69
|
+
leakedTypeStartLine: row.tgt_line ?? null,
|
|
63
70
|
reason: `Exported symbol ${row.src_name} references non-exported ${row.tgt_name} from another community`,
|
|
64
71
|
});
|
|
65
72
|
}
|
|
@@ -70,3 +77,43 @@ export function detectPrivateTypeLeaks(db: MonographDb): PrivateTypeLeaksResult
|
|
|
70
77
|
affectedExports: affectedExportIds.size,
|
|
71
78
|
};
|
|
72
79
|
}
|
|
80
|
+
|
|
81
|
+
/** Format PrivateTypeLeaksResult as structured text with file:line hints for LLM navigation. */
|
|
82
|
+
export function formatPrivateTypeLeaks(result: PrivateTypeLeaksResult): string {
|
|
83
|
+
if (result.totalLeaks === 0) {
|
|
84
|
+
return 'Private type leaks: none detected.';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const lines: string[] = [
|
|
88
|
+
`Private type leaks: ${result.totalLeaks} leak(s) across ${result.affectedExports} exported symbol(s).`,
|
|
89
|
+
'',
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
// Group leaks by exporting file for compact display
|
|
93
|
+
const byFile = new Map<string, PrivateTypeLeak[]>();
|
|
94
|
+
for (const leak of result.leaks) {
|
|
95
|
+
const key = leak.exportFilePath ?? '(unknown)';
|
|
96
|
+
let group = byFile.get(key);
|
|
97
|
+
if (!group) { group = []; byFile.set(key, group); }
|
|
98
|
+
group.push(leak);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (const [filePath, fileLeaks] of byFile) {
|
|
102
|
+
lines.push(`File: ${filePath}`);
|
|
103
|
+
for (const leak of fileLeaks) {
|
|
104
|
+
const exportRef = leak.exportStartLine != null
|
|
105
|
+
? `${filePath}:${leak.exportStartLine}`
|
|
106
|
+
: filePath;
|
|
107
|
+
const leakRef = leak.leakedTypeFilePath
|
|
108
|
+
? leak.leakedTypeStartLine != null
|
|
109
|
+
? `${leak.leakedTypeFilePath}:${leak.leakedTypeStartLine}`
|
|
110
|
+
: leak.leakedTypeFilePath
|
|
111
|
+
: '(unknown)';
|
|
112
|
+
lines.push(` ${leak.exportName} (${exportRef}) → leaks ${leak.leakedTypeName} (${leakRef})`);
|
|
113
|
+
}
|
|
114
|
+
lines.push('');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
lines.push(`Fix: make leaked types public, move them to a shared module, or restructure community boundaries.`);
|
|
118
|
+
return lines.join('\n').trimEnd();
|
|
119
|
+
}
|