Skip to content

Commit cb867ba

Browse files
authored
refactor(mcp): project diff family output schema from its owning module (#3042)
Moves the single hand-authored MCP outputSchema entry for diff out of command-output-schemas.ts into a frozen DIFF_COMMAND_OUTPUT_SCHEMAS map exported from src/commands/capture/diff.ts, following the #2819 projection seam already used by wait and the other migrated families on this stack. Registers the family in the PROJECTED_FAMILIES disjointness check and adds command-tools-capture-diff-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. diff does not carry the post-action observation trait (#1652): it reports a snapshot comparison, not an interaction, so no settle-graft copy applies here. The moved code is byte-identical to what command-output-schemas.ts held: same objectSchema/numberSchema/enumSchema/stringSchema/stringArraySchema call and field order, and the shared command-input.ts helpers are unchanged from origin/main, so the published outputSchema for diff stays byte-identical to origin/main. Part of #2819.
1 parent 7c235bf commit cb867ba

4 files changed

Lines changed: 91 additions & 31 deletions

File tree

‎src/commands/capture/diff.ts‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,67 @@
11
import { PUBLIC_COMMANDS } from '@agent-device/command-registry/catalog';
22
import { SNAPSHOT_FLAGS } from '@agent-device/command-registry/flag-groups';
3+
import type { CommandResultMap } from '@agent-device/command-registry/command-result';
34
import { AppError } from '@agent-device/kernel/errors';
45
import {
56
booleanField,
7+
booleanSchema,
8+
constSchema,
9+
enumSchema,
610
integerField,
711
jsonSchemaField,
12+
numberSchema,
13+
objectSchema,
814
requiredField,
15+
stringArraySchema,
916
stringField,
17+
stringSchema,
1018
} from '../command-input.ts';
1119
import { commonInputFromFlags, direct, requiredDaemonString } from '../cli-grammar/common.ts';
1220
import type { CliReader, DaemonWriter } from '../cli-grammar/types.ts';
21+
import type { JsonSchema } from '../command-contract.ts';
1322
import { defineCommandFacet } from '../family/types.ts';
1423
import { defineFieldCommandMetadata } from '../field-command-contract.ts';
1524

1625
const DIFF_COMMAND_NAME = 'diff';
1726

27+
/**
28+
* This family's advertised MCP `outputSchema`, keyed by daemon command name and projected into
29+
* the command map by `src/mcp/command-output-schemas.ts`. Non-strict like every other entry: no
30+
* `additionalProperties: false`, so additive response fields keep validating. `diff` does not
31+
* carry the post-action observation trait (#1652): it reports a snapshot comparison, not an
32+
* interaction, so no settle-graft copy applies here.
33+
*/
34+
export const DIFF_COMMAND_OUTPUT_SCHEMAS = {
35+
// packages/contracts/src/diff.ts — the public Node command accepts snapshot diffs.
36+
diff: objectSchema(
37+
{
38+
mode: constSchema('snapshot'),
39+
baselineInitialized: booleanSchema(),
40+
summary: objectSchema(
41+
{
42+
additions: numberSchema(),
43+
removals: numberSchema(),
44+
unchanged: numberSchema(),
45+
},
46+
['additions', 'removals', 'unchanged'],
47+
),
48+
lines: {
49+
type: 'array',
50+
items: objectSchema(
51+
{
52+
kind: enumSchema(['added', 'removed', 'unchanged']),
53+
text: stringSchema(),
54+
ref: stringSchema(),
55+
},
56+
['kind', 'text'],
57+
),
58+
},
59+
warnings: stringArraySchema(),
60+
},
61+
['mode', 'baselineInitialized', 'summary', 'lines'],
62+
),
63+
} satisfies Pick<Record<keyof CommandResultMap, JsonSchema>, 'diff'>;
64+
1865
const diffCommandDescription =
1966
'Compare accessibility snapshots or screenshots to identify UI changes. Use snapshot comparisons for semantic tree changes and screenshot comparisons for pixel differences.';
2067

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import assert from 'node:assert/strict';
2+
import { test } from 'vitest';
3+
import { ownerFilesForCommand } from '@agent-device/command-registry/owner-files';
4+
import { DIFF_COMMAND_OUTPUT_SCHEMAS } from '../../commands/capture/diff.ts';
5+
import { COMMAND_OUTPUT_SCHEMAS } from '../command-output-schemas.ts';
6+
7+
const DIFF_COMMANDS = Object.keys(DIFF_COMMAND_OUTPUT_SCHEMAS) as Array<
8+
keyof typeof DIFF_COMMAND_OUTPUT_SCHEMAS
9+
>;
10+
11+
// diff does not carry the post-action observation trait (#1652): it reports a snapshot
12+
// comparison, not an interaction, so the composed map never copies the entry and reference
13+
// equality must hold.
14+
test('MCP diff family output schemas are the family module entries, not copies', () => {
15+
for (const command of DIFF_COMMANDS) {
16+
assert.equal(
17+
COMMAND_OUTPUT_SCHEMAS[command],
18+
DIFF_COMMAND_OUTPUT_SCHEMAS[command],
19+
`${command} is not reference-equal to the diff module's own schema object`,
20+
);
21+
}
22+
});
23+
24+
test('the projected family map declares exactly the commands its module owns', () => {
25+
for (const command of DIFF_COMMANDS) {
26+
assert.ok(
27+
ownerFilesForCommand(command).includes('src/commands/capture/diff.ts'),
28+
`${command} projects its output schema from this module but does not name it as its owner`,
29+
);
30+
}
31+
});
32+
33+
test('the diff line kinds include unchanged context lines', () => {
34+
const lines = (
35+
DIFF_COMMAND_OUTPUT_SCHEMAS.diff as unknown as {
36+
properties: { lines: { items: { properties: { kind: { enum: string[] } } } } };
37+
}
38+
).properties.lines.items.properties.kind;
39+
assert.deepEqual(lines.enum, ['added', 'removed', 'unchanged']);
40+
});

‎src/mcp/__tests__/command-tools-replay-schemas.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { test } from 'vitest';
33
import type { ReplayCommandResult, ReplaySuiteResult } from '@agent-device/contracts/replay';
44
import { ownerFilesForCommand } from '@agent-device/command-registry/owner-files';
55
import { commandSupportsSettleObservation } from '@agent-device/command-registry/registry';
6+
import { DIFF_COMMAND_OUTPUT_SCHEMAS } from '../../commands/capture/diff.ts';
67
import { WAIT_COMMAND_OUTPUT_SCHEMAS } from '../../commands/capture/wait.ts';
78
import { PREPARE_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/prepare.ts';
89
import { DOCTOR_COMMAND_OUTPUT_SCHEMAS } from '../../commands/management/doctor.ts';
@@ -80,6 +81,7 @@ const PROJECTED_FAMILIES = [
8081
{ name: 'replay', schemas: REPLAY_COMMAND_OUTPUT_SCHEMAS },
8182
{ name: 'system', schemas: SYSTEM_COMMAND_OUTPUT_SCHEMAS },
8283
{ name: 'interaction', schemas: INTERACTION_COMMAND_OUTPUT_SCHEMAS },
84+
{ name: 'capture-diff', schemas: DIFF_COMMAND_OUTPUT_SCHEMAS },
8385
{ name: 'capture-wait', schemas: WAIT_COMMAND_OUTPUT_SCHEMAS },
8486
{ name: 'management-prepare', schemas: PREPARE_COMMAND_OUTPUT_SCHEMAS },
8587
{ name: 'management-doctor', schemas: DOCTOR_COMMAND_OUTPUT_SCHEMAS },

‎src/mcp/command-output-schemas.ts‎

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@ import type { CommandResultMap } from '@agent-device/command-registry/command-re
33
import { commandSupportsSettleObservation } from '@agent-device/command-registry/registry';
44
import {
55
booleanSchema,
6-
constSchema,
76
enumSchema,
87
numberSchema,
98
objectSchema,
10-
stringArraySchema,
119
stringSchema,
1210
} from '../commands/command-input.ts';
11+
import { DIFF_COMMAND_OUTPUT_SCHEMAS } from '../commands/capture/diff.ts';
1312
import { WAIT_COMMAND_OUTPUT_SCHEMAS } from '../commands/capture/wait.ts';
1413
import { PREPARE_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/prepare.ts';
1514
import { DOCTOR_COMMAND_OUTPUT_SCHEMAS } from '../commands/management/doctor.ts';
@@ -183,39 +182,11 @@ const BASE_COMMAND_OUTPUT_SCHEMAS = {
183182
['direction'],
184183
),
185184

186-
// packages/contracts/src/diff.ts — the public Node command accepts snapshot diffs.
187-
diff: objectSchema(
188-
{
189-
mode: constSchema('snapshot'),
190-
baselineInitialized: booleanSchema(),
191-
summary: objectSchema(
192-
{
193-
additions: numberSchema(),
194-
removals: numberSchema(),
195-
unchanged: numberSchema(),
196-
},
197-
['additions', 'removals', 'unchanged'],
198-
),
199-
lines: {
200-
type: 'array',
201-
items: objectSchema(
202-
{
203-
kind: enumSchema(['added', 'removed']),
204-
text: stringSchema(),
205-
ref: stringSchema(),
206-
},
207-
['kind', 'text'],
208-
),
209-
},
210-
warnings: stringArraySchema(),
211-
},
212-
['mode', 'baselineInitialized', 'summary', 'lines'],
213-
),
214-
215185
// A family that owns its commands authors their advertised response shape beside the
216186
// command surface and projects it here. This spread stays last: a hand-written entry for
217187
// a projected command then fails as TS2783 instead of quietly overriding the family's,
218188
// and this map's `satisfies` still refuses a missing `CommandResultMap` key.
189+
...DIFF_COMMAND_OUTPUT_SCHEMAS,
219190
...WAIT_COMMAND_OUTPUT_SCHEMAS,
220191
...PREPARE_COMMAND_OUTPUT_SCHEMAS,
221192
...DOCTOR_COMMAND_OUTPUT_SCHEMAS,

0 commit comments

Comments
 (0)