From 9548953b479df5ff5fd09f1a0b3b3550809fa6de Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 25 Oct 2026 05:28:15 +0800 Subject: [PATCH] refactor(clients): retain inspected command ownership Carry the command identity already parsed during registration inspection into maintenance selection. Keep activation, validity and public status output separate. Validation: fresh build/catalog and full static checks; registration status, update maintenance and setup cleanup ownership suites passed (90 tests). --- src/application/ClientRegistrationStatus.ts | 7 +++- src/application/UpdateMaintenance.ts | 10 +++-- .../clientRegistrationStatus.test.ts | 15 ++++++- .../process-global/updateMaintenance.test.ts | 39 ++++++++++++++++++- 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/application/ClientRegistrationStatus.ts b/src/application/ClientRegistrationStatus.ts index 4aa7eb8c03..89093a411b 100644 --- a/src/application/ClientRegistrationStatus.ts +++ b/src/application/ClientRegistrationStatus.ts @@ -24,6 +24,7 @@ import { import { inspectClientRegistrationCommand, type ClientRegistrationCommandFacts, + type ClientRegistrationCommandIdentity, packageRunnerRegistrationCommand, } from "./ClientRegistrationIdentity.js"; import { @@ -94,6 +95,8 @@ interface ClientRegistrationReadOptions { export interface ClientRegistrationInspection { readonly status: ClientRegistrationStatus; readonly activation: "enabled" | "disabled" | "unknown"; + /** Ownership remains unknown until this inspection has parsed the launcher. */ + readonly commandIdentity: ClientRegistrationCommandIdentity; } /** Project diagnostics from the same inspection used to select maintenance. */ @@ -118,8 +121,9 @@ export const readClientRegistrationInspections = async ( const record = ( status: ClientRegistrationStatus, activation: ClientRegistrationInspection["activation"] = "unknown", + commandIdentity: ClientRegistrationCommandIdentity = "unknown", ): void => { - inspections.push({ status, activation }); + inspections.push({ status, activation, commandIdentity }); }; for (const client of supportedClients( home, @@ -330,6 +334,7 @@ export const readClientRegistrationInspections = async ( : undefined, ), activation, + commandFacts.identity, ); } catch (cause: unknown) { if ( diff --git a/src/application/UpdateMaintenance.ts b/src/application/UpdateMaintenance.ts index 9ea7e5e4c1..1369198321 100644 --- a/src/application/UpdateMaintenance.ts +++ b/src/application/UpdateMaintenance.ts @@ -1,7 +1,6 @@ import { z } from "zod"; import { RegularFileCleanupFailure } from "../filesystem/OwnedRegularFile.js"; -import { classifyClientRegistrationCommand } from "./ClientRegistrationIdentity.js"; import { readClientRegistrationInspections, type ClientRegistrationStatus, @@ -75,7 +74,11 @@ export const existingMaintenanceScope = async ( ); const clients: string[] = []; const unresolved: ClientRegistrationStatus[] = []; - for (const { status: registration, activation } of registrations) { + for (const { + status: registration, + activation, + commandIdentity, + } of registrations) { if (registration.state === "unknown" || registration.state === "invalid") { unresolved.push(registration); continue; @@ -83,8 +86,7 @@ export const existingMaintenanceScope = async ( if ( activation !== "enabled" || (registration.state !== "aligned" && registration.state !== "stale") || - classifyClientRegistrationCommand(registration.command, entryPoint) !== - "owned" + commandIdentity !== "owned" ) continue; clients.push(registration.client); diff --git a/tests/boundary/filesystem/clientRegistrationStatus.test.ts b/tests/boundary/filesystem/clientRegistrationStatus.test.ts index 79c465b572..53dc032ebe 100644 --- a/tests/boundary/filesystem/clientRegistrationStatus.test.ts +++ b/tests/boundary/filesystem/clientRegistrationStatus.test.ts @@ -6,7 +6,10 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { clearClientLocationEnvironment } from "../../fixtures/clientEnvironment.js"; -import { readClientRegistrationStatuses } from "../../../src/application/ClientRegistrationStatus.js"; +import { + readClientRegistrationInspections, + readClientRegistrationStatuses, +} from "../../../src/application/ClientRegistrationStatus.js"; import { configureClientConfiguration } from "../../../src/application/SetupClientConfiguration.js"; import { classifyClientRegistrationCommand } from "../../../src/application/ClientRegistrationIdentity.js"; import { existingMaintenanceScope } from "../../../src/application/UpdateMaintenance.js"; @@ -180,6 +183,14 @@ describe("client registration launcher identity", () => { state: "unknown", remediation: expect.stringContaining("registration was retained"), }); + expect(status).not.toHaveProperty("commandIdentity"); + expect(await readClientRegistrationInspections(home, "")).toEqual([ + expect.objectContaining({ + status, + activation: "enabled", + commandIdentity: "unknown", + }), + ]); }); it("does not use the REA process cwd to resolve a client's relative launcher path", async () => { @@ -230,7 +241,9 @@ describe("client registration launcher identity", () => { state: "unknown", }); }); +}); +describe("client registration package version identity", () => { it.each([ ["4.1.0", true], ["v4.1.0", false], diff --git a/tests/process-global/updateMaintenance.test.ts b/tests/process-global/updateMaintenance.test.ts index 48f44bc711..bf03c31f5a 100644 --- a/tests/process-global/updateMaintenance.test.ts +++ b/tests/process-global/updateMaintenance.test.ts @@ -6,6 +6,7 @@ import { err, ok } from "../../src/domain/result.js"; import { runUpdate, type UpdateResult } from "../../src/application/Update.js"; import { PRODUCT_IDENTITY } from "../../src/identity.js"; import { supportedClients } from "../../src/application/SupportedClients.js"; +import { readClientRegistrationInspections } from "../../src/application/ClientRegistrationStatus.js"; import { existingMaintenanceScope, planIntegrationMaintenance, @@ -238,7 +239,7 @@ describe("maintenance activation and layered configuration", () => { }); }); -describe("existing REA integration maintenance", () => { +describe("registration ownership in maintenance", () => { it("selects owned registrations while preserving unconfigured, foreign, and disabled choices", async () => { await writeClient("codex", staleCodex); await writeClient("claude_code", '{"mcpServers":{}}'); @@ -256,6 +257,40 @@ describe("existing REA integration maintenance", () => { servers: { rea: { type: "stdio", command: entryPoint, args: ["mcp"] } }, }), ); + expect( + await readClientRegistrationInspections(home, entryPoint, { + environment: process.env, + }), + ).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + status: expect.objectContaining({ client: "codex", state: "stale" }), + activation: "enabled", + commandIdentity: "owned", + }), + expect.objectContaining({ + status: expect.objectContaining({ client: "cursor", state: "stale" }), + activation: "enabled", + commandIdentity: "foreign", + }), + expect.objectContaining({ + status: expect.objectContaining({ + client: "opencode", + state: "stale", + }), + activation: "disabled", + commandIdentity: "owned", + }), + expect.objectContaining({ + status: expect.objectContaining({ + client: "claude_code", + state: "missing", + }), + activation: "unknown", + commandIdentity: "unknown", + }), + ]), + ); await expect( existingMaintenanceScope(home, entryPoint, process.env), ).resolves.toEqual({ @@ -264,7 +299,9 @@ describe("existing REA integration maintenance", () => { skillDestinations: [], }); }); +}); +describe("existing REA integration maintenance", () => { it("does not install a missing skill or overwrite an unrelated skill", async () => { await expect( existingMaintenanceScope(home, entryPoint, process.env),