@variance-authority/mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/LICENSE +21 -0
  3. package/README.md +407 -0
  4. package/dist/bin.d.ts +3 -0
  5. package/dist/bin.js +32 -0
  6. package/dist/bin.js.map +1 -0
  7. package/dist/index.d.ts +35 -0
  8. package/dist/index.js +31 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/observability-subject.d.ts +22 -0
  11. package/dist/observability-subject.js +2 -0
  12. package/dist/observability-subject.js.map +1 -0
  13. package/dist/presentation.d.ts +6 -0
  14. package/dist/presentation.js +64 -0
  15. package/dist/presentation.js.map +1 -0
  16. package/dist/protocol.d.ts +82 -0
  17. package/dist/protocol.js +186 -0
  18. package/dist/protocol.js.map +1 -0
  19. package/dist/server.d.ts +71 -0
  20. package/dist/server.js +103 -0
  21. package/dist/server.js.map +1 -0
  22. package/dist/tools/adjudicate.d.ts +25 -0
  23. package/dist/tools/adjudicate.js +122 -0
  24. package/dist/tools/adjudicate.js.map +1 -0
  25. package/dist/tools/attention.d.ts +13 -0
  26. package/dist/tools/attention.js +151 -0
  27. package/dist/tools/attention.js.map +1 -0
  28. package/dist/tools/changelog.d.ts +38 -0
  29. package/dist/tools/changelog.js +201 -0
  30. package/dist/tools/changelog.js.map +1 -0
  31. package/dist/tools/changes.d.ts +31 -0
  32. package/dist/tools/changes.js +120 -0
  33. package/dist/tools/changes.js.map +1 -0
  34. package/dist/tools/composition.d.ts +13 -0
  35. package/dist/tools/composition.js +366 -0
  36. package/dist/tools/composition.js.map +1 -0
  37. package/dist/tools/describe.d.ts +20 -0
  38. package/dist/tools/describe.js +279 -0
  39. package/dist/tools/describe.js.map +1 -0
  40. package/dist/tools/diff.d.ts +20 -0
  41. package/dist/tools/diff.js +85 -0
  42. package/dist/tools/diff.js.map +1 -0
  43. package/dist/tools/explain-verdict.d.ts +25 -0
  44. package/dist/tools/explain-verdict.js +92 -0
  45. package/dist/tools/explain-verdict.js.map +1 -0
  46. package/dist/tools/findings.d.ts +40 -0
  47. package/dist/tools/findings.js +130 -0
  48. package/dist/tools/findings.js.map +1 -0
  49. package/dist/tools/narrowing.d.ts +26 -0
  50. package/dist/tools/narrowing.js +38 -0
  51. package/dist/tools/narrowing.js.map +1 -0
  52. package/dist/tools/observability.d.ts +7 -0
  53. package/dist/tools/observability.js +199 -0
  54. package/dist/tools/observability.js.map +1 -0
  55. package/dist/tools/presentations.d.ts +6 -0
  56. package/dist/tools/presentations.js +74 -0
  57. package/dist/tools/presentations.js.map +1 -0
  58. package/dist/tools/run-signals.d.ts +12 -0
  59. package/dist/tools/run-signals.js +115 -0
  60. package/dist/tools/run-signals.js.map +1 -0
  61. package/dist/tools/scenarios.d.ts +6 -0
  62. package/dist/tools/scenarios.js +62 -0
  63. package/dist/tools/scenarios.js.map +1 -0
  64. package/dist/tools/source-tests.d.ts +5 -0
  65. package/dist/tools/source-tests.js +95 -0
  66. package/dist/tools/source-tests.js.map +1 -0
  67. package/dist/tools/subject.d.ts +45 -0
  68. package/dist/tools/subject.js +57 -0
  69. package/dist/tools/subject.js.map +1 -0
  70. package/dist/tools/summary.d.ts +31 -0
  71. package/dist/tools/summary.js +436 -0
  72. package/dist/tools/summary.js.map +1 -0
  73. package/dist/tools/test-signals.d.ts +16 -0
  74. package/dist/tools/test-signals.js +106 -0
  75. package/dist/tools/test-signals.js.map +1 -0
  76. package/dist/tools/tool.d.ts +64 -0
  77. package/dist/tools/tool.js +9 -0
  78. package/dist/tools/tool.js.map +1 -0
  79. package/dist/tools/trace-component.d.ts +17 -0
  80. package/dist/tools/trace-component.js +50 -0
  81. package/dist/tools/trace-component.js.map +1 -0
  82. package/dist/tools/vantage-lines.d.ts +44 -0
  83. package/dist/tools/vantage-lines.js +66 -0
  84. package/dist/tools/vantage-lines.js.map +1 -0
  85. package/dist/tools/variations.d.ts +27 -0
  86. package/dist/tools/variations.js +125 -0
  87. package/dist/tools/variations.js.map +1 -0
  88. package/dist/tools.d.ts +120 -0
  89. package/dist/tools.js +127 -0
  90. package/dist/tools.js.map +1 -0
  91. package/mark.svg +30 -0
  92. package/package.json +64 -0
@@ -0,0 +1,22 @@
1
+ import type { EyesArchive } from '@variance-authority/eyes';
2
+ import type { PresentationReport } from '@variance-authority/presentation';
3
+ import type { RunReport } from '@variance-authority/report';
4
+ import type { ScenarioArchiveManifest } from '@variance-authority/scenario/archive';
5
+ import type { ExecutionIndex } from '@variance-authority/sense/test-selection';
6
+ import type { VantageState } from '@variance-authority/vantage';
7
+ /**
8
+ * Independently produced observability domains supplied to one MCP connection.
9
+ *
10
+ * Every field is optional because no instrument may stand in for another. A
11
+ * missing field means no producer supplied that domain; a present empty value
12
+ * means its producer measured an empty set.
13
+ */
14
+ export interface ObservabilitySubject {
15
+ readonly report?: RunReport;
16
+ readonly presentations?: readonly PresentationReport[];
17
+ readonly execution?: ExecutionIndex;
18
+ readonly vantage?: VantageState;
19
+ readonly eyes?: EyesArchive;
20
+ readonly scenarios?: readonly ScenarioArchiveManifest[];
21
+ }
22
+ //# sourceMappingURL=observability-subject.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=observability-subject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observability-subject.js","sourceRoot":"","sources":["../src/observability-subject.ts"],"names":[],"mappings":"","sourcesContent":["import type { EyesArchive } from '@variance-authority/eyes';\nimport type { PresentationReport } from '@variance-authority/presentation';\nimport type { RunReport } from '@variance-authority/report';\nimport type { ScenarioArchiveManifest } from '@variance-authority/scenario/archive';\nimport type { ExecutionIndex } from '@variance-authority/sense/test-selection';\nimport type { VantageState } from '@variance-authority/vantage';\n\n/**\n * Independently produced observability domains supplied to one MCP connection.\n *\n * Every field is optional because no instrument may stand in for another. A\n * missing field means no producer supplied that domain; a present empty value\n * means its producer measured an empty set.\n */\nexport interface ObservabilitySubject {\n readonly report?: RunReport;\n readonly presentations?: readonly PresentationReport[];\n readonly execution?: ExecutionIndex;\n readonly vantage?: VantageState;\n readonly eyes?: EyesArchive;\n readonly scenarios?: readonly ScenarioArchiveManifest[];\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { PresentationSignalRecord, RunReport } from '@variance-authority/report';
2
+ /** Presentation consequence in the shared text vocabulary used by CLI and MCP. */
3
+ export declare function presentationSummary(report: RunReport): readonly string[];
4
+ /** One subject's durable presentation consequence, without re-running analysis. */
5
+ export declare function describePresentation(signal: PresentationSignalRecord): readonly string[];
6
+ //# sourceMappingURL=presentation.d.ts.map
@@ -0,0 +1,64 @@
1
+ /** Presentation consequence in the shared text vocabulary used by CLI and MCP. */
2
+ export function presentationSummary(report) {
3
+ const observed = report.observations.filter((entry) => entry.signals?.presentation !== undefined);
4
+ if (observed.length === 0)
5
+ return [];
6
+ const comparable = observed.filter((entry) => entry.signals.presentation.verdict !== 'incomparable');
7
+ const incomparable = observed.length - comparable.length;
8
+ const effects = comparable.flatMap((entry) => {
9
+ const signal = entry.signals.presentation;
10
+ return signal.verdict === 'incomparable' ? [] : signal.effects;
11
+ });
12
+ const counts = new Map();
13
+ for (const effect of effects)
14
+ counts.set(effect.transition, (counts.get(effect.transition) ?? 0) + 1);
15
+ const changed = comparable.filter((entry) => entry.signals.presentation.verdict === 'changed').length;
16
+ return [
17
+ `presentation impact: ${counts.get('introduced') ?? 0} introduced, ` +
18
+ `${counts.get('resolved') ?? 0} resolved, ${counts.get('persisted') ?? 0} persisted ` +
19
+ `across ${changed} changed subject(s); ${comparable.length} compared, ${incomparable} incomparable. ` +
20
+ 'Independent of the regression verdict.',
21
+ ];
22
+ }
23
+ /** One subject's durable presentation consequence, without re-running analysis. */
24
+ export function describePresentation(signal) {
25
+ if (signal.verdict === 'incomparable') {
26
+ return [`presentation: incomparable — ${signal.because}`];
27
+ }
28
+ const information = signal.information;
29
+ const info = [
30
+ information.contentPreserved ? 'content preserved' : 'content changed',
31
+ delta('characters', information.characters.delta),
32
+ delta('elements', information.elements.delta),
33
+ delta('repeated objects', information.repeatedObjects.delta),
34
+ ].filter((part) => part !== '');
35
+ if (signal.effects.length === 0) {
36
+ return [`presentation: ${signal.verdict} — ${info.join(', ')}`];
37
+ }
38
+ return [
39
+ `presentation: ${signal.verdict} — ${info.join(', ')}`,
40
+ ...signal.effects.flatMap(effectLines),
41
+ ];
42
+ }
43
+ function effectLines(effect) {
44
+ const identity = ` [${effect.transition}] ${effect.rule} at ${effect.owner}` +
45
+ (effect.contract === undefined ? '' : ` (contract ${effect.contract})`);
46
+ if (effect.transition === 'introduced')
47
+ return [identity, evidenceLine('after', effect.after)];
48
+ if (effect.transition === 'resolved')
49
+ return [identity, evidenceLine('before', effect.before)];
50
+ return [identity, evidenceLine('before', effect.before), evidenceLine('after', effect.after)];
51
+ }
52
+ function evidenceLine(side, evidence) {
53
+ const measurements = Object.entries(evidence.measurements)
54
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
55
+ .map(([name, value]) => `${name}=${value}`)
56
+ .join(', ');
57
+ return ` ${side} ${evidence.finding}${measurements === '' ? '' : `: ${measurements}`}`;
58
+ }
59
+ function delta(name, value) {
60
+ if (value === 0)
61
+ return '';
62
+ return `${name} ${value > 0 ? '+' : ''}${value}`;
63
+ }
64
+ //# sourceMappingURL=presentation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presentation.js","sourceRoot":"","sources":["../src/presentation.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n PresentationEffectEvidence,\n PresentationEffectRecord,\n PresentationSignalRecord,\n RunReport,\n} from '@variance-authority/report';\n\n/** Presentation consequence in the shared text vocabulary used by CLI and MCP. */\nexport function presentationSummary(report: RunReport): readonly string[] {\n const observed = report.observations.filter((entry) => entry.signals?.presentation !== undefined);\n if (observed.length === 0) return [];\n const comparable = observed.filter((entry) => entry.signals!.presentation!.verdict !== 'incomparable');\n const incomparable = observed.length - comparable.length;\n const effects = comparable.flatMap((entry) => {\n const signal = entry.signals!.presentation!;\n return signal.verdict === 'incomparable' ? [] : signal.effects;\n });\n const counts = new Map<string, number>();\n for (const effect of effects) counts.set(effect.transition, (counts.get(effect.transition) ?? 0) + 1);\n const changed = comparable.filter((entry) => entry.signals!.presentation!.verdict === 'changed').length;\n\n return [\n `presentation impact: ${counts.get('introduced') ?? 0} introduced, ` +\n `${counts.get('resolved') ?? 0} resolved, ${counts.get('persisted') ?? 0} persisted ` +\n `across ${changed} changed subject(s); ${comparable.length} compared, ${incomparable} incomparable. ` +\n 'Independent of the regression verdict.',\n ];\n}\n\n/** One subject's durable presentation consequence, without re-running analysis. */\nexport function describePresentation(signal: PresentationSignalRecord): readonly string[] {\n if (signal.verdict === 'incomparable') {\n return [`presentation: incomparable — ${signal.because}`];\n }\n const information = signal.information;\n const info = [\n information.contentPreserved ? 'content preserved' : 'content changed',\n delta('characters', information.characters.delta),\n delta('elements', information.elements.delta),\n delta('repeated objects', information.repeatedObjects.delta),\n ].filter((part) => part !== '');\n if (signal.effects.length === 0) {\n return [`presentation: ${signal.verdict} — ${info.join(', ')}`];\n }\n return [\n `presentation: ${signal.verdict} — ${info.join(', ')}`,\n ...signal.effects.flatMap(effectLines),\n ];\n}\n\nfunction effectLines(effect: PresentationEffectRecord): readonly string[] {\n const identity =\n ` [${effect.transition}] ${effect.rule} at ${effect.owner}` +\n (effect.contract === undefined ? '' : ` (contract ${effect.contract})`);\n if (effect.transition === 'introduced') return [identity, evidenceLine('after', effect.after!)];\n if (effect.transition === 'resolved') return [identity, evidenceLine('before', effect.before!)];\n return [identity, evidenceLine('before', effect.before!), evidenceLine('after', effect.after!)];\n}\n\nfunction evidenceLine(side: string, evidence: PresentationEffectEvidence): string {\n const measurements = Object.entries(evidence.measurements)\n .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))\n .map(([name, value]) => `${name}=${value}`)\n .join(', ');\n return ` ${side} ${evidence.finding}${measurements === '' ? '' : `: ${measurements}`}`;\n}\n\nfunction delta(name: string, value: number): string {\n if (value === 0) return '';\n return `${name} ${value > 0 ? '+' : ''}${value}`;\n}\n"]}
@@ -0,0 +1,82 @@
1
+ import type { EyesArchive } from '@variance-authority/eyes';
2
+ import type { PresentationReport } from '@variance-authority/presentation';
3
+ import type { RunReport } from '@variance-authority/report';
4
+ import type { ScenarioArchiveManifest } from '@variance-authority/scenario/archive';
5
+ import type { ExecutionIndex } from '@variance-authority/sense/test-selection';
6
+ import type { VantageState } from '@variance-authority/vantage';
7
+ import type { ObservabilitySubject } from './observability-subject.js';
8
+ import type { Served, ToolInvocation } from './tools/tool.js';
9
+ /**
10
+ * MCP over stdio, written out rather than depended on.
11
+ *
12
+ * The protocol is JSON-RPC 2.0 with newline-delimited framing and three methods
13
+ * that matter. Writing it is roughly a hundred lines; depending on an SDK to
14
+ * obtain those hundred lines costs a package, a version, and a transitive tree
15
+ * in a repository whose `core` went to the trouble of implementing SHA-256 to
16
+ * avoid depending on `node:crypto` (ADR-0006). The same judgement applies.
17
+ *
18
+ * The payoff is the same too: `handle` is a pure function from a request to a
19
+ * response. No sockets, no streams, no process. Every behaviour below can be
20
+ * asserted by calling it.
21
+ */
22
+ export declare const PROTOCOL_VERSION = "2024-11-05";
23
+ export declare const SERVER_NAME = "variance-authority";
24
+ export declare const SERVER_VERSION = "0.0.0";
25
+ export interface JsonRpcRequest {
26
+ readonly jsonrpc: '2.0';
27
+ /** Absent on a notification, which must not be answered. */
28
+ readonly id?: string | number;
29
+ readonly method: string;
30
+ readonly params?: Readonly<Record<string, unknown>>;
31
+ }
32
+ export interface JsonRpcResponse {
33
+ readonly jsonrpc: '2.0';
34
+ readonly id: string | number;
35
+ readonly result?: unknown;
36
+ readonly error?: {
37
+ readonly code: number;
38
+ readonly message: string;
39
+ };
40
+ }
41
+ /** The visual-report tools this package ships, as the thing `handle` is handed. */
42
+ export declare const REPORTS: Served<RunReport>;
43
+ /** The MCP surface for agents asking which named tests exercise source. */
44
+ export declare const SOURCE_TESTS: Served<ExecutionIndex>;
45
+ /**
46
+ * The MCP surface for an agent watching a suite that has not finished.
47
+ *
48
+ * The only served set that instructs. The other two answer about something that
49
+ * already exists; this one answers about a run somebody still has to start, and
50
+ * an agent that is not told the variable at the handshake starts it without one.
51
+ */
52
+ export declare const VANTAGE: Served<VantageState>;
53
+ /** The MCP surface for synchronously captured test attention. */
54
+ export declare const EYES: Served<EyesArchive>;
55
+ /** The MCP surface for full presentation graphs held by their caller. */
56
+ export declare const PRESENTATIONS: Served<readonly PresentationReport[]>;
57
+ /** The MCP surface for retained scenario executions. */
58
+ export declare const SCENARIOS: Served<readonly ScenarioArchiveManifest[]>;
59
+ /**
60
+ * The MCP surface that exposes every supplied observability domain without
61
+ * merging their evidence or inventing cross-domain identity.
62
+ */
63
+ export declare const OBSERVABILITY: Served<ObservabilitySubject>;
64
+ /**
65
+ * Answer one request. `null` means "no response", which is not the same as an
66
+ * empty one — a notification that gets answered is a protocol violation.
67
+ *
68
+ * `served` is required rather than defaulted to {@link REPORTS}. A default would
69
+ * infer its subject from whatever the second argument returns and then answer it
70
+ * with report tools, which is the class of mistake this file exists to make
71
+ * impossible: a server that speaks fluently about the wrong thing.
72
+ */
73
+ export declare function handle<Subject>(request: JsonRpcRequest, subject: () => Subject, served: Served<Subject>, invocation?: ToolInvocation<Subject>): JsonRpcResponse | null;
74
+ /**
75
+ * Newline-delimited framing.
76
+ *
77
+ * Buffers partial lines because a stdio chunk boundary falls wherever the OS
78
+ * puts it, not where a JSON value ends. Getting this wrong produces a parse
79
+ * error under load and never in a test.
80
+ */
81
+ export declare function createLineReader(onLine: (line: string) => void): (chunk: string) => void;
82
+ //# sourceMappingURL=protocol.d.ts.map
@@ -0,0 +1,186 @@
1
+ import { EYES_TOOLS, OBSERVABILITY_TOOLS, PRESENTATION_TOOLS, SCENARIO_TOOLS, SOURCE_TEST_TOOLS, TOOLS, VANTAGE_TOOLS, } from './tools.js';
2
+ import { attaching } from './tools/vantage-lines.js';
3
+ /**
4
+ * MCP over stdio, written out rather than depended on.
5
+ *
6
+ * The protocol is JSON-RPC 2.0 with newline-delimited framing and three methods
7
+ * that matter. Writing it is roughly a hundred lines; depending on an SDK to
8
+ * obtain those hundred lines costs a package, a version, and a transitive tree
9
+ * in a repository whose `core` went to the trouble of implementing SHA-256 to
10
+ * avoid depending on `node:crypto` (ADR-0006). The same judgement applies.
11
+ *
12
+ * The payoff is the same too: `handle` is a pure function from a request to a
13
+ * response. No sockets, no streams, no process. Every behaviour below can be
14
+ * asserted by calling it.
15
+ */
16
+ export const PROTOCOL_VERSION = '2024-11-05';
17
+ export const SERVER_NAME = 'variance-authority';
18
+ export const SERVER_VERSION = '0.0.0';
19
+ /** The visual-report tools this package ships, as the thing `handle` is handed. */
20
+ export const REPORTS = {
21
+ name: SERVER_NAME,
22
+ version: SERVER_VERSION,
23
+ tools: TOOLS,
24
+ };
25
+ /** The MCP surface for agents asking which named tests exercise source. */
26
+ export const SOURCE_TESTS = {
27
+ name: SERVER_NAME,
28
+ version: SERVER_VERSION,
29
+ tools: SOURCE_TEST_TOOLS,
30
+ };
31
+ /**
32
+ * The MCP surface for an agent watching a suite that has not finished.
33
+ *
34
+ * The only served set that instructs. The other two answer about something that
35
+ * already exists; this one answers about a run somebody still has to start, and
36
+ * an agent that is not told the variable at the handshake starts it without one.
37
+ */
38
+ export const VANTAGE = {
39
+ name: SERVER_NAME,
40
+ version: SERVER_VERSION,
41
+ tools: VANTAGE_TOOLS,
42
+ instructions: (state) => [
43
+ 'This server watches a test run while it happens. It answers about a suite ' +
44
+ 'in flight rather than about a report on disk, and none of it is limited ' +
45
+ 'to visual tests.',
46
+ '',
47
+ 'Ask `variance_run_signals` for where the run has got to and which test is ' +
48
+ 'still going, and `variance_test_signals` for everything one test has ' +
49
+ 'announced — in order, with the realm that said each, plus work that ' +
50
+ 'started and never ended. Both answer while the test is still running, ' +
51
+ 'which is what a runner cannot do from outside the worker.',
52
+ '',
53
+ attaching(state),
54
+ ].join('\n'),
55
+ };
56
+ /** The MCP surface for synchronously captured test attention. */
57
+ export const EYES = {
58
+ name: SERVER_NAME,
59
+ version: SERVER_VERSION,
60
+ tools: EYES_TOOLS,
61
+ };
62
+ /** The MCP surface for full presentation graphs held by their caller. */
63
+ export const PRESENTATIONS = {
64
+ name: SERVER_NAME,
65
+ version: SERVER_VERSION,
66
+ tools: PRESENTATION_TOOLS,
67
+ };
68
+ /** The MCP surface for retained scenario executions. */
69
+ export const SCENARIOS = {
70
+ name: SERVER_NAME,
71
+ version: SERVER_VERSION,
72
+ tools: SCENARIO_TOOLS,
73
+ };
74
+ /**
75
+ * The MCP surface that exposes every supplied observability domain without
76
+ * merging their evidence or inventing cross-domain identity.
77
+ */
78
+ export const OBSERVABILITY = {
79
+ name: SERVER_NAME,
80
+ version: SERVER_VERSION,
81
+ tools: OBSERVABILITY_TOOLS,
82
+ instructions: () => [
83
+ 'This connection can expose visual reports, full presentation readings and durable signals, runtime journeys, ' +
84
+ 'live events, Eyes attention and retained scenarios. Start with `variance_observability` ' +
85
+ 'to learn which independent evidence domains were supplied.',
86
+ '',
87
+ 'Unavailable evidence is not an empty measurement. Cross-domain answers join only on ' +
88
+ 'exact identities emitted by both producers. `variance_testing_surface` reports replay ' +
89
+ 'candidates; it does not establish that a branch is safe to mock.',
90
+ ].join('\n'),
91
+ };
92
+ const METHOD_NOT_FOUND = -32601;
93
+ const INVALID_PARAMS = -32602;
94
+ /**
95
+ * Answer one request. `null` means "no response", which is not the same as an
96
+ * empty one — a notification that gets answered is a protocol violation.
97
+ *
98
+ * `served` is required rather than defaulted to {@link REPORTS}. A default would
99
+ * infer its subject from whatever the second argument returns and then answer it
100
+ * with report tools, which is the class of mistake this file exists to make
101
+ * impossible: a server that speaks fluently about the wrong thing.
102
+ */
103
+ export function handle(request, subject, served, invocation) {
104
+ if (request.id === undefined)
105
+ return null;
106
+ const id = request.id;
107
+ switch (request.method) {
108
+ case 'initialize':
109
+ return ok(id, {
110
+ protocolVersion: PROTOCOL_VERSION,
111
+ capabilities: { tools: {} },
112
+ serverInfo: { name: served.name, version: served.version },
113
+ // Read at the handshake rather than fixed at construction, because the
114
+ // one thing worth saying here — the address a run reports to — is not
115
+ // known until the listener has a port.
116
+ ...(served.instructions === undefined
117
+ ? {}
118
+ : { instructions: served.instructions(subject()) }),
119
+ });
120
+ case 'ping':
121
+ return ok(id, {});
122
+ case 'tools/list':
123
+ return ok(id, {
124
+ tools: served.tools.map((tool) => ({
125
+ name: tool.name,
126
+ description: tool.description,
127
+ inputSchema: tool.inputSchema,
128
+ })),
129
+ });
130
+ case 'tools/call':
131
+ return callTool(id, request.params ?? {}, subject, served.tools, invocation);
132
+ default:
133
+ return fail(id, METHOD_NOT_FOUND, `unknown method: ${request.method}`);
134
+ }
135
+ }
136
+ function callTool(id, params, subject, tools, invocation) {
137
+ const name = params['name'];
138
+ if (typeof name !== 'string')
139
+ return fail(id, INVALID_PARAMS, 'tools/call requires a name');
140
+ const tool = tools.find((candidate) => candidate.name === name);
141
+ if (tool === undefined)
142
+ return fail(id, INVALID_PARAMS, `unknown tool: ${name}`);
143
+ try {
144
+ const text = tool.run(subject(), (params['arguments'] ?? {}), invocation);
145
+ return ok(id, { content: [{ type: 'text', text }] });
146
+ }
147
+ catch (error) {
148
+ // A tool failure is a *result* with `isError`, not a JSON-RPC error. The
149
+ // distinction matters here: a transport error is invisible to the model,
150
+ // while an error result is text it can read and correct from — and "unknown
151
+ // subject; this run has: …" is precisely the kind of correction worth
152
+ // handing back rather than swallowing.
153
+ return ok(id, {
154
+ content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
155
+ isError: true,
156
+ });
157
+ }
158
+ }
159
+ function ok(id, result) {
160
+ return { jsonrpc: '2.0', id, result };
161
+ }
162
+ function fail(id, code, message) {
163
+ return { jsonrpc: '2.0', id, error: { code, message } };
164
+ }
165
+ /**
166
+ * Newline-delimited framing.
167
+ *
168
+ * Buffers partial lines because a stdio chunk boundary falls wherever the OS
169
+ * puts it, not where a JSON value ends. Getting this wrong produces a parse
170
+ * error under load and never in a test.
171
+ */
172
+ export function createLineReader(onLine) {
173
+ let buffer = '';
174
+ return (chunk) => {
175
+ buffer += chunk;
176
+ let index = buffer.indexOf('\n');
177
+ while (index !== -1) {
178
+ const line = buffer.slice(0, index).trim();
179
+ buffer = buffer.slice(index + 1);
180
+ if (line !== '')
181
+ onLine(line);
182
+ index = buffer.indexOf('\n');
183
+ }
184
+ };
185
+ }
186
+ //# sourceMappingURL=protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["import type { EyesArchive } from '@variance-authority/eyes';\nimport type { PresentationReport } from '@variance-authority/presentation';\nimport type { RunReport } from '@variance-authority/report';\nimport type { ScenarioArchiveManifest } from '@variance-authority/scenario/archive';\nimport type { ExecutionIndex } from '@variance-authority/sense/test-selection';\nimport type { VantageState } from '@variance-authority/vantage';\nimport type { ObservabilitySubject } from './observability-subject.js';\nimport type { Served, Tool, ToolInvocation } from './tools/tool.js';\nimport {\n EYES_TOOLS,\n OBSERVABILITY_TOOLS,\n PRESENTATION_TOOLS,\n SCENARIO_TOOLS,\n SOURCE_TEST_TOOLS,\n TOOLS,\n VANTAGE_TOOLS,\n} from './tools.js';\nimport { attaching } from './tools/vantage-lines.js';\n\n/**\n * MCP over stdio, written out rather than depended on.\n *\n * The protocol is JSON-RPC 2.0 with newline-delimited framing and three methods\n * that matter. Writing it is roughly a hundred lines; depending on an SDK to\n * obtain those hundred lines costs a package, a version, and a transitive tree\n * in a repository whose `core` went to the trouble of implementing SHA-256 to\n * avoid depending on `node:crypto` (ADR-0006). The same judgement applies.\n *\n * The payoff is the same too: `handle` is a pure function from a request to a\n * response. No sockets, no streams, no process. Every behaviour below can be\n * asserted by calling it.\n */\n\nexport const PROTOCOL_VERSION = '2024-11-05';\nexport const SERVER_NAME = 'variance-authority';\nexport const SERVER_VERSION = '0.0.0';\n\nexport interface JsonRpcRequest {\n readonly jsonrpc: '2.0';\n /** Absent on a notification, which must not be answered. */\n readonly id?: string | number;\n readonly method: string;\n readonly params?: Readonly<Record<string, unknown>>;\n}\n\nexport interface JsonRpcResponse {\n readonly jsonrpc: '2.0';\n readonly id: string | number;\n readonly result?: unknown;\n readonly error?: { readonly code: number; readonly message: string };\n}\n\n/** The visual-report tools this package ships, as the thing `handle` is handed. */\nexport const REPORTS: Served<RunReport> = {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n tools: TOOLS,\n};\n\n/** The MCP surface for agents asking which named tests exercise source. */\nexport const SOURCE_TESTS: Served<ExecutionIndex> = {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n tools: SOURCE_TEST_TOOLS,\n};\n\n/**\n * The MCP surface for an agent watching a suite that has not finished.\n *\n * The only served set that instructs. The other two answer about something that\n * already exists; this one answers about a run somebody still has to start, and\n * an agent that is not told the variable at the handshake starts it without one.\n */\nexport const VANTAGE: Served<VantageState> = {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n tools: VANTAGE_TOOLS,\n instructions: (state) =>\n [\n 'This server watches a test run while it happens. It answers about a suite ' +\n 'in flight rather than about a report on disk, and none of it is limited ' +\n 'to visual tests.',\n '',\n 'Ask `variance_run_signals` for where the run has got to and which test is ' +\n 'still going, and `variance_test_signals` for everything one test has ' +\n 'announced — in order, with the realm that said each, plus work that ' +\n 'started and never ended. Both answer while the test is still running, ' +\n 'which is what a runner cannot do from outside the worker.',\n '',\n attaching(state),\n ].join('\\n'),\n};\n\n/** The MCP surface for synchronously captured test attention. */\nexport const EYES: Served<EyesArchive> = {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n tools: EYES_TOOLS,\n};\n\n/** The MCP surface for full presentation graphs held by their caller. */\nexport const PRESENTATIONS: Served<readonly PresentationReport[]> = {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n tools: PRESENTATION_TOOLS,\n};\n\n/** The MCP surface for retained scenario executions. */\nexport const SCENARIOS: Served<readonly ScenarioArchiveManifest[]> = {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n tools: SCENARIO_TOOLS,\n};\n\n/**\n * The MCP surface that exposes every supplied observability domain without\n * merging their evidence or inventing cross-domain identity.\n */\nexport const OBSERVABILITY: Served<ObservabilitySubject> = {\n name: SERVER_NAME,\n version: SERVER_VERSION,\n tools: OBSERVABILITY_TOOLS,\n instructions: () => [\n 'This connection can expose visual reports, full presentation readings and durable signals, runtime journeys, ' +\n 'live events, Eyes attention and retained scenarios. Start with `variance_observability` ' +\n 'to learn which independent evidence domains were supplied.',\n '',\n 'Unavailable evidence is not an empty measurement. Cross-domain answers join only on ' +\n 'exact identities emitted by both producers. `variance_testing_surface` reports replay ' +\n 'candidates; it does not establish that a branch is safe to mock.',\n ].join('\\n'),\n};\n\nconst METHOD_NOT_FOUND = -32601;\nconst INVALID_PARAMS = -32602;\n\n/**\n * Answer one request. `null` means \"no response\", which is not the same as an\n * empty one — a notification that gets answered is a protocol violation.\n *\n * `served` is required rather than defaulted to {@link REPORTS}. A default would\n * infer its subject from whatever the second argument returns and then answer it\n * with report tools, which is the class of mistake this file exists to make\n * impossible: a server that speaks fluently about the wrong thing.\n */\nexport function handle<Subject>(\n request: JsonRpcRequest,\n subject: () => Subject,\n served: Served<Subject>,\n invocation?: ToolInvocation<Subject>,\n): JsonRpcResponse | null {\n if (request.id === undefined) return null;\n const id = request.id;\n\n switch (request.method) {\n case 'initialize':\n return ok(id, {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: { tools: {} },\n serverInfo: { name: served.name, version: served.version },\n // Read at the handshake rather than fixed at construction, because the\n // one thing worth saying here — the address a run reports to — is not\n // known until the listener has a port.\n ...(served.instructions === undefined\n ? {}\n : { instructions: served.instructions(subject()) }),\n });\n\n case 'ping':\n return ok(id, {});\n\n case 'tools/list':\n return ok(id, {\n tools: served.tools.map((tool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n })),\n });\n\n case 'tools/call':\n return callTool(id, request.params ?? {}, subject, served.tools, invocation);\n\n default:\n return fail(id, METHOD_NOT_FOUND, `unknown method: ${request.method}`);\n }\n}\n\nfunction callTool<Subject>(\n id: string | number,\n params: Readonly<Record<string, unknown>>,\n subject: () => Subject,\n tools: readonly Tool<Subject>[],\n invocation?: ToolInvocation<Subject>,\n): JsonRpcResponse {\n const name = params['name'];\n if (typeof name !== 'string') return fail(id, INVALID_PARAMS, 'tools/call requires a name');\n\n const tool = tools.find((candidate) => candidate.name === name);\n if (tool === undefined) return fail(id, INVALID_PARAMS, `unknown tool: ${name}`);\n\n try {\n const text = tool.run(\n subject(),\n (params['arguments'] ?? {}) as Record<string, unknown>,\n invocation,\n );\n return ok(id, { content: [{ type: 'text', text }] });\n } catch (error) {\n // A tool failure is a *result* with `isError`, not a JSON-RPC error. The\n // distinction matters here: a transport error is invisible to the model,\n // while an error result is text it can read and correct from — and \"unknown\n // subject; this run has: …\" is precisely the kind of correction worth\n // handing back rather than swallowing.\n return ok(id, {\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],\n isError: true,\n });\n }\n}\n\nfunction ok(id: string | number, result: unknown): JsonRpcResponse {\n return { jsonrpc: '2.0', id, result };\n}\n\nfunction fail(id: string | number, code: number, message: string): JsonRpcResponse {\n return { jsonrpc: '2.0', id, error: { code, message } };\n}\n\n/**\n * Newline-delimited framing.\n *\n * Buffers partial lines because a stdio chunk boundary falls wherever the OS\n * puts it, not where a JSON value ends. Getting this wrong produces a parse\n * error under load and never in a test.\n */\nexport function createLineReader(onLine: (line: string) => void): (chunk: string) => void {\n let buffer = '';\n\n return (chunk: string): void => {\n buffer += chunk;\n\n let index = buffer.indexOf('\\n');\n while (index !== -1) {\n const line = buffer.slice(0, index).trim();\n buffer = buffer.slice(index + 1);\n if (line !== '') onLine(line);\n index = buffer.indexOf('\\n');\n }\n };\n}\n"]}
@@ -0,0 +1,71 @@
1
+ import type { Readable, Writable } from 'node:stream';
2
+ import type { RunReport } from '@variance-authority/report';
3
+ import type { Served } from './tools/tool.js';
4
+ /**
5
+ * The transport, and nothing else.
6
+ *
7
+ * Everything decidable lives in `protocol.ts` and `tools.ts`, both of which are
8
+ * pure. What is left here is stream plumbing, which is the part that cannot be
9
+ * unit-tested meaningfully and therefore the part that should contain no
10
+ * decisions.
11
+ */
12
+ export interface ServerOptions<Subject = RunReport> {
13
+ readonly input: Readable;
14
+ readonly output: Writable;
15
+ /** Which tools answer, and what the server calls itself. */
16
+ readonly served: Served<Subject>;
17
+ /**
18
+ * Supplies the current subject.
19
+ *
20
+ * A function rather than a value so a long-lived server picks up a re-run
21
+ * without a restart — an agent that fixes something and asks again should be
22
+ * answered from the new report, not from the one loaded at boot.
23
+ *
24
+ * It may be async, and the request waits for it. A supplier that kicked off a
25
+ * refresh and answered from the previous value would make *this* request the
26
+ * stale one, which is the request that matters: the agent asking is the agent
27
+ * that just re-ran.
28
+ */
29
+ readonly subject: () => Subject | Promise<Subject>;
30
+ }
31
+ export declare function serve<Subject>(options: ServerOptions<Subject>): () => void;
32
+ /**
33
+ * Where a report-file server reads and writes. Both default to this process's
34
+ * stdio; they are parameters so the reload below is reachable from a test.
35
+ */
36
+ export interface ReportFileOptions {
37
+ readonly input?: Readable;
38
+ readonly output?: Writable;
39
+ }
40
+ /**
41
+ * Serve a report file, re-read before each request is answered.
42
+ *
43
+ * The reload is why `report` is a function, and awaiting it is why the answer is
44
+ * current rather than one request behind. An agent that fixes something, re-runs,
45
+ * and asks again is the whole reason this server outlives a single report — and
46
+ * answering that agent from the copy loaded at boot tells it the edit did not
47
+ * take, which is the one sentence here nobody should ever produce falsely.
48
+ */
49
+ export declare function serveReportFile(path: string, streams?: ReportFileOptions): Promise<() => void>;
50
+ /** A watcher that is listening, and the address a run must be started with. */
51
+ export interface ServedVantage {
52
+ /** What to put in the run's environment, verbatim. */
53
+ readonly address: string;
54
+ readonly stop: () => Promise<void>;
55
+ }
56
+ /**
57
+ * Serve a suite that has not finished.
58
+ *
59
+ * The subject is a **snapshot** rather than the store, and that is the whole of
60
+ * why a live subject fits a surface built for files. A store is a thing a socket
61
+ * writes into while an answer is being composed; a snapshot is a value, so every
62
+ * tool here stays what every tool here is — a pure function over evidence — and
63
+ * `variance_diff` gets a coherent previous state to compare against instead of
64
+ * an object that changed underneath it.
65
+ *
66
+ * Nothing is written to disk, and nothing outlives this process. That is the
67
+ * position `@variance-authority/event` takes about announcements, kept: what
68
+ * changes is how long one execution lasts when somebody is watching it.
69
+ */
70
+ export declare function serveVantage(streams?: ReportFileOptions): Promise<ServedVantage>;
71
+ //# sourceMappingURL=server.d.ts.map
package/dist/server.js ADDED
@@ -0,0 +1,103 @@
1
+ import { readRunReport } from '@variance-authority/report/file';
2
+ import { attachVantage } from '@variance-authority/vantage/attach';
3
+ import { REPORTS, VANTAGE, createLineReader, handle } from './protocol.js';
4
+ export function serve(options) {
5
+ const write = (value) => {
6
+ options.output.write(`${JSON.stringify(value)}\n`);
7
+ };
8
+ // Requests are answered in the order they arrived. JSON-RPC matches responses
9
+ // by id and would tolerate any order, but a supplier that awaits makes the
10
+ // ordering depend on how long each read took, and an interleaving that only
11
+ // shows up under a slow disk is not a thing to debug later.
12
+ let queue = Promise.resolve();
13
+ let previous;
14
+ const read = createLineReader((line) => {
15
+ let request;
16
+ try {
17
+ request = JSON.parse(line);
18
+ }
19
+ catch {
20
+ // Unparseable input has no id, so there is nothing to answer. Answering
21
+ // with a null id would be a second protocol violation on top of the first.
22
+ return;
23
+ }
24
+ queue = queue.then(async () => {
25
+ const subject = await options.subject();
26
+ const response = handle(request, () => subject, options.served, previous === undefined ? {} : { previous });
27
+ if (request.method === 'tools/call' && succeeded(response)) {
28
+ previous = structuredClone(subject);
29
+ }
30
+ if (response !== null)
31
+ write(response);
32
+ });
33
+ });
34
+ const onData = (chunk) => read(chunk.toString());
35
+ options.input.on('data', onData);
36
+ return () => options.input.off('data', onData);
37
+ }
38
+ function succeeded(response) {
39
+ if (response === null || response.error !== undefined)
40
+ return false;
41
+ return response.result?.isError !== true;
42
+ }
43
+ /**
44
+ * Serve a report file, re-read before each request is answered.
45
+ *
46
+ * The reload is why `report` is a function, and awaiting it is why the answer is
47
+ * current rather than one request behind. An agent that fixes something, re-runs,
48
+ * and asks again is the whole reason this server outlives a single report — and
49
+ * answering that agent from the copy loaded at boot tells it the edit did not
50
+ * take, which is the one sentence here nobody should ever produce falsely.
51
+ */
52
+ export async function serveReportFile(path, streams = {}) {
53
+ // Read once before serving, so a path that is not a run report fails at
54
+ // startup rather than on whichever request happens to arrive first.
55
+ let cached = await readRunReport(path);
56
+ return serve({
57
+ input: streams.input ?? process.stdin,
58
+ output: streams.output ?? process.stdout,
59
+ served: REPORTS,
60
+ subject: async () => {
61
+ try {
62
+ cached = await readRunReport(path);
63
+ }
64
+ catch {
65
+ // A report that becomes unreadable mid-run — being rewritten, most
66
+ // likely — must not take the server down. The previous one is stale,
67
+ // not wrong.
68
+ }
69
+ return cached;
70
+ },
71
+ });
72
+ }
73
+ /**
74
+ * Serve a suite that has not finished.
75
+ *
76
+ * The subject is a **snapshot** rather than the store, and that is the whole of
77
+ * why a live subject fits a surface built for files. A store is a thing a socket
78
+ * writes into while an answer is being composed; a snapshot is a value, so every
79
+ * tool here stays what every tool here is — a pure function over evidence — and
80
+ * `variance_diff` gets a coherent previous state to compare against instead of
81
+ * an object that changed underneath it.
82
+ *
83
+ * Nothing is written to disk, and nothing outlives this process. That is the
84
+ * position `@variance-authority/event` takes about announcements, kept: what
85
+ * changes is how long one execution lasts when somebody is watching it.
86
+ */
87
+ export async function serveVantage(streams = {}) {
88
+ const attached = await attachVantage();
89
+ const stop = serve({
90
+ input: streams.input ?? process.stdin,
91
+ output: streams.output ?? process.stdout,
92
+ served: VANTAGE,
93
+ subject: () => attached.observatory.snapshot(),
94
+ });
95
+ return {
96
+ address: attached.address,
97
+ stop: async () => {
98
+ stop();
99
+ await attached.close();
100
+ },
101
+ };
102
+ }
103
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Readable, Writable } from 'node:stream';\nimport type { RunReport } from '@variance-authority/report';\nimport { readRunReport } from '@variance-authority/report/file';\nimport { attachVantage } from '@variance-authority/vantage/attach';\nimport { REPORTS, VANTAGE, createLineReader, handle, type JsonRpcRequest } from './protocol.js';\nimport type { Served } from './tools/tool.js';\n\n/**\n * The transport, and nothing else.\n *\n * Everything decidable lives in `protocol.ts` and `tools.ts`, both of which are\n * pure. What is left here is stream plumbing, which is the part that cannot be\n * unit-tested meaningfully and therefore the part that should contain no\n * decisions.\n */\n\nexport interface ServerOptions<Subject = RunReport> {\n readonly input: Readable;\n readonly output: Writable;\n /** Which tools answer, and what the server calls itself. */\n readonly served: Served<Subject>;\n /**\n * Supplies the current subject.\n *\n * A function rather than a value so a long-lived server picks up a re-run\n * without a restart — an agent that fixes something and asks again should be\n * answered from the new report, not from the one loaded at boot.\n *\n * It may be async, and the request waits for it. A supplier that kicked off a\n * refresh and answered from the previous value would make *this* request the\n * stale one, which is the request that matters: the agent asking is the agent\n * that just re-ran.\n */\n readonly subject: () => Subject | Promise<Subject>;\n}\n\nexport function serve<Subject>(options: ServerOptions<Subject>): () => void {\n const write = (value: unknown): void => {\n options.output.write(`${JSON.stringify(value)}\\n`);\n };\n\n // Requests are answered in the order they arrived. JSON-RPC matches responses\n // by id and would tolerate any order, but a supplier that awaits makes the\n // ordering depend on how long each read took, and an interleaving that only\n // shows up under a slow disk is not a thing to debug later.\n let queue: Promise<void> = Promise.resolve();\n let previous: Subject | undefined;\n\n const read = createLineReader((line) => {\n let request: JsonRpcRequest;\n try {\n request = JSON.parse(line) as JsonRpcRequest;\n } catch {\n // Unparseable input has no id, so there is nothing to answer. Answering\n // with a null id would be a second protocol violation on top of the first.\n return;\n }\n\n queue = queue.then(async () => {\n const subject = await options.subject();\n const response = handle(\n request,\n () => subject,\n options.served,\n previous === undefined ? {} : { previous },\n );\n if (request.method === 'tools/call' && succeeded(response)) {\n previous = structuredClone(subject);\n }\n if (response !== null) write(response);\n });\n });\n\n const onData = (chunk: Buffer | string): void => read(chunk.toString());\n options.input.on('data', onData);\n\n return () => options.input.off('data', onData);\n}\n\nfunction succeeded(response: ReturnType<typeof handle>): boolean {\n if (response === null || response.error !== undefined) return false;\n return (response.result as { readonly isError?: boolean } | undefined)?.isError !== true;\n}\n\n/**\n * Where a report-file server reads and writes. Both default to this process's\n * stdio; they are parameters so the reload below is reachable from a test.\n */\nexport interface ReportFileOptions {\n readonly input?: Readable;\n readonly output?: Writable;\n}\n\n/**\n * Serve a report file, re-read before each request is answered.\n *\n * The reload is why `report` is a function, and awaiting it is why the answer is\n * current rather than one request behind. An agent that fixes something, re-runs,\n * and asks again is the whole reason this server outlives a single report — and\n * answering that agent from the copy loaded at boot tells it the edit did not\n * take, which is the one sentence here nobody should ever produce falsely.\n */\nexport async function serveReportFile(\n path: string,\n streams: ReportFileOptions = {},\n): Promise<() => void> {\n // Read once before serving, so a path that is not a run report fails at\n // startup rather than on whichever request happens to arrive first.\n let cached = await readRunReport(path);\n\n return serve({\n input: streams.input ?? process.stdin,\n output: streams.output ?? process.stdout,\n served: REPORTS,\n subject: async () => {\n try {\n cached = await readRunReport(path);\n } catch {\n // A report that becomes unreadable mid-run — being rewritten, most\n // likely — must not take the server down. The previous one is stale,\n // not wrong.\n }\n return cached;\n },\n });\n}\n\n/** A watcher that is listening, and the address a run must be started with. */\nexport interface ServedVantage {\n /** What to put in the run's environment, verbatim. */\n readonly address: string;\n readonly stop: () => Promise<void>;\n}\n\n/**\n * Serve a suite that has not finished.\n *\n * The subject is a **snapshot** rather than the store, and that is the whole of\n * why a live subject fits a surface built for files. A store is a thing a socket\n * writes into while an answer is being composed; a snapshot is a value, so every\n * tool here stays what every tool here is — a pure function over evidence — and\n * `variance_diff` gets a coherent previous state to compare against instead of\n * an object that changed underneath it.\n *\n * Nothing is written to disk, and nothing outlives this process. That is the\n * position `@variance-authority/event` takes about announcements, kept: what\n * changes is how long one execution lasts when somebody is watching it.\n */\nexport async function serveVantage(streams: ReportFileOptions = {}): Promise<ServedVantage> {\n const attached = await attachVantage();\n const stop = serve({\n input: streams.input ?? process.stdin,\n output: streams.output ?? process.stdout,\n served: VANTAGE,\n subject: () => attached.observatory.snapshot(),\n });\n\n return {\n address: attached.address,\n stop: async () => {\n stop();\n await attached.close();\n },\n };\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import type { Tool } from './tool.js';
2
+ /**
3
+ * `variance_adjudicate` — the run read back against what the agent said it did.
4
+ *
5
+ * Every other tool here answers about the run. This one answers about the
6
+ * *agent*, and it is the only tool in the set that takes evidence in rather than
7
+ * only handing it out: the declaration is something no report can contain,
8
+ * because the report was written by a process that never knew what the edit was
9
+ * for.
10
+ *
11
+ * What it buys is the arm nothing else in this category ships. `variance_changes`
12
+ * can tell an agent that `Button` moved in twelve subjects; it cannot tell it
13
+ * that `Card` — which the agent believes it just edited — did not move at all.
14
+ * That is not a diff finding, it is the *absence* of one, and an absence is only
15
+ * a finding once something declared it should have been there.
16
+ *
17
+ * The tool deliberately refuses to help an agent declare after the fact. It takes
18
+ * claims as an argument and derives none, so an agent that calls
19
+ * `variance_changes` first and then submits the answer back as its intent is
20
+ * scoring the run against itself — visibly, because the transcript shows the
21
+ * order. Nothing here can prevent that; what it can do is never do it *for* the
22
+ * agent.
23
+ */
24
+ export declare const adjudicate: Tool;
25
+ //# sourceMappingURL=adjudicate.d.ts.map