From 30e1ccfcc9d58cd3f1c048a5974d30692c5b7b25 Mon Sep 17 00:00:00 2001 From: Clayton nunes <114633702+KayzenRoot@users.noreply.github.com> Date: Tue, 22 Sep 2026 21:27:00 -0300 Subject: [PATCH 1/2] fix(uads): refresh built-in specialists while preserving user profiles --- src/kernel/specialist-registry.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/kernel/specialist-registry.ts b/src/kernel/specialist-registry.ts index 9cc296c..7b0254f 100644 --- a/src/kernel/specialist-registry.ts +++ b/src/kernel/specialist-registry.ts @@ -290,6 +290,19 @@ export function loadSpecialistRegistry(paths: UadsPaths, schemaRoot?: string): S if (normalized.registryDigest !== parsed.value.registryDigest || parsed.value.policyVersion !== SPECIALIST_POLICY_VERSION) { throw new SpecialistRegistryError("specialist registry digest or policy version mismatch"); } + + const currentBuiltins = builtinSpecialistRegistry(schemaRoot); + const builtinIds = new Set(currentBuiltins.profiles.map((profile) => profile.specialistId)); + const preserved = normalized.profiles.filter((profile) => profile.source !== "builtin"); + const collision = preserved.find((profile) => builtinIds.has(profile.specialistId)); + if (collision) { + throw new SpecialistRegistryError(`persisted non-builtin specialist collides with built-in ID: ${collision.specialistId}`); + } + + const reconciled = createSpecialistRegistry([...currentBuiltins.profiles, ...preserved]); + if (reconciled.registryDigest !== normalized.registryDigest) { + return persistSpecialistRegistry(paths, reconciled, schemaRoot); + } return normalized; } From 14b92e13394762b7185e3205ceaf3ee9470842a3 Mon Sep 17 00:00:00 2001 From: Clayton nunes <114633702+KayzenRoot@users.noreply.github.com> Date: Tue, 22 Sep 2026 21:29:25 -0300 Subject: [PATCH 2/2] test(uads): preserve user profiles during built-in refresh --- tests/specialist-registry-refresh.test.ts | 76 +++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/specialist-registry-refresh.test.ts diff --git a/tests/specialist-registry-refresh.test.ts b/tests/specialist-registry-refresh.test.ts new file mode 100644 index 0000000..64eb547 --- /dev/null +++ b/tests/specialist-registry-refresh.test.ts @@ -0,0 +1,76 @@ +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { getUadsPaths } from "../src/lib/workspace.js"; +import { + builtinSpecialistRegistry, + createSpecialistRegistry, + loadSpecialistRegistry, + normalizeSpecialistProfile, + persistSpecialistRegistry, +} from "../src/kernel/specialist-registry.js"; + +const ROOT = process.cwd(); + +describe("built-in specialist registry refresh", () => { + it("refreshes official built-ins while preserving user-config profiles", () => { + const home = fs.mkdtempSync(path.join(os.tmpdir(), "uads-registry-refresh-")); + const paths = getUadsPaths("project-refresh", home); + const current = builtinSpecialistRegistry(ROOT); + + const legacyBuiltins = current.profiles + .filter((profile) => profile.specialistId !== "systems-runtime-specialist") + .map((profile) => { + if (profile.specialistId === "test-engineer") { + return normalizeSpecialistProfile({ + ...profile, + coveredDomains: profile.coveredDomains.filter((domain) => domain !== "testing"), + profileDigest: undefined, + }, "builtin"); + } + if (profile.specialistId === "independent-reviewer") { + return normalizeSpecialistProfile({ + ...profile, + coveredDomains: profile.coveredDomains.filter((domain) => domain !== "certification"), + profileDigest: undefined, + }, "builtin"); + } + return profile; + }); + + const template = current.profiles.find((profile) => profile.specialistId === "frontend-specialist")!; + const custom = normalizeSpecialistProfile({ + ...template, + specialistId: "user-custom-frontend", + source: "user-config", + profileDigest: undefined, + }, "user-config"); + + const legacy = createSpecialistRegistry([...legacyBuiltins, custom]); + persistSpecialistRegistry(paths, legacy, ROOT); + + const loaded = loadSpecialistRegistry(paths, ROOT); + expect(loaded.profiles).toHaveLength(current.profiles.length + 1); + expect(loaded.profiles.some((profile) => profile.specialistId === "systems-runtime-specialist")).toBe(true); + expect( + loaded.profiles.find((profile) => profile.specialistId === "test-engineer")?.coveredDomains, + ).toContain("testing"); + expect( + loaded.profiles.find((profile) => profile.specialistId === "independent-reviewer")?.coveredDomains, + ).toContain("certification"); + expect(loaded.profiles.find((profile) => profile.specialistId === "user-custom-frontend")?.source).toBe("user-config"); + + const persisted = JSON.parse(fs.readFileSync(paths.specialistRegistry, "utf8")) as { + registryDigest: string; + profiles: Array<{ specialistId: string }>; + }; + const state = JSON.parse(fs.readFileSync(paths.specialistState, "utf8")) as { + registryDigest: string; + profileCount: number; + }; + expect(persisted.registryDigest).toBe(loaded.registryDigest); + expect(state.registryDigest).toBe(loaded.registryDigest); + expect(state.profileCount).toBe(current.profiles.length + 1); + }); +});