gitnexus 1.5.3 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/dist/_shared/graph/types.d.ts +1 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/index.d.ts +1 -0
- package/dist/_shared/index.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +2 -0
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/languages.d.ts +1 -0
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +1 -0
- package/dist/_shared/languages.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +3 -1
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/mro-strategy.d.ts +19 -0
- package/dist/_shared/mro-strategy.d.ts.map +1 -0
- package/dist/_shared/mro-strategy.js +2 -0
- package/dist/_shared/mro-strategy.js.map +1 -0
- package/dist/cli/ai-context.d.ts +1 -0
- package/dist/cli/ai-context.js +28 -4
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +30 -4
- package/dist/cli/group.d.ts +2 -0
- package/dist/cli/group.js +233 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli/serve.js +4 -1
- package/dist/cli/setup.js +34 -3
- package/dist/config/ignore-service.js +8 -3
- package/dist/core/augmentation/engine.js +1 -1
- package/dist/core/git-staleness.d.ts +13 -0
- package/dist/core/git-staleness.js +29 -0
- package/dist/core/group/bridge-db.d.ts +82 -0
- package/dist/core/group/bridge-db.js +460 -0
- package/dist/core/group/bridge-schema.d.ts +27 -0
- package/dist/core/group/bridge-schema.js +55 -0
- package/dist/core/group/config-parser.d.ts +3 -0
- package/dist/core/group/config-parser.js +83 -0
- package/dist/core/group/contract-extractor.d.ts +7 -0
- package/dist/core/group/contract-extractor.js +1 -0
- package/dist/core/group/extractors/fs-utils.d.ts +10 -0
- package/dist/core/group/extractors/fs-utils.js +24 -0
- package/dist/core/group/extractors/grpc-extractor.d.ts +25 -0
- package/dist/core/group/extractors/grpc-extractor.js +386 -0
- package/dist/core/group/extractors/grpc-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/go.js +97 -0
- package/dist/core/group/extractors/grpc-patterns/index.d.ts +19 -0
- package/dist/core/group/extractors/grpc-patterns/index.js +46 -0
- package/dist/core/group/extractors/grpc-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/java.js +173 -0
- package/dist/core/group/extractors/grpc-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/grpc-patterns/node.js +290 -0
- package/dist/core/group/extractors/grpc-patterns/proto.d.ts +9 -0
- package/dist/core/group/extractors/grpc-patterns/proto.js +134 -0
- package/dist/core/group/extractors/grpc-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/grpc-patterns/python.js +67 -0
- package/dist/core/group/extractors/grpc-patterns/types.d.ts +50 -0
- package/dist/core/group/extractors/grpc-patterns/types.js +1 -0
- package/dist/core/group/extractors/http-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/go.js +215 -0
- package/dist/core/group/extractors/http-patterns/index.d.ts +17 -0
- package/dist/core/group/extractors/http-patterns/index.js +44 -0
- package/dist/core/group/extractors/http-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/java.js +253 -0
- package/dist/core/group/extractors/http-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/http-patterns/node.js +354 -0
- package/dist/core/group/extractors/http-patterns/php.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/php.js +70 -0
- package/dist/core/group/extractors/http-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/http-patterns/python.js +133 -0
- package/dist/core/group/extractors/http-patterns/types.d.ts +61 -0
- package/dist/core/group/extractors/http-patterns/types.js +1 -0
- package/dist/core/group/extractors/http-route-extractor.d.ts +21 -0
- package/dist/core/group/extractors/http-route-extractor.js +391 -0
- package/dist/core/group/extractors/manifest-extractor.d.ts +54 -0
- package/dist/core/group/extractors/manifest-extractor.js +235 -0
- package/dist/core/group/extractors/topic-extractor.d.ts +8 -0
- package/dist/core/group/extractors/topic-extractor.js +97 -0
- package/dist/core/group/extractors/topic-patterns/go.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/go.js +120 -0
- package/dist/core/group/extractors/topic-patterns/index.d.ts +14 -0
- package/dist/core/group/extractors/topic-patterns/index.js +38 -0
- package/dist/core/group/extractors/topic-patterns/java.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/java.js +80 -0
- package/dist/core/group/extractors/topic-patterns/node.d.ts +4 -0
- package/dist/core/group/extractors/topic-patterns/node.js +155 -0
- package/dist/core/group/extractors/topic-patterns/python.d.ts +2 -0
- package/dist/core/group/extractors/topic-patterns/python.js +116 -0
- package/dist/core/group/extractors/topic-patterns/types.d.ts +25 -0
- package/dist/core/group/extractors/topic-patterns/types.js +10 -0
- package/dist/core/group/extractors/tree-sitter-scanner.d.ts +113 -0
- package/dist/core/group/extractors/tree-sitter-scanner.js +94 -0
- package/dist/core/group/matching.d.ts +13 -0
- package/dist/core/group/matching.js +198 -0
- package/dist/core/group/normalization.d.ts +3 -0
- package/dist/core/group/normalization.js +115 -0
- package/dist/core/group/service-boundary-detector.d.ts +8 -0
- package/dist/core/group/service-boundary-detector.js +155 -0
- package/dist/core/group/service.d.ts +46 -0
- package/dist/core/group/service.js +160 -0
- package/dist/core/group/storage.d.ts +9 -0
- package/dist/core/group/storage.js +91 -0
- package/dist/core/group/sync.d.ts +21 -0
- package/dist/core/group/sync.js +148 -0
- package/dist/core/group/types.d.ts +130 -0
- package/dist/core/group/types.js +1 -0
- package/dist/core/ingestion/binding-accumulator.d.ts +212 -0
- package/dist/core/ingestion/binding-accumulator.js +336 -0
- package/dist/core/ingestion/call-processor.d.ts +155 -24
- package/dist/core/ingestion/call-processor.js +1129 -247
- package/dist/core/ingestion/class-extractors/generic.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/generic.js +135 -0
- package/dist/core/ingestion/class-types.d.ts +34 -0
- package/dist/core/ingestion/class-types.js +1 -0
- package/dist/core/ingestion/cobol-processor.d.ts +1 -1
- package/dist/core/ingestion/entry-point-scoring.d.ts +1 -0
- package/dist/core/ingestion/entry-point-scoring.js +1 -0
- package/dist/core/ingestion/field-types.d.ts +2 -2
- package/dist/core/ingestion/filesystem-walker.js +8 -0
- package/dist/core/ingestion/framework-detection.d.ts +1 -0
- package/dist/core/ingestion/framework-detection.js +1 -0
- package/dist/core/ingestion/heritage-processor.d.ts +8 -15
- package/dist/core/ingestion/heritage-processor.js +15 -28
- package/dist/core/ingestion/import-processor.d.ts +1 -11
- package/dist/core/ingestion/import-processor.js +1 -13
- package/dist/core/ingestion/import-resolvers/utils.js +1 -0
- package/dist/core/ingestion/import-resolvers/vue.d.ts +8 -0
- package/dist/core/ingestion/import-resolvers/vue.js +9 -0
- package/dist/core/ingestion/language-config.js +1 -1
- package/dist/core/ingestion/language-provider.d.ts +14 -3
- package/dist/core/ingestion/languages/c-cpp.js +168 -1
- package/dist/core/ingestion/languages/csharp.js +20 -0
- package/dist/core/ingestion/languages/dart.js +26 -4
- package/dist/core/ingestion/languages/go.js +22 -0
- package/dist/core/ingestion/languages/index.d.ts +1 -0
- package/dist/core/ingestion/languages/index.js +2 -0
- package/dist/core/ingestion/languages/java.js +17 -0
- package/dist/core/ingestion/languages/kotlin.js +24 -1
- package/dist/core/ingestion/languages/php.js +23 -11
- package/dist/core/ingestion/languages/python.js +9 -0
- package/dist/core/ingestion/languages/ruby.js +43 -0
- package/dist/core/ingestion/languages/rust.js +38 -0
- package/dist/core/ingestion/languages/swift.js +31 -0
- package/dist/core/ingestion/languages/typescript.d.ts +1 -0
- package/dist/core/ingestion/languages/typescript.js +52 -3
- package/dist/core/ingestion/languages/vue.d.ts +13 -0
- package/dist/core/ingestion/languages/vue.js +81 -0
- package/dist/core/ingestion/markdown-processor.d.ts +1 -1
- package/dist/core/ingestion/method-extractors/configs/c-cpp.d.ts +3 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
- package/dist/core/ingestion/method-extractors/configs/csharp.js +5 -1
- package/dist/core/ingestion/method-extractors/configs/dart.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/dart.js +376 -0
- package/dist/core/ingestion/method-extractors/configs/go.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/go.js +176 -0
- package/dist/core/ingestion/method-extractors/configs/jvm.js +14 -4
- package/dist/core/ingestion/method-extractors/configs/php.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/php.js +304 -0
- package/dist/core/ingestion/method-extractors/configs/python.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/python.js +309 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/ruby.js +286 -0
- package/dist/core/ingestion/method-extractors/configs/rust.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/rust.js +195 -0
- package/dist/core/ingestion/method-extractors/configs/swift.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/swift.js +277 -0
- package/dist/core/ingestion/method-extractors/configs/typescript-javascript.js +85 -8
- package/dist/core/ingestion/method-extractors/generic.d.ts +6 -0
- package/dist/core/ingestion/method-extractors/generic.js +84 -17
- package/dist/core/ingestion/method-types.d.ts +29 -0
- package/dist/core/ingestion/model/field-registry.d.ts +18 -0
- package/dist/core/ingestion/model/field-registry.js +22 -0
- package/dist/core/ingestion/model/heritage-map.d.ts +70 -0
- package/dist/core/ingestion/model/heritage-map.js +159 -0
- package/dist/core/ingestion/model/index.d.ts +20 -0
- package/dist/core/ingestion/model/index.js +41 -0
- package/dist/core/ingestion/model/method-registry.d.ts +62 -0
- package/dist/core/ingestion/model/method-registry.js +130 -0
- package/dist/core/ingestion/model/registration-table.d.ts +139 -0
- package/dist/core/ingestion/model/registration-table.js +224 -0
- package/dist/core/ingestion/model/resolution-context.d.ts +93 -0
- package/dist/core/ingestion/model/resolution-context.js +337 -0
- package/dist/core/ingestion/model/resolve.d.ts +56 -0
- package/dist/core/ingestion/model/resolve.js +297 -0
- package/dist/core/ingestion/model/semantic-model.d.ts +86 -0
- package/dist/core/ingestion/model/semantic-model.js +120 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +222 -0
- package/dist/core/ingestion/model/symbol-table.js +206 -0
- package/dist/core/ingestion/model/type-registry.d.ts +39 -0
- package/dist/core/ingestion/model/type-registry.js +62 -0
- package/dist/core/ingestion/mro-processor.d.ts +5 -4
- package/dist/core/ingestion/mro-processor.js +311 -107
- package/dist/core/ingestion/parsing-processor.d.ts +5 -4
- package/dist/core/ingestion/parsing-processor.js +224 -87
- package/dist/core/ingestion/pipeline-phases/cobol.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/cobol.js +45 -0
- package/dist/core/ingestion/pipeline-phases/communities.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/communities.js +62 -0
- package/dist/core/ingestion/pipeline-phases/cross-file-impl.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/cross-file-impl.js +156 -0
- package/dist/core/ingestion/pipeline-phases/cross-file.d.ts +37 -0
- package/dist/core/ingestion/pipeline-phases/cross-file.js +63 -0
- package/dist/core/ingestion/pipeline-phases/index.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/index.js +22 -0
- package/dist/core/ingestion/pipeline-phases/markdown.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/markdown.js +33 -0
- package/dist/core/ingestion/pipeline-phases/mro.d.ts +18 -0
- package/dist/core/ingestion/pipeline-phases/mro.js +36 -0
- package/dist/core/ingestion/pipeline-phases/orm-extraction.d.ts +22 -0
- package/dist/core/ingestion/pipeline-phases/orm-extraction.js +92 -0
- package/dist/core/ingestion/pipeline-phases/orm.d.ts +15 -0
- package/dist/core/ingestion/pipeline-phases/orm.js +74 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +47 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +437 -0
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +49 -0
- package/dist/core/ingestion/pipeline-phases/parse.js +33 -0
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +16 -0
- package/dist/core/ingestion/pipeline-phases/processes.js +143 -0
- package/dist/core/ingestion/pipeline-phases/routes.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +243 -0
- package/dist/core/ingestion/pipeline-phases/runner.d.ts +22 -0
- package/dist/core/ingestion/pipeline-phases/runner.js +203 -0
- package/dist/core/ingestion/pipeline-phases/scan.d.ts +21 -0
- package/dist/core/ingestion/pipeline-phases/scan.js +46 -0
- package/dist/core/ingestion/pipeline-phases/structure.d.ts +27 -0
- package/dist/core/ingestion/pipeline-phases/structure.js +35 -0
- package/dist/core/ingestion/pipeline-phases/tools.d.ts +20 -0
- package/dist/core/ingestion/pipeline-phases/tools.js +79 -0
- package/dist/core/ingestion/pipeline-phases/types.d.ts +79 -0
- package/dist/core/ingestion/pipeline-phases/types.js +37 -0
- package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.d.ts +35 -0
- package/dist/core/ingestion/pipeline-phases/wildcard-synthesis.js +174 -0
- package/dist/core/ingestion/pipeline.d.ts +18 -10
- package/dist/core/ingestion/pipeline.js +66 -1410
- package/dist/core/ingestion/process-processor.js +1 -1
- package/dist/core/ingestion/tree-sitter-queries.d.ts +5 -5
- package/dist/core/ingestion/tree-sitter-queries.js +90 -0
- package/dist/core/ingestion/type-env.d.ts +15 -2
- package/dist/core/ingestion/type-env.js +163 -102
- package/dist/core/ingestion/type-extractors/csharp.js +17 -0
- package/dist/core/ingestion/type-extractors/jvm.js +11 -0
- package/dist/core/ingestion/type-extractors/php.js +0 -55
- package/dist/core/ingestion/type-extractors/ruby.js +0 -32
- package/dist/core/ingestion/type-extractors/swift.js +13 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +8 -8
- package/dist/core/ingestion/type-extractors/typescript.js +66 -69
- package/dist/core/ingestion/utils/ast-helpers.d.ts +32 -44
- package/dist/core/ingestion/utils/ast-helpers.js +157 -573
- package/dist/core/ingestion/utils/env.d.ts +10 -0
- package/dist/core/ingestion/utils/env.js +10 -0
- package/dist/core/ingestion/utils/graph-sort.d.ts +58 -0
- package/dist/core/ingestion/utils/graph-sort.js +100 -0
- package/dist/core/ingestion/utils/method-props.d.ts +32 -0
- package/dist/core/ingestion/utils/method-props.js +147 -0
- package/dist/core/ingestion/vue-sfc-extractor.d.ts +44 -0
- package/dist/core/ingestion/vue-sfc-extractor.js +94 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +31 -19
- package/dist/core/ingestion/workers/parse-worker.js +469 -200
- package/dist/core/lbug/lbug-adapter.d.ts +6 -0
- package/dist/core/lbug/lbug-adapter.js +134 -27
- package/dist/core/lbug/pool-adapter.d.ts +76 -0
- package/dist/core/lbug/pool-adapter.js +522 -0
- package/dist/core/run-analyze.d.ts +2 -0
- package/dist/core/run-analyze.js +1 -1
- package/dist/core/search/bm25-index.js +1 -1
- package/dist/core/tree-sitter/parser-loader.js +1 -0
- package/dist/core/wiki/graph-queries.js +1 -1
- package/dist/mcp/core/embedder.js +6 -5
- package/dist/mcp/core/lbug-adapter.d.ts +3 -63
- package/dist/mcp/core/lbug-adapter.js +3 -484
- package/dist/mcp/local/local-backend.d.ts +31 -2
- package/dist/mcp/local/local-backend.js +255 -46
- package/dist/mcp/resources.js +5 -4
- package/dist/mcp/staleness.d.ts +3 -13
- package/dist/mcp/staleness.js +2 -31
- package/dist/mcp/tools.js +80 -4
- package/dist/server/analyze-job.d.ts +2 -0
- package/dist/server/analyze-job.js +4 -0
- package/dist/server/api.d.ts +20 -1
- package/dist/server/api.js +306 -71
- package/dist/server/git-clone.d.ts +2 -1
- package/dist/server/git-clone.js +98 -5
- package/dist/storage/git.d.ts +13 -0
- package/dist/storage/git.js +25 -0
- package/dist/storage/repo-manager.js +1 -1
- package/package.json +9 -3
- package/scripts/patch-tree-sitter-swift.cjs +78 -0
- package/vendor/tree-sitter-proto/binding.gyp +30 -0
- package/vendor/tree-sitter-proto/bindings/node/binding.cc +20 -0
- package/vendor/tree-sitter-proto/bindings/node/index.d.ts +28 -0
- package/vendor/tree-sitter-proto/bindings/node/index.js +7 -0
- package/vendor/tree-sitter-proto/package.json +18 -0
- package/vendor/tree-sitter-proto/src/node-types.json +1145 -0
- package/vendor/tree-sitter-proto/src/parser.c +10149 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/alloc.h +54 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/array.h +291 -0
- package/vendor/tree-sitter-proto/src/tree_sitter/parser.h +266 -0
- package/dist/core/ingestion/named-binding-processor.d.ts +0 -18
- package/dist/core/ingestion/named-binding-processor.js +0 -42
- package/dist/core/ingestion/resolution-context.d.ts +0 -58
- package/dist/core/ingestion/resolution-context.js +0 -135
- package/dist/core/ingestion/symbol-table.d.ts +0 -79
- package/dist/core/ingestion/symbol-table.js +0 -115
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import Python from 'tree-sitter-python';
|
|
2
|
+
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
3
|
+
/**
|
|
4
|
+
* Python HTTP plugin. Handles:
|
|
5
|
+
* - FastAPI `@app.get("/path")` provider decorators
|
|
6
|
+
* - `requests.get/post/...("url")` consumer calls
|
|
7
|
+
* - Generic `requests.request("METHOD", "url")` consumer calls
|
|
8
|
+
*/
|
|
9
|
+
const FASTAPI_VERBS = {
|
|
10
|
+
get: 'GET',
|
|
11
|
+
post: 'POST',
|
|
12
|
+
put: 'PUT',
|
|
13
|
+
delete: 'DELETE',
|
|
14
|
+
patch: 'PATCH',
|
|
15
|
+
};
|
|
16
|
+
// ─── Provider: FastAPI @app.get/... ──────────────────────────────────
|
|
17
|
+
const FASTAPI_PATTERNS = compilePatterns({
|
|
18
|
+
name: 'python-fastapi',
|
|
19
|
+
language: Python,
|
|
20
|
+
patterns: [
|
|
21
|
+
{
|
|
22
|
+
meta: {},
|
|
23
|
+
query: `
|
|
24
|
+
(decorator
|
|
25
|
+
(call
|
|
26
|
+
function: (attribute
|
|
27
|
+
object: (identifier) @obj (#eq? @obj "app")
|
|
28
|
+
attribute: (identifier) @method (#match? @method "^(get|post|put|delete|patch)$"))
|
|
29
|
+
arguments: (argument_list . (string) @path)))
|
|
30
|
+
`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
// ─── Consumer: requests.get/post/... ──────────────────────────────────
|
|
35
|
+
const REQUESTS_VERB_PATTERNS = compilePatterns({
|
|
36
|
+
name: 'python-requests-verb',
|
|
37
|
+
language: Python,
|
|
38
|
+
patterns: [
|
|
39
|
+
{
|
|
40
|
+
meta: {},
|
|
41
|
+
query: `
|
|
42
|
+
(call
|
|
43
|
+
function: (attribute
|
|
44
|
+
object: (identifier) @obj (#eq? @obj "requests")
|
|
45
|
+
attribute: (identifier) @method (#match? @method "^(get|post|put|delete|patch)$"))
|
|
46
|
+
arguments: (argument_list . (string) @path))
|
|
47
|
+
`,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
});
|
|
51
|
+
// ─── Consumer: requests.request("METHOD", "url") ─────────────────────
|
|
52
|
+
const REQUESTS_GENERIC_PATTERNS = compilePatterns({
|
|
53
|
+
name: 'python-requests-generic',
|
|
54
|
+
language: Python,
|
|
55
|
+
patterns: [
|
|
56
|
+
{
|
|
57
|
+
meta: {},
|
|
58
|
+
query: `
|
|
59
|
+
(call
|
|
60
|
+
function: (attribute
|
|
61
|
+
object: (identifier) @obj (#eq? @obj "requests")
|
|
62
|
+
attribute: (identifier) @method (#eq? @method "request"))
|
|
63
|
+
arguments: (argument_list . (string) @http_method (string) @path))
|
|
64
|
+
`,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
export const PYTHON_HTTP_PLUGIN = {
|
|
69
|
+
name: 'python-http',
|
|
70
|
+
language: Python,
|
|
71
|
+
scan(tree) {
|
|
72
|
+
const out = [];
|
|
73
|
+
// Providers: FastAPI
|
|
74
|
+
for (const match of runCompiledPatterns(FASTAPI_PATTERNS, tree)) {
|
|
75
|
+
const methodNode = match.captures.method;
|
|
76
|
+
const pathNode = match.captures.path;
|
|
77
|
+
if (!methodNode || !pathNode)
|
|
78
|
+
continue;
|
|
79
|
+
const httpMethod = FASTAPI_VERBS[methodNode.text];
|
|
80
|
+
if (!httpMethod)
|
|
81
|
+
continue;
|
|
82
|
+
const path = unquoteLiteral(pathNode.text);
|
|
83
|
+
if (path === null)
|
|
84
|
+
continue;
|
|
85
|
+
out.push({
|
|
86
|
+
role: 'provider',
|
|
87
|
+
framework: 'fastapi',
|
|
88
|
+
method: httpMethod,
|
|
89
|
+
path,
|
|
90
|
+
name: null,
|
|
91
|
+
confidence: 0.8,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// Consumers: requests.<verb>
|
|
95
|
+
for (const match of runCompiledPatterns(REQUESTS_VERB_PATTERNS, tree)) {
|
|
96
|
+
const methodNode = match.captures.method;
|
|
97
|
+
const pathNode = match.captures.path;
|
|
98
|
+
if (!methodNode || !pathNode)
|
|
99
|
+
continue;
|
|
100
|
+
const path = unquoteLiteral(pathNode.text);
|
|
101
|
+
if (path === null)
|
|
102
|
+
continue;
|
|
103
|
+
out.push({
|
|
104
|
+
role: 'consumer',
|
|
105
|
+
framework: 'python-requests',
|
|
106
|
+
method: methodNode.text.toUpperCase(),
|
|
107
|
+
path,
|
|
108
|
+
name: null,
|
|
109
|
+
confidence: 0.7,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Consumers: requests.request("METHOD", "url")
|
|
113
|
+
for (const match of runCompiledPatterns(REQUESTS_GENERIC_PATTERNS, tree)) {
|
|
114
|
+
const methodNode = match.captures.http_method;
|
|
115
|
+
const pathNode = match.captures.path;
|
|
116
|
+
if (!methodNode || !pathNode)
|
|
117
|
+
continue;
|
|
118
|
+
const methodRaw = unquoteLiteral(methodNode.text);
|
|
119
|
+
const path = unquoteLiteral(pathNode.text);
|
|
120
|
+
if (methodRaw === null || path === null)
|
|
121
|
+
continue;
|
|
122
|
+
out.push({
|
|
123
|
+
role: 'consumer',
|
|
124
|
+
framework: 'python-requests',
|
|
125
|
+
method: methodRaw.toUpperCase(),
|
|
126
|
+
path,
|
|
127
|
+
name: null,
|
|
128
|
+
confidence: 0.7,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
},
|
|
133
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type Parser from 'tree-sitter';
|
|
2
|
+
/**
|
|
3
|
+
* Shared types for the http-route-extractor language plugins.
|
|
4
|
+
*
|
|
5
|
+
* Each plugin lives in its own file (java.ts, node.ts, ...) and owns
|
|
6
|
+
* the tree-sitter grammar import + queries. The top-level
|
|
7
|
+
* `http-route-extractor.ts` orchestrator only knows about this type
|
|
8
|
+
* module and the plugin registry (`./index.ts`). It MUST NOT import
|
|
9
|
+
* any grammar or query text directly — language-specific knowledge
|
|
10
|
+
* belongs in the plugins.
|
|
11
|
+
*/
|
|
12
|
+
export type HttpRole = 'provider' | 'consumer';
|
|
13
|
+
/**
|
|
14
|
+
* One raw HTTP detection produced by a plugin's `scan()` function. The
|
|
15
|
+
* orchestrator converts this into a full `ExtractedContract` by running
|
|
16
|
+
* path normalization and building the contract id.
|
|
17
|
+
*
|
|
18
|
+
* `path` is the raw literal string as it appeared in source (with
|
|
19
|
+
* `${...}` template placeholders still in place); the orchestrator
|
|
20
|
+
* runs the appropriate normalizer for provider vs. consumer paths.
|
|
21
|
+
*/
|
|
22
|
+
export interface HttpDetection {
|
|
23
|
+
role: HttpRole;
|
|
24
|
+
/** Short framework label, e.g. `'spring'`, `'nest'`, `'express'`. */
|
|
25
|
+
framework: string;
|
|
26
|
+
/** HTTP method in upper case (`'GET'`, `'POST'`, ...). */
|
|
27
|
+
method: string;
|
|
28
|
+
/** Raw path literal as seen in source (template placeholders intact). */
|
|
29
|
+
path: string;
|
|
30
|
+
/**
|
|
31
|
+
* Symbol name of the handler (for providers) or calling function
|
|
32
|
+
* (for consumers) when the plugin can determine it structurally.
|
|
33
|
+
* Null when no good candidate is available.
|
|
34
|
+
*/
|
|
35
|
+
name: string | null;
|
|
36
|
+
/** Confidence in (0, 1]. Source-scan plugins typically use 0.7–0.8. */
|
|
37
|
+
confidence: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* One language-scoped HTTP plugin. The plugin owns the tree-sitter
|
|
41
|
+
* grammar and the `scan` function that translates a parsed tree into
|
|
42
|
+
* zero or more `HttpDetection`s. Plugins are free to run multiple
|
|
43
|
+
* compiled pattern bundles internally (see the shared scanner's
|
|
44
|
+
* `runCompiledPatterns` helper).
|
|
45
|
+
*
|
|
46
|
+
* `language` is typed as `unknown` for the same reason as
|
|
47
|
+
* `LanguagePatterns.language` in `tree-sitter-scanner.ts` — the
|
|
48
|
+
* grammar modules export different shapes.
|
|
49
|
+
*/
|
|
50
|
+
export interface HttpLanguagePlugin {
|
|
51
|
+
/** Human-readable plugin name for diagnostics. */
|
|
52
|
+
name: string;
|
|
53
|
+
/** tree-sitter grammar object (passed to the shared parser). */
|
|
54
|
+
language: unknown;
|
|
55
|
+
/**
|
|
56
|
+
* Scan a parsed tree and return zero or more HTTP detections. Plugins
|
|
57
|
+
* must not throw — they should swallow per-match errors so a single
|
|
58
|
+
* malformed construct does not abort the whole file.
|
|
59
|
+
*/
|
|
60
|
+
scan(tree: Parser.Tree): HttpDetection[];
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ContractExtractor, CypherExecutor } from '../contract-extractor.js';
|
|
2
|
+
import type { ExtractedContract, RepoHandle } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Canonicalize a provider-side HTTP path for contract-id generation:
|
|
5
|
+
* - strip query string
|
|
6
|
+
* - lower-case
|
|
7
|
+
* - drop trailing slash
|
|
8
|
+
* - collapse `:id`, `{id}`, `[id]` path params into a single `{param}`
|
|
9
|
+
*/
|
|
10
|
+
export declare function normalizeHttpPath(p: string): string;
|
|
11
|
+
export declare class HttpRouteExtractor implements ContractExtractor {
|
|
12
|
+
type: "http";
|
|
13
|
+
canExtract(_repo: RepoHandle): Promise<boolean>;
|
|
14
|
+
extract(dbExecutor: CypherExecutor | null, repoPath: string, _repo: RepoHandle): Promise<ExtractedContract[]>;
|
|
15
|
+
private scanFiles;
|
|
16
|
+
private extractProvidersGraph;
|
|
17
|
+
private extractProvidersSourceScan;
|
|
18
|
+
private extractConsumersGraph;
|
|
19
|
+
private extractConsumersSourceScan;
|
|
20
|
+
private dedupeContracts;
|
|
21
|
+
}
|
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { glob } from 'glob';
|
|
3
|
+
import Parser from 'tree-sitter';
|
|
4
|
+
import { readSafe } from './fs-utils.js';
|
|
5
|
+
import { getPluginForFile, HTTP_SCAN_GLOB } from './http-patterns/index.js';
|
|
6
|
+
/**
|
|
7
|
+
* Language-agnostic orchestrator for HTTP route (provider + consumer)
|
|
8
|
+
* contract extraction. Two strategies, in order of preference per role:
|
|
9
|
+
*
|
|
10
|
+
* 1. **Graph-assisted (Strategy A)** — if a per-repo LadybugDB executor
|
|
11
|
+
* is available, read `HANDLES_ROUTE` / `FETCHES` Cypher edges that
|
|
12
|
+
* the ingestion pipeline already produced via tree-sitter. This is
|
|
13
|
+
* the preferred path because the graph has richer symbol metadata
|
|
14
|
+
* (real uids, class/method structure, etc.).
|
|
15
|
+
*
|
|
16
|
+
* 2. **Source-scan fallback (Strategy B)** — parse files directly with
|
|
17
|
+
* the per-language plugin registry in `./http-patterns/`. Used when
|
|
18
|
+
* the graph has no routes/fetches for this repo (e.g. a repo that
|
|
19
|
+
* hasn't been indexed yet, or whose indexer doesn't know the
|
|
20
|
+
* framework). Each plugin owns its tree-sitter grammar and query
|
|
21
|
+
* sources — this orchestrator imports NO grammars or query strings.
|
|
22
|
+
*
|
|
23
|
+
* Adding a new language for Strategy B is a one-file edit in
|
|
24
|
+
* `http-patterns/index.ts`: register a new `HttpLanguagePlugin` and
|
|
25
|
+
* widen `HTTP_SCAN_GLOB` if needed.
|
|
26
|
+
*/
|
|
27
|
+
// ─── Graph-assisted queries ──────────────────────────────────────────
|
|
28
|
+
const HANDLES_ROUTE_QUERY = `
|
|
29
|
+
MATCH (handlerFile:File)-[r:CodeRelation {type: 'HANDLES_ROUTE'}]->(route:Route)
|
|
30
|
+
RETURN handlerFile.id AS fileId, handlerFile.filePath AS filePath,
|
|
31
|
+
route.name AS routePath, route.id AS routeId,
|
|
32
|
+
route.responseKeys AS responseKeys,
|
|
33
|
+
r.reason AS routeSource`;
|
|
34
|
+
const FETCHES_QUERY = `
|
|
35
|
+
MATCH (callerFile:File)-[r:CodeRelation {type: 'FETCHES'}]->(route:Route)
|
|
36
|
+
RETURN callerFile.id AS fileId, callerFile.filePath AS filePath,
|
|
37
|
+
route.name AS routePath, route.id AS routeId,
|
|
38
|
+
r.reason AS fetchReason`;
|
|
39
|
+
const CONTAINS_QUERY = `
|
|
40
|
+
MATCH (file:File {id: $fileId})<-[:CodeRelation {type: 'CONTAINS'}]-(sym)
|
|
41
|
+
WHERE sym.startLine IS NOT NULL
|
|
42
|
+
RETURN sym.id AS uid, sym.name AS name, sym.filePath AS filePath, labels(sym) AS labels
|
|
43
|
+
ORDER BY sym.startLine`;
|
|
44
|
+
// ─── Path normalization (shared between provider / consumer paths) ──
|
|
45
|
+
/**
|
|
46
|
+
* Canonicalize a provider-side HTTP path for contract-id generation:
|
|
47
|
+
* - strip query string
|
|
48
|
+
* - lower-case
|
|
49
|
+
* - drop trailing slash
|
|
50
|
+
* - collapse `:id`, `{id}`, `[id]` path params into a single `{param}`
|
|
51
|
+
*/
|
|
52
|
+
export function normalizeHttpPath(p) {
|
|
53
|
+
let s = p.trim().split('?')[0].toLowerCase().replace(/\/+$/, '');
|
|
54
|
+
s = s.replace(/:\w+/g, '{param}');
|
|
55
|
+
s = s.replace(/\{[^}]+\}/g, '{param}');
|
|
56
|
+
s = s.replace(/\[[^\]]+\]/g, '{param}');
|
|
57
|
+
// Preserve root: after stripping trailing slashes, the root "/"
|
|
58
|
+
// collapses to "" which would produce malformed contract ids like
|
|
59
|
+
// `http::GET::`. Restore a single slash for the root case.
|
|
60
|
+
return s === '' ? '/' : s;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Consumer-side normalization is more aggressive:
|
|
64
|
+
* - template literals (`${x}`) → `{param}`
|
|
65
|
+
* - strip protocol + host if the URL is absolute
|
|
66
|
+
* - numeric segments → `{param}` (so `/api/orders/42` → `/api/orders/{param}`)
|
|
67
|
+
*/
|
|
68
|
+
function normalizeConsumerPath(url) {
|
|
69
|
+
const templated = url.replace(/\$\{[^}]+\}/g, '{param}').trim();
|
|
70
|
+
let pathOnly = templated;
|
|
71
|
+
if (/^https?:\/\//i.test(templated)) {
|
|
72
|
+
try {
|
|
73
|
+
pathOnly = new URL(templated).pathname;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
pathOnly = templated.replace(/^https?:\/\/[^/]+/i, '');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const normalized = normalizeHttpPath(pathOnly || '/');
|
|
80
|
+
const segments = normalized
|
|
81
|
+
.split('/')
|
|
82
|
+
.filter(Boolean)
|
|
83
|
+
.map((segment) => (/^\d+$/.test(segment) ? '{param}' : segment));
|
|
84
|
+
return `/${segments.join('/')}`.replace(/\/+$/, '') || '/';
|
|
85
|
+
}
|
|
86
|
+
function contractIdFor(method, pathNorm) {
|
|
87
|
+
return `http::${method.toUpperCase()}::${pathNorm}`;
|
|
88
|
+
}
|
|
89
|
+
// ─── Graph row helpers ───────────────────────────────────────────────
|
|
90
|
+
function methodFromRouteReason(reason) {
|
|
91
|
+
const r = reason || '';
|
|
92
|
+
if (/GetMapping|decorator-Get/i.test(r))
|
|
93
|
+
return 'GET';
|
|
94
|
+
if (/PostMapping|decorator-Post/i.test(r))
|
|
95
|
+
return 'POST';
|
|
96
|
+
if (/PutMapping|decorator-Put/i.test(r))
|
|
97
|
+
return 'PUT';
|
|
98
|
+
if (/DeleteMapping|decorator-Delete/i.test(r))
|
|
99
|
+
return 'DELETE';
|
|
100
|
+
if (/PatchMapping|decorator-Patch/i.test(r))
|
|
101
|
+
return 'PATCH';
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
function pickSymbolUid(rows, preferredName) {
|
|
105
|
+
const norm = (x) => String(x ?? '');
|
|
106
|
+
const labeled = rows.filter((r) => {
|
|
107
|
+
const labels = r.labels ?? r[3];
|
|
108
|
+
const s = JSON.stringify(labels);
|
|
109
|
+
return s.includes('Method') || s.includes('Function');
|
|
110
|
+
});
|
|
111
|
+
const pool = labeled.length > 0 ? labeled : rows;
|
|
112
|
+
if (preferredName) {
|
|
113
|
+
const hit = pool.find((r) => norm(r.name ?? r[1]) === preferredName);
|
|
114
|
+
if (hit) {
|
|
115
|
+
return {
|
|
116
|
+
uid: norm(hit.uid ?? hit[0]),
|
|
117
|
+
name: norm(hit.name ?? hit[1]),
|
|
118
|
+
filePath: norm(hit.filePath ?? hit[2]),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const first = pool[0] || rows[0];
|
|
123
|
+
return {
|
|
124
|
+
uid: norm(first?.uid ?? first?.[0]),
|
|
125
|
+
name: norm(first?.name ?? first?.[1]),
|
|
126
|
+
filePath: norm(first?.filePath ?? first?.[2]),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
// ─── Orchestrator ────────────────────────────────────────────────────
|
|
130
|
+
export class HttpRouteExtractor {
|
|
131
|
+
type = 'http';
|
|
132
|
+
async canExtract(_repo) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
async extract(dbExecutor, repoPath, _repo) {
|
|
136
|
+
// Parse each file at most once and reuse the plugin results across
|
|
137
|
+
// both graph-assisted enrichment and source-scan emission.
|
|
138
|
+
const parser = new Parser();
|
|
139
|
+
const cachedDetections = new Map();
|
|
140
|
+
const getDetections = (rel) => {
|
|
141
|
+
const cached = cachedDetections.get(rel);
|
|
142
|
+
if (cached)
|
|
143
|
+
return cached;
|
|
144
|
+
const plugin = getPluginForFile(rel);
|
|
145
|
+
if (!plugin) {
|
|
146
|
+
cachedDetections.set(rel, []);
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
const content = readSafe(repoPath, rel);
|
|
150
|
+
if (!content) {
|
|
151
|
+
cachedDetections.set(rel, []);
|
|
152
|
+
return [];
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
parser.setLanguage(plugin.language);
|
|
156
|
+
const tree = parser.parse(content);
|
|
157
|
+
const detections = plugin.scan(tree);
|
|
158
|
+
cachedDetections.set(rel, detections);
|
|
159
|
+
return detections;
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
cachedDetections.set(rel, []);
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
// Glob the source-scan file list at most once per extract() —
|
|
167
|
+
// both provider and consumer fallback paths share the same list.
|
|
168
|
+
let scannedFiles = null;
|
|
169
|
+
const getScannedFiles = async () => {
|
|
170
|
+
if (scannedFiles)
|
|
171
|
+
return scannedFiles;
|
|
172
|
+
scannedFiles = await this.scanFiles(repoPath);
|
|
173
|
+
return scannedFiles;
|
|
174
|
+
};
|
|
175
|
+
const graphProviders = dbExecutor != null ? await this.extractProvidersGraph(dbExecutor, getDetections) : [];
|
|
176
|
+
const providers = graphProviders.length > 0
|
|
177
|
+
? graphProviders
|
|
178
|
+
: this.extractProvidersSourceScan(await getScannedFiles(), getDetections);
|
|
179
|
+
const graphConsumers = dbExecutor != null ? await this.extractConsumersGraph(dbExecutor, getDetections) : [];
|
|
180
|
+
const consumers = graphConsumers.length > 0
|
|
181
|
+
? graphConsumers
|
|
182
|
+
: this.extractConsumersSourceScan(await getScannedFiles(), getDetections);
|
|
183
|
+
return [...providers, ...consumers];
|
|
184
|
+
}
|
|
185
|
+
async scanFiles(repoPath) {
|
|
186
|
+
return glob(HTTP_SCAN_GLOB, {
|
|
187
|
+
cwd: repoPath,
|
|
188
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/vendor/**'],
|
|
189
|
+
nodir: true,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// ─── Graph-assisted providers ──────────────────────────────────────
|
|
193
|
+
async extractProvidersGraph(db, getDetections) {
|
|
194
|
+
const out = [];
|
|
195
|
+
let rows;
|
|
196
|
+
try {
|
|
197
|
+
rows = await db(HANDLES_ROUTE_QUERY);
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
for (const row of rows) {
|
|
203
|
+
const filePath = String(row.filePath ?? '');
|
|
204
|
+
const routePath = String(row.routePath ?? '');
|
|
205
|
+
const routeSource = String(row.routeSource ?? row.routeReason ?? '');
|
|
206
|
+
let method = methodFromRouteReason(routeSource);
|
|
207
|
+
// Look up handler name (and backfill method if missing) from the
|
|
208
|
+
// plugin's scan of the handler file. This replaces the old
|
|
209
|
+
// regex-based `inferMethodFromFileScan` and `pickJavaHandlerName`
|
|
210
|
+
// helpers — tree-sitter gives both pieces of information
|
|
211
|
+
// structurally. Always run the lookup: even when method is set by
|
|
212
|
+
// `methodFromRouteReason`, we still need the handler name.
|
|
213
|
+
const detections = filePath ? getDetections(filePath) : [];
|
|
214
|
+
const providerDetections = detections.filter((d) => d.role === 'provider');
|
|
215
|
+
let handlerName = null;
|
|
216
|
+
const normalizedRoute = normalizeHttpPath(routePath);
|
|
217
|
+
const match = providerDetections.find((d) => normalizeHttpPath(d.path) === normalizedRoute);
|
|
218
|
+
if (match) {
|
|
219
|
+
if (!method)
|
|
220
|
+
method = match.method;
|
|
221
|
+
handlerName = match.name;
|
|
222
|
+
}
|
|
223
|
+
if (!method)
|
|
224
|
+
method = 'GET';
|
|
225
|
+
const pathNorm = normalizeHttpPath(routePath);
|
|
226
|
+
const cid = contractIdFor(method, pathNorm);
|
|
227
|
+
let symbolUid = '';
|
|
228
|
+
let symbolName = path.basename(filePath) || 'handler';
|
|
229
|
+
let symPath = filePath;
|
|
230
|
+
const fileId = row.fileId ?? row[0];
|
|
231
|
+
if (fileId) {
|
|
232
|
+
try {
|
|
233
|
+
const syms = await db(CONTAINS_QUERY, { fileId });
|
|
234
|
+
if (syms.length > 0) {
|
|
235
|
+
const picked = pickSymbolUid(syms, handlerName);
|
|
236
|
+
symbolUid = picked.uid;
|
|
237
|
+
symbolName = picked.name;
|
|
238
|
+
symPath = picked.filePath || filePath;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
/* ignore */
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
out.push({
|
|
246
|
+
contractId: cid,
|
|
247
|
+
type: 'http',
|
|
248
|
+
role: 'provider',
|
|
249
|
+
symbolUid,
|
|
250
|
+
symbolRef: { filePath: symPath, name: symbolName },
|
|
251
|
+
symbolName,
|
|
252
|
+
confidence: 0.9,
|
|
253
|
+
meta: {
|
|
254
|
+
method,
|
|
255
|
+
path: pathNorm,
|
|
256
|
+
pathSegments: pathNorm.split('/').filter(Boolean),
|
|
257
|
+
extractionStrategy: 'graph_assisted',
|
|
258
|
+
routeSource,
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
// ─── Source-scan providers ─────────────────────────────────────────
|
|
265
|
+
extractProvidersSourceScan(files, getDetections) {
|
|
266
|
+
const out = [];
|
|
267
|
+
for (const rel of files) {
|
|
268
|
+
const detections = getDetections(rel);
|
|
269
|
+
for (const d of detections) {
|
|
270
|
+
if (d.role !== 'provider')
|
|
271
|
+
continue;
|
|
272
|
+
const pathNorm = normalizeHttpPath(d.path);
|
|
273
|
+
out.push({
|
|
274
|
+
contractId: contractIdFor(d.method, pathNorm),
|
|
275
|
+
type: 'http',
|
|
276
|
+
role: 'provider',
|
|
277
|
+
symbolUid: '',
|
|
278
|
+
symbolRef: { filePath: rel, name: d.name ?? 'handler' },
|
|
279
|
+
symbolName: d.name ?? 'handler',
|
|
280
|
+
confidence: d.confidence,
|
|
281
|
+
meta: {
|
|
282
|
+
method: d.method,
|
|
283
|
+
path: pathNorm,
|
|
284
|
+
pathSegments: pathNorm.split('/').filter(Boolean),
|
|
285
|
+
extractionStrategy: 'source_scan',
|
|
286
|
+
framework: d.framework,
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return this.dedupeContracts(out);
|
|
292
|
+
}
|
|
293
|
+
// ─── Graph-assisted consumers ──────────────────────────────────────
|
|
294
|
+
async extractConsumersGraph(db, getDetections) {
|
|
295
|
+
const out = [];
|
|
296
|
+
let rows;
|
|
297
|
+
try {
|
|
298
|
+
rows = await db(FETCHES_QUERY);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
for (const row of rows) {
|
|
304
|
+
const filePath = String(row.filePath ?? '');
|
|
305
|
+
const routePath = String(row.routePath ?? '');
|
|
306
|
+
const pathNorm = normalizeHttpPath(routePath);
|
|
307
|
+
let method = 'GET';
|
|
308
|
+
// Prefer the plugin's detected method if we can find a matching
|
|
309
|
+
// fetch/axios call in the same file.
|
|
310
|
+
const detections = filePath ? getDetections(filePath) : [];
|
|
311
|
+
const inferred = detections.find((d) => d.role === 'consumer' && normalizeConsumerPath(d.path) === pathNorm);
|
|
312
|
+
if (inferred)
|
|
313
|
+
method = inferred.method;
|
|
314
|
+
const cid = contractIdFor(method, pathNorm);
|
|
315
|
+
let symbolUid = '';
|
|
316
|
+
let symbolName = 'fetch';
|
|
317
|
+
let symPath = filePath;
|
|
318
|
+
const fileId = row.fileId ?? row[0];
|
|
319
|
+
if (fileId) {
|
|
320
|
+
try {
|
|
321
|
+
const syms = await db(CONTAINS_QUERY, { fileId });
|
|
322
|
+
if (syms.length > 0) {
|
|
323
|
+
const picked = pickSymbolUid(syms, null);
|
|
324
|
+
symbolUid = picked.uid;
|
|
325
|
+
symbolName = picked.name;
|
|
326
|
+
symPath = picked.filePath || filePath;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
/* ignore */
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
out.push({
|
|
334
|
+
contractId: cid,
|
|
335
|
+
type: 'http',
|
|
336
|
+
role: 'consumer',
|
|
337
|
+
symbolUid,
|
|
338
|
+
symbolRef: { filePath: symPath, name: symbolName },
|
|
339
|
+
symbolName,
|
|
340
|
+
confidence: 0.9,
|
|
341
|
+
meta: {
|
|
342
|
+
method,
|
|
343
|
+
path: pathNorm,
|
|
344
|
+
extractionStrategy: 'graph_assisted',
|
|
345
|
+
fetchReason: String(row.fetchReason ?? ''),
|
|
346
|
+
},
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return out;
|
|
350
|
+
}
|
|
351
|
+
// ─── Source-scan consumers ─────────────────────────────────────────
|
|
352
|
+
extractConsumersSourceScan(files, getDetections) {
|
|
353
|
+
const out = [];
|
|
354
|
+
for (const rel of files) {
|
|
355
|
+
const detections = getDetections(rel);
|
|
356
|
+
for (const d of detections) {
|
|
357
|
+
if (d.role !== 'consumer')
|
|
358
|
+
continue;
|
|
359
|
+
const pathNorm = normalizeConsumerPath(d.path);
|
|
360
|
+
out.push({
|
|
361
|
+
contractId: contractIdFor(d.method, pathNorm),
|
|
362
|
+
type: 'http',
|
|
363
|
+
role: 'consumer',
|
|
364
|
+
symbolUid: '',
|
|
365
|
+
symbolRef: { filePath: rel, name: 'fetch' },
|
|
366
|
+
symbolName: 'fetch',
|
|
367
|
+
confidence: d.confidence,
|
|
368
|
+
meta: {
|
|
369
|
+
method: d.method,
|
|
370
|
+
path: pathNorm,
|
|
371
|
+
extractionStrategy: 'source_scan',
|
|
372
|
+
framework: d.framework,
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return this.dedupeContracts(out);
|
|
378
|
+
}
|
|
379
|
+
dedupeContracts(items) {
|
|
380
|
+
const seen = new Set();
|
|
381
|
+
const out = [];
|
|
382
|
+
for (const c of items) {
|
|
383
|
+
const k = `${c.contractId}|${c.symbolRef.filePath}|${c.symbolRef.name}`;
|
|
384
|
+
if (seen.has(k))
|
|
385
|
+
continue;
|
|
386
|
+
seen.add(k);
|
|
387
|
+
out.push(c);
|
|
388
|
+
}
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { CrossLink, GroupManifestLink, StoredContract } from '../types.js';
|
|
2
|
+
import type { CypherExecutor } from '../contract-extractor.js';
|
|
3
|
+
export interface ManifestExtractResult {
|
|
4
|
+
contracts: StoredContract[];
|
|
5
|
+
crossLinks: CrossLink[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Stable synthetic symbolUid for a manifest-declared contract whose target
|
|
9
|
+
* symbol could not be resolved against the per-repo graph (resolveSymbol
|
|
10
|
+
* returned null). Two reasons we don't leave the uid empty:
|
|
11
|
+
*
|
|
12
|
+
* 1. The bridge stores Contract nodes keyed in part by symbolUid; an empty
|
|
13
|
+
* uid means downstream Cypher queries that anchor on `provider.symbolUid`
|
|
14
|
+
* can't tell two different unresolved manifest contracts apart.
|
|
15
|
+
* 2. The cross-impact bridge query in cross-impact.ts joins local impact
|
|
16
|
+
* results to bridge contracts via `WHERE provider.symbolUid IN $localUids`.
|
|
17
|
+
* If the local impact engine produces a deterministic identifier for the
|
|
18
|
+
* unresolved target, it must agree with the value the bridge stored. A
|
|
19
|
+
* synthetic uid keyed off (repo, contractId) is the only thing both sides
|
|
20
|
+
* can derive without knowing about each other.
|
|
21
|
+
*
|
|
22
|
+
* Format: `manifest::<repo>::<contractId>`. Stable across syncs, scoped to a
|
|
23
|
+
* single repo within a group, and never collides with real indexer uids
|
|
24
|
+
* (which never start with `manifest::`).
|
|
25
|
+
*/
|
|
26
|
+
export declare function manifestSymbolUid(repo: string, contractId: string): string;
|
|
27
|
+
export declare class ManifestExtractor {
|
|
28
|
+
extractFromManifest(links: GroupManifestLink[], dbExecutors?: Map<string, CypherExecutor>): Promise<ManifestExtractResult>;
|
|
29
|
+
private resolveSymbol;
|
|
30
|
+
/**
|
|
31
|
+
* Build a canonical contract id for a manifest link.
|
|
32
|
+
*
|
|
33
|
+
* HTTP is the only type with two valid forms:
|
|
34
|
+
* - Explicit method: `"GET::/api/orders"` → `"http::GET::/api/orders"`
|
|
35
|
+
* (matches exactly against `HttpRouteExtractor` provider/consumer
|
|
36
|
+
* contracts, which are also keyed by `http::<METHOD>::<path>`).
|
|
37
|
+
* - Method-agnostic: `"/api/orders"` → `"http::*::/api/orders"`
|
|
38
|
+
* — the `*` is a wildcard and is intended to match any concrete
|
|
39
|
+
* HTTP method on that path. Wildcard-aware matching is the
|
|
40
|
+
* responsibility of the sync / cross-impact layer (see #793);
|
|
41
|
+
* downstream code should treat `http::*::<path>` as matching
|
|
42
|
+
* every `http::<METHOD>::<path>` for the same path.
|
|
43
|
+
*
|
|
44
|
+
* Recommend the explicit-method form in group.yaml whenever the
|
|
45
|
+
* manifest author knows the method — it round-trips through exact
|
|
46
|
+
* equality matching without requiring wildcard logic downstream.
|
|
47
|
+
*
|
|
48
|
+
* NOTE on exhaustiveness: the switch covers every current
|
|
49
|
+
* `ContractType` variant and falls through to a `never` assertion so
|
|
50
|
+
* TypeScript fails the build if a new variant is added without a
|
|
51
|
+
* corresponding case.
|
|
52
|
+
*/
|
|
53
|
+
private buildContractId;
|
|
54
|
+
}
|