gitnexus 1.5.2 → 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 (207) 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/cli/wiki.js +15 -44
  31. package/dist/config/ignore-service.js +8 -3
  32. package/dist/core/augmentation/engine.js +1 -1
  33. package/dist/core/git-staleness.d.ts +13 -0
  34. package/dist/core/git-staleness.js +29 -0
  35. package/dist/core/group/bridge-db.d.ts +82 -0
  36. package/dist/core/group/bridge-db.js +460 -0
  37. package/dist/core/group/bridge-schema.d.ts +27 -0
  38. package/dist/core/group/bridge-schema.js +55 -0
  39. package/dist/core/group/config-parser.d.ts +3 -0
  40. package/dist/core/group/config-parser.js +83 -0
  41. package/dist/core/group/contract-extractor.d.ts +7 -0
  42. package/dist/core/group/contract-extractor.js +1 -0
  43. package/dist/core/group/extractors/grpc-extractor.d.ts +16 -0
  44. package/dist/core/group/extractors/grpc-extractor.js +264 -0
  45. package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
  46. package/dist/core/group/extractors/http-route-extractor.js +428 -0
  47. package/dist/core/group/extractors/topic-extractor.d.ts +9 -0
  48. package/dist/core/group/extractors/topic-extractor.js +234 -0
  49. package/dist/core/group/matching.d.ts +13 -0
  50. package/dist/core/group/matching.js +198 -0
  51. package/dist/core/group/normalization.d.ts +3 -0
  52. package/dist/core/group/normalization.js +115 -0
  53. package/dist/core/group/service-boundary-detector.d.ts +8 -0
  54. package/dist/core/group/service-boundary-detector.js +155 -0
  55. package/dist/core/group/service.d.ts +46 -0
  56. package/dist/core/group/service.js +160 -0
  57. package/dist/core/group/storage.d.ts +9 -0
  58. package/dist/core/group/storage.js +91 -0
  59. package/dist/core/group/sync.d.ts +21 -0
  60. package/dist/core/group/sync.js +148 -0
  61. package/dist/core/group/types.d.ts +130 -0
  62. package/dist/core/group/types.js +1 -0
  63. package/dist/core/ingestion/binding-accumulator.d.ts +207 -0
  64. package/dist/core/ingestion/binding-accumulator.js +332 -0
  65. package/dist/core/ingestion/call-processor.d.ts +155 -24
  66. package/dist/core/ingestion/call-processor.js +1129 -247
  67. package/dist/core/ingestion/class-extractors/generic.d.ts +2 -0
  68. package/dist/core/ingestion/class-extractors/generic.js +135 -0
  69. package/dist/core/ingestion/class-types.d.ts +34 -0
  70. package/dist/core/ingestion/class-types.js +1 -0
  71. package/dist/core/ingestion/entry-point-scoring.d.ts +1 -0
  72. package/dist/core/ingestion/entry-point-scoring.js +1 -0
  73. package/dist/core/ingestion/field-extractors/configs/helpers.d.ts +5 -1
  74. package/dist/core/ingestion/field-extractors/configs/helpers.js +13 -3
  75. package/dist/core/ingestion/field-types.d.ts +2 -2
  76. package/dist/core/ingestion/filesystem-walker.js +8 -0
  77. package/dist/core/ingestion/framework-detection.d.ts +1 -0
  78. package/dist/core/ingestion/framework-detection.js +1 -0
  79. package/dist/core/ingestion/heritage-processor.d.ts +8 -15
  80. package/dist/core/ingestion/heritage-processor.js +15 -28
  81. package/dist/core/ingestion/import-processor.d.ts +1 -11
  82. package/dist/core/ingestion/import-processor.js +0 -12
  83. package/dist/core/ingestion/import-resolvers/utils.js +1 -0
  84. package/dist/core/ingestion/import-resolvers/vue.d.ts +8 -0
  85. package/dist/core/ingestion/import-resolvers/vue.js +9 -0
  86. package/dist/core/ingestion/language-provider.d.ts +6 -3
  87. package/dist/core/ingestion/languages/c-cpp.js +168 -1
  88. package/dist/core/ingestion/languages/csharp.js +20 -0
  89. package/dist/core/ingestion/languages/dart.js +26 -4
  90. package/dist/core/ingestion/languages/go.js +22 -0
  91. package/dist/core/ingestion/languages/index.d.ts +1 -0
  92. package/dist/core/ingestion/languages/index.js +2 -0
  93. package/dist/core/ingestion/languages/java.js +17 -0
  94. package/dist/core/ingestion/languages/kotlin.js +24 -1
  95. package/dist/core/ingestion/languages/php.js +23 -11
  96. package/dist/core/ingestion/languages/python.js +9 -0
  97. package/dist/core/ingestion/languages/ruby.js +28 -0
  98. package/dist/core/ingestion/languages/rust.js +38 -0
  99. package/dist/core/ingestion/languages/swift.js +31 -0
  100. package/dist/core/ingestion/languages/typescript.d.ts +1 -0
  101. package/dist/core/ingestion/languages/typescript.js +54 -1
  102. package/dist/core/ingestion/languages/vue.d.ts +13 -0
  103. package/dist/core/ingestion/languages/vue.js +81 -0
  104. package/dist/core/ingestion/method-extractors/configs/c-cpp.d.ts +3 -0
  105. package/dist/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
  106. package/dist/core/ingestion/method-extractors/configs/csharp.js +5 -1
  107. package/dist/core/ingestion/method-extractors/configs/dart.d.ts +2 -0
  108. package/dist/core/ingestion/method-extractors/configs/dart.js +376 -0
  109. package/dist/core/ingestion/method-extractors/configs/go.d.ts +2 -0
  110. package/dist/core/ingestion/method-extractors/configs/go.js +176 -0
  111. package/dist/core/ingestion/method-extractors/configs/jvm.js +13 -4
  112. package/dist/core/ingestion/method-extractors/configs/php.d.ts +2 -0
  113. package/dist/core/ingestion/method-extractors/configs/php.js +304 -0
  114. package/dist/core/ingestion/method-extractors/configs/python.d.ts +2 -0
  115. package/dist/core/ingestion/method-extractors/configs/python.js +309 -0
  116. package/dist/core/ingestion/method-extractors/configs/ruby.d.ts +2 -0
  117. package/dist/core/ingestion/method-extractors/configs/ruby.js +285 -0
  118. package/dist/core/ingestion/method-extractors/configs/rust.d.ts +2 -0
  119. package/dist/core/ingestion/method-extractors/configs/rust.js +195 -0
  120. package/dist/core/ingestion/method-extractors/configs/swift.d.ts +2 -0
  121. package/dist/core/ingestion/method-extractors/configs/swift.js +277 -0
  122. package/dist/core/ingestion/method-extractors/configs/typescript-javascript.d.ts +3 -0
  123. package/dist/core/ingestion/method-extractors/configs/typescript-javascript.js +338 -0
  124. package/dist/core/ingestion/method-extractors/generic.js +38 -15
  125. package/dist/core/ingestion/method-types.d.ts +25 -0
  126. package/dist/core/ingestion/model/field-registry.d.ts +18 -0
  127. package/dist/core/ingestion/model/field-registry.js +22 -0
  128. package/dist/core/ingestion/model/heritage-map.d.ts +70 -0
  129. package/dist/core/ingestion/model/heritage-map.js +159 -0
  130. package/dist/core/ingestion/model/index.d.ts +20 -0
  131. package/dist/core/ingestion/model/index.js +41 -0
  132. package/dist/core/ingestion/model/method-registry.d.ts +62 -0
  133. package/dist/core/ingestion/model/method-registry.js +130 -0
  134. package/dist/core/ingestion/model/registration-table.d.ts +139 -0
  135. package/dist/core/ingestion/model/registration-table.js +224 -0
  136. package/dist/core/ingestion/model/resolution-context.d.ts +93 -0
  137. package/dist/core/ingestion/model/resolution-context.js +337 -0
  138. package/dist/core/ingestion/model/resolve.d.ts +56 -0
  139. package/dist/core/ingestion/model/resolve.js +242 -0
  140. package/dist/core/ingestion/model/semantic-model.d.ts +86 -0
  141. package/dist/core/ingestion/model/semantic-model.js +120 -0
  142. package/dist/core/ingestion/model/symbol-table.d.ts +222 -0
  143. package/dist/core/ingestion/model/symbol-table.js +206 -0
  144. package/dist/core/ingestion/model/type-registry.d.ts +39 -0
  145. package/dist/core/ingestion/model/type-registry.js +62 -0
  146. package/dist/core/ingestion/mro-processor.d.ts +4 -3
  147. package/dist/core/ingestion/mro-processor.js +310 -106
  148. package/dist/core/ingestion/parsing-processor.d.ts +5 -4
  149. package/dist/core/ingestion/parsing-processor.js +210 -85
  150. package/dist/core/ingestion/pipeline.d.ts +2 -0
  151. package/dist/core/ingestion/pipeline.js +192 -68
  152. package/dist/core/ingestion/tree-sitter-queries.d.ts +6 -6
  153. package/dist/core/ingestion/tree-sitter-queries.js +37 -0
  154. package/dist/core/ingestion/type-env.d.ts +15 -2
  155. package/dist/core/ingestion/type-env.js +163 -102
  156. package/dist/core/ingestion/type-extractors/csharp.js +17 -0
  157. package/dist/core/ingestion/type-extractors/jvm.js +11 -0
  158. package/dist/core/ingestion/type-extractors/php.js +0 -55
  159. package/dist/core/ingestion/type-extractors/ruby.js +0 -32
  160. package/dist/core/ingestion/type-extractors/swift.js +13 -0
  161. package/dist/core/ingestion/type-extractors/types.d.ts +8 -8
  162. package/dist/core/ingestion/type-extractors/typescript.js +66 -69
  163. package/dist/core/ingestion/utils/ast-helpers.d.ts +33 -43
  164. package/dist/core/ingestion/utils/ast-helpers.js +129 -565
  165. package/dist/core/ingestion/utils/method-props.d.ts +32 -0
  166. package/dist/core/ingestion/utils/method-props.js +147 -0
  167. package/dist/core/ingestion/vue-sfc-extractor.d.ts +44 -0
  168. package/dist/core/ingestion/vue-sfc-extractor.js +94 -0
  169. package/dist/core/ingestion/workers/parse-worker.d.ts +31 -19
  170. package/dist/core/ingestion/workers/parse-worker.js +463 -198
  171. package/dist/core/lbug/lbug-adapter.d.ts +6 -0
  172. package/dist/core/lbug/lbug-adapter.js +68 -3
  173. package/dist/core/lbug/pool-adapter.d.ts +76 -0
  174. package/dist/core/lbug/pool-adapter.js +522 -0
  175. package/dist/core/run-analyze.d.ts +2 -0
  176. package/dist/core/run-analyze.js +1 -1
  177. package/dist/core/search/bm25-index.js +1 -1
  178. package/dist/core/tree-sitter/parser-loader.js +1 -0
  179. package/dist/core/wiki/graph-queries.js +1 -1
  180. package/dist/core/wiki/html-viewer.js +6 -4
  181. package/dist/core/wiki/llm-client.js +4 -6
  182. package/dist/mcp/core/embedder.js +6 -5
  183. package/dist/mcp/core/lbug-adapter.d.ts +3 -63
  184. package/dist/mcp/core/lbug-adapter.js +3 -484
  185. package/dist/mcp/local/local-backend.d.ts +31 -2
  186. package/dist/mcp/local/local-backend.js +255 -46
  187. package/dist/mcp/resources.js +5 -4
  188. package/dist/mcp/staleness.d.ts +3 -13
  189. package/dist/mcp/staleness.js +2 -31
  190. package/dist/mcp/tools.js +80 -4
  191. package/dist/server/analyze-job.d.ts +2 -0
  192. package/dist/server/analyze-job.js +4 -0
  193. package/dist/server/api.d.ts +20 -1
  194. package/dist/server/api.js +306 -71
  195. package/dist/server/git-clone.d.ts +2 -1
  196. package/dist/server/git-clone.js +98 -5
  197. package/dist/storage/git.d.ts +13 -0
  198. package/dist/storage/git.js +25 -0
  199. package/dist/storage/repo-manager.js +1 -1
  200. package/package.json +8 -2
  201. package/scripts/patch-tree-sitter-swift.cjs +78 -0
  202. package/dist/core/ingestion/named-binding-processor.d.ts +0 -18
  203. package/dist/core/ingestion/named-binding-processor.js +0 -42
  204. package/dist/core/ingestion/resolution-context.d.ts +0 -58
  205. package/dist/core/ingestion/resolution-context.js +0 -135
  206. package/dist/core/ingestion/symbol-table.d.ts +0 -79
  207. package/dist/core/ingestion/symbol-table.js +0 -115
@@ -0,0 +1,9 @@
1
+ import type { ContractRegistry } from './types.js';
2
+ export declare function getDefaultGitnexusDir(): string;
3
+ export declare function getGroupsBaseDir(gitnexusDir?: string): string;
4
+ export declare function validateGroupName(name: string): void;
5
+ export declare function getGroupDir(gitnexusDir: string, groupName: string): string;
6
+ export declare function writeContractRegistry(groupDir: string, registry: ContractRegistry): Promise<void>;
7
+ export declare function readContractRegistry(groupDir: string): Promise<ContractRegistry | null>;
8
+ export declare function listGroups(gitnexusDir?: string): Promise<string[]>;
9
+ export declare function createGroupDir(gitnexusDir: string, groupName: string, force?: boolean): Promise<string>;
@@ -0,0 +1,91 @@
1
+ import * as fs from 'node:fs';
2
+ import * as fsp from 'node:fs/promises';
3
+ import * as path from 'node:path';
4
+ import * as os from 'node:os';
5
+ const CONTRACTS_FILE = 'contracts.json';
6
+ export function getDefaultGitnexusDir() {
7
+ return process.env.GITNEXUS_HOME || path.join(os.homedir(), '.gitnexus');
8
+ }
9
+ export function getGroupsBaseDir(gitnexusDir) {
10
+ return path.join(gitnexusDir || getDefaultGitnexusDir(), 'groups');
11
+ }
12
+ const GROUP_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9_-]*$/;
13
+ export function validateGroupName(name) {
14
+ if (!GROUP_NAME_RE.test(name)) {
15
+ throw new Error(`Invalid group name "${name}". Names must start with a letter or digit and contain only [a-zA-Z0-9_-].`);
16
+ }
17
+ }
18
+ export function getGroupDir(gitnexusDir, groupName) {
19
+ validateGroupName(groupName);
20
+ return path.join(gitnexusDir, 'groups', groupName);
21
+ }
22
+ export async function writeContractRegistry(groupDir, registry) {
23
+ const targetPath = path.join(groupDir, CONTRACTS_FILE);
24
+ const tmpPath = `${targetPath}.tmp.${Date.now()}`;
25
+ await fsp.writeFile(tmpPath, JSON.stringify(registry, null, 2), 'utf-8');
26
+ await fsp.rename(tmpPath, targetPath);
27
+ }
28
+ export async function readContractRegistry(groupDir) {
29
+ const filePath = path.join(groupDir, CONTRACTS_FILE);
30
+ try {
31
+ const content = await fsp.readFile(filePath, 'utf-8');
32
+ return JSON.parse(content);
33
+ }
34
+ catch (err) {
35
+ if (err.code === 'ENOENT')
36
+ return null;
37
+ throw err;
38
+ }
39
+ }
40
+ export async function listGroups(gitnexusDir) {
41
+ const groupsDir = getGroupsBaseDir(gitnexusDir);
42
+ try {
43
+ const entries = await fsp.readdir(groupsDir, { withFileTypes: true });
44
+ const names = [];
45
+ for (const entry of entries) {
46
+ if (entry.isDirectory()) {
47
+ const yamlPath = path.join(groupsDir, entry.name, 'group.yaml');
48
+ if (fs.existsSync(yamlPath)) {
49
+ names.push(entry.name);
50
+ }
51
+ }
52
+ }
53
+ return names;
54
+ }
55
+ catch (err) {
56
+ if (err.code === 'ENOENT')
57
+ return [];
58
+ throw err;
59
+ }
60
+ }
61
+ export async function createGroupDir(gitnexusDir, groupName, force = false) {
62
+ const groupDir = getGroupDir(gitnexusDir, groupName);
63
+ if (fs.existsSync(path.join(groupDir, 'group.yaml')) && !force) {
64
+ throw new Error(`Group "${groupName}" already exists. Use --force to overwrite.`);
65
+ }
66
+ await fsp.mkdir(groupDir, { recursive: true });
67
+ const template = `version: 1
68
+ name: ${groupName}
69
+ description: ""
70
+
71
+ repos: {}
72
+
73
+ links: []
74
+
75
+ packages: {}
76
+
77
+ detect:
78
+ http: true
79
+ grpc: true
80
+ topics: true
81
+ shared_libs: true
82
+ embedding_fallback: true
83
+
84
+ matching:
85
+ bm25_threshold: 0.7
86
+ embedding_threshold: 0.65
87
+ max_candidates_per_step: 3
88
+ `;
89
+ await fsp.writeFile(path.join(groupDir, 'group.yaml'), template, 'utf-8');
90
+ return groupDir;
91
+ }
@@ -0,0 +1,21 @@
1
+ import { type RegistryEntry } from '../../storage/repo-manager.js';
2
+ import type { GroupConfig, RepoHandle, RepoSnapshot, StoredContract, CrossLink } from './types.js';
3
+ export interface SyncOptions {
4
+ extractorOverride?: ((repo: RepoHandle) => Promise<StoredContract[]>) | (() => Promise<StoredContract[]>);
5
+ resolveRepoHandle?: (registryName: string, groupPath: string) => Promise<RepoHandle | null>;
6
+ skipWrite?: boolean;
7
+ groupDir?: string;
8
+ allowStale?: boolean;
9
+ verbose?: boolean;
10
+ exactOnly?: boolean;
11
+ skipEmbeddings?: boolean;
12
+ }
13
+ export interface SyncResult {
14
+ contracts: StoredContract[];
15
+ crossLinks: CrossLink[];
16
+ unmatched: StoredContract[];
17
+ missingRepos: string[];
18
+ repoSnapshots: Record<string, RepoSnapshot>;
19
+ }
20
+ export declare function stableRepoPoolId(entry: RegistryEntry, allEntries: RegistryEntry[]): string;
21
+ export declare function syncGroup(config: GroupConfig, opts?: SyncOptions): Promise<SyncResult>;
@@ -0,0 +1,148 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { Buffer } from 'node:buffer';
4
+ import { initLbug, closeLbug, executeParameterized } from '../lbug/pool-adapter.js';
5
+ import { readRegistry } from '../../storage/repo-manager.js';
6
+ import { HttpRouteExtractor } from './extractors/http-route-extractor.js';
7
+ import { GrpcExtractor } from './extractors/grpc-extractor.js';
8
+ import { TopicExtractor } from './extractors/topic-extractor.js';
9
+ import { runExactMatch } from './matching.js';
10
+ import { detectServiceBoundaries, assignService } from './service-boundary-detector.js';
11
+ import { writeContractRegistry } from './storage.js';
12
+ export function stableRepoPoolId(entry, allEntries) {
13
+ const base = entry.name.toLowerCase();
14
+ const resolved = path.resolve(entry.path);
15
+ for (const other of allEntries) {
16
+ if (other.name.toLowerCase() === base && path.resolve(other.path) !== resolved) {
17
+ const hash = Buffer.from(entry.path).toString('base64url').slice(0, 6);
18
+ return `${base}-${hash}`;
19
+ }
20
+ }
21
+ return base;
22
+ }
23
+ function defaultResolveHandle(allEntries) {
24
+ return async (registryName, groupPath) => {
25
+ const e = allEntries.find((en) => en.name === registryName);
26
+ if (!e)
27
+ return null;
28
+ const poolId = stableRepoPoolId(e, allEntries);
29
+ return {
30
+ id: poolId,
31
+ path: groupPath,
32
+ repoPath: e.path,
33
+ storagePath: e.storagePath,
34
+ };
35
+ };
36
+ }
37
+ export async function syncGroup(config, opts) {
38
+ const missingRepos = [];
39
+ const repoSnapshots = {};
40
+ let autoContracts = [];
41
+ let dbExecutors;
42
+ const eo = opts?.extractorOverride;
43
+ if (eo && eo.length === 0) {
44
+ autoContracts = await eo();
45
+ }
46
+ else {
47
+ const entries = await readRegistry();
48
+ const resolve = opts?.resolveRepoHandle ?? defaultResolveHandle(entries);
49
+ const httpEx = new HttpRouteExtractor();
50
+ const grpcEx = new GrpcExtractor();
51
+ const topicEx = new TopicExtractor();
52
+ dbExecutors = new Map();
53
+ const openPoolIds = [];
54
+ try {
55
+ for (const [groupPath, regName] of Object.entries(config.repos)) {
56
+ const handle = await resolve(regName, groupPath);
57
+ if (!handle) {
58
+ missingRepos.push(groupPath);
59
+ continue;
60
+ }
61
+ const poolId = handle.id;
62
+ const lbugPath = path.join(handle.storagePath, 'lbug');
63
+ try {
64
+ await initLbug(poolId, lbugPath);
65
+ openPoolIds.push(poolId);
66
+ const executor = (query, params) => executeParameterized(poolId, query, params ?? {});
67
+ dbExecutors.set(groupPath, executor);
68
+ const boundaries = await detectServiceBoundaries(handle.repoPath);
69
+ if (config.detect.http) {
70
+ const extracted = await httpEx.extract(executor, handle.repoPath, handle);
71
+ for (const c of extracted) {
72
+ autoContracts.push({
73
+ ...c,
74
+ repo: groupPath,
75
+ service: assignService(c.symbolRef.filePath, boundaries),
76
+ });
77
+ }
78
+ }
79
+ if (config.detect.grpc) {
80
+ const extracted = await grpcEx.extract(executor, handle.repoPath, handle);
81
+ for (const c of extracted) {
82
+ autoContracts.push({
83
+ ...c,
84
+ repo: groupPath,
85
+ service: assignService(c.symbolRef.filePath, boundaries),
86
+ });
87
+ }
88
+ }
89
+ if (config.detect.topics) {
90
+ const extracted = await topicEx.extract(executor, handle.repoPath, handle);
91
+ for (const c of extracted) {
92
+ autoContracts.push({
93
+ ...c,
94
+ repo: groupPath,
95
+ service: assignService(c.symbolRef.filePath, boundaries),
96
+ });
97
+ }
98
+ }
99
+ const metaPath = path.join(handle.storagePath, 'meta.json');
100
+ try {
101
+ const raw = await fs.readFile(metaPath, 'utf-8');
102
+ const m = JSON.parse(raw);
103
+ repoSnapshots[groupPath] = {
104
+ indexedAt: m.indexedAt || '',
105
+ lastCommit: m.lastCommit || '',
106
+ };
107
+ }
108
+ catch {
109
+ const e = entries.find((en) => en.name === regName);
110
+ repoSnapshots[groupPath] = {
111
+ indexedAt: e?.indexedAt || '',
112
+ lastCommit: e?.lastCommit || '',
113
+ };
114
+ }
115
+ }
116
+ catch {
117
+ missingRepos.push(groupPath);
118
+ }
119
+ }
120
+ }
121
+ finally {
122
+ for (const id of [...new Set(openPoolIds)]) {
123
+ await closeLbug(id).catch(() => { });
124
+ }
125
+ }
126
+ }
127
+ const { matched, unmatched } = runExactMatch(autoContracts);
128
+ const crossLinks = matched;
129
+ const allContracts = autoContracts;
130
+ const registry = {
131
+ version: 1,
132
+ generatedAt: new Date().toISOString(),
133
+ repoSnapshots,
134
+ missingRepos,
135
+ contracts: allContracts,
136
+ crossLinks,
137
+ };
138
+ if (opts?.groupDir && !opts.skipWrite) {
139
+ await writeContractRegistry(opts.groupDir, registry);
140
+ }
141
+ return {
142
+ contracts: allContracts,
143
+ crossLinks,
144
+ unmatched,
145
+ missingRepos,
146
+ repoSnapshots,
147
+ };
148
+ }
@@ -0,0 +1,130 @@
1
+ export type ContractType = 'http' | 'grpc' | 'topic' | 'lib' | 'custom';
2
+ export type MatchType = 'exact' | 'manifest' | 'wildcard' | 'bm25' | 'embedding';
3
+ export type ContractRole = 'provider' | 'consumer';
4
+ export interface GroupConfig {
5
+ version: number;
6
+ name: string;
7
+ description: string;
8
+ repos: Record<string, string>;
9
+ links: GroupManifestLink[];
10
+ packages: Record<string, Record<string, string>>;
11
+ detect: DetectConfig;
12
+ matching: MatchingConfig;
13
+ }
14
+ export interface GroupManifestLink {
15
+ from: string;
16
+ to: string;
17
+ type: ContractType;
18
+ contract: string;
19
+ role: ContractRole;
20
+ }
21
+ export interface DetectConfig {
22
+ http: boolean;
23
+ grpc: boolean;
24
+ topics: boolean;
25
+ shared_libs: boolean;
26
+ embedding_fallback: boolean;
27
+ }
28
+ export interface MatchingConfig {
29
+ bm25_threshold: number;
30
+ embedding_threshold: number;
31
+ max_candidates_per_step: number;
32
+ }
33
+ export interface SymbolRef {
34
+ filePath: string;
35
+ name: string;
36
+ }
37
+ export interface ExtractedContract {
38
+ contractId: string;
39
+ type: ContractType;
40
+ role: ContractRole;
41
+ symbolUid: string;
42
+ symbolRef: SymbolRef;
43
+ symbolName: string;
44
+ confidence: number;
45
+ meta: Record<string, unknown>;
46
+ /** Service boundary within a monorepo (relative path from repo root, e.g. "services/auth"). */
47
+ service?: string;
48
+ }
49
+ export interface CrossLinkEndpoint {
50
+ repo: string;
51
+ /** Service boundary within a monorepo (relative path from repo root). */
52
+ service?: string;
53
+ symbolUid: string;
54
+ symbolRef: SymbolRef;
55
+ }
56
+ export interface CrossLink {
57
+ from: CrossLinkEndpoint;
58
+ to: CrossLinkEndpoint;
59
+ type: ContractType;
60
+ contractId: string;
61
+ matchType: MatchType;
62
+ confidence: number;
63
+ }
64
+ export interface RepoSnapshot {
65
+ indexedAt: string;
66
+ lastCommit: string;
67
+ }
68
+ export interface ContractRegistry {
69
+ version: number;
70
+ generatedAt: string;
71
+ repoSnapshots: Record<string, RepoSnapshot>;
72
+ missingRepos: string[];
73
+ contracts: StoredContract[];
74
+ crossLinks: CrossLink[];
75
+ }
76
+ export interface StoredContract extends ExtractedContract {
77
+ repo: string;
78
+ }
79
+ /** Repo within a group (group path + paths; name collision with MCP RepoHandle — import from group/types only). */
80
+ export interface RepoHandle {
81
+ id: string;
82
+ path: string;
83
+ repoPath: string;
84
+ storagePath: string;
85
+ }
86
+ export interface GroupImpactResult {
87
+ local: unknown;
88
+ group: string;
89
+ cross: CrossRepoImpact[];
90
+ outOfScope: OutOfScopeLink[];
91
+ truncated: boolean;
92
+ truncatedRepos: string[];
93
+ summary: {
94
+ direct: number;
95
+ processes_affected: number;
96
+ modules_affected: number;
97
+ cross_repo_hits: number;
98
+ };
99
+ risk: string;
100
+ }
101
+ export interface CrossRepoImpact {
102
+ repo: string;
103
+ repo_path: string;
104
+ contract: {
105
+ id: string;
106
+ type: ContractType;
107
+ match_type: MatchType;
108
+ confidence: number;
109
+ };
110
+ by_depth: Record<string, unknown[]>;
111
+ affected_processes: string[];
112
+ }
113
+ export interface OutOfScopeLink {
114
+ from: string;
115
+ to: string;
116
+ contractId: string;
117
+ confidence: number;
118
+ }
119
+ /** Opaque handle to an open bridge LadybugDB. */
120
+ export interface BridgeHandle {
121
+ /** Internal — do not access directly. */
122
+ readonly _db: unknown;
123
+ readonly _conn: unknown;
124
+ readonly groupDir: string;
125
+ }
126
+ export interface BridgeMeta {
127
+ version: number;
128
+ generatedAt: string;
129
+ missingRepos: string[];
130
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,207 @@
1
+ /**
2
+ * BindingAccumulator — read-append-only accumulator that collects TypeEnv
3
+ * bindings across files in the GitNexus analyzer pipeline.
4
+ *
5
+ * **Current behavior (both execution paths):** The accumulator carries only
6
+ * file-scope (`scope = ''`) entries. Function-scope bindings are stripped
7
+ * at both write sites:
8
+ *
9
+ * - **Worker path**: `parse-worker.ts` serializes only
10
+ * `typeEnv.fileScope()` entries across the IPC boundary.
11
+ * - **Sequential path**: `type-env.ts::flush()` iterates only the FILE_SCOPE
12
+ * entry of the env map and writes `BindingEntry` records with
13
+ * `scope: ''` hardcoded.
14
+ *
15
+ * The narrowing exists because function-scope bindings have zero downstream
16
+ * consumers today and were previously costing ~4.9 MB of heap + IPC on
17
+ * every pipeline run. See `type-env.ts::flush()` and the `FileScopeBindings`
18
+ * JSDoc in `parse-worker.ts` for the paired Phase 9 reversion checklist.
19
+ *
20
+ * **Historical quality asymmetry (Phase 9 consideration):** Even though
21
+ * both paths now carry only file-scope data, the two paths were built
22
+ * under different resolution capabilities, and a future Phase 9 reverter
23
+ * that widens them back to all scopes will inherit that asymmetry:
24
+ *
25
+ * - **Sequential path** had (and would regain) access to the full
26
+ * `SymbolTable` and `importedBindings`, so its bindings benefit from
27
+ * Tier 2 cross-file propagation.
28
+ * - **Worker path** runs without `SymbolTable` / `importedBindings` and
29
+ * can only produce Tier 0 (annotation-declared) and local Tier 1
30
+ * (same-file constructor inference) bindings.
31
+ *
32
+ * Phase 9 consumers that trust every entry equally will silently produce
33
+ * worse results for large repos (worker-dominant) than small ones
34
+ * (sequential-dominant). If Phase 9 needs homogeneous quality, either
35
+ * (a) tag entries with their tier at insert time so consumers can filter,
36
+ * or (b) post-process worker-path entries through a follow-up resolution
37
+ * pass after the main-thread `SymbolTable` is complete.
38
+ *
39
+ * **Lifecycle contract**: `append → finalize → consume → dispose`. See
40
+ * `finalize()` and `dispose()` for the state machine. Disposal is
41
+ * orthogonal to finalization: either order is legal.
42
+ */
43
+ export interface BindingEntry {
44
+ readonly scope: string;
45
+ readonly varName: string;
46
+ readonly typeName: string;
47
+ }
48
+ /**
49
+ * Minimal graph-node shape required by `enrichExportedTypeMap()`. Intentionally
50
+ * narrower than the full `GraphNode` type in `graph/types.ts` so tests can
51
+ * construct a minimal mock without depending on the full graph module, and
52
+ * so the enrichment logic is a pure function over this contract.
53
+ *
54
+ * Matches the shape of the real `KnowledgeGraph` node's `properties.isExported`
55
+ * access path — tests that use a different shape silently pass while
56
+ * production fails.
57
+ */
58
+ export interface EnrichmentGraphNode {
59
+ readonly id: string;
60
+ readonly properties?: {
61
+ readonly isExported?: boolean;
62
+ } | undefined;
63
+ }
64
+ /**
65
+ * Minimal graph lookup interface used by `enrichExportedTypeMap()`.
66
+ * Consumes only the method the enrichment loop actually calls.
67
+ */
68
+ export interface EnrichmentGraphLookup {
69
+ getNode(id: string): EnrichmentGraphNode | undefined;
70
+ }
71
+ /**
72
+ * Merge file-scope bindings from a (finalized) `BindingAccumulator` into an
73
+ * `exportedTypeMap` for symbols whose graph nodes are marked as exported.
74
+ *
75
+ * This is the single source of truth for the worker-path ExportedTypeMap
76
+ * enrichment loop. Previously the logic lived inline in `pipeline.ts` and
77
+ * the test suite reimplemented it as a `runEnrichmentLoop` helper — a
78
+ * drift-prone pattern that meant tests could pass while production regressed.
79
+ * Extracting it here makes the production code call the same function the
80
+ * tests call.
81
+ *
82
+ * **Node ID candidate order**: `Function:{filePath}:{name}` →
83
+ * `Variable:{filePath}:{name}` → `Const:{filePath}:{name}`. First match wins.
84
+ *
85
+ * **Tier 0 priority**: if `exportedTypeMap` already has an entry for a
86
+ * `(filePath, name)` pair, the accumulator entry does NOT overwrite it —
87
+ * the SymbolTable tier-0 pass is authoritative. Without this guard, a
88
+ * worker-path binding could clobber a higher-quality type from SymbolTable.
89
+ *
90
+ * **Finalize precondition**: the accumulator should be finalized before
91
+ * calling this function. The lifecycle contract is
92
+ * `append → finalize → enrich → dispose`. Finalization is not asserted
93
+ * here (the test suite and pipeline both honor it separately), but any
94
+ * append happening concurrently with this enrichment would be a lifecycle
95
+ * bug at the caller level.
96
+ *
97
+ * @returns The number of new entries written into `exportedTypeMap`
98
+ * (0 on empty accumulator or when every candidate was filtered
99
+ * out by the export check or the Tier 0 guard).
100
+ */
101
+ export declare function enrichExportedTypeMap(bindingAccumulator: BindingAccumulator, graph: EnrichmentGraphLookup, exportedTypeMap: Map<string, Map<string, string>>): number;
102
+ export declare class BindingAccumulator {
103
+ private readonly _allByFile;
104
+ private readonly _fileScopeByFile;
105
+ private _totalBindings;
106
+ private _finalized;
107
+ private _disposed;
108
+ /**
109
+ * Append bindings for a file. Safe to call multiple times for the same file.
110
+ * Throws if the accumulator has been finalized. Skips if entries is empty.
111
+ *
112
+ * The `entries` parameter is `readonly` — this method never mutates the
113
+ * caller's array. Internally, the first `appendFile` call per filePath
114
+ * makes a defensive copy (`slice()`), and subsequent calls push into the
115
+ * accumulator's own storage.
116
+ */
117
+ appendFile(filePath: string, entries: readonly BindingEntry[]): void;
118
+ /** Lock the accumulator — no further appends. Idempotent. */
119
+ finalize(): void;
120
+ /**
121
+ * Release the accumulator's heap footprint. Clears both internal storage
122
+ * maps and resets `_totalBindings` to zero. Idempotent and orthogonal to
123
+ * `finalize()` — calling `dispose()` does not change the finalized state.
124
+ *
125
+ * Post-dispose contract: all read methods return empty/undefined state
126
+ * matching a never-appended-to accumulator. Specifically:
127
+ * - `fileCount === 0`
128
+ * - `totalBindings === 0`
129
+ * - `files()` yields an empty iterator
130
+ * - `getFile(x)` returns `undefined` for all `x`
131
+ * - `fileScopeEntries(x)` returns `[]` for all `x`
132
+ * - `estimateMemoryBytes()` returns `0`
133
+ *
134
+ * If `dispose()` is called **before** `finalize()`, subsequent `appendFile`
135
+ * calls succeed — the accumulator behaves like a fresh one. If called
136
+ * **after** `finalize()`, subsequent `appendFile` calls throw the existing
137
+ * "finalized" error.
138
+ *
139
+ * Lifecycle note: the pipeline disposes the accumulator after both Phase 9
140
+ * consumers (`processCallsFromExtracted`, `processAssignmentsFromExtracted`)
141
+ * and the ExportedTypeMap enrichment loop have completed, so the heap is
142
+ * released before Phase 14 (`runCrossFileBindingPropagation`) and
143
+ * `runGraphAnalysisPhases` begin their long-running work.
144
+ */
145
+ dispose(): void;
146
+ /** Get all bindings for a file, or undefined if the file is unknown. */
147
+ getFile(filePath: string): readonly BindingEntry[] | undefined;
148
+ /**
149
+ * Get only scope='' (file-level) entries as [varName, typeName] tuples.
150
+ * For iteration-based consumers (e.g., `enrichExportedTypeMap`).
151
+ * Returns an empty array for an unknown file.
152
+ *
153
+ * O(1) map lookup + O(n_file_scope) tuple reconstruction from the inner
154
+ * Map. Does NOT walk function-scope entries.
155
+ *
156
+ * For point-lookup consumers (e.g., Phase 9 fallback), prefer
157
+ * `fileScopeGet(filePath, name)` — O(1) with no allocation.
158
+ */
159
+ fileScopeEntries(filePath: string): readonly (readonly [string, string])[];
160
+ /**
161
+ * O(1) point-lookup for a single file-scope binding by (filePath, name).
162
+ * Returns the typeName if found, `undefined` otherwise.
163
+ *
164
+ * This is the preferred lookup path for Phase 9 consumers that resolve
165
+ * a single callee's return type — avoids the O(n_file_scope) iteration
166
+ * and defensive-copy allocation of `fileScopeEntries()`.
167
+ */
168
+ fileScopeGet(filePath: string, name: string): string | undefined;
169
+ /** Iterate over all file paths in insertion order. */
170
+ files(): IterableIterator<string>;
171
+ /** Number of distinct files with at least one binding. */
172
+ get fileCount(): number;
173
+ /** Total number of binding entries across all files. */
174
+ get totalBindings(): number;
175
+ /** Whether the accumulator has been finalized. */
176
+ get finalized(): boolean;
177
+ /**
178
+ * Whether the accumulator has been disposed. Exposed for symmetry with
179
+ * `finalized` so debug tooling and future Phase 9 consumers can detect a
180
+ * disposed accumulator without inspecting empty state heuristically.
181
+ *
182
+ * Disposal and finalization are orthogonal: a disposed accumulator may or
183
+ * may not be finalized, and vice versa. See `dispose()` for the full
184
+ * lifecycle contract.
185
+ */
186
+ get disposed(): boolean;
187
+ /**
188
+ * Rough memory estimate in bytes (intentionally pessimistic).
189
+ * Formula: sum of (ENTRY_OVERHEAD + char bytes of scope+varName+typeName) per entry
190
+ * + MAP_ENTRY_OVERHEAD + char bytes of filePath per file.
191
+ *
192
+ * Note: V8 stores all-ASCII strings as Latin-1 (1 byte/char) and only upgrades
193
+ * to UCS-2 (2 bytes/char) for non-Latin-1 code points. Source paths and type names
194
+ * are typically all-ASCII, so actual heap cost is roughly half what this returns.
195
+ * The pessimistic factor is intentional — better to over-budget than under-budget.
196
+ *
197
+ * **⚠ Cost profile**: O(totalBindings) — iterates every entry in
198
+ * `_allByFile` and reads three string `.length` properties per entry.
199
+ * At a typical repo scale (10k files × ~20 file-scope bindings) this is
200
+ * ~200k property reads per call. Call at most once per pipeline run,
201
+ * NOT per file, per chunk, or per progress tick. The current single
202
+ * call site is the dev-mode telemetry log at the pipeline finalize
203
+ * seam. Adding a per-file-progress caller would silently make it
204
+ * quadratic in repo size.
205
+ */
206
+ estimateMemoryBytes(): number;
207
+ }