@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/mcp-tools/impact.ts
CHANGED
|
@@ -118,13 +118,32 @@ export function getMonographImpact(
|
|
|
118
118
|
|
|
119
119
|
// Reverse BFS to find all callers (depth 0 = start node)
|
|
120
120
|
const visited = reverseBfs(nodeId, db, maxDepth, {});
|
|
121
|
+
return { node, ...extractCallerResult(db, nodeId, visited) };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── Shared helper: fetch nodes by IDs in a single query ───────────────────────
|
|
125
|
+
|
|
126
|
+
function getNodesByIds(db: Database.Database, ids: string[]): MonographNode[] {
|
|
127
|
+
if (ids.length === 0) return [];
|
|
128
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
129
|
+
const rows = db
|
|
130
|
+
.prepare(`SELECT * FROM nodes WHERE id IN (${placeholders})`)
|
|
131
|
+
.all(...ids) as Record<string, unknown>[];
|
|
132
|
+
return rows.map(rowToNode);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── Shared helper: turn a visited map into structured caller lists ─────────────
|
|
121
136
|
|
|
122
|
-
|
|
137
|
+
function extractCallerResult(
|
|
138
|
+
db: Database.Database,
|
|
139
|
+
startNodeId: string,
|
|
140
|
+
visited: Map<string, number>,
|
|
141
|
+
): Omit<MonographImpactResult, 'node'> {
|
|
123
142
|
const directCallerIds: string[] = [];
|
|
124
143
|
const byDepth = new Map<number, string[]>();
|
|
125
144
|
|
|
126
145
|
for (const [id, depth] of visited.entries()) {
|
|
127
|
-
if (id ===
|
|
146
|
+
if (id === startNodeId) continue;
|
|
128
147
|
if (depth === 1) {
|
|
129
148
|
directCallerIds.push(id);
|
|
130
149
|
} else {
|
|
@@ -134,39 +153,24 @@ export function getMonographImpact(
|
|
|
134
153
|
}
|
|
135
154
|
}
|
|
136
155
|
|
|
137
|
-
|
|
138
|
-
const getNodesByIds = (ids: string[]): MonographNode[] => {
|
|
139
|
-
if (ids.length === 0) return [];
|
|
140
|
-
const placeholders = ids.map(() => '?').join(',');
|
|
141
|
-
const rows = db
|
|
142
|
-
.prepare(`SELECT * FROM nodes WHERE id IN (${placeholders})`)
|
|
143
|
-
.all(...ids) as Record<string, unknown>[];
|
|
144
|
-
return rows.map(rowToNode);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
const directCallers = getNodesByIds(directCallerIds);
|
|
156
|
+
const directCallers = getNodesByIds(db, directCallerIds);
|
|
148
157
|
|
|
149
158
|
const transitiveCallers: Array<{ depth: number; nodes: MonographNode[] }> = [];
|
|
150
159
|
const sortedDepths = Array.from(byDepth.keys()).sort((a, b) => a - b);
|
|
151
160
|
for (const depth of sortedDepths) {
|
|
152
|
-
|
|
153
|
-
transitiveCallers.push({ depth, nodes: getNodesByIds(ids) });
|
|
161
|
+
transitiveCallers.push({ depth, nodes: getNodesByIds(db, byDepth.get(depth)!) });
|
|
154
162
|
}
|
|
155
163
|
|
|
156
|
-
// Collect unique affected file paths (excluding start node)
|
|
157
164
|
const allAffectedNodes = [...directCallers, ...transitiveCallers.flatMap(t => t.nodes)];
|
|
158
165
|
const affectedFiles = [...new Set(
|
|
159
|
-
allAffectedNodes
|
|
160
|
-
.map(n => n.filePath)
|
|
161
|
-
.filter((p): p is string => p != null),
|
|
166
|
+
allAffectedNodes.map(n => n.filePath).filter((p): p is string => p != null),
|
|
162
167
|
)];
|
|
163
168
|
|
|
164
|
-
// Risk score: log2(totalCallerCount + 1) normalized to [0, 1] (max log2(11) ≈ 3.46, capped at 10, /10)
|
|
165
169
|
const totalCallerCount = visited.size - 1; // exclude start node
|
|
166
170
|
const rawScore = Math.min(Math.log2(totalCallerCount + 1), 10);
|
|
167
171
|
const riskScore = rawScore / 10;
|
|
168
172
|
|
|
169
|
-
return {
|
|
173
|
+
return { directCallers, transitiveCallers, affectedFiles, riskScore, riskLevel: computeRiskLevel(riskScore) };
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
// ── id-based impact with filtering options ────────────────────────────────────
|
|
@@ -188,49 +192,5 @@ export async function monographImpact(
|
|
|
188
192
|
|
|
189
193
|
const node = rowToNode(nodeRow);
|
|
190
194
|
const visited = reverseBfs(nodeId, db, maxDepth, options);
|
|
191
|
-
|
|
192
|
-
const directCallerIds: string[] = [];
|
|
193
|
-
const byDepth = new Map<number, string[]>();
|
|
194
|
-
|
|
195
|
-
for (const [id, depth] of visited.entries()) {
|
|
196
|
-
if (id === nodeId) continue;
|
|
197
|
-
if (depth === 1) {
|
|
198
|
-
directCallerIds.push(id);
|
|
199
|
-
} else {
|
|
200
|
-
const existing = byDepth.get(depth) ?? [];
|
|
201
|
-
existing.push(id);
|
|
202
|
-
byDepth.set(depth, existing);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const getNodesByIds = (ids: string[]): MonographNode[] => {
|
|
207
|
-
if (ids.length === 0) return [];
|
|
208
|
-
const placeholders = ids.map(() => '?').join(',');
|
|
209
|
-
const rows = db
|
|
210
|
-
.prepare(`SELECT * FROM nodes WHERE id IN (${placeholders})`)
|
|
211
|
-
.all(...ids) as Record<string, unknown>[];
|
|
212
|
-
return rows.map(rowToNode);
|
|
213
|
-
};
|
|
214
|
-
|
|
215
|
-
const directCallers = getNodesByIds(directCallerIds);
|
|
216
|
-
|
|
217
|
-
const transitiveCallers: Array<{ depth: number; nodes: MonographNode[] }> = [];
|
|
218
|
-
const sortedDepths = Array.from(byDepth.keys()).sort((a, b) => a - b);
|
|
219
|
-
for (const depth of sortedDepths) {
|
|
220
|
-
const ids = byDepth.get(depth)!;
|
|
221
|
-
transitiveCallers.push({ depth, nodes: getNodesByIds(ids) });
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const allAffectedNodes = [...directCallers, ...transitiveCallers.flatMap(t => t.nodes)];
|
|
225
|
-
const affectedFiles = [...new Set(
|
|
226
|
-
allAffectedNodes
|
|
227
|
-
.map(n => n.filePath)
|
|
228
|
-
.filter((p): p is string => p != null),
|
|
229
|
-
)];
|
|
230
|
-
|
|
231
|
-
const totalCallerCount = visited.size - 1;
|
|
232
|
-
const rawScore = Math.min(Math.log2(totalCallerCount + 1), 10);
|
|
233
|
-
const riskScore = rawScore / 10;
|
|
234
|
-
|
|
235
|
-
return { node, directCallers, transitiveCallers, affectedFiles, riskScore, riskLevel: computeRiskLevel(riskScore) };
|
|
195
|
+
return { node, ...extractCallerResult(db, nodeId, visited) };
|
|
236
196
|
}
|
|
@@ -30,6 +30,36 @@ export interface MonographNeighborsResult {
|
|
|
30
30
|
neighbors: NeighborEntry[];
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// ── Shared edge query helper ──────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
function queryEdges(
|
|
36
|
+
db: Database.Database,
|
|
37
|
+
nodeId: string,
|
|
38
|
+
direction: 'outbound' | 'inbound',
|
|
39
|
+
relationFilter?: string,
|
|
40
|
+
): NeighborEntry[] {
|
|
41
|
+
// outbound: source_id = nodeId → join target_id
|
|
42
|
+
// inbound: target_id = nodeId → join source_id
|
|
43
|
+
const [idCol, joinCol] = direction === 'outbound'
|
|
44
|
+
? ['source_id', 'target_id']
|
|
45
|
+
: ['target_id', 'source_id'];
|
|
46
|
+
|
|
47
|
+
const sql = relationFilter
|
|
48
|
+
? `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.${joinCol} WHERE e.${idCol} = ? AND e.relation = ? LIMIT 50`
|
|
49
|
+
: `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.${joinCol} WHERE e.${idCol} = ? LIMIT 50`;
|
|
50
|
+
|
|
51
|
+
const params = relationFilter ? [nodeId, relationFilter] : [nodeId];
|
|
52
|
+
const rows = db.prepare(sql).all(...params) as Record<string, unknown>[];
|
|
53
|
+
|
|
54
|
+
return rows.map(row => ({
|
|
55
|
+
node: rowToNode(row),
|
|
56
|
+
relation: row['relation'] as string,
|
|
57
|
+
confidence: row['confidence'] as string,
|
|
58
|
+
confidenceScore: (row['confidence_score'] as number) ?? 1,
|
|
59
|
+
direction,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
33
63
|
export function getMonographNeighbors(
|
|
34
64
|
db: Database.Database,
|
|
35
65
|
input: { name: string; relationFilter?: string; includeInbound?: boolean },
|
|
@@ -41,45 +71,10 @@ export function getMonographNeighbors(
|
|
|
41
71
|
if (!nodeRow) return { node: null, neighbors: [] };
|
|
42
72
|
|
|
43
73
|
const node = rowToNode(nodeRow);
|
|
44
|
-
const neighbors: NeighborEntry[] = [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
? `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.target_id WHERE e.source_id = ? AND e.relation = ? LIMIT 50`
|
|
49
|
-
: `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.target_id WHERE e.source_id = ? LIMIT 50`;
|
|
50
|
-
|
|
51
|
-
const outboundParams = input.relationFilter ? [node.id, input.relationFilter] : [node.id];
|
|
52
|
-
const outboundRows = db.prepare(outboundSql).all(...outboundParams) as Record<string, unknown>[];
|
|
53
|
-
|
|
54
|
-
for (const row of outboundRows) {
|
|
55
|
-
neighbors.push({
|
|
56
|
-
node: rowToNode(row),
|
|
57
|
-
relation: row['relation'] as string,
|
|
58
|
-
confidence: row['confidence'] as string,
|
|
59
|
-
confidenceScore: (row['confidence_score'] as number) ?? 1,
|
|
60
|
-
direction: 'outbound',
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Inbound edges
|
|
65
|
-
if (input.includeInbound) {
|
|
66
|
-
const inboundSql = input.relationFilter
|
|
67
|
-
? `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.source_id WHERE e.target_id = ? AND e.relation = ? LIMIT 50`
|
|
68
|
-
: `SELECT n.*, e.relation, e.confidence, e.confidence_score FROM nodes n JOIN edges e ON n.id = e.source_id WHERE e.target_id = ? LIMIT 50`;
|
|
69
|
-
|
|
70
|
-
const inboundParams = input.relationFilter ? [node.id, input.relationFilter] : [node.id];
|
|
71
|
-
const inboundRows = db.prepare(inboundSql).all(...inboundParams) as Record<string, unknown>[];
|
|
72
|
-
|
|
73
|
-
for (const row of inboundRows) {
|
|
74
|
-
neighbors.push({
|
|
75
|
-
node: rowToNode(row),
|
|
76
|
-
relation: row['relation'] as string,
|
|
77
|
-
confidence: row['confidence'] as string,
|
|
78
|
-
confidenceScore: (row['confidence_score'] as number) ?? 1,
|
|
79
|
-
direction: 'inbound',
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}
|
|
74
|
+
const neighbors: NeighborEntry[] = [
|
|
75
|
+
...queryEdges(db, node.id, 'outbound', input.relationFilter),
|
|
76
|
+
...(input.includeInbound ? queryEdges(db, node.id, 'inbound', input.relationFilter) : []),
|
|
77
|
+
];
|
|
83
78
|
|
|
84
79
|
return { node, neighbors };
|
|
85
80
|
}
|
package/src/mcp-tools/query.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface QueryResult {
|
|
|
8
8
|
label: string;
|
|
9
9
|
name: string;
|
|
10
10
|
filePath?: string;
|
|
11
|
+
/** Line number where the symbol is defined — enables direct file:line navigation. */
|
|
12
|
+
startLine?: number | null;
|
|
11
13
|
score: number;
|
|
12
14
|
isProcess: boolean;
|
|
13
15
|
}
|
|
@@ -66,6 +68,7 @@ export const monographQueryTool = {
|
|
|
66
68
|
label: h.label ?? 'Symbol',
|
|
67
69
|
name: h.name ?? h.id,
|
|
68
70
|
filePath: h.filePath ?? undefined,
|
|
71
|
+
startLine: h.startLine ?? null,
|
|
69
72
|
score: h.score,
|
|
70
73
|
isProcess: h.label === 'Process',
|
|
71
74
|
}));
|
package/src/mcp-tools/rename.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
1
|
+
import { readFileSync, statSync } from 'fs';
|
|
2
2
|
import type Database from 'better-sqlite3';
|
|
3
3
|
import type { MonographNode } from '../types.js';
|
|
4
4
|
|
|
@@ -68,7 +68,10 @@ export function getMonographRename(
|
|
|
68
68
|
|
|
69
69
|
// Build changes list by reading source files
|
|
70
70
|
const changes: Array<{ file: string; line: number; before: string; after: string }> = [];
|
|
71
|
-
|
|
71
|
+
// Two separate regexes: testRe has no `g` flag (safe for repeated test()), replaceRe has `g`
|
|
72
|
+
const testRe = new RegExp(`\\b${escapeRegExp(input.oldName)}\\b`);
|
|
73
|
+
const replaceRe = new RegExp(`\\b${escapeRegExp(input.oldName)}\\b`, 'g');
|
|
74
|
+
const MAX_FILE_BYTES = 1_048_576; // 1 MiB guard
|
|
72
75
|
|
|
73
76
|
// File line cache to avoid re-reading the same file multiple times
|
|
74
77
|
const fileLineCache = new Map<string, string[]>();
|
|
@@ -76,6 +79,8 @@ export function getMonographRename(
|
|
|
76
79
|
const getLines = (filePath: string): string[] => {
|
|
77
80
|
if (fileLineCache.has(filePath)) return fileLineCache.get(filePath)!;
|
|
78
81
|
try {
|
|
82
|
+
const st = statSync(filePath);
|
|
83
|
+
if (st.size > MAX_FILE_BYTES) { fileLineCache.set(filePath, []); return []; }
|
|
79
84
|
const content = readFileSync(filePath, 'utf-8');
|
|
80
85
|
const lines = content.split('\n');
|
|
81
86
|
fileLineCache.set(filePath, lines);
|
|
@@ -94,13 +99,9 @@ export function getMonographRename(
|
|
|
94
99
|
if (lineIdx < 0 || lineIdx >= lines.length) continue;
|
|
95
100
|
|
|
96
101
|
const originalLine = lines[lineIdx];
|
|
97
|
-
if (!
|
|
98
|
-
nameRegex.lastIndex = 0;
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
nameRegex.lastIndex = 0;
|
|
102
|
+
if (!testRe.test(originalLine)) continue;
|
|
102
103
|
|
|
103
|
-
const updatedLine = originalLine.replace(
|
|
104
|
+
const updatedLine = originalLine.replace(replaceRe, input.newName);
|
|
104
105
|
changes.push({
|
|
105
106
|
file: row.file_path,
|
|
106
107
|
line: row.start_line,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
1
|
+
import { readFileSync, statSync } from 'fs';
|
|
2
2
|
import type Database from 'better-sqlite3';
|
|
3
3
|
import { extractMiddlewareChain } from '../pipeline/phases/middleware-extractor.js';
|
|
4
4
|
|
|
@@ -19,39 +19,34 @@ export interface MonographRouteMapResult {
|
|
|
19
19
|
total: number;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
const MAX_FILE_BYTES = 1_048_576; // 1 MiB guard for middleware source reads
|
|
23
|
+
|
|
22
24
|
// ── Implementation ─────────────────────────────────────────────────────────────
|
|
23
25
|
|
|
24
26
|
export function getMonographRouteMap(
|
|
25
27
|
db: Database.Database,
|
|
26
28
|
input: { prefix?: string; method?: string; includeMiddleware?: boolean; repoPath?: string },
|
|
27
29
|
): MonographRouteMapResult {
|
|
28
|
-
// 1.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.all() as Record<string, unknown>[];
|
|
30
|
+
// 1. Build SQL-level filters to avoid loading the whole Route table into JS
|
|
31
|
+
const conditions: string[] = ["label = 'Route'"];
|
|
32
|
+
const params: unknown[] = [];
|
|
32
33
|
|
|
33
|
-
// 2. Apply prefix filter
|
|
34
34
|
if (input.prefix) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
// name is like "GET /api/users" — find the path part
|
|
39
|
-
const spaceIdx = name.indexOf(' ');
|
|
40
|
-
const path = spaceIdx >= 0 ? name.slice(spaceIdx + 1) : name;
|
|
41
|
-
return path.startsWith(prefix);
|
|
42
|
-
});
|
|
35
|
+
// Route name format: "METHOD /path" — prefix applies to path portion after first space
|
|
36
|
+
conditions.push("name LIKE ?");
|
|
37
|
+
params.push(`% ${input.prefix}%`);
|
|
43
38
|
}
|
|
44
39
|
|
|
45
|
-
// 3. Apply method filter
|
|
46
40
|
if (input.method) {
|
|
47
41
|
const methodUpper = input.method.toUpperCase();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return name.startsWith(methodUpper + ' ') || name.startsWith('ANY ');
|
|
51
|
-
});
|
|
42
|
+
conditions.push("(name LIKE ? OR name LIKE ?)");
|
|
43
|
+
params.push(`${methodUpper} %`, 'ANY %');
|
|
52
44
|
}
|
|
53
45
|
|
|
54
|
-
|
|
46
|
+
const sql = `SELECT * FROM nodes WHERE ${conditions.join(' AND ')}`;
|
|
47
|
+
const routeRows = db.prepare(sql).all(...params) as Record<string, unknown>[];
|
|
48
|
+
|
|
49
|
+
// 2. For each route, find handler via HANDLES_ROUTE edge (prepared once)
|
|
55
50
|
const handlerStmt = db.prepare(
|
|
56
51
|
`SELECT n.name, n.file_path, n.start_line FROM nodes n
|
|
57
52
|
JOIN edges e ON n.id = e.target_id
|
|
@@ -78,10 +73,13 @@ export function getMonographRouteMap(
|
|
|
78
73
|
if (input.includeMiddleware && input.repoPath && handlerRow?.name && handlerRow?.file_path) {
|
|
79
74
|
try {
|
|
80
75
|
const absPath = `${input.repoPath}/${handlerRow.file_path}`;
|
|
81
|
-
const
|
|
82
|
-
|
|
76
|
+
const st = statSync(absPath);
|
|
77
|
+
if (st.size <= MAX_FILE_BYTES) {
|
|
78
|
+
const source = readFileSync(absPath, 'utf-8');
|
|
79
|
+
middlewareChain = extractMiddlewareChain(source, handlerRow.name).middlewareNames;
|
|
80
|
+
}
|
|
83
81
|
} catch {
|
|
84
|
-
// File not found or
|
|
82
|
+
// File not found, unreadable, or too large — leave middlewareChain as []
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
1
|
+
import { readFileSync, statSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import type Database from 'better-sqlite3';
|
|
4
4
|
import {
|
|
@@ -24,8 +24,12 @@ export interface ShapeCheckResult {
|
|
|
24
24
|
|
|
25
25
|
// ── Internal helpers ───────────────────────────────────────────────────────────
|
|
26
26
|
|
|
27
|
+
const MAX_FILE_BYTES = 1_048_576; // 1 MiB guard
|
|
28
|
+
|
|
27
29
|
function safeReadFile(absPath: string): string {
|
|
28
30
|
try {
|
|
31
|
+
const st = statSync(absPath);
|
|
32
|
+
if (st.size > MAX_FILE_BYTES) return '';
|
|
29
33
|
return readFileSync(absPath, 'utf-8');
|
|
30
34
|
} catch {
|
|
31
35
|
return '';
|
|
@@ -64,19 +68,19 @@ export function getShapeCheck(
|
|
|
64
68
|
.prepare("SELECT * FROM nodes WHERE label = 'Route' AND file_path = ? LIMIT 1")
|
|
65
69
|
.get(options.file) as Record<string, unknown> | undefined;
|
|
66
70
|
} else if (options.route) {
|
|
67
|
-
// Search by name substring (name is like "GET /api/users")
|
|
68
|
-
//
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
71
|
+
// Search by name substring (name is like "GET /api/users") using SQL LIKE to avoid full scan
|
|
72
|
+
// The route path follows the first space, so we match anywhere after it via a LIKE pattern.
|
|
73
|
+
const likeTerm = `% ${options.route.toLowerCase()}%`;
|
|
74
|
+
routeRow = db
|
|
75
|
+
.prepare("SELECT * FROM nodes WHERE label = 'Route' AND lower(name) LIKE ? LIMIT 1")
|
|
76
|
+
.get(likeTerm) as Record<string, unknown> | undefined;
|
|
77
|
+
|
|
78
|
+
// Fallback: match anywhere in the name (e.g. no space prefix)
|
|
79
|
+
if (!routeRow) {
|
|
80
|
+
routeRow = db
|
|
81
|
+
.prepare("SELECT * FROM nodes WHERE label = 'Route' AND lower(name) LIKE ? LIMIT 1")
|
|
82
|
+
.get(`%${options.route.toLowerCase()}%`) as Record<string, unknown> | undefined;
|
|
83
|
+
}
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
if (!routeRow) {
|
package/src/parsers/loader.ts
CHANGED
|
@@ -70,14 +70,14 @@ export async function getParser(ext: string): Promise<{ parser: Parser; config:
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
try {
|
|
73
|
+
const lang = config.getLanguage();
|
|
74
|
+
if (!lang) throw new Error('getLanguage() returned undefined');
|
|
73
75
|
const parser = new Parser();
|
|
74
|
-
parser.setLanguage(
|
|
76
|
+
parser.setLanguage(lang);
|
|
75
77
|
parserCache.set(ext, parser);
|
|
76
78
|
return { parser, config };
|
|
77
|
-
} catch
|
|
78
|
-
// Grammar unavailable at runtime (ABI mismatch, native build failure, etc.).
|
|
79
|
-
// Log a warning and skip the language gracefully.
|
|
80
|
-
console.warn(`[monograph] warning: grammar for "${config.name}" (${ext}) could not be loaded: ${err}`);
|
|
79
|
+
} catch {
|
|
80
|
+
// Grammar unavailable at runtime (ABI mismatch, native build failure, etc.) — skip silently.
|
|
81
81
|
return null;
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -43,18 +43,22 @@ export async function runClusterOnly(db: Database.Database): Promise<ClusterOnly
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// Write community assignments back to DB
|
|
46
|
+
// Write community assignments back to DB and count in a single pass
|
|
47
47
|
const updateStmt = db.prepare('UPDATE nodes SET community_id = ? WHERE id = ?');
|
|
48
|
+
const communityIds = new Set<number>();
|
|
49
|
+
let nodeCount = 0;
|
|
48
50
|
const updateAll = db.transaction(() => {
|
|
49
|
-
for (const
|
|
51
|
+
for (const nodeId in communities) {
|
|
52
|
+
const commId = communities[nodeId];
|
|
50
53
|
updateStmt.run(commId, nodeId);
|
|
54
|
+
communityIds.add(commId);
|
|
55
|
+
nodeCount++;
|
|
51
56
|
}
|
|
52
57
|
});
|
|
53
58
|
updateAll();
|
|
54
59
|
|
|
55
|
-
const communityIds = new Set(Object.values(communities));
|
|
56
60
|
return {
|
|
57
61
|
communityCount: communityIds.size,
|
|
58
|
-
nodeCount
|
|
62
|
+
nodeCount,
|
|
59
63
|
};
|
|
60
64
|
}
|
|
@@ -38,11 +38,33 @@ function getCurrentCommitHash(repoPath: string): string | null {
|
|
|
38
38
|
export interface BuildOptions extends Partial<PipelineOptions> {
|
|
39
39
|
onProgress?: (p: PipelineProgress) => void;
|
|
40
40
|
force?: boolean;
|
|
41
|
+
/** When true, skip the full rebuild if the index is already fresh (matches HEAD). Default false. */
|
|
42
|
+
incremental?: boolean;
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export async function buildAsync(repoPath: string, options: BuildOptions = {}): Promise<void> {
|
|
44
46
|
const dbPath = resolve(join(repoPath, '.monomind', 'monograph.db'));
|
|
45
47
|
const fullOptions: PipelineOptions = { ...DEFAULT_OPTIONS, ...options };
|
|
48
|
+
|
|
49
|
+
// Incremental guard: if the caller requested skip-when-fresh and force is
|
|
50
|
+
// not set, check staleness before opening the DB for a full write cycle.
|
|
51
|
+
if (options.incremental && !options.force) {
|
|
52
|
+
const { existsSync: _existsSync } = await import('fs');
|
|
53
|
+
if (_existsSync(dbPath)) {
|
|
54
|
+
const { checkStaleness } = await import('../staleness/git-staleness.js');
|
|
55
|
+
const tmpDb = openDb(dbPath);
|
|
56
|
+
try {
|
|
57
|
+
const report = checkStaleness(tmpDb, resolve(repoPath));
|
|
58
|
+
if (!report.isStale && report.currentCommit !== null) {
|
|
59
|
+
options.onProgress?.({ phase: 'skip', message: 'Index is fresh — skipping rebuild' });
|
|
60
|
+
return; // Already up-to-date
|
|
61
|
+
}
|
|
62
|
+
} finally {
|
|
63
|
+
closeDb(tmpDb);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
const db = openDb(dbPath);
|
|
47
69
|
|
|
48
70
|
try {
|
|
@@ -34,6 +34,28 @@ function globToRegex(glob: string): RegExp {
|
|
|
34
34
|
return new RegExp('^' + pattern + '$');
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** Zone with its regex precompiled — built once per detectBoundaryViolations call. */
|
|
38
|
+
interface CompiledZone {
|
|
39
|
+
name: string;
|
|
40
|
+
re: RegExp;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Compile a ZoneConfig[] into CompiledZone[] once to avoid reconstructing RegExps per path. */
|
|
44
|
+
function compileZones(zones: ZoneConfig[]): CompiledZone[] {
|
|
45
|
+
return zones.map(z => ({ name: z.name, re: globToRegex(z.glob) }));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Classify a file path into a zone name using precompiled regexes.
|
|
50
|
+
* Returns null if no zone matches.
|
|
51
|
+
*/
|
|
52
|
+
function classifyZoneCompiled(filePath: string, compiled: CompiledZone[]): string | null {
|
|
53
|
+
for (const z of compiled) {
|
|
54
|
+
if (z.re.test(filePath)) return z.name;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
37
59
|
/**
|
|
38
60
|
* Load .monographrc.json from repoRoot. Returns empty config if not found or invalid.
|
|
39
61
|
*/
|
|
@@ -50,6 +72,8 @@ export function loadMonographConfig(repoRoot: string): MonographConfig {
|
|
|
50
72
|
|
|
51
73
|
/**
|
|
52
74
|
* Classify a file path into a zone name. Returns null if no zone matches.
|
|
75
|
+
* Compiles the glob patterns on every call — use classifyZoneCompiled with
|
|
76
|
+
* precompiled zones for hot paths.
|
|
53
77
|
*/
|
|
54
78
|
export function classifyZone(filePath: string, zones: ZoneConfig[]): string | null {
|
|
55
79
|
for (const zone of zones) {
|
|
@@ -78,6 +102,9 @@ export function detectBoundaryViolations(
|
|
|
78
102
|
(config.allowedImports ?? []).map(([from, to]) => `${from}→${to}`),
|
|
79
103
|
);
|
|
80
104
|
|
|
105
|
+
// Precompile zone regexes once — avoids re-constructing RegExp objects per file path.
|
|
106
|
+
const compiledZones = compileZones(zones);
|
|
107
|
+
|
|
81
108
|
const rows = db.prepare(`
|
|
82
109
|
SELECT e.id, e.relation,
|
|
83
110
|
ns.file_path AS src_path,
|
|
@@ -88,11 +115,22 @@ export function detectBoundaryViolations(
|
|
|
88
115
|
WHERE ns.file_path IS NOT NULL AND nt.file_path IS NOT NULL
|
|
89
116
|
`).all() as { id: string; relation: string; src_path: string; tgt_path: string }[];
|
|
90
117
|
|
|
118
|
+
// Cache per-path zone classification to avoid O(rows * zones) repeated regex tests.
|
|
119
|
+
const pathZoneCache = new Map<string, string | null>();
|
|
120
|
+
const classifyPath = (p: string): string | null => {
|
|
121
|
+
let zone = pathZoneCache.get(p);
|
|
122
|
+
if (zone === undefined) {
|
|
123
|
+
zone = classifyZoneCompiled(p, compiledZones);
|
|
124
|
+
pathZoneCache.set(p, zone);
|
|
125
|
+
}
|
|
126
|
+
return zone;
|
|
127
|
+
};
|
|
128
|
+
|
|
91
129
|
const violations: BoundaryViolation[] = [];
|
|
92
130
|
|
|
93
131
|
for (const row of rows) {
|
|
94
|
-
const fromZone =
|
|
95
|
-
const toZone =
|
|
132
|
+
const fromZone = classifyPath(row.src_path);
|
|
133
|
+
const toZone = classifyPath(row.tgt_path);
|
|
96
134
|
|
|
97
135
|
if (fromZone === null || toZone === null) continue;
|
|
98
136
|
if (fromZone === toZone) continue;
|
|
@@ -40,6 +40,70 @@ export function computeCohesion(
|
|
|
40
40
|
return internalCount / maxEdges;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Compute cohesion scores for all communities in a single O(N+E) pass.
|
|
45
|
+
*
|
|
46
|
+
* This replaces calling `computeCohesion` inside a loop, which was O(K*(N+E))
|
|
47
|
+
* because each call re-scanned all memberships (O(N)) and all edges (O(E)).
|
|
48
|
+
*
|
|
49
|
+
* @param memberships - nodeId → communityId map from the clustering step
|
|
50
|
+
* @param edges - all edges used for the graph (IMPORTS + resolved)
|
|
51
|
+
* @returns Map of communityId → cohesion score ∈ [0, 1]
|
|
52
|
+
*/
|
|
53
|
+
export function computeAllCohesionScores(
|
|
54
|
+
memberships: Map<string, number>,
|
|
55
|
+
edges: Array<{ sourceId: string; targetId: string }>,
|
|
56
|
+
): Map<number, number> {
|
|
57
|
+
// Single pass over memberships: build communityId → Set<nodeId>
|
|
58
|
+
const memberSets = new Map<number, Set<string>>();
|
|
59
|
+
for (const [nodeId, commId] of memberships) {
|
|
60
|
+
let s = memberSets.get(commId);
|
|
61
|
+
if (!s) {
|
|
62
|
+
s = new Set();
|
|
63
|
+
memberSets.set(commId, s);
|
|
64
|
+
}
|
|
65
|
+
s.add(nodeId);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Single pass over edges: count unique undirected internal edges per community
|
|
69
|
+
const internalEdgeCounts = new Map<number, number>();
|
|
70
|
+
const seenEdgeKeys = new Map<number, Set<string>>();
|
|
71
|
+
|
|
72
|
+
for (const e of edges) {
|
|
73
|
+
const commSrc = memberships.get(e.sourceId);
|
|
74
|
+
const commTgt = memberships.get(e.targetId);
|
|
75
|
+
if (commSrc === undefined || commTgt === undefined || commSrc !== commTgt) continue;
|
|
76
|
+
|
|
77
|
+
let seen = seenEdgeKeys.get(commSrc);
|
|
78
|
+
if (!seen) {
|
|
79
|
+
seen = new Set();
|
|
80
|
+
seenEdgeKeys.set(commSrc, seen);
|
|
81
|
+
}
|
|
82
|
+
const key = e.sourceId < e.targetId
|
|
83
|
+
? `${e.sourceId}\0${e.targetId}`
|
|
84
|
+
: `${e.targetId}\0${e.sourceId}`;
|
|
85
|
+
if (!seen.has(key)) {
|
|
86
|
+
seen.add(key);
|
|
87
|
+
internalEdgeCounts.set(commSrc, (internalEdgeCounts.get(commSrc) ?? 0) + 1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Compute final scores
|
|
92
|
+
const scores = new Map<number, number>();
|
|
93
|
+
for (const [commId, members] of memberSets) {
|
|
94
|
+
const n = members.size;
|
|
95
|
+
if (n <= 1) {
|
|
96
|
+
scores.set(commId, 1);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const maxEdges = (n * (n - 1)) / 2;
|
|
100
|
+
const internalCount = internalEdgeCounts.get(commId) ?? 0;
|
|
101
|
+
scores.set(commId, maxEdges > 0 ? internalCount / maxEdges : 1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return scores;
|
|
105
|
+
}
|
|
106
|
+
|
|
43
107
|
export const communitiesPhase: PipelinePhase<CommunitiesOutput> = {
|
|
44
108
|
name: 'communities',
|
|
45
109
|
deps: ['parse', 'cross-file', 'mro'],
|
|
@@ -75,11 +139,8 @@ export const communitiesPhase: PipelinePhase<CommunitiesOutput> = {
|
|
|
75
139
|
communityLabels.set(commId, `community-${commId}(${topNode.slice(0, 20)})`);
|
|
76
140
|
}
|
|
77
141
|
|
|
78
|
-
|
|
79
|
-
const cohesionScores =
|
|
80
|
-
for (const cid of communityIds) {
|
|
81
|
-
cohesionScores.set(cid, computeCohesion(cid, memberships, allUsedEdges));
|
|
82
|
-
}
|
|
142
|
+
// Compute all cohesion scores in one O(N+E) pass instead of O(K*(N+E))
|
|
143
|
+
const cohesionScores = computeAllCohesionScores(memberships, allUsedEdges);
|
|
83
144
|
|
|
84
145
|
return { memberships, communityLabels, cohesionScores };
|
|
85
146
|
},
|