From aff1c888b042669d2b7e2020d3027334bf83de35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 28 Sep 2026 15:14:18 +0200 Subject: [PATCH 1/2] refactor(mcp): project doctor family output schema from its owning module Moves the single hand-authored MCP outputSchema entry for doctor out of command-output-schemas.ts into a frozen DOCTOR_COMMAND_OUTPUT_SCHEMAS map exported from src/commands/management/doctor.ts, following the #2810 projection seam already used by the other projected families on this stack. Registers the family in the PROJECTED_FAMILIES disjointness check and adds command-tools-management-doctor-schemas.test.ts with a reference-equality proof against the module object and an owner-file check, matching the pattern from the other projected families. The moved code is byte-identical to what command-output-schemas.ts held: same objectSchema/enumSchema/stringSchema/numberSchema/looseObjectSchema calls and field order. Verified the published outputSchema for doctor is byte-identical to origin/main by dumping COMMAND_OUTPUT_SCHEMAS.doctor as JSON in a temporary detached worktree of origin/main and diffing against this branch's head; both dumps matched byte-for-byte. Part of #2819. --- src/commands/management/doctor.ts | 44 +++++++++++++++++++ ...nd-tools-management-doctor-schemas.test.ts | 31 +++++++++++++ .../command-tools-replay-schemas.test.ts | 2 + src/mcp/command-output-schemas.ts | 32 +------------- 4 files changed, 79 insertions(+), 30 deletions(-) create mode 100644 src/mcp/__tests__/command-tools-management-doctor-schemas.test.ts diff --git a/src/commands/management/doctor.ts b/src/commands/management/doctor.ts index e9860c668f..2c28ca317a 100644 --- a/src/commands/management/doctor.ts +++ b/src/commands/management/doctor.ts @@ -1,12 +1,56 @@ import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; import type { CommandSchemaOverride } from '@agent-device/command-registry/command-schema'; +import type { CommandResultMap } from '@agent-device/command-registry/command-result'; +import { DEVICE_TARGETS } from '@agent-device/kernel/device'; import * as commandInput from '../command-input.ts'; +import { + enumSchema, + looseObjectSchema, + numberSchema, + objectSchema, + stringSchema, +} from '../command-input.ts'; import { commonInputFromFlags, direct } from '../cli-grammar/common.ts'; import type { CliReader, DaemonWriter } from '../cli-grammar/types.ts'; +import type { JsonSchema } from '../command-contract.ts'; import { defineCommandFacet } from '../family/types.ts'; import { defineFieldCommandMetadata } from '../field-command-contract.ts'; import { managementCliOutputFormatters } from './output.ts'; +/** + * This family's advertised MCP `outputSchema`, keyed by daemon command name and projected into + * the command map by `src/mcp/command-output-schemas.ts`. + */ +export const DOCTOR_COMMAND_OUTPUT_SCHEMAS = { + // packages/contracts/src/doctor.ts + doctor: objectSchema( + { + status: enumSchema(['pass', 'warn', 'fail', 'info']), + summary: stringSchema(), + kind: enumSchema(['auto', 'react-native', 'expo', 'repack']), + platform: stringSchema(), + target: enumSchema(DEVICE_TARGETS), + targetApp: stringSchema(), + metro: objectSchema({ host: stringSchema(), port: numberSchema() }, ['host', 'port']), + checks: { + type: 'array', + items: objectSchema( + { + id: stringSchema(), + status: enumSchema(['pass', 'warn', 'fail', 'info']), + summary: stringSchema(), + hint: stringSchema(), + command: stringSchema(), + evidence: looseObjectSchema(), + }, + ['id', 'status', 'summary'], + ), + }, + }, + ['status', 'summary', 'kind', 'checks'], + ), +} satisfies Pick, 'doctor'>; + const doctorCommandMetadata = defineFieldCommandMetadata( 'doctor', 'Diagnose device, app, development-server, and React Native or Expo readiness issues. Returns compact evidence for local inventory, sessions, optional app discovery, toolchains, and server reachability.', diff --git a/src/mcp/__tests__/command-tools-management-doctor-schemas.test.ts b/src/mcp/__tests__/command-tools-management-doctor-schemas.test.ts new file mode 100644 index 0000000000..cbf81dbeb6 --- /dev/null +++ b/src/mcp/__tests__/command-tools-management-doctor-schemas.test.ts @@ -0,0 +1,31 @@ +import assert from 'node:assert/strict'; +import { test } from 'vitest'; +import { ownerFilesForCommand } from '@agent-device/command-registry/owner-files'; +import { DOCTOR_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/doctor.ts'; +import { COMMAND_OUTPUT_SCHEMAS } from '../command-output-schemas.ts'; + +const DOCTOR_COMMANDS = Object.keys(DOCTOR_COMMAND_OUTPUT_SCHEMAS) as Array< + keyof typeof DOCTOR_COMMAND_OUTPUT_SCHEMAS +>; + +// doctor does not carry the post-action observation trait (#1652): it is a device-runtime +// command, not an interaction command, so the composed map never copies the entry and +// reference equality must hold. +test('MCP doctor family output schemas are the family module entries, not copies', () => { + for (const command of DOCTOR_COMMANDS) { + assert.equal( + COMMAND_OUTPUT_SCHEMAS[command], + DOCTOR_COMMAND_OUTPUT_SCHEMAS[command], + `${command} is not reference-equal to the doctor module's own schema object`, + ); + } +}); + +test('the projected family map declares exactly the commands its module owns', () => { + for (const command of DOCTOR_COMMANDS) { + assert.ok( + ownerFilesForCommand(command).includes('src/commands/management/doctor.ts'), + `${command} projects its output schema from this module but does not name it as its owner`, + ); + } +}); diff --git a/src/mcp/__tests__/command-tools-replay-schemas.test.ts b/src/mcp/__tests__/command-tools-replay-schemas.test.ts index 4d1be21c1c..b7054bfdac 100644 --- a/src/mcp/__tests__/command-tools-replay-schemas.test.ts +++ b/src/mcp/__tests__/command-tools-replay-schemas.test.ts @@ -5,6 +5,7 @@ import { ownerFilesForCommand } from '@agent-device/command-registry/owner-files import { commandSupportsSettleObservation } from '@agent-device/command-registry/registry'; import { WAIT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/capture/wait.ts'; import { PREPARE_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/prepare.ts'; +import { DOCTOR_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/doctor.ts'; import { DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/device.ts'; import { PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/push.ts'; import { VIEWPORT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/viewport.ts'; @@ -81,6 +82,7 @@ const PROJECTED_FAMILIES = [ { name: 'interaction', schemas: INTERACTION_COMMAND_OUTPUT_SCHEMAS }, { name: 'capture-wait', schemas: WAIT_COMMAND_OUTPUT_SCHEMAS }, { name: 'management-prepare', schemas: PREPARE_COMMAND_OUTPUT_SCHEMAS }, + { name: 'management-doctor', schemas: DOCTOR_COMMAND_OUTPUT_SCHEMAS }, { name: 'management-device', schemas: DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS }, { name: 'management-push', schemas: PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS }, { name: 'management-viewport', schemas: VIEWPORT_COMMAND_OUTPUT_SCHEMAS }, diff --git a/src/mcp/command-output-schemas.ts b/src/mcp/command-output-schemas.ts index 98f95291bc..60665f5d71 100644 --- a/src/mcp/command-output-schemas.ts +++ b/src/mcp/command-output-schemas.ts @@ -5,7 +5,6 @@ import { booleanSchema, constSchema, enumSchema, - looseObjectSchema, numberSchema, objectSchema, stringArraySchema, @@ -13,6 +12,7 @@ import { } from '../commands/command-input.ts'; import { WAIT_COMMAND_OUTPUT_SCHEMAS } from '../commands/capture/wait.ts'; import { PREPARE_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/prepare.ts'; +import { DOCTOR_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/doctor.ts'; import { DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/device.ts'; import { PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/push.ts'; import { VIEWPORT_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/viewport.ts'; @@ -23,7 +23,6 @@ import { import { RECORDING_COMMAND_OUTPUT_SCHEMAS } from '../commands/recording/output-schemas.ts'; import { REPLAY_COMMAND_OUTPUT_SCHEMAS } from '../commands/replay/index.ts'; import { SYSTEM_COMMAND_OUTPUT_SCHEMAS } from '../commands/system/index.ts'; -import { DEVICE_TARGETS } from '@agent-device/kernel/device'; /** * Registry of per-command MCP `outputSchema`s, keyed by the daemon command @@ -184,34 +183,6 @@ const BASE_COMMAND_OUTPUT_SCHEMAS = { ['direction'], ), - // packages/contracts/src/doctor.ts - doctor: objectSchema( - { - status: enumSchema(['pass', 'warn', 'fail', 'info']), - summary: stringSchema(), - kind: enumSchema(['auto', 'react-native', 'expo', 'repack']), - platform: stringSchema(), - target: enumSchema(DEVICE_TARGETS), - targetApp: stringSchema(), - metro: objectSchema({ host: stringSchema(), port: numberSchema() }, ['host', 'port']), - checks: { - type: 'array', - items: objectSchema( - { - id: stringSchema(), - status: enumSchema(['pass', 'warn', 'fail', 'info']), - summary: stringSchema(), - hint: stringSchema(), - command: stringSchema(), - evidence: looseObjectSchema(), - }, - ['id', 'status', 'summary'], - ), - }, - }, - ['status', 'summary', 'kind', 'checks'], - ), - // packages/contracts/src/diff.ts — the public Node command accepts snapshot diffs. diff: objectSchema( { @@ -247,6 +218,7 @@ const BASE_COMMAND_OUTPUT_SCHEMAS = { // and this map's `satisfies` still refuses a missing `CommandResultMap` key. ...WAIT_COMMAND_OUTPUT_SCHEMAS, ...PREPARE_COMMAND_OUTPUT_SCHEMAS, + ...DOCTOR_COMMAND_OUTPUT_SCHEMAS, ...DEVICE_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS, ...PUSH_MANAGEMENT_COMMAND_OUTPUT_SCHEMAS, ...VIEWPORT_COMMAND_OUTPUT_SCHEMAS, From 8cb0ff57afa5a255e8738593b0a3b30770fa4ac0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Tue, 29 Sep 2026 08:26:43 +0200 Subject: [PATCH 2/2] refactor(mcp): merge the doctor family command-input imports --- src/commands/management/doctor.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/commands/management/doctor.ts b/src/commands/management/doctor.ts index 2c28ca317a..20a4f6c93a 100644 --- a/src/commands/management/doctor.ts +++ b/src/commands/management/doctor.ts @@ -2,12 +2,13 @@ import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog'; import type { CommandSchemaOverride } from '@agent-device/command-registry/command-schema'; import type { CommandResultMap } from '@agent-device/command-registry/command-result'; import { DEVICE_TARGETS } from '@agent-device/kernel/device'; -import * as commandInput from '../command-input.ts'; import { + booleanField, enumSchema, looseObjectSchema, numberSchema, objectSchema, + stringField, stringSchema, } from '../command-input.ts'; import { commonInputFromFlags, direct } from '../cli-grammar/common.ts'; @@ -55,12 +56,10 @@ const doctorCommandMetadata = defineFieldCommandMetadata( 'doctor', 'Diagnose device, app, development-server, and React Native or Expo readiness issues. Returns compact evidence for local inventory, sessions, optional app discovery, toolchains, and server reachability.', { - targetApp: commandInput.stringField( + targetApp: stringField( 'Installed app package/bundle id or app name to verify without opening a session.', ), - remote: commandInput.booleanField( - 'Check remote connection setup instead of local device inventory.', - ), + remote: booleanField('Check remote connection setup instead of local device inventory.'), }, );