diff --git a/src/grok/inject.ts b/src/grok/inject.ts index 9304d923c..4659a58cd 100644 --- a/src/grok/inject.ts +++ b/src/grok/inject.ts @@ -395,7 +395,11 @@ export function injectGrokConfig( // Adopt our own pre-fence entries (#511) BEFORE reserving user aliases, so the stale // duplicate is replaced instead of routed around forever. Runs inside the normalized // window so the user's dominant EOL is still restored below. - const orphans = findOpencodexOrphans(originalContent, originalRegion); + const emittedModelIds = new Set( + models.filter(model => !opts.excluded?.has(model.id)).map(model => model.id), + ); + const orphans = findOpencodexOrphans(originalContent, originalRegion) + .filter(orphan => orphan.modelId !== undefined && emittedModelIds.has(orphan.modelId)); const content = removeOrphanTables(originalContent, orphans); // Removing bytes above the fence MOVES it: recompute rather than adjust arithmetic, // so the splice below cannot cut the file in the wrong place. diff --git a/tests/grok-orphan-adoption.test.ts b/tests/grok-orphan-adoption.test.ts index 252090408..981cb976d 100644 --- a/tests/grok-orphan-adoption.test.ts +++ b/tests/grok-orphan-adoption.test.ts @@ -160,6 +160,21 @@ describe("Grok orphan adoption (#511)", () => { injectGrokConfig(10100, MODELS, { grokHome }); const content = readFileSync(configPath, "utf8"); expect(content).toContain('default = "ocx-retired"'); + expect(content).toContain("[model.ocx-retired]"); + expect(content).toContain('model = "retired/model"'); + }); + + test("keeps an orphan whose catalog model is excluded", () => { + writeOrphanedConfig(); + + injectGrokConfig(10100, MODELS, { + grokHome, + excluded: new Set(["gpt-5.6-sol"]), + }); + const content = readFileSync(configPath, "utf8"); + expect(content).toContain('default = "ocx-gpt-5-6-sol"'); + expect(content).toContain("[model.ocx-gpt-5-6-sol]"); + expect(content).not.toContain("[model.ocx-gpt-5-6-sol-2]"); }); // F7: the sweep must converge, or `changed` is meaningless to callers.