From 90425a45540ed671b3f05ef9688f8d2058d8e2e7 Mon Sep 17 00:00:00 2001 From: ReidenXerx Date: Thu, 20 Aug 2026 17:16:52 +0300 Subject: [PATCH] fix(group)!: remove the matching cascade that was advertised but never built MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `gitnexus group create` wrote `matching.bm25_threshold` and `matching.embedding_threshold` into every generated group.yaml, and no matcher ever read either one. That was not the whole of it — an entire feature surface described a BM25/embedding cascade that does not exist: - `matching.bm25_threshold` / `matching.embedding_threshold` — parsed, persisted, unread - `detect.embedding_fallback` — defaulted and templated, unread - `MatchType` declared `'bm25' | 'embedding'`; both variants unreachable - `SyncOptions.skipEmbeddings` — declared in sync.ts and never read - `gitnexus group sync --skip-embeddings` — accepted, threaded through GroupService, ignored - CLI help in en and zh-CN promised "Exact + BM25 only (no embedding fallback)" - the MCP `group_sync` schema exposed `skipEmbeddings`, described as "Exact + BM25 only (Demo PR: same as default exact path)" `sync.ts` imports exactly `buildProviderIndex`, `runExactMatch` and `runWildcardMatch`, and the printed cascade has one stage. An operator whose links do not match reaches for those thresholds first, and turning either knob changes nothing — config that silently does nothing is how people conclude a feature is broken. Evidence that the cascade should be deleted rather than implemented, from a real backend/frontend pair: of 165 consumer contracts, 149 link exactly and 16 do not. Nine of the sixteen are third-party APIs (Google OAuth, Apple public keys, PostHog, image annotation) with no in-group provider by construction — similarity matching cannot recover them, it can only invent false links. Two are verb mismatches: the frontend calls `POST /links` and `GET /links/check-exists` while the backend declares `GET /links` and eleven other `/links/*` routes but neither of those, so a fuzzy path match would link a POST consumer to a GET provider. The rest are path-extraction artifacts. Roughly none of the sixteen would be correctly recovered, and several would be actively mis-linked. BREAKING CHANGE: `gitnexus group sync --skip-embeddings` and the MCP `group_sync` `skipEmbeddings` parameter are removed. Both were accepted and ignored, so no behavior changes — but a script passing the flag now fails with `unknown option` instead of being silently misled. Existing group.yaml files keep loading: the removed keys are simply no longer part of the schema, and a regression test pins that a legacy config carrying all three still parses. Closes #3006 Co-Authored-By: Claude Opus 5 (1M context) --- gitnexus/src/cli/group.ts | 2 -- gitnexus/src/cli/help-i18n.ts | 1 - gitnexus/src/cli/i18n/en.ts | 1 - gitnexus/src/cli/i18n/zh-CN.ts | 1 - gitnexus/src/core/group/config-parser.ts | 3 -- gitnexus/src/core/group/service.ts | 1 - gitnexus/src/core/group/storage.ts | 3 -- gitnexus/src/core/group/sync.ts | Bin 17612 -> 17584 bytes gitnexus/src/core/group/types.ts | 5 +--- gitnexus/src/mcp/tools.ts | 4 --- .../test/unit/group/config-parser.test.ts | 27 +++++++++++++++++- 11 files changed, 27 insertions(+), 21 deletions(-) diff --git a/gitnexus/src/cli/group.ts b/gitnexus/src/cli/group.ts index 3111f69d0..0a8595844 100644 --- a/gitnexus/src/cli/group.ts +++ b/gitnexus/src/cli/group.ts @@ -149,7 +149,6 @@ export function registerGroupCommands(program: Command): void { group .command('sync ') .description('Sync Contract Registry — extract contracts and build cross-links') - .option('--skip-embeddings', 'Exact + BM25 only (no embedding fallback)') .option('--exact-only', 'Exact match only') .option('--allow-stale', 'Skip stale index warnings') .option('--verbose', 'Show each cross-link detail') @@ -168,7 +167,6 @@ export function registerGroupCommands(program: Command): void { groupDir, allowStale: Boolean(opts.allowStale), verbose: Boolean(opts.verbose), - skipEmbeddings: Boolean(opts.skipEmbeddings), exactOnly: Boolean(opts.exactOnly), }); diff --git a/gitnexus/src/cli/help-i18n.ts b/gitnexus/src/cli/help-i18n.ts index 58f28d11a..e81f03570 100644 --- a/gitnexus/src/cli/help-i18n.ts +++ b/gitnexus/src/cli/help-i18n.ts @@ -155,7 +155,6 @@ const OPTION_DESCRIPTION_KEYS = { 'embeddings install|--cuda': 'help.option.embeddings.install.cuda', 'embeddings install|--force': 'help.option.embeddings.install.force', 'group create|--force': 'help.option.group.create.force', - 'group sync|--skip-embeddings': 'help.option.group.sync.skipEmbeddings', 'group sync|--exact-only': 'help.option.group.sync.exactOnly', 'group sync|--allow-stale': 'help.option.group.sync.allowStale', 'group sync|--verbose': 'help.option.group.sync.verbose', diff --git a/gitnexus/src/cli/i18n/en.ts b/gitnexus/src/cli/i18n/en.ts index aaaf442cb..e5be91f99 100644 --- a/gitnexus/src/cli/i18n/en.ts +++ b/gitnexus/src/cli/i18n/en.ts @@ -293,7 +293,6 @@ export const en = { 'help.option.embeddings.install.force': 'Install into the runtime prefix even when the stack already resolves', 'help.option.group.create.force': 'Overwrite existing group', - 'help.option.group.sync.skipEmbeddings': 'Exact + BM25 only (no embedding fallback)', 'help.option.group.sync.exactOnly': 'Exact match only', 'help.option.group.sync.allowStale': 'Skip stale index warnings', 'help.option.group.sync.verbose': 'Show each cross-link detail', diff --git a/gitnexus/src/cli/i18n/zh-CN.ts b/gitnexus/src/cli/i18n/zh-CN.ts index 0ed1c7f1e..27e7c835c 100644 --- a/gitnexus/src/cli/i18n/zh-CN.ts +++ b/gitnexus/src/cli/i18n/zh-CN.ts @@ -273,7 +273,6 @@ export const zhCN = { '同时下载 CUDA GPU 二进制文件(运行 onnxruntime-node 的 NuGet postinstall;代理后请设置 GLOBAL_AGENT_HTTPS_PROXY)', 'help.option.embeddings.install.force': '即使嵌入组件已可解析,也强制安装到运行时目录', 'help.option.group.create.force': '覆盖现有仓库组', - 'help.option.group.sync.skipEmbeddings': '仅使用 exact + BM25(不使用嵌入回退)', 'help.option.group.sync.exactOnly': '仅精确匹配', 'help.option.group.sync.allowStale': '跳过过期索引警告', 'help.option.group.sync.verbose': '显示每条跨仓库链接详情', diff --git a/gitnexus/src/core/group/config-parser.ts b/gitnexus/src/core/group/config-parser.ts index 29c868171..e2b42c131 100644 --- a/gitnexus/src/core/group/config-parser.ts +++ b/gitnexus/src/core/group/config-parser.ts @@ -30,14 +30,11 @@ const DEFAULT_DETECT = { thrift: true, topics: true, shared_libs: true, - embedding_fallback: true, includes: false, workspace_deps: false, }; const DEFAULT_MATCHING = { - bm25_threshold: 0.7, - embedding_threshold: 0.65, max_candidates_per_step: 3, exclude_links_paths: [] as string[], exclude_links_param_only_paths: false, diff --git a/gitnexus/src/core/group/service.ts b/gitnexus/src/core/group/service.ts index f1356dcec..b6866824d 100644 --- a/gitnexus/src/core/group/service.ts +++ b/gitnexus/src/core/group/service.ts @@ -350,7 +350,6 @@ export class GroupService { const result = await syncGroup(config, { groupDir, exactOnly: Boolean(params.exactOnly), - skipEmbeddings: Boolean(params.skipEmbeddings), allowStale: Boolean(params.allowStale), verbose: Boolean(params.verbose), }); diff --git a/gitnexus/src/core/group/storage.ts b/gitnexus/src/core/group/storage.ts index 6e568cd6e..020659ff6 100644 --- a/gitnexus/src/core/group/storage.ts +++ b/gitnexus/src/core/group/storage.ts @@ -94,11 +94,8 @@ detect: grpc: true topics: true shared_libs: true - embedding_fallback: true matching: - bm25_threshold: 0.7 - embedding_threshold: 0.65 max_candidates_per_step: 3 # exclude_links_paths: [/ping, /health, /healthcheck] # exclude_links_param_only_paths: false diff --git a/gitnexus/src/core/group/sync.ts b/gitnexus/src/core/group/sync.ts index a329500be5a0edb4a4588be0960b53d4c79b71aa..1dc000247503c0014f28522139bb8073f8f4c03b 100644 GIT binary patch delta 14 UcmX@p$+!WC*0OCr$;NC405F0Dw*UYD delta 31 mcmdnc$#|xdal=|R0R@HP?92k!+@#c$l+3*J;>`=$`0W6{@e7my diff --git a/gitnexus/src/core/group/types.ts b/gitnexus/src/core/group/types.ts index db9d1989f..972f3cd86 100644 --- a/gitnexus/src/core/group/types.ts +++ b/gitnexus/src/core/group/types.ts @@ -1,5 +1,5 @@ export type ContractType = 'http' | 'grpc' | 'thrift' | 'topic' | 'lib' | 'custom' | 'include'; -export type MatchType = 'exact' | 'manifest' | 'wildcard' | 'bm25' | 'embedding'; +export type MatchType = 'exact' | 'manifest' | 'wildcard'; export type ContractRole = 'provider' | 'consumer'; export interface GroupConfig { @@ -27,14 +27,11 @@ export interface DetectConfig { thrift: boolean; topics: boolean; shared_libs: boolean; - embedding_fallback: boolean; includes: boolean; workspace_deps: boolean; } export interface MatchingConfig { - bm25_threshold: number; - embedding_threshold: number; max_candidates_per_step: number; /** * HTTP paths to exclude from cross-link matching. Contracts at these paths diff --git a/gitnexus/src/mcp/tools.ts b/gitnexus/src/mcp/tools.ts index e81d70d34..ce319b15a 100644 --- a/gitnexus/src/mcp/tools.ts +++ b/gitnexus/src/mcp/tools.ts @@ -859,10 +859,6 @@ WHEN TO USE: After changing group.yaml or re-indexing member repos.`, type: 'object', properties: { name: { type: 'string', description: 'Group name' }, - skipEmbeddings: { - type: 'boolean', - description: 'Exact + BM25 only (Demo PR: same as default exact path)', - }, exactOnly: { type: 'boolean', description: 'Exact match only in cascade' }, }, required: ['name'], diff --git a/gitnexus/test/unit/group/config-parser.test.ts b/gitnexus/test/unit/group/config-parser.test.ts index 22bb2ad26..6b1037a74 100644 --- a/gitnexus/test/unit/group/config-parser.test.ts +++ b/gitnexus/test/unit/group/config-parser.test.ts @@ -59,11 +59,36 @@ repos: expect(config.links).toEqual([]); expect(config.packages).toEqual({}); expect(config.detect.http).toBe(true); - expect(config.matching.bm25_threshold).toBe(0.7); + expect(config.matching.max_candidates_per_step).toBe(3); expect(config.matching.exclude_links_paths).toEqual([]); expect(config.matching.exclude_links_param_only_paths).toBe(false); }); + it('still parses a legacy config carrying the removed matching knobs', () => { + // `bm25_threshold`, `embedding_threshold` and `detect.embedding_fallback` + // were written into every generated group.yaml but read by no matcher, so + // they are gone from the schema and the template. Every group.yaml already + // on disk still has them, and must keep loading without complaint. + const legacy = ` +version: 1 +name: test +repos: + app: my-app +detect: + http: true + embedding_fallback: true +matching: + bm25_threshold: 0.7 + embedding_threshold: 0.65 + max_candidates_per_step: 3 +`; + const config = parseGroupConfig(legacy); + expect(config.name).toBe('test'); + expect(config.repos).toEqual({ app: 'my-app' }); + expect(config.detect.http).toBe(true); + expect(config.matching.max_candidates_per_step).toBe(3); + }); + it('defaults thrift detection to true', () => { const minimal = ` version: 1