@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.
- package/CHANGELOG.md +89 -0
- package/LICENSE +21 -0
- package/README.md +407 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.js +32 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -0
- package/dist/observability-subject.d.ts +22 -0
- package/dist/observability-subject.js +2 -0
- package/dist/observability-subject.js.map +1 -0
- package/dist/presentation.d.ts +6 -0
- package/dist/presentation.js +64 -0
- package/dist/presentation.js.map +1 -0
- package/dist/protocol.d.ts +82 -0
- package/dist/protocol.js +186 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server.d.ts +71 -0
- package/dist/server.js +103 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/adjudicate.d.ts +25 -0
- package/dist/tools/adjudicate.js +122 -0
- package/dist/tools/adjudicate.js.map +1 -0
- package/dist/tools/attention.d.ts +13 -0
- package/dist/tools/attention.js +151 -0
- package/dist/tools/attention.js.map +1 -0
- package/dist/tools/changelog.d.ts +38 -0
- package/dist/tools/changelog.js +201 -0
- package/dist/tools/changelog.js.map +1 -0
- package/dist/tools/changes.d.ts +31 -0
- package/dist/tools/changes.js +120 -0
- package/dist/tools/changes.js.map +1 -0
- package/dist/tools/composition.d.ts +13 -0
- package/dist/tools/composition.js +366 -0
- package/dist/tools/composition.js.map +1 -0
- package/dist/tools/describe.d.ts +20 -0
- package/dist/tools/describe.js +279 -0
- package/dist/tools/describe.js.map +1 -0
- package/dist/tools/diff.d.ts +20 -0
- package/dist/tools/diff.js +85 -0
- package/dist/tools/diff.js.map +1 -0
- package/dist/tools/explain-verdict.d.ts +25 -0
- package/dist/tools/explain-verdict.js +92 -0
- package/dist/tools/explain-verdict.js.map +1 -0
- package/dist/tools/findings.d.ts +40 -0
- package/dist/tools/findings.js +130 -0
- package/dist/tools/findings.js.map +1 -0
- package/dist/tools/narrowing.d.ts +26 -0
- package/dist/tools/narrowing.js +38 -0
- package/dist/tools/narrowing.js.map +1 -0
- package/dist/tools/observability.d.ts +7 -0
- package/dist/tools/observability.js +199 -0
- package/dist/tools/observability.js.map +1 -0
- package/dist/tools/presentations.d.ts +6 -0
- package/dist/tools/presentations.js +74 -0
- package/dist/tools/presentations.js.map +1 -0
- package/dist/tools/run-signals.d.ts +12 -0
- package/dist/tools/run-signals.js +115 -0
- package/dist/tools/run-signals.js.map +1 -0
- package/dist/tools/scenarios.d.ts +6 -0
- package/dist/tools/scenarios.js +62 -0
- package/dist/tools/scenarios.js.map +1 -0
- package/dist/tools/source-tests.d.ts +5 -0
- package/dist/tools/source-tests.js +95 -0
- package/dist/tools/source-tests.js.map +1 -0
- package/dist/tools/subject.d.ts +45 -0
- package/dist/tools/subject.js +57 -0
- package/dist/tools/subject.js.map +1 -0
- package/dist/tools/summary.d.ts +31 -0
- package/dist/tools/summary.js +436 -0
- package/dist/tools/summary.js.map +1 -0
- package/dist/tools/test-signals.d.ts +16 -0
- package/dist/tools/test-signals.js +106 -0
- package/dist/tools/test-signals.js.map +1 -0
- package/dist/tools/tool.d.ts +64 -0
- package/dist/tools/tool.js +9 -0
- package/dist/tools/tool.js.map +1 -0
- package/dist/tools/trace-component.d.ts +17 -0
- package/dist/tools/trace-component.js +50 -0
- package/dist/tools/trace-component.js.map +1 -0
- package/dist/tools/vantage-lines.d.ts +44 -0
- package/dist/tools/vantage-lines.js +66 -0
- package/dist/tools/vantage-lines.js.map +1 -0
- package/dist/tools/variations.d.ts +27 -0
- package/dist/tools/variations.js +125 -0
- package/dist/tools/variations.js.map +1 -0
- package/dist/tools.d.ts +120 -0
- package/dist/tools.js +127 -0
- package/dist/tools.js.map +1 -0
- package/mark.svg +30 -0
- package/package.json +64 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { adjudicateRun, describeAdjudication } from '@variance-authority/report';
|
|
2
|
+
/**
|
|
3
|
+
* `variance_adjudicate` — the run read back against what the agent said it did.
|
|
4
|
+
*
|
|
5
|
+
* Every other tool here answers about the run. This one answers about the
|
|
6
|
+
* *agent*, and it is the only tool in the set that takes evidence in rather than
|
|
7
|
+
* only handing it out: the declaration is something no report can contain,
|
|
8
|
+
* because the report was written by a process that never knew what the edit was
|
|
9
|
+
* for.
|
|
10
|
+
*
|
|
11
|
+
* What it buys is the arm nothing else in this category ships. `variance_changes`
|
|
12
|
+
* can tell an agent that `Button` moved in twelve subjects; it cannot tell it
|
|
13
|
+
* that `Card` — which the agent believes it just edited — did not move at all.
|
|
14
|
+
* That is not a diff finding, it is the *absence* of one, and an absence is only
|
|
15
|
+
* a finding once something declared it should have been there.
|
|
16
|
+
*
|
|
17
|
+
* The tool deliberately refuses to help an agent declare after the fact. It takes
|
|
18
|
+
* claims as an argument and derives none, so an agent that calls
|
|
19
|
+
* `variance_changes` first and then submits the answer back as its intent is
|
|
20
|
+
* scoring the run against itself — visibly, because the transcript shows the
|
|
21
|
+
* order. Nothing here can prevent that; what it can do is never do it *for* the
|
|
22
|
+
* agent.
|
|
23
|
+
*/
|
|
24
|
+
export const adjudicate = {
|
|
25
|
+
name: 'variance_adjudicate',
|
|
26
|
+
description: 'Read this run back against what you meant to change. Declare each intended change as a ' +
|
|
27
|
+
'claim — the component or shape, why, and at most how many subjects it should reach — and ' +
|
|
28
|
+
'get three answers: what you declared and delivered, what moved that you did not declare, ' +
|
|
29
|
+
'and what you declared that never happened. The last one is the point: it is how you find ' +
|
|
30
|
+
'out an edit did not take (wrong file, dead branch, an overridden rule, a stale build), ' +
|
|
31
|
+
'which no screenshot comparison can tell you. Declare before you read the diff; claims ' +
|
|
32
|
+
'copied out of `variance_changes` score the run against itself.',
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
claims: {
|
|
37
|
+
type: 'array',
|
|
38
|
+
minItems: 1,
|
|
39
|
+
description: 'What you intended to change. One entry per intended change.',
|
|
40
|
+
items: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
root: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
description: 'What the change is about: `component:Button`, `shape:<fingerprint>`, or a ' +
|
|
46
|
+
'bare name read as a component. Matched exactly, never by prefix.',
|
|
47
|
+
},
|
|
48
|
+
reason: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Why you changed it, in your own words. Carried into the answer.',
|
|
51
|
+
},
|
|
52
|
+
maxSubjects: {
|
|
53
|
+
type: 'integer',
|
|
54
|
+
minimum: 1,
|
|
55
|
+
description: 'At most how many subjects this change should reach. Optional, and the most ' +
|
|
56
|
+
'useful field here: exceeding it is not a failure, it is the blast radius ' +
|
|
57
|
+
'you did not expect.',
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
required: ['root', 'reason'],
|
|
61
|
+
additionalProperties: true,
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ['claims'],
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
},
|
|
68
|
+
run(report, input) {
|
|
69
|
+
const { claims, unchecked } = parseClaims(input['claims']);
|
|
70
|
+
if (claims.length === 0) {
|
|
71
|
+
// An empty declaration is not a clean run, and answering it as one would
|
|
72
|
+
// report "nothing you meant to do is missing" to an agent that declared
|
|
73
|
+
// nothing. Said plainly instead, because the fix is one field away.
|
|
74
|
+
return ('No claims were supplied, so there is nothing to adjudicate. This tool answers what ' +
|
|
75
|
+
'your run did against what you said you were doing; with no declaration it can only ' +
|
|
76
|
+
'repeat `variance_changes`. Declare the edits you made, then ask again.');
|
|
77
|
+
}
|
|
78
|
+
return describeAdjudication(adjudicateRun(report, claims, unchecked.length > 0 ? { unchecked } : {}));
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Narrow whatever JSON a model produced, and keep what could not be checked.
|
|
83
|
+
*
|
|
84
|
+
* A declared field this resolution cannot verify is carried out rather than
|
|
85
|
+
* dropped. An agent that declares `bands: ['paint']` and is answered `delivered`
|
|
86
|
+
* has been told its band claim held; a run report keeps no band per change, so
|
|
87
|
+
* nothing looked, and the answer has to say which of the two it is.
|
|
88
|
+
*/
|
|
89
|
+
function parseClaims(value) {
|
|
90
|
+
if (!Array.isArray(value)) {
|
|
91
|
+
throw new Error('`claims` is required and must be an array of {root, reason, maxSubjects?}');
|
|
92
|
+
}
|
|
93
|
+
const claims = [];
|
|
94
|
+
const unchecked = new Set();
|
|
95
|
+
for (const [index, entry] of value.entries()) {
|
|
96
|
+
if (typeof entry !== 'object' || entry === null) {
|
|
97
|
+
throw new Error(`claims[${index}] must be an object with \`root\` and \`reason\``);
|
|
98
|
+
}
|
|
99
|
+
const record = entry;
|
|
100
|
+
const root = record['root'];
|
|
101
|
+
const reason = record['reason'];
|
|
102
|
+
if (typeof root !== 'string' || root === '') {
|
|
103
|
+
throw new Error(`claims[${index}].root is required and must be a non-empty string`);
|
|
104
|
+
}
|
|
105
|
+
if (typeof reason !== 'string' || reason === '') {
|
|
106
|
+
throw new Error(`claims[${index}].reason is required and must be a non-empty string`);
|
|
107
|
+
}
|
|
108
|
+
const maxSubjects = record['maxSubjects'];
|
|
109
|
+
if (maxSubjects !== undefined && (typeof maxSubjects !== 'number' || maxSubjects < 1)) {
|
|
110
|
+
throw new Error(`claims[${index}].maxSubjects must be a positive integer when present`);
|
|
111
|
+
}
|
|
112
|
+
for (const key of Object.keys(record)) {
|
|
113
|
+
if (key !== 'root' && key !== 'reason' && key !== 'maxSubjects')
|
|
114
|
+
unchecked.add(key);
|
|
115
|
+
}
|
|
116
|
+
claims.push(maxSubjects === undefined
|
|
117
|
+
? { root, reason }
|
|
118
|
+
: { root, reason, maxSubjects: Math.trunc(maxSubjects) });
|
|
119
|
+
}
|
|
120
|
+
return { claims, unchecked: [...unchecked] };
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=adjudicate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adjudicate.js","sourceRoot":"","sources":["../../src/tools/adjudicate.ts"],"names":[],"mappings":"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","sourcesContent":["import { adjudicateRun, describeAdjudication, type Claim } from '@variance-authority/report';\nimport type { Tool } from './tool.js';\n\n/**\n * `variance_adjudicate` — the run read back against what the agent said it did.\n *\n * Every other tool here answers about the run. This one answers about the\n * *agent*, and it is the only tool in the set that takes evidence in rather than\n * only handing it out: the declaration is something no report can contain,\n * because the report was written by a process that never knew what the edit was\n * for.\n *\n * What it buys is the arm nothing else in this category ships. `variance_changes`\n * can tell an agent that `Button` moved in twelve subjects; it cannot tell it\n * that `Card` — which the agent believes it just edited — did not move at all.\n * That is not a diff finding, it is the *absence* of one, and an absence is only\n * a finding once something declared it should have been there.\n *\n * The tool deliberately refuses to help an agent declare after the fact. It takes\n * claims as an argument and derives none, so an agent that calls\n * `variance_changes` first and then submits the answer back as its intent is\n * scoring the run against itself — visibly, because the transcript shows the\n * order. Nothing here can prevent that; what it can do is never do it *for* the\n * agent.\n */\nexport const adjudicate: Tool = {\n name: 'variance_adjudicate',\n description:\n 'Read this run back against what you meant to change. Declare each intended change as a ' +\n 'claim — the component or shape, why, and at most how many subjects it should reach — and ' +\n 'get three answers: what you declared and delivered, what moved that you did not declare, ' +\n 'and what you declared that never happened. The last one is the point: it is how you find ' +\n 'out an edit did not take (wrong file, dead branch, an overridden rule, a stale build), ' +\n 'which no screenshot comparison can tell you. Declare before you read the diff; claims ' +\n 'copied out of `variance_changes` score the run against itself.',\n inputSchema: {\n type: 'object',\n properties: {\n claims: {\n type: 'array',\n minItems: 1,\n description: 'What you intended to change. One entry per intended change.',\n items: {\n type: 'object',\n properties: {\n root: {\n type: 'string',\n description:\n 'What the change is about: `component:Button`, `shape:<fingerprint>`, or a ' +\n 'bare name read as a component. Matched exactly, never by prefix.',\n },\n reason: {\n type: 'string',\n description: 'Why you changed it, in your own words. Carried into the answer.',\n },\n maxSubjects: {\n type: 'integer',\n minimum: 1,\n description:\n 'At most how many subjects this change should reach. Optional, and the most ' +\n 'useful field here: exceeding it is not a failure, it is the blast radius ' +\n 'you did not expect.',\n },\n },\n required: ['root', 'reason'],\n additionalProperties: true,\n },\n },\n },\n required: ['claims'],\n additionalProperties: false,\n },\n\n run(report, input) {\n const { claims, unchecked } = parseClaims(input['claims']);\n\n if (claims.length === 0) {\n // An empty declaration is not a clean run, and answering it as one would\n // report \"nothing you meant to do is missing\" to an agent that declared\n // nothing. Said plainly instead, because the fix is one field away.\n return (\n 'No claims were supplied, so there is nothing to adjudicate. This tool answers what ' +\n 'your run did against what you said you were doing; with no declaration it can only ' +\n 'repeat `variance_changes`. Declare the edits you made, then ask again.'\n );\n }\n\n return describeAdjudication(\n adjudicateRun(report, claims, unchecked.length > 0 ? { unchecked } : {}),\n );\n },\n};\n\n/**\n * Narrow whatever JSON a model produced, and keep what could not be checked.\n *\n * A declared field this resolution cannot verify is carried out rather than\n * dropped. An agent that declares `bands: ['paint']` and is answered `delivered`\n * has been told its band claim held; a run report keeps no band per change, so\n * nothing looked, and the answer has to say which of the two it is.\n */\nfunction parseClaims(value: unknown): { claims: Claim[]; unchecked: string[] } {\n if (!Array.isArray(value)) {\n throw new Error('`claims` is required and must be an array of {root, reason, maxSubjects?}');\n }\n\n const claims: Claim[] = [];\n const unchecked = new Set<string>();\n\n for (const [index, entry] of value.entries()) {\n if (typeof entry !== 'object' || entry === null) {\n throw new Error(`claims[${index}] must be an object with \\`root\\` and \\`reason\\``);\n }\n\n const record = entry as Record<string, unknown>;\n const root = record['root'];\n const reason = record['reason'];\n\n if (typeof root !== 'string' || root === '') {\n throw new Error(`claims[${index}].root is required and must be a non-empty string`);\n }\n if (typeof reason !== 'string' || reason === '') {\n throw new Error(`claims[${index}].reason is required and must be a non-empty string`);\n }\n\n const maxSubjects = record['maxSubjects'];\n if (maxSubjects !== undefined && (typeof maxSubjects !== 'number' || maxSubjects < 1)) {\n throw new Error(`claims[${index}].maxSubjects must be a positive integer when present`);\n }\n\n for (const key of Object.keys(record)) {\n if (key !== 'root' && key !== 'reason' && key !== 'maxSubjects') unchecked.add(key);\n }\n\n claims.push(\n maxSubjects === undefined\n ? { root, reason }\n : { root, reason, maxSubjects: Math.trunc(maxSubjects) },\n );\n }\n\n return { claims, unchecked: [...unchecked] };\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Attention, EyesArchive, EyesPhase, EyesTestAttention, TargetSnapshot } from '@variance-authority/eyes';
|
|
2
|
+
import { type Tool } from './tool.js';
|
|
3
|
+
type Phase = EyesPhase | 'unphased';
|
|
4
|
+
/** Test attention in authored AAA order, with selectors and attributed targets. */
|
|
5
|
+
export declare const attention: Tool<EyesArchive>;
|
|
6
|
+
export declare function locateEyesTest(archive: EyesArchive, asked: string): EyesTestAttention | string;
|
|
7
|
+
export declare function phasedAttention(entries: readonly Attention[]): readonly {
|
|
8
|
+
phase: Phase;
|
|
9
|
+
entry: Attention;
|
|
10
|
+
}[];
|
|
11
|
+
export declare function targetsOf(entry: Attention): readonly TargetSnapshot[];
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=attention.d.ts.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { stringArg } from './tool.js';
|
|
2
|
+
/** Test attention in authored AAA order, with selectors and attributed targets. */
|
|
3
|
+
export const attention = {
|
|
4
|
+
name: 'variance_test_attention',
|
|
5
|
+
description: 'List Eyes-instrumented tests, or explain one test’s selectors, Locator consumption, DOM events, and authored Arrange/Act/Assert boundaries in order.',
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
test: { type: 'string', description: 'A test id, exact title, or unambiguous title part.' },
|
|
10
|
+
phase: { enum: ['arrange', 'act', 'assert'], description: 'Optional authored AAA phase.' },
|
|
11
|
+
},
|
|
12
|
+
additionalProperties: false,
|
|
13
|
+
},
|
|
14
|
+
run(archive, input) {
|
|
15
|
+
if (input['test'] === undefined)
|
|
16
|
+
return listing(archive);
|
|
17
|
+
const found = locate(archive, stringArg(input, 'test'));
|
|
18
|
+
if (typeof found === 'string')
|
|
19
|
+
return found;
|
|
20
|
+
const phase = optionalPhase(input);
|
|
21
|
+
const lines = attentionLines(found.attention, phase);
|
|
22
|
+
return [
|
|
23
|
+
`${found.title} — ${found.file ?? 'file not supplied'} [${found.id}]`,
|
|
24
|
+
found.complete ? 'Attention journal: complete.' : `Attention journal: partial — ${found.because}`,
|
|
25
|
+
...(phase === undefined ? [] : [`Authored phase: ${phase}.`]),
|
|
26
|
+
'',
|
|
27
|
+
...(lines.length === 0
|
|
28
|
+
? [`No attention was recorded${phase === undefined ? '' : ` in ${phase}`}.`]
|
|
29
|
+
: lines),
|
|
30
|
+
].join('\n');
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
export function locateEyesTest(archive, asked) {
|
|
34
|
+
const byId = archive.tests.find((test) => test.id === asked);
|
|
35
|
+
if (byId !== undefined)
|
|
36
|
+
return byId;
|
|
37
|
+
const exact = archive.tests.filter((test) => test.title === asked);
|
|
38
|
+
if (exact.length === 1)
|
|
39
|
+
return exact[0];
|
|
40
|
+
const lowered = asked.toLowerCase();
|
|
41
|
+
const partial = archive.tests.filter((test) => test.title.toLowerCase().includes(lowered));
|
|
42
|
+
if (partial.length === 1)
|
|
43
|
+
return partial[0];
|
|
44
|
+
if (partial.length === 0)
|
|
45
|
+
return `No Eyes test matches ${asked}. ${archive.tests.length} test(s) are recorded.`;
|
|
46
|
+
return `${partial.length} Eyes tests match ${asked}; ask by id:\n${partial.map((test) => ` ${test.title} [${test.id}]`).join('\n')}`;
|
|
47
|
+
}
|
|
48
|
+
export function phasedAttention(entries) {
|
|
49
|
+
let phase = 'unphased';
|
|
50
|
+
const phased = [];
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry.kind === 'eyes-phase')
|
|
53
|
+
phase = entry.phase;
|
|
54
|
+
phased.push({ phase, entry });
|
|
55
|
+
}
|
|
56
|
+
return phased;
|
|
57
|
+
}
|
|
58
|
+
export function targetsOf(entry) {
|
|
59
|
+
if (entry.kind === 'rtl-query' && entry.outcome === 'resolved')
|
|
60
|
+
return entry.targets;
|
|
61
|
+
if (entry.kind === 'document-event')
|
|
62
|
+
return [entry.target];
|
|
63
|
+
if (entry.kind === 'playwright-locator' && entry.operation !== 'planned') {
|
|
64
|
+
return [...(entry.before ?? []), ...(entry.outcome === 'resolved' ? entry.after ?? [] : [])];
|
|
65
|
+
}
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
function listing(archive) {
|
|
69
|
+
if (archive.tests.length === 0)
|
|
70
|
+
return 'Eyes measured an archive containing no tests.';
|
|
71
|
+
return [
|
|
72
|
+
`${archive.tests.length} Eyes test(s):`,
|
|
73
|
+
...archive.tests.map((test) => ` ${test.complete ? 'complete' : 'partial'} — ${test.title} — ${test.file ?? 'file not supplied'} [${test.id}]`),
|
|
74
|
+
].join('\n');
|
|
75
|
+
}
|
|
76
|
+
function locate(archive, asked) {
|
|
77
|
+
return locateEyesTest(archive, asked);
|
|
78
|
+
}
|
|
79
|
+
function optionalPhase(input) {
|
|
80
|
+
const value = input['phase'];
|
|
81
|
+
if (value === undefined)
|
|
82
|
+
return undefined;
|
|
83
|
+
if (value !== 'arrange' && value !== 'act' && value !== 'assert') {
|
|
84
|
+
throw new Error('`phase` must be arrange, act, or assert');
|
|
85
|
+
}
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
function attentionLines(entries, only) {
|
|
89
|
+
return phasedAttention(entries)
|
|
90
|
+
.filter(({ phase }) => only === undefined || phase === only)
|
|
91
|
+
.map(({ phase, entry }) => `#${entry.sequence} [${phase}] ${describe(entry)}`);
|
|
92
|
+
}
|
|
93
|
+
function describe(entry) {
|
|
94
|
+
if (entry.kind === 'eyes-phase')
|
|
95
|
+
return `phase → ${entry.phase}`;
|
|
96
|
+
if (entry.kind === 'react-commit') {
|
|
97
|
+
const rendered = entry.commit.components.length === 0
|
|
98
|
+
? 'no composite renderers'
|
|
99
|
+
: entry.commit.components.join(', ');
|
|
100
|
+
if (entry.commit.updaters === undefined) {
|
|
101
|
+
return `React commit → rendered ${rendered}; update initiators unavailable`;
|
|
102
|
+
}
|
|
103
|
+
const updaters = entry.commit.updaters.length === 0
|
|
104
|
+
? 'measured empty'
|
|
105
|
+
: entry.commit.updaters.map((updater) => updater.path.map((frame) => frame.name).join(' ← ')).join('; ');
|
|
106
|
+
return `React commit → rendered ${rendered}; update initiators: ${updaters}`;
|
|
107
|
+
}
|
|
108
|
+
if (entry.kind === 'document-event') {
|
|
109
|
+
return `document ${entry.event}${entry.trusted ? '' : ' (synthetic)'} → ${targetLine(entry.target)}`;
|
|
110
|
+
}
|
|
111
|
+
if (entry.kind === 'rtl-query') {
|
|
112
|
+
const call = `${entry.query}(${argumentsLine(entry.arguments)})`;
|
|
113
|
+
if (entry.outcome === 'threw')
|
|
114
|
+
return `${call} threw ${entry.error}`;
|
|
115
|
+
if (entry.outcome === 'absent')
|
|
116
|
+
return `${call} → absent`;
|
|
117
|
+
return `${call} → ${targetList(entry.targets)}`;
|
|
118
|
+
}
|
|
119
|
+
const locator = locatorLine(entry.locator);
|
|
120
|
+
if (entry.operation === 'planned')
|
|
121
|
+
return `planned ${locator}`;
|
|
122
|
+
if (entry.outcome === 'threw')
|
|
123
|
+
return `${entry.operation} ${entry.member} on ${locator} threw ${entry.error}`;
|
|
124
|
+
return `${entry.operation} ${entry.member} on ${locator} — before ${targetList(entry.before ?? [])}; after ${targetList(entry.after ?? [])}`;
|
|
125
|
+
}
|
|
126
|
+
function locatorLine(steps) {
|
|
127
|
+
return steps.map((step) => `${step.member}(${argumentsLine(step.arguments)})`).join('.');
|
|
128
|
+
}
|
|
129
|
+
function argumentsLine(values) {
|
|
130
|
+
return values.map((value) => JSON.stringify(value)).join(', ');
|
|
131
|
+
}
|
|
132
|
+
function targetList(targets) {
|
|
133
|
+
return targets.length === 0 ? '0 targets' : targets.map(targetLine).join('; ');
|
|
134
|
+
}
|
|
135
|
+
function targetLine(target) {
|
|
136
|
+
const identity = [
|
|
137
|
+
target.nodeName,
|
|
138
|
+
target.role === undefined ? undefined : `role=${target.role}`,
|
|
139
|
+
target.ariaLabel === undefined ? undefined : `name=${JSON.stringify(target.ariaLabel)}`,
|
|
140
|
+
target.id === undefined ? undefined : `#${target.id}`,
|
|
141
|
+
].filter((part) => part !== undefined).join(' ');
|
|
142
|
+
if (target.provenance.status === 'no-fiber')
|
|
143
|
+
return `${identity} [no Fiber: ${target.provenance.reason}]`;
|
|
144
|
+
const provenance = target.provenance.provenance;
|
|
145
|
+
const owners = provenance.owners.map((owner) => owner.name).join(' ← ') || 'React root';
|
|
146
|
+
const source = provenance.source === undefined
|
|
147
|
+
? ''
|
|
148
|
+
: ` at ${provenance.source.file}:${provenance.source.line}`;
|
|
149
|
+
return `${identity} [${owners}${source}]`;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=attention.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attention.js","sourceRoot":"","sources":["../../src/tools/attention.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n Attention,\n EyesArchive,\n EyesPhase,\n EyesTestAttention,\n LocatorStep,\n TargetSnapshot,\n} from '@variance-authority/eyes';\nimport { stringArg, type Tool } from './tool.js';\n\ntype Phase = EyesPhase | 'unphased';\n\n/** Test attention in authored AAA order, with selectors and attributed targets. */\nexport const attention: Tool<EyesArchive> = {\n name: 'variance_test_attention',\n description:\n 'List Eyes-instrumented tests, or explain one test’s selectors, Locator consumption, DOM events, and authored Arrange/Act/Assert boundaries in order.',\n inputSchema: {\n type: 'object',\n properties: {\n test: { type: 'string', description: 'A test id, exact title, or unambiguous title part.' },\n phase: { enum: ['arrange', 'act', 'assert'], description: 'Optional authored AAA phase.' },\n },\n additionalProperties: false,\n },\n run(archive, input) {\n if (input['test'] === undefined) return listing(archive);\n const found = locate(archive, stringArg(input, 'test'));\n if (typeof found === 'string') return found;\n const phase = optionalPhase(input);\n const lines = attentionLines(found.attention, phase);\n return [\n `${found.title} — ${found.file ?? 'file not supplied'} [${found.id}]`,\n found.complete ? 'Attention journal: complete.' : `Attention journal: partial — ${found.because}`,\n ...(phase === undefined ? [] : [`Authored phase: ${phase}.`]),\n '',\n ...(lines.length === 0\n ? [`No attention was recorded${phase === undefined ? '' : ` in ${phase}`}.`]\n : lines),\n ].join('\\n');\n },\n};\n\nexport function locateEyesTest(archive: EyesArchive, asked: string): EyesTestAttention | string {\n const byId = archive.tests.find((test) => test.id === asked);\n if (byId !== undefined) return byId;\n const exact = archive.tests.filter((test) => test.title === asked);\n if (exact.length === 1) return exact[0]!;\n const lowered = asked.toLowerCase();\n const partial = archive.tests.filter((test) => test.title.toLowerCase().includes(lowered));\n if (partial.length === 1) return partial[0]!;\n if (partial.length === 0) return `No Eyes test matches ${asked}. ${archive.tests.length} test(s) are recorded.`;\n return `${partial.length} Eyes tests match ${asked}; ask by id:\\n${partial.map((test) => ` ${test.title} [${test.id}]`).join('\\n')}`;\n}\n\nexport function phasedAttention(entries: readonly Attention[]): readonly { phase: Phase; entry: Attention }[] {\n let phase: Phase = 'unphased';\n const phased: { phase: Phase; entry: Attention }[] = [];\n for (const entry of entries) {\n if (entry.kind === 'eyes-phase') phase = entry.phase;\n phased.push({ phase, entry });\n }\n return phased;\n}\n\nexport function targetsOf(entry: Attention): readonly TargetSnapshot[] {\n if (entry.kind === 'rtl-query' && entry.outcome === 'resolved') return entry.targets;\n if (entry.kind === 'document-event') return [entry.target];\n if (entry.kind === 'playwright-locator' && entry.operation !== 'planned') {\n return [...(entry.before ?? []), ...(entry.outcome === 'resolved' ? entry.after ?? [] : [])];\n }\n return [];\n}\n\nfunction listing(archive: EyesArchive): string {\n if (archive.tests.length === 0) return 'Eyes measured an archive containing no tests.';\n return [\n `${archive.tests.length} Eyes test(s):`,\n ...archive.tests.map((test) =>\n ` ${test.complete ? 'complete' : 'partial'} — ${test.title} — ${test.file ?? 'file not supplied'} [${test.id}]`),\n ].join('\\n');\n}\n\nfunction locate(archive: EyesArchive, asked: string): EyesTestAttention | string {\n return locateEyesTest(archive, asked);\n}\n\nfunction optionalPhase(input: Readonly<Record<string, unknown>>): EyesPhase | undefined {\n const value = input['phase'];\n if (value === undefined) return undefined;\n if (value !== 'arrange' && value !== 'act' && value !== 'assert') {\n throw new Error('`phase` must be arrange, act, or assert');\n }\n return value;\n}\n\nfunction attentionLines(entries: readonly Attention[], only: EyesPhase | undefined): string[] {\n return phasedAttention(entries)\n .filter(({ phase }) => only === undefined || phase === only)\n .map(({ phase, entry }) => `#${entry.sequence} [${phase}] ${describe(entry)}`);\n}\n\nfunction describe(entry: Attention): string {\n if (entry.kind === 'eyes-phase') return `phase → ${entry.phase}`;\n if (entry.kind === 'react-commit') {\n const rendered = entry.commit.components.length === 0\n ? 'no composite renderers'\n : entry.commit.components.join(', ');\n if (entry.commit.updaters === undefined) {\n return `React commit → rendered ${rendered}; update initiators unavailable`;\n }\n const updaters = entry.commit.updaters.length === 0\n ? 'measured empty'\n : entry.commit.updaters.map((updater) =>\n updater.path.map((frame) => frame.name).join(' ← ')).join('; ');\n return `React commit → rendered ${rendered}; update initiators: ${updaters}`;\n }\n if (entry.kind === 'document-event') {\n return `document ${entry.event}${entry.trusted ? '' : ' (synthetic)'} → ${targetLine(entry.target)}`;\n }\n if (entry.kind === 'rtl-query') {\n const call = `${entry.query}(${argumentsLine(entry.arguments)})`;\n if (entry.outcome === 'threw') return `${call} threw ${entry.error}`;\n if (entry.outcome === 'absent') return `${call} → absent`;\n return `${call} → ${targetList(entry.targets)}`;\n }\n const locator = locatorLine(entry.locator);\n if (entry.operation === 'planned') return `planned ${locator}`;\n if (entry.outcome === 'threw') return `${entry.operation} ${entry.member} on ${locator} threw ${entry.error}`;\n return `${entry.operation} ${entry.member} on ${locator} — before ${targetList(entry.before ?? [])}; after ${targetList(entry.after ?? [])}`;\n}\n\nfunction locatorLine(steps: readonly LocatorStep[]): string {\n return steps.map((step) => `${step.member}(${argumentsLine(step.arguments)})`).join('.');\n}\n\nfunction argumentsLine(values: readonly unknown[]): string {\n return values.map((value) => JSON.stringify(value)).join(', ');\n}\n\nfunction targetList(targets: readonly TargetSnapshot[]): string {\n return targets.length === 0 ? '0 targets' : targets.map(targetLine).join('; ');\n}\n\nfunction targetLine(target: TargetSnapshot): string {\n const identity = [\n target.nodeName,\n target.role === undefined ? undefined : `role=${target.role}`,\n target.ariaLabel === undefined ? undefined : `name=${JSON.stringify(target.ariaLabel)}`,\n target.id === undefined ? undefined : `#${target.id}`,\n ].filter((part): part is string => part !== undefined).join(' ');\n if (target.provenance.status === 'no-fiber') return `${identity} [no Fiber: ${target.provenance.reason}]`;\n const provenance = target.provenance.provenance;\n const owners = provenance.owners.map((owner) => owner.name).join(' ← ') || 'React root';\n const source = provenance.source === undefined\n ? ''\n : ` at ${provenance.source.file}:${provenance.source.line}`;\n return `${identity} [${owners}${source}]`;\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Tool } from './tool.js';
|
|
2
|
+
/**
|
|
3
|
+
* `variance_changelog` — what the baseline update would say, before it is made.
|
|
4
|
+
*
|
|
5
|
+
* Every other tool here answers *what changed*. This one answers *what would be
|
|
6
|
+
* written down about it*, and the difference matters because of where that
|
|
7
|
+
* writing goes: a baseline update is explained in the commit that carries it or
|
|
8
|
+
* in a review database, and both are written at the moment of acceptance and
|
|
9
|
+
* never again. An agent that runs `accept` without knowing what the record will
|
|
10
|
+
* say has already written it.
|
|
11
|
+
*
|
|
12
|
+
* ## Why it previews rather than proposes prose
|
|
13
|
+
*
|
|
14
|
+
* The answer is not this tool's phrasing of the change. It is the lines the
|
|
15
|
+
* commit will actually carry, rendered by the same function that renders them
|
|
16
|
+
* into the commit — `changelogBody` over a record from `changelogOf`. A tool
|
|
17
|
+
* that wrote its own summary would be a second account of a baseline update,
|
|
18
|
+
* edited separately from the first, and the two would disagree in the direction
|
|
19
|
+
* that matters: the one an agent read would not be the one that survived.
|
|
20
|
+
*
|
|
21
|
+
* The subject set is chosen by the same rules `accept` applies, for the same
|
|
22
|
+
* reason. `promotionOf` refuses an unstable subject and a subject the run left
|
|
23
|
+
* no image for; a preview that counted those would promise entries the command
|
|
24
|
+
* then refuses by name, which is worse than not previewing at all.
|
|
25
|
+
*
|
|
26
|
+
* ## What it deliberately does not do
|
|
27
|
+
*
|
|
28
|
+
* It does not render the trailers. Those are the record — versioned, encoded,
|
|
29
|
+
* append-only once committed — and a record exists when somebody accepted
|
|
30
|
+
* something. A trailer an agent could copy out of a preview is a record of a
|
|
31
|
+
* promotion that never happened, which is the one artifact this whole mechanism
|
|
32
|
+
* is arranged to prevent.
|
|
33
|
+
*
|
|
34
|
+
* It reads no clock. The instant a record carries is the instant of acceptance,
|
|
35
|
+
* and acceptance happens in another process, later, possibly not at all.
|
|
36
|
+
*/
|
|
37
|
+
export declare const changelog: Tool;
|
|
38
|
+
//# sourceMappingURL=changelog.d.ts.map
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { changelogBody, changelogOf, isRecorded, promotionOf, selectByShape, whyNotWhole, } from '@variance-authority/report';
|
|
2
|
+
/**
|
|
3
|
+
* `variance_changelog` — what the baseline update would say, before it is made.
|
|
4
|
+
*
|
|
5
|
+
* Every other tool here answers *what changed*. This one answers *what would be
|
|
6
|
+
* written down about it*, and the difference matters because of where that
|
|
7
|
+
* writing goes: a baseline update is explained in the commit that carries it or
|
|
8
|
+
* in a review database, and both are written at the moment of acceptance and
|
|
9
|
+
* never again. An agent that runs `accept` without knowing what the record will
|
|
10
|
+
* say has already written it.
|
|
11
|
+
*
|
|
12
|
+
* ## Why it previews rather than proposes prose
|
|
13
|
+
*
|
|
14
|
+
* The answer is not this tool's phrasing of the change. It is the lines the
|
|
15
|
+
* commit will actually carry, rendered by the same function that renders them
|
|
16
|
+
* into the commit — `changelogBody` over a record from `changelogOf`. A tool
|
|
17
|
+
* that wrote its own summary would be a second account of a baseline update,
|
|
18
|
+
* edited separately from the first, and the two would disagree in the direction
|
|
19
|
+
* that matters: the one an agent read would not be the one that survived.
|
|
20
|
+
*
|
|
21
|
+
* The subject set is chosen by the same rules `accept` applies, for the same
|
|
22
|
+
* reason. `promotionOf` refuses an unstable subject and a subject the run left
|
|
23
|
+
* no image for; a preview that counted those would promise entries the command
|
|
24
|
+
* then refuses by name, which is worse than not previewing at all.
|
|
25
|
+
*
|
|
26
|
+
* ## What it deliberately does not do
|
|
27
|
+
*
|
|
28
|
+
* It does not render the trailers. Those are the record — versioned, encoded,
|
|
29
|
+
* append-only once committed — and a record exists when somebody accepted
|
|
30
|
+
* something. A trailer an agent could copy out of a preview is a record of a
|
|
31
|
+
* promotion that never happened, which is the one artifact this whole mechanism
|
|
32
|
+
* is arranged to prevent.
|
|
33
|
+
*
|
|
34
|
+
* It reads no clock. The instant a record carries is the instant of acceptance,
|
|
35
|
+
* and acceptance happens in another process, later, possibly not at all.
|
|
36
|
+
*/
|
|
37
|
+
export const changelog = {
|
|
38
|
+
name: 'variance_changelog',
|
|
39
|
+
description: 'Preview what accepting this run would write into the baseline changelog: the same lines ' +
|
|
40
|
+
'the commit message will carry, the subjects that would land in it, and the subjects that ' +
|
|
41
|
+
'would be refused and why. Takes the same selection as `variance accept` — a shape, named ' +
|
|
42
|
+
'subjects, or everything. Ask this before proposing an accept command; the record is ' +
|
|
43
|
+
'written once, at acceptance, and is the only explanation of the baseline that outlives ' +
|
|
44
|
+
'the run.',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: 'object',
|
|
47
|
+
properties: {
|
|
48
|
+
shape: {
|
|
49
|
+
type: 'string',
|
|
50
|
+
description: 'Optional. A shape digest from `variance_changes`. Previews `accept --shape`, which ' +
|
|
51
|
+
'takes the subjects this shape wholly explains and refuses the rest by name.',
|
|
52
|
+
},
|
|
53
|
+
subjects: {
|
|
54
|
+
type: 'array',
|
|
55
|
+
items: { type: 'string' },
|
|
56
|
+
description: 'Optional. Subject ids, as `accept` would take them by name.',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
},
|
|
61
|
+
run(report, input) {
|
|
62
|
+
const shape = typeof input['shape'] === 'string' ? input['shape'] : undefined;
|
|
63
|
+
const named = Array.isArray(input['subjects'])
|
|
64
|
+
? input['subjects'].filter((value) => typeof value === 'string')
|
|
65
|
+
: undefined;
|
|
66
|
+
if (shape !== undefined && named !== undefined && named.length > 0) {
|
|
67
|
+
// Refused rather than resolved by precedence. These are two different
|
|
68
|
+
// commands, and picking one silently would preview an update the agent did
|
|
69
|
+
// not ask about and then hand it a command that performs the other.
|
|
70
|
+
return ('Ask for one selection at a time: a shape, or subjects by name. They are two ' +
|
|
71
|
+
'different accept commands and this would preview one while naming the other.');
|
|
72
|
+
}
|
|
73
|
+
const selection = shape !== undefined ? 'shape' : named !== undefined && named.length > 0 ? 'named' : 'all';
|
|
74
|
+
const chosen = select(report, shape, named);
|
|
75
|
+
if (typeof chosen === 'string')
|
|
76
|
+
return chosen;
|
|
77
|
+
const promotable = [];
|
|
78
|
+
const refused = [...chosen.refused];
|
|
79
|
+
let alreadyBaseline = 0;
|
|
80
|
+
for (const observation of chosen.targets) {
|
|
81
|
+
const promotion = promotionOf(observation);
|
|
82
|
+
if (promotion.kind === 'promotable')
|
|
83
|
+
promotable.push(observation.subject);
|
|
84
|
+
else if (promotion.kind === 'already-baseline')
|
|
85
|
+
alreadyBaseline += 1;
|
|
86
|
+
else
|
|
87
|
+
refused.push({ subject: observation.subject, because: promotion.because });
|
|
88
|
+
}
|
|
89
|
+
const record = changelogOf({
|
|
90
|
+
report,
|
|
91
|
+
accepted: promotable,
|
|
92
|
+
selection,
|
|
93
|
+
// The run's own instant, never this process's. A record is stamped when
|
|
94
|
+
// somebody accepts, in another process; a clock read here would put a time
|
|
95
|
+
// in a preview that the record will not carry.
|
|
96
|
+
at: report.at,
|
|
97
|
+
});
|
|
98
|
+
if (!isRecorded(record)) {
|
|
99
|
+
return [
|
|
100
|
+
`Nothing would be recorded: ${record.because}.`,
|
|
101
|
+
...(refused.length > 0
|
|
102
|
+
? ['', `Refused (${String(refused.length)}):`, ...refused.map(line)]
|
|
103
|
+
: []),
|
|
104
|
+
...(alreadyBaseline > 0
|
|
105
|
+
? ['', `${String(alreadyBaseline)} subject(s) already are the baseline.`]
|
|
106
|
+
: []),
|
|
107
|
+
].join('\n');
|
|
108
|
+
}
|
|
109
|
+
const unshaped = promotable.filter((subject) => !inRecord(record.entries, subject));
|
|
110
|
+
return [
|
|
111
|
+
`Accepting this would record ${String(record.entries.length)} change(s) across ` +
|
|
112
|
+
`${String(promotable.length)} subject(s). These are the lines the commit will carry:`,
|
|
113
|
+
'',
|
|
114
|
+
...changelogBody(record),
|
|
115
|
+
'',
|
|
116
|
+
` ${command(selection, shape, promotable)}`,
|
|
117
|
+
...(unshaped.length > 0
|
|
118
|
+
? [
|
|
119
|
+
'',
|
|
120
|
+
`Promoted but not described (${String(unshaped.length)}): ${unshaped.join(', ')}`,
|
|
121
|
+
' The run named no difference shape in these, so the record counts them and ' +
|
|
122
|
+
'cannot say what changed in them. Nothing later can recover it.',
|
|
123
|
+
]
|
|
124
|
+
: []),
|
|
125
|
+
...(refused.length > 0
|
|
126
|
+
? ['', `Refused, and absent from the record (${String(refused.length)}):`, ...refused.map(line)]
|
|
127
|
+
: []),
|
|
128
|
+
...(alreadyBaseline > 0
|
|
129
|
+
? ['', `${String(alreadyBaseline)} subject(s) already are the baseline and record nothing.`]
|
|
130
|
+
: []),
|
|
131
|
+
].join('\n');
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* The subjects the named selection reaches, or the sentence saying it reaches none.
|
|
136
|
+
*
|
|
137
|
+
* `--shape` carries its own refusals — the subjects where the shape is present
|
|
138
|
+
* beside something else — and they are part of the preview rather than a detail
|
|
139
|
+
* of the command, because they are the part of the change this update would
|
|
140
|
+
* leave in the suite.
|
|
141
|
+
*/
|
|
142
|
+
function select(report, shape, named) {
|
|
143
|
+
if (shape !== undefined) {
|
|
144
|
+
const selected = selectByShape(report.observations, new Set([shape]));
|
|
145
|
+
if (selected.whole.length === 0 && selected.partial.length === 0) {
|
|
146
|
+
return (`The shape ${shape} appears in no region of this run. A fingerprint is copied from a ` +
|
|
147
|
+
'region in a report; check it came from this one.');
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
targets: selected.whole,
|
|
151
|
+
refused: selected.partial.map((observation) => ({
|
|
152
|
+
subject: observation.subject,
|
|
153
|
+
because: whyNotWhole(observation),
|
|
154
|
+
})),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (named !== undefined && named.length > 0) {
|
|
158
|
+
const targets = [];
|
|
159
|
+
const missing = [];
|
|
160
|
+
for (const subject of named) {
|
|
161
|
+
const found = report.observations.find((entry) => entry.subject === subject);
|
|
162
|
+
if (found === undefined)
|
|
163
|
+
missing.push(subject);
|
|
164
|
+
else
|
|
165
|
+
targets.push(found);
|
|
166
|
+
}
|
|
167
|
+
if (targets.length === 0) {
|
|
168
|
+
return `None of those subjects are in this run: ${missing.join(', ')}.`;
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
targets,
|
|
172
|
+
refused: missing.map((subject) => ({
|
|
173
|
+
subject,
|
|
174
|
+
because: 'this run has no observation for it, so `accept` would refuse the command',
|
|
175
|
+
})),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return { targets: report.observations, refused: [] };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* The command that writes it, spelled out.
|
|
182
|
+
*
|
|
183
|
+
* `--message-file` is on every one of them. The message is where the record
|
|
184
|
+
* goes, and an accept run without it promotes the images and explains nothing —
|
|
185
|
+
* which is the state this tool exists to make visible before it happens.
|
|
186
|
+
*/
|
|
187
|
+
function command(selection, shape, promotable) {
|
|
188
|
+
const how = selection === 'shape'
|
|
189
|
+
? `--shape ${shape ?? ''}`
|
|
190
|
+
: selection === 'named'
|
|
191
|
+
? promotable.map((subject) => `"${subject}"`).join(' ')
|
|
192
|
+
: '--all';
|
|
193
|
+
return `variance accept ${how} --message-file .variance/commit-message.txt`;
|
|
194
|
+
}
|
|
195
|
+
function inRecord(entries, subject) {
|
|
196
|
+
return entries.some((entry) => entry.subjects.includes(subject));
|
|
197
|
+
}
|
|
198
|
+
function line(refusal) {
|
|
199
|
+
return ` ${refusal.subject} — ${refusal.because}`;
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=changelog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"changelog.js","sourceRoot":"","sources":["../../src/tools/changelog.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n changelogBody,\n changelogOf,\n isRecorded,\n promotionOf,\n selectByShape,\n whyNotWhole,\n type ChangelogSelection,\n type ObservationRecord,\n type RunReport,\n} from '@variance-authority/report';\nimport type { Tool } from './tool.js';\n\n/**\n * `variance_changelog` — what the baseline update would say, before it is made.\n *\n * Every other tool here answers *what changed*. This one answers *what would be\n * written down about it*, and the difference matters because of where that\n * writing goes: a baseline update is explained in the commit that carries it or\n * in a review database, and both are written at the moment of acceptance and\n * never again. An agent that runs `accept` without knowing what the record will\n * say has already written it.\n *\n * ## Why it previews rather than proposes prose\n *\n * The answer is not this tool's phrasing of the change. It is the lines the\n * commit will actually carry, rendered by the same function that renders them\n * into the commit — `changelogBody` over a record from `changelogOf`. A tool\n * that wrote its own summary would be a second account of a baseline update,\n * edited separately from the first, and the two would disagree in the direction\n * that matters: the one an agent read would not be the one that survived.\n *\n * The subject set is chosen by the same rules `accept` applies, for the same\n * reason. `promotionOf` refuses an unstable subject and a subject the run left\n * no image for; a preview that counted those would promise entries the command\n * then refuses by name, which is worse than not previewing at all.\n *\n * ## What it deliberately does not do\n *\n * It does not render the trailers. Those are the record — versioned, encoded,\n * append-only once committed — and a record exists when somebody accepted\n * something. A trailer an agent could copy out of a preview is a record of a\n * promotion that never happened, which is the one artifact this whole mechanism\n * is arranged to prevent.\n *\n * It reads no clock. The instant a record carries is the instant of acceptance,\n * and acceptance happens in another process, later, possibly not at all.\n */\n\nexport const changelog: Tool = {\n name: 'variance_changelog',\n description:\n 'Preview what accepting this run would write into the baseline changelog: the same lines ' +\n 'the commit message will carry, the subjects that would land in it, and the subjects that ' +\n 'would be refused and why. Takes the same selection as `variance accept` — a shape, named ' +\n 'subjects, or everything. Ask this before proposing an accept command; the record is ' +\n 'written once, at acceptance, and is the only explanation of the baseline that outlives ' +\n 'the run.',\n inputSchema: {\n type: 'object',\n properties: {\n shape: {\n type: 'string',\n description:\n 'Optional. A shape digest from `variance_changes`. Previews `accept --shape`, which ' +\n 'takes the subjects this shape wholly explains and refuses the rest by name.',\n },\n subjects: {\n type: 'array',\n items: { type: 'string' },\n description: 'Optional. Subject ids, as `accept` would take them by name.',\n },\n },\n additionalProperties: false,\n },\n\n run(report, input) {\n const shape = typeof input['shape'] === 'string' ? input['shape'] : undefined;\n const named = Array.isArray(input['subjects'])\n ? input['subjects'].filter((value): value is string => typeof value === 'string')\n : undefined;\n\n if (shape !== undefined && named !== undefined && named.length > 0) {\n // Refused rather than resolved by precedence. These are two different\n // commands, and picking one silently would preview an update the agent did\n // not ask about and then hand it a command that performs the other.\n return (\n 'Ask for one selection at a time: a shape, or subjects by name. They are two ' +\n 'different accept commands and this would preview one while naming the other.'\n );\n }\n\n const selection: ChangelogSelection =\n shape !== undefined ? 'shape' : named !== undefined && named.length > 0 ? 'named' : 'all';\n\n const chosen = select(report, shape, named);\n if (typeof chosen === 'string') return chosen;\n\n const promotable: string[] = [];\n const refused: { readonly subject: string; readonly because: string }[] = [...chosen.refused];\n let alreadyBaseline = 0;\n\n for (const observation of chosen.targets) {\n const promotion = promotionOf(observation);\n if (promotion.kind === 'promotable') promotable.push(observation.subject);\n else if (promotion.kind === 'already-baseline') alreadyBaseline += 1;\n else refused.push({ subject: observation.subject, because: promotion.because });\n }\n\n const record = changelogOf({\n report,\n accepted: promotable,\n selection,\n // The run's own instant, never this process's. A record is stamped when\n // somebody accepts, in another process; a clock read here would put a time\n // in a preview that the record will not carry.\n at: report.at,\n });\n\n if (!isRecorded(record)) {\n return [\n `Nothing would be recorded: ${record.because}.`,\n ...(refused.length > 0\n ? ['', `Refused (${String(refused.length)}):`, ...refused.map(line)]\n : []),\n ...(alreadyBaseline > 0\n ? ['', `${String(alreadyBaseline)} subject(s) already are the baseline.`]\n : []),\n ].join('\\n');\n }\n\n const unshaped = promotable.filter((subject) => !inRecord(record.entries, subject));\n\n return [\n `Accepting this would record ${String(record.entries.length)} change(s) across ` +\n `${String(promotable.length)} subject(s). These are the lines the commit will carry:`,\n '',\n ...changelogBody(record),\n '',\n ` ${command(selection, shape, promotable)}`,\n ...(unshaped.length > 0\n ? [\n '',\n `Promoted but not described (${String(unshaped.length)}): ${unshaped.join(', ')}`,\n ' The run named no difference shape in these, so the record counts them and ' +\n 'cannot say what changed in them. Nothing later can recover it.',\n ]\n : []),\n ...(refused.length > 0\n ? ['', `Refused, and absent from the record (${String(refused.length)}):`, ...refused.map(line)]\n : []),\n ...(alreadyBaseline > 0\n ? ['', `${String(alreadyBaseline)} subject(s) already are the baseline and record nothing.`]\n : []),\n ].join('\\n');\n },\n};\n\n/**\n * The subjects the named selection reaches, or the sentence saying it reaches none.\n *\n * `--shape` carries its own refusals — the subjects where the shape is present\n * beside something else — and they are part of the preview rather than a detail\n * of the command, because they are the part of the change this update would\n * leave in the suite.\n */\nfunction select(\n report: RunReport,\n shape: string | undefined,\n named: readonly string[] | undefined,\n):\n | string\n | {\n readonly targets: readonly ObservationRecord[];\n readonly refused: readonly { readonly subject: string; readonly because: string }[];\n } {\n if (shape !== undefined) {\n const selected = selectByShape(report.observations, new Set([shape]));\n if (selected.whole.length === 0 && selected.partial.length === 0) {\n return (\n `The shape ${shape} appears in no region of this run. A fingerprint is copied from a ` +\n 'region in a report; check it came from this one.'\n );\n }\n return {\n targets: selected.whole,\n refused: selected.partial.map((observation) => ({\n subject: observation.subject,\n because: whyNotWhole(observation),\n })),\n };\n }\n\n if (named !== undefined && named.length > 0) {\n const targets: ObservationRecord[] = [];\n const missing: string[] = [];\n\n for (const subject of named) {\n const found = report.observations.find((entry) => entry.subject === subject);\n if (found === undefined) missing.push(subject);\n else targets.push(found);\n }\n\n if (targets.length === 0) {\n return `None of those subjects are in this run: ${missing.join(', ')}.`;\n }\n\n return {\n targets,\n refused: missing.map((subject) => ({\n subject,\n because: 'this run has no observation for it, so `accept` would refuse the command',\n })),\n };\n }\n\n return { targets: report.observations, refused: [] };\n}\n\n/**\n * The command that writes it, spelled out.\n *\n * `--message-file` is on every one of them. The message is where the record\n * goes, and an accept run without it promotes the images and explains nothing —\n * which is the state this tool exists to make visible before it happens.\n */\nfunction command(\n selection: ChangelogSelection,\n shape: string | undefined,\n promotable: readonly string[],\n): string {\n const how =\n selection === 'shape'\n ? `--shape ${shape ?? ''}`\n : selection === 'named'\n ? promotable.map((subject) => `\"${subject}\"`).join(' ')\n : '--all';\n\n return `variance accept ${how} --message-file .variance/commit-message.txt`;\n}\n\nfunction inRecord(\n entries: readonly { readonly subjects: readonly string[] }[],\n subject: string,\n): boolean {\n return entries.some((entry) => entry.subjects.includes(subject));\n}\n\nfunction line(refusal: { readonly subject: string; readonly because: string }): string {\n return ` ${refusal.subject} — ${refusal.because}`;\n}\n"]}
|