@monoes/monograph 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.monomind/loops/mastermind-review-1782162423447.json +16 -0
- package/__tests__/mcp-tools/cypher.test.ts +10 -6
- package/__tests__/mcp-tools/detect-changes.test.ts +14 -11
- package/__tests__/pipeline/phases/wildcard-synthesis.test.ts +4 -4
- package/__tests__/search/embed-batch-config.test.ts +7 -7
- package/__tests__/search/hybrid-query.test.ts +3 -1
- package/__tests__/web/api.test.ts +1 -1
- package/dist/src/analysis/churn-cache.d.ts.map +1 -1
- package/dist/src/analysis/churn-cache.js +19 -6
- package/dist/src/analysis/churn-cache.js.map +1 -1
- package/dist/src/analysis/churn-trend.d.ts.map +1 -1
- package/dist/src/analysis/churn-trend.js +50 -6
- package/dist/src/analysis/churn-trend.js.map +1 -1
- package/dist/src/analysis/cluster-quality.d.ts +3 -0
- package/dist/src/analysis/cluster-quality.d.ts.map +1 -1
- package/dist/src/analysis/cluster-quality.js +18 -12
- package/dist/src/analysis/cluster-quality.js.map +1 -1
- package/dist/src/analysis/coverage-gaps.d.ts +3 -0
- package/dist/src/analysis/coverage-gaps.d.ts.map +1 -1
- package/dist/src/analysis/coverage-gaps.js +40 -1
- package/dist/src/analysis/coverage-gaps.js.map +1 -1
- package/dist/src/analysis/dep-classification.d.ts +2 -0
- package/dist/src/analysis/dep-classification.d.ts.map +1 -1
- package/dist/src/analysis/dep-classification.js +30 -0
- package/dist/src/analysis/dep-classification.js.map +1 -1
- package/dist/src/analysis/dependency-health.d.ts +4 -0
- package/dist/src/analysis/dependency-health.d.ts.map +1 -1
- package/dist/src/analysis/dependency-health.js +28 -0
- package/dist/src/analysis/dependency-health.js.map +1 -1
- package/dist/src/analysis/duplicate-exports.d.ts +9 -5
- package/dist/src/analysis/duplicate-exports.d.ts.map +1 -1
- package/dist/src/analysis/duplicate-exports.js +26 -2
- package/dist/src/analysis/duplicate-exports.js.map +1 -1
- package/dist/src/analysis/feature-flags.d.ts.map +1 -1
- package/dist/src/analysis/feature-flags.js +36 -17
- package/dist/src/analysis/feature-flags.js.map +1 -1
- package/dist/src/analysis/node-similarity.d.ts +19 -0
- package/dist/src/analysis/node-similarity.d.ts.map +1 -1
- package/dist/src/analysis/node-similarity.js +34 -3
- package/dist/src/analysis/node-similarity.js.map +1 -1
- package/dist/src/analysis/private-type-leaks.d.ts +4 -0
- package/dist/src/analysis/private-type-leaks.d.ts.map +1 -1
- package/dist/src/analysis/private-type-leaks.js +43 -2
- package/dist/src/analysis/private-type-leaks.js.map +1 -1
- package/dist/src/analysis/project-state.d.ts +2 -0
- package/dist/src/analysis/project-state.d.ts.map +1 -1
- package/dist/src/analysis/project-state.js +18 -7
- package/dist/src/analysis/project-state.js.map +1 -1
- package/dist/src/analysis/suppressions.d.ts.map +1 -1
- package/dist/src/analysis/suppressions.js +3 -1
- package/dist/src/analysis/suppressions.js.map +1 -1
- package/dist/src/analysis/trace.d.ts +5 -3
- package/dist/src/analysis/trace.d.ts.map +1 -1
- package/dist/src/analysis/trace.js +59 -53
- package/dist/src/analysis/trace.js.map +1 -1
- package/dist/src/analysis/unused-class-members.d.ts.map +1 -1
- package/dist/src/analysis/unused-class-members.js +7 -1
- package/dist/src/analysis/unused-class-members.js.map +1 -1
- package/dist/src/analysis/worker-pool.d.ts +3 -0
- package/dist/src/analysis/worker-pool.d.ts.map +1 -1
- package/dist/src/analysis/worker-pool.js +30 -8
- package/dist/src/analysis/worker-pool.js.map +1 -1
- package/dist/src/cli/augment.d.ts.map +1 -1
- package/dist/src/cli/augment.js +10 -3
- package/dist/src/cli/augment.js.map +1 -1
- package/dist/src/cli/skill-gen.js +3 -0
- package/dist/src/cli/skill-gen.js.map +1 -1
- package/dist/src/export/sarif.js +5 -5
- package/dist/src/export/sarif.js.map +1 -1
- package/dist/src/graph/analyzer.d.ts.map +1 -1
- package/dist/src/graph/analyzer.js +17 -2
- package/dist/src/graph/analyzer.js.map +1 -1
- package/dist/src/graph/author-analytics.d.ts +2 -0
- package/dist/src/graph/author-analytics.d.ts.map +1 -1
- package/dist/src/graph/author-analytics.js +39 -5
- package/dist/src/graph/author-analytics.js.map +1 -1
- package/dist/src/graph/baseline.d.ts +5 -0
- package/dist/src/graph/baseline.d.ts.map +1 -1
- package/dist/src/graph/baseline.js +65 -18
- package/dist/src/graph/baseline.js.map +1 -1
- package/dist/src/graph/clone-detector.d.ts.map +1 -1
- package/dist/src/graph/clone-detector.js +19 -33
- package/dist/src/graph/clone-detector.js.map +1 -1
- package/dist/src/graph/clone-families.d.ts +2 -0
- package/dist/src/graph/clone-families.d.ts.map +1 -1
- package/dist/src/graph/clone-families.js +38 -10
- package/dist/src/graph/clone-families.js.map +1 -1
- package/dist/src/graph/codeowners-extended.d.ts +17 -0
- package/dist/src/graph/codeowners-extended.d.ts.map +1 -1
- package/dist/src/graph/codeowners-extended.js +42 -0
- package/dist/src/graph/codeowners-extended.js.map +1 -1
- package/dist/src/graph/codeowners-gitlab.d.ts +21 -0
- package/dist/src/graph/codeowners-gitlab.d.ts.map +1 -1
- package/dist/src/graph/codeowners-gitlab.js +37 -0
- package/dist/src/graph/codeowners-gitlab.js.map +1 -1
- package/dist/src/graph/codeowners.d.ts +10 -0
- package/dist/src/graph/codeowners.d.ts.map +1 -1
- package/dist/src/graph/codeowners.js +24 -2
- package/dist/src/graph/codeowners.js.map +1 -1
- package/dist/src/graph/complexity.d.ts +10 -0
- package/dist/src/graph/complexity.d.ts.map +1 -1
- package/dist/src/graph/complexity.js +93 -43
- package/dist/src/graph/complexity.js.map +1 -1
- package/dist/src/graph/cross-reference-findings.d.ts.map +1 -1
- package/dist/src/graph/cross-reference-findings.js +19 -2
- package/dist/src/graph/cross-reference-findings.js.map +1 -1
- package/dist/src/graph/cross-reference.d.ts +4 -0
- package/dist/src/graph/cross-reference.d.ts.map +1 -1
- package/dist/src/graph/cross-reference.js +23 -0
- package/dist/src/graph/cross-reference.js.map +1 -1
- package/dist/src/graph/cycle-detection.d.ts +2 -0
- package/dist/src/graph/cycle-detection.d.ts.map +1 -1
- package/dist/src/graph/cycle-detection.js +26 -3
- package/dist/src/graph/cycle-detection.js.map +1 -1
- package/dist/src/graph/cycles.d.ts +11 -3
- package/dist/src/graph/cycles.d.ts.map +1 -1
- package/dist/src/graph/cycles.js +121 -83
- package/dist/src/graph/cycles.js.map +1 -1
- package/dist/src/graph/dead-code.d.ts +23 -0
- package/dist/src/graph/dead-code.d.ts.map +1 -1
- package/dist/src/graph/dead-code.js +53 -13
- package/dist/src/graph/dead-code.js.map +1 -1
- package/dist/src/graph/dependency-closure.d.ts.map +1 -1
- package/dist/src/graph/dependency-closure.js +20 -4
- package/dist/src/graph/dependency-closure.js.map +1 -1
- package/dist/src/graph/dfs.d.ts +14 -0
- package/dist/src/graph/dfs.d.ts.map +1 -1
- package/dist/src/graph/dfs.js +61 -0
- package/dist/src/graph/dfs.js.map +1 -1
- package/dist/src/graph/duplication-stats.d.ts.map +1 -1
- package/dist/src/graph/duplication-stats.js +19 -3
- package/dist/src/graph/duplication-stats.js.map +1 -1
- package/dist/src/graph/edge-filter.d.ts +2 -0
- package/dist/src/graph/edge-filter.d.ts.map +1 -1
- package/dist/src/graph/edge-filter.js +26 -12
- package/dist/src/graph/edge-filter.js.map +1 -1
- package/dist/src/graph/health-score.d.ts +8 -0
- package/dist/src/graph/health-score.d.ts.map +1 -1
- package/dist/src/graph/health-score.js +48 -17
- package/dist/src/graph/health-score.js.map +1 -1
- package/dist/src/graph/hotspots.d.ts +7 -0
- package/dist/src/graph/hotspots.d.ts.map +1 -1
- package/dist/src/graph/hotspots.js +50 -5
- package/dist/src/graph/hotspots.js.map +1 -1
- package/dist/src/graph/import-chain.d.ts +17 -1
- package/dist/src/graph/import-chain.d.ts.map +1 -1
- package/dist/src/graph/import-chain.js +68 -19
- package/dist/src/graph/import-chain.js.map +1 -1
- package/dist/src/graph/loader.d.ts.map +1 -1
- package/dist/src/graph/loader.js +9 -10
- package/dist/src/graph/loader.js.map +1 -1
- package/dist/src/graph/maintainability.d.ts +8 -0
- package/dist/src/graph/maintainability.d.ts.map +1 -1
- package/dist/src/graph/maintainability.js +65 -10
- package/dist/src/graph/maintainability.js.map +1 -1
- package/dist/src/graph/mirrored-dirs.d.ts +4 -0
- package/dist/src/graph/mirrored-dirs.d.ts.map +1 -1
- package/dist/src/graph/mirrored-dirs.js +48 -13
- package/dist/src/graph/mirrored-dirs.js.map +1 -1
- package/dist/src/graph/narrowing.d.ts +15 -0
- package/dist/src/graph/narrowing.d.ts.map +1 -1
- package/dist/src/graph/narrowing.js +33 -0
- package/dist/src/graph/narrowing.js.map +1 -1
- package/dist/src/graph/node-search.d.ts +3 -0
- package/dist/src/graph/node-search.d.ts.map +1 -1
- package/dist/src/graph/node-search.js +37 -4
- package/dist/src/graph/node-search.js.map +1 -1
- package/dist/src/graph/normalize-id.d.ts +4 -0
- package/dist/src/graph/normalize-id.d.ts.map +1 -1
- package/dist/src/graph/normalize-id.js +25 -2
- package/dist/src/graph/normalize-id.js.map +1 -1
- package/dist/src/graph/pagerank.d.ts +7 -0
- package/dist/src/graph/pagerank.d.ts.map +1 -1
- package/dist/src/graph/pagerank.js +49 -2
- package/dist/src/graph/pagerank.js.map +1 -1
- package/dist/src/graph/prune.d.ts +31 -0
- package/dist/src/graph/prune.d.ts.map +1 -1
- package/dist/src/graph/prune.js +65 -0
- package/dist/src/graph/prune.js.map +1 -1
- package/dist/src/graph/regex-search.d.ts +10 -2
- package/dist/src/graph/regex-search.d.ts.map +1 -1
- package/dist/src/graph/regex-search.js +33 -4
- package/dist/src/graph/regex-search.js.map +1 -1
- package/dist/src/graph/regression.d.ts +5 -0
- package/dist/src/graph/regression.d.ts.map +1 -1
- package/dist/src/graph/regression.js +45 -23
- package/dist/src/graph/regression.js.map +1 -1
- package/dist/src/graph/ripple-impact.d.ts +27 -0
- package/dist/src/graph/ripple-impact.d.ts.map +1 -1
- package/dist/src/graph/ripple-impact.js +55 -7
- package/dist/src/graph/ripple-impact.js.map +1 -1
- package/dist/src/graph/risk-profile.d.ts +7 -0
- package/dist/src/graph/risk-profile.d.ts.map +1 -1
- package/dist/src/graph/risk-profile.js +82 -48
- package/dist/src/graph/risk-profile.js.map +1 -1
- package/dist/src/graph/shingle-filter.d.ts.map +1 -1
- package/dist/src/graph/shingle-filter.js +25 -26
- package/dist/src/graph/shingle-filter.js.map +1 -1
- package/dist/src/graph/statistics.d.ts +19 -0
- package/dist/src/graph/statistics.d.ts.map +1 -1
- package/dist/src/graph/statistics.js +53 -40
- package/dist/src/graph/statistics.js.map +1 -1
- package/dist/src/graph/stats.d.ts +2 -0
- package/dist/src/graph/stats.d.ts.map +1 -1
- package/dist/src/graph/stats.js +54 -8
- package/dist/src/graph/stats.js.map +1 -1
- package/dist/src/graph/subgraph.d.ts +5 -0
- package/dist/src/graph/subgraph.d.ts.map +1 -1
- package/dist/src/graph/subgraph.js +73 -9
- package/dist/src/graph/subgraph.js.map +1 -1
- package/dist/src/graph/topo-sort.d.ts +15 -0
- package/dist/src/graph/topo-sort.d.ts.map +1 -1
- package/dist/src/graph/topo-sort.js +83 -10
- package/dist/src/graph/topo-sort.js.map +1 -1
- package/dist/src/graph/wcc.d.ts +29 -0
- package/dist/src/graph/wcc.d.ts.map +1 -1
- package/dist/src/graph/wcc.js +122 -39
- package/dist/src/graph/wcc.js.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp-tools/api-impact.d.ts.map +1 -1
- package/dist/src/mcp-tools/api-impact.js +13 -6
- package/dist/src/mcp-tools/api-impact.js.map +1 -1
- package/dist/src/mcp-tools/context.d.ts.map +1 -1
- package/dist/src/mcp-tools/context.js +19 -29
- package/dist/src/mcp-tools/context.js.map +1 -1
- package/dist/src/mcp-tools/cypher.d.ts +11 -0
- package/dist/src/mcp-tools/cypher.d.ts.map +1 -1
- package/dist/src/mcp-tools/cypher.js +13 -1
- package/dist/src/mcp-tools/cypher.js.map +1 -1
- package/dist/src/mcp-tools/detect-changes.d.ts.map +1 -1
- package/dist/src/mcp-tools/detect-changes.js +35 -20
- package/dist/src/mcp-tools/detect-changes.js.map +1 -1
- package/dist/src/mcp-tools/explain.d.ts.map +1 -1
- package/dist/src/mcp-tools/explain.js +4 -1
- package/dist/src/mcp-tools/explain.js.map +1 -1
- package/dist/src/mcp-tools/graph-query.d.ts +2 -0
- package/dist/src/mcp-tools/graph-query.d.ts.map +1 -1
- package/dist/src/mcp-tools/graph-query.js +20 -3
- package/dist/src/mcp-tools/graph-query.js.map +1 -1
- package/dist/src/mcp-tools/group-list.d.ts +1 -0
- package/dist/src/mcp-tools/group-list.d.ts.map +1 -1
- package/dist/src/mcp-tools/group-list.js +7 -9
- package/dist/src/mcp-tools/group-list.js.map +1 -1
- package/dist/src/mcp-tools/impact.d.ts.map +1 -1
- package/dist/src/mcp-tools/impact.js +20 -59
- package/dist/src/mcp-tools/impact.js.map +1 -1
- package/dist/src/mcp-tools/neighbors.d.ts.map +1 -1
- package/dist/src/mcp-tools/neighbors.js +24 -33
- package/dist/src/mcp-tools/neighbors.js.map +1 -1
- package/dist/src/mcp-tools/query.d.ts +2 -0
- package/dist/src/mcp-tools/query.d.ts.map +1 -1
- package/dist/src/mcp-tools/query.js +1 -0
- package/dist/src/mcp-tools/query.js.map +1 -1
- package/dist/src/mcp-tools/rename.d.ts.map +1 -1
- package/dist/src/mcp-tools/rename.js +12 -7
- package/dist/src/mcp-tools/rename.js.map +1 -1
- package/dist/src/mcp-tools/route-map.d.ts.map +1 -1
- package/dist/src/mcp-tools/route-map.js +19 -23
- package/dist/src/mcp-tools/route-map.js.map +1 -1
- package/dist/src/mcp-tools/shape-check.d.ts.map +1 -1
- package/dist/src/mcp-tools/shape-check.js +17 -13
- package/dist/src/mcp-tools/shape-check.js.map +1 -1
- package/dist/src/parsers/loader.js +6 -5
- package/dist/src/parsers/loader.js.map +1 -1
- package/dist/src/pipeline/cluster-only.d.ts.map +1 -1
- package/dist/src/pipeline/cluster-only.js +8 -4
- package/dist/src/pipeline/cluster-only.js.map +1 -1
- package/dist/src/pipeline/orchestrator.d.ts +2 -0
- package/dist/src/pipeline/orchestrator.d.ts.map +1 -1
- package/dist/src/pipeline/orchestrator.js +19 -0
- package/dist/src/pipeline/orchestrator.js.map +1 -1
- package/dist/src/pipeline/phases/boundary.d.ts +2 -0
- package/dist/src/pipeline/phases/boundary.d.ts.map +1 -1
- package/dist/src/pipeline/phases/boundary.js +31 -2
- package/dist/src/pipeline/phases/boundary.js.map +1 -1
- package/dist/src/pipeline/phases/communities.d.ts +14 -0
- package/dist/src/pipeline/phases/communities.d.ts.map +1 -1
- package/dist/src/pipeline/phases/communities.js +58 -5
- package/dist/src/pipeline/phases/communities.js.map +1 -1
- package/dist/src/pipeline/phases/contextual-proximity.d.ts.map +1 -1
- package/dist/src/pipeline/phases/contextual-proximity.js +21 -14
- package/dist/src/pipeline/phases/contextual-proximity.js.map +1 -1
- package/dist/src/pipeline/phases/cross-file.d.ts.map +1 -1
- package/dist/src/pipeline/phases/cross-file.js +35 -14
- package/dist/src/pipeline/phases/cross-file.js.map +1 -1
- package/dist/src/pipeline/phases/field-access.d.ts.map +1 -1
- package/dist/src/pipeline/phases/field-access.js +9 -4
- package/dist/src/pipeline/phases/field-access.js.map +1 -1
- package/dist/src/pipeline/phases/god-nodes.d.ts +4 -0
- package/dist/src/pipeline/phases/god-nodes.d.ts.map +1 -1
- package/dist/src/pipeline/phases/god-nodes.js +43 -9
- package/dist/src/pipeline/phases/god-nodes.js.map +1 -1
- package/dist/src/pipeline/phases/import-resolver.d.ts +11 -0
- package/dist/src/pipeline/phases/import-resolver.d.ts.map +1 -1
- package/dist/src/pipeline/phases/import-resolver.js +34 -7
- package/dist/src/pipeline/phases/import-resolver.js.map +1 -1
- package/dist/src/pipeline/phases/leiden.d.ts.map +1 -1
- package/dist/src/pipeline/phases/leiden.js +27 -19
- package/dist/src/pipeline/phases/leiden.js.map +1 -1
- package/dist/src/pipeline/phases/markdown.d.ts.map +1 -1
- package/dist/src/pipeline/phases/markdown.js +10 -5
- package/dist/src/pipeline/phases/markdown.js.map +1 -1
- package/dist/src/pipeline/phases/pdf-parse.d.ts.map +1 -1
- package/dist/src/pipeline/phases/pdf-parse.js +18 -3
- package/dist/src/pipeline/phases/pdf-parse.js.map +1 -1
- package/dist/src/pipeline/phases/processes.d.ts.map +1 -1
- package/dist/src/pipeline/phases/processes.js +26 -9
- package/dist/src/pipeline/phases/processes.js.map +1 -1
- package/dist/src/pipeline/phases/re-export-propagation.d.ts.map +1 -1
- package/dist/src/pipeline/phases/re-export-propagation.js +33 -14
- package/dist/src/pipeline/phases/re-export-propagation.js.map +1 -1
- package/dist/src/pipeline/phases/reachability.d.ts.map +1 -1
- package/dist/src/pipeline/phases/reachability.js +42 -26
- package/dist/src/pipeline/phases/reachability.js.map +1 -1
- package/dist/src/pipeline/phases/routes.d.ts.map +1 -1
- package/dist/src/pipeline/phases/routes.js +30 -21
- package/dist/src/pipeline/phases/routes.js.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.d.ts.map +1 -1
- package/dist/src/pipeline/phases/scope-resolution.js +93 -63
- package/dist/src/pipeline/phases/scope-resolution.js.map +1 -1
- package/dist/src/pipeline/runner.d.ts +4 -0
- package/dist/src/pipeline/runner.d.ts.map +1 -1
- package/dist/src/pipeline/runner.js +30 -11
- package/dist/src/pipeline/runner.js.map +1 -1
- package/dist/src/query/cypher-parser.d.ts +20 -0
- package/dist/src/query/cypher-parser.d.ts.map +1 -1
- package/dist/src/query/cypher-parser.js +73 -0
- package/dist/src/query/cypher-parser.js.map +1 -1
- package/dist/src/search/embed-batch.d.ts.map +1 -1
- package/dist/src/search/embed-batch.js +4 -1
- package/dist/src/search/embed-batch.js.map +1 -1
- package/dist/src/search/hybrid-query.d.ts +4 -0
- package/dist/src/search/hybrid-query.d.ts.map +1 -1
- package/dist/src/search/hybrid-query.js +5 -1
- package/dist/src/search/hybrid-query.js.map +1 -1
- package/dist/src/staleness/git-staleness.js +3 -3
- package/dist/src/staleness/git-staleness.js.map +1 -1
- package/dist/src/storage/db.d.ts.map +1 -1
- package/dist/src/storage/db.js +12 -0
- package/dist/src/storage/db.js.map +1 -1
- package/dist/src/storage/edge-store.d.ts +2 -0
- package/dist/src/storage/edge-store.d.ts.map +1 -1
- package/dist/src/storage/edge-store.js +26 -1
- package/dist/src/storage/edge-store.js.map +1 -1
- package/dist/src/storage/embedding-store.d.ts +16 -0
- package/dist/src/storage/embedding-store.d.ts.map +1 -1
- package/dist/src/storage/embedding-store.js +26 -2
- package/dist/src/storage/embedding-store.js.map +1 -1
- package/dist/src/storage/file-cache.d.ts +5 -0
- package/dist/src/storage/file-cache.d.ts.map +1 -1
- package/dist/src/storage/file-cache.js +35 -9
- package/dist/src/storage/file-cache.js.map +1 -1
- package/dist/src/storage/fts-store.d.ts +4 -0
- package/dist/src/storage/fts-store.d.ts.map +1 -1
- package/dist/src/storage/fts-store.js +8 -3
- package/dist/src/storage/fts-store.js.map +1 -1
- package/dist/src/storage/node-store.d.ts +2 -0
- package/dist/src/storage/node-store.d.ts.map +1 -1
- package/dist/src/storage/node-store.js +28 -1
- package/dist/src/storage/node-store.js.map +1 -1
- package/dist/src/wiki/wiki-generator.d.ts.map +1 -1
- package/dist/src/wiki/wiki-generator.js +5 -1
- package/dist/src/wiki/wiki-generator.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/__tests__/pipeline/runner.parallel.test.ts +2 -2
- package/src/analysis/churn-cache.ts +18 -7
- package/src/analysis/churn-trend.ts +35 -6
- package/src/analysis/cluster-quality.ts +20 -11
- package/src/analysis/coverage-gaps.ts +44 -1
- package/src/analysis/dep-classification.ts +34 -0
- package/src/analysis/dependency-health.ts +25 -0
- package/src/analysis/duplicate-exports.ts +40 -4
- package/src/analysis/feature-flags.ts +37 -17
- package/src/analysis/node-similarity.ts +45 -4
- package/src/analysis/private-type-leaks.ts +49 -2
- package/src/analysis/project-state.ts +19 -9
- package/src/analysis/suppressions.ts +4 -1
- package/src/analysis/trace.ts +73 -54
- package/src/analysis/unused-class-members.ts +8 -1
- package/src/analysis/worker-pool.ts +40 -8
- package/src/cli/augment.ts +10 -3
- package/src/cli/skill-gen.ts +3 -0
- package/src/export/sarif.ts +5 -5
- package/src/graph/analyzer.ts +26 -2
- package/src/graph/author-analytics.ts +45 -5
- package/src/graph/baseline.ts +78 -24
- package/src/graph/clone-detector.ts +22 -36
- package/src/graph/clone-families.ts +34 -10
- package/src/graph/codeowners-extended.ts +54 -0
- package/src/graph/codeowners-gitlab.ts +46 -0
- package/src/graph/codeowners.ts +33 -2
- package/src/graph/complexity.ts +102 -47
- package/src/graph/cross-reference-findings.ts +23 -2
- package/src/graph/cross-reference.ts +26 -0
- package/src/graph/cycle-detection.ts +22 -3
- package/src/graph/cycles.ts +155 -96
- package/src/graph/dead-code.ts +76 -17
- package/src/graph/dependency-closure.ts +24 -7
- package/src/graph/dfs.ts +78 -0
- package/src/graph/duplication-stats.ts +17 -6
- package/src/graph/edge-filter.ts +26 -12
- package/src/graph/health-score.ts +47 -17
- package/src/graph/hotspots.ts +57 -8
- package/src/graph/import-chain.ts +89 -19
- package/src/graph/loader.ts +10 -7
- package/src/graph/maintainability.ts +72 -22
- package/src/graph/mirrored-dirs.ts +45 -14
- package/src/graph/narrowing.ts +44 -0
- package/src/graph/node-search.ts +50 -4
- package/src/graph/normalize-id.ts +26 -2
- package/src/graph/pagerank.ts +70 -2
- package/src/graph/prune.ts +76 -0
- package/src/graph/regex-search.ts +38 -4
- package/src/graph/regression.ts +48 -23
- package/src/graph/ripple-impact.ts +71 -11
- package/src/graph/risk-profile.ts +98 -53
- package/src/graph/shingle-filter.ts +33 -27
- package/src/graph/statistics.ts +73 -45
- package/src/graph/stats.ts +50 -16
- package/src/graph/subgraph.ts +97 -25
- package/src/graph/topo-sort.ts +101 -16
- package/src/graph/wcc.ts +146 -37
- package/src/index.ts +1 -1
- package/src/mcp-tools/api-impact.ts +13 -6
- package/src/mcp-tools/context.ts +31 -40
- package/src/mcp-tools/cypher.ts +14 -1
- package/src/mcp-tools/detect-changes.ts +38 -23
- package/src/mcp-tools/explain.ts +4 -1
- package/src/mcp-tools/graph-query.ts +24 -3
- package/src/mcp-tools/group-list.ts +9 -11
- package/src/mcp-tools/impact.ts +26 -66
- package/src/mcp-tools/neighbors.ts +34 -39
- package/src/mcp-tools/query.ts +3 -0
- package/src/mcp-tools/rename.ts +9 -8
- package/src/mcp-tools/route-map.ts +21 -23
- package/src/mcp-tools/shape-check.ts +18 -14
- package/src/parsers/loader.ts +5 -5
- package/src/pipeline/cluster-only.ts +8 -4
- package/src/pipeline/orchestrator.ts +22 -0
- package/src/pipeline/phases/boundary.ts +40 -2
- package/src/pipeline/phases/communities.ts +66 -5
- package/src/pipeline/phases/contextual-proximity.ts +15 -14
- package/src/pipeline/phases/cross-file.ts +35 -14
- package/src/pipeline/phases/field-access.ts +11 -4
- package/src/pipeline/phases/god-nodes.ts +47 -10
- package/src/pipeline/phases/import-resolver.ts +37 -5
- package/src/pipeline/phases/leiden.ts +25 -18
- package/src/pipeline/phases/markdown.ts +12 -7
- package/src/pipeline/phases/pdf-parse.ts +19 -2
- package/src/pipeline/phases/processes.ts +27 -15
- package/src/pipeline/phases/re-export-propagation.ts +40 -18
- package/src/pipeline/phases/reachability.ts +39 -24
- package/src/pipeline/phases/routes.ts +32 -21
- package/src/pipeline/phases/scope-resolution.ts +110 -71
- package/src/pipeline/runner.ts +28 -11
- package/src/query/cypher-parser.ts +83 -0
- package/src/search/embed-batch.ts +4 -1
- package/src/search/hybrid-query.ts +11 -1
- package/src/staleness/git-staleness.ts +3 -3
- package/src/storage/db.ts +11 -0
- package/src/storage/edge-store.ts +34 -1
- package/src/storage/embedding-store.ts +38 -6
- package/src/storage/file-cache.ts +35 -21
- package/src/storage/fts-store.ts +12 -3
- package/src/storage/node-store.ts +39 -1
- package/src/wiki/wiki-generator.ts +5 -1
- package/src/coverage/runtime.ts +0 -249
|
@@ -22,19 +22,26 @@ export const contextualProximityPhase: PipelinePhase<ContextualProximityOutput>
|
|
|
22
22
|
|
|
23
23
|
if (rows.length === 0) return { coOccursEdges: 0, conceptsScored: 0 };
|
|
24
24
|
|
|
25
|
-
// Group concepts by section
|
|
25
|
+
// Group concepts by section using ??= to avoid has+get pattern
|
|
26
26
|
const sectionConcepts = new Map<string, string[]>();
|
|
27
27
|
for (const { source_id, target_id } of rows) {
|
|
28
|
-
|
|
29
|
-
sectionConcepts.
|
|
28
|
+
let list = sectionConcepts.get(source_id);
|
|
29
|
+
if (!list) { list = []; sectionConcepts.set(source_id, list); }
|
|
30
|
+
list.push(target_id);
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
// Count concept co-occurrences across all sections
|
|
33
|
+
// Count concept co-occurrences across all sections.
|
|
34
|
+
// Accumulate conceptDegree in the same pass to avoid a second loop over sectionConcepts.
|
|
35
|
+
// Use a ternary comparison instead of [a,b].sort() to avoid per-pair array allocation.
|
|
33
36
|
const coOccur = new Map<string, number>();
|
|
37
|
+
const conceptDegree = new Map<string, number>();
|
|
34
38
|
for (const concepts of sectionConcepts.values()) {
|
|
35
39
|
for (let i = 0; i < concepts.length; i++) {
|
|
40
|
+
conceptDegree.set(concepts[i], (conceptDegree.get(concepts[i]) ?? 0) + 1);
|
|
36
41
|
for (let j = i + 1; j < concepts.length; j++) {
|
|
37
|
-
|
|
42
|
+
// Avoid allocating a 2-element sort array — ternary comparison is equivalent
|
|
43
|
+
const a = concepts[i] <= concepts[j] ? concepts[i] : concepts[j];
|
|
44
|
+
const b = concepts[i] <= concepts[j] ? concepts[j] : concepts[i];
|
|
38
45
|
const key = `${a}::${b}`;
|
|
39
46
|
coOccur.set(key, (coOccur.get(key) ?? 0) + 1);
|
|
40
47
|
}
|
|
@@ -58,16 +65,10 @@ export const contextualProximityPhase: PipelinePhase<ContextualProximityOutput>
|
|
|
58
65
|
});
|
|
59
66
|
}
|
|
60
67
|
|
|
61
|
-
// Score concept importance 1-5 based on normalized degree (sections it appears in)
|
|
62
|
-
const conceptDegree = new Map<string, number>();
|
|
63
|
-
for (const concepts of sectionConcepts.values()) {
|
|
64
|
-
for (const cid of concepts) {
|
|
65
|
-
conceptDegree.set(cid, (conceptDegree.get(cid) ?? 0) + 1);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
68
|
if (conceptDegree.size > 0) {
|
|
70
|
-
|
|
69
|
+
// Compute maxDeg with a loop to avoid spread array allocation from [...values()]
|
|
70
|
+
let maxDeg = 0;
|
|
71
|
+
for (const deg of conceptDegree.values()) { if (deg > maxDeg) maxDeg = deg; }
|
|
71
72
|
const update = ctx.db.prepare(
|
|
72
73
|
`UPDATE nodes SET properties = json_set(COALESCE(properties, '{}'), '$.importance', ?) WHERE id = ?`,
|
|
73
74
|
);
|
|
@@ -16,27 +16,49 @@ export const crossFilePhase: PipelinePhase<CrossFileOutput> = {
|
|
|
16
16
|
const { allEdges, symbolNodes } = deps.get('parse') as ParseOutput;
|
|
17
17
|
const { fileNodes } = deps.get('structure') as StructureOutput;
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
/** Extract the last path segment of a '/'-separated string (faster than split+pop). */
|
|
20
|
+
function lastSegment(s: string): string {
|
|
21
|
+
const idx = s.lastIndexOf('/');
|
|
22
|
+
return idx === -1 ? s : s.slice(idx + 1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Strip leading `import_` prefix, then return the last path segment. */
|
|
26
|
+
function targetBasename(rawId: string): string {
|
|
27
|
+
const stripped = rawId.startsWith('import_') ? rawId.slice(7) : rawId;
|
|
28
|
+
return lastSegment(stripped);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Symbol name → symbol node ID.
|
|
32
|
+
// Pre-key lowercase variants so IMPORTS resolution is a single Map.get() instead of two.
|
|
20
33
|
const nameIndex = new Map<string, string>();
|
|
21
34
|
for (const node of symbolNodes) {
|
|
22
35
|
nameIndex.set(node.name, node.id);
|
|
23
|
-
|
|
36
|
+
const lower = node.name.toLowerCase();
|
|
37
|
+
if (lower !== node.name) nameIndex.set(lower, node.id);
|
|
38
|
+
if (node.normLabel) {
|
|
39
|
+
nameIndex.set(node.normLabel, node.id);
|
|
40
|
+
const normLower = node.normLabel.toLowerCase();
|
|
41
|
+
if (normLower !== node.normLabel) nameIndex.set(normLower, node.id);
|
|
42
|
+
}
|
|
24
43
|
}
|
|
25
44
|
|
|
26
|
-
// Basename (without extension) → File node ID, for resolving RE_EXPORTS
|
|
45
|
+
// Basename (without extension) → File node ID, for resolving RE_EXPORTS.
|
|
46
|
+
// Use lastIndexOf to avoid allocating a full split array per file path.
|
|
27
47
|
const fileBasenameIndex = new Map<string, string>();
|
|
28
48
|
for (const fileNode of fileNodes) {
|
|
29
|
-
const basename = (fileNode.filePath ?? '').
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
fileBasenameIndex.set(basename
|
|
49
|
+
const basename = lastSegment(fileNode.filePath ?? '').toLowerCase();
|
|
50
|
+
const dotIdx = basename.lastIndexOf('.');
|
|
51
|
+
const noExt = dotIdx === -1 ? basename : basename.slice(0, dotIdx);
|
|
52
|
+
fileBasenameIndex.set(basename, fileNode.id);
|
|
53
|
+
if (noExt !== basename) fileBasenameIndex.set(noExt, fileNode.id);
|
|
33
54
|
}
|
|
34
55
|
|
|
35
56
|
const resolvedEdges: MonographEdge[] = [];
|
|
36
57
|
|
|
37
58
|
for (const edge of allEdges) {
|
|
38
59
|
if (edge.relation === 'IMPORTS') {
|
|
39
|
-
const targetName = edge.targetId
|
|
60
|
+
const targetName = targetBasename(edge.targetId);
|
|
61
|
+
// Single lookup — lowercase variants are pre-keyed in nameIndex
|
|
40
62
|
const resolvedId = nameIndex.get(targetName) ?? nameIndex.get(targetName.toLowerCase());
|
|
41
63
|
|
|
42
64
|
if (resolvedId && resolvedId !== edge.targetId) {
|
|
@@ -49,12 +71,11 @@ export const crossFilePhase: PipelinePhase<CrossFileOutput> = {
|
|
|
49
71
|
});
|
|
50
72
|
}
|
|
51
73
|
} else if (edge.relation === 'RE_EXPORTS') {
|
|
52
|
-
// Resolve to a File node ID
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
const noExt = basename.
|
|
56
|
-
const resolvedId = fileBasenameIndex.get(basename.
|
|
57
|
-
?? fileBasenameIndex.get(noExt.toLowerCase());
|
|
74
|
+
// Resolve to a File node ID using the pre-built basename index
|
|
75
|
+
const basename = targetBasename(edge.targetId).toLowerCase();
|
|
76
|
+
const dotIdx = basename.lastIndexOf('.');
|
|
77
|
+
const noExt = dotIdx === -1 ? basename : basename.slice(0, dotIdx);
|
|
78
|
+
const resolvedId = fileBasenameIndex.get(basename) ?? fileBasenameIndex.get(noExt);
|
|
58
79
|
|
|
59
80
|
if (resolvedId && resolvedId !== edge.targetId) {
|
|
60
81
|
resolvedEdges.push({
|
|
@@ -16,12 +16,19 @@ export function extractFieldAccesses(source: string, varName: string, filePath:
|
|
|
16
16
|
const lines = source.split('\n');
|
|
17
17
|
const escapedName = escapeRegexChars(varName);
|
|
18
18
|
|
|
19
|
+
// Hoist regex construction outside the loop — both patterns are fixed for the
|
|
20
|
+
// entire call, so creating them once avoids O(lines) RegExp allocations.
|
|
21
|
+
// Write: varName.field = or varName.field +=/-=/*=
|
|
22
|
+
const writeRe = new RegExp(`\\b${escapedName}\\.(\\w+)\\s*(?:\\+|-|\\*|\\/|%)?=(?!=)`, 'g');
|
|
23
|
+
// Read: varName.field NOT followed by assignment operator
|
|
24
|
+
const readRe = new RegExp(`\\b${escapedName}\\.(\\w+)`, 'g');
|
|
25
|
+
|
|
19
26
|
for (let i = 0; i < lines.length; i++) {
|
|
20
27
|
const line = lines[i];
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
|
|
29
|
+
// Reset stateful lastIndex before each line (required for reused /g regexes).
|
|
30
|
+
writeRe.lastIndex = 0;
|
|
31
|
+
readRe.lastIndex = 0;
|
|
25
32
|
|
|
26
33
|
const writeFields = new Set<string>();
|
|
27
34
|
let m: RegExpExecArray | null;
|
|
@@ -53,16 +53,17 @@ export const godNodesPhase: PipelinePhase<GodNodesOutput> = {
|
|
|
53
53
|
inDeg.set(e.targetId, (inDeg.get(e.targetId) ?? 0) + 1);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// Compute degree distributions from all symbol nodes
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
.
|
|
64
|
-
|
|
65
|
-
|
|
56
|
+
// Compute degree distributions from all symbol nodes in a single pass
|
|
57
|
+
// (avoids two separate filter + map passes over potentially large arrays)
|
|
58
|
+
const allFanIn: number[] = [];
|
|
59
|
+
const allFanOut: number[] = [];
|
|
60
|
+
for (const n of symbolNodes) {
|
|
61
|
+
if (EXCLUDED_LABELS.has(n.label)) continue;
|
|
62
|
+
allFanIn.push(inDeg.get(n.id) ?? 0);
|
|
63
|
+
allFanOut.push(outDeg.get(n.id) ?? 0);
|
|
64
|
+
}
|
|
65
|
+
allFanIn.sort((a, b) => a - b);
|
|
66
|
+
allFanOut.sort((a, b) => a - b);
|
|
66
67
|
|
|
67
68
|
const thresholds: GodNodesThresholds = {
|
|
68
69
|
p75FanIn: percentile(allFanIn, 75),
|
|
@@ -116,3 +117,39 @@ export const godNodesPhase: PipelinePhase<GodNodesOutput> = {
|
|
|
116
117
|
return { godNodes, thresholds };
|
|
117
118
|
},
|
|
118
119
|
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Format god-node results as structured text with file:line hints for LLM navigation.
|
|
123
|
+
*/
|
|
124
|
+
export function formatGodNodes(output: GodNodesOutput): string {
|
|
125
|
+
const { godNodes, thresholds } = output;
|
|
126
|
+
if (godNodes.length === 0) {
|
|
127
|
+
return 'god-nodes: none\nstatus: no high-centrality nodes detected\n';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const lines: string[] = [
|
|
131
|
+
`god-nodes: ${godNodes.length} high-centrality node(s) detected`,
|
|
132
|
+
`thresholds: fanIn p75=${thresholds.p75FanIn} p90=${thresholds.p90FanIn} p95=${thresholds.p95FanIn}; fanOut p75=${thresholds.p75FanOut} p90=${thresholds.p90FanOut}`,
|
|
133
|
+
'',
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
for (let i = 0; i < godNodes.length; i++) {
|
|
137
|
+
const n = godNodes[i];
|
|
138
|
+
const loc = n.filePath
|
|
139
|
+
? `${n.filePath}${n.startLine !== undefined ? `:${n.startLine}` : ''}`
|
|
140
|
+
: n.id;
|
|
141
|
+
lines.push(`[${i + 1}] ${n.name} (${n.label}) — ${n.category}`);
|
|
142
|
+
lines.push(` file: ${loc}`);
|
|
143
|
+
lines.push(` degree: ${n.degree} (fanIn=${n.inDegree}, fanOut=${n.outDegree})`);
|
|
144
|
+
for (const f of n.contributingFactors) {
|
|
145
|
+
lines.push(` factor: ${f.metric}=${f.value} (threshold=${f.threshold})`);
|
|
146
|
+
}
|
|
147
|
+
lines.push('');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const bridgeCount = godNodes.filter(n => n.category === 'BRIDGE_NODE').length;
|
|
151
|
+
const highCentralityCount = godNodes.length - bridgeCount;
|
|
152
|
+
lines.push(`summary: ${highCentralityCount} HIGH_CENTRALITY, ${bridgeCount} BRIDGE_NODE`);
|
|
153
|
+
|
|
154
|
+
return lines.join('\n');
|
|
155
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
2
2
|
import { join, resolve } from 'path';
|
|
3
3
|
import type { PipelinePhase, PipelineContext } from '../types.js';
|
|
4
4
|
|
|
@@ -18,7 +18,7 @@ function globWorkspacePattern(repoPath: string, pattern: string): string[] {
|
|
|
18
18
|
}
|
|
19
19
|
const [base] = pattern.split('/*');
|
|
20
20
|
const baseDir = join(repoPath, base ?? '');
|
|
21
|
-
|
|
21
|
+
// Skip existsSync — readdirSync will throw ENOENT which the catch already handles.
|
|
22
22
|
try {
|
|
23
23
|
return readdirSync(baseDir, { withFileTypes: true })
|
|
24
24
|
.filter(d => d.isDirectory())
|
|
@@ -30,8 +30,6 @@ function globWorkspacePattern(repoPath: string, pattern: string): string[] {
|
|
|
30
30
|
|
|
31
31
|
export function detectWorkspacePackages(repoPath: string): WorkspacePackage[] {
|
|
32
32
|
const pkgPath = join(repoPath, 'package.json');
|
|
33
|
-
if (!existsSync(pkgPath)) return [];
|
|
34
|
-
|
|
35
33
|
let workspaceGlobs: string[] = [];
|
|
36
34
|
try {
|
|
37
35
|
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
@@ -47,8 +45,8 @@ export function detectWorkspacePackages(repoPath: string): WorkspacePackage[] {
|
|
|
47
45
|
const dirs = globWorkspacePattern(repoPath, glob);
|
|
48
46
|
for (const dir of dirs) {
|
|
49
47
|
const subPkgPath = join(dir, 'package.json');
|
|
50
|
-
if (!existsSync(subPkgPath)) continue;
|
|
51
48
|
try {
|
|
49
|
+
// existsSync guard removed — readFileSync throws ENOENT which the catch handles.
|
|
52
50
|
const subPkg = JSON.parse(readFileSync(subPkgPath, 'utf8'));
|
|
53
51
|
if (subPkg.name) {
|
|
54
52
|
result.push({ name: subPkg.name, path: resolve(dir) });
|
|
@@ -72,6 +70,40 @@ export function resolveWorkspaceImport(
|
|
|
72
70
|
return null;
|
|
73
71
|
}
|
|
74
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Build a Map<packageName, packagePath> index from a WorkspacePackage array.
|
|
75
|
+
* Use this when resolveWorkspaceImport will be called many times (e.g. per import
|
|
76
|
+
* statement across a whole repo) to reduce resolution cost from O(N*I) to O(N+I).
|
|
77
|
+
*/
|
|
78
|
+
export function buildPackageIndex(packages: WorkspacePackage[]): Map<string, string> {
|
|
79
|
+
const index = new Map<string, string>();
|
|
80
|
+
for (const pkg of packages) {
|
|
81
|
+
index.set(pkg.name, pkg.path);
|
|
82
|
+
}
|
|
83
|
+
return index;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* O(1) workspace import resolution using a pre-built package index.
|
|
88
|
+
* Falls back to prefix scan only for sub-path imports (e.g. `pkg/subpath`).
|
|
89
|
+
*/
|
|
90
|
+
export function resolveWorkspaceImportFromIndex(
|
|
91
|
+
importSpecifier: string,
|
|
92
|
+
index: Map<string, string>,
|
|
93
|
+
): string | null {
|
|
94
|
+
// Exact match: O(1)
|
|
95
|
+
const exact = index.get(importSpecifier);
|
|
96
|
+
if (exact !== undefined) return exact;
|
|
97
|
+
// Sub-path import: find the matching package name prefix
|
|
98
|
+
const slash = importSpecifier.indexOf('/', importSpecifier.startsWith('@') ? importSpecifier.indexOf('/') + 1 : 0);
|
|
99
|
+
if (slash !== -1) {
|
|
100
|
+
const pkgName = importSpecifier.slice(0, slash);
|
|
101
|
+
const path = index.get(pkgName);
|
|
102
|
+
if (path !== undefined) return path;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
75
107
|
export const importResolverPhase: PipelinePhase<ImportResolverOutput> = {
|
|
76
108
|
name: 'import-resolver',
|
|
77
109
|
deps: ['cross-file'],
|
|
@@ -26,11 +26,18 @@ export function leiden(graph: Graph, _options: { seed?: number } = {}): Record<s
|
|
|
26
26
|
// Phase 1: Louvain with deterministic settings
|
|
27
27
|
const communities: Record<string, number> = louvain(graph, { randomWalk: false });
|
|
28
28
|
|
|
29
|
-
// Phase 2: Refinement — merge singletons into their largest neighbor's community
|
|
29
|
+
// Phase 2: Refinement — merge singletons into their largest neighbor's community.
|
|
30
|
+
// Single pass over Object.entries to build both communitySizes and the singletonNodes Set,
|
|
31
|
+
// avoiding a separate Object.values scan. The singletonNodes Set enables O(1) skip in the
|
|
32
|
+
// refinement loop below instead of a map.get(commId) <= 1 test per node.
|
|
30
33
|
const communitySizes = new Map<number, number>();
|
|
31
34
|
for (const comm of Object.values(communities)) {
|
|
32
35
|
communitySizes.set(comm, (communitySizes.get(comm) ?? 0) + 1);
|
|
33
36
|
}
|
|
37
|
+
const singletonNodes = new Set<string>();
|
|
38
|
+
for (const [nodeId, commId] of Object.entries(communities)) {
|
|
39
|
+
if ((communitySizes.get(commId) ?? 0) <= 1) singletonNodes.add(nodeId);
|
|
40
|
+
}
|
|
34
41
|
|
|
35
42
|
// Refinement: merge singletons into their largest neighbor's community.
|
|
36
43
|
// Pass 1: update communitySizes eagerly (so adjacent singletons see each other's moves
|
|
@@ -38,26 +45,26 @@ export function leiden(graph: Graph, _options: { seed?: number } = {}): Record<s
|
|
|
38
45
|
// Pass 2: apply community writes — keeps the community map consistent at all times.
|
|
39
46
|
const pending = new Map<string, number>();
|
|
40
47
|
for (const [nodeId, commId] of Object.entries(communities)) {
|
|
41
|
-
if (
|
|
42
|
-
let bestComm = commId;
|
|
43
|
-
let bestSize = 0;
|
|
48
|
+
if (!singletonNodes.has(nodeId)) continue; // O(1) skip for non-singletons
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if (neighborComm === undefined) return;
|
|
48
|
-
const size = communitySizes.get(neighborComm) ?? 0;
|
|
49
|
-
if (size > bestSize) {
|
|
50
|
-
bestSize = size;
|
|
51
|
-
bestComm = neighborComm;
|
|
52
|
-
}
|
|
53
|
-
});
|
|
50
|
+
let bestComm = commId;
|
|
51
|
+
let bestSize = 0;
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
graph.forEachNeighbor(nodeId, (neighbor: string) => {
|
|
54
|
+
const neighborComm = communities[neighbor];
|
|
55
|
+
if (neighborComm === undefined) return;
|
|
56
|
+
const size = communitySizes.get(neighborComm) ?? 0;
|
|
57
|
+
if (size > bestSize) {
|
|
58
|
+
bestSize = size;
|
|
59
|
+
bestComm = neighborComm;
|
|
60
60
|
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (bestComm !== commId) {
|
|
64
|
+
// Update sizes eagerly so subsequent singletons see the correct community sizes
|
|
65
|
+
communitySizes.set(commId, (communitySizes.get(commId) ?? 1) - 1);
|
|
66
|
+
communitySizes.set(bestComm, (communitySizes.get(bestComm) ?? 0) + 1);
|
|
67
|
+
pending.set(nodeId, bestComm);
|
|
61
68
|
}
|
|
62
69
|
}
|
|
63
70
|
|
|
@@ -10,6 +10,8 @@ import type { StructureOutput } from './structure.js';
|
|
|
10
10
|
const MARKDOWN_EXTENSIONS = new Set(['.md', '.mdx']);
|
|
11
11
|
// Inline code span: `identifier`
|
|
12
12
|
const INLINE_CODE_RE = /`([^`]+)`/g;
|
|
13
|
+
// Module-level constant: compiled once per process, not per isIdentifier() call.
|
|
14
|
+
const IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$.-]*$/;
|
|
13
15
|
|
|
14
16
|
export interface MarkdownOutput {
|
|
15
17
|
documentNodes: MonographNode[];
|
|
@@ -23,6 +25,7 @@ export const markdownPhase: PipelinePhase<MarkdownOutput> = {
|
|
|
23
25
|
// Runs before communities so REFERENCES edges are visible to graph analyses.
|
|
24
26
|
deps: ['parse', 'structure'],
|
|
25
27
|
async execute(ctx, deps) {
|
|
28
|
+
if (ctx.options.codeOnly) return { documentNodes: [], referencesEdges: [] };
|
|
26
29
|
const { fileNodes } = deps.get('structure') as StructureOutput;
|
|
27
30
|
const documentNodes: MonographNode[] = [];
|
|
28
31
|
const referencesEdges: MonographEdge[] = [];
|
|
@@ -62,17 +65,19 @@ export const markdownPhase: PipelinePhase<MarkdownOutput> = {
|
|
|
62
65
|
// Extract inline code spans and match against symbol names in the DB
|
|
63
66
|
if (ctx.db) {
|
|
64
67
|
const spans = extractCodeSpans(source);
|
|
68
|
+
// Hoist prepared statement outside the span loop — avoids re-compiling the
|
|
69
|
+
// SQL query for every identifier in every markdown file (N+1 preparation).
|
|
70
|
+
const symbolLookup = ctx.db.prepare(
|
|
71
|
+
`SELECT id FROM nodes WHERE name = ?
|
|
72
|
+
AND label NOT IN ('File', 'Folder', 'Document')`,
|
|
73
|
+
);
|
|
74
|
+
|
|
65
75
|
for (const span of spans) {
|
|
66
76
|
// Only match single-word identifiers (not shell commands, paths, etc.)
|
|
67
77
|
if (!isIdentifier(span)) continue;
|
|
68
78
|
|
|
69
79
|
// Exact name match, excluding structural nodes to avoid matching file names
|
|
70
|
-
const rows =
|
|
71
|
-
.prepare(
|
|
72
|
-
`SELECT id FROM nodes WHERE name = ?
|
|
73
|
-
AND label NOT IN ('File', 'Folder', 'Document')`,
|
|
74
|
-
)
|
|
75
|
-
.all(span) as { id: string }[];
|
|
80
|
+
const rows = symbolLookup.all(span) as { id: string }[];
|
|
76
81
|
|
|
77
82
|
if (rows.length === 1) {
|
|
78
83
|
const edgeId = makeId(docId, rows[0].id, 'references');
|
|
@@ -112,5 +117,5 @@ function extractCodeSpans(source: string): string[] {
|
|
|
112
117
|
|
|
113
118
|
/** Accept only simple identifiers — letters, digits, underscores, hyphens, dots (for namespaced names). */
|
|
114
119
|
function isIdentifier(span: string): boolean {
|
|
115
|
-
return
|
|
120
|
+
return IDENTIFIER_RE.test(span);
|
|
116
121
|
}
|
|
@@ -107,8 +107,25 @@ export const pdfParsePhase: PipelinePhase<PdfParseOutput> = {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
if (sectionNodes.length > 0)
|
|
111
|
-
|
|
110
|
+
if (sectionNodes.length > 0) {
|
|
111
|
+
// File nodes are not persisted by structure/parse phases for non-code files —
|
|
112
|
+
// insert them here before edges to satisfy the FK constraint.
|
|
113
|
+
const seenFileIds = new Set<string>();
|
|
114
|
+
const fileNodes: MonographNode[] = [];
|
|
115
|
+
for (const edge of allEdges) {
|
|
116
|
+
if (!seenFileIds.has(edge.sourceId)) {
|
|
117
|
+
seenFileIds.add(edge.sourceId);
|
|
118
|
+
const relPath = sectionNodes.find(n => n.id === edge.targetId)?.filePath ?? '';
|
|
119
|
+
fileNodes.push({
|
|
120
|
+
id: edge.sourceId, label: 'File',
|
|
121
|
+
name: basename(relPath), normLabel: toNormLabel(basename(relPath)),
|
|
122
|
+
filePath: relPath, isExported: false,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
insertNodes(ctx.db, [...fileNodes, ...sectionNodes]);
|
|
127
|
+
insertEdges(ctx.db, allEdges);
|
|
128
|
+
}
|
|
112
129
|
|
|
113
130
|
ctx.onProgress?.({ phase: 'pdf-parse', message: `Parsed ${pdfPaths.length} PDF files → ${sectionNodes.length} chunks` });
|
|
114
131
|
return { sectionNodes, pdfFiles: pdfPaths.length };
|
|
@@ -79,31 +79,43 @@ function scoreEntryPoints(db: Database.Database): ScoredNode[] {
|
|
|
79
79
|
language: string | null;
|
|
80
80
|
}>;
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
82
|
+
// Preload all three edge-count signals in a single pass instead of 3 per-row queries.
|
|
83
|
+
// LEFT JOIN with FILTER avoids N*3 prepared-stmt round-trips.
|
|
84
|
+
const edgeCounts = db
|
|
85
|
+
.prepare(
|
|
86
|
+
`SELECT
|
|
87
|
+
n.id,
|
|
88
|
+
COUNT(CASE WHEN e.relation = 'CALLS' THEN 1 END) AS incoming_calls,
|
|
89
|
+
COUNT(CASE WHEN e.relation = 'HANDLES_ROUTE' THEN 1 END) AS route_count,
|
|
90
|
+
COUNT(CASE WHEN e.relation = 'HANDLES_TOOL' THEN 1 END) AS tool_count
|
|
91
|
+
FROM nodes n
|
|
92
|
+
LEFT JOIN edges e ON e.target_id = n.id AND e.relation IN ('CALLS','HANDLES_ROUTE','HANDLES_TOOL')
|
|
93
|
+
WHERE n.label IN ('Function','Method','Class')
|
|
94
|
+
GROUP BY n.id`,
|
|
95
|
+
)
|
|
96
|
+
.all() as Array<{ id: string; incoming_calls: number; route_count: number; tool_count: number }>;
|
|
97
|
+
|
|
98
|
+
const callsMap = new Map<string, number>();
|
|
99
|
+
const routeSet = new Set<string>();
|
|
100
|
+
const toolSet = new Set<string>();
|
|
101
|
+
for (const r of edgeCounts) {
|
|
102
|
+
callsMap.set(r.id, r.incoming_calls);
|
|
103
|
+
if (r.route_count > 0) routeSet.add(r.id);
|
|
104
|
+
if (r.tool_count > 0) toolSet.add(r.id);
|
|
105
|
+
}
|
|
91
106
|
|
|
92
107
|
const scored: ScoredNode[] = [];
|
|
93
108
|
|
|
94
109
|
for (const row of symbolRows) {
|
|
95
110
|
let score = 0;
|
|
96
111
|
|
|
97
|
-
|
|
98
|
-
if (incomingCalls === 0) score += 3;
|
|
112
|
+
if ((callsMap.get(row.id) ?? 0) === 0) score += 3;
|
|
99
113
|
|
|
100
114
|
if (row.is_exported === 1) score += 2;
|
|
101
115
|
|
|
102
|
-
|
|
103
|
-
if (isRouteHandler) score += 4;
|
|
116
|
+
if (routeSet.has(row.id)) score += 4;
|
|
104
117
|
|
|
105
|
-
|
|
106
|
-
if (isToolHandler) score += 4;
|
|
118
|
+
if (toolSet.has(row.id)) score += 4;
|
|
107
119
|
|
|
108
120
|
const filePath = row.file_path ?? '';
|
|
109
121
|
const filename = basename(filePath, extname(filePath)).toLowerCase();
|
|
@@ -74,6 +74,36 @@ export function propagateReExports(db: Database.Database): ReExportPropagationOu
|
|
|
74
74
|
},
|
|
75
75
|
);
|
|
76
76
|
|
|
77
|
+
// ── Preload: avoid per-iteration DB queries inside the BFS while-loop ─────────
|
|
78
|
+
|
|
79
|
+
// nodeId → file_path for all nodes (used to resolve importer file paths)
|
|
80
|
+
const nodeIdToFilePath = new Map<string, string>(
|
|
81
|
+
(db.prepare(`SELECT id, file_path FROM nodes WHERE file_path IS NOT NULL`)
|
|
82
|
+
.all() as { id: string; file_path: string }[]).map(r => [r.id, r.file_path]),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// barrelFilePath → Set<importerNodeId>: who IMPORTS any node from each barrel file
|
|
86
|
+
// Load all IMPORTS edges once and group by target file_path
|
|
87
|
+
const allImportEdges = db.prepare(`
|
|
88
|
+
SELECT e.source_id, n.file_path AS target_file_path
|
|
89
|
+
FROM edges e
|
|
90
|
+
JOIN nodes n ON n.id = e.target_id
|
|
91
|
+
WHERE e.relation = 'IMPORTS'
|
|
92
|
+
AND n.file_path IS NOT NULL
|
|
93
|
+
`).all() as { source_id: string; target_file_path: string }[];
|
|
94
|
+
|
|
95
|
+
const barrelImporters = new Map<string, Set<string>>();
|
|
96
|
+
for (const row of allImportEdges) {
|
|
97
|
+
let s = barrelImporters.get(row.target_file_path);
|
|
98
|
+
if (!s) {
|
|
99
|
+
s = new Set();
|
|
100
|
+
barrelImporters.set(row.target_file_path, s);
|
|
101
|
+
}
|
|
102
|
+
s.add(row.source_id);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ── BFS fixed-point (all lookups now O(1) from preloaded maps) ────────────────
|
|
106
|
+
|
|
77
107
|
let totalPropagated = 0;
|
|
78
108
|
let changed = true;
|
|
79
109
|
|
|
@@ -85,24 +115,16 @@ export function propagateReExports(db: Database.Database): ReExportPropagationOu
|
|
|
85
115
|
const barrelFilePath = fileIdToPath.get(barrelFileId);
|
|
86
116
|
if (!barrelFilePath || !reachedFilePaths.has(barrelFilePath)) continue;
|
|
87
117
|
|
|
88
|
-
//
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
for (const { source_id: importerNodeId } of importers) {
|
|
100
|
-
// Walk up to the importing File node
|
|
101
|
-
const importerFilePath = (db.prepare(
|
|
102
|
-
`SELECT file_path FROM nodes WHERE id = ? AND file_path IS NOT NULL LIMIT 1`,
|
|
103
|
-
).get(importerNodeId) as { file_path: string } | null)?.file_path;
|
|
104
|
-
|
|
105
|
-
const importerFileId = importerFilePath ? filePathToId.get(importerFilePath) : undefined;
|
|
118
|
+
// O(1) lookup instead of per-barrel DB query
|
|
119
|
+
const importerNodeIds = barrelImporters.get(barrelFilePath);
|
|
120
|
+
if (!importerNodeIds) continue;
|
|
121
|
+
|
|
122
|
+
for (const importerNodeId of importerNodeIds) {
|
|
123
|
+
// O(1) lookup instead of per-importer DB query
|
|
124
|
+
const importerFilePath = nodeIdToFilePath.get(importerNodeId);
|
|
125
|
+
if (!importerFilePath) continue;
|
|
126
|
+
|
|
127
|
+
const importerFileId = filePathToId.get(importerFilePath);
|
|
106
128
|
if (!importerFileId) continue;
|
|
107
129
|
|
|
108
130
|
for (const targetFileId of exportedFileIds) {
|