openlore 2.0.0
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/LICENSE +21 -0
- package/README.md +268 -0
- package/dist/api/analyze.d.ts +17 -0
- package/dist/api/analyze.d.ts.map +1 -0
- package/dist/api/analyze.js +143 -0
- package/dist/api/analyze.js.map +1 -0
- package/dist/api/audit.d.ts +10 -0
- package/dist/api/audit.d.ts.map +1 -0
- package/dist/api/audit.js +117 -0
- package/dist/api/audit.js.map +1 -0
- package/dist/api/decisions.d.ts +55 -0
- package/dist/api/decisions.d.ts.map +1 -0
- package/dist/api/decisions.js +157 -0
- package/dist/api/decisions.js.map +1 -0
- package/dist/api/drift.d.ts +21 -0
- package/dist/api/drift.d.ts.map +1 -0
- package/dist/api/drift.js +152 -0
- package/dist/api/drift.js.map +1 -0
- package/dist/api/generate.d.ts +18 -0
- package/dist/api/generate.d.ts.map +1 -0
- package/dist/api/generate.js +259 -0
- package/dist/api/generate.js.map +1 -0
- package/dist/api/index.d.ts +41 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +34 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/init.d.ts +18 -0
- package/dist/api/init.d.ts.map +1 -0
- package/dist/api/init.js +83 -0
- package/dist/api/init.js.map +1 -0
- package/dist/api/run.d.ts +19 -0
- package/dist/api/run.d.ts.map +1 -0
- package/dist/api/run.js +312 -0
- package/dist/api/run.js.map +1 -0
- package/dist/api/specs.d.ts +49 -0
- package/dist/api/specs.d.ts.map +1 -0
- package/dist/api/specs.js +137 -0
- package/dist/api/specs.js.map +1 -0
- package/dist/api/types.d.ts +201 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +9 -0
- package/dist/api/types.js.map +1 -0
- package/dist/api/verify.d.ts +20 -0
- package/dist/api/verify.d.ts.map +1 -0
- package/dist/api/verify.js +117 -0
- package/dist/api/verify.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +30 -0
- package/dist/cli/commands/analyze.d.ts.map +1 -0
- package/dist/cli/commands/analyze.js +683 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/audit.d.ts +9 -0
- package/dist/cli/commands/audit.d.ts.map +1 -0
- package/dist/cli/commands/audit.js +98 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/decisions.d.ts +16 -0
- package/dist/cli/commands/decisions.d.ts.map +1 -0
- package/dist/cli/commands/decisions.js +864 -0
- package/dist/cli/commands/decisions.js.map +1 -0
- package/dist/cli/commands/digest.d.ts +9 -0
- package/dist/cli/commands/digest.d.ts.map +1 -0
- package/dist/cli/commands/digest.js +61 -0
- package/dist/cli/commands/digest.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +9 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +398 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/drift.d.ts +9 -0
- package/dist/cli/commands/drift.d.ts.map +1 -0
- package/dist/cli/commands/drift.js +550 -0
- package/dist/cli/commands/drift.js.map +1 -0
- package/dist/cli/commands/generate.d.ts +9 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/generate.js +565 -0
- package/dist/cli/commands/generate.js.map +1 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +173 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +2235 -0
- package/dist/cli/commands/mcp.d.ts.map +1 -0
- package/dist/cli/commands/mcp.js +1384 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/refresh-stories.d.ts +10 -0
- package/dist/cli/commands/refresh-stories.d.ts.map +1 -0
- package/dist/cli/commands/refresh-stories.js +314 -0
- package/dist/cli/commands/refresh-stories.js.map +1 -0
- package/dist/cli/commands/run.d.ts +9 -0
- package/dist/cli/commands/run.d.ts.map +1 -0
- package/dist/cli/commands/run.js +459 -0
- package/dist/cli/commands/run.js.map +1 -0
- package/dist/cli/commands/setup.d.ts +19 -0
- package/dist/cli/commands/setup.d.ts.map +1 -0
- package/dist/cli/commands/setup.js +355 -0
- package/dist/cli/commands/setup.js.map +1 -0
- package/dist/cli/commands/test.d.ts +22 -0
- package/dist/cli/commands/test.d.ts.map +1 -0
- package/dist/cli/commands/test.js +180 -0
- package/dist/cli/commands/test.js.map +1 -0
- package/dist/cli/commands/verify.d.ts +9 -0
- package/dist/cli/commands/verify.d.ts.map +1 -0
- package/dist/cli/commands/verify.js +383 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/commands/view.d.ts +13 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +547 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +118 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/tui-approval.d.ts +11 -0
- package/dist/cli/tui-approval.d.ts.map +1 -0
- package/dist/cli/tui-approval.js +129 -0
- package/dist/cli/tui-approval.js.map +1 -0
- package/dist/constants.d.ts +314 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +382 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/analyzer/ai-config-generator.d.ts +54 -0
- package/dist/core/analyzer/ai-config-generator.d.ts.map +1 -0
- package/dist/core/analyzer/ai-config-generator.js +98 -0
- package/dist/core/analyzer/ai-config-generator.js.map +1 -0
- package/dist/core/analyzer/architecture-writer.d.ts +67 -0
- package/dist/core/analyzer/architecture-writer.d.ts.map +1 -0
- package/dist/core/analyzer/architecture-writer.js +209 -0
- package/dist/core/analyzer/architecture-writer.js.map +1 -0
- package/dist/core/analyzer/artifact-generator.d.ts +261 -0
- package/dist/core/analyzer/artifact-generator.d.ts.map +1 -0
- package/dist/core/analyzer/artifact-generator.js +909 -0
- package/dist/core/analyzer/artifact-generator.js.map +1 -0
- package/dist/core/analyzer/ast-chunker.d.ts +24 -0
- package/dist/core/analyzer/ast-chunker.d.ts.map +1 -0
- package/dist/core/analyzer/ast-chunker.js +198 -0
- package/dist/core/analyzer/ast-chunker.js.map +1 -0
- package/dist/core/analyzer/call-graph.d.ts +162 -0
- package/dist/core/analyzer/call-graph.d.ts.map +1 -0
- package/dist/core/analyzer/call-graph.js +2040 -0
- package/dist/core/analyzer/call-graph.js.map +1 -0
- package/dist/core/analyzer/code-shaper.d.ts +33 -0
- package/dist/core/analyzer/code-shaper.d.ts.map +1 -0
- package/dist/core/analyzer/code-shaper.js +154 -0
- package/dist/core/analyzer/code-shaper.js.map +1 -0
- package/dist/core/analyzer/codebase-digest.d.ts +40 -0
- package/dist/core/analyzer/codebase-digest.d.ts.map +1 -0
- package/dist/core/analyzer/codebase-digest.js +195 -0
- package/dist/core/analyzer/codebase-digest.js.map +1 -0
- package/dist/core/analyzer/cpp-header-resolver.d.ts +30 -0
- package/dist/core/analyzer/cpp-header-resolver.d.ts.map +1 -0
- package/dist/core/analyzer/cpp-header-resolver.js +71 -0
- package/dist/core/analyzer/cpp-header-resolver.js.map +1 -0
- package/dist/core/analyzer/dependency-graph.d.ts +230 -0
- package/dist/core/analyzer/dependency-graph.d.ts.map +1 -0
- package/dist/core/analyzer/dependency-graph.js +752 -0
- package/dist/core/analyzer/dependency-graph.js.map +1 -0
- package/dist/core/analyzer/duplicate-detector.d.ts +52 -0
- package/dist/core/analyzer/duplicate-detector.d.ts.map +1 -0
- package/dist/core/analyzer/duplicate-detector.js +289 -0
- package/dist/core/analyzer/duplicate-detector.js.map +1 -0
- package/dist/core/analyzer/embedding-service.d.ts +56 -0
- package/dist/core/analyzer/embedding-service.d.ts.map +1 -0
- package/dist/core/analyzer/embedding-service.js +118 -0
- package/dist/core/analyzer/embedding-service.js.map +1 -0
- package/dist/core/analyzer/env-extractor.d.ts +33 -0
- package/dist/core/analyzer/env-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/env-extractor.js +196 -0
- package/dist/core/analyzer/env-extractor.js.map +1 -0
- package/dist/core/analyzer/external-packages.d.ts +20 -0
- package/dist/core/analyzer/external-packages.d.ts.map +1 -0
- package/dist/core/analyzer/external-packages.js +175 -0
- package/dist/core/analyzer/external-packages.js.map +1 -0
- package/dist/core/analyzer/file-walker.d.ts +78 -0
- package/dist/core/analyzer/file-walker.d.ts.map +1 -0
- package/dist/core/analyzer/file-walker.js +532 -0
- package/dist/core/analyzer/file-walker.js.map +1 -0
- package/dist/core/analyzer/function-registry-trie.d.ts +21 -0
- package/dist/core/analyzer/function-registry-trie.d.ts.map +1 -0
- package/dist/core/analyzer/function-registry-trie.js +39 -0
- package/dist/core/analyzer/function-registry-trie.js.map +1 -0
- package/dist/core/analyzer/http-route-parser.d.ts +152 -0
- package/dist/core/analyzer/http-route-parser.d.ts.map +1 -0
- package/dist/core/analyzer/http-route-parser.js +971 -0
- package/dist/core/analyzer/http-route-parser.js.map +1 -0
- package/dist/core/analyzer/import-parser.d.ts +100 -0
- package/dist/core/analyzer/import-parser.d.ts.map +1 -0
- package/dist/core/analyzer/import-parser.js +952 -0
- package/dist/core/analyzer/import-parser.js.map +1 -0
- package/dist/core/analyzer/import-resolver-bridge.d.ts +25 -0
- package/dist/core/analyzer/import-resolver-bridge.d.ts.map +1 -0
- package/dist/core/analyzer/import-resolver-bridge.js +99 -0
- package/dist/core/analyzer/import-resolver-bridge.js.map +1 -0
- package/dist/core/analyzer/index.d.ts +10 -0
- package/dist/core/analyzer/index.d.ts.map +1 -0
- package/dist/core/analyzer/index.js +10 -0
- package/dist/core/analyzer/index.js.map +1 -0
- package/dist/core/analyzer/middleware-extractor.d.ts +29 -0
- package/dist/core/analyzer/middleware-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/middleware-extractor.js +195 -0
- package/dist/core/analyzer/middleware-extractor.js.map +1 -0
- package/dist/core/analyzer/refactor-analyzer.d.ts +83 -0
- package/dist/core/analyzer/refactor-analyzer.d.ts.map +1 -0
- package/dist/core/analyzer/refactor-analyzer.js +351 -0
- package/dist/core/analyzer/refactor-analyzer.js.map +1 -0
- package/dist/core/analyzer/repository-mapper.d.ts +150 -0
- package/dist/core/analyzer/repository-mapper.d.ts.map +1 -0
- package/dist/core/analyzer/repository-mapper.js +740 -0
- package/dist/core/analyzer/repository-mapper.js.map +1 -0
- package/dist/core/analyzer/schema-extractor.d.ts +41 -0
- package/dist/core/analyzer/schema-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/schema-extractor.js +229 -0
- package/dist/core/analyzer/schema-extractor.js.map +1 -0
- package/dist/core/analyzer/signature-extractor.d.ts +31 -0
- package/dist/core/analyzer/signature-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/signature-extractor.js +675 -0
- package/dist/core/analyzer/signature-extractor.js.map +1 -0
- package/dist/core/analyzer/significance-scorer.d.ts +79 -0
- package/dist/core/analyzer/significance-scorer.d.ts.map +1 -0
- package/dist/core/analyzer/significance-scorer.js +407 -0
- package/dist/core/analyzer/significance-scorer.js.map +1 -0
- package/dist/core/analyzer/spec-snapshot-generator.d.ts +17 -0
- package/dist/core/analyzer/spec-snapshot-generator.d.ts.map +1 -0
- package/dist/core/analyzer/spec-snapshot-generator.js +201 -0
- package/dist/core/analyzer/spec-snapshot-generator.js.map +1 -0
- package/dist/core/analyzer/spec-vector-index.d.ts +68 -0
- package/dist/core/analyzer/spec-vector-index.d.ts.map +1 -0
- package/dist/core/analyzer/spec-vector-index.js +340 -0
- package/dist/core/analyzer/spec-vector-index.js.map +1 -0
- package/dist/core/analyzer/subgraph-extractor.d.ts +51 -0
- package/dist/core/analyzer/subgraph-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/subgraph-extractor.js +147 -0
- package/dist/core/analyzer/subgraph-extractor.js.map +1 -0
- package/dist/core/analyzer/type-inference-engine.d.ts +23 -0
- package/dist/core/analyzer/type-inference-engine.d.ts.map +1 -0
- package/dist/core/analyzer/type-inference-engine.js +130 -0
- package/dist/core/analyzer/type-inference-engine.js.map +1 -0
- package/dist/core/analyzer/ui-component-extractor.d.ts +43 -0
- package/dist/core/analyzer/ui-component-extractor.d.ts.map +1 -0
- package/dist/core/analyzer/ui-component-extractor.js +245 -0
- package/dist/core/analyzer/ui-component-extractor.js.map +1 -0
- package/dist/core/analyzer/unified-search.d.ts +116 -0
- package/dist/core/analyzer/unified-search.d.ts.map +1 -0
- package/dist/core/analyzer/unified-search.js +231 -0
- package/dist/core/analyzer/unified-search.js.map +1 -0
- package/dist/core/analyzer/vector-index.d.ts +92 -0
- package/dist/core/analyzer/vector-index.d.ts.map +1 -0
- package/dist/core/analyzer/vector-index.js +451 -0
- package/dist/core/analyzer/vector-index.js.map +1 -0
- package/dist/core/decisions/consolidator.d.ts +14 -0
- package/dist/core/decisions/consolidator.d.ts.map +1 -0
- package/dist/core/decisions/consolidator.js +169 -0
- package/dist/core/decisions/consolidator.js.map +1 -0
- package/dist/core/decisions/extractor.d.ts +26 -0
- package/dist/core/decisions/extractor.d.ts.map +1 -0
- package/dist/core/decisions/extractor.js +156 -0
- package/dist/core/decisions/extractor.js.map +1 -0
- package/dist/core/decisions/index.d.ts +19 -0
- package/dist/core/decisions/index.d.ts.map +1 -0
- package/dist/core/decisions/index.js +16 -0
- package/dist/core/decisions/index.js.map +1 -0
- package/dist/core/decisions/store.d.ts +36 -0
- package/dist/core/decisions/store.d.ts.map +1 -0
- package/dist/core/decisions/store.js +109 -0
- package/dist/core/decisions/store.js.map +1 -0
- package/dist/core/decisions/syncer.d.ts +27 -0
- package/dist/core/decisions/syncer.d.ts.map +1 -0
- package/dist/core/decisions/syncer.js +214 -0
- package/dist/core/decisions/syncer.js.map +1 -0
- package/dist/core/decisions/verifier.d.ts +20 -0
- package/dist/core/decisions/verifier.d.ts.map +1 -0
- package/dist/core/decisions/verifier.js +115 -0
- package/dist/core/decisions/verifier.js.map +1 -0
- package/dist/core/digest/digest-generator.d.ts +29 -0
- package/dist/core/digest/digest-generator.d.ts.map +1 -0
- package/dist/core/digest/digest-generator.js +181 -0
- package/dist/core/digest/digest-generator.js.map +1 -0
- package/dist/core/drift/drift-detector.d.ts +102 -0
- package/dist/core/drift/drift-detector.d.ts.map +1 -0
- package/dist/core/drift/drift-detector.js +598 -0
- package/dist/core/drift/drift-detector.js.map +1 -0
- package/dist/core/drift/git-diff.d.ts +60 -0
- package/dist/core/drift/git-diff.d.ts.map +1 -0
- package/dist/core/drift/git-diff.js +383 -0
- package/dist/core/drift/git-diff.js.map +1 -0
- package/dist/core/drift/index.d.ts +12 -0
- package/dist/core/drift/index.d.ts.map +1 -0
- package/dist/core/drift/index.js +9 -0
- package/dist/core/drift/index.js.map +1 -0
- package/dist/core/drift/spec-mapper.d.ts +73 -0
- package/dist/core/drift/spec-mapper.d.ts.map +1 -0
- package/dist/core/drift/spec-mapper.js +353 -0
- package/dist/core/drift/spec-mapper.js.map +1 -0
- package/dist/core/drift/test-suggester.d.ts +18 -0
- package/dist/core/drift/test-suggester.d.ts.map +1 -0
- package/dist/core/drift/test-suggester.js +107 -0
- package/dist/core/drift/test-suggester.js.map +1 -0
- package/dist/core/generator/adr-generator.d.ts +32 -0
- package/dist/core/generator/adr-generator.d.ts.map +1 -0
- package/dist/core/generator/adr-generator.js +192 -0
- package/dist/core/generator/adr-generator.js.map +1 -0
- package/dist/core/generator/index.d.ts +9 -0
- package/dist/core/generator/index.d.ts.map +1 -0
- package/dist/core/generator/index.js +12 -0
- package/dist/core/generator/index.js.map +1 -0
- package/dist/core/generator/mapping-generator.d.ts +54 -0
- package/dist/core/generator/mapping-generator.d.ts.map +1 -0
- package/dist/core/generator/mapping-generator.js +240 -0
- package/dist/core/generator/mapping-generator.js.map +1 -0
- package/dist/core/generator/openspec-compat.d.ts +160 -0
- package/dist/core/generator/openspec-compat.d.ts.map +1 -0
- package/dist/core/generator/openspec-compat.js +524 -0
- package/dist/core/generator/openspec-compat.js.map +1 -0
- package/dist/core/generator/openspec-format-generator.d.ts +131 -0
- package/dist/core/generator/openspec-format-generator.d.ts.map +1 -0
- package/dist/core/generator/openspec-format-generator.js +963 -0
- package/dist/core/generator/openspec-format-generator.js.map +1 -0
- package/dist/core/generator/openspec-writer.d.ts +130 -0
- package/dist/core/generator/openspec-writer.d.ts.map +1 -0
- package/dist/core/generator/openspec-writer.js +404 -0
- package/dist/core/generator/openspec-writer.js.map +1 -0
- package/dist/core/generator/prompts.d.ts +35 -0
- package/dist/core/generator/prompts.d.ts.map +1 -0
- package/dist/core/generator/prompts.js +212 -0
- package/dist/core/generator/prompts.js.map +1 -0
- package/dist/core/generator/rag-manifest-generator.d.ts +37 -0
- package/dist/core/generator/rag-manifest-generator.d.ts.map +1 -0
- package/dist/core/generator/rag-manifest-generator.js +134 -0
- package/dist/core/generator/rag-manifest-generator.js.map +1 -0
- package/dist/core/generator/schemas.d.ts +365 -0
- package/dist/core/generator/schemas.d.ts.map +1 -0
- package/dist/core/generator/schemas.js +190 -0
- package/dist/core/generator/schemas.js.map +1 -0
- package/dist/core/generator/spec-pipeline.d.ts +123 -0
- package/dist/core/generator/spec-pipeline.d.ts.map +1 -0
- package/dist/core/generator/spec-pipeline.js +699 -0
- package/dist/core/generator/spec-pipeline.js.map +1 -0
- package/dist/core/generator/stages/stage1-survey.d.ts +19 -0
- package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -0
- package/dist/core/generator/stages/stage1-survey.js +171 -0
- package/dist/core/generator/stages/stage1-survey.js.map +1 -0
- package/dist/core/generator/stages/stage2-entities.d.ts +11 -0
- package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -0
- package/dist/core/generator/stages/stage2-entities.js +74 -0
- package/dist/core/generator/stages/stage2-entities.js.map +1 -0
- package/dist/core/generator/stages/stage3-services.d.ts +11 -0
- package/dist/core/generator/stages/stage3-services.d.ts.map +1 -0
- package/dist/core/generator/stages/stage3-services.js +85 -0
- package/dist/core/generator/stages/stage3-services.js.map +1 -0
- package/dist/core/generator/stages/stage4-api.d.ts +11 -0
- package/dist/core/generator/stages/stage4-api.d.ts.map +1 -0
- package/dist/core/generator/stages/stage4-api.js +72 -0
- package/dist/core/generator/stages/stage4-api.js.map +1 -0
- package/dist/core/generator/stages/stage5-architecture.d.ts +11 -0
- package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -0
- package/dist/core/generator/stages/stage5-architecture.js +75 -0
- package/dist/core/generator/stages/stage5-architecture.js.map +1 -0
- package/dist/core/generator/stages/stage6-adr.d.ts +8 -0
- package/dist/core/generator/stages/stage6-adr.d.ts.map +1 -0
- package/dist/core/generator/stages/stage6-adr.js +47 -0
- package/dist/core/generator/stages/stage6-adr.js.map +1 -0
- package/dist/core/services/chat-agent.d.ts +50 -0
- package/dist/core/services/chat-agent.d.ts.map +1 -0
- package/dist/core/services/chat-agent.js +369 -0
- package/dist/core/services/chat-agent.js.map +1 -0
- package/dist/core/services/chat-tools.d.ts +32 -0
- package/dist/core/services/chat-tools.d.ts.map +1 -0
- package/dist/core/services/chat-tools.js +494 -0
- package/dist/core/services/chat-tools.js.map +1 -0
- package/dist/core/services/config-manager.d.ts +61 -0
- package/dist/core/services/config-manager.d.ts.map +1 -0
- package/dist/core/services/config-manager.js +149 -0
- package/dist/core/services/config-manager.js.map +1 -0
- package/dist/core/services/edge-store.d.ts +57 -0
- package/dist/core/services/edge-store.d.ts.map +1 -0
- package/dist/core/services/edge-store.js +419 -0
- package/dist/core/services/edge-store.js.map +1 -0
- package/dist/core/services/gitignore-manager.d.ts +29 -0
- package/dist/core/services/gitignore-manager.d.ts.map +1 -0
- package/dist/core/services/gitignore-manager.js +95 -0
- package/dist/core/services/gitignore-manager.js.map +1 -0
- package/dist/core/services/index.d.ts +8 -0
- package/dist/core/services/index.d.ts.map +1 -0
- package/dist/core/services/index.js +8 -0
- package/dist/core/services/index.js.map +1 -0
- package/dist/core/services/llm-service.d.ts +379 -0
- package/dist/core/services/llm-service.d.ts.map +1 -0
- package/dist/core/services/llm-service.js +1553 -0
- package/dist/core/services/llm-service.js.map +1 -0
- package/dist/core/services/mcp-handlers/analysis.d.ts +127 -0
- package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/analysis.js +1185 -0
- package/dist/core/services/mcp-handlers/analysis.js.map +1 -0
- package/dist/core/services/mcp-handlers/change.d.ts +14 -0
- package/dist/core/services/mcp-handlers/change.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/change.js +416 -0
- package/dist/core/services/mcp-handlers/change.js.map +1 -0
- package/dist/core/services/mcp-handlers/decisions.d.ts +16 -0
- package/dist/core/services/mcp-handlers/decisions.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/decisions.js +239 -0
- package/dist/core/services/mcp-handlers/decisions.js.map +1 -0
- package/dist/core/services/mcp-handlers/graph.d.ts +94 -0
- package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/graph.js +693 -0
- package/dist/core/services/mcp-handlers/graph.js.map +1 -0
- package/dist/core/services/mcp-handlers/orient.d.ts +17 -0
- package/dist/core/services/mcp-handlers/orient.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/orient.js +357 -0
- package/dist/core/services/mcp-handlers/orient.js.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts +66 -0
- package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/semantic.js +432 -0
- package/dist/core/services/mcp-handlers/semantic.js.map +1 -0
- package/dist/core/services/mcp-handlers/utils.d.ts +85 -0
- package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -0
- package/dist/core/services/mcp-handlers/utils.js +262 -0
- package/dist/core/services/mcp-handlers/utils.js.map +1 -0
- package/dist/core/services/mcp-watcher.d.ts +41 -0
- package/dist/core/services/mcp-watcher.d.ts.map +1 -0
- package/dist/core/services/mcp-watcher.js +254 -0
- package/dist/core/services/mcp-watcher.js.map +1 -0
- package/dist/core/services/project-detector.d.ts +32 -0
- package/dist/core/services/project-detector.d.ts.map +1 -0
- package/dist/core/services/project-detector.js +100 -0
- package/dist/core/services/project-detector.js.map +1 -0
- package/dist/core/test-generator/coverage-analyzer.d.ts +27 -0
- package/dist/core/test-generator/coverage-analyzer.d.ts.map +1 -0
- package/dist/core/test-generator/coverage-analyzer.js +285 -0
- package/dist/core/test-generator/coverage-analyzer.js.map +1 -0
- package/dist/core/test-generator/framework-detector.d.ts +17 -0
- package/dist/core/test-generator/framework-detector.d.ts.map +1 -0
- package/dist/core/test-generator/framework-detector.js +65 -0
- package/dist/core/test-generator/framework-detector.js.map +1 -0
- package/dist/core/test-generator/index.d.ts +14 -0
- package/dist/core/test-generator/index.d.ts.map +1 -0
- package/dist/core/test-generator/index.js +11 -0
- package/dist/core/test-generator/index.js.map +1 -0
- package/dist/core/test-generator/renderers/catch2.d.ts +8 -0
- package/dist/core/test-generator/renderers/catch2.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/catch2.js +47 -0
- package/dist/core/test-generator/renderers/catch2.js.map +1 -0
- package/dist/core/test-generator/renderers/gtest.d.ts +8 -0
- package/dist/core/test-generator/renderers/gtest.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/gtest.js +45 -0
- package/dist/core/test-generator/renderers/gtest.js.map +1 -0
- package/dist/core/test-generator/renderers/index.d.ts +20 -0
- package/dist/core/test-generator/renderers/index.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/index.js +35 -0
- package/dist/core/test-generator/renderers/index.js.map +1 -0
- package/dist/core/test-generator/renderers/playwright.d.ts +8 -0
- package/dist/core/test-generator/renderers/playwright.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/playwright.js +44 -0
- package/dist/core/test-generator/renderers/playwright.js.map +1 -0
- package/dist/core/test-generator/renderers/pytest.d.ts +8 -0
- package/dist/core/test-generator/renderers/pytest.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/pytest.js +44 -0
- package/dist/core/test-generator/renderers/pytest.js.map +1 -0
- package/dist/core/test-generator/renderers/shared.d.ts +21 -0
- package/dist/core/test-generator/renderers/shared.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/shared.js +56 -0
- package/dist/core/test-generator/renderers/shared.js.map +1 -0
- package/dist/core/test-generator/renderers/vitest.d.ts +8 -0
- package/dist/core/test-generator/renderers/vitest.d.ts.map +1 -0
- package/dist/core/test-generator/renderers/vitest.js +52 -0
- package/dist/core/test-generator/renderers/vitest.js.map +1 -0
- package/dist/core/test-generator/scenario-parser.d.ts +33 -0
- package/dist/core/test-generator/scenario-parser.d.ts.map +1 -0
- package/dist/core/test-generator/scenario-parser.js +244 -0
- package/dist/core/test-generator/scenario-parser.js.map +1 -0
- package/dist/core/test-generator/test-generator.d.ts +30 -0
- package/dist/core/test-generator/test-generator.d.ts.map +1 -0
- package/dist/core/test-generator/test-generator.js +174 -0
- package/dist/core/test-generator/test-generator.js.map +1 -0
- package/dist/core/test-generator/test-writer.d.ts +25 -0
- package/dist/core/test-generator/test-writer.d.ts.map +1 -0
- package/dist/core/test-generator/test-writer.js +128 -0
- package/dist/core/test-generator/test-writer.js.map +1 -0
- package/dist/core/test-generator/then-matchers.d.ts +35 -0
- package/dist/core/test-generator/then-matchers.d.ts.map +1 -0
- package/dist/core/test-generator/then-matchers.js +211 -0
- package/dist/core/test-generator/then-matchers.js.map +1 -0
- package/dist/core/verifier/index.d.ts +5 -0
- package/dist/core/verifier/index.d.ts.map +1 -0
- package/dist/core/verifier/index.js +5 -0
- package/dist/core/verifier/index.js.map +1 -0
- package/dist/core/verifier/verification-engine.d.ts +293 -0
- package/dist/core/verifier/verification-engine.d.ts.map +1 -0
- package/dist/core/verifier/verification-engine.js +919 -0
- package/dist/core/verifier/verification-engine.js.map +1 -0
- package/dist/types/index.d.ts +368 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/pipeline.d.ts +167 -0
- package/dist/types/pipeline.d.ts.map +1 -0
- package/dist/types/pipeline.js +5 -0
- package/dist/types/pipeline.js.map +1 -0
- package/dist/types/test-generator.d.ts +103 -0
- package/dist/types/test-generator.d.ts.map +1 -0
- package/dist/types/test-generator.js +17 -0
- package/dist/types/test-generator.js.map +1 -0
- package/dist/utils/command-helpers.d.ts +68 -0
- package/dist/utils/command-helpers.d.ts.map +1 -0
- package/dist/utils/command-helpers.js +150 -0
- package/dist/utils/command-helpers.js.map +1 -0
- package/dist/utils/errors.d.ts +51 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +129 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +149 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +342 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/misc.d.ts +10 -0
- package/dist/utils/misc.d.ts.map +1 -0
- package/dist/utils/misc.js +21 -0
- package/dist/utils/misc.js.map +1 -0
- package/dist/utils/progress.d.ts +142 -0
- package/dist/utils/progress.d.ts.map +1 -0
- package/dist/utils/progress.js +283 -0
- package/dist/utils/progress.js.map +1 -0
- package/dist/utils/prompts.d.ts +53 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +199 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/shutdown.d.ts +89 -0
- package/dist/utils/shutdown.d.ts.map +1 -0
- package/dist/utils/shutdown.js +238 -0
- package/dist/utils/shutdown.js.map +1 -0
- package/examples/bmad/README.md +113 -0
- package/examples/bmad/agents/architect.md +226 -0
- package/examples/bmad/agents/dev-brownfield.md +69 -0
- package/examples/bmad/setup/architect.customize.yaml +14 -0
- package/examples/bmad/tasks/implement-story.md +254 -0
- package/examples/bmad/tasks/onboarding.md +169 -0
- package/examples/bmad/tasks/refactor.md +178 -0
- package/examples/bmad/tasks/sprint-planning.md +168 -0
- package/examples/bmad/templates/story.md +108 -0
- package/examples/cline-workflows/openlore-analyze-codebase.md +101 -0
- package/examples/cline-workflows/openlore-check-spec-drift.md +102 -0
- package/examples/cline-workflows/openlore-execute-refactor.md +212 -0
- package/examples/cline-workflows/openlore-implement-feature.md +266 -0
- package/examples/cline-workflows/openlore-plan-refactor.md +279 -0
- package/examples/cline-workflows/openlore-refactor-codebase.md +16 -0
- package/examples/cline-workflows/openlore-write-tests.md +177 -0
- package/examples/drift-demo/openspec/config.yaml +14 -0
- package/examples/drift-demo/openspec/specs/architecture/spec.md +30 -0
- package/examples/drift-demo/openspec/specs/auth/spec.md +71 -0
- package/examples/drift-demo/openspec/specs/database/spec.md +33 -0
- package/examples/drift-demo/openspec/specs/overview/spec.md +20 -0
- package/examples/drift-demo/openspec/specs/projects/spec.md +55 -0
- package/examples/drift-demo/openspec/specs/tasks/spec.md +78 -0
- package/examples/drift-demo/package.json +21 -0
- package/examples/drift-demo/src/auth/auth-middleware.ts +30 -0
- package/examples/drift-demo/src/auth/auth-routes.ts +29 -0
- package/examples/drift-demo/src/auth/auth-service.ts +45 -0
- package/examples/drift-demo/src/database/connection.ts +27 -0
- package/examples/drift-demo/src/index.ts +16 -0
- package/examples/drift-demo/src/projects/project-model.ts +15 -0
- package/examples/drift-demo/src/projects/project-service.ts +34 -0
- package/examples/drift-demo/src/tasks/task-model.ts +37 -0
- package/examples/drift-demo/src/tasks/task-routes.ts +53 -0
- package/examples/drift-demo/src/tasks/task-service.ts +60 -0
- package/examples/drift-demo/src/utils/validation.ts +11 -0
- package/examples/drift-demo/tests/auth.test.ts +4 -0
- package/examples/drift-demo/tests/tasks.test.ts +4 -0
- package/examples/drift-demo/tsconfig.json +10 -0
- package/examples/drift-test/run-drift-test.sh +1087 -0
- package/examples/gsd/README.md +119 -0
- package/examples/gsd/commands/gsd/openlore-drift.md +111 -0
- package/examples/gsd/commands/gsd/openlore-orient.md +191 -0
- package/examples/mistral-vibe/README.md +101 -0
- package/examples/mistral-vibe/antipatterns-template.md +18 -0
- package/examples/mistral-vibe/skills/openlore-analyze-codebase/SKILL.md +124 -0
- package/examples/mistral-vibe/skills/openlore-brainstorm/SKILL.md +379 -0
- package/examples/mistral-vibe/skills/openlore-debug/SKILL.md +330 -0
- package/examples/mistral-vibe/skills/openlore-execute-refactor/SKILL.md +291 -0
- package/examples/mistral-vibe/skills/openlore-generate/SKILL.md +245 -0
- package/examples/mistral-vibe/skills/openlore-implement-story/SKILL.md +326 -0
- package/examples/mistral-vibe/skills/openlore-plan-refactor/SKILL.md +365 -0
- package/examples/mistral-vibe/skills/openlore-review-changes/SKILL.md +128 -0
- package/examples/mistral-vibe/skills/openlore-write-tests/SKILL.md +261 -0
- package/examples/opencode/agent-guard.ts +170 -0
- package/examples/opencode/plugins/anti-laziness.ts +202 -0
- package/examples/opencode/plugins/lib/openlore-context-injector-helpers.ts +116 -0
- package/examples/opencode/plugins/lib/openlore-decision-extractor-helpers.ts +65 -0
- package/examples/opencode/plugins/openlore-context-injector.test.ts +211 -0
- package/examples/opencode/plugins/openlore-context-injector.ts +165 -0
- package/examples/opencode/plugins/openlore-decision-extractor.test.ts +131 -0
- package/examples/opencode/plugins/openlore-decision-extractor.ts +322 -0
- package/examples/opencode/plugins/openlore-enforcer.ts +227 -0
- package/examples/opencode/prompts/sisyphus-sdd.md +150 -0
- package/examples/opencode-skills/openlore-analyze-codebase/SKILL.md +101 -0
- package/examples/opencode-skills/openlore-brainstorm/SKILL.md +354 -0
- package/examples/opencode-skills/openlore-debug/SKILL.md +291 -0
- package/examples/opencode-skills/openlore-execute-refactor/SKILL.md +241 -0
- package/examples/opencode-skills/openlore-generate/SKILL.md +236 -0
- package/examples/opencode-skills/openlore-implement-story/SKILL.md +251 -0
- package/examples/opencode-skills/openlore-plan-refactor/SKILL.md +298 -0
- package/examples/opencode-skills/openlore-review-changes/SKILL.md +134 -0
- package/examples/opencode-skills/openlore-write-tests/SKILL.md +230 -0
- package/examples/openspec-analysis/README.md +59 -0
- package/examples/openspec-analysis/SUMMARY.md +72 -0
- package/examples/openspec-analysis/config.json +16 -0
- package/examples/openspec-analysis/dependencies.mermaid +35 -0
- package/examples/openspec-analysis/dependency-graph.json +12116 -0
- package/examples/openspec-analysis/llm-context.json +119 -0
- package/examples/openspec-analysis/repo-structure.json +871 -0
- package/examples/openspec-cli/README.md +67 -0
- package/examples/openspec-cli/openspec/config.yaml +26 -0
- package/examples/openspec-cli/openspec/specs/architecture/spec.md +178 -0
- package/examples/openspec-cli/openspec/specs/artifact-graph/spec.md +143 -0
- package/examples/openspec-cli/openspec/specs/cli/spec.md +138 -0
- package/examples/openspec-cli/openspec/specs/overview/spec.md +60 -0
- package/examples/openspec-cli/openspec/specs/parsing/spec.md +123 -0
- package/examples/openspec-cli/openspec/specs/validation/spec.md +108 -0
- package/examples/spec-kit/README.md +104 -0
- package/examples/spec-kit/commands/drift.md +87 -0
- package/examples/spec-kit/commands/orient.md +138 -0
- package/examples/spec-kit/extension.yml +54 -0
- package/package.json +125 -0
- package/src/viewer/InteractiveGraphViewer.jsx +1600 -0
- package/src/viewer/app/index.html +17 -0
- package/src/viewer/app/main.jsx +13 -0
- package/src/viewer/components/ArchitectureView.jsx +177 -0
- package/src/viewer/components/ChatPanel.jsx +450 -0
- package/src/viewer/components/ClassGraph.jsx +782 -0
- package/src/viewer/components/ClusterGraph.jsx +469 -0
- package/src/viewer/components/FilterBar.jsx +179 -0
- package/src/viewer/components/FlatGraph.jsx +282 -0
- package/src/viewer/components/MicroComponents.jsx +85 -0
- package/src/viewer/hooks/usePanZoom.js +79 -0
- package/src/viewer/utils/constants.js +64 -0
- package/src/viewer/utils/graph-helpers.js +303 -0
- package/src/viewer/utils/graph-helpers.test.ts +39 -0
- package/src/viewer/utils/themes.js +206 -0
- package/stubs/tree-sitter-cli-stub/package.json +6 -0
|
@@ -0,0 +1,693 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP tool handlers for call-graph analysis:
|
|
3
|
+
* get_call_graph, get_subgraph, analyze_impact, get_critical_hubs,
|
|
4
|
+
* get_leaf_functions, get_low_risk_refactor_candidates, get_god_functions,
|
|
5
|
+
* trace_execution_path.
|
|
6
|
+
*/
|
|
7
|
+
import { validateDirectory, readCachedContext } from './utils.js';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { RISK_SCORE_FAN_IN_WEIGHT, RISK_SCORE_FAN_OUT_WEIGHT, RISK_SCORE_HUB_BONUS, RISK_SCORE_BLAST_RADIUS_WEIGHT, RISK_SCORE_LOW_THRESHOLD, RISK_SCORE_MEDIUM_THRESHOLD, GOD_FUNCTION_FAN_OUT_THRESHOLD, REFACTOR_SRP_FAN_OUT_THRESHOLD, LOW_RISK_MAX_FAN_IN, LOW_RISK_MAX_FAN_OUT, CRITICAL_HUBS_DEFAULT_MIN_FAN_IN, SUBGRAPH_DEFAULT_MAX_DEPTH, SUBGRAPH_MAX_DEPTH_LIMIT, CRITICALITY_FAN_IN_WEIGHT, CRITICALITY_FAN_OUT_WEIGHT, CRITICALITY_VIOLATION_BONUS, STABILITY_SCORE_CAN_REFACTOR, STABILITY_SCORE_STABILISE_FIRST, LOW_RISK_REFACTOR_CANDIDATES_DEFAULT_LIMIT, LEAF_FUNCTIONS_DEFAULT_LIMIT, HUB_HIGH_FAN_IN_THRESHOLD, HUB_HIGH_FAN_OUT_THRESHOLD, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR, TRACE_PATH_DEFAULT_MAX_DEPTH, TRACE_PATH_MAX_PATHS, } from '../../../constants.js';
|
|
10
|
+
import { getFileGodFunctions, extractSubgraph } from '../../analyzer/subgraph-extractor.js';
|
|
11
|
+
import { readOpenLoreConfig } from '../config-manager.js';
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// SHARED GRAPH HELPERS (also exported for chat-tools.ts)
|
|
14
|
+
// ============================================================================
|
|
15
|
+
/**
|
|
16
|
+
* Build forward (caller→callees) and backward (callee→callers) adjacency maps
|
|
17
|
+
* from a serialised call graph, returning both maps and a node lookup.
|
|
18
|
+
*
|
|
19
|
+
* Includes inheritance edges: parent class methods point forward to all child
|
|
20
|
+
* class methods so blast-radius BFS propagates through the class hierarchy.
|
|
21
|
+
*/
|
|
22
|
+
export function buildAdjacency(cg) {
|
|
23
|
+
const nodeMap = new Map(cg.nodes.map(n => [n.id, n]));
|
|
24
|
+
const forward = new Map(); // callerId → Set<calleeId>
|
|
25
|
+
const backward = new Map(); // calleeId → Set<callerId>
|
|
26
|
+
for (const n of cg.nodes) {
|
|
27
|
+
forward.set(n.id, new Set());
|
|
28
|
+
backward.set(n.id, new Set());
|
|
29
|
+
}
|
|
30
|
+
for (const e of cg.edges) {
|
|
31
|
+
if (!e.calleeId)
|
|
32
|
+
continue;
|
|
33
|
+
// Ensure external nodes (not in cg.nodes) get adjacency entries
|
|
34
|
+
if (!forward.has(e.calleeId))
|
|
35
|
+
forward.set(e.calleeId, new Set());
|
|
36
|
+
if (!backward.has(e.calleeId))
|
|
37
|
+
backward.set(e.calleeId, new Set());
|
|
38
|
+
forward.get(e.callerId)?.add(e.calleeId);
|
|
39
|
+
backward.get(e.calleeId)?.add(e.callerId);
|
|
40
|
+
}
|
|
41
|
+
// Expand class-level inheritance: parent method → child method
|
|
42
|
+
// (changing a parent propagates blast radius to all child class methods)
|
|
43
|
+
const classMap = new Map((cg.classes ?? []).map(c => [c.id, c]));
|
|
44
|
+
for (const ie of (cg.inheritanceEdges ?? [])) {
|
|
45
|
+
const parentClass = classMap.get(ie.parentId);
|
|
46
|
+
const childClass = classMap.get(ie.childId);
|
|
47
|
+
if (!parentClass || !childClass)
|
|
48
|
+
continue;
|
|
49
|
+
// Guard against N×M explosion on large classes
|
|
50
|
+
if (parentClass.methodIds.length * childClass.methodIds.length > 200)
|
|
51
|
+
continue;
|
|
52
|
+
for (const parentMethodId of parentClass.methodIds) {
|
|
53
|
+
if (!forward.has(parentMethodId))
|
|
54
|
+
forward.set(parentMethodId, new Set());
|
|
55
|
+
for (const childMethodId of childClass.methodIds) {
|
|
56
|
+
if (!backward.has(childMethodId))
|
|
57
|
+
backward.set(childMethodId, new Set());
|
|
58
|
+
forward.get(parentMethodId).add(childMethodId);
|
|
59
|
+
backward.get(childMethodId).add(parentMethodId);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { nodeMap, forward, backward };
|
|
64
|
+
}
|
|
65
|
+
/** BFS up to `maxDepth`. Returns a map of visited node-id → depth reached. */
|
|
66
|
+
export function bfs(seeds, adjacency, maxDepth) {
|
|
67
|
+
const visited = new Map();
|
|
68
|
+
const queue = seeds.map(id => ({ id, depth: 0 }));
|
|
69
|
+
for (const id of seeds)
|
|
70
|
+
visited.set(id, 0);
|
|
71
|
+
while (queue.length > 0) {
|
|
72
|
+
const { id, depth } = queue.shift();
|
|
73
|
+
if (depth >= maxDepth)
|
|
74
|
+
continue;
|
|
75
|
+
for (const nId of adjacency.get(id) ?? []) {
|
|
76
|
+
if (!visited.has(nId)) {
|
|
77
|
+
visited.set(nId, depth + 1);
|
|
78
|
+
queue.push({ id: nId, depth: depth + 1 });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return visited;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* DB-backed lazy BFS — fetches only edges for visited nodes instead of loading all edges.
|
|
86
|
+
* direction: 'forward' = downstream (callees), 'backward' = upstream (callers).
|
|
87
|
+
*/
|
|
88
|
+
export function bfsFromDB(seeds, direction, maxDepth, es) {
|
|
89
|
+
const visited = new Map();
|
|
90
|
+
for (const id of seeds)
|
|
91
|
+
visited.set(id, 0);
|
|
92
|
+
// Level-by-level BFS: one batch query per depth level, O(maxDepth) SQL queries.
|
|
93
|
+
// Recursive CTE was tested but regressed backward BFS on high fan-in hubs (19ms→30ms):
|
|
94
|
+
// SQLite UNION deduplicates on (id,depth) pairs, so (X,1) and (X,2) are both kept,
|
|
95
|
+
// causing path explosion. Iterative frontier never re-visits a node.
|
|
96
|
+
let frontier = seeds.filter(id => !id.startsWith('external::'));
|
|
97
|
+
for (let depth = 0; depth < maxDepth && frontier.length > 0; depth++) {
|
|
98
|
+
const edges = direction === 'forward'
|
|
99
|
+
? es.getCalleesForIds(frontier)
|
|
100
|
+
: es.getCallersForIds(frontier);
|
|
101
|
+
const nextFrontier = [];
|
|
102
|
+
for (const e of edges) {
|
|
103
|
+
const nId = direction === 'forward' ? e.calleeId : e.callerId;
|
|
104
|
+
if (!visited.has(nId) && !nId.startsWith('external::')) {
|
|
105
|
+
visited.set(nId, depth + 1);
|
|
106
|
+
nextFrontier.push(nId);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
frontier = nextFrontier;
|
|
110
|
+
}
|
|
111
|
+
return visited;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Compute a risk score [0–100] for a node.
|
|
115
|
+
*
|
|
116
|
+
* Weights: fan-in × 4, fan-out × 2, isHub × 20, blastRadius × 1.5. Capped at 100.
|
|
117
|
+
*/
|
|
118
|
+
export function computeRiskScore(node, blastRadius, isHub) {
|
|
119
|
+
const raw = (node.fanIn ?? 0) * RISK_SCORE_FAN_IN_WEIGHT +
|
|
120
|
+
(node.fanOut ?? 0) * RISK_SCORE_FAN_OUT_WEIGHT +
|
|
121
|
+
(isHub ? RISK_SCORE_HUB_BONUS : 0) +
|
|
122
|
+
blastRadius * RISK_SCORE_BLAST_RADIUS_WEIGHT;
|
|
123
|
+
return Math.min(100, Math.round(raw));
|
|
124
|
+
}
|
|
125
|
+
/** Derive a plain-language refactoring strategy from the risk profile. */
|
|
126
|
+
export function recommendStrategy(riskScore, fanIn, fanOut, isHub) {
|
|
127
|
+
if (riskScore <= RISK_SCORE_LOW_THRESHOLD) {
|
|
128
|
+
return {
|
|
129
|
+
approach: 'refactor freely',
|
|
130
|
+
rationale: 'Low fan-in and fan-out. Safe to rename, extract, or rewrite inline. ' +
|
|
131
|
+
'A single PR with unit tests is sufficient.',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (riskScore <= RISK_SCORE_MEDIUM_THRESHOLD) {
|
|
135
|
+
return {
|
|
136
|
+
approach: 'refactor with tests',
|
|
137
|
+
rationale: 'Moderate caller count. Write characterisation tests before changing the signature. ' +
|
|
138
|
+
'Prefer additive changes (new overload / wrapper) then migrate callers.',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (isHub && fanOut > REFACTOR_SRP_FAN_OUT_THRESHOLD) {
|
|
142
|
+
return {
|
|
143
|
+
approach: 'split responsibility (SRP)',
|
|
144
|
+
rationale: 'God-function: high fan-in AND high fan-out. Extract cohesive sub-responsibilities ' +
|
|
145
|
+
'into smaller functions behind a thin façade. Migrate callers incrementally.',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (isHub) {
|
|
149
|
+
return {
|
|
150
|
+
approach: 'introduce façade',
|
|
151
|
+
rationale: 'Critical hub with many callers. Do not change the public signature. ' +
|
|
152
|
+
'Introduce a façade or adapter layer, move logic behind it, ' +
|
|
153
|
+
'then update callers in waves.',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (fanOut > GOD_FUNCTION_FAN_OUT_THRESHOLD) {
|
|
157
|
+
return {
|
|
158
|
+
approach: 'decompose fan-out',
|
|
159
|
+
rationale: 'Too many outgoing dependencies. Extract orchestration logic into smaller coordinators. ' +
|
|
160
|
+
'Consider dependency injection to decouple from concrete callees.',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
return {
|
|
164
|
+
approach: 'incremental extraction',
|
|
165
|
+
rationale: 'High risk due to caller count. Use the Strangler-Fig pattern: introduce a parallel ' +
|
|
166
|
+
'implementation, migrate callers one by one, then delete the original.',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
export function nodeToSummary(n) {
|
|
170
|
+
if (!n)
|
|
171
|
+
return { name: '', file: '', className: null, depth: 0 };
|
|
172
|
+
return { name: n.name, file: n.filePath, className: n.className ?? null, depth: 0 };
|
|
173
|
+
}
|
|
174
|
+
// ============================================================================
|
|
175
|
+
// HANDLERS
|
|
176
|
+
// ============================================================================
|
|
177
|
+
/**
|
|
178
|
+
* Return the call graph summary from cached analysis.
|
|
179
|
+
*/
|
|
180
|
+
export async function handleGetCallGraph(directory) {
|
|
181
|
+
const absDir = await validateDirectory(directory);
|
|
182
|
+
const ctx = await readCachedContext(absDir);
|
|
183
|
+
if (!ctx)
|
|
184
|
+
return { error: 'No analysis found. Run analyze_codebase first.' };
|
|
185
|
+
if (!ctx.callGraph)
|
|
186
|
+
return { error: 'Call graph not available in cached analysis. Re-run analyze_codebase.' };
|
|
187
|
+
const cg = ctx.callGraph;
|
|
188
|
+
return {
|
|
189
|
+
stats: cg.stats,
|
|
190
|
+
hubFunctions: cg.hubFunctions.map(n => ({
|
|
191
|
+
name: n.name, file: n.filePath, className: n.className,
|
|
192
|
+
fanIn: n.fanIn, fanOut: n.fanOut, language: n.language,
|
|
193
|
+
})),
|
|
194
|
+
entryPoints: cg.entryPoints.map(n => ({
|
|
195
|
+
name: n.name, file: n.filePath, className: n.className, language: n.language,
|
|
196
|
+
})),
|
|
197
|
+
layerViolations: cg.layerViolations,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Extract a depth-limited subgraph centred on a named function.
|
|
202
|
+
* Falls back to semantic search if no exact name match is found.
|
|
203
|
+
*/
|
|
204
|
+
export async function handleGetSubgraph(directory, functionName, direction = 'downstream', maxDepth = SUBGRAPH_DEFAULT_MAX_DEPTH, format = 'json') {
|
|
205
|
+
maxDepth = Math.max(1, Math.min(maxDepth, SUBGRAPH_MAX_DEPTH_LIMIT));
|
|
206
|
+
const absDir = await validateDirectory(directory);
|
|
207
|
+
const ctx = await readCachedContext(absDir);
|
|
208
|
+
if (!ctx)
|
|
209
|
+
return { error: 'No analysis found. Run analyze_codebase first.' };
|
|
210
|
+
if (!ctx.edgeStore)
|
|
211
|
+
return { error: 'Call graph DB not available. Re-run analyze_codebase.' };
|
|
212
|
+
const lower = functionName.toLowerCase();
|
|
213
|
+
let seeds = ctx.edgeStore.searchNodes(lower);
|
|
214
|
+
// Semantic search fallback when no name match
|
|
215
|
+
if (seeds.length === 0) {
|
|
216
|
+
try {
|
|
217
|
+
const { VectorIndex } = await import('../../analyzer/vector-index.js');
|
|
218
|
+
const { EmbeddingService } = await import('../../analyzer/embedding-service.js');
|
|
219
|
+
const outputDir = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR);
|
|
220
|
+
if (VectorIndex.exists(outputDir)) {
|
|
221
|
+
let embedSvc = null;
|
|
222
|
+
try {
|
|
223
|
+
embedSvc = EmbeddingService.fromEnv();
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
const cfg = await readOpenLoreConfig(absDir);
|
|
227
|
+
if (cfg?.embedding)
|
|
228
|
+
embedSvc = EmbeddingService.fromConfig(cfg) ?? null;
|
|
229
|
+
}
|
|
230
|
+
if (embedSvc) {
|
|
231
|
+
const results = await VectorIndex.search(outputDir, functionName, embedSvc, { limit: 1 });
|
|
232
|
+
if (results.length > 0) {
|
|
233
|
+
const matched = ctx.edgeStore.getNode(results[0].record.id);
|
|
234
|
+
if (matched)
|
|
235
|
+
seeds = [matched];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch { /* ignore fallback errors */ }
|
|
241
|
+
}
|
|
242
|
+
if (seeds.length === 0)
|
|
243
|
+
return { error: `No function matching "${functionName}" found in call graph.` };
|
|
244
|
+
const seedIds = seeds.map(s => s.id);
|
|
245
|
+
const fwdVisited = (direction === 'downstream' || direction === 'both')
|
|
246
|
+
? bfsFromDB(seedIds, 'forward', maxDepth, ctx.edgeStore)
|
|
247
|
+
: new Map();
|
|
248
|
+
const bwdVisited = (direction === 'upstream' || direction === 'both')
|
|
249
|
+
? bfsFromDB(seedIds, 'backward', maxDepth, ctx.edgeStore)
|
|
250
|
+
: new Map();
|
|
251
|
+
const visitedIds = new Set([...fwdVisited.keys(), ...bwdVisited.keys()]);
|
|
252
|
+
const resolveNode = (id) => ctx.edgeStore.getNode(id);
|
|
253
|
+
const visibleNodes = Array.from(visitedIds)
|
|
254
|
+
.map(id => resolveNode(id))
|
|
255
|
+
.filter(Boolean)
|
|
256
|
+
// stdlib nodes (Array.isArray, t.slice, …) are noise — exclude unless they are a seed
|
|
257
|
+
.filter(n => !n.isExternal || n.externalKind !== 'stdlib' || seeds.some(s => s.id === n.id));
|
|
258
|
+
const subNodes = visibleNodes.map(n => ({
|
|
259
|
+
name: n.isExternal ? `[external] ${n.name}` : n.name,
|
|
260
|
+
file: n.filePath,
|
|
261
|
+
className: n.className,
|
|
262
|
+
fanIn: n.fanIn, fanOut: n.fanOut, language: n.language,
|
|
263
|
+
isExternal: n.isExternal ?? false,
|
|
264
|
+
externalKind: n.externalKind,
|
|
265
|
+
isSeed: seeds.some(s => s.id === n.id),
|
|
266
|
+
}));
|
|
267
|
+
const subEdges = Array.from(visitedIds).flatMap(id => ctx.edgeStore.getCallees(id)
|
|
268
|
+
.filter(e => e.calleeId && visitedIds.has(e.calleeId))
|
|
269
|
+
.map(e => {
|
|
270
|
+
const callerN = resolveNode(e.callerId);
|
|
271
|
+
const calleeN = resolveNode(e.calleeId);
|
|
272
|
+
return {
|
|
273
|
+
caller: callerN?.name ?? e.callerId,
|
|
274
|
+
callee: calleeN?.isExternal ? `[external] ${calleeN.name}` : (calleeN?.name ?? e.calleeId),
|
|
275
|
+
callerFile: callerN?.filePath,
|
|
276
|
+
calleeFile: calleeN?.filePath,
|
|
277
|
+
kind: e.kind ?? 'calls',
|
|
278
|
+
callType: e.callType,
|
|
279
|
+
};
|
|
280
|
+
}));
|
|
281
|
+
if (format === 'mermaid') {
|
|
282
|
+
const idOf = new Map();
|
|
283
|
+
subNodes.forEach((n, i) => idOf.set(n.name + '|' + n.file, `n${i}`));
|
|
284
|
+
const nodeLines = subNodes.map(n => {
|
|
285
|
+
const id = idOf.get(n.name + '|' + n.file);
|
|
286
|
+
const label = `"${n.name}\\n${n.file}"`;
|
|
287
|
+
return n.isSeed ? ` ${id}[${label}]:::seed` : ` ${id}[${label}]`;
|
|
288
|
+
});
|
|
289
|
+
const edgeLines = subEdges.map(e => {
|
|
290
|
+
const fromId = idOf.get(e.caller + '|' + e.callerFile) ?? e.caller;
|
|
291
|
+
const toId = idOf.get(e.callee + '|' + e.calleeFile) ?? e.callee;
|
|
292
|
+
return ` ${fromId} --> ${toId}`;
|
|
293
|
+
});
|
|
294
|
+
const deduped = [...new Set(edgeLines)];
|
|
295
|
+
const diagram = [
|
|
296
|
+
'flowchart LR',
|
|
297
|
+
' classDef seed fill:#f5a623,stroke:#d4891a,color:#000',
|
|
298
|
+
...nodeLines, ...deduped,
|
|
299
|
+
].join('\n');
|
|
300
|
+
return `\`\`\`mermaid\n${diagram}\n\`\`\`\n\n` +
|
|
301
|
+
`_${subNodes.length} nodes · ${deduped.length} edges · seeds: ${seeds.map(s => s.name).join(', ')}_`;
|
|
302
|
+
}
|
|
303
|
+
return {
|
|
304
|
+
query: { functionName, direction, maxDepth },
|
|
305
|
+
seeds: seeds.map(n => ({ name: n.name, file: n.filePath })),
|
|
306
|
+
stats: { nodes: subNodes.length, edges: subEdges.length },
|
|
307
|
+
nodes: subNodes,
|
|
308
|
+
edges: subEdges,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Deep impact analysis for a single symbol.
|
|
313
|
+
* Falls back to semantic search if no exact name match is found.
|
|
314
|
+
*/
|
|
315
|
+
export async function handleAnalyzeImpact(directory, symbol, depth = 2) {
|
|
316
|
+
const absDir = await validateDirectory(directory);
|
|
317
|
+
const ctx = await readCachedContext(absDir);
|
|
318
|
+
if (!ctx)
|
|
319
|
+
return { error: 'No analysis found. Run analyze_codebase first.' };
|
|
320
|
+
if (!ctx.edgeStore)
|
|
321
|
+
return { error: 'Call graph DB not available. Re-run analyze_codebase.' };
|
|
322
|
+
const lower = symbol.toLowerCase();
|
|
323
|
+
let seeds = ctx.edgeStore.searchNodes(lower);
|
|
324
|
+
// Semantic search fallback when no name match
|
|
325
|
+
if (seeds.length === 0) {
|
|
326
|
+
try {
|
|
327
|
+
const { VectorIndex } = await import('../../analyzer/vector-index.js');
|
|
328
|
+
const { EmbeddingService } = await import('../../analyzer/embedding-service.js');
|
|
329
|
+
const outputDir = join(absDir, OPENLORE_DIR, OPENLORE_ANALYSIS_SUBDIR);
|
|
330
|
+
if (VectorIndex.exists(outputDir)) {
|
|
331
|
+
let embedSvc = null;
|
|
332
|
+
try {
|
|
333
|
+
embedSvc = EmbeddingService.fromEnv();
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
const cfg = await readOpenLoreConfig(absDir);
|
|
337
|
+
if (cfg?.embedding)
|
|
338
|
+
embedSvc = EmbeddingService.fromConfig(cfg) ?? null;
|
|
339
|
+
}
|
|
340
|
+
if (embedSvc) {
|
|
341
|
+
const results = await VectorIndex.search(outputDir, symbol, embedSvc, { limit: 1 });
|
|
342
|
+
if (results.length > 0) {
|
|
343
|
+
const matched = ctx.edgeStore.getNode(results[0].record.id);
|
|
344
|
+
if (matched)
|
|
345
|
+
seeds = [matched];
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch { /* ignore fallback errors */ }
|
|
351
|
+
}
|
|
352
|
+
if (seeds.length === 0)
|
|
353
|
+
return { error: `No function matching "${symbol}" found in call graph.` };
|
|
354
|
+
const seedIds = seeds.map(n => n.id);
|
|
355
|
+
const hubIds = new Set(ctx.edgeStore.getHubs(500).map(n => n.id));
|
|
356
|
+
const upstreamMap = bfsFromDB(seedIds, 'backward', depth, ctx.edgeStore);
|
|
357
|
+
const downstreamMap = bfsFromDB(seedIds, 'forward', depth, ctx.edgeStore);
|
|
358
|
+
const resolveNode = (id) => ctx.edgeStore.getNode(id) ?? undefined;
|
|
359
|
+
const upstreamNodes = [...upstreamMap.entries()]
|
|
360
|
+
.filter(([id]) => !seedIds.includes(id))
|
|
361
|
+
.map(([id, d]) => ({ ...nodeToSummary(resolveNode(id)), depth: d }))
|
|
362
|
+
.filter(n => n.name);
|
|
363
|
+
const downstreamNodes = [...downstreamMap.entries()]
|
|
364
|
+
.filter(([id]) => !seedIds.includes(id))
|
|
365
|
+
.map(([id, d]) => ({ ...nodeToSummary(resolveNode(id)), depth: d }))
|
|
366
|
+
.filter(n => n.name);
|
|
367
|
+
const blastRadius = upstreamNodes.length + downstreamNodes.length;
|
|
368
|
+
const results = seeds.map(seed => {
|
|
369
|
+
const isHub = hubIds.has(seed.id);
|
|
370
|
+
const riskScore = computeRiskScore(seed, blastRadius, isHub);
|
|
371
|
+
const strategy = recommendStrategy(riskScore, seed.fanIn ?? 0, seed.fanOut ?? 0, isHub);
|
|
372
|
+
const criticalPathLeaves = downstreamNodes.filter(n => n.depth === depth).map(n => n.name);
|
|
373
|
+
return {
|
|
374
|
+
symbol: seed.name,
|
|
375
|
+
file: seed.filePath,
|
|
376
|
+
className: seed.className ?? null,
|
|
377
|
+
language: seed.language,
|
|
378
|
+
metrics: { fanIn: seed.fanIn ?? 0, fanOut: seed.fanOut ?? 0, isHub },
|
|
379
|
+
blastRadius: { total: blastRadius, upstream: upstreamNodes.length, downstream: downstreamNodes.length },
|
|
380
|
+
riskScore,
|
|
381
|
+
riskLevel: riskScore <= 20 ? 'low' : riskScore <= 45 ? 'medium' : riskScore <= 70 ? 'high' : 'critical',
|
|
382
|
+
upstreamChain: upstreamNodes,
|
|
383
|
+
downstreamCriticalPath: downstreamNodes,
|
|
384
|
+
criticalPathLeaves,
|
|
385
|
+
recommendedStrategy: strategy,
|
|
386
|
+
};
|
|
387
|
+
});
|
|
388
|
+
return seeds.length === 1 ? results[0] : { matches: results };
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Return the N safest functions to refactor.
|
|
392
|
+
*/
|
|
393
|
+
export async function handleGetLowRiskRefactorCandidates(directory, limit = LOW_RISK_REFACTOR_CANDIDATES_DEFAULT_LIMIT, filePattern) {
|
|
394
|
+
limit = Math.max(1, Math.min(limit, 500));
|
|
395
|
+
const absDir = await validateDirectory(directory);
|
|
396
|
+
const ctx = await readCachedContext(absDir);
|
|
397
|
+
if (!ctx)
|
|
398
|
+
return { error: 'No analysis found. Run analyze_codebase first.' };
|
|
399
|
+
if (!ctx.callGraph)
|
|
400
|
+
return { error: 'Call graph not available. Re-run analyze_codebase.' };
|
|
401
|
+
const cg = ctx.callGraph;
|
|
402
|
+
const hubIds = new Set(cg.hubFunctions.map(n => n.id));
|
|
403
|
+
const entryIds = new Set(cg.entryPoints.map(n => n.id));
|
|
404
|
+
let candidates = cg.nodes.filter(n => {
|
|
405
|
+
const fanIn = n.fanIn ?? 0;
|
|
406
|
+
const fanOut = n.fanOut ?? 0;
|
|
407
|
+
return fanIn <= LOW_RISK_MAX_FAN_IN && fanOut <= LOW_RISK_MAX_FAN_OUT && !hubIds.has(n.id) && !entryIds.has(n.id);
|
|
408
|
+
});
|
|
409
|
+
if (filePattern)
|
|
410
|
+
candidates = candidates.filter(n => n.filePath.includes(filePattern));
|
|
411
|
+
candidates.sort((a, b) => {
|
|
412
|
+
const ra = (a.fanIn ?? 0) + (a.fanOut ?? 0);
|
|
413
|
+
const rb = (b.fanIn ?? 0) + (b.fanOut ?? 0);
|
|
414
|
+
return ra !== rb ? ra - rb : a.name.localeCompare(b.name);
|
|
415
|
+
});
|
|
416
|
+
const top = candidates.slice(0, limit).map(n => ({
|
|
417
|
+
name: n.name, file: n.filePath, className: n.className ?? null, language: n.language,
|
|
418
|
+
fanIn: n.fanIn ?? 0, fanOut: n.fanOut ?? 0,
|
|
419
|
+
riskScore: computeRiskScore(n, 0, false),
|
|
420
|
+
rationale: 'Low fan-in, low fan-out, not a hub — safe to rename, extract, or rewrite.',
|
|
421
|
+
}));
|
|
422
|
+
return {
|
|
423
|
+
total: candidates.length, returned: top.length, candidates: top,
|
|
424
|
+
tip: 'Start with the first candidate and work downward. Each can be changed in isolation.',
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Return leaf functions (fan-out === 0).
|
|
429
|
+
*/
|
|
430
|
+
export async function handleGetLeafFunctions(directory, limit = LEAF_FUNCTIONS_DEFAULT_LIMIT, filePattern, sortBy = 'fanIn') {
|
|
431
|
+
limit = Math.max(1, Math.min(limit, 500));
|
|
432
|
+
const absDir = await validateDirectory(directory);
|
|
433
|
+
const ctx = await readCachedContext(absDir);
|
|
434
|
+
if (!ctx)
|
|
435
|
+
return { error: 'No analysis found. Run analyze_codebase first.' };
|
|
436
|
+
if (!ctx.callGraph)
|
|
437
|
+
return { error: 'Call graph not available. Re-run analyze_codebase.' };
|
|
438
|
+
const cg = ctx.callGraph;
|
|
439
|
+
const hasOutgoing = new Set(cg.edges.filter(e => e.calleeId).map(e => e.callerId));
|
|
440
|
+
let leaves = cg.nodes.filter(n => !n.isExternal && !n.isTest && !hasOutgoing.has(n.id));
|
|
441
|
+
if (filePattern)
|
|
442
|
+
leaves = leaves.filter(n => n.filePath.includes(filePattern));
|
|
443
|
+
leaves.sort((a, b) => {
|
|
444
|
+
if (sortBy === 'fanIn')
|
|
445
|
+
return (b.fanIn ?? 0) - (a.fanIn ?? 0);
|
|
446
|
+
if (sortBy === 'name')
|
|
447
|
+
return a.name.localeCompare(b.name);
|
|
448
|
+
return a.filePath.localeCompare(b.filePath) || a.name.localeCompare(b.name);
|
|
449
|
+
});
|
|
450
|
+
const top = leaves.slice(0, limit).map(n => ({
|
|
451
|
+
name: n.name, file: n.filePath, className: n.className ?? null, language: n.language,
|
|
452
|
+
fanIn: n.fanIn ?? 0, fanOut: 0, blastRadius: 0,
|
|
453
|
+
riskScore: computeRiskScore(n, 0, false),
|
|
454
|
+
refactorAdvice: (n.fanIn ?? 0) === 0
|
|
455
|
+
? 'Unreachable or dead code — safe to delete after confirmation.'
|
|
456
|
+
: 'Pure leaf: rewrite freely, then re-run tests for its callers.',
|
|
457
|
+
}));
|
|
458
|
+
return {
|
|
459
|
+
totalLeaves: leaves.length, returned: top.length, sortedBy: sortBy, leaves: top,
|
|
460
|
+
insight: 'Refactoring leaves bottom-up lets you build confidence and test coverage before tackling higher-risk hubs.',
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Return critical hub functions ranked by composite criticality.
|
|
465
|
+
*/
|
|
466
|
+
export async function handleGetCriticalHubs(directory, limit = 10, minFanIn = CRITICAL_HUBS_DEFAULT_MIN_FAN_IN) {
|
|
467
|
+
limit = Math.max(1, Math.min(limit, 500));
|
|
468
|
+
minFanIn = Math.max(1, Math.min(minFanIn, 100));
|
|
469
|
+
const absDir = await validateDirectory(directory);
|
|
470
|
+
const ctx = await readCachedContext(absDir);
|
|
471
|
+
if (!ctx)
|
|
472
|
+
return { error: 'No analysis found. Run analyze_codebase first.' };
|
|
473
|
+
if (!ctx.callGraph)
|
|
474
|
+
return { error: 'Call graph not available. Re-run analyze_codebase.' };
|
|
475
|
+
const cg = ctx.callGraph;
|
|
476
|
+
const nodeMap = new Map(cg.nodes.map(n => [n.id, n]));
|
|
477
|
+
const violatorFiles = new Set(cg.layerViolations.flatMap(v => [nodeMap.get(v.callerId)?.filePath, nodeMap.get(v.calleeId)?.filePath].filter(Boolean)));
|
|
478
|
+
const hubs = cg.nodes
|
|
479
|
+
.filter(n => !n.isExternal && !n.isTest && (n.fanIn ?? 0) >= minFanIn)
|
|
480
|
+
.map(n => {
|
|
481
|
+
const fanIn = n.fanIn ?? 0;
|
|
482
|
+
const fanOut = n.fanOut ?? 0;
|
|
483
|
+
const hasViolation = violatorFiles.has(n.filePath);
|
|
484
|
+
const criticality = fanIn * CRITICALITY_FAN_IN_WEIGHT + fanOut * CRITICALITY_FAN_OUT_WEIGHT + (hasViolation ? CRITICALITY_VIOLATION_BONUS : 0);
|
|
485
|
+
const stabilityScore = Math.max(0, Math.round(100 - Math.min(100, criticality)));
|
|
486
|
+
let approach;
|
|
487
|
+
let approachRationale;
|
|
488
|
+
if (fanIn >= HUB_HIGH_FAN_IN_THRESHOLD && fanOut >= HUB_HIGH_FAN_OUT_THRESHOLD) {
|
|
489
|
+
approach = 'split responsibility';
|
|
490
|
+
approachRationale = 'God-function: extract cohesive groups of callees into dedicated modules and expose a minimal coordinator interface.';
|
|
491
|
+
}
|
|
492
|
+
else if (fanIn >= HUB_HIGH_FAN_IN_THRESHOLD) {
|
|
493
|
+
approach = 'introduce façade';
|
|
494
|
+
approachRationale = 'Heavily depended-upon: keep the signature stable, move implementation behind a façade, then migrate callers to the new interface over time.';
|
|
495
|
+
}
|
|
496
|
+
else if (fanOut >= HUB_HIGH_FAN_OUT_THRESHOLD) {
|
|
497
|
+
approach = 'delegate';
|
|
498
|
+
approachRationale = "Too many outgoing calls: extract groups of related calls into helper services and delegate to them, reducing this function's orchestration burden.";
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
approach = 'extract';
|
|
502
|
+
approachRationale = 'Moderate hub: identify the core responsibility, extract secondary logic into well-named helpers, and add integration tests before changing callers.';
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
name: n.name, file: n.filePath, className: n.className ?? null, language: n.language,
|
|
506
|
+
fanIn, fanOut, hasLayerViolation: hasViolation,
|
|
507
|
+
criticality: Math.round(criticality * 10) / 10,
|
|
508
|
+
stabilityScore,
|
|
509
|
+
riskScore: computeRiskScore(n, fanIn + fanOut, true),
|
|
510
|
+
recommendedApproach: { approach, rationale: approachRationale },
|
|
511
|
+
refactoringOrder: stabilityScore >= STABILITY_SCORE_CAN_REFACTOR ? 'can refactor now with good test coverage'
|
|
512
|
+
: stabilityScore >= STABILITY_SCORE_STABILISE_FIRST ? 'refactor after stabilising its leaf dependencies'
|
|
513
|
+
: 'defer — stabilise surrounding code first, then tackle incrementally',
|
|
514
|
+
};
|
|
515
|
+
})
|
|
516
|
+
.sort((a, b) => b.criticality - a.criticality)
|
|
517
|
+
.slice(0, limit);
|
|
518
|
+
return {
|
|
519
|
+
totalHubs: cg.nodes.filter(n => !n.isExternal && !n.isTest && (n.fanIn ?? 0) >= minFanIn).length,
|
|
520
|
+
returned: hubs.length, minFanIn, hubs,
|
|
521
|
+
guidance: 'Start with hubs that have the highest stabilityScore (easiest wins). Defer hubs with stabilityScore < 30 until their dependencies are cleaner.',
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Detect god functions (high fan-out) and return their call-graph neighborhood.
|
|
526
|
+
*/
|
|
527
|
+
export async function handleGetGodFunctions(directory, filePath, fanOutThreshold = GOD_FUNCTION_FAN_OUT_THRESHOLD) {
|
|
528
|
+
const absDir = await validateDirectory(directory);
|
|
529
|
+
const ctx = await readCachedContext(absDir);
|
|
530
|
+
if (!ctx)
|
|
531
|
+
return { error: 'No analysis found. Run analyze_codebase first.' };
|
|
532
|
+
if (!ctx.callGraph)
|
|
533
|
+
return { error: 'Call graph not available. Re-run analyze_codebase.' };
|
|
534
|
+
const cg = ctx.callGraph;
|
|
535
|
+
let candidates;
|
|
536
|
+
if (filePath) {
|
|
537
|
+
candidates = getFileGodFunctions(cg, filePath, fanOutThreshold);
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
candidates = cg.nodes.filter(n => !n.isExternal && !n.isTest && n.fanOut >= fanOutThreshold);
|
|
541
|
+
}
|
|
542
|
+
if (candidates.length === 0) {
|
|
543
|
+
return { threshold: fanOutThreshold, count: 0, godFunctions: [], message: `No god functions found with fanOut >= ${fanOutThreshold}` };
|
|
544
|
+
}
|
|
545
|
+
const godFunctions = candidates
|
|
546
|
+
.sort((a, b) => b.fanOut - a.fanOut)
|
|
547
|
+
.map(fn => {
|
|
548
|
+
const sub = extractSubgraph(cg, fn);
|
|
549
|
+
const directCallees = [...new Set(sub.edges.filter(([from]) => from === fn.name).map(([, to]) => to))];
|
|
550
|
+
return { name: fn.name, file: fn.filePath, className: fn.className, fanIn: fn.fanIn, fanOut: fn.fanOut, directCallees, subgraphNodes: sub.nodes.length };
|
|
551
|
+
});
|
|
552
|
+
return { threshold: fanOutThreshold, count: godFunctions.length, godFunctions };
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Return the file-level import dependencies for a given file.
|
|
556
|
+
*
|
|
557
|
+
* Uses the dependency-graph.json produced by `openlore analyze`.
|
|
558
|
+
* direction:
|
|
559
|
+
* "imports" — files this file depends on (outgoing edges)
|
|
560
|
+
* "importedBy" — files that depend on this file (incoming edges)
|
|
561
|
+
* "both" — both directions
|
|
562
|
+
*/
|
|
563
|
+
export async function handleGetFileDependencies(directory, filePath, direction = 'both') {
|
|
564
|
+
const absDir = await validateDirectory(directory);
|
|
565
|
+
const depGraphPath = join(absDir, '.openlore', 'analysis', 'dependency-graph.json');
|
|
566
|
+
let graph;
|
|
567
|
+
try {
|
|
568
|
+
const { readFile } = await import('node:fs/promises');
|
|
569
|
+
const raw = await readFile(depGraphPath, 'utf-8');
|
|
570
|
+
graph = JSON.parse(raw);
|
|
571
|
+
}
|
|
572
|
+
catch {
|
|
573
|
+
return { error: 'No dependency graph found. Run "openlore analyze" first.' };
|
|
574
|
+
}
|
|
575
|
+
// Resolve the file path to the same form used in the graph (relative or absolute)
|
|
576
|
+
const node = graph.nodes.find(n => n.file.path === filePath || n.file.absolutePath.endsWith('/' + filePath.replace(/^\//, '')));
|
|
577
|
+
if (!node) {
|
|
578
|
+
return { error: `File not found in dependency graph: ${filePath}`, hint: 'Use a relative path from the project root, e.g. "src/core/analyzer/vector-index.ts"' };
|
|
579
|
+
}
|
|
580
|
+
const nodeIdToPath = new Map(graph.nodes.map(n => [n.id, n.file.path]));
|
|
581
|
+
const imports = (direction === 'imports' || direction === 'both')
|
|
582
|
+
? graph.edges
|
|
583
|
+
.filter(e => e.source === node.id)
|
|
584
|
+
.map(e => ({
|
|
585
|
+
filePath: nodeIdToPath.get(e.target) ?? e.target,
|
|
586
|
+
importedNames: e.importedNames,
|
|
587
|
+
isTypeOnly: e.isTypeOnly,
|
|
588
|
+
}))
|
|
589
|
+
: undefined;
|
|
590
|
+
const importedBy = (direction === 'importedBy' || direction === 'both')
|
|
591
|
+
? graph.edges
|
|
592
|
+
.filter(e => e.target === node.id)
|
|
593
|
+
.map(e => ({
|
|
594
|
+
filePath: nodeIdToPath.get(e.source) ?? e.source,
|
|
595
|
+
importedNames: e.importedNames,
|
|
596
|
+
isTypeOnly: e.isTypeOnly,
|
|
597
|
+
}))
|
|
598
|
+
: undefined;
|
|
599
|
+
return {
|
|
600
|
+
filePath: node.file.path,
|
|
601
|
+
direction,
|
|
602
|
+
importsCount: imports?.length ?? null,
|
|
603
|
+
importedByCount: importedBy?.length ?? null,
|
|
604
|
+
imports,
|
|
605
|
+
importedBy,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Find all execution paths between two functions in the call graph.
|
|
610
|
+
* Useful for debugging: "how does request X reach function Y?",
|
|
611
|
+
* "which call chain produced this error?".
|
|
612
|
+
*
|
|
613
|
+
* Uses DFS with visited-set cycle detection. Returns paths ordered
|
|
614
|
+
* by hop count (shortest first), capped at maxPaths.
|
|
615
|
+
*/
|
|
616
|
+
export async function handleTraceExecutionPath(directory, entryFunction, targetFunction, maxDepth = TRACE_PATH_DEFAULT_MAX_DEPTH, maxPaths = TRACE_PATH_MAX_PATHS) {
|
|
617
|
+
maxDepth = Math.max(1, Math.min(maxDepth, SUBGRAPH_MAX_DEPTH_LIMIT));
|
|
618
|
+
maxPaths = Math.max(1, Math.min(maxPaths, 50));
|
|
619
|
+
const absDir = await validateDirectory(directory);
|
|
620
|
+
const ctx = await readCachedContext(absDir);
|
|
621
|
+
if (!ctx)
|
|
622
|
+
return { error: 'No analysis found. Run analyze_codebase first.' };
|
|
623
|
+
if (!ctx.callGraph)
|
|
624
|
+
return { error: 'Call graph not available. Re-run analyze_codebase.' };
|
|
625
|
+
const cg = ctx.callGraph;
|
|
626
|
+
const { nodeMap, forward } = buildAdjacency(cg);
|
|
627
|
+
const entryLower = entryFunction.toLowerCase();
|
|
628
|
+
const targetLower = targetFunction.toLowerCase();
|
|
629
|
+
const entryNodes = cg.nodes.filter(n => n.name.toLowerCase().includes(entryLower));
|
|
630
|
+
const targetNodes = cg.nodes.filter(n => n.name.toLowerCase().includes(targetLower));
|
|
631
|
+
if (entryNodes.length === 0)
|
|
632
|
+
return { error: `No function matching "${entryFunction}" found in call graph.` };
|
|
633
|
+
if (targetNodes.length === 0)
|
|
634
|
+
return { error: `No function matching "${targetFunction}" found in call graph.` };
|
|
635
|
+
const targetIds = new Set(targetNodes.map(n => n.id));
|
|
636
|
+
const allPaths = [];
|
|
637
|
+
function dfs(currentId, path, visited) {
|
|
638
|
+
if (allPaths.length >= maxPaths)
|
|
639
|
+
return;
|
|
640
|
+
if (targetIds.has(currentId) && path.length > 1) {
|
|
641
|
+
allPaths.push([...path]);
|
|
642
|
+
return; // don't traverse past the target
|
|
643
|
+
}
|
|
644
|
+
if (path.length > maxDepth)
|
|
645
|
+
return;
|
|
646
|
+
for (const neighborId of forward.get(currentId) ?? []) {
|
|
647
|
+
if (!visited.has(neighborId)) {
|
|
648
|
+
visited.add(neighborId);
|
|
649
|
+
path.push(neighborId);
|
|
650
|
+
dfs(neighborId, path, visited);
|
|
651
|
+
path.pop();
|
|
652
|
+
visited.delete(neighborId);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
for (const entry of entryNodes) {
|
|
657
|
+
if (allPaths.length >= maxPaths)
|
|
658
|
+
break;
|
|
659
|
+
dfs(entry.id, [entry.id], new Set([entry.id]));
|
|
660
|
+
}
|
|
661
|
+
if (allPaths.length === 0) {
|
|
662
|
+
return {
|
|
663
|
+
entryFunction,
|
|
664
|
+
targetFunction,
|
|
665
|
+
pathsFound: 0,
|
|
666
|
+
message: `No execution path found from "${entryFunction}" to "${targetFunction}" within depth ${maxDepth}.`,
|
|
667
|
+
hint: 'Try increasing maxDepth, or check whether both functions are in the same connected component of the call graph.',
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
allPaths.sort((a, b) => a.length - b.length);
|
|
671
|
+
const paths = allPaths.map(pathIds => {
|
|
672
|
+
const steps = pathIds.map(id => {
|
|
673
|
+
const node = nodeMap.get(id);
|
|
674
|
+
return node
|
|
675
|
+
? { name: node.name, file: node.filePath, className: node.className ?? null }
|
|
676
|
+
: { name: id, file: '', className: null };
|
|
677
|
+
});
|
|
678
|
+
return {
|
|
679
|
+
hops: pathIds.length - 1,
|
|
680
|
+
chain: steps.map(s => s.name).join(' → '),
|
|
681
|
+
steps,
|
|
682
|
+
};
|
|
683
|
+
});
|
|
684
|
+
return {
|
|
685
|
+
entryFunction: entryNodes[0].name,
|
|
686
|
+
targetFunction: targetNodes[0].name,
|
|
687
|
+
pathsFound: paths.length,
|
|
688
|
+
maxDepth,
|
|
689
|
+
shortestPath: paths[0].chain,
|
|
690
|
+
paths,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
//# sourceMappingURL=graph.js.map
|