@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,89 @@
1
+ # @variance-authority/mcp
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 1d402d1: Carry the narrowing coordinate in the report, and print it in the summary header.
8
+
9
+ A `RunReport` now holds `narrowing`: the ref the run was told to observe from,
10
+ and where the recorded execution index stands — the commit it was written at and
11
+ how many files the working tree differs from it by. A run that narrowed nothing
12
+ carries the second half alone, so the coordinate is present whether or not it was
13
+ spent.
14
+
15
+ `variance_summary` prints it. Narrowing is an option and stays one; what this
16
+ refuses is the state where an agent works against a suite for weeks without ever
17
+ learning that an index is on disk and that the distance from it is a number. The
18
+ line names the commit and spells out the `variance run --since` that would use
19
+ it, and is omitted when there is no index, no position, or no distance.
20
+
21
+ `run` takes the coordinate as `index` and acts on it for nothing else.
22
+ `narrowingFor` resolves `since`, `against` and `index` together, so a caller
23
+ assembling a run reaches one call rather than three.
24
+ - 26ae9ed: Distinguish the React component instance that initiated a commit from every
25
+ component whose render body ran because of it.
26
+
27
+ The commit tap retains `memoizedUpdaters` as bounded structural component paths,
28
+ and Eyes records those commits in the authored test chronology. The MCP testing
29
+ surface places update initiators inside or outside the component paths a test
30
+ addressed while keeping whole-test source execution separate.
31
+
32
+ Export bounded read-only Fiber subtree, parent-chain, component-path, and source
33
+ location helpers for diagnostics that already hold a Fiber.
34
+ - a87d008: Hold what a running suite is saying in a process an agent can ask, so a test in
35
+ flight is something to look at rather than something to wait for.
36
+
37
+ A suite already knows what nothing outside it can see: which realms answered and
38
+ in what order, which work began and never finished, that a service is plainly
39
+ talking while the test hearing it hears nothing. All of it is spent settling
40
+ waits and then discarded, so nobody can ask it while it is true. A runner's
41
+ timeout reports what a test *wanted* — the last thing the failure knows and the
42
+ first thing the reader already knew.
43
+
44
+ `@variance-authority/vantage` is the second reader. A watching process listens,
45
+ the run reports, and the signals live in memory that outlives the test.
46
+ `variance-authority-mcp --watch` is that process for an agent: it prints the one
47
+ line the suite has to be started with, then answers `variance_run_signals` for
48
+ where the run has got to and `variance_test_signals` for everything one
49
+ execution has heard, in order, with the realm that said each and the work that
50
+ started and never ended. Both answer while the test is still running, which is
51
+ the point.
52
+
53
+ The address is in the handshake, not only on stderr. A `Served` may carry
54
+ `instructions`, and the watching one does: a set of tools about a run nobody has
55
+ started yet reads as broken, and an agent told the variable after it has started
56
+ the suite has been told one run too late.
57
+
58
+ None of it is about visual regression. A test that takes no screenshot reports
59
+ exactly what one that does reports, and a run started without
60
+ `VARIANCE_AUTHORITY_VANTAGE` pays one environment read per worker.
61
+
62
+ The medium is the wire the announcements already travel: one participant, `run`,
63
+ and `channelTo` for a participant handed an address rather than sent one.
64
+ `createEventLog` takes `onRecord` and `onRemark`, called at the moment of
65
+ recording rather than at teardown — an answer that arrives when the test
66
+ finishes answers a different question. `varianceWatched` is automatic so a
67
+ listing has no holes.
68
+
69
+ Still nothing written down. There is no report directory and no artifact to
70
+ mistake for evidence later; what changes is only how long one execution lasts
71
+ when somebody is watching.
72
+
73
+ ### Patch Changes
74
+
75
+ - Updated dependencies [1d402d1]
76
+ - Updated dependencies [9587133]
77
+ - Updated dependencies [5b2a28f]
78
+ - Updated dependencies [26ae9ed]
79
+ - Updated dependencies [92f62d4]
80
+ - Updated dependencies [f09528d]
81
+ - Updated dependencies [cfb333d]
82
+ - Updated dependencies [48d32eb]
83
+ - Updated dependencies [a87d008]
84
+ - @variance-authority/report@0.1.0
85
+ - @variance-authority/sense@0.1.0
86
+ - @variance-authority/presentation@0.1.0
87
+ - @variance-authority/eyes@0.1.0
88
+ - @variance-authority/scenario@0.1.0
89
+ - @variance-authority/vantage@0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Machine Garden
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,407 @@
1
+ <p align="center"><img src="./mark.svg" alt="Variance Authority mark" width="72"></p>
2
+
3
+ # @variance-authority/mcp
4
+
5
+ > Expose independently supplied observability evidence to an MCP client.
6
+
7
+ Use this package when an MCP client needs to inspect a visual run, presentation
8
+ signals, source-to-test journeys, live events, test attention, or retained
9
+ scenarios. The server reads supplied evidence and returns text; it never runs
10
+ tests, rerenders a subject, changes a baseline, or infers evidence that is not
11
+ there.
12
+
13
+ The supplied evidence remains canonical. This package makes visual causes,
14
+ regions, verdicts, findings, composition, variation, acceptance preview, and
15
+ source-to-test reach available from another process after collection finishes.
16
+ It also holds one previous invocation state in memory so an agent can compare
17
+ the evidence it sees now with the evidence it saw one call ago.
18
+
19
+ ## Entrypoints
20
+
21
+ | entrypoint | requires | holds |
22
+ |---|---|---|
23
+ | `.` | stdio | everything, plus `serve`, `serveReportFile` and `serveVantage` |
24
+ | `./tools` | nothing | observability answers as pure functions over their native evidence |
25
+ | `./protocol` | nothing | MCP framing, as a pure function from a request to a response |
26
+
27
+ Two of the three halves are pure, and that is deliberate. The question that
28
+ matters — *does this actually help an agent fix it?* — has to stay cheap to ask,
29
+ and it stops being asked the moment answering it requires speaking a protocol
30
+ over a pipe.
31
+
32
+ What a producer *wrote* is not here either. Reports, execution indexes, live
33
+ state, Eyes archives, and scenario manifests belong to their producing
34
+ packages. MCP reads those contracts; it owns none.
35
+
36
+ ## Give an agent all observability
37
+
38
+ `OBSERVABILITY` serves one connection over an `ObservabilitySubject`. Each field
39
+ is optional because each instrument has its own lifecycle and retention rules:
40
+
41
+ ```ts
42
+ import { OBSERVABILITY, serve } from '@variance-authority/mcp';
43
+ import type { ObservabilitySubject } from '@variance-authority/mcp';
44
+
45
+ export function serveObservability(current: () => ObservabilitySubject) {
46
+ return serve({
47
+ input: process.stdin,
48
+ output: process.stdout,
49
+ served: OBSERVABILITY,
50
+ subject: current,
51
+ });
52
+ }
53
+ ```
54
+
55
+ The handshake tells the client to call `variance_observability` first. Its
56
+ answer distinguishes a missing domain from a supplied domain that measured zero
57
+ members. Native tools remain available on the same connection:
58
+
59
+ | tool | evidence | answers |
60
+ |---|---|---|
61
+ | `variance_test_attention` | Eyes archive | one test's selectors, Locator consumption, DOM events, synchronous Fiber attribution, and authored AAA markers |
62
+ | `variance_presentations` | presentation reports | full presentation graphs, telemetry, semantic evidence, measured structures, and findings |
63
+ | `variance_source_tests` | Sense execution index | which named tests entered source and their minimum observed distance |
64
+ | `variance_run_signals`, `variance_test_signals` | Vantage state | what an in-flight suite and one test have announced |
65
+ | visual report tools | run report | visual decisions, presentation signals, composition, variation, history, and review evidence |
66
+ | `variance_scenarios` | scenario manifests | the witnessed Arrange state and observed or unobserved Act outcomes |
67
+
68
+ `variance_testing_surface` is the deliberate cross-domain answer. It maps the
69
+ DOM owners and source locations a test addressed in each authored phase, then
70
+ places React update initiators inside or outside those exact structural component
71
+ paths and contrasts both with files that the same exact test id entered.
72
+ `PerformedWork` says a render body ran; it is not substituted for an updater.
73
+ An executed file with no addressed target is a replay candidate, not proof that
74
+ the branch is unrelated or safe to mock. The tool does not join by title or file
75
+ when stable producer identities disagree. `ExecutionIndex` retains whole-test
76
+ crossings, not AAA intervals, so runtime files remain test-scoped rather than
77
+ phase-scoped.
78
+
79
+ The individual served sets remain available as `REPORTS`, `PRESENTATIONS`,
80
+ `SOURCE_TESTS`, `VANTAGE`, `EYES`, and `SCENARIOS`. Use one when the integration owns only that
81
+ domain. React Testing Library and Playwright remain optional peer dependencies
82
+ of `@variance-authority/eyes`; installing MCP does not add either runner.
83
+
84
+ ## Give an agent the tests for source
85
+
86
+ `variance_source_tests` answers the question a coding agent needs before and
87
+ after an edit: which named tests reached this source, and how directly? A line
88
+ or function query returns tests ordered by minimum observed call-stack depth. A
89
+ file query returns every indexed line as compact ranges, including ranges no
90
+ test reached.
91
+
92
+ An integration that owns the current `ExecutionIndex` serves it directly; the
93
+ supplier is called for every request so a rerun is visible without restarting
94
+ the agent's MCP connection:
95
+
96
+ ```ts
97
+ import { SOURCE_TESTS, serve } from '@variance-authority/mcp';
98
+ import type { ExecutionIndex } from '@variance-authority/sense/test-selection';
99
+
100
+ export function serveSourceTests(current: () => ExecutionIndex | Promise<ExecutionIndex>) {
101
+ return serve({
102
+ input: process.stdin,
103
+ output: process.stdout,
104
+ served: SOURCE_TESTS,
105
+ subject: current,
106
+ });
107
+ }
108
+ ```
109
+
110
+ The tool takes `file` and optionally one of `line` or `function`. With neither,
111
+ it answers the whole file. It distinguishes an indexed range reached by no test
112
+ from a line absent from the execution index. The index is supplied by the test
113
+ collector or editor integration; MCP does not manufacture coverage or control
114
+ the test runner.
115
+
116
+ ## Watch a suite that has not finished
117
+
118
+ Every tool above answers about a run that is over. `--watch` answers about one
119
+ that is not.
120
+
121
+ ```bash
122
+ npx variance-authority-mcp --watch
123
+ ```
124
+
125
+ It prints, on stderr, the one line the suite has to be started with:
126
+
127
+ ```
128
+ variance-authority is watching. Start the suite with:
129
+ VARIANCE_AUTHORITY_VANTAGE=http://127.0.0.1:54321
130
+ ```
131
+
132
+ That variable goes wherever `VARIANCE_AUTHORITY_EVENTS` goes. The suite needs
133
+ `varianceFixtures` from `@variance-authority/playwright-test` and nothing else,
134
+ **and none of it is about visual regression** — a test that takes no screenshot
135
+ reports exactly what one that does reports.
136
+
137
+ The same line is handed to the client at the handshake, as the server's
138
+ `instructions`, because stderr goes to a log the model never reads. An agent
139
+ that is told the address only after it has already started the suite has been
140
+ told it one run too late.
141
+
142
+ | tool | answers | ask it when |
143
+ |---|---|---|
144
+ | `variance_run_signals` | every test that has reported, in the order the run opened them, its state, and how much each has announced | you want to know where the suite has got to, or which test is the one still going |
145
+ | `variance_test_signals` | everything one test has announced, in order, with the realm that said each, plus work that started and never ended | a test is hanging, or failed, and the assertion that did not settle is the part you already know |
146
+
147
+ `variance_run_signals` takes `state`, `file` and `limit`, and marks a running
148
+ test with `▸`. `variance_test_signals` takes `test` — an id from the listing, a
149
+ title, or enough of one to be unambiguous; where it is not unambiguous, the
150
+ answer is the candidates and their ids.
151
+
152
+ The second is the one a timeout cannot give. A runner reports what a test
153
+ *wanted*; this reports what its execution actually **heard**, and from whom.
154
+ Nothing at all is a wiring fact — no listener installed, or code that does not
155
+ announce yet. A page that spoke while a service did not is a request that never
156
+ arrived or never came back. Three announcements and then silence, with one
157
+ `vaStart` still open, names the call that is hanging.
158
+
159
+ Nothing is written down and nothing is added to the run's evidence: a report file
160
+ records what a run **decided**, and this records what it **is doing**, which
161
+ stops being a fact the moment this process exits. What it holds is bounded, and
162
+ it says so when it dropped something, because a reader who cannot tell *nothing
163
+ was announced* from *the beginning was forgotten* draws the first conclusion.
164
+
165
+ `variance_diff` is served here too, so *what changed since I last asked* works
166
+ against a suite in flight the same way it works against a report.
167
+
168
+ ## Diff the current state
169
+
170
+ A **subject** is whatever data the server currently holds and answers questions
171
+ about — a `RunReport` for the visual tools, an `ExecutionIndex` for the
172
+ source-test tool, a `VantageState` for a suite that is still running, or an
173
+ `ObservabilitySubject` carrying independently optional domains. (Inside a `RunReport`, each individually observed
174
+ rendering, such as `story:card--dark`, is also called a subject; the tool
175
+ contract below works at that finer grain.)
176
+
177
+ `variance_diff` compares the current supplied subject with the subject from the
178
+ previous successful tool call. The first call records the current state and says
179
+ there is nothing to compare. Each successful call then replaces that one value.
180
+
181
+ The value lives only in the MCP process. It is not written to disk, does not move
182
+ or replace a baseline, and disappears when the process exits. Initialization,
183
+ tool discovery, invalid calls, and failed calls do not replace it.
184
+
185
+ `diffState(before, after)` exposes the same JSON-compatible state comparison
186
+ without MCP framing.
187
+
188
+ ## Run the report server
189
+
190
+ Install it wherever the MCP client will launch it from:
191
+
192
+ ```bash
193
+ npm install @variance-authority/mcp
194
+ ```
195
+
196
+ No install is needed to just run the published binary:
197
+
198
+ ```bash
199
+ npx variance serve # via the CLI, reading .variance/run.json
200
+ npx variance-authority-mcp .variance/run.json # directly
201
+ npx variance-authority-mcp --watch # a run that has not finished
202
+ ```
203
+
204
+ ```jsonc
205
+ // claude_desktop_config.json, or any MCP client
206
+ {
207
+ "mcpServers": {
208
+ "variance": { "command": "npx", "args": ["variance-authority-mcp", ".variance/run.json"] }
209
+ }
210
+ }
211
+ ```
212
+
213
+ Skip this package if there is no MCP client in the loop: running the suite and
214
+ reading its output yourself is `@variance-authority/cli` (`variance run`,
215
+ `variance accept`), with no server or protocol involved. Reach for this package
216
+ only to hand an already-finished run, or an execution index, to an agent that
217
+ speaks MCP.
218
+
219
+ ## Visual report tool contract
220
+
221
+ Eleven tools, all answering from the artifact and **never re-running anything**.
222
+ The run may have happened on a pinned machine in CI an hour ago; the questions
223
+ are asked wherever the agent is. One compares invocations; the other ten inspect
224
+ the current artifact.
225
+
226
+ A **component** is a named unit inside a rendering, such as `Button`; the same
227
+ component can appear inside several subjects, which is what
228
+ `variance_composition` and `variance_trace_component` below compare.
229
+
230
+ ```ts
231
+ import { toolByName } from '@variance-authority/mcp/tools';
232
+
233
+ const priorReport = report;
234
+ toolByName('variance_summary')?.run(report, {});
235
+ toolByName('variance_diff')?.run(report, {}, { previous: priorReport });
236
+ toolByName('variance_changes')?.run(report, {});
237
+ toolByName('variance_adjudicate')?.run(report, {
238
+ claims: [{ root: 'component:Button', reason: 'new brand accent', maxSubjects: 3 }],
239
+ });
240
+ toolByName('variance_composition')?.run(report, {});
241
+ toolByName('variance_variations')?.run(report, { subject: 'story:card--dark' });
242
+ toolByName('variance_changelog')?.run(report, { shape: 'v1:9a3f1c2e04' });
243
+ toolByName('variance_describe')?.run(report, { subject: 'story:card--populated' });
244
+ toolByName('variance_findings')?.run(report, {});
245
+ toolByName('variance_trace_component')?.run(report, { component: 'Button' });
246
+ toolByName('variance_explain_verdict')?.run(report, { subject: 'story:card--populated' });
247
+ ```
248
+
249
+ | tool | answers | ask it when |
250
+ |---|---|---|
251
+ | `variance_summary` | how the run came out across every subject, including the ones nobody observed | starting from nothing: *did anything change, and was anything missed?* |
252
+ | `variance_diff` | how the current supplied state differs from the previous successful MCP tool invocation | after rerunning or replacing the supplied evidence |
253
+ | `variance_changes` | the distinct changes behind the changed subjects, most decidable first, each with the command that settles it | immediately after the summary, before touching any individual subject |
254
+ | `variance_adjudicate` | this run against **what you said you were doing**: declared and delivered, moved and undeclared, and declared and never happened | you edited something and are reading your own run — declare before you read the diff |
255
+ | `variance_composition` | the run's subjects compared to **each other**: the component graph, the renderings two examples share, and why each component that moved moved — including *nothing here explains it* | a change has no obvious author, or you are about to call something flaky |
256
+ | `variance_variations` | the measured difference between a subject and the subject it declares as its parent, such as a feature arm, theme, or viewport | reviewing what a variant changes rather than whether it regressed |
257
+ | `variance_changelog` | what accepting this run would write into the baseline record: the lines the commit will carry, and the subjects that would be refused | before proposing an `accept` command, because the record is written once and outlives the run |
258
+ | `variance_describe` | what changed inside one subject — regions, components, files | the summary named a subject and you need the detail |
259
+ | `variance_findings` | accessibility defects in the renders themselves, grouped by rule, with no baseline involved | fixing a component, whether or not it changed |
260
+ | `variance_trace_component` | every subject one component appears in, with pixels and cause-or-displaced | sizing the blast radius of a design-system or token edit |
261
+ | `variance_explain_verdict` | why a subject was **not compared** — `incomparable`, `new`, or never observed | before attempting a fix, because none of those is a code problem |
262
+
263
+ `variance_changes` is the one that decides how many of the others get called. A
264
+ design-token edit reaching forty stories is one decision presented as forty, and
265
+ an agent that walks them one at a time spends forty calls learning what one call
266
+ says. It is also the only tool that hands back a *command* — the shape digest
267
+ cannot be derived from anything else in the report, and it names which subjects
268
+ the command will refuse, so the agent proposes something that works rather than
269
+ something that gets rejected.
270
+
271
+ `variance_adjudicate` is the only tool that takes evidence *in*, and the only
272
+ one that can report an **absence**. Everything else answers about the run;
273
+ this answers about the agent. `variance_changes` can say that `Button` moved in
274
+ twelve subjects. It cannot say that `Card` — which the agent believes it just
275
+ edited — did not move at all, because a diff has no opinion about what was
276
+ supposed to happen. That third arm is where a wrong file, a dead branch, an
277
+ overridden rule or a stale build surfaces, and no screenshot comparison reaches
278
+ it.
279
+
280
+ The declaration has to come first: the tool takes claims as an argument rather
281
+ than deriving them, so an agent must state what it expected before reading what
282
+ happened. A claim that reaches more subjects than it declared comes back
283
+ `overreached`.
284
+
285
+ A claim carrying a field this resolution cannot check is named rather than
286
+ dropped. A **band** is a category of visual difference, such as `content` or
287
+ `geometry`; an agent told `delivered` about a band nothing looked at has been
288
+ told something the run never established, so the answer ends `Not checked here:
289
+ bands`. `examples/agent-claim` runs the whole
290
+ boundary — CLI and this tool, every verdict, one process.
291
+
292
+ `variance_changelog` previews what accepting this run would write into the
293
+ baseline record, before the write happens — otherwise a baseline update is only
294
+ explained in the commit that carries it, which does not exist until `accept`
295
+ runs. The preview renders the record's own lines through the same function that
296
+ writes the commit, over the subject set the same rules select, so the preview
297
+ and the eventual commit cannot disagree. It stops short of the commit trailers:
298
+ those are written only once a promotion has actually happened.
299
+
300
+ `variance_composition` is the only one that reads the other axis. Everything
301
+ else compares a subject to its baseline — two revisions, one thing. This
302
+ compares the run's subjects to each other, at one commit, because **a
303
+ visual-regression example is a component built from components**: the example
304
+ *is* a component at a boundary, and the same component appears again, with the
305
+ same or different props, inside larger examples. Once those boundaries are
306
+ addressable the run can say which of its examples are watching literally the
307
+ same bytes, which of them disagree at one commit, and — for anything that moved
308
+ — whether an edited file, a moved token or an edited *caller* accounts for it.
309
+
310
+ This is also where an unexplained movement gets a **control group**: the
311
+ subjects where that same component, with the same props, held — did not move —
312
+ the stable states to compare against. Given one, an unexplained movement is
313
+ `flake` if the subject also failed to read the same way twice, or `suspect` — a
314
+ shortlist, not a verdict — if nobody has read it twice yet.
315
+
316
+ `variance_summary` labels a subject by what it *is*, which is not always its
317
+ verdict. Three subjects can all be `changed` — the pixels did move — and need
318
+ three different people:
319
+
320
+ | label | what happened | what to do |
321
+ |---|---|---|
322
+ | `unstable` | read twice, seconds apart, nothing changed in between, and the two readings disagreed | fix what moves between readings; the named component and band say where. Do not review the regions — which ones appear was decided by a race |
323
+ | `order-dependent` | the difference is gone when the subject is collected with nothing else in the world | do not change the component; bisect run order to find the subject that writes the state this one reads |
324
+ | `changed` | it survived both | review it |
325
+
326
+ A fourth state is deliberately *not* on that table. A **sensitivity level** is a
327
+ per-subject declaration of which bands it asserts on — a route declared `layout`
328
+ has said, in its config, that it does not assert on what the page is painted
329
+ with, so a clock inside it is a fact about the page rather than a defect. A
330
+ subject whose two readings differed entirely in bands outside its declared level
331
+ is listed under **not asserted on** and carries no instruction. It is still named
332
+ and counted, with the rule that absorbed it — the same reason a subject whose
333
+ pixels were excluded by an ignore rule is reported as `ignored` rather than
334
+ folded into `unchanged`: an exclusion nobody can see again is one nobody is
335
+ really watching.
336
+
337
+ `accept` refuses `unstable` and `order-dependent` subjects; only `changed` can be
338
+ promoted. Instability is checked first: the clean-vs-shared comparison behind
339
+ `order-dependent` only means something when a subject's two readings would
340
+ otherwise agree.
341
+
342
+ `variance_findings` is the one that is not about a change. A control that never
343
+ had an accessible name compares equal to itself on every run, so a comparison can
344
+ never report it — and an agent asked to fix a component wants it anyway. Its
345
+ findings do not affect the verdict, and an empty answer distinguishes *inspected
346
+ and clean* from *nobody looked* — the same distinction `variance_summary` keeps
347
+ when it accounts for the subjects nobody observed.
348
+
349
+ ## Coverage boundary
350
+
351
+ An agent told **"no changes"** concludes the product is fine. If what actually
352
+ happened is that eleven subjects failed to render, that sentence is a lie the
353
+ agent will act on — and unlike a human reading a dashboard, it has nothing else
354
+ to check against.
355
+
356
+ So the summary accounts for every subject including the ones nobody observed, and
357
+ a run with unobserved subjects never reads as clean. `notObserved` is the list of
358
+ subjects the run planned and has no result for; it distinguishes `excluded` from
359
+ `failed`, and a malformed entry is refused rather than defaulted: guessing
360
+ `excluded` turns a coverage hole into a decision somebody made, and guessing
361
+ `failed` turns every deliberate exclusion into a permanently red build.
362
+
363
+ ## The narrowing coordinate
364
+
365
+ `variance_summary` prints, in its header, where the recorded execution index
366
+ stands and how many files the working tree differs from it by — followed by the
367
+ `variance run --since <commit>` that would observe only what those files reach.
368
+
369
+ It is in the header rather than in a tool of its own because an option an agent
370
+ is never told about is an option it does not have. Nothing about the line
371
+ proposes that a run should have skipped anything; narrowing stays the operator's
372
+ decision, and the header carries the coordinate the decision needs. It is omitted
373
+ when there is nothing to offer: no index on disk, an index with no position, or a
374
+ tree that has not moved from it.
375
+
376
+ ## Serve a custom subject
377
+
378
+ `serveReportFile(path)` is the whole executable, and `serve(options)` is what it
379
+ composes when the report does not come from a file:
380
+
381
+ | option | what it decides |
382
+ |---|---|
383
+ | `input` | the `Readable` requests arrive on |
384
+ | `output` | the `Writable` responses leave on |
385
+ | `served` | the `Served<Subject>` name and tools for the subject; use `REPORTS` for a `RunReport` or supply a set for another serializable subject |
386
+ | `subject` | supplies the current subject. A function rather than a value, so a long-lived server picks up a re-run without a restart — an agent that fixes something and asks again should be answered from the new report, not from the one loaded at boot. It may be async, and the request waits for it: a supplier that started a refresh and answered from the previous value would make *this* request the stale one, and this request is the agent that just re-ran |
387
+
388
+ A `Served` is a name, a version and the tools. It may also carry
389
+ `instructions`, a function of the subject whose answer the client puts in front
390
+ of the model before it has called anything — for a set of tools whose subject
391
+ has to be *arranged* first, and which therefore reads as broken to an agent that
392
+ finds it empty. `REPORTS` has none: a report on disk is already there.
393
+
394
+ Both return a function that detaches the server from its streams.
395
+ `serveVantage()` is the third: it opens the listener, serves the watch tools over
396
+ the same streams, and returns the `address` to start a run with alongside the
397
+ call that stops both. Its subject is a snapshot taken per request, which is why a
398
+ subject that is moving fits a surface built for one that is not.
399
+
400
+ ## Stability
401
+
402
+ **The tool names, their argument shapes and the wording of their answers carry no
403
+ compatibility guarantee.** They are answers chosen to be useful to an agent
404
+ rather than a published interface, and an answer that turns out to be the wrong
405
+ one to give will change without a deprecation. If you need them to hold still,
406
+ import `@variance-authority/mcp/tools` behind an adapter of your own and pin the
407
+ version.
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
package/dist/bin.js ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env node
2
+ import { serveReportFile, serveVantage } from './server.js';
3
+ /**
4
+ * `variance-authority-mcp <report.json>` — answer about a run that finished.
5
+ * `variance-authority-mcp --watch` — answer about one that has not.
6
+ *
7
+ * One argument either way, no flags beyond it and no config file. The two modes
8
+ * are two subjects, not two servers: a report is a file somebody produced, and a
9
+ * watcher is a socket a run reports to for as long as this process is running.
10
+ * Nothing it could be configured to *do* would be work the run should have done,
11
+ * on the machine the run was on.
12
+ *
13
+ * The address goes to stderr because stdout is the protocol. It is printed
14
+ * rather than merely available, because a watcher nobody attached a run to is
15
+ * indistinguishable from a broken one, and one line at startup is the cheapest
16
+ * place to make attaching obvious.
17
+ */
18
+ const [, , path] = process.argv;
19
+ if (path === '--watch') {
20
+ const { address } = await serveVantage();
21
+ process.stderr.write(`variance-authority is watching. Start the suite with:\n` +
22
+ ` VARIANCE_AUTHORITY_VANTAGE=${address}\n`);
23
+ }
24
+ else if (path === undefined) {
25
+ process.stderr.write('usage: variance-authority-mcp <run-report.json>\n' +
26
+ ' variance-authority-mcp --watch\n');
27
+ process.exit(2);
28
+ }
29
+ else {
30
+ await serveReportFile(path);
31
+ }
32
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5D;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,EAAE,AAAD,EAAG,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;AAEhC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;IACvB,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,YAAY,EAAE,CAAC;IACzC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,yDAAyD;QACvD,gCAAgC,OAAO,IAAI,CAC9C,CAAC;AACJ,CAAC;KAAM,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;IAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,mDAAmD;QACjD,yCAAyC,CAC5C,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;KAAM,CAAC;IACN,MAAM,eAAe,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { serveReportFile, serveVantage } from './server.js';\n\n/**\n * `variance-authority-mcp <report.json>` — answer about a run that finished.\n * `variance-authority-mcp --watch` — answer about one that has not.\n *\n * One argument either way, no flags beyond it and no config file. The two modes\n * are two subjects, not two servers: a report is a file somebody produced, and a\n * watcher is a socket a run reports to for as long as this process is running.\n * Nothing it could be configured to *do* would be work the run should have done,\n * on the machine the run was on.\n *\n * The address goes to stderr because stdout is the protocol. It is printed\n * rather than merely available, because a watcher nobody attached a run to is\n * indistinguishable from a broken one, and one line at startup is the cheapest\n * place to make attaching obvious.\n */\nconst [, , path] = process.argv;\n\nif (path === '--watch') {\n const { address } = await serveVantage();\n process.stderr.write(\n `variance-authority is watching. Start the suite with:\\n` +\n ` VARIANCE_AUTHORITY_VANTAGE=${address}\\n`,\n );\n} else if (path === undefined) {\n process.stderr.write(\n 'usage: variance-authority-mcp <run-report.json>\\n' +\n ' variance-authority-mcp --watch\\n',\n );\n process.exit(2);\n} else {\n await serveReportFile(path);\n}\n"]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * `@variance-authority/mcp` — observed evidence, exposed to an agent.
3
+ *
4
+ * The chain the rest of this repository builds ends at a sentence: a cause, a
5
+ * place, and a file. This package is what puts that sentence somewhere an agent
6
+ * can reach it *after the fact*, from a different process, without access to
7
+ * whatever was in scope when the change was sensed.
8
+ *
9
+ * Two concerns, two entrypoints, and the split is load-bearing:
10
+ *
11
+ * - `mcp/tools` — the answers, as pure functions from a report to text. Testable
12
+ * without speaking a protocol, which is the only way the question that matters
13
+ * ("does this help an agent fix it?") stays cheap to ask.
14
+ * - `mcp/protocol` — MCP framing, as a pure function from a request to a
15
+ * response. Also pure, and separately exercisable, and generic in what it
16
+ * serves: a request, a subject, and a set of tools that read that subject.
17
+ * `REPORTS` is the set this package ships; a server over some other subject
18
+ * passes its own and reuses every line of the framing.
19
+ *
20
+ * `server.ts` is the stdio plumbing left over once those two are removed, and it
21
+ * deliberately contains no decisions.
22
+ *
23
+ * What a producer *wrote* is not here. The report format is
24
+ * `@variance-authority/report`, because the CLI writes it, a PR comment renders
25
+ * it and these tools read it — and a format owned by one reader bends towards
26
+ * that reader.
27
+ */
28
+ export { NO_ARGS, EYES_TOOLS, OBSERVABILITY_TOOLS, PRESENTATION_TOOLS, SCENARIO_TOOLS, SOURCE_TEST_TOOLS, TOOLS, VANTAGE_TOOLS, eyesToolByName, observabilityToolByName, presentationToolByName, scenarioToolByName, sourceTestToolByName, stringArg, toolByName, vantageToolByName, diffState, } from './tools.js';
29
+ export type { Served, StateDifference, Tool, ToolInvocation } from './tools.js';
30
+ export { EYES, OBSERVABILITY, PRESENTATIONS, REPORTS, SCENARIOS, SOURCE_TESTS, VANTAGE, handle, createLineReader, PROTOCOL_VERSION, SERVER_NAME, SERVER_VERSION, } from './protocol.js';
31
+ export type { JsonRpcRequest, JsonRpcResponse } from './protocol.js';
32
+ export type { ObservabilitySubject } from './observability-subject.js';
33
+ export { serve, serveReportFile, serveVantage } from './server.js';
34
+ export type { ReportFileOptions, ServerOptions, ServedVantage } from './server.js';
35
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * `@variance-authority/mcp` — observed evidence, exposed to an agent.
3
+ *
4
+ * The chain the rest of this repository builds ends at a sentence: a cause, a
5
+ * place, and a file. This package is what puts that sentence somewhere an agent
6
+ * can reach it *after the fact*, from a different process, without access to
7
+ * whatever was in scope when the change was sensed.
8
+ *
9
+ * Two concerns, two entrypoints, and the split is load-bearing:
10
+ *
11
+ * - `mcp/tools` — the answers, as pure functions from a report to text. Testable
12
+ * without speaking a protocol, which is the only way the question that matters
13
+ * ("does this help an agent fix it?") stays cheap to ask.
14
+ * - `mcp/protocol` — MCP framing, as a pure function from a request to a
15
+ * response. Also pure, and separately exercisable, and generic in what it
16
+ * serves: a request, a subject, and a set of tools that read that subject.
17
+ * `REPORTS` is the set this package ships; a server over some other subject
18
+ * passes its own and reuses every line of the framing.
19
+ *
20
+ * `server.ts` is the stdio plumbing left over once those two are removed, and it
21
+ * deliberately contains no decisions.
22
+ *
23
+ * What a producer *wrote* is not here. The report format is
24
+ * `@variance-authority/report`, because the CLI writes it, a PR comment renders
25
+ * it and these tools read it — and a format owned by one reader bends towards
26
+ * that reader.
27
+ */
28
+ export { NO_ARGS, EYES_TOOLS, OBSERVABILITY_TOOLS, PRESENTATION_TOOLS, SCENARIO_TOOLS, SOURCE_TEST_TOOLS, TOOLS, VANTAGE_TOOLS, eyesToolByName, observabilityToolByName, presentationToolByName, scenarioToolByName, sourceTestToolByName, stringArg, toolByName, vantageToolByName, diffState, } from './tools.js';
29
+ export { EYES, OBSERVABILITY, PRESENTATIONS, REPORTS, SCENARIOS, SOURCE_TESTS, VANTAGE, handle, createLineReader, PROTOCOL_VERSION, SERVER_NAME, SERVER_VERSION, } from './protocol.js';
30
+ export { serve, serveReportFile, serveVantage } from './server.js';
31
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EACL,OAAO,EACP,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,KAAK,EACL,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,SAAS,EACT,UAAU,EACV,iBAAiB,EACjB,SAAS,GACV,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,IAAI,EACJ,aAAa,EACb,aAAa,EACb,OAAO,EACP,SAAS,EACT,YAAY,EACZ,OAAO,EACP,MAAM,EACN,gBAAgB,EAChB,gBAAgB,EAChB,WAAW,EACX,cAAc,GACf,MAAM,eAAe,CAAC;AAIvB,OAAO,EAAE,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC","sourcesContent":["/**\n * `@variance-authority/mcp` — observed evidence, exposed to an agent.\n *\n * The chain the rest of this repository builds ends at a sentence: a cause, a\n * place, and a file. This package is what puts that sentence somewhere an agent\n * can reach it *after the fact*, from a different process, without access to\n * whatever was in scope when the change was sensed.\n *\n * Two concerns, two entrypoints, and the split is load-bearing:\n *\n * - `mcp/tools` — the answers, as pure functions from a report to text. Testable\n * without speaking a protocol, which is the only way the question that matters\n * (\"does this help an agent fix it?\") stays cheap to ask.\n * - `mcp/protocol` — MCP framing, as a pure function from a request to a\n * response. Also pure, and separately exercisable, and generic in what it\n * serves: a request, a subject, and a set of tools that read that subject.\n * `REPORTS` is the set this package ships; a server over some other subject\n * passes its own and reuses every line of the framing.\n *\n * `server.ts` is the stdio plumbing left over once those two are removed, and it\n * deliberately contains no decisions.\n *\n * What a producer *wrote* is not here. The report format is\n * `@variance-authority/report`, because the CLI writes it, a PR comment renders\n * it and these tools read it — and a format owned by one reader bends towards\n * that reader.\n */\n\nexport {\n NO_ARGS,\n EYES_TOOLS,\n OBSERVABILITY_TOOLS,\n PRESENTATION_TOOLS,\n SCENARIO_TOOLS,\n SOURCE_TEST_TOOLS,\n TOOLS,\n VANTAGE_TOOLS,\n eyesToolByName,\n observabilityToolByName,\n presentationToolByName,\n scenarioToolByName,\n sourceTestToolByName,\n stringArg,\n toolByName,\n vantageToolByName,\n diffState,\n} from './tools.js';\nexport type { Served, StateDifference, Tool, ToolInvocation } from './tools.js';\n\nexport {\n EYES,\n OBSERVABILITY,\n PRESENTATIONS,\n REPORTS,\n SCENARIOS,\n SOURCE_TESTS,\n VANTAGE,\n handle,\n createLineReader,\n PROTOCOL_VERSION,\n SERVER_NAME,\n SERVER_VERSION,\n} from './protocol.js';\nexport type { JsonRpcRequest, JsonRpcResponse } from './protocol.js';\nexport type { ObservabilitySubject } from './observability-subject.js';\n\nexport { serve, serveReportFile, serveVantage } from './server.js';\nexport type { ReportFileOptions, ServerOptions, ServedVantage } from './server.js';\n"]}