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,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
|
+
}
|