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.
Files changed (201) hide show
  1. package/README.md +10 -0
  2. package/dist/_shared/graph/types.d.ts +1 -1
  3. package/dist/_shared/graph/types.d.ts.map +1 -1
  4. package/dist/_shared/index.d.ts +1 -0
  5. package/dist/_shared/index.d.ts.map +1 -1
  6. package/dist/_shared/language-detection.d.ts.map +1 -1
  7. package/dist/_shared/language-detection.js +2 -0
  8. package/dist/_shared/language-detection.js.map +1 -1
  9. package/dist/_shared/languages.d.ts +1 -0
  10. package/dist/_shared/languages.d.ts.map +1 -1
  11. package/dist/_shared/languages.js +1 -0
  12. package/dist/_shared/languages.js.map +1 -1
  13. package/dist/_shared/lbug/schema-constants.d.ts +1 -1
  14. package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
  15. package/dist/_shared/lbug/schema-constants.js +3 -1
  16. package/dist/_shared/lbug/schema-constants.js.map +1 -1
  17. package/dist/_shared/mro-strategy.d.ts +19 -0
  18. package/dist/_shared/mro-strategy.d.ts.map +1 -0
  19. package/dist/_shared/mro-strategy.js +2 -0
  20. package/dist/_shared/mro-strategy.js.map +1 -0
  21. package/dist/cli/ai-context.d.ts +1 -0
  22. package/dist/cli/ai-context.js +28 -4
  23. package/dist/cli/analyze.d.ts +2 -0
  24. package/dist/cli/analyze.js +2 -1
  25. package/dist/cli/group.d.ts +2 -0
  26. package/dist/cli/group.js +233 -0
  27. package/dist/cli/index.js +3 -0
  28. package/dist/cli/serve.js +4 -1
  29. package/dist/cli/setup.js +34 -3
  30. package/dist/config/ignore-service.js +8 -3
  31. package/dist/core/augmentation/engine.js +1 -1
  32. package/dist/core/git-staleness.d.ts +13 -0
  33. package/dist/core/git-staleness.js +29 -0
  34. package/dist/core/group/bridge-db.d.ts +82 -0
  35. package/dist/core/group/bridge-db.js +460 -0
  36. package/dist/core/group/bridge-schema.d.ts +27 -0
  37. package/dist/core/group/bridge-schema.js +55 -0
  38. package/dist/core/group/config-parser.d.ts +3 -0
  39. package/dist/core/group/config-parser.js +83 -0
  40. package/dist/core/group/contract-extractor.d.ts +7 -0
  41. package/dist/core/group/contract-extractor.js +1 -0
  42. package/dist/core/group/extractors/grpc-extractor.d.ts +16 -0
  43. package/dist/core/group/extractors/grpc-extractor.js +264 -0
  44. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  45. package/dist/core/group/extractors/http-route-extractor.js +428 -0
  46. package/dist/core/group/extractors/topic-extractor.d.ts +9 -0
  47. package/dist/core/group/extractors/topic-extractor.js +234 -0
  48. package/dist/core/group/matching.d.ts +13 -0
  49. package/dist/core/group/matching.js +198 -0
  50. package/dist/core/group/normalization.d.ts +3 -0
  51. package/dist/core/group/normalization.js +115 -0
  52. package/dist/core/group/service-boundary-detector.d.ts +8 -0
  53. package/dist/core/group/service-boundary-detector.js +155 -0
  54. package/dist/core/group/service.d.ts +46 -0
  55. package/dist/core/group/service.js +160 -0
  56. package/dist/core/group/storage.d.ts +9 -0
  57. package/dist/core/group/storage.js +91 -0
  58. package/dist/core/group/sync.d.ts +21 -0
  59. package/dist/core/group/sync.js +148 -0
  60. package/dist/core/group/types.d.ts +130 -0
  61. package/dist/core/group/types.js +1 -0
  62. package/dist/core/ingestion/binding-accumulator.d.ts +207 -0
  63. package/dist/core/ingestion/binding-accumulator.js +332 -0
  64. package/dist/core/ingestion/call-processor.d.ts +155 -24
  65. package/dist/core/ingestion/call-processor.js +1129 -247
  66. package/dist/core/ingestion/class-extractors/generic.d.ts +2 -0
  67. package/dist/core/ingestion/class-extractors/generic.js +135 -0
  68. package/dist/core/ingestion/class-types.d.ts +34 -0
  69. package/dist/core/ingestion/class-types.js +1 -0
  70. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -0
  71. package/dist/core/ingestion/entry-point-scoring.js +1 -0
  72. package/dist/core/ingestion/field-types.d.ts +2 -2
  73. package/dist/core/ingestion/filesystem-walker.js +8 -0
  74. package/dist/core/ingestion/framework-detection.d.ts +1 -0
  75. package/dist/core/ingestion/framework-detection.js +1 -0
  76. package/dist/core/ingestion/heritage-processor.d.ts +8 -15
  77. package/dist/core/ingestion/heritage-processor.js +15 -28
  78. package/dist/core/ingestion/import-processor.d.ts +1 -11
  79. package/dist/core/ingestion/import-processor.js +0 -12
  80. package/dist/core/ingestion/import-resolvers/utils.js +1 -0
  81. package/dist/core/ingestion/import-resolvers/vue.d.ts +8 -0
  82. package/dist/core/ingestion/import-resolvers/vue.js +9 -0
  83. package/dist/core/ingestion/language-provider.d.ts +6 -3
  84. package/dist/core/ingestion/languages/c-cpp.js +168 -1
  85. package/dist/core/ingestion/languages/csharp.js +20 -0
  86. package/dist/core/ingestion/languages/dart.js +26 -4
  87. package/dist/core/ingestion/languages/go.js +22 -0
  88. package/dist/core/ingestion/languages/index.d.ts +1 -0
  89. package/dist/core/ingestion/languages/index.js +2 -0
  90. package/dist/core/ingestion/languages/java.js +17 -0
  91. package/dist/core/ingestion/languages/kotlin.js +24 -1
  92. package/dist/core/ingestion/languages/php.js +23 -11
  93. package/dist/core/ingestion/languages/python.js +9 -0
  94. package/dist/core/ingestion/languages/ruby.js +28 -0
  95. package/dist/core/ingestion/languages/rust.js +38 -0
  96. package/dist/core/ingestion/languages/swift.js +31 -0
  97. package/dist/core/ingestion/languages/typescript.d.ts +1 -0
  98. package/dist/core/ingestion/languages/typescript.js +52 -3
  99. package/dist/core/ingestion/languages/vue.d.ts +13 -0
  100. package/dist/core/ingestion/languages/vue.js +81 -0
  101. package/dist/core/ingestion/method-extractors/configs/c-cpp.d.ts +3 -0
  102. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
  103. package/dist/core/ingestion/method-extractors/configs/csharp.js +5 -1
  104. package/dist/core/ingestion/method-extractors/configs/dart.d.ts +2 -0
  105. package/dist/core/ingestion/method-extractors/configs/dart.js +376 -0
  106. package/dist/core/ingestion/method-extractors/configs/go.d.ts +2 -0
  107. package/dist/core/ingestion/method-extractors/configs/go.js +176 -0
  108. package/dist/core/ingestion/method-extractors/configs/jvm.js +13 -4
  109. package/dist/core/ingestion/method-extractors/configs/php.d.ts +2 -0
  110. package/dist/core/ingestion/method-extractors/configs/php.js +304 -0
  111. package/dist/core/ingestion/method-extractors/configs/python.d.ts +2 -0
  112. package/dist/core/ingestion/method-extractors/configs/python.js +309 -0
  113. package/dist/core/ingestion/method-extractors/configs/ruby.d.ts +2 -0
  114. package/dist/core/ingestion/method-extractors/configs/ruby.js +285 -0
  115. package/dist/core/ingestion/method-extractors/configs/rust.d.ts +2 -0
  116. package/dist/core/ingestion/method-extractors/configs/rust.js +195 -0
  117. package/dist/core/ingestion/method-extractors/configs/swift.d.ts +2 -0
  118. package/dist/core/ingestion/method-extractors/configs/swift.js +277 -0
  119. package/dist/core/ingestion/method-extractors/configs/typescript-javascript.js +85 -8
  120. package/dist/core/ingestion/method-extractors/generic.js +38 -15
  121. package/dist/core/ingestion/method-types.d.ts +25 -0
  122. package/dist/core/ingestion/model/field-registry.d.ts +18 -0
  123. package/dist/core/ingestion/model/field-registry.js +22 -0
  124. package/dist/core/ingestion/model/heritage-map.d.ts +70 -0
  125. package/dist/core/ingestion/model/heritage-map.js +159 -0
  126. package/dist/core/ingestion/model/index.d.ts +20 -0
  127. package/dist/core/ingestion/model/index.js +41 -0
  128. package/dist/core/ingestion/model/method-registry.d.ts +62 -0
  129. package/dist/core/ingestion/model/method-registry.js +130 -0
  130. package/dist/core/ingestion/model/registration-table.d.ts +139 -0
  131. package/dist/core/ingestion/model/registration-table.js +224 -0
  132. package/dist/core/ingestion/model/resolution-context.d.ts +93 -0
  133. package/dist/core/ingestion/model/resolution-context.js +337 -0
  134. package/dist/core/ingestion/model/resolve.d.ts +56 -0
  135. package/dist/core/ingestion/model/resolve.js +242 -0
  136. package/dist/core/ingestion/model/semantic-model.d.ts +86 -0
  137. package/dist/core/ingestion/model/semantic-model.js +120 -0
  138. package/dist/core/ingestion/model/symbol-table.d.ts +222 -0
  139. package/dist/core/ingestion/model/symbol-table.js +206 -0
  140. package/dist/core/ingestion/model/type-registry.d.ts +39 -0
  141. package/dist/core/ingestion/model/type-registry.js +62 -0
  142. package/dist/core/ingestion/mro-processor.d.ts +4 -3
  143. package/dist/core/ingestion/mro-processor.js +310 -106
  144. package/dist/core/ingestion/parsing-processor.d.ts +5 -4
  145. package/dist/core/ingestion/parsing-processor.js +210 -85
  146. package/dist/core/ingestion/pipeline.d.ts +2 -0
  147. package/dist/core/ingestion/pipeline.js +192 -68
  148. package/dist/core/ingestion/tree-sitter-queries.d.ts +5 -5
  149. package/dist/core/ingestion/tree-sitter-queries.js +21 -0
  150. package/dist/core/ingestion/type-env.d.ts +15 -2
  151. package/dist/core/ingestion/type-env.js +163 -102
  152. package/dist/core/ingestion/type-extractors/csharp.js +17 -0
  153. package/dist/core/ingestion/type-extractors/jvm.js +11 -0
  154. package/dist/core/ingestion/type-extractors/php.js +0 -55
  155. package/dist/core/ingestion/type-extractors/ruby.js +0 -32
  156. package/dist/core/ingestion/type-extractors/swift.js +13 -0
  157. package/dist/core/ingestion/type-extractors/types.d.ts +8 -8
  158. package/dist/core/ingestion/type-extractors/typescript.js +66 -69
  159. package/dist/core/ingestion/utils/ast-helpers.d.ts +33 -43
  160. package/dist/core/ingestion/utils/ast-helpers.js +129 -572
  161. package/dist/core/ingestion/utils/method-props.d.ts +32 -0
  162. package/dist/core/ingestion/utils/method-props.js +147 -0
  163. package/dist/core/ingestion/vue-sfc-extractor.d.ts +44 -0
  164. package/dist/core/ingestion/vue-sfc-extractor.js +94 -0
  165. package/dist/core/ingestion/workers/parse-worker.d.ts +31 -19
  166. package/dist/core/ingestion/workers/parse-worker.js +463 -198
  167. package/dist/core/lbug/lbug-adapter.d.ts +6 -0
  168. package/dist/core/lbug/lbug-adapter.js +68 -3
  169. package/dist/core/lbug/pool-adapter.d.ts +76 -0
  170. package/dist/core/lbug/pool-adapter.js +522 -0
  171. package/dist/core/run-analyze.d.ts +2 -0
  172. package/dist/core/run-analyze.js +1 -1
  173. package/dist/core/search/bm25-index.js +1 -1
  174. package/dist/core/tree-sitter/parser-loader.js +1 -0
  175. package/dist/core/wiki/graph-queries.js +1 -1
  176. package/dist/mcp/core/embedder.js +6 -5
  177. package/dist/mcp/core/lbug-adapter.d.ts +3 -63
  178. package/dist/mcp/core/lbug-adapter.js +3 -484
  179. package/dist/mcp/local/local-backend.d.ts +31 -2
  180. package/dist/mcp/local/local-backend.js +255 -46
  181. package/dist/mcp/resources.js +5 -4
  182. package/dist/mcp/staleness.d.ts +3 -13
  183. package/dist/mcp/staleness.js +2 -31
  184. package/dist/mcp/tools.js +80 -4
  185. package/dist/server/analyze-job.d.ts +2 -0
  186. package/dist/server/analyze-job.js +4 -0
  187. package/dist/server/api.d.ts +20 -1
  188. package/dist/server/api.js +306 -71
  189. package/dist/server/git-clone.d.ts +2 -1
  190. package/dist/server/git-clone.js +98 -5
  191. package/dist/storage/git.d.ts +13 -0
  192. package/dist/storage/git.js +25 -0
  193. package/dist/storage/repo-manager.js +1 -1
  194. package/package.json +8 -2
  195. package/scripts/patch-tree-sitter-swift.cjs +78 -0
  196. package/dist/core/ingestion/named-binding-processor.d.ts +0 -18
  197. package/dist/core/ingestion/named-binding-processor.js +0 -42
  198. package/dist/core/ingestion/resolution-context.d.ts +0 -58
  199. package/dist/core/ingestion/resolution-context.js +0 -135
  200. package/dist/core/ingestion/symbol-table.d.ts +0 -79
  201. 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,3 @@
1
+ import type { GroupConfig } from './types.js';
2
+ export declare function parseGroupConfig(yamlContent: string): GroupConfig;
3
+ export declare function loadGroupConfig(groupDir: string): Promise<GroupConfig>;
@@ -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
+ }