@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/statistics.ts
CHANGED
|
@@ -1,14 +1,47 @@
|
|
|
1
1
|
import { bfsFromNode } from 'graphology-traversal/bfs.js';
|
|
2
|
+
import type Graph from 'graphology';
|
|
2
3
|
import { loadGraphFromDb } from './loader.js';
|
|
3
4
|
import type { MonographDb } from '../storage/db.js';
|
|
4
5
|
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Module-level graph cache — avoids reloading the full graph on every call
|
|
8
|
+
// when multiple statistics functions are invoked in the same request cycle.
|
|
9
|
+
// TTL: 5 seconds; keyed by DB file path (better-sqlite3 exposes .name).
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const GRAPH_CACHE_TTL_MS = 5_000;
|
|
12
|
+
|
|
13
|
+
interface GraphCacheEntry {
|
|
14
|
+
graph: Graph;
|
|
15
|
+
expiresAt: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const _graphCache = new Map<string, GraphCacheEntry>();
|
|
19
|
+
|
|
20
|
+
function getGraph(db: MonographDb): Graph {
|
|
21
|
+
const key = (db as unknown as { name: string }).name ?? '__default__';
|
|
22
|
+
const now = Date.now();
|
|
23
|
+
const cached = _graphCache.get(key);
|
|
24
|
+
if (cached && now < cached.expiresAt) return cached.graph;
|
|
25
|
+
const graph = loadGraphFromDb(db);
|
|
26
|
+
_graphCache.set(key, { graph, expiresAt: now + GRAPH_CACHE_TTL_MS });
|
|
27
|
+
return graph;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Evict the cached graph for a given DB instance (call after writes).
|
|
32
|
+
*/
|
|
33
|
+
export function invalidateGraphCache(db: MonographDb): void {
|
|
34
|
+
const key = (db as unknown as { name: string }).name ?? '__default__';
|
|
35
|
+
_graphCache.delete(key);
|
|
36
|
+
}
|
|
37
|
+
|
|
5
38
|
/**
|
|
6
39
|
* Graph density: ratio of actual edges to maximum possible directed edges.
|
|
7
40
|
* For a directed graph with n nodes: max = n * (n - 1).
|
|
8
41
|
* Ignores self-loops.
|
|
9
42
|
*/
|
|
10
43
|
export function graphDensity(db: MonographDb): number {
|
|
11
|
-
const graph =
|
|
44
|
+
const graph = getGraph(db);
|
|
12
45
|
const n = graph.order;
|
|
13
46
|
if (n < 2) return 0;
|
|
14
47
|
const maxEdges = n * (n - 1);
|
|
@@ -28,7 +61,7 @@ export function graphDensity(db: MonographDb): number {
|
|
|
28
61
|
* We use the undirected approximation: treat edges as undirected, count triangles.
|
|
29
62
|
*/
|
|
30
63
|
export function clusteringCoefficient(db: MonographDb): number {
|
|
31
|
-
const graph =
|
|
64
|
+
const graph = getGraph(db);
|
|
32
65
|
if (graph.order === 0) return 0;
|
|
33
66
|
|
|
34
67
|
// Build undirected adjacency sets
|
|
@@ -67,67 +100,62 @@ export function clusteringCoefficient(db: MonographDb): number {
|
|
|
67
100
|
return totalCoeff / countWithNeighbors;
|
|
68
101
|
}
|
|
69
102
|
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Shared BFS result for path-length metrics
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
export interface PathStats {
|
|
108
|
+
/** Average shortest path length across all reachable pairs (i ≠ j). */
|
|
109
|
+
averagePathLength: number;
|
|
110
|
+
/** Maximum shortest path length (diameter) across all reachable pairs. */
|
|
111
|
+
diameter: number;
|
|
112
|
+
}
|
|
113
|
+
|
|
70
114
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
115
|
+
* Compute both average path length and graph diameter in a single BFS pass.
|
|
116
|
+
* Unreachable pairs are excluded from averagePathLength.
|
|
73
117
|
*/
|
|
74
|
-
export function
|
|
75
|
-
const graph =
|
|
118
|
+
export function pathStats(db: MonographDb): PathStats {
|
|
119
|
+
const graph = getGraph(db);
|
|
76
120
|
const nodes = graph.nodes();
|
|
77
|
-
if (nodes.length < 2) return 0;
|
|
121
|
+
if (nodes.length < 2) return { averagePathLength: 0, diameter: 0 };
|
|
78
122
|
|
|
79
123
|
let totalLength = 0;
|
|
80
124
|
let reachablePairs = 0;
|
|
125
|
+
let maxDist = 0;
|
|
81
126
|
|
|
82
127
|
for (const source of nodes) {
|
|
83
|
-
const distances = new Map<string, number>();
|
|
84
|
-
distances.set(source, 0);
|
|
85
|
-
|
|
86
128
|
bfsFromNode(graph, source, (node, _attr, depth) => {
|
|
87
|
-
if (node !== source) {
|
|
88
|
-
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
for (const [target, dist] of distances) {
|
|
93
|
-
if (target !== source && dist > 0) {
|
|
94
|
-
totalLength += dist;
|
|
129
|
+
if (node !== source && depth > 0) {
|
|
130
|
+
totalLength += depth;
|
|
95
131
|
reachablePairs++;
|
|
132
|
+
if (depth > maxDist) maxDist = depth;
|
|
96
133
|
}
|
|
97
|
-
}
|
|
134
|
+
});
|
|
98
135
|
}
|
|
99
136
|
|
|
100
|
-
|
|
101
|
-
|
|
137
|
+
return {
|
|
138
|
+
averagePathLength: reachablePairs === 0 ? 0 : totalLength / reachablePairs,
|
|
139
|
+
diameter: maxDist,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Average shortest path length across all reachable node pairs (i, j) where i ≠ j.
|
|
145
|
+
* Computed using BFS from each node. Unreachable pairs are excluded from the average.
|
|
146
|
+
*
|
|
147
|
+
* @deprecated Prefer `pathStats(db).averagePathLength` to share BFS with `graphDiameter`.
|
|
148
|
+
*/
|
|
149
|
+
export function averagePathLength(db: MonographDb): number {
|
|
150
|
+
return pathStats(db).averagePathLength;
|
|
102
151
|
}
|
|
103
152
|
|
|
104
153
|
/**
|
|
105
154
|
* Graph diameter: the maximum shortest path length across all reachable node pairs.
|
|
106
155
|
* Returns 0 for empty or single-node graphs.
|
|
156
|
+
*
|
|
157
|
+
* @deprecated Prefer `pathStats(db).diameter` to share BFS with `averagePathLength`.
|
|
107
158
|
*/
|
|
108
159
|
export function graphDiameter(db: MonographDb): number {
|
|
109
|
-
|
|
110
|
-
const nodes = graph.nodes();
|
|
111
|
-
if (nodes.length < 2) return 0;
|
|
112
|
-
|
|
113
|
-
let maxDist = 0;
|
|
114
|
-
|
|
115
|
-
for (const source of nodes) {
|
|
116
|
-
const distances = new Map<string, number>();
|
|
117
|
-
distances.set(source, 0);
|
|
118
|
-
|
|
119
|
-
bfsFromNode(graph, source, (node, _attr, depth) => {
|
|
120
|
-
if (node !== source) {
|
|
121
|
-
distances.set(node, depth);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
for (const [target, dist] of distances) {
|
|
126
|
-
if (target !== source && dist > maxDist) {
|
|
127
|
-
maxDist = dist;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return maxDist;
|
|
160
|
+
return pathStats(db).diameter;
|
|
133
161
|
}
|
package/src/graph/stats.ts
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import type Database from 'better-sqlite3';
|
|
2
2
|
|
|
3
|
+
// Per-DB prepared statement cache to avoid re-parsing SQL on repeated calls
|
|
4
|
+
const stmtCache = new WeakMap<Database.Database, Map<string, Database.Statement>>();
|
|
5
|
+
|
|
6
|
+
function stmt(db: Database.Database, sql: string): Database.Statement {
|
|
7
|
+
let dbCache = stmtCache.get(db);
|
|
8
|
+
if (!dbCache) { dbCache = new Map(); stmtCache.set(db, dbCache); }
|
|
9
|
+
let s = dbCache.get(sql);
|
|
10
|
+
if (!s) { s = db.prepare(sql); dbCache.set(sql, s); }
|
|
11
|
+
return s;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Binary search: returns index of first element > value in a sorted ascending array. */
|
|
15
|
+
function upperBound(sorted: number[], value: number): number {
|
|
16
|
+
let lo = 0, hi = sorted.length;
|
|
17
|
+
while (lo < hi) {
|
|
18
|
+
const mid = (lo + hi) >>> 1;
|
|
19
|
+
if (sorted[mid] <= value) lo = mid + 1;
|
|
20
|
+
else hi = mid;
|
|
21
|
+
}
|
|
22
|
+
return lo;
|
|
23
|
+
}
|
|
24
|
+
|
|
3
25
|
export type RiskBin = 'low' | 'medium' | 'high' | 'critical';
|
|
4
26
|
|
|
5
27
|
export interface RiskProfile {
|
|
@@ -64,23 +86,21 @@ function buildRiskProfile(values: number[]): RiskProfile {
|
|
|
64
86
|
* Compute full coupling profile from SQLite.
|
|
65
87
|
*/
|
|
66
88
|
export function computeCouplingProfile(db: Database.Database): CouplingProfile {
|
|
67
|
-
const fanInRows = db
|
|
68
|
-
|
|
69
|
-
).all() as { target_id: string; c: number }[];
|
|
89
|
+
const fanInRows = stmt(db, 'SELECT target_id, COUNT(*) as c FROM edges GROUP BY target_id')
|
|
90
|
+
.all() as { target_id: string; c: number }[];
|
|
70
91
|
|
|
71
|
-
const fanOutRows = db
|
|
72
|
-
|
|
73
|
-
).all() as { source_id: string; c: number }[];
|
|
92
|
+
const fanOutRows = stmt(db, 'SELECT source_id, COUNT(*) as c FROM edges GROUP BY source_id')
|
|
93
|
+
.all() as { source_id: string; c: number }[];
|
|
74
94
|
|
|
75
|
-
const totalFiles = (db
|
|
76
|
-
|
|
77
|
-
).get() as { n: number }).n;
|
|
95
|
+
const totalFiles = (stmt(db, "SELECT COUNT(*) as n FROM nodes WHERE label = 'File'")
|
|
96
|
+
.get() as { n: number }).n;
|
|
78
97
|
|
|
79
98
|
const fanInValues = fanInRows.map(r => r.c).sort((a, b) => a - b);
|
|
80
99
|
const fanOutValues = fanOutRows.map(r => r.c).sort((a, b) => a - b);
|
|
81
100
|
|
|
82
101
|
const p95FanIn = percentile(fanInValues, 95);
|
|
83
|
-
|
|
102
|
+
// Use binary search instead of O(N) filter to count elements above p95 threshold
|
|
103
|
+
const couplingHighCount = fanInValues.length - upperBound(fanInValues, p95FanIn);
|
|
84
104
|
const couplingHighPct = fanInValues.length > 0
|
|
85
105
|
? Math.round((couplingHighCount / fanInValues.length) * 100)
|
|
86
106
|
: 0;
|
|
@@ -101,14 +121,13 @@ export function computeCouplingProfile(db: Database.Database): CouplingProfile {
|
|
|
101
121
|
* Quick stats summary (extends existing stats from monograph_stats MCP tool).
|
|
102
122
|
*/
|
|
103
123
|
export function computeGraphStats(db: Database.Database): GraphStatsSummary {
|
|
104
|
-
const nodeCount = (db
|
|
105
|
-
const edgeCount = (db
|
|
106
|
-
const communityCount = (db
|
|
124
|
+
const nodeCount = (stmt(db, 'SELECT COUNT(*) as n FROM nodes').get() as { n: number }).n;
|
|
125
|
+
const edgeCount = (stmt(db, 'SELECT COUNT(*) as n FROM edges').get() as { n: number }).n;
|
|
126
|
+
const communityCount = (stmt(db,
|
|
107
127
|
"SELECT COUNT(DISTINCT community_id) as n FROM nodes WHERE community_id IS NOT NULL"
|
|
108
128
|
).get() as { n: number }).n;
|
|
109
|
-
const fileCount = (db
|
|
110
|
-
|
|
111
|
-
).get() as { n: number }).n;
|
|
129
|
+
const fileCount = (stmt(db, "SELECT COUNT(*) as n FROM nodes WHERE label = 'File'")
|
|
130
|
+
.get() as { n: number }).n;
|
|
112
131
|
|
|
113
132
|
const couplingProfile = computeCouplingProfile(db);
|
|
114
133
|
|
|
@@ -120,3 +139,18 @@ export function computeGraphStats(db: Database.Database): GraphStatsSummary {
|
|
|
120
139
|
couplingProfile,
|
|
121
140
|
};
|
|
122
141
|
}
|
|
142
|
+
|
|
143
|
+
/** Format a GraphStatsSummary as structured text for LLM consumption. */
|
|
144
|
+
export function formatGraphStats(s: GraphStatsSummary): string {
|
|
145
|
+
const cp = s.couplingProfile;
|
|
146
|
+
const lines: string[] = [
|
|
147
|
+
`Graph Stats`,
|
|
148
|
+
` Nodes: ${s.nodeCount} Edges: ${s.edgeCount} Communities: ${s.communityCount} Files: ${s.fileCount}`,
|
|
149
|
+
`Coupling Profile (${cp.totalFiles} files)`,
|
|
150
|
+
` Fan-in p50/p75/p90/p95: ${cp.p50FanIn}/${cp.p75FanIn}/${cp.p90FanIn}/${cp.p95FanIn}`,
|
|
151
|
+
` High-coupling (>p95): ${cp.couplingHighPct}%`,
|
|
152
|
+
`Fan-in Risk: low=${cp.fanInProfile.low}(${cp.fanInProfile.lowPct}%) med=${cp.fanInProfile.medium}(${cp.fanInProfile.mediumPct}%) high=${cp.fanInProfile.high}(${cp.fanInProfile.highPct}%) crit=${cp.fanInProfile.critical}(${cp.fanInProfile.criticalPct}%)`,
|
|
153
|
+
`Fan-out Risk: low=${cp.fanOutProfile.low}(${cp.fanOutProfile.lowPct}%) med=${cp.fanOutProfile.medium}(${cp.fanOutProfile.mediumPct}%) high=${cp.fanOutProfile.high}(${cp.fanOutProfile.highPct}%) crit=${cp.fanOutProfile.critical}(${cp.fanOutProfile.criticalPct}%)`,
|
|
154
|
+
];
|
|
155
|
+
return lines.join('\n');
|
|
156
|
+
}
|
package/src/graph/subgraph.ts
CHANGED
|
@@ -21,46 +21,80 @@ export interface InducedSubgraph {
|
|
|
21
21
|
// (source_id IN chunk), then filters target in-memory — full limit available per chunk.
|
|
22
22
|
const SQLITE_VAR_LIMIT = 32766;
|
|
23
23
|
|
|
24
|
+
/** Cache of placeholder strings keyed by count: 1 → "?", 2 → "?,?", etc. */
|
|
25
|
+
const placeholderCache = new Map<number, string>();
|
|
26
|
+
function placeholders(n: number): string {
|
|
27
|
+
let ph = placeholderCache.get(n);
|
|
28
|
+
if (!ph) {
|
|
29
|
+
ph = Array(n).fill('?').join(',');
|
|
30
|
+
placeholderCache.set(n, ph);
|
|
31
|
+
}
|
|
32
|
+
return ph;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type RawNode = {
|
|
36
|
+
id: string; label: string; name: string; norm_label?: string | null; file_path: string | null;
|
|
37
|
+
start_line: number | null; end_line: number | null; community_id: number | null;
|
|
38
|
+
is_exported: number; language: string | null; properties: string | null;
|
|
39
|
+
};
|
|
40
|
+
type RawEdge = {
|
|
41
|
+
id: string; source_id: string; target_id: string; relation: string;
|
|
42
|
+
confidence: string; confidence_score: number; reason: string | null;
|
|
43
|
+
// evidence column omitted — not surfaced in InducedSubgraph
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Prepared-statement caches per db instance — stored on the db object to avoid
|
|
48
|
+
* cross-db pollution while ensuring each unique chunk size is prepared at most once.
|
|
49
|
+
*/
|
|
50
|
+
function getStmtCaches(db: MonographDb): {
|
|
51
|
+
nodes: Map<number, ReturnType<MonographDb['prepare']>>;
|
|
52
|
+
edges: Map<number, ReturnType<MonographDb['prepare']>>;
|
|
53
|
+
} {
|
|
54
|
+
const key = '__subgraphStmtCache__';
|
|
55
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
56
|
+
const dbAny = db as any;
|
|
57
|
+
if (!dbAny[key]) {
|
|
58
|
+
dbAny[key] = { nodes: new Map(), edges: new Map() };
|
|
59
|
+
}
|
|
60
|
+
return dbAny[key] as { nodes: Map<number, ReturnType<MonographDb['prepare']>>; edges: Map<number, ReturnType<MonographDb['prepare']>> };
|
|
61
|
+
}
|
|
62
|
+
|
|
24
63
|
export function extractInducedSubgraph(db: MonographDb, nodeIds: string[]): InducedSubgraph {
|
|
25
64
|
if (nodeIds.length === 0) return { nodes: [], edges: [] };
|
|
26
65
|
|
|
27
|
-
|
|
28
|
-
|
|
66
|
+
const stmts = getStmtCaches(db);
|
|
67
|
+
|
|
68
|
+
// Chunk helper — caches prepared statements by chunk size to avoid re-prepare per call.
|
|
69
|
+
function queryChunked<T>(ids: string[], chunkSize: number, stmtCache: Map<number, ReturnType<MonographDb['prepare']>>, sql: (ph: string) => string): T[] {
|
|
29
70
|
const results: T[] = [];
|
|
30
71
|
for (let i = 0; i < ids.length; i += chunkSize) {
|
|
31
72
|
const chunk = ids.slice(i, i + chunkSize);
|
|
32
|
-
const ph = chunk.
|
|
33
|
-
|
|
73
|
+
const ph = placeholders(chunk.length);
|
|
74
|
+
let stmt = stmtCache.get(chunk.length);
|
|
75
|
+
if (!stmt) { stmt = db.prepare(sql(ph)); stmtCache.set(chunk.length, stmt); }
|
|
76
|
+
results.push(...(stmt.all(...chunk) as T[]));
|
|
34
77
|
}
|
|
35
78
|
return results;
|
|
36
79
|
}
|
|
37
80
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
type RawEdge = {
|
|
44
|
-
id: string; source_id: string; target_id: string; relation: string;
|
|
45
|
-
confidence: string; confidence_score: number; reason: string | null; evidence: string | null;
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const rawNodes = queryChunked<RawNode>(nodeIds, SQLITE_VAR_LIMIT, (ph, chunk) =>
|
|
49
|
-
db.prepare(
|
|
50
|
-
`SELECT id, label, name, file_path, start_line, end_line, community_id, is_exported, language, properties
|
|
51
|
-
FROM nodes WHERE id IN (${ph})`
|
|
52
|
-
).all(...chunk) as RawNode[]
|
|
81
|
+
const rawNodes = queryChunked<RawNode>(
|
|
82
|
+
nodeIds,
|
|
83
|
+
SQLITE_VAR_LIMIT,
|
|
84
|
+
stmts.nodes,
|
|
85
|
+
ph => `SELECT id, label, name, file_path, start_line, end_line, community_id, is_exported, language, properties FROM nodes WHERE id IN (${ph})`,
|
|
53
86
|
);
|
|
54
87
|
|
|
55
88
|
// For edges: query by source_id chunks (1 bind per row), then filter target in-memory.
|
|
56
|
-
// Querying by both source+target would miss cross-chunk edges and requires 2× bind slots.
|
|
57
89
|
const nodeSet = new Set(nodeIds);
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
90
|
+
const allSourceEdges = queryChunked<RawEdge>(
|
|
91
|
+
nodeIds,
|
|
92
|
+
SQLITE_VAR_LIMIT,
|
|
93
|
+
stmts.edges,
|
|
94
|
+
// Drop unused 'evidence' column to reduce row data transfer
|
|
95
|
+
ph => `SELECT id, source_id, target_id, relation, confidence, confidence_score, reason FROM edges WHERE source_id IN (${ph})`,
|
|
63
96
|
);
|
|
97
|
+
const rawEdges = allSourceEdges.filter(e => nodeSet.has(e.target_id));
|
|
64
98
|
|
|
65
99
|
const nodes: MonographNode[] = rawNodes.map(n => ({
|
|
66
100
|
id: n.id,
|
|
@@ -88,3 +122,40 @@ export function extractInducedSubgraph(db: MonographDb, nodeIds: string[]): Indu
|
|
|
88
122
|
|
|
89
123
|
return { nodes, edges };
|
|
90
124
|
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Format an InducedSubgraph as structured text for LLM consumption.
|
|
128
|
+
* Groups nodes by file path and lists edges with relation + file:line hints.
|
|
129
|
+
*/
|
|
130
|
+
export function formatInducedSubgraph(sg: InducedSubgraph): string {
|
|
131
|
+
if (sg.nodes.length === 0) return 'Subgraph is empty.';
|
|
132
|
+
|
|
133
|
+
const lines: string[] = [`Subgraph: ${sg.nodes.length} nodes, ${sg.edges.length} edges`];
|
|
134
|
+
|
|
135
|
+
// Group nodes by file
|
|
136
|
+
const byFile = new Map<string, MonographNode[]>();
|
|
137
|
+
for (const n of sg.nodes) {
|
|
138
|
+
const file = n.filePath ?? '(unknown)';
|
|
139
|
+
let list = byFile.get(file);
|
|
140
|
+
if (!list) { list = []; byFile.set(file, list); }
|
|
141
|
+
list.push(n);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
lines.push('\nNodes:');
|
|
145
|
+
for (const [file, nodes] of byFile) {
|
|
146
|
+
lines.push(` ${file}`);
|
|
147
|
+
for (const n of nodes) {
|
|
148
|
+
const loc = n.startLine ? `:${n.startLine}` : '';
|
|
149
|
+
lines.push(` ${n.label} ${n.name}${loc}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (sg.edges.length > 0) {
|
|
154
|
+
lines.push('\nEdges:');
|
|
155
|
+
for (const e of sg.edges) {
|
|
156
|
+
lines.push(` ${e.sourceId} --[${e.relation}]--> ${e.targetId}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return lines.join('\n');
|
|
161
|
+
}
|
package/src/graph/topo-sort.ts
CHANGED
|
@@ -16,35 +16,49 @@ export interface TopoSortResult {
|
|
|
16
16
|
* Files with no incoming edges on the reverse graph (i.e., no one imports them)
|
|
17
17
|
* are leaves and appear in level 0.
|
|
18
18
|
*
|
|
19
|
+
* The in-degree computation is pushed into SQL (GROUP BY aggregation) to avoid
|
|
20
|
+
* materialising the full edge table into JavaScript memory.
|
|
21
|
+
*
|
|
19
22
|
* @param db - The MonographDb instance
|
|
20
23
|
* @returns Object with `levels` (array of independent groups, leaf-first) and `cycleCount`.
|
|
21
24
|
*/
|
|
22
25
|
export function topologicalLevelSort(db: MonographDb): TopoSortResult {
|
|
23
26
|
const nodeRows = db.prepare('SELECT id FROM nodes').all() as { id: string }[];
|
|
24
|
-
const edgeRows = db.prepare('SELECT source_id, target_id FROM edges').all() as {
|
|
25
|
-
source_id: string;
|
|
26
|
-
target_id: string;
|
|
27
|
-
}[];
|
|
28
|
-
|
|
29
27
|
if (nodeRows.length === 0) return { levels: [], cycleCount: 0 };
|
|
30
28
|
|
|
31
29
|
const nodes = nodeRows.map(r => r.id);
|
|
32
30
|
|
|
33
|
-
//
|
|
34
|
-
// In
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
// Compute in-degree (on the reverse graph) via SQL aggregation.
|
|
32
|
+
// In the original graph each edge src→tgt means "src imports tgt".
|
|
33
|
+
// In the reverse graph the edge becomes tgt→src, so src gains in-degree +1.
|
|
34
|
+
// We aggregate target_id counts grouped by source_id to get reverse-in-degree per node.
|
|
35
|
+
const inDegreeRows = db
|
|
36
|
+
.prepare(
|
|
37
|
+
`SELECT source_id AS node_id, COUNT(*) AS cnt
|
|
38
|
+
FROM edges
|
|
39
|
+
WHERE source_id != target_id
|
|
40
|
+
GROUP BY source_id`,
|
|
41
|
+
)
|
|
42
|
+
.all() as { node_id: string; cnt: number }[];
|
|
37
43
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
const inDegree = new Map<string, number>();
|
|
45
|
+
for (const n of nodes) inDegree.set(n, 0);
|
|
46
|
+
for (const { node_id, cnt } of inDegreeRows) {
|
|
47
|
+
if (inDegree.has(node_id)) inDegree.set(node_id, cnt);
|
|
41
48
|
}
|
|
42
49
|
|
|
50
|
+
// Build reverse adjacency list — still needed for Kahn's BFS relaxation step.
|
|
51
|
+
// Fetch only what Kahn needs: target_id → [source_id, ...] (reverse direction).
|
|
52
|
+
const reverseAdj = new Map<string, string[]>();
|
|
53
|
+
for (const n of nodes) reverseAdj.set(n, []);
|
|
54
|
+
|
|
55
|
+
const edgeRows = db
|
|
56
|
+
.prepare('SELECT source_id, target_id FROM edges WHERE source_id != target_id')
|
|
57
|
+
.all() as { source_id: string; target_id: string }[];
|
|
58
|
+
|
|
43
59
|
for (const { source_id: src, target_id: tgt } of edgeRows) {
|
|
44
|
-
if (src === tgt) continue; // skip self-loops
|
|
45
60
|
if (!reverseAdj.has(tgt) || !reverseAdj.has(src)) continue;
|
|
46
61
|
reverseAdj.get(tgt)!.push(src);
|
|
47
|
-
inDegree.set(src, (inDegree.get(src) ?? 0) + 1);
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
// Kahn's BFS on the reverse graph
|
|
@@ -57,7 +71,6 @@ export function topologicalLevelSort(db: MonographDb): TopoSortResult {
|
|
|
57
71
|
const visited = new Set<string>();
|
|
58
72
|
|
|
59
73
|
while (queue.length > 0) {
|
|
60
|
-
// All nodes currently at in-degree 0 form one level
|
|
61
74
|
const level = [...queue];
|
|
62
75
|
queue.length = 0;
|
|
63
76
|
levels.push(level);
|
|
@@ -76,7 +89,6 @@ export function topologicalLevelSort(db: MonographDb): TopoSortResult {
|
|
|
76
89
|
// Remaining unvisited nodes are in cycles
|
|
77
90
|
const cycleNodes = nodes.filter(n => !visited.has(n));
|
|
78
91
|
let cycleCount = 0;
|
|
79
|
-
|
|
80
92
|
if (cycleNodes.length > 0) {
|
|
81
93
|
cycleCount = cycleNodes.length;
|
|
82
94
|
levels.push(cycleNodes);
|
|
@@ -84,3 +96,76 @@ export function topologicalLevelSort(db: MonographDb): TopoSortResult {
|
|
|
84
96
|
|
|
85
97
|
return { levels, cycleCount };
|
|
86
98
|
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Structured text formatter for LLM consumption
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Format topological sort levels as structured text.
|
|
106
|
+
*
|
|
107
|
+
* Resolves node IDs to names and file paths so LLMs can navigate directly
|
|
108
|
+
* to the source. Cycle nodes (if any) are clearly labelled.
|
|
109
|
+
*
|
|
110
|
+
* @param db - The MonographDb instance (for name resolution)
|
|
111
|
+
* @param result - Result from topologicalLevelSort()
|
|
112
|
+
* @param maxLevels - Max levels to include in output (default: all)
|
|
113
|
+
* @returns Structured text suitable for injection into LLM context
|
|
114
|
+
*/
|
|
115
|
+
export function formatTopoSort(
|
|
116
|
+
db: MonographDb,
|
|
117
|
+
result: TopoSortResult,
|
|
118
|
+
maxLevels?: number,
|
|
119
|
+
): string {
|
|
120
|
+
if (result.levels.length === 0) {
|
|
121
|
+
return 'Topological sort: no nodes found.';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Batch-resolve all node IDs
|
|
125
|
+
const allIds = result.levels.flat();
|
|
126
|
+
const CHUNK = 200;
|
|
127
|
+
const nodeInfo = new Map<string, { name: string; filePath: string | null }>();
|
|
128
|
+
|
|
129
|
+
for (let i = 0; i < allIds.length; i += CHUNK) {
|
|
130
|
+
const chunk = allIds.slice(i, i + CHUNK);
|
|
131
|
+
const ph = chunk.map(() => '?').join(',');
|
|
132
|
+
const rows = db
|
|
133
|
+
.prepare(`SELECT id, name, file_path FROM nodes WHERE id IN (${ph})`)
|
|
134
|
+
.all(...chunk) as { id: string; name: string; file_path: string | null }[];
|
|
135
|
+
for (const row of rows) {
|
|
136
|
+
nodeInfo.set(row.id, { name: row.name ?? row.id, filePath: row.file_path });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const isCycleLevelIdx =
|
|
141
|
+
result.cycleCount > 0 && result.levels.length > 0
|
|
142
|
+
? result.levels.length - 1
|
|
143
|
+
: -1;
|
|
144
|
+
|
|
145
|
+
const displayLevels = maxLevels !== undefined ? result.levels.slice(0, maxLevels) : result.levels;
|
|
146
|
+
|
|
147
|
+
const lines: string[] = [
|
|
148
|
+
`Topological sort: ${result.levels.length - (result.cycleCount > 0 ? 1 : 0)} level${result.levels.length === 1 ? '' : 's'}` +
|
|
149
|
+
(result.cycleCount > 0 ? `, ${result.cycleCount} node${result.cycleCount === 1 ? '' : 's'} in cycles` : ''),
|
|
150
|
+
'',
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
for (let li = 0; li < displayLevels.length; li++) {
|
|
154
|
+
const level = displayLevels[li];
|
|
155
|
+
const label = li === isCycleLevelIdx ? 'Cycle nodes (unresolvable order)' : `Level ${li}`;
|
|
156
|
+
lines.push(`${label} (${level.length} node${level.length === 1 ? '' : 's'}):`);
|
|
157
|
+
for (const id of level) {
|
|
158
|
+
const info = nodeInfo.get(id);
|
|
159
|
+
const name = info?.name ?? id;
|
|
160
|
+
const fp = info?.filePath;
|
|
161
|
+
lines.push(` ${name}${fp ? ` — ${fp}` : ''}`);
|
|
162
|
+
}
|
|
163
|
+
lines.push('');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (maxLevels !== undefined && result.levels.length > maxLevels) {
|
|
167
|
+
lines.push(`... (${result.levels.length - maxLevels} more level${result.levels.length - maxLevels === 1 ? '' : 's'} omitted)`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return lines.join('\n').trimEnd();
|
|
171
|
+
}
|