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,971 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Route Parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts two complementary sets of data:
|
|
5
|
+
* 1. HTTP CALLS — fetch/axios/ky/got calls in JS/TS frontend files
|
|
6
|
+
* 2. ROUTE DEFS — FastAPI / Flask / Django route declarations in Python files
|
|
7
|
+
*
|
|
8
|
+
* These are then matched by `buildHttpEdges()` to create cross-language edges
|
|
9
|
+
* between the frontend files that call an endpoint and the Python handlers that
|
|
10
|
+
* serve it — filling the gap that static import analysis cannot reach.
|
|
11
|
+
*
|
|
12
|
+
* Matching strategy
|
|
13
|
+
* -----------------
|
|
14
|
+
* Routes are normalised to a canonical form before comparison:
|
|
15
|
+
* - Path parameters are replaced with a placeholder: /items/{id} → /items/:param
|
|
16
|
+
* - Leading slashes are normalised
|
|
17
|
+
* - Query strings are stripped from call-site URLs
|
|
18
|
+
* - Common API prefixes (/api, /api/v1, /v1, …) are tried both with and
|
|
19
|
+
* without the prefix so that a frontend call to /api/v1/search still matches
|
|
20
|
+
* a FastAPI router mounted at /search.
|
|
21
|
+
*
|
|
22
|
+
* Confidence levels
|
|
23
|
+
* -----------------
|
|
24
|
+
* exact — method + full path match
|
|
25
|
+
* path — path matches, method unknown on one side (e.g. bare fetch)
|
|
26
|
+
* fuzzy — normalised path matches after prefix stripping
|
|
27
|
+
*/
|
|
28
|
+
import { readFile } from 'node:fs/promises';
|
|
29
|
+
import { extname } from 'node:path';
|
|
30
|
+
import { getSkeletonContent, detectLanguage } from './code-shaper.js';
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// NORMALISATION HELPERS
|
|
33
|
+
// ============================================================================
|
|
34
|
+
/** Common API prefixes that frontends add but backends may not declare */
|
|
35
|
+
const API_PREFIXES = [
|
|
36
|
+
'/api/v1', '/api/v2', '/api/v3',
|
|
37
|
+
'/api',
|
|
38
|
+
'/v1', '/v2', '/v3',
|
|
39
|
+
];
|
|
40
|
+
/**
|
|
41
|
+
* Reduce a URL/path to a comparable canonical form:
|
|
42
|
+
* - Strip protocol + host if present (https://example.com/foo → /foo)
|
|
43
|
+
* - Strip query string and fragment
|
|
44
|
+
* - Replace path parameters with :param
|
|
45
|
+
* {id}, :id, <int:id>, <id> → :param
|
|
46
|
+
* - Collapse duplicate slashes
|
|
47
|
+
* - Remove trailing slash (except root)
|
|
48
|
+
*/
|
|
49
|
+
export function normalizeUrl(raw) {
|
|
50
|
+
// Remove template-literal variable parts: ${...}
|
|
51
|
+
let url = raw.replace(/\$\{[^}]+\}/g, ':param');
|
|
52
|
+
// Strip protocol + host
|
|
53
|
+
url = url.replace(/^https?:\/\/[^/]+/, '');
|
|
54
|
+
// Strip query string and fragment
|
|
55
|
+
url = url.replace(/[?#].*$/, '');
|
|
56
|
+
// Replace FastAPI / Flask style path params
|
|
57
|
+
url = url.replace(/\{[^}]+\}/g, ':param'); // {item_id}
|
|
58
|
+
url = url.replace(/<[^>]+>/g, ':param'); // <int:item_id>
|
|
59
|
+
url = url.replace(/:[\w]+/g, ':param'); // :item_id (Express style)
|
|
60
|
+
// Collapse duplicate slashes, ensure leading slash
|
|
61
|
+
url = ('/' + url).replace(/\/+/g, '/');
|
|
62
|
+
// Remove trailing slash unless it IS the root
|
|
63
|
+
if (url.length > 1 && url.endsWith('/'))
|
|
64
|
+
url = url.slice(0, -1);
|
|
65
|
+
return url.toLowerCase();
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Return all candidate normalised paths to try for a frontend URL.
|
|
69
|
+
* We try both the full path and the path with each known prefix stripped,
|
|
70
|
+
* to handle cases where the backend router is mounted without the prefix.
|
|
71
|
+
*/
|
|
72
|
+
function candidatePaths(normalizedUrl) {
|
|
73
|
+
const candidates = new Set([normalizedUrl]);
|
|
74
|
+
for (const prefix of API_PREFIXES) {
|
|
75
|
+
if (normalizedUrl.startsWith(prefix + '/') || normalizedUrl === prefix) {
|
|
76
|
+
candidates.add(normalizedUrl.slice(prefix.length) || '/');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return Array.from(candidates);
|
|
80
|
+
}
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// HTTP CALL EXTRACTION (JS / TS)
|
|
83
|
+
// ============================================================================
|
|
84
|
+
/**
|
|
85
|
+
* Extract all HTTP calls from a JavaScript or TypeScript source file.
|
|
86
|
+
*/
|
|
87
|
+
export async function extractHttpCalls(filePath) {
|
|
88
|
+
const ext = extname(filePath).toLowerCase();
|
|
89
|
+
if (!['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'].includes(ext))
|
|
90
|
+
return [];
|
|
91
|
+
let content;
|
|
92
|
+
try {
|
|
93
|
+
content = await readFile(filePath, 'utf8');
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
const calls = [];
|
|
99
|
+
// Strip comments to avoid false matches.
|
|
100
|
+
// The line-comment regex must NOT match `://` inside URLs — we only strip
|
|
101
|
+
// `//` that is preceded by whitespace, punctuation, brackets, or the start
|
|
102
|
+
// of the line (i.e. genuine JS/TS comments, not protocol separators).
|
|
103
|
+
// The character class intentionally includes ) and ] so that patterns like
|
|
104
|
+
// `fetch('/api/items') // comment` are correctly stripped.
|
|
105
|
+
const clean = content
|
|
106
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
107
|
+
.replace(/(^|[\s,;()[\]{}])\/\/.*$/gm, '$1');
|
|
108
|
+
const lines = content.split('\n'); // keep original for line numbers
|
|
109
|
+
// ── fetch ──────────────────────────────────────────────────────────────────
|
|
110
|
+
// fetch('/api/search')
|
|
111
|
+
// fetch(`/api/search/${id}`, { method: 'POST' })
|
|
112
|
+
const fetchRegex = /\bfetch\s*\(\s*(`[^`]+`|'[^']+'|"[^"]+")\s*(?:,\s*\{([^}]*)\})?\s*\)/g;
|
|
113
|
+
let m;
|
|
114
|
+
while ((m = fetchRegex.exec(clean)) !== null) {
|
|
115
|
+
const rawUrl = m[1].replace(/^[`'"]/, '').replace(/[`'"]$/, '');
|
|
116
|
+
const optionsBlock = m[2] ?? '';
|
|
117
|
+
const methodMatch = optionsBlock.match(/method\s*:\s*['"`](\w+)['"`]/i);
|
|
118
|
+
const method = methodMatch ? methodMatch[1].toUpperCase() : 'GET';
|
|
119
|
+
calls.push({
|
|
120
|
+
file: filePath,
|
|
121
|
+
method,
|
|
122
|
+
url: rawUrl,
|
|
123
|
+
normalizedUrl: normalizeUrl(rawUrl),
|
|
124
|
+
line: getLine(lines, m.index),
|
|
125
|
+
client: 'fetch',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// ── axios (method shorthands + generic) ────────────────────────────────────
|
|
129
|
+
// axios.get('/api/items')
|
|
130
|
+
// axios.post('/api/items', data)
|
|
131
|
+
// axios({ method: 'post', url: '/api/items' })
|
|
132
|
+
// axios.request({ method: 'DELETE', url: '/api/items/1' })
|
|
133
|
+
const axiosMethodRegex = /\baxios\.(get|post|put|patch|delete|head|options)\s*\(\s*(`[^`]+`|'[^']+'|"[^"]+")/g;
|
|
134
|
+
while ((m = axiosMethodRegex.exec(clean)) !== null) {
|
|
135
|
+
const method = m[1].toUpperCase();
|
|
136
|
+
const rawUrl = m[2].replace(/^[`'"]/, '').replace(/[`'"]$/, '');
|
|
137
|
+
calls.push({
|
|
138
|
+
file: filePath,
|
|
139
|
+
method,
|
|
140
|
+
url: rawUrl,
|
|
141
|
+
normalizedUrl: normalizeUrl(rawUrl),
|
|
142
|
+
line: getLine(lines, m.index),
|
|
143
|
+
client: 'axios',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// axios({ url: '...', method: '...' }) or axios.request({ ... })
|
|
147
|
+
const axiosConfigRegex = /\baxios(?:\.request)?\s*\(\s*\{([^}]{0,400})\}/g;
|
|
148
|
+
while ((m = axiosConfigRegex.exec(clean)) !== null) {
|
|
149
|
+
const block = m[1];
|
|
150
|
+
const urlMatch = block.match(/url\s*:\s*(`[^`]+`|'[^']+'|"[^"]+")/);
|
|
151
|
+
if (!urlMatch)
|
|
152
|
+
continue;
|
|
153
|
+
const rawUrl = urlMatch[1].replace(/^[`'"]/, '').replace(/[`'"]$/, '');
|
|
154
|
+
const methodMatch = block.match(/method\s*:\s*['"`](\w+)['"`]/i);
|
|
155
|
+
const method = methodMatch ? methodMatch[1].toUpperCase() : 'UNKNOWN';
|
|
156
|
+
calls.push({
|
|
157
|
+
file: filePath,
|
|
158
|
+
method,
|
|
159
|
+
url: rawUrl,
|
|
160
|
+
normalizedUrl: normalizeUrl(rawUrl),
|
|
161
|
+
line: getLine(lines, m.index),
|
|
162
|
+
client: 'axios',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// ── ky ─────────────────────────────────────────────────────────────────────
|
|
166
|
+
// ky.get('/api/items') ky.post('/api/items', { json: data })
|
|
167
|
+
const kyRegex = /\bky\.(get|post|put|patch|delete|head)\s*\(\s*(`[^`]+`|'[^']+'|"[^"]+")/g;
|
|
168
|
+
while ((m = kyRegex.exec(clean)) !== null) {
|
|
169
|
+
const rawUrl = m[2].replace(/^[`'"]/, '').replace(/[`'"]$/, '');
|
|
170
|
+
calls.push({
|
|
171
|
+
file: filePath,
|
|
172
|
+
method: m[1].toUpperCase(),
|
|
173
|
+
url: rawUrl,
|
|
174
|
+
normalizedUrl: normalizeUrl(rawUrl),
|
|
175
|
+
line: getLine(lines, m.index),
|
|
176
|
+
client: 'ky',
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
// ── got ────────────────────────────────────────────────────────────────────
|
|
180
|
+
// got.get('/api/items')
|
|
181
|
+
const gotRegex = /\bgot\.(get|post|put|patch|delete|head)\s*\(\s*(`[^`]+`|'[^']+'|"[^"]+")/g;
|
|
182
|
+
while ((m = gotRegex.exec(clean)) !== null) {
|
|
183
|
+
const rawUrl = m[2].replace(/^[`'"]/, '').replace(/[`'"]$/, '');
|
|
184
|
+
calls.push({
|
|
185
|
+
file: filePath,
|
|
186
|
+
method: m[1].toUpperCase(),
|
|
187
|
+
url: rawUrl,
|
|
188
|
+
normalizedUrl: normalizeUrl(rawUrl),
|
|
189
|
+
line: getLine(lines, m.index),
|
|
190
|
+
client: 'got',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// ── React Query / SWR convenience wrappers ─────────────────────────────────
|
|
194
|
+
// useQuery(['key', id], () => fetch('/api/items')) — already caught above
|
|
195
|
+
// useMutation(() => axios.post('/api/items')) — already caught above
|
|
196
|
+
return calls;
|
|
197
|
+
}
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// ROUTE DEFINITION EXTRACTION (Python)
|
|
200
|
+
// ============================================================================
|
|
201
|
+
/**
|
|
202
|
+
* Extract all route definitions from a Python source file.
|
|
203
|
+
* Supports FastAPI, Starlette, Flask, and Django (urls.py path/re_path).
|
|
204
|
+
*/
|
|
205
|
+
export async function extractRouteDefinitions(filePath) {
|
|
206
|
+
const ext = extname(filePath).toLowerCase();
|
|
207
|
+
if (!['.py', '.pyw'].includes(ext))
|
|
208
|
+
return [];
|
|
209
|
+
let content;
|
|
210
|
+
try {
|
|
211
|
+
content = await readFile(filePath, 'utf8');
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
const routes = [];
|
|
217
|
+
const lines = content.split('\n');
|
|
218
|
+
// Remove comments for cleaner matching
|
|
219
|
+
const clean = content.replace(/#.*$/gm, '');
|
|
220
|
+
// ── FastAPI / Starlette decorators ─────────────────────────────────────────
|
|
221
|
+
// @app.get("/items/{item_id}")
|
|
222
|
+
// @router.post("/search", ...)
|
|
223
|
+
// @app.api_route("/multi", methods=["GET","POST"])
|
|
224
|
+
const fastapiDecoratorRegex = /@(?:app|router|api_router)\.(get|post|put|patch|delete|head|options|trace)\s*\(\s*(['"/][^'")\n]+['"])/gm;
|
|
225
|
+
let m;
|
|
226
|
+
while ((m = fastapiDecoratorRegex.exec(clean)) !== null) {
|
|
227
|
+
const method = m[1].toUpperCase();
|
|
228
|
+
const path = m[2].replace(/^['"]/, '').replace(/['"]$/, '');
|
|
229
|
+
const lineNum = getLine(lines, m.index);
|
|
230
|
+
// The handler name is on the `def` line right after the decorator block
|
|
231
|
+
const handlerName = extractNextDefName(lines, lineNum);
|
|
232
|
+
routes.push({
|
|
233
|
+
file: filePath,
|
|
234
|
+
method,
|
|
235
|
+
path,
|
|
236
|
+
normalizedPath: normalizeUrl(path),
|
|
237
|
+
handlerName,
|
|
238
|
+
framework: 'fastapi',
|
|
239
|
+
line: lineNum,
|
|
240
|
+
contractSource: 'none',
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
// @app.api_route("/path", methods=["GET", "POST"])
|
|
244
|
+
const apiRouteRegex = /@(?:app|router|api_router)\.api_route\s*\(\s*(['"/][^'")\n]+['"]),\s*methods\s*=\s*\[([^\]]+)\]/gm;
|
|
245
|
+
while ((m = apiRouteRegex.exec(clean)) !== null) {
|
|
246
|
+
const path = m[1].replace(/^['"]/, '').replace(/['"]$/, '');
|
|
247
|
+
const lineNum = getLine(lines, m.index);
|
|
248
|
+
const handlerName = extractNextDefName(lines, lineNum);
|
|
249
|
+
// Parse the methods list
|
|
250
|
+
const methodMatches = m[2].matchAll(/['"](\w+)['"]/g);
|
|
251
|
+
for (const mm of methodMatches) {
|
|
252
|
+
routes.push({
|
|
253
|
+
file: filePath,
|
|
254
|
+
method: mm[1].toUpperCase(),
|
|
255
|
+
path,
|
|
256
|
+
normalizedPath: normalizeUrl(path),
|
|
257
|
+
handlerName,
|
|
258
|
+
framework: 'fastapi',
|
|
259
|
+
line: lineNum,
|
|
260
|
+
contractSource: 'none',
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
// ── Flask ──────────────────────────────────────────────────────────────────
|
|
265
|
+
// @app.route("/items", methods=["GET", "POST"])
|
|
266
|
+
// @bp.route("/items/<int:item_id>", methods=["DELETE"])
|
|
267
|
+
const flaskRouteRegex = /@(?:\w+)\.route\s*\(\s*(['"/][^'")\n]+['"]),?\s*(?:methods\s*=\s*\[([^\]]*)\])?\s*\)/gm;
|
|
268
|
+
while ((m = flaskRouteRegex.exec(clean)) !== null) {
|
|
269
|
+
const path = m[1].replace(/^['"]/, '').replace(/['"]$/, '');
|
|
270
|
+
const lineNum = getLine(lines, m.index);
|
|
271
|
+
const handlerName = extractNextDefName(lines, lineNum);
|
|
272
|
+
const rawMethods = m[2];
|
|
273
|
+
if (rawMethods) {
|
|
274
|
+
const methodMatches = rawMethods.matchAll(/['"](\w+)['"]/g);
|
|
275
|
+
for (const mm of methodMatches) {
|
|
276
|
+
routes.push({
|
|
277
|
+
file: filePath,
|
|
278
|
+
method: mm[1].toUpperCase(),
|
|
279
|
+
path,
|
|
280
|
+
normalizedPath: normalizeUrl(path),
|
|
281
|
+
handlerName,
|
|
282
|
+
framework: 'flask',
|
|
283
|
+
line: lineNum,
|
|
284
|
+
contractSource: 'none',
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// Flask default is GET when no methods specified
|
|
290
|
+
routes.push({
|
|
291
|
+
file: filePath,
|
|
292
|
+
method: 'GET',
|
|
293
|
+
path,
|
|
294
|
+
normalizedPath: normalizeUrl(path),
|
|
295
|
+
handlerName,
|
|
296
|
+
framework: 'flask',
|
|
297
|
+
line: lineNum,
|
|
298
|
+
contractSource: 'none',
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// ── Django urls.py ─────────────────────────────────────────────────────────
|
|
303
|
+
// path('api/items/', views.ItemListView.as_view(), name='item-list'),
|
|
304
|
+
// re_path(r'^api/items/(?P<pk>[0-9]+)/$', views.ItemDetailView.as_view()),
|
|
305
|
+
//
|
|
306
|
+
// NOTE: Django views handle HTTP method dispatch internally (via class-based
|
|
307
|
+
// views or decorators), so no method is declared in urls.py. All Django
|
|
308
|
+
// routes are stored with method='UNKNOWN', which means any frontend call
|
|
309
|
+
// matched against a Django route will receive confidence='path' at best —
|
|
310
|
+
// never 'exact'. This may produce false-positive edges when multiple HTTP
|
|
311
|
+
// methods share the same URL pattern. Filter by confidence if this matters.
|
|
312
|
+
const djangoPathRegex = /\bpath\s*\(\s*r?(['"])(.*?)\1\s*,\s*([\w.]+)/gm;
|
|
313
|
+
while ((m = djangoPathRegex.exec(clean)) !== null) {
|
|
314
|
+
const path = '/' + m[2].replace(/\$$/, '').replace(/^\^/, '');
|
|
315
|
+
const handlerName = m[3].split('.').pop() ?? m[3];
|
|
316
|
+
const lineNum = getLine(lines, m.index);
|
|
317
|
+
routes.push({
|
|
318
|
+
file: filePath,
|
|
319
|
+
method: 'UNKNOWN', // Django views handle method internally
|
|
320
|
+
path,
|
|
321
|
+
normalizedPath: normalizeUrl(path),
|
|
322
|
+
handlerName,
|
|
323
|
+
framework: 'django',
|
|
324
|
+
line: lineNum,
|
|
325
|
+
contractSource: 'none',
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return routes;
|
|
329
|
+
}
|
|
330
|
+
// ============================================================================
|
|
331
|
+
// ROUTE DEFINITION EXTRACTION (Java — Spring MVC / JAX-RS)
|
|
332
|
+
// ============================================================================
|
|
333
|
+
const SPRING_METHOD_ANNOTATIONS = [
|
|
334
|
+
['GetMapping', 'GET'],
|
|
335
|
+
['PostMapping', 'POST'],
|
|
336
|
+
['PutMapping', 'PUT'],
|
|
337
|
+
['DeleteMapping', 'DELETE'],
|
|
338
|
+
['PatchMapping', 'PATCH'],
|
|
339
|
+
];
|
|
340
|
+
const JAXRS_METHOD_ANNOTATIONS = [
|
|
341
|
+
['GET', 'GET'],
|
|
342
|
+
['POST', 'POST'],
|
|
343
|
+
['PUT', 'PUT'],
|
|
344
|
+
['DELETE', 'DELETE'],
|
|
345
|
+
['PATCH', 'PATCH'],
|
|
346
|
+
['HEAD', 'HEAD'],
|
|
347
|
+
['OPTIONS', 'OPTIONS'],
|
|
348
|
+
];
|
|
349
|
+
/**
|
|
350
|
+
* Extract a path string from a Spring annotation argument blob.
|
|
351
|
+
* ("/foo") → /foo
|
|
352
|
+
* (value = "/foo") → /foo
|
|
353
|
+
* (path = "/foo") → /foo
|
|
354
|
+
* (value = {"/foo", "/bar"}) → /foo (first only)
|
|
355
|
+
* ("/foo", method=…) → /foo
|
|
356
|
+
*/
|
|
357
|
+
function extractSpringPath(argsBlob) {
|
|
358
|
+
// Positional string: first quoted literal at start, possibly preceded by `{`
|
|
359
|
+
const positional = argsBlob.match(/^\s*\{?\s*"([^"]*)"/);
|
|
360
|
+
if (positional)
|
|
361
|
+
return positional[1];
|
|
362
|
+
const named = argsBlob.match(/(?:value|path)\s*=\s*\{?\s*"([^"]*)"/);
|
|
363
|
+
if (named)
|
|
364
|
+
return named[1];
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Extract HTTP method from a Spring @RequestMapping argument blob.
|
|
369
|
+
* (method = RequestMethod.GET) → GET
|
|
370
|
+
* (method = {RequestMethod.GET, RequestMethod.POST}) → [GET, POST]
|
|
371
|
+
*/
|
|
372
|
+
function extractSpringMethods(argsBlob) {
|
|
373
|
+
const match = argsBlob.match(/method\s*=\s*\{?([^}]+)\}?/);
|
|
374
|
+
if (!match)
|
|
375
|
+
return [];
|
|
376
|
+
const methods = [];
|
|
377
|
+
const methodRegex = /(?:RequestMethod\.)?([A-Z]+)/g;
|
|
378
|
+
let m;
|
|
379
|
+
while ((m = methodRegex.exec(match[1])) !== null) {
|
|
380
|
+
methods.push(m[1].toUpperCase());
|
|
381
|
+
}
|
|
382
|
+
return methods;
|
|
383
|
+
}
|
|
384
|
+
function combineSpringPaths(prefix, path) {
|
|
385
|
+
const normalizedPrefix = prefix.replace(/\/+$/, '');
|
|
386
|
+
const normalizedPath = path ? '/' + path.replace(/^\/+/, '') : '';
|
|
387
|
+
const combined = normalizedPrefix + normalizedPath;
|
|
388
|
+
return combined || '/';
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Scan forward from an annotation line to find the handler method name.
|
|
392
|
+
* Java method signatures: `public ReturnType methodName(args) ...`
|
|
393
|
+
*/
|
|
394
|
+
function extractNextJavaMethodName(lines, annotationLine) {
|
|
395
|
+
const start = annotationLine - 1;
|
|
396
|
+
const maxLook = Math.min(lines.length, start + 20);
|
|
397
|
+
const skipNames = new Set(['if', 'for', 'while', 'switch', 'return', 'class', 'interface', 'enum', 'record', 'new']);
|
|
398
|
+
for (let i = start; i < maxLook; i++) {
|
|
399
|
+
const l = lines[i] ?? '';
|
|
400
|
+
// Skip further annotation lines
|
|
401
|
+
if (l.trim().startsWith('@'))
|
|
402
|
+
continue;
|
|
403
|
+
// Match `[modifiers] ReturnType methodName(` — return type can include
|
|
404
|
+
// generics, arrays, and dotted names.
|
|
405
|
+
const match = l.match(/\b(?:public|private|protected)\s+(?:static\s+|final\s+|abstract\s+|synchronized\s+|default\s+|native\s+)*(?:<[^>]+>\s+)?[\w<>[\], ?.]+?\s+(\w+)\s*\(/);
|
|
406
|
+
if (match && !skipNames.has(match[1]))
|
|
407
|
+
return match[1];
|
|
408
|
+
}
|
|
409
|
+
return 'unknown';
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Extract all HTTP route definitions from a Java source file.
|
|
413
|
+
* Supports Spring MVC (@RestController / @Controller + @RequestMapping and the
|
|
414
|
+
* shorthand @GetMapping / @PostMapping / …) and JAX-RS (@Path + @GET / @POST).
|
|
415
|
+
*/
|
|
416
|
+
export async function extractJavaRouteDefinitions(filePath) {
|
|
417
|
+
const ext = extname(filePath).toLowerCase();
|
|
418
|
+
if (ext !== '.java')
|
|
419
|
+
return [];
|
|
420
|
+
let content;
|
|
421
|
+
try {
|
|
422
|
+
content = await readFile(filePath, 'utf8');
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
const routes = [];
|
|
428
|
+
const lines = content.split('\n');
|
|
429
|
+
// Strip comments but preserve offsets so line numbers stay accurate.
|
|
430
|
+
const clean = content
|
|
431
|
+
.replace(/\/\*[\s\S]*?\*\//g, m => ' '.repeat(m.length))
|
|
432
|
+
.replace(/\/\/.*$/gm, m => ' '.repeat(m.length));
|
|
433
|
+
// ── Detect framework and compute class-level path prefix ───────────────────
|
|
434
|
+
// Spring: class-level @RequestMapping(...) | JAX-RS: class-level @Path(...)
|
|
435
|
+
let springPrefix = '';
|
|
436
|
+
let jaxrsPrefix = '';
|
|
437
|
+
// Find the first class declaration and look at annotations preceding it.
|
|
438
|
+
const classMatch = clean.match(/\bclass\s+\w+/);
|
|
439
|
+
if (classMatch && classMatch.index !== undefined) {
|
|
440
|
+
const preamble = clean.slice(0, classMatch.index);
|
|
441
|
+
const springClassMapping = preamble.match(/@RequestMapping\s*\(([^)]*)\)(?![^@]*@RequestMapping)/);
|
|
442
|
+
if (springClassMapping) {
|
|
443
|
+
const p = extractSpringPath(springClassMapping[1]);
|
|
444
|
+
if (p)
|
|
445
|
+
springPrefix = '/' + p.replace(/^\//, '');
|
|
446
|
+
}
|
|
447
|
+
const jaxrsClassPath = preamble.match(/@Path\s*\(\s*"([^"]+)"\s*\)(?![^@]*@Path)/);
|
|
448
|
+
if (jaxrsClassPath) {
|
|
449
|
+
jaxrsPrefix = '/' + jaxrsClassPath[1].replace(/^\//, '');
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const isSpring = /@(?:Rest)?Controller\b|@(?:Get|Post|Put|Delete|Patch)Mapping\b|@RequestMapping\b/.test(clean);
|
|
453
|
+
const isJaxrs = /@Path\b/.test(clean) && /@(?:GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\b/.test(clean);
|
|
454
|
+
// ── Spring: shorthand mappings (@GetMapping, @PostMapping, …) ──────────────
|
|
455
|
+
if (isSpring) {
|
|
456
|
+
for (const [annotation, method] of SPRING_METHOD_ANNOTATIONS) {
|
|
457
|
+
const re = new RegExp(`@${annotation}\\s*(?:\\(([^)]*)\\))?`, 'g');
|
|
458
|
+
let m;
|
|
459
|
+
while ((m = re.exec(clean)) !== null) {
|
|
460
|
+
const argsBlob = m[1] ?? '';
|
|
461
|
+
const path = extractSpringPath(argsBlob) ?? '';
|
|
462
|
+
const fullPath = combineSpringPaths(springPrefix, path);
|
|
463
|
+
const lineNum = getLine(lines, m.index);
|
|
464
|
+
const handlerName = extractNextJavaMethodName(lines, lineNum);
|
|
465
|
+
routes.push({
|
|
466
|
+
file: filePath,
|
|
467
|
+
method,
|
|
468
|
+
path: fullPath,
|
|
469
|
+
normalizedPath: normalizeUrl(fullPath),
|
|
470
|
+
handlerName,
|
|
471
|
+
framework: 'spring',
|
|
472
|
+
line: lineNum,
|
|
473
|
+
contractSource: 'none',
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
// @RequestMapping(method = RequestMethod.GET, value = "/foo") on a method.
|
|
478
|
+
// The class-level @RequestMapping is skipped because the class declaration
|
|
479
|
+
// immediately follows it — we detect that by checking whether the nearest
|
|
480
|
+
// forward token after the annotation is `class`.
|
|
481
|
+
const reqMappingRegex = /@RequestMapping\s*\(([^)]*)\)/g;
|
|
482
|
+
let m;
|
|
483
|
+
while ((m = reqMappingRegex.exec(clean)) !== null) {
|
|
484
|
+
const argsBlob = m[1];
|
|
485
|
+
const methods = extractSpringMethods(argsBlob);
|
|
486
|
+
if (methods.length === 0)
|
|
487
|
+
continue; // no method= → class-level or unhandled
|
|
488
|
+
// Ensure this annotation is on a method, not on the class. Peek forward
|
|
489
|
+
// past any subsequent annotations and check that we don't hit `class`
|
|
490
|
+
// before a method-like signature.
|
|
491
|
+
const afterIdx = m.index + m[0].length;
|
|
492
|
+
const ahead = clean.slice(afterIdx, afterIdx + 400);
|
|
493
|
+
const nextClass = ahead.search(/\bclass\s+\w+/);
|
|
494
|
+
const nextMethod = ahead.search(/\b(?:public|private|protected)\s+(?:static\s+|final\s+|abstract\s+|synchronized\s+|default\s+|native\s+)*(?:<[^>]+>\s+)?[\w<>[\], ?.]+?\s+\w+\s*\(/);
|
|
495
|
+
if (nextClass >= 0 && (nextMethod < 0 || nextClass < nextMethod))
|
|
496
|
+
continue;
|
|
497
|
+
const path = extractSpringPath(argsBlob) ?? '';
|
|
498
|
+
const fullPath = combineSpringPaths(springPrefix, path);
|
|
499
|
+
const lineNum = getLine(lines, m.index);
|
|
500
|
+
const handlerName = extractNextJavaMethodName(lines, lineNum);
|
|
501
|
+
for (const method of methods) {
|
|
502
|
+
routes.push({
|
|
503
|
+
file: filePath,
|
|
504
|
+
method,
|
|
505
|
+
path: fullPath,
|
|
506
|
+
normalizedPath: normalizeUrl(fullPath),
|
|
507
|
+
handlerName,
|
|
508
|
+
framework: 'spring',
|
|
509
|
+
line: lineNum,
|
|
510
|
+
contractSource: 'none',
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// ── JAX-RS: @GET / @POST / @Path on methods ────────────────────────────────
|
|
516
|
+
if (isJaxrs) {
|
|
517
|
+
// Regex for a Java method signature; used to bound the annotation block
|
|
518
|
+
// of the current method so we don't pick up a @Path from a later method.
|
|
519
|
+
const methodSigRegex = /\b(?:public|private|protected)\s+[^{;]+?\s+\w+\s*\(/;
|
|
520
|
+
for (const [annotation, method] of JAXRS_METHOD_ANNOTATIONS) {
|
|
521
|
+
// Bare annotation with no argument list; path comes from class @Path
|
|
522
|
+
// prefix combined with any @Path on the same method.
|
|
523
|
+
const re = new RegExp(`@${annotation}\\b\\s*(?!\\()`, 'g');
|
|
524
|
+
let m;
|
|
525
|
+
while ((m = re.exec(clean)) !== null) {
|
|
526
|
+
// Only look for a method-level @Path *within this method's annotation
|
|
527
|
+
// block* — i.e. between the @GET and the next method signature.
|
|
528
|
+
const afterIdx = m.index + m[0].length;
|
|
529
|
+
const ahead = clean.slice(afterIdx, afterIdx + 400);
|
|
530
|
+
const sigMatch = methodSigRegex.exec(ahead);
|
|
531
|
+
const window = sigMatch ? ahead.slice(0, sigMatch.index) : ahead;
|
|
532
|
+
const methodPathMatch = window.match(/@Path\s*\(\s*"([^"]+)"\s*\)/);
|
|
533
|
+
const methodPath = methodPathMatch ? '/' + methodPathMatch[1].replace(/^\//, '') : '';
|
|
534
|
+
const fullPath = combineSpringPaths(jaxrsPrefix, methodPath);
|
|
535
|
+
const lineNum = getLine(lines, m.index);
|
|
536
|
+
const handlerName = extractNextJavaMethodName(lines, lineNum);
|
|
537
|
+
routes.push({
|
|
538
|
+
file: filePath,
|
|
539
|
+
method,
|
|
540
|
+
path: fullPath,
|
|
541
|
+
normalizedPath: normalizeUrl(fullPath),
|
|
542
|
+
handlerName,
|
|
543
|
+
framework: 'jaxrs',
|
|
544
|
+
line: lineNum,
|
|
545
|
+
contractSource: 'none',
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return routes;
|
|
551
|
+
}
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// EDGE BUILDER
|
|
554
|
+
// ============================================================================
|
|
555
|
+
/**
|
|
556
|
+
* Match HTTP calls from JS/TS files against route definitions from Python files
|
|
557
|
+
* and return cross-language edges.
|
|
558
|
+
*
|
|
559
|
+
* Pass in pre-extracted calls and routes (so callers can cache them across
|
|
560
|
+
* multiple graph builds without re-parsing).
|
|
561
|
+
*/
|
|
562
|
+
export function buildHttpEdges(calls, routes) {
|
|
563
|
+
const edges = [];
|
|
564
|
+
// Index routes by normalised path for O(1) lookup
|
|
565
|
+
const routesByPath = new Map();
|
|
566
|
+
for (const route of routes) {
|
|
567
|
+
const existing = routesByPath.get(route.normalizedPath) ?? [];
|
|
568
|
+
existing.push(route);
|
|
569
|
+
routesByPath.set(route.normalizedPath, existing);
|
|
570
|
+
}
|
|
571
|
+
for (const call of calls) {
|
|
572
|
+
// Build all candidate paths (handles /api/v1 prefix stripping)
|
|
573
|
+
const candidates = candidatePaths(call.normalizedUrl);
|
|
574
|
+
let matched = false;
|
|
575
|
+
for (const candidate of candidates) {
|
|
576
|
+
const matchingRoutes = routesByPath.get(candidate);
|
|
577
|
+
if (!matchingRoutes)
|
|
578
|
+
continue;
|
|
579
|
+
for (const route of matchingRoutes) {
|
|
580
|
+
// Determine confidence
|
|
581
|
+
let confidence;
|
|
582
|
+
const methodsKnown = call.method !== 'UNKNOWN' && route.method !== 'UNKNOWN';
|
|
583
|
+
const methodsMatch = call.method === route.method;
|
|
584
|
+
if (methodsKnown && methodsMatch && candidate === call.normalizedUrl) {
|
|
585
|
+
confidence = 'exact';
|
|
586
|
+
}
|
|
587
|
+
else if (!methodsKnown || !methodsMatch) {
|
|
588
|
+
confidence = candidate !== call.normalizedUrl ? 'fuzzy' : 'path';
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
confidence = candidate !== call.normalizedUrl ? 'fuzzy' : 'exact';
|
|
592
|
+
}
|
|
593
|
+
edges.push({
|
|
594
|
+
callerFile: call.file,
|
|
595
|
+
handlerFile: route.file,
|
|
596
|
+
method: methodsKnown ? call.method : route.method,
|
|
597
|
+
path: candidate,
|
|
598
|
+
call,
|
|
599
|
+
route,
|
|
600
|
+
confidence,
|
|
601
|
+
});
|
|
602
|
+
matched = true;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// If no match found via exact/prefix logic, try fuzzy segment comparison
|
|
606
|
+
if (!matched) {
|
|
607
|
+
const callSegments = call.normalizedUrl.replace(/:param/g, '*').split('/');
|
|
608
|
+
for (const [routePath, routeList] of routesByPath) {
|
|
609
|
+
const routeSegments = routePath.replace(/:param/g, '*').split('/');
|
|
610
|
+
if (callSegments.length !== routeSegments.length)
|
|
611
|
+
continue;
|
|
612
|
+
const allMatch = callSegments.every((seg, i) => seg === routeSegments[i] || seg === '*' || routeSegments[i] === '*');
|
|
613
|
+
if (!allMatch)
|
|
614
|
+
continue;
|
|
615
|
+
for (const route of routeList) {
|
|
616
|
+
edges.push({
|
|
617
|
+
callerFile: call.file,
|
|
618
|
+
handlerFile: route.file,
|
|
619
|
+
method: call.method !== 'UNKNOWN' ? call.method : route.method,
|
|
620
|
+
path: routePath,
|
|
621
|
+
call,
|
|
622
|
+
route,
|
|
623
|
+
confidence: 'fuzzy',
|
|
624
|
+
});
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// Deduplicate: same caller file + handler file + method + path
|
|
630
|
+
const seen = new Set();
|
|
631
|
+
return edges.filter(e => {
|
|
632
|
+
const key = `${e.callerFile}|${e.handlerFile}|${e.method}|${e.path}`;
|
|
633
|
+
if (seen.has(key))
|
|
634
|
+
return false;
|
|
635
|
+
seen.add(key);
|
|
636
|
+
return true;
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
// ============================================================================
|
|
640
|
+
// BATCH HELPERS
|
|
641
|
+
// ============================================================================
|
|
642
|
+
/**
|
|
643
|
+
* Parse all files in a mixed JS+Python codebase and return HTTP edges.
|
|
644
|
+
* Intended to be called once per graph build and its result merged into
|
|
645
|
+
* the DependencyGraphResult edges.
|
|
646
|
+
*/
|
|
647
|
+
export async function extractAllHttpEdges(filePaths) {
|
|
648
|
+
const allCalls = [];
|
|
649
|
+
const allRoutes = [];
|
|
650
|
+
await Promise.all(filePaths.map(async (fp) => {
|
|
651
|
+
const ext = extname(fp).toLowerCase();
|
|
652
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'].includes(ext)) {
|
|
653
|
+
const calls = await extractHttpCalls(fp);
|
|
654
|
+
allCalls.push(...calls);
|
|
655
|
+
}
|
|
656
|
+
else if (['.py', '.pyw'].includes(ext)) {
|
|
657
|
+
const routes = await extractRouteDefinitions(fp);
|
|
658
|
+
allRoutes.push(...routes);
|
|
659
|
+
}
|
|
660
|
+
else if (ext === '.java') {
|
|
661
|
+
const routes = await extractJavaRouteDefinitions(fp);
|
|
662
|
+
allRoutes.push(...routes);
|
|
663
|
+
}
|
|
664
|
+
}));
|
|
665
|
+
const edges = buildHttpEdges(allCalls, allRoutes);
|
|
666
|
+
return { calls: allCalls, routes: allRoutes, edges };
|
|
667
|
+
}
|
|
668
|
+
// ============================================================================
|
|
669
|
+
// PRIVATE UTILITIES
|
|
670
|
+
// ============================================================================
|
|
671
|
+
/** Convert a character offset in `content` to a 1-based line number */
|
|
672
|
+
function getLine(lines, charOffset) {
|
|
673
|
+
let accumulated = 0;
|
|
674
|
+
for (let i = 0; i < lines.length; i++) {
|
|
675
|
+
accumulated += lines[i].length + 1; // +1 for newline
|
|
676
|
+
if (accumulated > charOffset)
|
|
677
|
+
return i + 1;
|
|
678
|
+
}
|
|
679
|
+
return lines.length;
|
|
680
|
+
}
|
|
681
|
+
/**
|
|
682
|
+
* Given the line of a decorator, scan forward to find the next `def` name.
|
|
683
|
+
* Handles multi-line decorators with up to 10 lines of lookahead.
|
|
684
|
+
*
|
|
685
|
+
* `decoratorLine` is 1-based (from getLine()), so we convert to a 0-based
|
|
686
|
+
* index before indexing into the `lines` array.
|
|
687
|
+
*/
|
|
688
|
+
function extractNextDefName(lines, decoratorLine) {
|
|
689
|
+
const start = decoratorLine - 1; // convert 1-based → 0-based
|
|
690
|
+
const maxLook = Math.min(lines.length, start + 10);
|
|
691
|
+
for (let i = start; i < maxLook; i++) {
|
|
692
|
+
const defMatch = lines[i]?.match(/^\s*(?:async\s+)?def\s+(\w+)/);
|
|
693
|
+
if (defMatch)
|
|
694
|
+
return defMatch[1];
|
|
695
|
+
}
|
|
696
|
+
return 'unknown';
|
|
697
|
+
}
|
|
698
|
+
// ============================================================================
|
|
699
|
+
// CONTRACT / TYPE EXTRACTION HELPERS
|
|
700
|
+
// ============================================================================
|
|
701
|
+
/**
|
|
702
|
+
* Extract contract information from a handler function body or surrounding context.
|
|
703
|
+
*
|
|
704
|
+
* Strategies:
|
|
705
|
+
* 1. TypeScript Request<P, ResBody, ReqBody, Q> generic → requestBodyType = ReqBody
|
|
706
|
+
* 2. NestJS @Body() dto: Type → requestBodyType = Type
|
|
707
|
+
* 3. Zod .parse( / .parseAsync( → contractSource = 'validator'
|
|
708
|
+
* 4. Promise<ResponseType> return annotation
|
|
709
|
+
*/
|
|
710
|
+
function extractContractFromHandler(handlerSource) {
|
|
711
|
+
let requestBodyType;
|
|
712
|
+
let responseType;
|
|
713
|
+
let contractSource = 'none';
|
|
714
|
+
// 1. TypeScript Request<P, ResBody, ReqBody, Q> generic
|
|
715
|
+
// handler(req: Request<Params, ResBody, Body, Query>)
|
|
716
|
+
const reqGenericRe = /:\s*Request\s*<[^,>]+,\s*([^,>]+),\s*([^,>]+)/;
|
|
717
|
+
const reqGenericMatch = reqGenericRe.exec(handlerSource);
|
|
718
|
+
if (reqGenericMatch) {
|
|
719
|
+
const resBodyType = reqGenericMatch[1].trim();
|
|
720
|
+
const reqBodyType = reqGenericMatch[2].trim();
|
|
721
|
+
if (reqBodyType && reqBodyType !== 'unknown' && reqBodyType !== 'any') {
|
|
722
|
+
requestBodyType = reqBodyType;
|
|
723
|
+
contractSource = 'annotation';
|
|
724
|
+
}
|
|
725
|
+
if (resBodyType && resBodyType !== 'unknown' && resBodyType !== 'any') {
|
|
726
|
+
responseType = resBodyType;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
// 2. NestJS @Body() dto: CreateUserDto
|
|
730
|
+
const bodyParamRe = /@Body\s*\(\s*\)\s+\w+\s*:\s*(\w+)/;
|
|
731
|
+
const bodyParamMatch = bodyParamRe.exec(handlerSource);
|
|
732
|
+
if (bodyParamMatch) {
|
|
733
|
+
requestBodyType = bodyParamMatch[1];
|
|
734
|
+
contractSource = 'annotation';
|
|
735
|
+
}
|
|
736
|
+
// 3. Zod validators: schema.parse( / schema.parseAsync( / z.infer<typeof
|
|
737
|
+
const zodRe = /\b\w+\.parse(?:Async)?\s*\(|z\.infer\s*<\s*typeof\s+(\w+)/;
|
|
738
|
+
const zodMatch = zodRe.exec(handlerSource);
|
|
739
|
+
if (zodMatch) {
|
|
740
|
+
contractSource = 'validator';
|
|
741
|
+
if (zodMatch[1]) {
|
|
742
|
+
requestBodyType = `z.infer<typeof ${zodMatch[1]}>`;
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
// Extract schema variable name from .parse( call
|
|
746
|
+
const parseVarRe = /(\w+)\.parse(?:Async)?\s*\(/;
|
|
747
|
+
const parseVarMatch = parseVarRe.exec(handlerSource);
|
|
748
|
+
if (parseVarMatch) {
|
|
749
|
+
requestBodyType = parseVarMatch[1];
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// 4. Promise<ResponseType> return type annotation
|
|
754
|
+
const returnTypeRe = /\):\s*Promise\s*<\s*([^>]+)>/;
|
|
755
|
+
const returnTypeMatch = returnTypeRe.exec(handlerSource);
|
|
756
|
+
if (returnTypeMatch && !responseType) {
|
|
757
|
+
const rType = returnTypeMatch[1].trim();
|
|
758
|
+
if (rType && rType !== 'void' && rType !== 'unknown' && rType !== 'any') {
|
|
759
|
+
responseType = rType;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return { requestBodyType, responseType, contractSource };
|
|
763
|
+
}
|
|
764
|
+
// ============================================================================
|
|
765
|
+
// TS/JS SERVER ROUTE EXTRACTION
|
|
766
|
+
// ============================================================================
|
|
767
|
+
// Express / Hono / Fastify / Koa / Elysia style:
|
|
768
|
+
// app.get('/path', handler)
|
|
769
|
+
// router.post('/path', ...)
|
|
770
|
+
// app.use('/prefix', router) ← prefix accumulation
|
|
771
|
+
const EXPRESS_ROUTE_RE = /(?:^|[\s;(,])(?:app|router|server|api|r)\.(get|post|put|delete|patch|head|options|all)\s*\(\s*['"`]([^'"`]+)['"`]/gm;
|
|
772
|
+
const EXPRESS_USE_RE = /(?:^|[\s;(,])(?:app|router|server|api|r)\.use\s*\(\s*['"`]([^'"`]+)['"`]/gm;
|
|
773
|
+
// NestJS decorator-based:
|
|
774
|
+
// @Controller('prefix') → class methods with @Get / @Post etc.
|
|
775
|
+
const NESTJS_CONTROLLER_RE = /@Controller\s*\(\s*['"`]([^'"`]*)['"`]\s*\)/g;
|
|
776
|
+
const NESTJS_METHOD_RE = /@(Get|Post|Put|Delete|Patch|Head|Options|All)\s*\(\s*(?:['"`]([^'"`]*)['"`])?\s*\)/g;
|
|
777
|
+
const NESTJS_HANDLER_RE = /(?:async\s+)?(\w+)\s*\(/;
|
|
778
|
+
// Next.js App Router: export (async) function GET(...) in app/**/route.ts
|
|
779
|
+
const NEXTJS_APP_ROUTER_RE = /^export\s+(?:async\s+)?function\s+(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(/gm;
|
|
780
|
+
/** Detected framework from a file's content */
|
|
781
|
+
function detectTsFramework(source, filePath) {
|
|
782
|
+
if (/@Controller\s*\(/.test(source) && /@(Get|Post|Put|Delete|Patch)\s*\(/.test(source))
|
|
783
|
+
return 'nestjs';
|
|
784
|
+
if (/app\/.*\/route\.[jt]sx?$/.test(filePath.replace(/\\/g, '/')))
|
|
785
|
+
return 'nextjs-app';
|
|
786
|
+
if (/pages\/api\//.test(filePath.replace(/\\/g, '/')))
|
|
787
|
+
return 'nextjs-pages';
|
|
788
|
+
if (/from\s+['"]hono['"]/.test(source) || /new\s+Hono\s*[(<]/.test(source))
|
|
789
|
+
return 'hono';
|
|
790
|
+
if (/from\s+['"]fastify['"]/.test(source) || /fastify\s*\(/.test(source))
|
|
791
|
+
return 'fastify';
|
|
792
|
+
if (/from\s+['"]express['"]/.test(source) || /require\s*\(\s*['"]express['"]\s*\)/.test(source))
|
|
793
|
+
return 'express';
|
|
794
|
+
if (/from\s+['"]koa['"]/.test(source))
|
|
795
|
+
return 'koa';
|
|
796
|
+
if (/from\s+['"]elysia['"]/.test(source))
|
|
797
|
+
return 'elysia';
|
|
798
|
+
if (new RegExp(EXPRESS_ROUTE_RE.source).test(source))
|
|
799
|
+
return 'express';
|
|
800
|
+
return 'unknown';
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Extract HTTP route definitions from a TypeScript/JavaScript server file.
|
|
804
|
+
* Handles Express-style, NestJS decorators, and Next.js App Router.
|
|
805
|
+
*/
|
|
806
|
+
export async function extractTsRouteDefinitions(filePath) {
|
|
807
|
+
let source;
|
|
808
|
+
try {
|
|
809
|
+
const { readFile } = await import('node:fs/promises');
|
|
810
|
+
// Use skeleton to strip comments — prevents false positives from comment
|
|
811
|
+
// examples inside parser/extractor files that contain route pattern strings.
|
|
812
|
+
// Line numbers in the result are approximate (skeleton line positions).
|
|
813
|
+
source = getSkeletonContent(await readFile(filePath, 'utf-8'), detectLanguage(filePath));
|
|
814
|
+
}
|
|
815
|
+
catch {
|
|
816
|
+
return [];
|
|
817
|
+
}
|
|
818
|
+
const framework = detectTsFramework(source, filePath);
|
|
819
|
+
const routes = [];
|
|
820
|
+
const lines = source.split('\n');
|
|
821
|
+
function lineOf(index) {
|
|
822
|
+
return source.slice(0, index).split('\n').length;
|
|
823
|
+
}
|
|
824
|
+
// ── Next.js App Router ────────────────────────────────────────────────────
|
|
825
|
+
if (framework === 'nextjs-app') {
|
|
826
|
+
// Derive path from file location: app/users/route.ts → /users
|
|
827
|
+
const rel = filePath.replace(/\\/g, '/');
|
|
828
|
+
const appIdx = rel.lastIndexOf('/app/');
|
|
829
|
+
let routePath = '/';
|
|
830
|
+
if (appIdx >= 0) {
|
|
831
|
+
routePath = rel.slice(appIdx + 4).replace(/\/route\.[jt]sx?$/, '') || '/';
|
|
832
|
+
// Remove dynamic segments brackets for display: [id] → :id
|
|
833
|
+
routePath = routePath.replace(/\[([^\]]+)\]/g, ':$1');
|
|
834
|
+
}
|
|
835
|
+
const re = new RegExp(NEXTJS_APP_ROUTER_RE.source, NEXTJS_APP_ROUTER_RE.flags);
|
|
836
|
+
let m;
|
|
837
|
+
while ((m = re.exec(source)) !== null) {
|
|
838
|
+
// Extract handler body for contract detection (scan next 500 chars)
|
|
839
|
+
const handlerBody = source.slice(m.index, m.index + 500);
|
|
840
|
+
const contract = extractContractFromHandler(handlerBody);
|
|
841
|
+
routes.push({
|
|
842
|
+
file: filePath,
|
|
843
|
+
method: m[1].toUpperCase(),
|
|
844
|
+
path: routePath,
|
|
845
|
+
normalizedPath: normalizeUrl(routePath),
|
|
846
|
+
handlerName: m[1],
|
|
847
|
+
framework: 'nextjs-app',
|
|
848
|
+
line: lineOf(m.index),
|
|
849
|
+
...contract,
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
return routes;
|
|
853
|
+
}
|
|
854
|
+
// ── NestJS ────────────────────────────────────────────────────────────────
|
|
855
|
+
if (framework === 'nestjs') {
|
|
856
|
+
// Collect controller prefixes
|
|
857
|
+
const ctrlRe = new RegExp(NESTJS_CONTROLLER_RE.source, NESTJS_CONTROLLER_RE.flags);
|
|
858
|
+
let ctrlPrefix = '';
|
|
859
|
+
const ctrlMatch = ctrlRe.exec(source);
|
|
860
|
+
if (ctrlMatch) {
|
|
861
|
+
ctrlPrefix = ctrlMatch[1] ? `/${ctrlMatch[1].replace(/^\//, '')}` : '';
|
|
862
|
+
}
|
|
863
|
+
const methodRe = new RegExp(NESTJS_METHOD_RE.source, NESTJS_METHOD_RE.flags);
|
|
864
|
+
let m;
|
|
865
|
+
while ((m = methodRe.exec(source)) !== null) {
|
|
866
|
+
const httpMethod = m[1].toUpperCase();
|
|
867
|
+
const subPath = m[2] ? `/${m[2].replace(/^\//, '')}` : '';
|
|
868
|
+
const fullPath = `${ctrlPrefix}${subPath}` || '/';
|
|
869
|
+
// Find handler function name on subsequent lines
|
|
870
|
+
const afterDecorator = source.slice(m.index + m[0].length);
|
|
871
|
+
const handlerMatch = NESTJS_HANDLER_RE.exec(afterDecorator.slice(0, 200));
|
|
872
|
+
const handlerName = handlerMatch?.[1] ?? 'unknown';
|
|
873
|
+
// Extract contract from decorator + handler context (scan next 400 chars)
|
|
874
|
+
const handlerContext = source.slice(m.index, m.index + 400);
|
|
875
|
+
const contract = extractContractFromHandler(handlerContext);
|
|
876
|
+
routes.push({
|
|
877
|
+
file: filePath,
|
|
878
|
+
method: httpMethod,
|
|
879
|
+
path: fullPath,
|
|
880
|
+
normalizedPath: normalizeUrl(fullPath),
|
|
881
|
+
handlerName,
|
|
882
|
+
framework: 'nestjs',
|
|
883
|
+
line: lineOf(m.index),
|
|
884
|
+
...contract,
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
return routes;
|
|
888
|
+
}
|
|
889
|
+
// ── Express / Hono / Fastify / Koa / Elysia ───────────────────────────────
|
|
890
|
+
// Collect prefix map from .use() calls (best-effort)
|
|
891
|
+
const prefixes = [];
|
|
892
|
+
const useRe = new RegExp(EXPRESS_USE_RE.source, EXPRESS_USE_RE.flags);
|
|
893
|
+
let um;
|
|
894
|
+
while ((um = useRe.exec(source)) !== null) {
|
|
895
|
+
prefixes.push(um[1]);
|
|
896
|
+
}
|
|
897
|
+
const routeRe = new RegExp(EXPRESS_ROUTE_RE.source, EXPRESS_ROUTE_RE.flags);
|
|
898
|
+
let m;
|
|
899
|
+
while ((m = routeRe.exec(source)) !== null) {
|
|
900
|
+
const method = m[1].toUpperCase();
|
|
901
|
+
let path = m[2];
|
|
902
|
+
// Apply a prefix if the route is relative (no leading slash)
|
|
903
|
+
if (!path.startsWith('/') && prefixes.length > 0) {
|
|
904
|
+
path = `${prefixes[0]}/${path}`;
|
|
905
|
+
}
|
|
906
|
+
// Find the handler name from the same line
|
|
907
|
+
const lineText = lines[lineOf(m.index) - 1] ?? '';
|
|
908
|
+
const handlerMatch = lineText.match(/,\s*(?:async\s+)?(?:function\s+)?(\w+)\s*[,)]/);
|
|
909
|
+
const handlerName = handlerMatch?.[1] ?? 'handler';
|
|
910
|
+
// Extract contract from route context (scan next 600 chars)
|
|
911
|
+
const routeContext = source.slice(m.index, m.index + 600);
|
|
912
|
+
const contract = extractContractFromHandler(routeContext);
|
|
913
|
+
routes.push({
|
|
914
|
+
file: filePath,
|
|
915
|
+
method,
|
|
916
|
+
path,
|
|
917
|
+
normalizedPath: normalizeUrl(path),
|
|
918
|
+
handlerName,
|
|
919
|
+
framework,
|
|
920
|
+
line: lineOf(m.index),
|
|
921
|
+
...contract,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
return routes;
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Build a complete route inventory from all source files.
|
|
928
|
+
* Combines Python routes (extractRouteDefinitions) and TS/JS routes
|
|
929
|
+
* (extractTsRouteDefinitions) into a single summary.
|
|
930
|
+
*
|
|
931
|
+
* @param filePaths - Absolute paths to all source files in the project
|
|
932
|
+
* @param rootDir - Project root for computing relative paths
|
|
933
|
+
*/
|
|
934
|
+
export async function buildRouteInventory(filePaths, rootDir) {
|
|
935
|
+
const { relative } = await import('node:path');
|
|
936
|
+
const allRoutes = [];
|
|
937
|
+
await Promise.all(filePaths.map(async (fp) => {
|
|
938
|
+
const ext = extname(fp).toLowerCase();
|
|
939
|
+
if (['.py', '.pyw'].includes(ext)) {
|
|
940
|
+
allRoutes.push(...await extractRouteDefinitions(fp));
|
|
941
|
+
}
|
|
942
|
+
else if (['.ts', '.tsx', '.js', '.jsx', '.mjs'].includes(ext)) {
|
|
943
|
+
allRoutes.push(...await extractTsRouteDefinitions(fp));
|
|
944
|
+
}
|
|
945
|
+
else if (ext === '.java') {
|
|
946
|
+
allRoutes.push(...await extractJavaRouteDefinitions(fp));
|
|
947
|
+
}
|
|
948
|
+
}));
|
|
949
|
+
const byMethod = {};
|
|
950
|
+
const byFramework = {};
|
|
951
|
+
for (const r of allRoutes) {
|
|
952
|
+
byMethod[r.method] = (byMethod[r.method] ?? 0) + 1;
|
|
953
|
+
byFramework[r.framework] = (byFramework[r.framework] ?? 0) + 1;
|
|
954
|
+
}
|
|
955
|
+
return {
|
|
956
|
+
total: allRoutes.length,
|
|
957
|
+
byMethod,
|
|
958
|
+
byFramework,
|
|
959
|
+
routes: allRoutes.map(r => ({
|
|
960
|
+
method: r.method,
|
|
961
|
+
path: r.path,
|
|
962
|
+
framework: r.framework,
|
|
963
|
+
file: relative(rootDir, r.file),
|
|
964
|
+
handler: r.handlerName,
|
|
965
|
+
requestBodyType: r.requestBodyType,
|
|
966
|
+
responseType: r.responseType,
|
|
967
|
+
contractSource: r.contractSource,
|
|
968
|
+
})),
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
//# sourceMappingURL=http-route-parser.js.map
|