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).
This commit is contained in:
morluto
2026-10-25 05:28:15 +08:00
parent d24ad4f55d
commit 9548953b47
4 changed files with 64 additions and 7 deletions
+6 -1
View File
@@ -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 (
+6 -4
View File
@@ -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);
@@ -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],
+38 -1
View File
@@ -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),