@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,279 @@
1
+ import { AGE_WORDS, ageOf, byArrival, byBand, carriedLine, findingTotals, mixedAges, } from '@variance-authority/report';
2
+ import { describePresentation } from '../presentation.js';
3
+ import { subjectOf, unobserved } from './subject.js';
4
+ /**
5
+ * `variance_describe`, and the region format nothing else prints.
6
+ *
7
+ * `regionLine` stays with the tool because the region list is this answer's whole
8
+ * substance, and everything around it is an ordering decision that belongs to
9
+ * this tool rather than to regions in general: the leak warning above the list
10
+ * because it changes what every line below means, the findings before it because
11
+ * an `unchanged` subject can carry nothing else, and the truncation count after
12
+ * it because a capped list that does not say so reads as a complete one.
13
+ */
14
+ /**
15
+ * One subject, in full: what changed, where it is, and which file to open.
16
+ *
17
+ * The tool an agent calls after the summary, and the one that has to be complete
18
+ * — an agent that has to ask three more questions to locate an edit will guess
19
+ * instead.
20
+ */
21
+ export const describe = {
22
+ name: 'variance_describe',
23
+ description: 'Everything known about one subject: the ranked regions, the component each belongs to, ' +
24
+ 'a landmark description of where it is on the page, the source file to edit, and each ' +
25
+ "region's shape fingerprint — which is what `variance accept --shape` and an `ignore` " +
26
+ 'rule are keyed on when a difference is not a code problem. Causes are listed before ' +
27
+ 'collateral. A subject the run did not observe is answered with why, not refused.',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: { subject: { type: 'string', description: 'Subject id from variance_summary.' } },
31
+ required: ['subject'],
32
+ additionalProperties: false,
33
+ },
34
+ run(report, input) {
35
+ const located = subjectOf(report, input);
36
+ if (!located.observed)
37
+ return unobserved(located.entry);
38
+ const { observation } = located;
39
+ const lines = [
40
+ `[${label(observation)}] ${observation.subject}`,
41
+ observation.because,
42
+ // Ahead of the leak note, because it disqualifies that one too. A clean
43
+ // world re-collection was never taken on this subject — its answer would
44
+ // have been a comparison between two readings that do not agree anyway —
45
+ // so there is no order-dependence claim here to place second.
46
+ ...(observation.unstable !== undefined && observation.unstable.absorbed === undefined
47
+ ? [
48
+ `NOT A COMPONENT CHANGE: ${observation.unstable.because}. The regions below are`,
49
+ 'the difference between one of those two readings and the baseline, so which',
50
+ 'ones appear here is decided by a race. Do not review them and do not accept',
51
+ 'this subject. Fix what moves between two readings of the same page' +
52
+ (observation.unstable.components.length === 0
53
+ ? ', starting from the subject itself — no snapshot was collected, so nothing'
54
+ + ' could name the component that moved.'
55
+ : `, starting at ${unstableAt(observation.unstable.components)}` +
56
+ (observation.unstable.bands.length === 0
57
+ ? '.'
58
+ : ` (${observation.unstable.bands.join(', ')}).`)),
59
+ ]
60
+ : []),
61
+ // What the record says, directly under the instruction it qualifies. An
62
+ // agent handed "fix what moves between two readings" acts differently when
63
+ // the answer is "this has fired in eleven of the last twelve sweeps" than
64
+ // when it is "twice in March, and nine sweeps have been clean since" — the
65
+ // second is a fix that already landed, and rewriting it is a day spent
66
+ // re-solving somebody's solved problem.
67
+ ...(observation.unstable !== undefined ? recurrence(report, observation.subject) : []),
68
+ // The experiment, spelled out. An agent that has just been told to fix
69
+ // what moves between two readings needs a way to find out whether it did,
70
+ // and the alternative it reaches for otherwise is a full run — three
71
+ // hundred subjects and a browser, to answer a question about one of them.
72
+ ...(observation.unstable !== undefined && observation.unstable.absorbed === undefined
73
+ ? [
74
+ 'VERIFY A FIX WITH:',
75
+ ` variance run --subjects '${observation.subject}' --flakes`,
76
+ 'which reads this subject twice and nothing else, and exits 1 while the two',
77
+ 'readings still disagree — even when every verdict is green.',
78
+ ]
79
+ : []),
80
+ // The same movement, inside the boundary the subject declared. Said rather
81
+ // than suppressed: an agent that later sees this subject go green wants to
82
+ // know a level was doing work, and the rule's name is what makes that
83
+ // auditable. It carries no instruction, because there is nothing to do.
84
+ ...(observation.unstable?.absorbed !== undefined
85
+ ? [
86
+ `read differently between two readings in ${(observation.unstable.bands ?? []).join(', ')}, ` +
87
+ `and \`${observation.unstable.absorbed.rule}\` asserts on ` +
88
+ `${observation.unstable.absorbed.level} — so none of it is asserted on here. ` +
89
+ 'Working as declared; the regions below are the comparison, not the movement.',
90
+ ]
91
+ : []),
92
+ // Placed under the verdict, because it changes what every line below it
93
+ // means. The regions are still correct — those pixels really did move, in
94
+ // those components — but they are the shape of a leak rather than the shape
95
+ // of an edit, and an agent that reads the region list first starts editing a
96
+ // component whose source nobody changed.
97
+ ...((observation.unstable === undefined || observation.unstable.absorbed !== undefined) &&
98
+ observation.alone?.reproduced === false
99
+ ? [
100
+ `NOT A COMPONENT CHANGE: ${observation.alone?.because}. The regions below are real`,
101
+ 'but they are what the leak did, not what an edit did. Do not change these',
102
+ 'components. Find the subject that writes the state this one reads by bisecting',
103
+ 'run order — the run cannot name it, because module-level state is invisible to',
104
+ 'anything a rendered document can observe about itself.',
105
+ ]
106
+ : []),
107
+ ...(observation.missingFonts !== undefined && observation.missingFonts.length > 0
108
+ ? [
109
+ `warning: the renderer lacked ${observation.missingFonts.join(', ')}; ` +
110
+ 'these images are of a substituted font and their metrics are not the product’s',
111
+ ]
112
+ : []),
113
+ ];
114
+ // What the record says about the components this subject's change was
115
+ // attributed to. Placed after the verdict and before the regions, because it
116
+ // changes how the region list should be read: a component that has caused an
117
+ // approved change in eleven of the last forty runs is a component whose next
118
+ // change is unremarkable, and one that has never moved before is the opposite.
119
+ lines.push(...componentChurn(report, observation));
120
+ // Findings before regions. A subject can be `unchanged` and still carry
121
+ // them, in which case they are the only thing this tool has to say, and a
122
+ // reader who stopped at "nothing changed" would never reach them.
123
+ if (observation.findings !== undefined && observation.findings.length > 0) {
124
+ lines.push('', findingTotals(observation.findings) + ':', ...findingLines(observation.findings));
125
+ }
126
+ if (observation.signals?.presentation !== undefined) {
127
+ lines.push('', ...describePresentation(observation.signals.presentation));
128
+ }
129
+ if (observation.regions.length === 0)
130
+ return lines.join('\n');
131
+ lines.push('', ...observation.regions.map(regionLine));
132
+ if (observation.truncated !== undefined && observation.truncated.regions > 0) {
133
+ // Never a silent cap: a truncated list that does not say so reads as
134
+ // complete coverage, and the reader has no way to tell the difference.
135
+ lines.push(`+${observation.truncated.regions} smaller region(s) not listed ` +
136
+ `(${observation.truncated.pixels}px)`);
137
+ }
138
+ if (observation.images !== undefined) {
139
+ lines.push('', 'images: ' +
140
+ Object.entries(observation.images)
141
+ .map(([kind, path]) => `${kind}=${path}`)
142
+ .join(' '));
143
+ }
144
+ return lines.join('\n');
145
+ },
146
+ };
147
+ /**
148
+ * What the subject *is*, which is not always its verdict.
149
+ *
150
+ * Three words over one, and the order is the precedence: instability disqualifies
151
+ * the clean-world answer, which in turn re-reads the verdict. All three subjects
152
+ * are `changed` — the pixels did move — and all three need different work, so a
153
+ * reader given the verdict alone acts on the wrong one two times out of three.
154
+ */
155
+ function label(observation) {
156
+ if (observation.unstable !== undefined && observation.unstable.absorbed === undefined) {
157
+ return 'unstable';
158
+ }
159
+ if (observation.alone?.reproduced === false)
160
+ return 'order-dependent';
161
+ return observation.verdict;
162
+ }
163
+ /**
164
+ * How often the components named here have changed before.
165
+ *
166
+ * Only the causes, and only the ones the record answered for. A component this
167
+ * report has no entry for is not a component that has never changed — it is one
168
+ * nobody asked about, either because no store answered or because the run capped
169
+ * how many it asked. Silence is therefore silent rather than reassuring: nothing
170
+ * is printed for it, and the run's own warnings carry the reason.
171
+ */
172
+ function componentChurn(report, observation) {
173
+ if (report.churn === undefined)
174
+ return [];
175
+ const named = [
176
+ ...new Set(observation.regions
177
+ .filter((region) => region.cause && region.component !== undefined)
178
+ .map((region) => region.component)),
179
+ ];
180
+ const lines = named.flatMap((component) => {
181
+ const record = report.churn?.[component];
182
+ return record === undefined ? [] : [` ${record.because}`];
183
+ });
184
+ return lines.length === 0 ? [] : ['', 'HOW OFTEN THESE COMPONENTS CHANGE:', ...lines];
185
+ }
186
+ /**
187
+ * How often this has happened before, or the fact that nobody was asked.
188
+ *
189
+ * The absent arm is not politeness. Two readings put a floor under flakiness and
190
+ * never a ceiling, so "no record answered" and "this has never happened" are
191
+ * different sentences with different next actions — and only one of them is
192
+ * something this report can support.
193
+ */
194
+ function recurrence(report, subject) {
195
+ const record = report.flakiness?.[subject];
196
+ if (record === undefined) {
197
+ return [
198
+ 'No history record answered for this subject, so nothing here says whether it has',
199
+ 'happened before. Absence of a record is not a first occurrence.',
200
+ ];
201
+ }
202
+ const named = record.causes
203
+ .map((cause) => [cause.component, cause.band].filter((part) => part !== undefined).join(' '))
204
+ .filter((label) => label !== '')
205
+ .slice(0, 3);
206
+ return [
207
+ `OVER THE RECORDED WINDOW: ${record.because}.` +
208
+ (named.length === 0 ? '' : ` Seen in: ${named.join(', ')}.`),
209
+ ...(record.sweepsSince > 0 && record.occurrences > 1
210
+ ? [
211
+ 'It has been quiet for the last few sweeps, so check whether a fix already landed',
212
+ 'before writing another one.',
213
+ ]
214
+ : []),
215
+ ];
216
+ }
217
+ /** The unstable components as `Name file:line`, which is what an editor opens. */
218
+ function unstableAt(components) {
219
+ return components
220
+ .map((component) => component.file === undefined ? component.name : `${component.name} ${component.file}`)
221
+ .join(', ');
222
+ }
223
+ function regionLine(region) {
224
+ const head = region.unattributed === true
225
+ ? `unattributed — a region no box contained, which usually means the scale or origin was wrong`
226
+ : (region.component ?? region.path ?? 'unknown');
227
+ return [
228
+ ` ${region.cause ? 'cause ' : 'collateral'} ${region.pixels}px ` +
229
+ `at ${region.x},${region.y} ${region.width}×${region.height} — ${head}`,
230
+ region.where !== undefined ? ` in ${region.where}` : null,
231
+ region.file !== undefined ? ` ${region.file}` : null,
232
+ // The digest, on the region it describes. An agent's two non-code responses
233
+ // to a recurring difference are `variance accept --shape <it>` and an
234
+ // `ignore` rule keyed on it, and neither is reachable without the value —
235
+ // printing it in a different section would make an agent guess which line it
236
+ // belonged to.
237
+ region.fingerprint !== undefined ? ` shape ${region.fingerprint}` : null,
238
+ ]
239
+ .filter((line) => line !== null)
240
+ .join('\n');
241
+ }
242
+ /**
243
+ * The defects under the band each was filed as, dated where the record says.
244
+ *
245
+ * The same two questions the review page and the HTML report answer above their
246
+ * own lists, and the same fold answering them — an agent reading a flat list of
247
+ * eleven rules has to know which of them the project blocks on and which of them
248
+ * it inherited, and neither is derivable from the rule name.
249
+ *
250
+ * The band heads a group rather than prefixing a row because most renders carry
251
+ * one band, and a word repeated down a column is a word a reader stops seeing.
252
+ *
253
+ * Above the bands is the split the review page and the HTML report draw: what
254
+ * this change brought, then — under its own count — what it did not. There is no
255
+ * folding to do in text, so the separation is an order and a line, and the order
256
+ * is the part that matters. An agent handed twenty inherited defects first will
257
+ * either fix twenty or fix none.
258
+ */
259
+ function findingLines(findings) {
260
+ const { arrived, rest, dated } = byArrival(findings);
261
+ if (!dated)
262
+ return bandLines(findings);
263
+ const carried = carriedLine(findings);
264
+ return [
265
+ ...(arrived.length === 0 ? [] : bandLines(arrived)),
266
+ ...(carried === undefined ? [] : [` ${carried} — separate work`, ...bandLines(rest)]),
267
+ ];
268
+ }
269
+ /** One list, banded, dated per row only where the rows disagree. */
270
+ function bandLines(findings) {
271
+ const dated = mixedAges(findings);
272
+ return byBand(findings).flatMap((group) => [
273
+ ` ${group.title}`,
274
+ ...group.findings.map((finding) => ` [${finding.rule}] ${finding.what}` +
275
+ (dated ? ` — ${AGE_WORDS[ageOf(finding)]}` : '') +
276
+ (finding.file === undefined ? '' : `\n ${finding.file}`)),
277
+ ]);
278
+ }
279
+ //# sourceMappingURL=describe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describe.js","sourceRoot":"","sources":["../../src/tools/describe.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n AGE_WORDS,\n ageOf,\n byArrival,\n byBand,\n carriedLine,\n findingTotals,\n mixedAges,\n} from '@variance-authority/report';\nimport type {\n FindingRecord,\n FlakinessRecord,\n RegionRecord,\n RunReport,\n} from '@variance-authority/report';\nimport { describePresentation } from '../presentation.js';\nimport { subjectOf, unobserved } from './subject.js';\nimport type { Tool } from './tool.js';\n\n/**\n * `variance_describe`, and the region format nothing else prints.\n *\n * `regionLine` stays with the tool because the region list is this answer's whole\n * substance, and everything around it is an ordering decision that belongs to\n * this tool rather than to regions in general: the leak warning above the list\n * because it changes what every line below means, the findings before it because\n * an `unchanged` subject can carry nothing else, and the truncation count after\n * it because a capped list that does not say so reads as a complete one.\n */\n\n/**\n * One subject, in full: what changed, where it is, and which file to open.\n *\n * The tool an agent calls after the summary, and the one that has to be complete\n * — an agent that has to ask three more questions to locate an edit will guess\n * instead.\n */\nexport const describe: Tool = {\n name: 'variance_describe',\n description:\n 'Everything known about one subject: the ranked regions, the component each belongs to, ' +\n 'a landmark description of where it is on the page, the source file to edit, and each ' +\n \"region's shape fingerprint — which is what `variance accept --shape` and an `ignore` \" +\n 'rule are keyed on when a difference is not a code problem. Causes are listed before ' +\n 'collateral. A subject the run did not observe is answered with why, not refused.',\n inputSchema: {\n type: 'object',\n properties: { subject: { type: 'string', description: 'Subject id from variance_summary.' } },\n required: ['subject'],\n additionalProperties: false,\n },\n\n run(report, input) {\n const located = subjectOf(report, input);\n if (!located.observed) return unobserved(located.entry);\n const { observation } = located;\n\n const lines = [\n `[${label(observation)}] ${observation.subject}`,\n observation.because,\n // Ahead of the leak note, because it disqualifies that one too. A clean\n // world re-collection was never taken on this subject — its answer would\n // have been a comparison between two readings that do not agree anyway —\n // so there is no order-dependence claim here to place second.\n ...(observation.unstable !== undefined && observation.unstable.absorbed === undefined\n ? [\n `NOT A COMPONENT CHANGE: ${observation.unstable.because}. The regions below are`,\n 'the difference between one of those two readings and the baseline, so which',\n 'ones appear here is decided by a race. Do not review them and do not accept',\n 'this subject. Fix what moves between two readings of the same page' +\n (observation.unstable.components.length === 0\n ? ', starting from the subject itself — no snapshot was collected, so nothing'\n + ' could name the component that moved.'\n : `, starting at ${unstableAt(observation.unstable.components)}` +\n (observation.unstable.bands.length === 0\n ? '.'\n : ` (${observation.unstable.bands.join(', ')}).`)),\n ]\n : []),\n // What the record says, directly under the instruction it qualifies. An\n // agent handed \"fix what moves between two readings\" acts differently when\n // the answer is \"this has fired in eleven of the last twelve sweeps\" than\n // when it is \"twice in March, and nine sweeps have been clean since\" — the\n // second is a fix that already landed, and rewriting it is a day spent\n // re-solving somebody's solved problem.\n ...(observation.unstable !== undefined ? recurrence(report, observation.subject) : []),\n // The experiment, spelled out. An agent that has just been told to fix\n // what moves between two readings needs a way to find out whether it did,\n // and the alternative it reaches for otherwise is a full run — three\n // hundred subjects and a browser, to answer a question about one of them.\n ...(observation.unstable !== undefined && observation.unstable.absorbed === undefined\n ? [\n 'VERIFY A FIX WITH:',\n ` variance run --subjects '${observation.subject}' --flakes`,\n 'which reads this subject twice and nothing else, and exits 1 while the two',\n 'readings still disagree — even when every verdict is green.',\n ]\n : []),\n // The same movement, inside the boundary the subject declared. Said rather\n // than suppressed: an agent that later sees this subject go green wants to\n // know a level was doing work, and the rule's name is what makes that\n // auditable. It carries no instruction, because there is nothing to do.\n ...(observation.unstable?.absorbed !== undefined\n ? [\n `read differently between two readings in ${(observation.unstable.bands ?? []).join(', ')}, ` +\n `and \\`${observation.unstable.absorbed.rule}\\` asserts on ` +\n `${observation.unstable.absorbed.level} — so none of it is asserted on here. ` +\n 'Working as declared; the regions below are the comparison, not the movement.',\n ]\n : []),\n // Placed under the verdict, because it changes what every line below it\n // means. The regions are still correct — those pixels really did move, in\n // those components — but they are the shape of a leak rather than the shape\n // of an edit, and an agent that reads the region list first starts editing a\n // component whose source nobody changed.\n ...((observation.unstable === undefined || observation.unstable.absorbed !== undefined) &&\n observation.alone?.reproduced === false\n ? [\n `NOT A COMPONENT CHANGE: ${observation.alone?.because}. The regions below are real`,\n 'but they are what the leak did, not what an edit did. Do not change these',\n 'components. Find the subject that writes the state this one reads by bisecting',\n 'run order — the run cannot name it, because module-level state is invisible to',\n 'anything a rendered document can observe about itself.',\n ]\n : []),\n ...(observation.missingFonts !== undefined && observation.missingFonts.length > 0\n ? [\n `warning: the renderer lacked ${observation.missingFonts.join(', ')}; ` +\n 'these images are of a substituted font and their metrics are not the product’s',\n ]\n : []),\n ];\n\n // What the record says about the components this subject's change was\n // attributed to. Placed after the verdict and before the regions, because it\n // changes how the region list should be read: a component that has caused an\n // approved change in eleven of the last forty runs is a component whose next\n // change is unremarkable, and one that has never moved before is the opposite.\n lines.push(...componentChurn(report, observation));\n\n // Findings before regions. A subject can be `unchanged` and still carry\n // them, in which case they are the only thing this tool has to say, and a\n // reader who stopped at \"nothing changed\" would never reach them.\n if (observation.findings !== undefined && observation.findings.length > 0) {\n lines.push('', findingTotals(observation.findings) + ':', ...findingLines(observation.findings));\n }\n\n if (observation.signals?.presentation !== undefined) {\n lines.push('', ...describePresentation(observation.signals.presentation));\n }\n\n if (observation.regions.length === 0) return lines.join('\\n');\n\n lines.push('', ...observation.regions.map(regionLine));\n\n if (observation.truncated !== undefined && observation.truncated.regions > 0) {\n // Never a silent cap: a truncated list that does not say so reads as\n // complete coverage, and the reader has no way to tell the difference.\n lines.push(\n `+${observation.truncated.regions} smaller region(s) not listed ` +\n `(${observation.truncated.pixels}px)`,\n );\n }\n\n if (observation.images !== undefined) {\n lines.push(\n '',\n 'images: ' +\n Object.entries(observation.images)\n .map(([kind, path]) => `${kind}=${path}`)\n .join(' '),\n );\n }\n\n return lines.join('\\n');\n },\n};\n\n/**\n * What the subject *is*, which is not always its verdict.\n *\n * Three words over one, and the order is the precedence: instability disqualifies\n * the clean-world answer, which in turn re-reads the verdict. All three subjects\n * are `changed` — the pixels did move — and all three need different work, so a\n * reader given the verdict alone acts on the wrong one two times out of three.\n */\nfunction label(observation: {\n readonly verdict: string;\n readonly unstable?: { readonly absorbed?: unknown };\n readonly alone?: { readonly reproduced: boolean };\n}): string {\n if (observation.unstable !== undefined && observation.unstable.absorbed === undefined) {\n return 'unstable';\n }\n if (observation.alone?.reproduced === false) return 'order-dependent';\n return observation.verdict;\n}\n\n/**\n * How often the components named here have changed before.\n *\n * Only the causes, and only the ones the record answered for. A component this\n * report has no entry for is not a component that has never changed — it is one\n * nobody asked about, either because no store answered or because the run capped\n * how many it asked. Silence is therefore silent rather than reassuring: nothing\n * is printed for it, and the run's own warnings carry the reason.\n */\nfunction componentChurn(\n report: RunReport,\n observation: { readonly regions: readonly RegionRecord[] },\n): readonly string[] {\n if (report.churn === undefined) return [];\n\n const named = [\n ...new Set(\n observation.regions\n .filter((region) => region.cause && region.component !== undefined)\n .map((region) => region.component as string),\n ),\n ];\n\n const lines = named.flatMap((component) => {\n const record = report.churn?.[component];\n return record === undefined ? [] : [` ${record.because}`];\n });\n\n return lines.length === 0 ? [] : ['', 'HOW OFTEN THESE COMPONENTS CHANGE:', ...lines];\n}\n\n/**\n * How often this has happened before, or the fact that nobody was asked.\n *\n * The absent arm is not politeness. Two readings put a floor under flakiness and\n * never a ceiling, so \"no record answered\" and \"this has never happened\" are\n * different sentences with different next actions — and only one of them is\n * something this report can support.\n */\nfunction recurrence(report: RunReport, subject: string): readonly string[] {\n const record = report.flakiness?.[subject];\n\n if (record === undefined) {\n return [\n 'No history record answered for this subject, so nothing here says whether it has',\n 'happened before. Absence of a record is not a first occurrence.',\n ];\n }\n\n const named = record.causes\n .map((cause: FlakinessRecord['causes'][number]) =>\n [cause.component, cause.band].filter((part) => part !== undefined).join(' '),\n )\n .filter((label: string) => label !== '')\n .slice(0, 3);\n\n return [\n `OVER THE RECORDED WINDOW: ${record.because}.` +\n (named.length === 0 ? '' : ` Seen in: ${named.join(', ')}.`),\n ...(record.sweepsSince > 0 && record.occurrences > 1\n ? [\n 'It has been quiet for the last few sweeps, so check whether a fix already landed',\n 'before writing another one.',\n ]\n : []),\n ];\n}\n\n/** The unstable components as `Name file:line`, which is what an editor opens. */\nfunction unstableAt(\n components: readonly { readonly name: string; readonly file?: string }[],\n): string {\n return components\n .map((component) =>\n component.file === undefined ? component.name : `${component.name} ${component.file}`,\n )\n .join(', ');\n}\n\nfunction regionLine(region: RegionRecord): string {\n const head = region.unattributed === true\n ? `unattributed — a region no box contained, which usually means the scale or origin was wrong`\n : (region.component ?? region.path ?? 'unknown');\n\n return [\n ` ${region.cause ? 'cause ' : 'collateral'} ${region.pixels}px ` +\n `at ${region.x},${region.y} ${region.width}×${region.height} — ${head}`,\n region.where !== undefined ? ` in ${region.where}` : null,\n region.file !== undefined ? ` ${region.file}` : null,\n // The digest, on the region it describes. An agent's two non-code responses\n // to a recurring difference are `variance accept --shape <it>` and an\n // `ignore` rule keyed on it, and neither is reachable without the value —\n // printing it in a different section would make an agent guess which line it\n // belonged to.\n region.fingerprint !== undefined ? ` shape ${region.fingerprint}` : null,\n ]\n .filter((line): line is string => line !== null)\n .join('\\n');\n}\n\n/**\n * The defects under the band each was filed as, dated where the record says.\n *\n * The same two questions the review page and the HTML report answer above their\n * own lists, and the same fold answering them — an agent reading a flat list of\n * eleven rules has to know which of them the project blocks on and which of them\n * it inherited, and neither is derivable from the rule name.\n *\n * The band heads a group rather than prefixing a row because most renders carry\n * one band, and a word repeated down a column is a word a reader stops seeing.\n *\n * Above the bands is the split the review page and the HTML report draw: what\n * this change brought, then — under its own count — what it did not. There is no\n * folding to do in text, so the separation is an order and a line, and the order\n * is the part that matters. An agent handed twenty inherited defects first will\n * either fix twenty or fix none.\n */\nfunction findingLines(findings: readonly FindingRecord[]): readonly string[] {\n const { arrived, rest, dated } = byArrival(findings);\n if (!dated) return bandLines(findings);\n\n const carried = carriedLine(findings);\n\n return [\n ...(arrived.length === 0 ? [] : bandLines(arrived)),\n ...(carried === undefined ? [] : [` ${carried} — separate work`, ...bandLines(rest)]),\n ];\n}\n\n/** One list, banded, dated per row only where the rows disagree. */\nfunction bandLines(findings: readonly FindingRecord[]): readonly string[] {\n const dated = mixedAges(findings);\n\n return byBand(findings).flatMap((group) => [\n ` ${group.title}`,\n ...group.findings.map(\n (finding) =>\n ` [${finding.rule}] ${finding.what}` +\n (dated ? ` — ${AGE_WORDS[ageOf(finding)]}` : '') +\n (finding.file === undefined ? '' : `\\n ${finding.file}`),\n ),\n ]);\n}\n\n"]}
@@ -0,0 +1,20 @@
1
+ import { type Tool } from './tool.js';
2
+ export type StateDifference = {
3
+ readonly kind: 'added';
4
+ readonly path: string;
5
+ readonly value: unknown;
6
+ } | {
7
+ readonly kind: 'removed';
8
+ readonly path: string;
9
+ readonly value: unknown;
10
+ } | {
11
+ readonly kind: 'changed';
12
+ readonly path: string;
13
+ readonly before: unknown;
14
+ readonly after: unknown;
15
+ };
16
+ /** Compare two JSON-compatible states without assigning meaning to either one. */
17
+ export declare function diffState(before: unknown, after: unknown): readonly StateDifference[];
18
+ /** The one stateful tool every served subject receives. */
19
+ export declare const diff: Tool<unknown>;
20
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1,85 @@
1
+ import { NO_ARGS } from './tool.js';
2
+ /** Compare two JSON-compatible states without assigning meaning to either one. */
3
+ export function diffState(before, after) {
4
+ const differences = [];
5
+ visit('$', before, after, differences);
6
+ return differences;
7
+ }
8
+ /** The one stateful tool every served subject receives. */
9
+ export const diff = {
10
+ name: 'variance_diff',
11
+ description: 'Compare the current state with the state held from the previous successful MCP tool invocation. ' +
12
+ 'The first invocation records state and has nothing to compare.',
13
+ inputSchema: NO_ARGS,
14
+ run(current, _input, invocation) {
15
+ if (invocation?.previous === undefined) {
16
+ return 'No previous MCP invocation state. The current state is now remembered.';
17
+ }
18
+ const differences = diffState(invocation.previous, current);
19
+ if (differences.length === 0) {
20
+ return 'The current state matches the previous MCP invocation.';
21
+ }
22
+ return [
23
+ `The current state differs from the previous MCP invocation at ${String(differences.length)} path(s).`,
24
+ ...differences.map(describe),
25
+ ].join('\n');
26
+ },
27
+ };
28
+ function visit(path, before, after, differences) {
29
+ if (Object.is(before, after))
30
+ return;
31
+ if (isRecord(before) && isRecord(after)) {
32
+ const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort();
33
+ for (const key of keys) {
34
+ const next = `${path}${property(key)}`;
35
+ if (!(key in before)) {
36
+ differences.push({ kind: 'added', path: next, value: after[key] });
37
+ }
38
+ else if (!(key in after)) {
39
+ differences.push({ kind: 'removed', path: next, value: before[key] });
40
+ }
41
+ else {
42
+ visit(next, before[key], after[key], differences);
43
+ }
44
+ }
45
+ return;
46
+ }
47
+ if (Array.isArray(before) && Array.isArray(after)) {
48
+ const length = Math.max(before.length, after.length);
49
+ for (let index = 0; index < length; index += 1) {
50
+ const next = `${path}[${String(index)}]`;
51
+ if (index >= before.length) {
52
+ differences.push({ kind: 'added', path: next, value: after[index] });
53
+ }
54
+ else if (index >= after.length) {
55
+ differences.push({ kind: 'removed', path: next, value: before[index] });
56
+ }
57
+ else {
58
+ visit(next, before[index], after[index], differences);
59
+ }
60
+ }
61
+ return;
62
+ }
63
+ differences.push({ kind: 'changed', path, before, after });
64
+ }
65
+ function isRecord(value) {
66
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
67
+ }
68
+ function property(key) {
69
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;
70
+ }
71
+ function describe(difference) {
72
+ switch (difference.kind) {
73
+ case 'added':
74
+ return `+ ${difference.path}: ${valueOf(difference.value)}`;
75
+ case 'removed':
76
+ return `- ${difference.path}: ${valueOf(difference.value)}`;
77
+ case 'changed':
78
+ return `~ ${difference.path}: ${valueOf(difference.before)} -> ${valueOf(difference.after)}`;
79
+ }
80
+ }
81
+ function valueOf(value) {
82
+ const encoded = JSON.stringify(value);
83
+ return encoded === undefined ? String(value) : encoded;
84
+ }
85
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/tools/diff.ts"],"names":[],"mappings":"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","sourcesContent":["import { NO_ARGS, type Tool } from './tool.js';\n\nexport type StateDifference =\n | { readonly kind: 'added'; readonly path: string; readonly value: unknown }\n | { readonly kind: 'removed'; readonly path: string; readonly value: unknown }\n | {\n readonly kind: 'changed';\n readonly path: string;\n readonly before: unknown;\n readonly after: unknown;\n };\n\n/** Compare two JSON-compatible states without assigning meaning to either one. */\nexport function diffState(before: unknown, after: unknown): readonly StateDifference[] {\n const differences: StateDifference[] = [];\n visit('$', before, after, differences);\n return differences;\n}\n\n/** The one stateful tool every served subject receives. */\nexport const diff: Tool<unknown> = {\n name: 'variance_diff',\n description:\n 'Compare the current state with the state held from the previous successful MCP tool invocation. ' +\n 'The first invocation records state and has nothing to compare.',\n inputSchema: NO_ARGS,\n run(current, _input, invocation) {\n if (invocation?.previous === undefined) {\n return 'No previous MCP invocation state. The current state is now remembered.';\n }\n\n const differences = diffState(invocation.previous, current);\n if (differences.length === 0) {\n return 'The current state matches the previous MCP invocation.';\n }\n\n return [\n `The current state differs from the previous MCP invocation at ${String(differences.length)} path(s).`,\n ...differences.map(describe),\n ].join('\\n');\n },\n};\n\nfunction visit(\n path: string,\n before: unknown,\n after: unknown,\n differences: StateDifference[],\n): void {\n if (Object.is(before, after)) return;\n\n if (isRecord(before) && isRecord(after)) {\n const keys = [...new Set([...Object.keys(before), ...Object.keys(after)])].sort();\n for (const key of keys) {\n const next = `${path}${property(key)}`;\n if (!(key in before)) {\n differences.push({ kind: 'added', path: next, value: after[key] });\n } else if (!(key in after)) {\n differences.push({ kind: 'removed', path: next, value: before[key] });\n } else {\n visit(next, before[key], after[key], differences);\n }\n }\n return;\n }\n\n if (Array.isArray(before) && Array.isArray(after)) {\n const length = Math.max(before.length, after.length);\n for (let index = 0; index < length; index += 1) {\n const next = `${path}[${String(index)}]`;\n if (index >= before.length) {\n differences.push({ kind: 'added', path: next, value: after[index] });\n } else if (index >= after.length) {\n differences.push({ kind: 'removed', path: next, value: before[index] });\n } else {\n visit(next, before[index], after[index], differences);\n }\n }\n return;\n }\n\n differences.push({ kind: 'changed', path, before, after });\n}\n\nfunction isRecord(value: unknown): value is Readonly<Record<string, unknown>> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction property(key: string): string {\n return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? `.${key}` : `[${JSON.stringify(key)}]`;\n}\n\nfunction describe(difference: StateDifference): string {\n switch (difference.kind) {\n case 'added':\n return `+ ${difference.path}: ${valueOf(difference.value)}`;\n case 'removed':\n return `- ${difference.path}: ${valueOf(difference.value)}`;\n case 'changed':\n return `~ ${difference.path}: ${valueOf(difference.before)} -> ${valueOf(difference.after)}`;\n }\n}\n\nfunction valueOf(value: unknown): string {\n const encoded = JSON.stringify(value);\n return encoded === undefined ? String(value) : encoded;\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import type { Tool } from './tool.js';
2
+ /**
3
+ * `variance_explain_verdict` — the answers that are not about the code.
4
+ *
5
+ * Kept apart from `variance_describe` although both start by locating a subject.
6
+ * `describe` answers about pixels that moved; this one answers about a comparison
7
+ * that never happened, and its whole job is to end with "there is no edit to
8
+ * make here" — a sentence a region list cannot say, and one that stops being
9
+ * said the moment the two tools share a body and a caller reaches for the
10
+ * regions because they are already in scope.
11
+ */
12
+ /**
13
+ * Why a subject has no comparison.
14
+ *
15
+ * `incomparable` and `new` are the two verdicts an agent will otherwise treat as
16
+ * failures and try to fix in code, which is exactly wrong: neither is about the
17
+ * code. This makes the reason legible enough to act on — or to decide not to.
18
+ *
19
+ * A subject with no observation at all is the third such case and the worst one
20
+ * to refuse. "Unknown subject" tells an agent the subject does not exist, so it
21
+ * stops asking; the truth is that the subject exists and nothing is known about
22
+ * it, which is the opposite conclusion.
23
+ */
24
+ export declare const explain: Tool;
25
+ //# sourceMappingURL=explain-verdict.d.ts.map
@@ -0,0 +1,92 @@
1
+ import { subjectOf, unobserved } from './subject.js';
2
+ /**
3
+ * `variance_explain_verdict` — the answers that are not about the code.
4
+ *
5
+ * Kept apart from `variance_describe` although both start by locating a subject.
6
+ * `describe` answers about pixels that moved; this one answers about a comparison
7
+ * that never happened, and its whole job is to end with "there is no edit to
8
+ * make here" — a sentence a region list cannot say, and one that stops being
9
+ * said the moment the two tools share a body and a caller reaches for the
10
+ * regions because they are already in scope.
11
+ */
12
+ /**
13
+ * Why a subject has no comparison.
14
+ *
15
+ * `incomparable` and `new` are the two verdicts an agent will otherwise treat as
16
+ * failures and try to fix in code, which is exactly wrong: neither is about the
17
+ * code. This makes the reason legible enough to act on — or to decide not to.
18
+ *
19
+ * A subject with no observation at all is the third such case and the worst one
20
+ * to refuse. "Unknown subject" tells an agent the subject does not exist, so it
21
+ * stops asking; the truth is that the subject exists and nothing is known about
22
+ * it, which is the opposite conclusion.
23
+ */
24
+ export const explain = {
25
+ name: 'variance_explain_verdict',
26
+ description: 'Why a subject was not compared. `incomparable` means a baseline exists but another ' +
27
+ 'machine rendered it; `new` means none exists; a subject in the coverage list was never ' +
28
+ 'observed at all. None is a code problem — call this before attempting a fix.',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: { subject: { type: 'string' } },
32
+ required: ['subject'],
33
+ additionalProperties: false,
34
+ },
35
+ run(report, input) {
36
+ const located = subjectOf(report, input);
37
+ if (!located.observed) {
38
+ return [
39
+ unobserved(located.entry),
40
+ '',
41
+ nothingCompared(located.entry.kind),
42
+ ].join('\n');
43
+ }
44
+ const { observation } = located;
45
+ switch (observation.verdict) {
46
+ case 'incomparable':
47
+ return [
48
+ observation.because,
49
+ '',
50
+ 'This is not a code change and cannot be fixed in code. Either run on the machine ' +
51
+ 'that wrote the baseline, re-record the baseline on this one, or use an ephemeral ' +
52
+ 'comparison, which renders both sides here and needs no stored image at all.',
53
+ ].join('\n');
54
+ case 'new':
55
+ return [
56
+ observation.because,
57
+ '',
58
+ 'Nothing has regressed; there is no baseline to regress from. Record one, ' +
59
+ 'or compare ephemerally against the previous revision.',
60
+ ].join('\n');
61
+ case 'unchanged':
62
+ return `${observation.subject} was compared and did not change: ${observation.because}`;
63
+ case 'ignored':
64
+ return [
65
+ observation.because,
66
+ '',
67
+ 'This subject changed. Every changed pixel landed inside a region the project ' +
68
+ 'excluded, so the run is green and nothing here was reviewed. That is a ' +
69
+ 'configuration decision, not a fact about the code: if the change should have ' +
70
+ 'been caught, the ignore is too wide.',
71
+ ].join('\n');
72
+ case 'changed':
73
+ return `${observation.subject} was compared and changed. Call variance_describe for the regions.`;
74
+ }
75
+ },
76
+ };
77
+ /** What an agent should do about a subject nothing was compared for. */
78
+ function nothingCompared(kind) {
79
+ switch (kind) {
80
+ case 'excluded':
81
+ return ('Nothing was compared, so nothing is known about this subject. There is no code ' +
82
+ 'change to make here; if it should be watched, change the exclusion.');
83
+ case 'unreached':
84
+ return ('Nothing was compared, and nothing needed to be: the run resolved that this ' +
85
+ 'change cannot reach this subject. There is no code change to make here.');
86
+ default:
87
+ return ('Nothing was compared, so nothing is known about this subject — an absent ' +
88
+ 'observation is not an unchanged one. Fix whatever stopped the run from seeing ' +
89
+ 'it before treating any part of this run as a pass for this subject.');
90
+ }
91
+ }
92
+ //# sourceMappingURL=explain-verdict.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain-verdict.js","sourceRoot":"","sources":["../../src/tools/explain-verdict.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAGrD;;;;;;;;;GASG;AAEH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,OAAO,GAAS;IAC3B,IAAI,EAAE,0BAA0B;IAChC,WAAW,EACT,qFAAqF;QACrF,yFAAyF;QACzF,8EAA8E;IAChF,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAC3C,QAAQ,EAAE,CAAC,SAAS,CAAC;QACrB,oBAAoB,EAAE,KAAK;KAC5B;IAED,GAAG,CAAC,MAAM,EAAE,KAAK;QACf,MAAM,OAAO,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;QAEzC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YACtB,OAAO;gBACL,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC;gBACzB,EAAE;gBACF,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;aACpC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACf,CAAC;QAED,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;QAEhC,QAAQ,WAAW,CAAC,OAAO,EAAE,CAAC;YAC5B,KAAK,cAAc;gBACjB,OAAO;oBACL,WAAW,CAAC,OAAO;oBACnB,EAAE;oBACF,mFAAmF;wBACjF,mFAAmF;wBACnF,6EAA6E;iBAChF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,KAAK,KAAK;gBACR,OAAO;oBACL,WAAW,CAAC,OAAO;oBACnB,EAAE;oBACF,2EAA2E;wBACzE,uDAAuD;iBAC1D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,KAAK,WAAW;gBACd,OAAO,GAAG,WAAW,CAAC,OAAO,qCAAqC,WAAW,CAAC,OAAO,EAAE,CAAC;YAC1F,KAAK,SAAS;gBACZ,OAAO;oBACL,WAAW,CAAC,OAAO;oBACnB,EAAE;oBACF,+EAA+E;wBAC7E,yEAAyE;wBACzE,+EAA+E;wBAC/E,sCAAsC;iBACzC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACf,KAAK,SAAS;gBACZ,OAAO,GAAG,WAAW,CAAC,OAAO,oEAAoE,CAAC;QACtG,CAAC;IACH,CAAC;CACF,CAAC;AAEF,wEAAwE;AACxE,SAAS,eAAe,CAAC,IAAyB;IAChD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,UAAU;YACb,OAAO,CACL,iFAAiF;gBACjF,qEAAqE,CACtE,CAAC;QACJ,KAAK,WAAW;YACd,OAAO,CACL,6EAA6E;gBAC7E,yEAAyE,CAC1E,CAAC;QACJ;YACE,OAAO,CACL,2EAA2E;gBAC3E,gFAAgF;gBAChF,qEAAqE,CACtE,CAAC;IACN,CAAC;AACH,CAAC","sourcesContent":["import type { NotObserved } from '@variance-authority/report';\nimport { subjectOf, unobserved } from './subject.js';\nimport type { Tool } from './tool.js';\n\n/**\n * `variance_explain_verdict` — the answers that are not about the code.\n *\n * Kept apart from `variance_describe` although both start by locating a subject.\n * `describe` answers about pixels that moved; this one answers about a comparison\n * that never happened, and its whole job is to end with \"there is no edit to\n * make here\" — a sentence a region list cannot say, and one that stops being\n * said the moment the two tools share a body and a caller reaches for the\n * regions because they are already in scope.\n */\n\n/**\n * Why a subject has no comparison.\n *\n * `incomparable` and `new` are the two verdicts an agent will otherwise treat as\n * failures and try to fix in code, which is exactly wrong: neither is about the\n * code. This makes the reason legible enough to act on — or to decide not to.\n *\n * A subject with no observation at all is the third such case and the worst one\n * to refuse. \"Unknown subject\" tells an agent the subject does not exist, so it\n * stops asking; the truth is that the subject exists and nothing is known about\n * it, which is the opposite conclusion.\n */\nexport const explain: Tool = {\n name: 'variance_explain_verdict',\n description:\n 'Why a subject was not compared. `incomparable` means a baseline exists but another ' +\n 'machine rendered it; `new` means none exists; a subject in the coverage list was never ' +\n 'observed at all. None is a code problem — call this before attempting a fix.',\n inputSchema: {\n type: 'object',\n properties: { subject: { type: 'string' } },\n required: ['subject'],\n additionalProperties: false,\n },\n\n run(report, input) {\n const located = subjectOf(report, input);\n\n if (!located.observed) {\n return [\n unobserved(located.entry),\n '',\n nothingCompared(located.entry.kind),\n ].join('\\n');\n }\n\n const { observation } = located;\n\n switch (observation.verdict) {\n case 'incomparable':\n return [\n observation.because,\n '',\n 'This is not a code change and cannot be fixed in code. Either run on the machine ' +\n 'that wrote the baseline, re-record the baseline on this one, or use an ephemeral ' +\n 'comparison, which renders both sides here and needs no stored image at all.',\n ].join('\\n');\n case 'new':\n return [\n observation.because,\n '',\n 'Nothing has regressed; there is no baseline to regress from. Record one, ' +\n 'or compare ephemerally against the previous revision.',\n ].join('\\n');\n case 'unchanged':\n return `${observation.subject} was compared and did not change: ${observation.because}`;\n case 'ignored':\n return [\n observation.because,\n '',\n 'This subject changed. Every changed pixel landed inside a region the project ' +\n 'excluded, so the run is green and nothing here was reviewed. That is a ' +\n 'configuration decision, not a fact about the code: if the change should have ' +\n 'been caught, the ignore is too wide.',\n ].join('\\n');\n case 'changed':\n return `${observation.subject} was compared and changed. Call variance_describe for the regions.`;\n }\n },\n};\n\n/** What an agent should do about a subject nothing was compared for. */\nfunction nothingCompared(kind: NotObserved['kind']): string {\n switch (kind) {\n case 'excluded':\n return (\n 'Nothing was compared, so nothing is known about this subject. There is no code ' +\n 'change to make here; if it should be watched, change the exclusion.'\n );\n case 'unreached':\n return (\n 'Nothing was compared, and nothing needed to be: the run resolved that this ' +\n 'change cannot reach this subject. There is no code change to make here.'\n );\n default:\n return (\n 'Nothing was compared, so nothing is known about this subject — an absent ' +\n 'observation is not an unchanged one. Fix whatever stopped the run from seeing ' +\n 'it before treating any part of this run as a pass for this subject.'\n );\n }\n}\n"]}
@@ -0,0 +1,40 @@
1
+ import type { Tool } from './tool.js';
2
+ /**
3
+ * `variance_findings` — the one answer here that reads no comparison.
4
+ *
5
+ * Alone in a module because nothing else in this package groups by anything but
6
+ * a subject. This tool inverts that twice — by rule, then by the place the rule
7
+ * fired — and both inversions exist for the same reason the docket does: the
8
+ * unit a reader acts on is an edit, not an occurrence.
9
+ */
10
+ /**
11
+ * Defects in the render itself, which no comparison could have reported.
12
+ *
13
+ * A separate tool rather than part of `variance_describe` because it answers a
14
+ * different question. `describe` answers "what did this change do"; this answers
15
+ * "what is wrong with this component regardless of whether anyone touched it" —
16
+ * and the second is the one a comparison structurally cannot reach, since a
17
+ * control that never had an accessible name compares equal to itself forever.
18
+ *
19
+ * Grouped by rule rather than by subject. One missing `alt` in twelve stories is
20
+ * one edit to one component, and listing it twelve times under twelve subject
21
+ * headings is the same fatigue the docket exists to prevent.
22
+ *
23
+ * ## Two things an agent cannot act on this list without
24
+ *
25
+ * **What kind of defect it is.** Most of these rules are accessibility and the
26
+ * description used to say all of them were, which is wrong about the two that
27
+ * are not — a string the locale pass never translated is a content defect, and
28
+ * a box past its container's edge is a layout one. The band is the same word the
29
+ * project blocks on, so the heading a reader sees and the policy that stops
30
+ * their merge are the same word.
31
+ *
32
+ * **Whether it is theirs.** A defect the baseline carried too is somebody else's
33
+ * afternoon, and an agent told twelve defects with no dating will either fix
34
+ * twelve or fix none. The date is only ever printed from a record; nothing here
35
+ * infers it from an absence — and the rules holding a defect this change brought
36
+ * are listed before the rules holding only inherited ones, so an agent that reads
37
+ * the first group and stops has read the part it can act on.
38
+ */
39
+ export declare const findings: Tool;
40
+ //# sourceMappingURL=findings.d.ts.map