@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,115 @@
1
+ import { heading, tally, unattached } from './vantage-lines.js';
2
+ /**
3
+ * What the suite is doing, right now.
4
+ *
5
+ * The listing an agent reads before it asks about one test. Ordered as the run
6
+ * opened them, because the useful reading of a suite in flight is chronological
7
+ * — the test that is hanging is the last one with an arrow next to it — and
8
+ * newest-first would put the answer at the top and the story in reverse.
9
+ */
10
+ export const runSignals = {
11
+ name: 'variance_run_signals',
12
+ description: 'What the running test suite is doing now: every test that has reported, its state, and how much each has announced. Not limited to visual tests.',
13
+ inputSchema: {
14
+ type: 'object',
15
+ properties: {
16
+ state: {
17
+ type: 'string',
18
+ enum: ['running', 'passed', 'failed', 'timedOut', 'skipped', 'interrupted'],
19
+ description: 'Show only tests in this state.',
20
+ },
21
+ file: { type: 'string', description: 'Show only tests whose spec file contains this.' },
22
+ limit: {
23
+ type: 'integer',
24
+ minimum: 1,
25
+ description: 'How many of the most recent to list. Defaults to 50.',
26
+ },
27
+ },
28
+ additionalProperties: false,
29
+ },
30
+ run(state, input) {
31
+ if (state.tests.length === 0)
32
+ return unattached(state);
33
+ const wanted = stateArg(input);
34
+ const file = fileArg(input);
35
+ const matching = state.tests.filter((test) => (wanted === undefined || test.state === wanted) &&
36
+ (file === undefined || test.file.includes(file)));
37
+ if (matching.length === 0)
38
+ return nothingMatched(state, wanted, file);
39
+ const limit = limitArg(input);
40
+ const shown = matching.slice(Math.max(0, matching.length - limit));
41
+ const elided = matching.length - shown.length;
42
+ return [
43
+ summary(state, matching),
44
+ '',
45
+ ...shown.map(row),
46
+ ...(elided === 0 ? [] : ['', `… ${elided} earlier match(es) not listed; raise \`limit\`.`]),
47
+ ...(state.forgotten === 0
48
+ ? []
49
+ : ['', `${state.forgotten} earlier test(s) were dropped to keep this watcher bounded.`]),
50
+ ].join('\n');
51
+ },
52
+ };
53
+ function summary(state, matching) {
54
+ const counted = new Map();
55
+ for (const test of matching)
56
+ counted.set(test.state, (counted.get(test.state) ?? 0) + 1);
57
+ const breakdown = [...counted]
58
+ .sort((left, right) => right[1] - left[1])
59
+ .map(([one, count]) => `${count} ${one}`)
60
+ .join(', ');
61
+ const where = state.address === undefined ? '' : ` at ${state.address}`;
62
+ return `${matching.length} test(s)${where} — ${breakdown}.`;
63
+ }
64
+ function row(test) {
65
+ const mark = test.state === 'running' ? '▸' : ' ';
66
+ const lines = [`${mark} ${test.state.padEnd(11)} ${heading(test)} — ${tally(test)}`];
67
+ if (test.error !== undefined) {
68
+ lines.push(...test.error.split('\n').map((line) => ` ${line}`));
69
+ }
70
+ return lines.join('\n');
71
+ }
72
+ function nothingMatched(state, wanted, file) {
73
+ const asked = [
74
+ ...(wanted === undefined ? [] : [`state ${wanted}`]),
75
+ ...(file === undefined ? [] : [`file containing ${file}`]),
76
+ ].join(' and ');
77
+ const states = [...new Set(state.tests.map((test) => test.state))].sort().join(', ');
78
+ return `No test here matches ${asked}. ${state.tests.length} test(s) have reported, in: ${states}.`;
79
+ }
80
+ const STATES = new Set([
81
+ 'running',
82
+ 'passed',
83
+ 'failed',
84
+ 'timedOut',
85
+ 'skipped',
86
+ 'interrupted',
87
+ ]);
88
+ function stateArg(input) {
89
+ const value = input['state'];
90
+ if (value === undefined)
91
+ return undefined;
92
+ if (typeof value !== 'string' || !STATES.has(value)) {
93
+ throw new Error(`\`state\` must be one of: ${[...STATES].join(', ')}`);
94
+ }
95
+ return value;
96
+ }
97
+ function fileArg(input) {
98
+ const value = input['file'];
99
+ if (value === undefined)
100
+ return undefined;
101
+ if (typeof value !== 'string' || value === '') {
102
+ throw new Error('`file` must be a non-empty string');
103
+ }
104
+ return value;
105
+ }
106
+ function limitArg(input) {
107
+ const value = input['limit'];
108
+ if (value === undefined)
109
+ return 50;
110
+ if (!Number.isInteger(value) || value < 1) {
111
+ throw new Error('`limit` must be a positive integer');
112
+ }
113
+ return value;
114
+ }
115
+ //# sourceMappingURL=run-signals.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-signals.js","sourceRoot":"","sources":["../../src/tools/run-signals.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGhE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAuB;IAC5C,IAAI,EAAE,sBAAsB;IAC5B,WAAW,EACT,kJAAkJ;IACpJ,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,KAAK,EAAE;gBACL,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,CAAC;gBAC3E,WAAW,EAAE,gCAAgC;aAC9C;YACD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gDAAgD,EAAE;YACvF,KAAK,EAAE;gBACL,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,CAAC;gBACV,WAAW,EAAE,sDAAsD;aACpE;SACF;QACD,oBAAoB,EAAE,KAAK;KAC5B;IACD,GAAG,CAAC,KAAK,EAAE,KAAK;QACd,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC,KAAK,CAAC,CAAC;QAEvD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CACjC,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,KAAK,MAAM,CAAC;YAC/C,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CACnD,CAAC;QACF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QAEtE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC9B,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QAE9C,OAAO;YACL,OAAO,CAAC,KAAK,EAAE,QAAQ,CAAC;YACxB,EAAE;YACF,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YACjB,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,MAAM,iDAAiD,CAAC,CAAC;YAC3F,GAAG,CAAC,KAAK,CAAC,SAAS,KAAK,CAAC;gBACvB,CAAC,CAAC,EAAE;gBACJ,CAAC,CAAC,CAAC,EAAE,EAAE,GAAG,KAAK,CAAC,SAAS,6DAA6D,CAAC,CAAC;SAC3F,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACf,CAAC;CACF,CAAC;AAEF,SAAS,OAAO,CAAC,KAAmB,EAAE,QAAgC;IACpE,MAAM,OAAO,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC7C,KAAK,MAAM,IAAI,IAAI,QAAQ;QAAE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACzF,MAAM,SAAS,GAAG,CAAC,GAAG,OAAO,CAAC;SAC3B,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;SACzC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,IAAI,GAAG,EAAE,CAAC;SACxC,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,OAAO,EAAE,CAAC;IACxE,OAAO,GAAG,QAAQ,CAAC,MAAM,WAAW,KAAK,MAAM,SAAS,GAAG,CAAC;AAC9D,CAAC;AAED,SAAS,GAAG,CAAC,IAAiB;IAC5B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IAClD,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrF,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,CAAC;IACvE,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,cAAc,CACrB,KAAmB,EACnB,MAA6B,EAC7B,IAAwB;IAExB,MAAM,KAAK,GAAG;QACZ,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;QACpD,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;KAC3D,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAChB,MAAM,MAAM,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,OAAO,wBAAwB,KAAK,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,+BAA+B,MAAM,GAAG,CAAC;AACtG,CAAC;AAED,MAAM,MAAM,GAAG,IAAI,GAAG,CAAS;IAC7B,SAAS;IACT,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,SAAS;IACT,aAAa;CACd,CAAC,CAAC;AAEH,SAAS,QAAQ,CAAC,KAAwC;IACxD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,KAAkB,CAAC;AAC5B,CAAC;AAED,SAAS,OAAO,CAAC,KAAwC;IACvD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,EAAE,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,KAAwC;IACxD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,CAAC;IACnC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAK,KAAgB,GAAG,CAAC,EAAE,CAAC;QACtD,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,KAAe,CAAC;AACzB,CAAC","sourcesContent":["import type { TestState, VantageState, WatchedTest } from '@variance-authority/vantage';\nimport { heading, tally, unattached } from './vantage-lines.js';\nimport type { Tool } from './tool.js';\n\n/**\n * What the suite is doing, right now.\n *\n * The listing an agent reads before it asks about one test. Ordered as the run\n * opened them, because the useful reading of a suite in flight is chronological\n * — the test that is hanging is the last one with an arrow next to it — and\n * newest-first would put the answer at the top and the story in reverse.\n */\nexport const runSignals: Tool<VantageState> = {\n name: 'variance_run_signals',\n description:\n 'What the running test suite is doing now: every test that has reported, its state, and how much each has announced. Not limited to visual tests.',\n inputSchema: {\n type: 'object',\n properties: {\n state: {\n type: 'string',\n enum: ['running', 'passed', 'failed', 'timedOut', 'skipped', 'interrupted'],\n description: 'Show only tests in this state.',\n },\n file: { type: 'string', description: 'Show only tests whose spec file contains this.' },\n limit: {\n type: 'integer',\n minimum: 1,\n description: 'How many of the most recent to list. Defaults to 50.',\n },\n },\n additionalProperties: false,\n },\n run(state, input) {\n if (state.tests.length === 0) return unattached(state);\n\n const wanted = stateArg(input);\n const file = fileArg(input);\n const matching = state.tests.filter(\n (test) =>\n (wanted === undefined || test.state === wanted) &&\n (file === undefined || test.file.includes(file)),\n );\n if (matching.length === 0) return nothingMatched(state, wanted, file);\n\n const limit = limitArg(input);\n const shown = matching.slice(Math.max(0, matching.length - limit));\n const elided = matching.length - shown.length;\n\n return [\n summary(state, matching),\n '',\n ...shown.map(row),\n ...(elided === 0 ? [] : ['', `… ${elided} earlier match(es) not listed; raise \\`limit\\`.`]),\n ...(state.forgotten === 0\n ? []\n : ['', `${state.forgotten} earlier test(s) were dropped to keep this watcher bounded.`]),\n ].join('\\n');\n },\n};\n\nfunction summary(state: VantageState, matching: readonly WatchedTest[]): string {\n const counted = new Map<TestState, number>();\n for (const test of matching) counted.set(test.state, (counted.get(test.state) ?? 0) + 1);\n const breakdown = [...counted]\n .sort((left, right) => right[1] - left[1])\n .map(([one, count]) => `${count} ${one}`)\n .join(', ');\n const where = state.address === undefined ? '' : ` at ${state.address}`;\n return `${matching.length} test(s)${where} — ${breakdown}.`;\n}\n\nfunction row(test: WatchedTest): string {\n const mark = test.state === 'running' ? '▸' : ' ';\n const lines = [`${mark} ${test.state.padEnd(11)} ${heading(test)} — ${tally(test)}`];\n if (test.error !== undefined) {\n lines.push(...test.error.split('\\n').map((line) => ` ${line}`));\n }\n return lines.join('\\n');\n}\n\nfunction nothingMatched(\n state: VantageState,\n wanted: TestState | undefined,\n file: string | undefined,\n): string {\n const asked = [\n ...(wanted === undefined ? [] : [`state ${wanted}`]),\n ...(file === undefined ? [] : [`file containing ${file}`]),\n ].join(' and ');\n const states = [...new Set(state.tests.map((test) => test.state))].sort().join(', ');\n return `No test here matches ${asked}. ${state.tests.length} test(s) have reported, in: ${states}.`;\n}\n\nconst STATES = new Set<string>([\n 'running',\n 'passed',\n 'failed',\n 'timedOut',\n 'skipped',\n 'interrupted',\n]);\n\nfunction stateArg(input: Readonly<Record<string, unknown>>): TestState | undefined {\n const value = input['state'];\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || !STATES.has(value)) {\n throw new Error(`\\`state\\` must be one of: ${[...STATES].join(', ')}`);\n }\n return value as TestState;\n}\n\nfunction fileArg(input: Readonly<Record<string, unknown>>): string | undefined {\n const value = input['file'];\n if (value === undefined) return undefined;\n if (typeof value !== 'string' || value === '') {\n throw new Error('`file` must be a non-empty string');\n }\n return value;\n}\n\nfunction limitArg(input: Readonly<Record<string, unknown>>): number {\n const value = input['limit'];\n if (value === undefined) return 50;\n if (!Number.isInteger(value) || (value as number) < 1) {\n throw new Error('`limit` must be a positive integer');\n }\n return value as number;\n}\n"]}
@@ -0,0 +1,6 @@
1
+ import type { ScenarioArchiveManifest } from '@variance-authority/scenario/archive';
2
+ import { type Tool } from './tool.js';
3
+ export type ScenarioEvidence = readonly ScenarioArchiveManifest[];
4
+ /** Witnessed Arrange states and authored Act outcomes from retained scenario manifests. */
5
+ export declare const scenarios: Tool<ScenarioEvidence>;
6
+ //# sourceMappingURL=scenarios.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { stringArg } from './tool.js';
2
+ /** Witnessed Arrange states and authored Act outcomes from retained scenario manifests. */
3
+ export const scenarios = {
4
+ name: 'variance_scenarios',
5
+ description: 'List retained runtime scenarios, or show one witnessed Arrange state and each authored Act outcome without inventing missing frames.',
6
+ inputSchema: {
7
+ type: 'object',
8
+ properties: {
9
+ scenario: { type: 'string', description: 'Scenario id.' },
10
+ execution: { type: 'string', description: 'Optional execution id when a scenario has several.' },
11
+ },
12
+ additionalProperties: false,
13
+ },
14
+ run(manifests, input) {
15
+ if (input['scenario'] === undefined)
16
+ return listing(manifests);
17
+ const scenario = stringArg(input, 'scenario');
18
+ const execution = input['execution'] === undefined ? undefined : stringArg(input, 'execution');
19
+ const candidates = manifests.filter((manifest) => manifest.definition.id === scenario &&
20
+ (execution === undefined || manifest.execution.id === execution));
21
+ if (candidates.length === 0)
22
+ return `No retained scenario matches ${scenario}${execution === undefined ? '' : ` / ${execution}`}.`;
23
+ if (candidates.length > 1) {
24
+ return `${candidates.length} executions match ${scenario}; choose one:\n${candidates.map((manifest) => ` ${manifest.execution.id}`).join('\n')}`;
25
+ }
26
+ return describe(candidates[0]);
27
+ },
28
+ };
29
+ function listing(manifests) {
30
+ if (manifests.length === 0)
31
+ return 'The scenario archive contains no manifests.';
32
+ return [
33
+ `${manifests.length} retained scenario execution(s):`,
34
+ ...manifests.map((manifest) => ` ${manifest.definition.id} / ${manifest.execution.id} — Arrange ${manifest.execution.precondition.id}, ${Math.max(0, manifest.execution.frames.length - 1)} Act frame(s)`),
35
+ ].join('\n');
36
+ }
37
+ function describe(manifest) {
38
+ const execution = manifest.execution;
39
+ const frames = execution.frames.map((frame) => {
40
+ const name = frame.at === 0
41
+ ? `Arrange ${execution.precondition.id}`
42
+ : `Act ${frame.act?.key ?? 'missing identity'} #${frame.act?.occurrence ?? '?'}`;
43
+ if (frame.outcome.kind === 'observed')
44
+ return ` ${frame.at}. ${name} → observed state ${frame.outcome.state}`;
45
+ return ` ${frame.at}. ${name} → unobserved: ${frame.outcome.diagnostics.map((diagnostic) => diagnostic.message).join('; ')}`;
46
+ });
47
+ return [
48
+ `${manifest.definition.id} / ${execution.id}`,
49
+ `Profile: ${execution.profile}. Retained until ${manifest.retainUntil}.`,
50
+ execution.preconditionLink === undefined
51
+ ? 'Arrange relation: not supplied.'
52
+ : execution.preconditionLink.kind === 'resolved'
53
+ ? `Arrange relation: ${execution.preconditionLink.how} parent ${execution.preconditionLink.parent}.`
54
+ : `Arrange relation: unresolved — ${execution.preconditionLink.because}`,
55
+ '',
56
+ ...frames,
57
+ ...(execution.termination === undefined
58
+ ? []
59
+ : ['', `Witnessed prefix ended: ${execution.termination.map((diagnostic) => diagnostic.message).join('; ')}`]),
60
+ ].join('\n');
61
+ }
62
+ //# sourceMappingURL=scenarios.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scenarios.js","sourceRoot":"","sources":["../../src/tools/scenarios.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAa,MAAM,WAAW,CAAC;AAIjD,2FAA2F;AAC3F,MAAM,CAAC,MAAM,SAAS,GAA2B;IAC/C,IAAI,EAAE,oBAAoB;IAC1B,WAAW,EACT,sIAAsI;IACxI,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,EAAE;YACzD,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,oDAAoD,EAAE;SACjG;QACD,oBAAoB,EAAE,KAAK;KAC5B;IACD,GAAG,CAAC,SAAS,EAAE,KAAK;QAClB,IAAI,KAAK,CAAC,UAAU,CAAC,KAAK,SAAS;YAAE,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;QAC9C,MAAM,SAAS,GAAG,KAAK,CAAC,WAAW,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC;QAC/F,MAAM,UAAU,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAC/C,QAAQ,CAAC,UAAU,CAAC,EAAE,KAAK,QAAQ;YACnC,CAAC,SAAS,KAAK,SAAS,IAAI,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC;QACpE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,gCAAgC,QAAQ,GAAG,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,SAAS,EAAE,GAAG,CAAC;QACnI,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,OAAO,GAAG,UAAU,CAAC,MAAM,qBAAqB,QAAQ,kBAAkB,UAAU,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,QAAQ,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpJ,CAAC;QACD,OAAO,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAE,CAAC,CAAC;IAClC,CAAC;CACF,CAAC;AAEF,SAAS,OAAO,CAAC,SAA2B;IAC1C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,6CAA6C,CAAC;IACjF,OAAO;QACL,GAAG,SAAS,CAAC,MAAM,kCAAkC;QACrD,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAC5B,KAAK,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,QAAQ,CAAC,SAAS,CAAC,EAAE,cAAc,QAAQ,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,KAAK,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,eAAe,CAAC;KAC/K,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,SAAS,QAAQ,CAAC,QAAiC;IACjD,MAAM,SAAS,GAAG,QAAQ,CAAC,SAAS,CAAC;IACrC,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5C,MAAM,IAAI,GAAG,KAAK,CAAC,EAAE,KAAK,CAAC;YACzB,CAAC,CAAC,WAAW,SAAS,CAAC,YAAY,CAAC,EAAE,EAAE;YACxC,CAAC,CAAC,OAAO,KAAK,CAAC,GAAG,EAAE,GAAG,IAAI,kBAAkB,KAAK,KAAK,CAAC,GAAG,EAAE,UAAU,IAAI,GAAG,EAAE,CAAC;QACnF,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU;YAAE,OAAO,KAAK,KAAK,CAAC,EAAE,KAAK,IAAI,qBAAqB,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QAC/G,OAAO,KAAK,KAAK,CAAC,EAAE,KAAK,IAAI,kBAAkB,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IAChI,CAAC,CAAC,CAAC;IACH,OAAO;QACL,GAAG,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,SAAS,CAAC,EAAE,EAAE;QAC7C,YAAY,SAAS,CAAC,OAAO,oBAAoB,QAAQ,CAAC,WAAW,GAAG;QACxE,SAAS,CAAC,gBAAgB,KAAK,SAAS;YACtC,CAAC,CAAC,iCAAiC;YACnC,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,IAAI,KAAK,UAAU;gBAC9C,CAAC,CAAC,qBAAqB,SAAS,CAAC,gBAAgB,CAAC,GAAG,WAAW,SAAS,CAAC,gBAAgB,CAAC,MAAM,GAAG;gBACpG,CAAC,CAAC,kCAAkC,SAAS,CAAC,gBAAgB,CAAC,OAAO,EAAE;QAC5E,EAAE;QACF,GAAG,MAAM;QACT,GAAG,CAAC,SAAS,CAAC,WAAW,KAAK,SAAS;YACrC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,CAAC,EAAE,EAAE,2BAA2B,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;KACjH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC","sourcesContent":["import type { ScenarioArchiveManifest } from '@variance-authority/scenario/archive';\nimport { stringArg, type Tool } from './tool.js';\n\nexport type ScenarioEvidence = readonly ScenarioArchiveManifest[];\n\n/** Witnessed Arrange states and authored Act outcomes from retained scenario manifests. */\nexport const scenarios: Tool<ScenarioEvidence> = {\n name: 'variance_scenarios',\n description:\n 'List retained runtime scenarios, or show one witnessed Arrange state and each authored Act outcome without inventing missing frames.',\n inputSchema: {\n type: 'object',\n properties: {\n scenario: { type: 'string', description: 'Scenario id.' },\n execution: { type: 'string', description: 'Optional execution id when a scenario has several.' },\n },\n additionalProperties: false,\n },\n run(manifests, input) {\n if (input['scenario'] === undefined) return listing(manifests);\n const scenario = stringArg(input, 'scenario');\n const execution = input['execution'] === undefined ? undefined : stringArg(input, 'execution');\n const candidates = manifests.filter((manifest) =>\n manifest.definition.id === scenario &&\n (execution === undefined || manifest.execution.id === execution));\n if (candidates.length === 0) return `No retained scenario matches ${scenario}${execution === undefined ? '' : ` / ${execution}`}.`;\n if (candidates.length > 1) {\n return `${candidates.length} executions match ${scenario}; choose one:\\n${candidates.map((manifest) => ` ${manifest.execution.id}`).join('\\n')}`;\n }\n return describe(candidates[0]!);\n },\n};\n\nfunction listing(manifests: ScenarioEvidence): string {\n if (manifests.length === 0) return 'The scenario archive contains no manifests.';\n return [\n `${manifests.length} retained scenario execution(s):`,\n ...manifests.map((manifest) =>\n ` ${manifest.definition.id} / ${manifest.execution.id} — Arrange ${manifest.execution.precondition.id}, ${Math.max(0, manifest.execution.frames.length - 1)} Act frame(s)`),\n ].join('\\n');\n}\n\nfunction describe(manifest: ScenarioArchiveManifest): string {\n const execution = manifest.execution;\n const frames = execution.frames.map((frame) => {\n const name = frame.at === 0\n ? `Arrange ${execution.precondition.id}`\n : `Act ${frame.act?.key ?? 'missing identity'} #${frame.act?.occurrence ?? '?'}`;\n if (frame.outcome.kind === 'observed') return ` ${frame.at}. ${name} → observed state ${frame.outcome.state}`;\n return ` ${frame.at}. ${name} → unobserved: ${frame.outcome.diagnostics.map((diagnostic) => diagnostic.message).join('; ')}`;\n });\n return [\n `${manifest.definition.id} / ${execution.id}`,\n `Profile: ${execution.profile}. Retained until ${manifest.retainUntil}.`,\n execution.preconditionLink === undefined\n ? 'Arrange relation: not supplied.'\n : execution.preconditionLink.kind === 'resolved'\n ? `Arrange relation: ${execution.preconditionLink.how} parent ${execution.preconditionLink.parent}.`\n : `Arrange relation: unresolved — ${execution.preconditionLink.because}`,\n '',\n ...frames,\n ...(execution.termination === undefined\n ? []\n : ['', `Witnessed prefix ended: ${execution.termination.map((diagnostic) => diagnostic.message).join('; ')}`]),\n ].join('\\n');\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import { type ExecutionIndex } from '@variance-authority/sense/test-selection';
2
+ import { type Tool } from './tool.js';
3
+ /** Named tests that reached one source file, line, or function. */
4
+ export declare const sourceTests: Tool<ExecutionIndex>;
5
+ //# sourceMappingURL=source-tests.d.ts.map
@@ -0,0 +1,95 @@
1
+ import { coveringTests, coveringTestsInFile, } from '@variance-authority/sense/test-selection';
2
+ import { stringArg } from './tool.js';
3
+ /** Named tests that reached one source file, line, or function. */
4
+ export const sourceTests = {
5
+ name: 'variance_source_tests',
6
+ description: 'Find named tests that reached a source file, line, or function, ranked by observed call-stack depth.',
7
+ inputSchema: {
8
+ type: 'object',
9
+ properties: {
10
+ file: { type: 'string', description: 'Repository-relative source file.' },
11
+ line: { type: 'integer', minimum: 1, description: 'Optional one-based source line.' },
12
+ function: { type: 'string', description: 'Optional exact indexed function name.' },
13
+ },
14
+ required: ['file'],
15
+ additionalProperties: false,
16
+ },
17
+ run(index, input) {
18
+ const file = stringArg(input, 'file');
19
+ const line = optionalLine(input);
20
+ const functionName = optionalFunction(input);
21
+ if (line !== undefined && functionName !== undefined) {
22
+ throw new Error('`line` and `function` are alternatives; provide at most one');
23
+ }
24
+ const module = index.modules.find((candidate) => candidate.file === file);
25
+ if (module === undefined)
26
+ return unknownFile(index, file);
27
+ if (line !== undefined) {
28
+ if (!module.blocks.some((block) => block.source && block.startLine <= line && line <= block.endLine))
29
+ return `Line ${line} is not indexed in ${file}.`;
30
+ return focused(file, `line ${line}`, coveringTests(index, { file, line }));
31
+ }
32
+ if (functionName !== undefined) {
33
+ if (!module.blocks.some((block) => block.source && block.kind === 'function' && block.name === functionName))
34
+ return `Function ${functionName} is not indexed in ${file}.`;
35
+ return focused(file, `function ${functionName}`, coveringTests(index, {
36
+ file,
37
+ function: functionName,
38
+ }));
39
+ }
40
+ return wholeFile(index, file);
41
+ },
42
+ };
43
+ function optionalLine(input) {
44
+ const value = input['line'];
45
+ if (value === undefined)
46
+ return undefined;
47
+ if (!Number.isInteger(value) || value < 1) {
48
+ throw new Error('`line` must be a positive integer');
49
+ }
50
+ return value;
51
+ }
52
+ function optionalFunction(input) {
53
+ if (input['function'] === undefined)
54
+ return undefined;
55
+ return stringArg(input, 'function');
56
+ }
57
+ function focused(file, target, tests) {
58
+ if (tests.length === 0)
59
+ return `No named test reached ${target} in ${file}.`;
60
+ return [
61
+ `${tests.length} named test(s) reached ${target} in ${file}:`,
62
+ ...tests.map(formatTest),
63
+ ].join('\n');
64
+ }
65
+ function wholeFile(index, file) {
66
+ const ranges = coveringTestsInFile(index, file);
67
+ if (ranges.length === 0)
68
+ return `No source lines are indexed for ${file}.`;
69
+ const identities = new Set(ranges.flatMap((range) => range.tests.map((test) => test.id)));
70
+ const lines = [
71
+ `${file} — ${ranges.length} source range(s), ${identities.size} named test(s)`,
72
+ ];
73
+ for (const range of ranges) {
74
+ lines.push(range.startLine === range.endLine
75
+ ? `line ${range.startLine}`
76
+ : `lines ${range.startLine}-${range.endLine}`);
77
+ lines.push(...(range.tests.length === 0
78
+ ? [' no named test reached this range']
79
+ : range.tests.map((test) => ` ${formatTest(test)}`)));
80
+ }
81
+ return lines.join('\n');
82
+ }
83
+ function formatTest(test) {
84
+ return `depth ${test.distance} — ${test.name} — ${test.file} [${test.id}]`;
85
+ }
86
+ function unknownFile(index, file) {
87
+ const files = index.modules.map((module) => module.file).sort(codeUnitOrder);
88
+ return files.length === 0
89
+ ? `Unknown source file: ${file}. The execution index contains no source files.`
90
+ : `Unknown source file: ${file}. Indexed files:\n${files.map((candidate) => ` ${candidate}`).join('\n')}`;
91
+ }
92
+ function codeUnitOrder(left, right) {
93
+ return left < right ? -1 : left > right ? 1 : 0;
94
+ }
95
+ //# sourceMappingURL=source-tests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source-tests.js","sourceRoot":"","sources":["../../src/tools/source-tests.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n coveringTests,\n coveringTestsInFile,\n type CoveringTest,\n type ExecutionIndex,\n} from '@variance-authority/sense/test-selection';\nimport { stringArg, type Tool } from './tool.js';\n\n/** Named tests that reached one source file, line, or function. */\nexport const sourceTests: Tool<ExecutionIndex> = {\n name: 'variance_source_tests',\n description:\n 'Find named tests that reached a source file, line, or function, ranked by observed call-stack depth.',\n inputSchema: {\n type: 'object',\n properties: {\n file: { type: 'string', description: 'Repository-relative source file.' },\n line: { type: 'integer', minimum: 1, description: 'Optional one-based source line.' },\n function: { type: 'string', description: 'Optional exact indexed function name.' },\n },\n required: ['file'],\n additionalProperties: false,\n },\n run(index, input) {\n const file = stringArg(input, 'file');\n const line = optionalLine(input);\n const functionName = optionalFunction(input);\n if (line !== undefined && functionName !== undefined) {\n throw new Error('`line` and `function` are alternatives; provide at most one');\n }\n\n const module = index.modules.find((candidate) => candidate.file === file);\n if (module === undefined) return unknownFile(index, file);\n if (line !== undefined) {\n if (!module.blocks.some((block) =>\n block.source && block.startLine <= line && line <= block.endLine,\n )) return `Line ${line} is not indexed in ${file}.`;\n return focused(file, `line ${line}`, coveringTests(index, { file, line }));\n }\n if (functionName !== undefined) {\n if (!module.blocks.some((block) =>\n block.source && block.kind === 'function' && block.name === functionName,\n )) return `Function ${functionName} is not indexed in ${file}.`;\n return focused(file, `function ${functionName}`, coveringTests(index, {\n file,\n function: functionName,\n }));\n }\n return wholeFile(index, file);\n },\n};\n\nfunction optionalLine(input: Readonly<Record<string, unknown>>): number | undefined {\n const value = input['line'];\n if (value === undefined) return undefined;\n if (!Number.isInteger(value) || (value as number) < 1) {\n throw new Error('`line` must be a positive integer');\n }\n return value as number;\n}\n\nfunction optionalFunction(input: Readonly<Record<string, unknown>>): string | undefined {\n if (input['function'] === undefined) return undefined;\n return stringArg(input, 'function');\n}\n\nfunction focused(file: string, target: string, tests: readonly CoveringTest[]): string {\n if (tests.length === 0) return `No named test reached ${target} in ${file}.`;\n return [\n `${tests.length} named test(s) reached ${target} in ${file}:`,\n ...tests.map(formatTest),\n ].join('\\n');\n}\n\nfunction wholeFile(index: ExecutionIndex, file: string): string {\n const ranges = coveringTestsInFile(index, file);\n if (ranges.length === 0) return `No source lines are indexed for ${file}.`;\n const identities = new Set(ranges.flatMap((range) => range.tests.map((test) => test.id)));\n const lines = [\n `${file} — ${ranges.length} source range(s), ${identities.size} named test(s)`,\n ];\n for (const range of ranges) {\n lines.push(range.startLine === range.endLine\n ? `line ${range.startLine}`\n : `lines ${range.startLine}-${range.endLine}`);\n lines.push(...(range.tests.length === 0\n ? [' no named test reached this range']\n : range.tests.map((test) => ` ${formatTest(test)}`)));\n }\n return lines.join('\\n');\n}\n\nfunction formatTest(test: CoveringTest): string {\n return `depth ${test.distance} — ${test.name} — ${test.file} [${test.id}]`;\n}\n\nfunction unknownFile(index: ExecutionIndex, file: string): string {\n const files = index.modules.map((module) => module.file).sort(codeUnitOrder);\n return files.length === 0\n ? `Unknown source file: ${file}. The execution index contains no source files.`\n : `Unknown source file: ${file}. Indexed files:\\n${files.map((candidate) => ` ${candidate}`).join('\\n')}`;\n}\n\nfunction codeUnitOrder(left: string, right: string): number {\n return left < right ? -1 : left > right ? 1 : 0;\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import type { NotObserved, ObservationRecord, RunReport } from '@variance-authority/report';
2
+ /**
3
+ * Naming a subject, and what to say when the run has nothing to say about it.
4
+ *
5
+ * Shared by the two tools an agent points at a single subject — `variance_describe`
6
+ * and `variance_explain_verdict` — and separate from both because the decision
7
+ * encoded here belongs to neither. A subject the run planned and could not see
8
+ * has to be answered rather than refused, and a lookup that returns `undefined`
9
+ * leaves each caller free to forget that on its own schedule.
10
+ */
11
+ /**
12
+ * A subject named in the run, and which of the two lists it came from.
13
+ *
14
+ * A union rather than `ObservationRecord | undefined` so that "the run did not
15
+ * observe this" cannot be handled by accident. Every caller has to decide what to
16
+ * say about a subject with no observation, and the answer is never a verdict.
17
+ */
18
+ export type Located = {
19
+ readonly observed: true;
20
+ readonly observation: ObservationRecord;
21
+ } | {
22
+ readonly observed: false;
23
+ readonly entry: NotObserved;
24
+ };
25
+ export declare function subjectOf(report: RunReport, input: Readonly<Record<string, unknown>>): Located;
26
+ /**
27
+ * What to say about a subject with no observation — the CLI's wording, verbatim.
28
+ *
29
+ * Duplicated text rather than a shared helper only because the packages point the
30
+ * other way round, and the duplication is deliberate where drift would be worst:
31
+ * a human running `variance report --subject x` and an agent calling
32
+ * `variance_describe` on the same subject must be told the same thing, or the two
33
+ * of them will argue about a run neither can re-observe.
34
+ */
35
+ export declare function unobserved(entry: NotObserved): string;
36
+ /**
37
+ * What the reader should do about a subject nobody looked at.
38
+ *
39
+ * One sentence per kind, and the three are different instructions. `failed` is
40
+ * work; `excluded` is a decision already on the record; `unreached` is the run
41
+ * having proved the change cannot arrive here, which is the only one of the
42
+ * three that is evidence rather than an absence of it.
43
+ */
44
+ export declare function notObservedSentence(kind: NotObserved['kind']): string;
45
+ //# sourceMappingURL=subject.d.ts.map
@@ -0,0 +1,57 @@
1
+ import { stringArg } from './tool.js';
2
+ export function subjectOf(report, input) {
3
+ const subject = stringArg(input, 'subject');
4
+ const observation = report.observations.find((entry) => entry.subject === subject);
5
+ if (observation !== undefined)
6
+ return { observed: true, observation };
7
+ // The coverage list is searched too, and this is the point of it. A subject the
8
+ // run planned and could not see is a subject an agent will ask about; refusing
9
+ // the name tells it the subject does not exist, which is both false and the
10
+ // conclusion that ends the investigation.
11
+ const skipped = report.notObserved?.find((entry) => entry.subject === subject);
12
+ if (skipped !== undefined)
13
+ return { observed: false, entry: skipped };
14
+ // Listing the alternatives rather than only refusing: an agent that gets
15
+ // "unknown subject" retries with another guess, and an agent that gets the
16
+ // list picks the right one.
17
+ throw new Error(`unknown subject "${subject}"; this run has: ` +
18
+ [
19
+ ...report.observations.map((entry) => entry.subject),
20
+ ...(report.notObserved ?? []).map((entry) => entry.subject),
21
+ ].join(', '));
22
+ }
23
+ /**
24
+ * What to say about a subject with no observation — the CLI's wording, verbatim.
25
+ *
26
+ * Duplicated text rather than a shared helper only because the packages point the
27
+ * other way round, and the duplication is deliberate where drift would be worst:
28
+ * a human running `variance report --subject x` and an agent calling
29
+ * `variance_describe` on the same subject must be told the same thing, or the two
30
+ * of them will argue about a run neither can re-observe.
31
+ */
32
+ export function unobserved(entry) {
33
+ return [
34
+ `[not observed] ${entry.subject}`,
35
+ entry.because,
36
+ notObservedSentence(entry.kind),
37
+ ].join('\n');
38
+ }
39
+ /**
40
+ * What the reader should do about a subject nobody looked at.
41
+ *
42
+ * One sentence per kind, and the three are different instructions. `failed` is
43
+ * work; `excluded` is a decision already on the record; `unreached` is the run
44
+ * having proved the change cannot arrive here, which is the only one of the
45
+ * three that is evidence rather than an absence of it.
46
+ */
47
+ export function notObservedSentence(kind) {
48
+ switch (kind) {
49
+ case 'excluded':
50
+ return 'This was excluded by configuration, not by a failure.';
51
+ case 'unreached':
52
+ return 'The change under review cannot reach this subject, so it was not rendered.';
53
+ default:
54
+ return 'The run meant to observe this and could not. It is not a pass.';
55
+ }
56
+ }
57
+ //# sourceMappingURL=subject.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subject.js","sourceRoot":"","sources":["../../src/tools/subject.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAuBtC,MAAM,UAAU,SAAS,CAAC,MAAiB,EAAE,KAAwC;IACnF,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;IAE5C,MAAM,WAAW,GAAG,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;IACnF,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAEtE,gFAAgF;IAChF,+EAA+E;IAC/E,4EAA4E;IAC5E,0CAA0C;IAC1C,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC;IAC/E,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;IAEtE,yEAAyE;IACzE,2EAA2E;IAC3E,4BAA4B;IAC5B,MAAM,IAAI,KAAK,CACb,oBAAoB,OAAO,mBAAmB;QAC5C;YACE,GAAG,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC;YACpD,GAAG,CAAC,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC;SAC5D,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,KAAkB;IAC3C,OAAO;QACL,kBAAkB,KAAK,CAAC,OAAO,EAAE;QACjC,KAAK,CAAC,OAAO;QACb,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC;KAChC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAyB;IAC3D,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,UAAU;YACb,OAAO,uDAAuD,CAAC;QACjE,KAAK,WAAW;YACd,OAAO,4EAA4E,CAAC;QACtF;YACE,OAAO,gEAAgE,CAAC;IAC5E,CAAC;AACH,CAAC","sourcesContent":["import type { NotObserved, ObservationRecord, RunReport } from '@variance-authority/report';\nimport { stringArg } from './tool.js';\n\n/**\n * Naming a subject, and what to say when the run has nothing to say about it.\n *\n * Shared by the two tools an agent points at a single subject — `variance_describe`\n * and `variance_explain_verdict` — and separate from both because the decision\n * encoded here belongs to neither. A subject the run planned and could not see\n * has to be answered rather than refused, and a lookup that returns `undefined`\n * leaves each caller free to forget that on its own schedule.\n */\n\n/**\n * A subject named in the run, and which of the two lists it came from.\n *\n * A union rather than `ObservationRecord | undefined` so that \"the run did not\n * observe this\" cannot be handled by accident. Every caller has to decide what to\n * say about a subject with no observation, and the answer is never a verdict.\n */\nexport type Located =\n | { readonly observed: true; readonly observation: ObservationRecord }\n | { readonly observed: false; readonly entry: NotObserved };\n\nexport function subjectOf(report: RunReport, input: Readonly<Record<string, unknown>>): Located {\n const subject = stringArg(input, 'subject');\n\n const observation = report.observations.find((entry) => entry.subject === subject);\n if (observation !== undefined) return { observed: true, observation };\n\n // The coverage list is searched too, and this is the point of it. A subject the\n // run planned and could not see is a subject an agent will ask about; refusing\n // the name tells it the subject does not exist, which is both false and the\n // conclusion that ends the investigation.\n const skipped = report.notObserved?.find((entry) => entry.subject === subject);\n if (skipped !== undefined) return { observed: false, entry: skipped };\n\n // Listing the alternatives rather than only refusing: an agent that gets\n // \"unknown subject\" retries with another guess, and an agent that gets the\n // list picks the right one.\n throw new Error(\n `unknown subject \"${subject}\"; this run has: ` +\n [\n ...report.observations.map((entry) => entry.subject),\n ...(report.notObserved ?? []).map((entry) => entry.subject),\n ].join(', '),\n );\n}\n\n/**\n * What to say about a subject with no observation — the CLI's wording, verbatim.\n *\n * Duplicated text rather than a shared helper only because the packages point the\n * other way round, and the duplication is deliberate where drift would be worst:\n * a human running `variance report --subject x` and an agent calling\n * `variance_describe` on the same subject must be told the same thing, or the two\n * of them will argue about a run neither can re-observe.\n */\nexport function unobserved(entry: NotObserved): string {\n return [\n `[not observed] ${entry.subject}`,\n entry.because,\n notObservedSentence(entry.kind),\n ].join('\\n');\n}\n\n/**\n * What the reader should do about a subject nobody looked at.\n *\n * One sentence per kind, and the three are different instructions. `failed` is\n * work; `excluded` is a decision already on the record; `unreached` is the run\n * having proved the change cannot arrive here, which is the only one of the\n * three that is evidence rather than an absence of it.\n */\nexport function notObservedSentence(kind: NotObserved['kind']): string {\n switch (kind) {\n case 'excluded':\n return 'This was excluded by configuration, not by a failure.';\n case 'unreached':\n return 'The change under review cannot reach this subject, so it was not rendered.';\n default:\n return 'The run meant to observe this and could not. It is not a pass.';\n }\n}\n"]}
@@ -0,0 +1,31 @@
1
+ import { type Tool } from './tool.js';
2
+ /**
3
+ * `variance_summary`, and the sections it is assembled from.
4
+ *
5
+ * The helpers below live with it rather than somewhere shared because none of
6
+ * them is a general fact about a report — each is one paragraph of this one
7
+ * answer, and each exists to stop this answer claiming something the run never
8
+ * established. Coverage, order dependence, findings and the closing sentence all
9
+ * repeat the same rule in different words: silence about a subject is not a pass.
10
+ * Read them in the order `run` prints them; that order is the argument.
11
+ */
12
+ /**
13
+ * Overview: what happened, what needs attention, and what was never looked at.
14
+ *
15
+ * Deliberately does not list unchanged subjects. A run over 300 subjects where
16
+ * two changed should print two lines — a list as long as the suite is the "100
17
+ * changes? merge" failure in its purest form, and the reader stops reading long
18
+ * before the interesting line.
19
+ *
20
+ * The coverage section is the exception to that economy and is not negotiable.
21
+ * A run that planned 300 subjects, failed on 50 and found the other 250
22
+ * unchanged has an observation list in which every entry is clean; a summary
23
+ * computed from observations alone therefore reports it as a clean run, and an
24
+ * agent acts on that. So `nothing to review` is claimed only from a report that
25
+ * accounted for every subject it planned — never from silence, and never over a
26
+ * subject the run meant to see and could not. That is the same rule the CLI's
27
+ * exit code applies, stated in the same words, because a human and an agent
28
+ * reading one artifact must not be able to disagree about whether it was green.
29
+ */
30
+ export declare const summarize: Tool;
31
+ //# sourceMappingURL=summary.d.ts.map