From 2a019933bb3876e730b9840b278b4ed66909da22 Mon Sep 17 00:00:00 2001 From: Gio Della-Libera Date: Thu, 2 Jul 2026 16:01:40 -0700 Subject: [PATCH] Keep legacy plugin manifest doctor check lint-only --- src/commands/doctor-plugin-manifests.test.ts | 58 -------- src/commands/doctor-plugin-manifests.ts | 133 +----------------- src/flows/doctor-health-contributions.test.ts | 60 -------- src/flows/doctor-health-contributions.ts | 11 -- 4 files changed, 1 insertion(+), 261 deletions(-) diff --git a/src/commands/doctor-plugin-manifests.test.ts b/src/commands/doctor-plugin-manifests.test.ts index 4d5000c88dd9..25ad69a1aac4 100644 --- a/src/commands/doctor-plugin-manifests.test.ts +++ b/src/commands/doctor-plugin-manifests.test.ts @@ -9,7 +9,6 @@ import { collectLegacyPluginManifestContractMigrations, legacyPluginManifestContractMigrationToHealthFinding, maybeRepairLegacyPluginManifestContracts, - repairLegacyPluginManifestContractFindings, } from "./doctor-plugin-manifests.js"; import type { DoctorPrompter } from "./doctor-prompter.js"; @@ -281,63 +280,6 @@ describe("doctor plugin manifest legacy contract repair", () => { }); }); - it("previews legacy manifest rewrites as file effects and diffs", async () => { - const pluginsRoot = await suiteTempDirs.make("preview-capability"); - const root = path.join(pluginsRoot, "openai"); - fs.mkdirSync(root, { recursive: true }); - writePackageJson(root); - writeManifest(root, { - id: "openai", - speechProviders: ["openai"], - configSchema: { type: "object" }, - }); - const manifestPath = path.join(root, "openclaw.plugin.json"); - - const result = await repairLegacyPluginManifestContractFindings({ - config: configWithPluginLoadPath(pluginsRoot), - env: { - ...process.env, - }, - manifestRoots: [pluginsRoot], - findings: [ - { - checkId: "core/doctor/legacy-plugin-manifests", - severity: "warning", - message: "Plugin manifest openai uses legacy top-level capability keys.", - path: manifestPath, - target: "openai", - requirement: "contracts-capability-keys", - }, - ], - dryRun: true, - diff: true, - }); - - expect(result.changes).toEqual([ - `- ${manifestPath}: moved speechProviders to contracts.speechProviders`, - ]); - expect(result.effects).toEqual([ - { - kind: "file", - action: "would-rewrite-legacy-plugin-manifest-contracts", - target: manifestPath, - dryRunSafe: false, - }, - ]); - expect(result.diffs).toEqual([ - expect.objectContaining({ - kind: "file", - path: manifestPath, - before: expect.stringContaining('"speechProviders"'), - after: expect.stringContaining('"contracts"'), - }), - ]); - const unchanged = JSON.parse(fs.readFileSync(manifestPath, "utf-8")) as { - speechProviders?: string[]; - }; - expect(unchanged.speechProviders).toEqual(["openai"]); - }); - it("ignores non-object contracts payloads when collecting migrations", async () => { const pluginsRoot = await suiteTempDirs.make("non-object-contracts"); const root = path.join(pluginsRoot, "openai"); diff --git a/src/commands/doctor-plugin-manifests.ts b/src/commands/doctor-plugin-manifests.ts index cfe6da3522d7..2027c39d213f 100644 --- a/src/commands/doctor-plugin-manifests.ts +++ b/src/commands/doctor-plugin-manifests.ts @@ -6,12 +6,7 @@ import { normalizeTrimmedStringList } from "@openclaw/normalization-core/string- import { z } from "zod"; import { note } from "../../packages/terminal-core/src/note.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; -import type { - HealthFinding, - HealthRepairDiff, - HealthRepairEffect, - HealthRepairResult, -} from "../flows/health-checks.js"; +import type { HealthFinding } from "../flows/health-checks.js"; import { loadPluginManifestRegistry } from "../plugins/manifest-registry.js"; import type { RuntimeEnv } from "../runtime.js"; import { shortenHomePath } from "../utils.js"; @@ -178,132 +173,6 @@ function migrationToManifestJson(migration: LegacyManifestContractMigration): st return `${JSON.stringify(migration.nextRaw, null, 2)}\n`; } -function legacyPluginManifestMigrationToRepairEffect( - migration: LegacyManifestContractMigration, - dryRun: boolean, -): HealthRepairEffect { - return { - kind: "file", - action: dryRun - ? "would-rewrite-legacy-plugin-manifest-contracts" - : "rewrite-legacy-plugin-manifest-contracts", - target: migration.manifestPath, - dryRunSafe: false, - }; -} - -function legacyPluginManifestMigrationToRepairDiff( - migration: LegacyManifestContractMigration, - before: string, -): HealthRepairDiff { - return { - kind: "file", - path: migration.manifestPath, - before, - after: migrationToManifestJson(migration), - }; -} - -/** Repairs or previews selected legacy plugin manifest contract migrations. */ -export async function repairLegacyPluginManifestContractFindings(params: { - config?: OpenClawConfig; - env?: NodeJS.ProcessEnv; - manifestRoots?: string[]; - workspaceDir?: string; - findings: readonly HealthFinding[]; - dryRun?: boolean; - diff?: boolean; - deps?: { - readFileSync?: typeof fs.readFileSync; - writeFileSync?: typeof fs.writeFileSync; - }; -}): Promise { - const selectedPaths = new Set( - params.findings - .filter( - (finding) => - finding.checkId === LEGACY_PLUGIN_MANIFESTS_CHECK_ID && - finding.requirement === "contracts-capability-keys" && - typeof finding.path === "string", - ) - .map((finding) => finding.path as string), - ); - if (selectedPaths.size === 0) { - return { - status: "skipped", - reason: "no repairable legacy plugin manifest findings were present", - changes: [], - }; - } - - const migrations = collectLegacyPluginManifestContractMigrations({ - ...(params.config ? { config: params.config } : {}), - ...(params.env ? { env: params.env } : {}), - ...(params.manifestRoots ? { manifestRoots: params.manifestRoots } : {}), - ...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}), - }).filter((migration) => selectedPaths.has(migration.manifestPath)); - if (migrations.length === 0) { - return { - status: "skipped", - reason: "selected legacy plugin manifests no longer need repair", - changes: [], - }; - } - - const readFile = params.deps?.readFileSync ?? fs.readFileSync; - const writeFile = params.deps?.writeFileSync ?? fs.writeFileSync; - const changes: string[] = []; - const diffs: HealthRepairDiff[] = []; - const effects: HealthRepairEffect[] = []; - const warnings: string[] = []; - - for (const migration of migrations) { - let before: string | undefined; - if (params.diff === true) { - try { - before = readFile(migration.manifestPath, "utf-8"); - diffs.push(legacyPluginManifestMigrationToRepairDiff(migration, before)); - } catch (error) { - warnings.push( - `Could not read legacy plugin manifest at ${migration.manifestPath}: ${String(error)}`, - ); - } - } - - if (params.dryRun !== true) { - try { - writeFile(migration.manifestPath, migrationToManifestJson(migration), "utf-8"); - } catch (error) { - warnings.push( - `Failed to rewrite legacy plugin manifest at ${migration.manifestPath}: ${String(error)}`, - ); - continue; - } - } - - changes.push(...migration.changeLines); - effects.push(legacyPluginManifestMigrationToRepairEffect(migration, params.dryRun === true)); - } - - if (changes.length === 0 && warnings.length > 0) { - return { - status: "failed", - reason: "could not rewrite selected legacy plugin manifests", - changes, - warnings, - diffs, - effects, - }; - } - - return { - changes, - warnings, - diffs, - effects, - }; -} - /** Prompts and rewrites legacy plugin manifest contract fields when doctor repair is enabled. */ export async function maybeRepairLegacyPluginManifestContracts(params: { config?: OpenClawConfig; diff --git a/src/flows/doctor-health-contributions.test.ts b/src/flows/doctor-health-contributions.test.ts index 81d4d69b6f39..59fad530dc5f 100644 --- a/src/flows/doctor-health-contributions.test.ts +++ b/src/flows/doctor-health-contributions.test.ts @@ -69,10 +69,6 @@ const mocks = vi.hoisted(() => ({ }), ), maybeRepairLegacyPluginManifestContracts: vi.fn().mockResolvedValue(undefined), - repairLegacyPluginManifestContractFindings: vi.fn(async () => ({ - changes: [] as string[], - effects: [] as unknown[], - })), detectLegacyClawdBrowserProfileResidue: vi.fn(), maybeArchiveLegacyClawdBrowserProfileResidue: vi.fn(), resolveAgentWorkspaceDir: vi.fn(() => "/tmp/openclaw-workspace"), @@ -179,7 +175,6 @@ vi.mock("../commands/doctor-plugin-manifests.js", () => ({ legacyPluginManifestContractMigrationToHealthFinding: mocks.legacyPluginManifestContractMigrationToHealthFinding, maybeRepairLegacyPluginManifestContracts: mocks.maybeRepairLegacyPluginManifestContracts, - repairLegacyPluginManifestContractFindings: mocks.repairLegacyPluginManifestContractFindings, })); vi.mock("../commands/doctor-auth-oauth-sidecar.js", () => ({ @@ -390,11 +385,6 @@ describe("doctor health contributions", () => { mocks.legacyPluginManifestContractMigrationToHealthFinding.mockClear(); mocks.maybeRepairLegacyPluginManifestContracts.mockClear(); mocks.maybeRepairLegacyPluginManifestContracts.mockResolvedValue(undefined); - mocks.repairLegacyPluginManifestContractFindings.mockClear(); - mocks.repairLegacyPluginManifestContractFindings.mockResolvedValue({ - changes: [], - effects: [], - }); mocks.maybeRepairLegacyOAuthSidecarProfiles.mockClear(); mocks.maybeRepairLegacyOAuthSidecarProfiles.mockResolvedValue(undefined); mocks.collectAuthProfileHealthFindings.mockClear(); @@ -566,56 +556,6 @@ describe("doctor health contributions", () => { ); }); - it("threads dry-run legacy plugin manifest repairs through the structured check", async () => { - const contribution = requireDoctorContribution("doctor:legacy-plugin-manifests"); - const check = contribution.healthChecks[0] as HealthCheck; - const findings = [ - { - checkId: "core/doctor/legacy-plugin-manifests", - severity: "warning" as const, - message: "Plugin manifest legacy-plugin uses legacy top-level capability keys.", - path: "/tmp/openclaw-plugin/openclaw.plugin.json", - target: "legacy-plugin", - requirement: "contracts-capability-keys", - }, - ]; - mocks.repairLegacyPluginManifestContractFindings.mockResolvedValueOnce({ - changes: ["Would rewrite legacy manifest."], - effects: [ - { - kind: "file", - action: "would-rewrite-legacy-plugin-manifest-contracts", - target: "/tmp/openclaw-plugin/openclaw.plugin.json", - dryRunSafe: false, - }, - ], - }); - - const result = await check.repair?.( - { - cfg: { plugins: { load: { paths: ["/tmp/openclaw-plugin"] } } }, - mode: "fix", - dryRun: true, - diff: true, - runtime: { log: vi.fn(), error: vi.fn(), exit: vi.fn() }, - }, - findings, - ); - - expect(mocks.repairLegacyPluginManifestContractFindings).toHaveBeenCalledWith({ - config: { plugins: { load: { paths: ["/tmp/openclaw-plugin"] } } }, - env: process.env, - findings, - dryRun: true, - diff: true, - }); - expect(result?.effects).toContainEqual( - expect.objectContaining({ - action: "would-rewrite-legacy-plugin-manifest-contracts", - }), - ); - }); - it("runs release configured plugin install repair before plugin registry and final config writes", () => { const ids = resolveDoctorHealthContributions().map((entry) => entry.id); diff --git a/src/flows/doctor-health-contributions.ts b/src/flows/doctor-health-contributions.ts index 901a16d6d0d7..c0a31bed5a9b 100644 --- a/src/flows/doctor-health-contributions.ts +++ b/src/flows/doctor-health-contributions.ts @@ -1353,17 +1353,6 @@ export function resolveDoctorHealthContributions(): DoctorHealthContribution[] { env: process.env, }).map(legacyPluginManifestContractMigrationToHealthFinding); }, - async repair(ctx, findings) { - const { repairLegacyPluginManifestContractFindings } = - await import("../commands/doctor-plugin-manifests.js"); - return await repairLegacyPluginManifestContractFindings({ - config: ctx.cfg, - env: process.env, - findings, - dryRun: ctx.dryRun, - diff: ctx.diff, - }); - }, }, run: runLegacyPluginManifestHealth, }),