From a18fea0d957962171d356b7f0fe75211718be856 Mon Sep 17 00:00:00 2001 From: jonasvanderhaegen-xve <> Date: Tue, 14 Apr 2026 21:54:07 +0200 Subject: [PATCH] fix(group/sync): wire ManifestExtractor into syncGroup pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ManifestExtractor was fully implemented in extractors/manifest-extractor.ts but never imported or called in sync.ts. As a result, any links declared in group.yaml were parsed and validated by config-parser.ts but silently dropped — config.links was always an empty dead-end as far as syncGroup was concerned. Changes: - Import ManifestExtractor in sync.ts - Call extractFromManifest(config.links, dbExecutors) inside the outer try block, after all repos are processed but before the finally closes the DB pools (symbol resolution via resolveSymbol requires open executors) - Collect the resulting contracts into autoContracts and the cross-links into a separate manifestCrossLinks array - Merge manifestCrossLinks into the final crossLinks alongside runExactMatch results Without this fix, users who declare explicit service dependencies in group.yaml links (the documented workaround for HTTP clients that use absolute URLs and are invisible to the auto-extractors) get 0 cross-links regardless of what they configure. --- gitnexus/src/core/group/sync.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/gitnexus/src/core/group/sync.ts b/gitnexus/src/core/group/sync.ts index 92cd9fe5f..c297cd9a1 100644 --- a/gitnexus/src/core/group/sync.ts +++ b/gitnexus/src/core/group/sync.ts @@ -7,6 +7,7 @@ import type { GroupConfig, RepoHandle, RepoSnapshot, StoredContract, CrossLink } import { HttpRouteExtractor } from './extractors/http-route-extractor.js'; import { GrpcExtractor } from './extractors/grpc-extractor.js'; import { TopicExtractor } from './extractors/topic-extractor.js'; +import { ManifestExtractor } from './extractors/manifest-extractor.js'; import { runExactMatch } from './matching.js'; import { detectServiceBoundaries, assignService } from './service-boundary-detector.js'; import type { CypherExecutor } from './contract-extractor.js'; @@ -64,6 +65,7 @@ export async function syncGroup(config: GroupConfig, opts?: SyncOptions): Promis const missingRepos: string[] = []; const repoSnapshots: Record = {}; let autoContracts: StoredContract[] = []; + let manifestCrossLinks: CrossLink[] = []; let dbExecutors: Map | undefined; const eo = opts?.extractorOverride; @@ -151,6 +153,21 @@ export async function syncGroup(config: GroupConfig, opts?: SyncOptions): Promis missingRepos.push(groupPath); } } + + // Process manifest links while DB pools are still open. + // ManifestExtractor is fully implemented but was never wired into this + // pipeline — config.links were parsed and validated but silently dropped. + if (config.links.length > 0) { + const manifestEx = new ManifestExtractor(); + const manifestResult = await manifestEx.extractFromManifest(config.links, dbExecutors); + autoContracts.push(...manifestResult.contracts); + manifestCrossLinks = manifestResult.crossLinks; + if (opts?.verbose) { + console.log( + ` manifest: ${manifestCrossLinks.length} cross-links from ${config.links.length} declared links`, + ); + } + } } finally { for (const id of [...new Set(openPoolIds)]) { await closeLbug(id).catch(() => {}); @@ -159,7 +176,7 @@ export async function syncGroup(config: GroupConfig, opts?: SyncOptions): Promis } const { matched, unmatched } = runExactMatch(autoContracts); - const crossLinks: CrossLink[] = matched; + const crossLinks: CrossLink[] = [...matched, ...manifestCrossLinks]; const allContracts: StoredContract[] = autoContracts; const registry: ContractRegistry = {