gitnexus 1.5.3 → 1.6.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/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 +2 -1
- 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/grpc-extractor.d.ts +16 -0
- package/dist/core/group/extractors/grpc-extractor.js +264 -0
- package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
- package/dist/core/group/extractors/http-route-extractor.js +428 -0
- package/dist/core/group/extractors/topic-extractor.d.ts +9 -0
- package/dist/core/group/extractors/topic-extractor.js +234 -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 +207 -0
- package/dist/core/ingestion/binding-accumulator.js +332 -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/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 +0 -12
- 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-provider.d.ts +6 -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 +28 -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/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 +13 -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 +285 -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.js +38 -15
- package/dist/core/ingestion/method-types.d.ts +25 -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 +242 -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 +4 -3
- package/dist/core/ingestion/mro-processor.js +310 -106
- package/dist/core/ingestion/parsing-processor.d.ts +5 -4
- package/dist/core/ingestion/parsing-processor.js +210 -85
- package/dist/core/ingestion/pipeline.d.ts +2 -0
- package/dist/core/ingestion/pipeline.js +192 -68
- package/dist/core/ingestion/tree-sitter-queries.d.ts +5 -5
- package/dist/core/ingestion/tree-sitter-queries.js +21 -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 +33 -43
- package/dist/core/ingestion/utils/ast-helpers.js +129 -572
- 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 +463 -198
- package/dist/core/lbug/lbug-adapter.d.ts +6 -0
- package/dist/core/lbug/lbug-adapter.js +68 -3
- 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 +8 -2
- package/scripts/patch-tree-sitter-swift.cjs +78 -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,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridge LadybugDB schema for cross-repo Contract Registry.
|
|
3
|
+
* Separate from per-repo schema in lbug/schema.ts.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Version of the bridge.lbug schema below. `openBridgeDbReadOnly` compares
|
|
7
|
+
* this against `meta.json`'s version field and returns `null` on mismatch,
|
|
8
|
+
* which trips the caller into either the JSON fallback path or a fresh
|
|
9
|
+
* `group sync` that rebuilds `bridge.lbug` from scratch.
|
|
10
|
+
*
|
|
11
|
+
* Migration contract for contributors bumping this constant:
|
|
12
|
+
* 1. Bump the number (e.g. `1` → `2`).
|
|
13
|
+
* 2. Update the DDL below to match the new schema.
|
|
14
|
+
* 3. DO NOT attempt an online migration in this file — the version gate
|
|
15
|
+
* is intentionally a "discard and re-sync" strategy for V1. An old
|
|
16
|
+
* bridge.lbug whose version doesn't match is treated as opaque and
|
|
17
|
+
* rebuilt by the next `group sync`.
|
|
18
|
+
* 4. If online migration becomes necessary (e.g. when groups accumulate
|
|
19
|
+
* large amounts of embedding data), add a migration path as a
|
|
20
|
+
* separate `bridge-migrations.ts` module rather than bloating this
|
|
21
|
+
* file — keep schema and migration concerns separate.
|
|
22
|
+
*/
|
|
23
|
+
export const BRIDGE_SCHEMA_VERSION = 1;
|
|
24
|
+
export const CONTRACT_SCHEMA = `
|
|
25
|
+
CREATE NODE TABLE Contract (
|
|
26
|
+
id STRING,
|
|
27
|
+
contractId STRING,
|
|
28
|
+
type STRING,
|
|
29
|
+
role STRING,
|
|
30
|
+
repo STRING,
|
|
31
|
+
service STRING DEFAULT '',
|
|
32
|
+
symbolUid STRING DEFAULT '',
|
|
33
|
+
filePath STRING DEFAULT '',
|
|
34
|
+
symbolName STRING DEFAULT '',
|
|
35
|
+
confidence DOUBLE DEFAULT 0.0,
|
|
36
|
+
meta STRING DEFAULT '{}',
|
|
37
|
+
PRIMARY KEY (id)
|
|
38
|
+
)`;
|
|
39
|
+
export const REPO_SNAPSHOT_SCHEMA = `
|
|
40
|
+
CREATE NODE TABLE RepoSnapshot (
|
|
41
|
+
id STRING,
|
|
42
|
+
indexedAt STRING DEFAULT '',
|
|
43
|
+
lastCommit STRING DEFAULT '',
|
|
44
|
+
PRIMARY KEY (id)
|
|
45
|
+
)`;
|
|
46
|
+
export const CONTRACT_LINK_SCHEMA = `
|
|
47
|
+
CREATE REL TABLE ContractLink (
|
|
48
|
+
FROM Contract TO Contract,
|
|
49
|
+
matchType STRING,
|
|
50
|
+
confidence DOUBLE,
|
|
51
|
+
contractId STRING,
|
|
52
|
+
fromRepo STRING,
|
|
53
|
+
toRepo STRING
|
|
54
|
+
)`;
|
|
55
|
+
export const BRIDGE_SCHEMA_QUERIES = [CONTRACT_SCHEMA, REPO_SNAPSHOT_SCHEMA, CONTRACT_LINK_SCHEMA];
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
const _require = createRequire(import.meta.url);
|
|
3
|
+
const yaml = _require('js-yaml');
|
|
4
|
+
const VALID_CONTRACT_TYPES = ['http', 'grpc', 'topic', 'lib', 'custom'];
|
|
5
|
+
const VALID_ROLES = ['provider', 'consumer'];
|
|
6
|
+
const DEFAULT_DETECT = {
|
|
7
|
+
http: true,
|
|
8
|
+
grpc: true,
|
|
9
|
+
topics: true,
|
|
10
|
+
shared_libs: true,
|
|
11
|
+
embedding_fallback: true,
|
|
12
|
+
};
|
|
13
|
+
const DEFAULT_MATCHING = {
|
|
14
|
+
bm25_threshold: 0.7,
|
|
15
|
+
embedding_threshold: 0.65,
|
|
16
|
+
max_candidates_per_step: 3,
|
|
17
|
+
};
|
|
18
|
+
export function parseGroupConfig(yamlContent) {
|
|
19
|
+
const raw = yaml.load(yamlContent, { schema: yaml.JSON_SCHEMA });
|
|
20
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
21
|
+
throw new Error('Invalid YAML: expected an object');
|
|
22
|
+
}
|
|
23
|
+
if (raw.version === undefined)
|
|
24
|
+
throw new Error('version is required in group.yaml');
|
|
25
|
+
if (raw.version !== 1) {
|
|
26
|
+
throw new Error(`Unsupported group.yaml version: ${raw.version}. Expected 1.`);
|
|
27
|
+
}
|
|
28
|
+
if (!raw.name || typeof raw.name !== 'string')
|
|
29
|
+
throw new Error('name is required in group.yaml');
|
|
30
|
+
if (!raw.repos || typeof raw.repos !== 'object' || Array.isArray(raw.repos)) {
|
|
31
|
+
throw new Error('repos is required in group.yaml (must be a mapping)');
|
|
32
|
+
}
|
|
33
|
+
const repos = raw.repos;
|
|
34
|
+
const repoPaths = new Set(Object.keys(repos));
|
|
35
|
+
const rawLinks = raw.links || [];
|
|
36
|
+
const links = rawLinks.map((l, i) => {
|
|
37
|
+
const link = l;
|
|
38
|
+
if (!link.from || !repoPaths.has(link.from)) {
|
|
39
|
+
throw new Error(`links[${i}].from "${link.from}" does not match any repo path in group`);
|
|
40
|
+
}
|
|
41
|
+
if (!link.to || !repoPaths.has(link.to)) {
|
|
42
|
+
throw new Error(`links[${i}].to "${link.to}" does not match any repo path in group`);
|
|
43
|
+
}
|
|
44
|
+
if (!VALID_CONTRACT_TYPES.includes(link.type)) {
|
|
45
|
+
throw new Error(`links[${i}].type "${link.type}" is invalid. Expected: ${VALID_CONTRACT_TYPES.join(', ')}`);
|
|
46
|
+
}
|
|
47
|
+
if (!VALID_ROLES.includes(link.role)) {
|
|
48
|
+
throw new Error(`links[${i}].role "${link.role}" is invalid. Expected: provider | consumer`);
|
|
49
|
+
}
|
|
50
|
+
if (link.contract === undefined ||
|
|
51
|
+
link.contract === null ||
|
|
52
|
+
String(link.contract).trim() === '') {
|
|
53
|
+
throw new Error(`links[${i}].contract is required`);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
from: link.from,
|
|
57
|
+
to: link.to,
|
|
58
|
+
type: link.type,
|
|
59
|
+
contract: String(link.contract),
|
|
60
|
+
role: link.role,
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
const detect = { ...DEFAULT_DETECT, ...(raw.detect || {}) };
|
|
64
|
+
const matching = { ...DEFAULT_MATCHING, ...(raw.matching || {}) };
|
|
65
|
+
const packages = raw.packages || {};
|
|
66
|
+
return {
|
|
67
|
+
version: 1,
|
|
68
|
+
name: raw.name,
|
|
69
|
+
description: raw.description || '',
|
|
70
|
+
repos,
|
|
71
|
+
links,
|
|
72
|
+
packages,
|
|
73
|
+
detect,
|
|
74
|
+
matching,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export async function loadGroupConfig(groupDir) {
|
|
78
|
+
const fsp = await import('node:fs/promises');
|
|
79
|
+
const path = await import('node:path');
|
|
80
|
+
const yamlPath = path.join(groupDir, 'group.yaml');
|
|
81
|
+
const content = await fsp.readFile(yamlPath, 'utf-8');
|
|
82
|
+
return parseGroupConfig(content);
|
|
83
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ContractType, ExtractedContract, RepoHandle } from './types.js';
|
|
2
|
+
export interface ContractExtractor {
|
|
3
|
+
type: ContractType;
|
|
4
|
+
canExtract(repo: RepoHandle): Promise<boolean>;
|
|
5
|
+
extract(dbExecutor: CypherExecutor | null, repoPath: string, repo: RepoHandle): Promise<ExtractedContract[]>;
|
|
6
|
+
}
|
|
7
|
+
export type CypherExecutor = (query: string, params?: Record<string, unknown>) => Promise<Record<string, unknown>[]>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ContractExtractor, CypherExecutor } from '../contract-extractor.js';
|
|
2
|
+
import type { ExtractedContract, RepoHandle } from '../types.js';
|
|
3
|
+
export declare class GrpcExtractor implements ContractExtractor {
|
|
4
|
+
type: "grpc";
|
|
5
|
+
canExtract(_repo: RepoHandle): Promise<boolean>;
|
|
6
|
+
extract(_dbExecutor: CypherExecutor | null, repoPath: string, _repo: RepoHandle): Promise<ExtractedContract[]>;
|
|
7
|
+
private parseProtoFile;
|
|
8
|
+
private scanGoProviders;
|
|
9
|
+
private scanGoConsumers;
|
|
10
|
+
private scanJavaProviders;
|
|
11
|
+
private scanJavaConsumers;
|
|
12
|
+
private scanPythonProviders;
|
|
13
|
+
private scanPythonConsumers;
|
|
14
|
+
private scanTsProviders;
|
|
15
|
+
private dedupe;
|
|
16
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { glob } from 'glob';
|
|
4
|
+
function readSafe(repoPath, rel) {
|
|
5
|
+
const abs = path.resolve(repoPath, rel);
|
|
6
|
+
const base = path.resolve(repoPath);
|
|
7
|
+
const relToBase = path.relative(base, abs);
|
|
8
|
+
if (relToBase.startsWith('..') || path.isAbsolute(relToBase))
|
|
9
|
+
return null;
|
|
10
|
+
try {
|
|
11
|
+
return fs.readFileSync(abs, 'utf-8');
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function contractId(pkg, service, method) {
|
|
18
|
+
const prefix = pkg ? `${pkg}.${service}` : service;
|
|
19
|
+
return `grpc::${prefix}/${method}`;
|
|
20
|
+
}
|
|
21
|
+
function serviceOnlyContractId(serviceName) {
|
|
22
|
+
return `grpc::${serviceName}/*`;
|
|
23
|
+
}
|
|
24
|
+
function extractServiceBlocks(content) {
|
|
25
|
+
const results = [];
|
|
26
|
+
// v1: brace-depth only — braces inside comments or string literals are not filtered (see spec Fix 2)
|
|
27
|
+
const headerRe = /service\s+(\w+)\s*\{/g;
|
|
28
|
+
let headerMatch;
|
|
29
|
+
while ((headerMatch = headerRe.exec(content)) !== null) {
|
|
30
|
+
const serviceName = headerMatch[1];
|
|
31
|
+
const bodyStart = headerMatch.index + headerMatch[0].length;
|
|
32
|
+
let depth = 1;
|
|
33
|
+
let pos = bodyStart;
|
|
34
|
+
while (pos < content.length && depth > 0) {
|
|
35
|
+
const ch = content[pos];
|
|
36
|
+
if (ch === '{')
|
|
37
|
+
depth++;
|
|
38
|
+
else if (ch === '}')
|
|
39
|
+
depth--;
|
|
40
|
+
pos++;
|
|
41
|
+
}
|
|
42
|
+
// If EOF before depth returns to 0, skip incomplete service
|
|
43
|
+
if (depth !== 0)
|
|
44
|
+
continue;
|
|
45
|
+
// body is between opening { (consumed by regex) and closing } (pos is one past it)
|
|
46
|
+
const body = content.slice(bodyStart, pos - 1);
|
|
47
|
+
results.push({ name: serviceName, body });
|
|
48
|
+
}
|
|
49
|
+
return results;
|
|
50
|
+
}
|
|
51
|
+
function makeContract(cid, role, filePath, symbolName, confidence, meta) {
|
|
52
|
+
return {
|
|
53
|
+
contractId: cid,
|
|
54
|
+
type: 'grpc',
|
|
55
|
+
role,
|
|
56
|
+
symbolUid: '',
|
|
57
|
+
symbolRef: { filePath: filePath.replace(/\\/g, '/'), name: symbolName },
|
|
58
|
+
symbolName,
|
|
59
|
+
confidence,
|
|
60
|
+
meta: { ...meta, extractionStrategy: 'source_scan' },
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
export class GrpcExtractor {
|
|
64
|
+
type = 'grpc';
|
|
65
|
+
async canExtract(_repo) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
async extract(_dbExecutor, repoPath, _repo) {
|
|
69
|
+
const out = [];
|
|
70
|
+
// Proto files — definitive provider source
|
|
71
|
+
const protoFiles = await glob('**/*.proto', {
|
|
72
|
+
cwd: repoPath,
|
|
73
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/vendor/**'],
|
|
74
|
+
nodir: true,
|
|
75
|
+
});
|
|
76
|
+
for (const rel of protoFiles) {
|
|
77
|
+
const content = readSafe(repoPath, rel);
|
|
78
|
+
if (content)
|
|
79
|
+
out.push(...this.parseProtoFile(content, rel));
|
|
80
|
+
}
|
|
81
|
+
// Source files — server/client detection
|
|
82
|
+
const sourceFiles = await glob('**/*.{go,java,py,ts,tsx,js,jsx}', {
|
|
83
|
+
cwd: repoPath,
|
|
84
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/vendor/**', '**/dist/**', '**/build/**'],
|
|
85
|
+
nodir: true,
|
|
86
|
+
});
|
|
87
|
+
for (const rel of sourceFiles) {
|
|
88
|
+
const content = readSafe(repoPath, rel);
|
|
89
|
+
if (!content)
|
|
90
|
+
continue;
|
|
91
|
+
const ext = path.extname(rel).toLowerCase();
|
|
92
|
+
if (ext === '.go') {
|
|
93
|
+
out.push(...this.scanGoProviders(content, rel));
|
|
94
|
+
out.push(...this.scanGoConsumers(content, rel));
|
|
95
|
+
}
|
|
96
|
+
else if (ext === '.java') {
|
|
97
|
+
out.push(...this.scanJavaProviders(content, rel));
|
|
98
|
+
out.push(...this.scanJavaConsumers(content, rel));
|
|
99
|
+
}
|
|
100
|
+
else if (ext === '.py') {
|
|
101
|
+
out.push(...this.scanPythonProviders(content, rel));
|
|
102
|
+
out.push(...this.scanPythonConsumers(content, rel));
|
|
103
|
+
}
|
|
104
|
+
else if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
105
|
+
out.push(...this.scanTsProviders(content, rel));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return this.dedupe(out);
|
|
109
|
+
}
|
|
110
|
+
parseProtoFile(content, filePath) {
|
|
111
|
+
const out = [];
|
|
112
|
+
const pkgMatch = content.match(/^package\s+([\w.]+)\s*;/m);
|
|
113
|
+
const pkg = pkgMatch ? pkgMatch[1] : '';
|
|
114
|
+
for (const { name: serviceName, body } of extractServiceBlocks(content)) {
|
|
115
|
+
const rpcRe = /rpc\s+(\w+)\s*\(/g;
|
|
116
|
+
let rpcMatch;
|
|
117
|
+
while ((rpcMatch = rpcRe.exec(body)) !== null) {
|
|
118
|
+
const methodName = rpcMatch[1];
|
|
119
|
+
const cid = contractId(pkg, serviceName, methodName);
|
|
120
|
+
out.push(makeContract(cid, 'provider', filePath, `${serviceName}.${methodName}`, 0.85, {
|
|
121
|
+
package: pkg,
|
|
122
|
+
service: serviceName,
|
|
123
|
+
method: methodName,
|
|
124
|
+
source: 'proto',
|
|
125
|
+
}));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
scanGoProviders(content, filePath) {
|
|
131
|
+
const out = [];
|
|
132
|
+
// pb.RegisterXxxServer(
|
|
133
|
+
const registerRe = /\w+\.Register(\w+)Server\s*\(/g;
|
|
134
|
+
let m;
|
|
135
|
+
while ((m = registerRe.exec(content)) !== null) {
|
|
136
|
+
const serviceName = m[1];
|
|
137
|
+
out.push(makeContract(serviceOnlyContractId(serviceName), 'provider', filePath, `Register${serviceName}Server`, 0.8, { service: serviceName, source: 'go_register' }));
|
|
138
|
+
}
|
|
139
|
+
// pb.UnimplementedXxxServer
|
|
140
|
+
const unimplRe = /\w+\.Unimplemented(\w+)Server\b/g;
|
|
141
|
+
while ((m = unimplRe.exec(content)) !== null) {
|
|
142
|
+
const serviceName = m[1];
|
|
143
|
+
out.push(makeContract(serviceOnlyContractId(serviceName), 'provider', filePath, `Unimplemented${serviceName}Server`, 0.8, { service: serviceName, source: 'go_unimplemented' }));
|
|
144
|
+
}
|
|
145
|
+
return out;
|
|
146
|
+
}
|
|
147
|
+
scanGoConsumers(content, filePath) {
|
|
148
|
+
const out = [];
|
|
149
|
+
const re = /\w+\.New(\w+)Client\s*\(/g;
|
|
150
|
+
let m;
|
|
151
|
+
while ((m = re.exec(content)) !== null) {
|
|
152
|
+
const serviceName = m[1];
|
|
153
|
+
out.push(makeContract(serviceOnlyContractId(serviceName), 'consumer', filePath, `New${serviceName}Client`, 0.7, { service: serviceName, source: 'go_client' }));
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
scanJavaProviders(content, filePath) {
|
|
158
|
+
const out = [];
|
|
159
|
+
// @GrpcService
|
|
160
|
+
if (content.includes('@GrpcService')) {
|
|
161
|
+
const implBaseRe = /extends\s+(\w+)Grpc\.(\w+)ImplBase/;
|
|
162
|
+
const m = content.match(implBaseRe);
|
|
163
|
+
if (m) {
|
|
164
|
+
out.push(makeContract(serviceOnlyContractId(m[1]), 'provider', filePath, m[2], 0.8, {
|
|
165
|
+
service: m[1],
|
|
166
|
+
source: 'java_grpc_service',
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
// Try extracting service name from class name
|
|
171
|
+
const classRe = /class\s+(\w*?)(?:Grpc)?(?:Service)?\s+extends\s+(\w+)(?:Grpc\.(\w+))?ImplBase/;
|
|
172
|
+
const cm = content.match(classRe);
|
|
173
|
+
if (cm) {
|
|
174
|
+
const svcName = cm[2].replace(/Grpc$/, '');
|
|
175
|
+
out.push(makeContract(serviceOnlyContractId(svcName), 'provider', filePath, cm[1], 0.8, {
|
|
176
|
+
service: svcName,
|
|
177
|
+
source: 'java_grpc_service',
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// extends XxxImplBase (without @GrpcService)
|
|
183
|
+
if (!content.includes('@GrpcService')) {
|
|
184
|
+
const implRe = /extends\s+(\w+?)(?:Grpc\.(\w+))?ImplBase/;
|
|
185
|
+
const m = content.match(implRe);
|
|
186
|
+
if (m) {
|
|
187
|
+
const svcName = m[2] || m[1].replace(/Grpc$/, '');
|
|
188
|
+
out.push(makeContract(serviceOnlyContractId(svcName), 'provider', filePath, svcName, 0.8, {
|
|
189
|
+
service: svcName,
|
|
190
|
+
source: 'java_impl_base',
|
|
191
|
+
}));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
scanJavaConsumers(content, filePath) {
|
|
197
|
+
const out = [];
|
|
198
|
+
// XxxGrpc.newBlockingStub( or XxxGrpc.newStub(
|
|
199
|
+
const re = /(\w+)Grpc\.new(?:Blocking)?Stub\s*\(/g;
|
|
200
|
+
let m;
|
|
201
|
+
while ((m = re.exec(content)) !== null) {
|
|
202
|
+
const serviceName = m[1];
|
|
203
|
+
out.push(makeContract(serviceOnlyContractId(serviceName), 'consumer', filePath, `${serviceName}Stub`, 0.7, { service: serviceName, source: 'java_stub' }));
|
|
204
|
+
}
|
|
205
|
+
return out;
|
|
206
|
+
}
|
|
207
|
+
scanPythonProviders(content, filePath) {
|
|
208
|
+
const out = [];
|
|
209
|
+
// add_XxxServicer_to_server(
|
|
210
|
+
const re = /add_(\w+?)Servicer_to_server\s*\(/g;
|
|
211
|
+
let m;
|
|
212
|
+
while ((m = re.exec(content)) !== null) {
|
|
213
|
+
const serviceName = m[1];
|
|
214
|
+
out.push(makeContract(serviceOnlyContractId(serviceName), 'provider', filePath, `add_${serviceName}Servicer_to_server`, 0.8, { service: serviceName, source: 'python_servicer' }));
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
scanPythonConsumers(content, filePath) {
|
|
219
|
+
const out = [];
|
|
220
|
+
// XxxStub(
|
|
221
|
+
const re = /(\w+)Stub\s*\(/g;
|
|
222
|
+
let m;
|
|
223
|
+
while ((m = re.exec(content)) !== null) {
|
|
224
|
+
const name = m[1];
|
|
225
|
+
// Filter out common false positives
|
|
226
|
+
if (['Mock', 'Test', 'Fake', 'Stub'].includes(name))
|
|
227
|
+
continue;
|
|
228
|
+
out.push(makeContract(serviceOnlyContractId(name), 'consumer', filePath, `${name}Stub`, 0.7, {
|
|
229
|
+
service: name,
|
|
230
|
+
source: 'python_stub',
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
scanTsProviders(content, filePath) {
|
|
236
|
+
const out = [];
|
|
237
|
+
// @GrpcMethod('ServiceName', 'MethodName')
|
|
238
|
+
const re = /@GrpcMethod\s*\(\s*['"](\w+)['"]\s*,\s*['"](\w+)['"]\s*\)/g;
|
|
239
|
+
let m;
|
|
240
|
+
while ((m = re.exec(content)) !== null) {
|
|
241
|
+
const serviceName = m[1];
|
|
242
|
+
const methodName = m[2];
|
|
243
|
+
const cid = contractId('', serviceName, methodName);
|
|
244
|
+
out.push(makeContract(cid, 'provider', filePath, `${serviceName}.${methodName}`, 0.8, {
|
|
245
|
+
service: serviceName,
|
|
246
|
+
method: methodName,
|
|
247
|
+
source: 'ts_grpc_method',
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
dedupe(items) {
|
|
253
|
+
const seen = new Set();
|
|
254
|
+
const out = [];
|
|
255
|
+
for (const c of items) {
|
|
256
|
+
const k = `${c.contractId}|${c.role}|${c.symbolRef.filePath}`;
|
|
257
|
+
if (seen.has(k))
|
|
258
|
+
continue;
|
|
259
|
+
seen.add(k);
|
|
260
|
+
out.push(c);
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ContractExtractor, CypherExecutor } from '../contract-extractor.js';
|
|
2
|
+
import type { ExtractedContract, RepoHandle } from '../types.js';
|
|
3
|
+
export declare function normalizeHttpPath(p: string): string;
|
|
4
|
+
export declare class HttpRouteExtractor implements ContractExtractor {
|
|
5
|
+
type: "http";
|
|
6
|
+
canExtract(_repo: RepoHandle): Promise<boolean>;
|
|
7
|
+
extract(dbExecutor: CypherExecutor | null, repoPath: string, repo: RepoHandle): Promise<ExtractedContract[]>;
|
|
8
|
+
private extractProvidersGraph;
|
|
9
|
+
private inferMethodFromFileScan;
|
|
10
|
+
private extractProvidersSourceScan;
|
|
11
|
+
private dedupeContracts;
|
|
12
|
+
private scanSpringProviders;
|
|
13
|
+
private scanExpressProviders;
|
|
14
|
+
private scanLaravelProviders;
|
|
15
|
+
private scanFastApiProviders;
|
|
16
|
+
private makeProvider;
|
|
17
|
+
private extractConsumersGraph;
|
|
18
|
+
private inferFetchMethod;
|
|
19
|
+
private extractConsumersSourceScan;
|
|
20
|
+
private scanFetchConsumers;
|
|
21
|
+
private templateToPattern;
|
|
22
|
+
private scanAxiosConsumers;
|
|
23
|
+
private makeConsumer;
|
|
24
|
+
}
|