@vizzly-testing/cli 0.35.2 → 0.35.3-beta.1
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/README.md +8 -5
- package/dist/api/client.js +20 -5
- package/dist/api/core.js +17 -3
- package/dist/api/endpoints.js +24 -3
- package/dist/api/index.js +1 -1
- package/dist/cli.js +25 -65
- package/dist/commands/baselines.js +0 -7
- package/dist/commands/comparisons.js +52 -45
- package/dist/commands/config-cmd.js +0 -7
- package/dist/commands/context.js +444 -238
- package/dist/commands/doctor.js +0 -8
- package/dist/commands/init.js +7 -12
- package/dist/commands/login.js +0 -13
- package/dist/commands/logout.js +0 -12
- package/dist/commands/orgs.js +0 -9
- package/dist/commands/run.js +84 -29
- package/dist/commands/status.js +276 -59
- package/dist/commands/tdd.js +19 -3
- package/dist/commands/whoami.js +0 -12
- package/dist/context/local-workspace-provider.js +293 -37
- package/dist/plugin-loader.js +1 -1
- package/dist/reporter/reporter-bundle.css +1 -1
- package/dist/reporter/reporter-bundle.iife.js +17 -17
- package/dist/server/routers/events.js +4 -4
- package/dist/test-runner/operations.js +70 -28
- package/dist/types/index.d.ts +4 -2
- package/dist/uploader/core.js +6 -6
- package/dist/uploader/operations.js +4 -1
- package/dist/utils/visual-context-normalizers.js +455 -0
- package/package.json +13 -13
- package/skills/vizzly/SKILL.md +68 -101
- package/skills/vizzly/agents/openai.yaml +2 -2
- package/skills/vizzly/references/cli-context.md +79 -27
- package/skills/vizzly/references/dynamic-content.md +28 -33
- package/skills/vizzly/references/sdks.md +26 -39
- package/skills/vizzly/references/setup-ci.md +44 -20
- package/dist/config/index.js +0 -13
- package/dist/project/index.js +0 -13
- package/dist/tdd/index.js +0 -19
- package/dist/tdd/services/baseline-downloader.js +0 -151
- package/dist/tdd/services/hotspot-service.js +0 -58
- package/dist/utils/environment.js +0 -109
- package/skills/vizzly/references/public-screenshots.md +0 -44
package/dist/commands/context.js
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
import { createApiClient as defaultCreateApiClient, getBuildContext as defaultGetBuildContext, getComparisonContext as defaultGetComparisonContext, getReviewQueueContext as defaultGetReviewQueueContext, getScreenshotContext as defaultGetScreenshotContext, getSimilarFingerprintContext as defaultGetSimilarFingerprintContext } from '../api/index.js';
|
|
6
6
|
import { createLocalWorkspaceContextProvider as defaultCreateLocalWorkspaceContextProvider } from '../context/local-workspace-provider.js';
|
|
7
7
|
import { resolveContextSource as defaultResolveContextSource } from '../context/provider-resolver.js';
|
|
8
|
+
import { VizzlyError } from '../errors/vizzly-error.js';
|
|
8
9
|
import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
|
|
9
10
|
import * as defaultOutput from '../utils/output.js';
|
|
10
11
|
import { readSession as defaultReadSession } from '../utils/session.js';
|
|
12
|
+
import { normalizeBuildContext } from '../utils/visual-context-normalizers.js';
|
|
11
13
|
function buildAuthErrorMessage() {
|
|
12
14
|
return 'Authentication required. Use --token, set VIZZLY_TOKEN, run "vizzly login", or link a project.';
|
|
13
15
|
}
|
|
@@ -15,7 +17,7 @@ function buildSourceErrorMessage() {
|
|
|
15
17
|
return '--source must be one of: auto, cloud, local';
|
|
16
18
|
}
|
|
17
19
|
function buildIncludeErrorMessage() {
|
|
18
|
-
return '--include must contain only:
|
|
20
|
+
return '--include must contain only: diffs';
|
|
19
21
|
}
|
|
20
22
|
function validateLimitRange(value, flagName, {
|
|
21
23
|
min = 1,
|
|
@@ -29,15 +31,81 @@ function validateLimitRange(value, flagName, {
|
|
|
29
31
|
}
|
|
30
32
|
return [];
|
|
31
33
|
}
|
|
32
|
-
function
|
|
34
|
+
function validateCursor(value) {
|
|
33
35
|
if (value == null) {
|
|
34
36
|
return [];
|
|
35
37
|
}
|
|
38
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
39
|
+
return ['--cursor must be a non-empty opaque cursor'];
|
|
40
|
+
}
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
function validateOffset(value) {
|
|
44
|
+
if (value == null) return [];
|
|
36
45
|
if (!Number.isInteger(value) || value < 0) {
|
|
37
46
|
return ['--offset must be a non-negative integer'];
|
|
38
47
|
}
|
|
39
48
|
return [];
|
|
40
49
|
}
|
|
50
|
+
let COMPACT_CONTEXT_LIMIT = 10;
|
|
51
|
+
let COMPACT_SUMMARY_MAX_BYTES = 64 * 1024;
|
|
52
|
+
let COMPACT_DIFFS_MAX_BYTES = 512 * 1024;
|
|
53
|
+
function getCompactPage(collection, label) {
|
|
54
|
+
if (!collection || !Array.isArray(collection.items) || !collection.page) {
|
|
55
|
+
throw new VizzlyError(`Vizzly returned an invalid compact ${label} collection`, 'COMPACT_CONTEXT_INVALID');
|
|
56
|
+
}
|
|
57
|
+
if (collection.items.length > COMPACT_CONTEXT_LIMIT) {
|
|
58
|
+
throw new VizzlyError(`Vizzly returned more than ${COMPACT_CONTEXT_LIMIT} compact ${label} items`, 'COMPACT_CONTEXT_INVALID');
|
|
59
|
+
}
|
|
60
|
+
if (collection.items.some(item => !item || typeof item !== 'object')) {
|
|
61
|
+
throw new VizzlyError(`Vizzly returned an invalid item in compact ${label}`, 'COMPACT_CONTEXT_INVALID');
|
|
62
|
+
}
|
|
63
|
+
let {
|
|
64
|
+
page
|
|
65
|
+
} = collection;
|
|
66
|
+
let validLimit = Number.isInteger(page.limit) && page.limit >= 1 && page.limit <= COMPACT_CONTEXT_LIMIT;
|
|
67
|
+
let validReturned = Number.isInteger(page.returned) && page.returned === collection.items.length;
|
|
68
|
+
let validTotal = page.total == null || Number.isInteger(page.total) && page.total >= 0 && page.total >= page.returned;
|
|
69
|
+
let validHasMore = typeof page.has_more === 'boolean';
|
|
70
|
+
let validCursor = page.next_cursor == null || typeof page.next_cursor === 'string' && page.next_cursor.length > 0;
|
|
71
|
+
if (!validLimit || !validReturned || !validTotal || !validHasMore || !validCursor) {
|
|
72
|
+
throw new VizzlyError(`Vizzly returned invalid pagination facts for compact ${label}`, 'COMPACT_CONTEXT_INVALID');
|
|
73
|
+
}
|
|
74
|
+
if (page.included === false && collection.items.length > 0) {
|
|
75
|
+
throw new VizzlyError(`Vizzly returned items for an omitted compact ${label} collection`, 'COMPACT_CONTEXT_INVALID');
|
|
76
|
+
}
|
|
77
|
+
if (page.has_more !== Boolean(page.next_cursor)) {
|
|
78
|
+
throw new VizzlyError(`Vizzly returned contradictory pagination facts for compact ${label}`, 'COMPACT_CONTEXT_INVALID');
|
|
79
|
+
}
|
|
80
|
+
return collection;
|
|
81
|
+
}
|
|
82
|
+
function validateCompactContext(context, resource) {
|
|
83
|
+
if (!context || typeof context !== 'object') {
|
|
84
|
+
throw new VizzlyError(`Vizzly returned an invalid compact ${resource} context`, 'COMPACT_CONTEXT_INVALID');
|
|
85
|
+
}
|
|
86
|
+
if (context.resource !== `${resource}_context` || !context[resource]?.id) {
|
|
87
|
+
throw new VizzlyError(`Vizzly returned an invalid compact ${resource} context`, 'COMPACT_CONTEXT_INVALID');
|
|
88
|
+
}
|
|
89
|
+
if (resource === 'build') {
|
|
90
|
+
getCompactPage(context.evidence, 'build evidence');
|
|
91
|
+
} else {
|
|
92
|
+
let history = context.history || {};
|
|
93
|
+
getCompactPage(history.similar_by_fingerprint, 'similar history');
|
|
94
|
+
getCompactPage(history.recent_by_name, 'recent history');
|
|
95
|
+
}
|
|
96
|
+
return context;
|
|
97
|
+
}
|
|
98
|
+
function validateCompactOutputSize(payload, resource, details) {
|
|
99
|
+
let bytes = Buffer.byteLength(JSON.stringify(payload));
|
|
100
|
+
let maxBytes = details === 'diffs' ? COMPACT_DIFFS_MAX_BYTES : COMPACT_SUMMARY_MAX_BYTES;
|
|
101
|
+
if (bytes > maxBytes) {
|
|
102
|
+
throw new VizzlyError(`Vizzly produced an oversized compact ${resource} context`, 'COMPACT_CONTEXT_OVERSIZED', {
|
|
103
|
+
bytes,
|
|
104
|
+
max_bytes: maxBytes
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return payload;
|
|
108
|
+
}
|
|
41
109
|
function validateSourceOption(value) {
|
|
42
110
|
if (value == null) {
|
|
43
111
|
return [];
|
|
@@ -54,8 +122,23 @@ function parseIncludeOption(value) {
|
|
|
54
122
|
let rawItems = Array.isArray(value) ? value : String(value).split(',');
|
|
55
123
|
return rawItems.map(item => item.trim()).filter(Boolean);
|
|
56
124
|
}
|
|
125
|
+
function buildCompactContextRequest(options = {}, globalOptions = {}) {
|
|
126
|
+
let include = parseIncludeOption(options.include);
|
|
127
|
+
let compact = !options.full && (!globalOptions.json || options.agent);
|
|
128
|
+
let details = include.includes('diffs') ? 'diffs' : 'summary';
|
|
129
|
+
return {
|
|
130
|
+
compact,
|
|
131
|
+
details,
|
|
132
|
+
include,
|
|
133
|
+
query: compact ? {
|
|
134
|
+
details,
|
|
135
|
+
limit: COMPACT_CONTEXT_LIMIT,
|
|
136
|
+
cursor: options.cursor
|
|
137
|
+
} : undefined
|
|
138
|
+
};
|
|
139
|
+
}
|
|
57
140
|
function validateIncludeOption(value) {
|
|
58
|
-
let allowed = new Set(['
|
|
141
|
+
let allowed = new Set(['diffs']);
|
|
59
142
|
let invalid = parseIncludeOption(value).filter(item => !allowed.has(item));
|
|
60
143
|
return invalid.length > 0 ? [buildIncludeErrorMessage()] : [];
|
|
61
144
|
}
|
|
@@ -76,6 +159,24 @@ function createClient(config, createApiClient) {
|
|
|
76
159
|
command: 'context'
|
|
77
160
|
});
|
|
78
161
|
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Attach the provider that produced a context payload.
|
|
165
|
+
*
|
|
166
|
+
* Context endpoints return API-owned evidence, while source selection belongs
|
|
167
|
+
* to the CLI. Keeping both in one payload prevents follow-up agent commands
|
|
168
|
+
* from silently crossing between cloud and local evidence.
|
|
169
|
+
*
|
|
170
|
+
* @param {Object} context - Context payload returned by a provider.
|
|
171
|
+
* @param {string} source - Provider source selected by the CLI.
|
|
172
|
+
* @returns {Object} Context payload with explicit source provenance.
|
|
173
|
+
*/
|
|
174
|
+
function attachContextSource(context, source) {
|
|
175
|
+
return {
|
|
176
|
+
...context,
|
|
177
|
+
source
|
|
178
|
+
};
|
|
179
|
+
}
|
|
79
180
|
async function loadContextConfig(globalOptions, options, deps) {
|
|
80
181
|
let {
|
|
81
182
|
loadConfig = defaultLoadConfig,
|
|
@@ -108,20 +209,20 @@ function createCloudContextProvider(config, deps = {}) {
|
|
|
108
209
|
let client = createClient(config, createApiClient);
|
|
109
210
|
return {
|
|
110
211
|
source: 'cloud',
|
|
111
|
-
async getBuildContext(buildId) {
|
|
112
|
-
return await getBuildContext(client, buildId);
|
|
212
|
+
async getBuildContext(buildId, query) {
|
|
213
|
+
return attachContextSource(await getBuildContext(client, buildId, query), 'cloud');
|
|
113
214
|
},
|
|
114
215
|
async getComparisonContext(comparisonId, query) {
|
|
115
|
-
return await getComparisonContext(client, comparisonId, query);
|
|
216
|
+
return attachContextSource(await getComparisonContext(client, comparisonId, query), 'cloud');
|
|
116
217
|
},
|
|
117
218
|
async getScreenshotContext(screenshotName, query) {
|
|
118
|
-
return await getScreenshotContext(client, screenshotName, query);
|
|
219
|
+
return attachContextSource(await getScreenshotContext(client, screenshotName, query), 'cloud');
|
|
119
220
|
},
|
|
120
221
|
async getSimilarFingerprintContext(fingerprintHash, query) {
|
|
121
|
-
return await getSimilarFingerprintContext(client, fingerprintHash, query);
|
|
222
|
+
return attachContextSource(await getSimilarFingerprintContext(client, fingerprintHash, query), 'cloud');
|
|
122
223
|
},
|
|
123
224
|
async getReviewQueueContext(query) {
|
|
124
|
-
return await getReviewQueueContext(client, query);
|
|
225
|
+
return attachContextSource(await getReviewQueueContext(client, query), 'cloud');
|
|
125
226
|
}
|
|
126
227
|
};
|
|
127
228
|
}
|
|
@@ -220,12 +321,6 @@ function getStatusTone(colors, status) {
|
|
|
220
321
|
function getComparisonDisplayState(comparison = {}) {
|
|
221
322
|
return comparison.result || comparison.status || 'unknown';
|
|
222
323
|
}
|
|
223
|
-
function isChangedComparison(comparison = {}) {
|
|
224
|
-
return ['changed', 'failed', 'pending'].includes(getComparisonDisplayState(comparison));
|
|
225
|
-
}
|
|
226
|
-
function isNewComparison(comparison = {}) {
|
|
227
|
-
return getComparisonDisplayState(comparison) === 'new';
|
|
228
|
-
}
|
|
229
324
|
function getComparisonName(comparison = {}) {
|
|
230
325
|
return comparison.screenshot_name || comparison.screenshot?.name || comparison.name || comparison.id || 'unknown screenshot';
|
|
231
326
|
}
|
|
@@ -235,141 +330,203 @@ function getComparisonDiffPercentage(comparison = {}) {
|
|
|
235
330
|
function getComparisonFingerprint(comparison = {}) {
|
|
236
331
|
return comparison.diff?.fingerprint_hash || comparison.analysis?.fingerprint_hash || null;
|
|
237
332
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
function
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
let diff = comparison.diff || comparison.analysis || {};
|
|
253
|
-
let compact = {
|
|
254
|
-
percentage: getComparisonDiffPercentage(comparison),
|
|
255
|
-
changed_pixels: diff.changed_pixels ?? comparison.changed_pixels ?? null,
|
|
256
|
-
total_pixels: diff.total_pixels ?? comparison.total_pixels ?? null,
|
|
257
|
-
threshold: diff.threshold ?? comparison.threshold ?? null,
|
|
258
|
-
fingerprint_hash: getComparisonFingerprint(comparison),
|
|
259
|
-
region_count: getComparisonRegionCount(comparison),
|
|
260
|
-
image_url: getComparisonDiffImageUrl(comparison)
|
|
261
|
-
};
|
|
262
|
-
if (includeDiffs) {
|
|
263
|
-
compact.regions = diff.regions || diff.diff_regions || [];
|
|
264
|
-
compact.cluster_metadata = diff.cluster_metadata || null;
|
|
265
|
-
compact.ssim_score = diff.ssim_score ?? null;
|
|
266
|
-
compact.gmsd_score = diff.gmsd_score ?? null;
|
|
267
|
-
compact.diff_lines = diff.diff_lines || [];
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Quote a value only when a suggested command needs shell protection.
|
|
336
|
+
*
|
|
337
|
+
* Suggested commands are meant to be executable, so names with whitespace or
|
|
338
|
+
* apostrophes must survive copy and paste without changing their value.
|
|
339
|
+
*
|
|
340
|
+
* @param {unknown} value - CLI argument value.
|
|
341
|
+
* @returns {string} A shell-safe argument for the displayed command.
|
|
342
|
+
*/
|
|
343
|
+
function quoteCommandArgument(value) {
|
|
344
|
+
let stringValue = String(value);
|
|
345
|
+
if (/^[A-Za-z0-9._:/-]+$/.test(stringValue)) {
|
|
346
|
+
return stringValue;
|
|
268
347
|
}
|
|
269
|
-
return
|
|
348
|
+
return `'${stringValue.replaceAll("'", `'\\''`)}'`;
|
|
270
349
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Recognize both local source labels emitted across supported context shapes.
|
|
353
|
+
*
|
|
354
|
+
* @param {Object} context - Normalized context response.
|
|
355
|
+
* @returns {boolean} Whether follow-up commands must stay in local mode.
|
|
356
|
+
*/
|
|
357
|
+
function isLocalContext(context = {}) {
|
|
358
|
+
return ['local', 'local_workspace'].includes(context.source);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Keep executable suggestions on the source that produced their evidence.
|
|
363
|
+
*
|
|
364
|
+
* Mixed workspaces can contain both a cloud run session and persisted local
|
|
365
|
+
* TDD results. Pinning either source prevents a follow-up command from
|
|
366
|
+
* silently crossing that boundary and describing a different build.
|
|
367
|
+
*
|
|
368
|
+
* @param {string} command - Base CLI command.
|
|
369
|
+
* @param {Object} context - Context that produced the command.
|
|
370
|
+
* @returns {string} Command pinned to its originating source.
|
|
371
|
+
*/
|
|
372
|
+
function appendContextSource(command, context = {}) {
|
|
373
|
+
let source = isLocalContext(context) ? 'local' : 'cloud';
|
|
374
|
+
return `${command} --source ${source}`;
|
|
375
|
+
}
|
|
376
|
+
function buildCompactBuildCommands(context = {}, include = [], cursor = null) {
|
|
377
|
+
let evidence = context.evidence?.items || [];
|
|
378
|
+
let page = context.evidence?.page || {};
|
|
379
|
+
let commands = [];
|
|
380
|
+
let buildTarget = isLocalContext(context) ? 'current' : context.build?.id || null;
|
|
381
|
+
let firstComparison = evidence.find(item => item.type === 'comparison' && item.id);
|
|
382
|
+
let firstNamedEvidence = evidence.find(item => item.screenshot_name);
|
|
383
|
+
if (firstComparison) {
|
|
384
|
+
commands.push({
|
|
385
|
+
label: 'Inspect comparison context',
|
|
386
|
+
command: appendContextSource(`vizzly --json context comparison ${quoteCommandArgument(firstComparison.id)} --agent`, context)
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
if (firstNamedEvidence) {
|
|
390
|
+
let screenshotName = firstNamedEvidence.screenshot_name;
|
|
391
|
+
commands.push({
|
|
392
|
+
label: 'Inspect screenshot history',
|
|
393
|
+
command: appendContextSource(`vizzly --json context screenshot ${quoteCommandArgument(screenshotName)}`, context)
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
if (buildTarget && evidence.length > 0) {
|
|
397
|
+
let cursorFlag = cursor ? ` --cursor ${quoteCommandArgument(cursor)}` : '';
|
|
398
|
+
commands.push({
|
|
399
|
+
label: 'Load raw diff diagnostics',
|
|
400
|
+
command: appendContextSource(`vizzly --json context build ${quoteCommandArgument(buildTarget)} --agent --include diffs${cursorFlag}`, context)
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (buildTarget && page.has_more && page.next_cursor) {
|
|
404
|
+
let includeFlag = include.length > 0 ? ` --include ${include.join(',')}` : '';
|
|
405
|
+
commands.push({
|
|
406
|
+
label: 'Load next evidence page',
|
|
407
|
+
command: appendContextSource(`vizzly --json context build ${quoteCommandArgument(buildTarget)} --agent --cursor ${quoteCommandArgument(page.next_cursor)}${includeFlag}`, context)
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
if (buildTarget) {
|
|
411
|
+
commands.push({
|
|
412
|
+
label: 'Load full build context',
|
|
413
|
+
command: appendContextSource(`vizzly --json context build ${quoteCommandArgument(buildTarget)} --agent --full`, context)
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
return commands;
|
|
294
417
|
}
|
|
295
|
-
function
|
|
418
|
+
function buildCompactBuildPayload(context, include = [], cursor = null) {
|
|
296
419
|
return {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
needs_review: comparisons.filter(comparison => comparison.needs_review).length
|
|
420
|
+
...context,
|
|
421
|
+
resource: 'build_agent_context',
|
|
422
|
+
suggested_commands: buildCompactBuildCommands(context, include, cursor)
|
|
301
423
|
};
|
|
302
424
|
}
|
|
303
|
-
function
|
|
304
|
-
|
|
305
|
-
|
|
425
|
+
function buildCompactComparisonCommands(context = {}, include = []) {
|
|
426
|
+
let commands = [];
|
|
427
|
+
let comparisonId = context.comparison?.id;
|
|
428
|
+
let streams = [['similar_by_fingerprint', 'similar history'], ['recent_by_name', 'recent history']];
|
|
429
|
+
let includeFlag = include.length > 0 ? ` --include ${include.join(',')}` : '';
|
|
430
|
+
for (let [stream, label] of streams) {
|
|
431
|
+
let page = context.history?.[stream]?.page;
|
|
432
|
+
if (!comparisonId || !page?.has_more || !page.next_cursor) {
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
commands.push({
|
|
436
|
+
label: `Load next ${label} page`,
|
|
437
|
+
command: appendContextSource(`vizzly --json context comparison ${quoteCommandArgument(comparisonId)} --agent --cursor ${quoteCommandArgument(page.next_cursor)}${includeFlag}`, context)
|
|
438
|
+
});
|
|
306
439
|
}
|
|
307
|
-
if (
|
|
308
|
-
|
|
440
|
+
if (comparisonId) {
|
|
441
|
+
commands.push({
|
|
442
|
+
label: 'Load full comparison context',
|
|
443
|
+
command: appendContextSource(`vizzly --json context comparison ${quoteCommandArgument(comparisonId)} --agent --full`, context)
|
|
444
|
+
});
|
|
309
445
|
}
|
|
310
|
-
return
|
|
446
|
+
return commands;
|
|
311
447
|
}
|
|
312
|
-
function
|
|
448
|
+
function buildCompactComparisonPayload(context, include = []) {
|
|
313
449
|
return {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
unresolved_comments: status.unresolved_comments || 0
|
|
450
|
+
...context,
|
|
451
|
+
resource: 'comparison_agent_context',
|
|
452
|
+
suggested_commands: buildCompactComparisonCommands(context, include)
|
|
318
453
|
};
|
|
319
454
|
}
|
|
320
|
-
function
|
|
321
|
-
return
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
455
|
+
function formatKnownBoolean(value) {
|
|
456
|
+
if (value === true) return 'yes';
|
|
457
|
+
if (value === false) return 'no';
|
|
458
|
+
return 'unknown';
|
|
459
|
+
}
|
|
460
|
+
function formatCompactPage(collection) {
|
|
461
|
+
let page = collection?.page || {};
|
|
462
|
+
let returned = page.returned ?? collection?.items?.length;
|
|
463
|
+
let total = page.total;
|
|
464
|
+
if (returned == null) return 'unknown';
|
|
465
|
+
if (total == null) return String(returned);
|
|
466
|
+
return `${returned} of ${total}${page.has_more ? ' · more available' : ''}`;
|
|
467
|
+
}
|
|
468
|
+
function displayCompactHeading(output, context, title, status) {
|
|
469
|
+
let colors = output.getColors();
|
|
470
|
+
let tone = getStatusTone(colors, status);
|
|
471
|
+
let organization = context.scope?.organization?.slug || 'unknown';
|
|
472
|
+
let project = context.scope?.project?.slug || 'unknown';
|
|
473
|
+
output.print(` ${colors.bold(title)} ${tone(status.toUpperCase())}`);
|
|
474
|
+
output.print(` ${colors.dim(`@${organization}/${project}`)}`);
|
|
475
|
+
output.blank();
|
|
476
|
+
}
|
|
477
|
+
function displayCompactBuildContext(output, context) {
|
|
478
|
+
output.header('context', 'build');
|
|
479
|
+
let colors = output.getColors();
|
|
480
|
+
let build = context.build || {};
|
|
481
|
+
let displayStatus = build.status || context.status?.state || 'unknown';
|
|
482
|
+
displayCompactHeading(output, context, build.name || build.id || 'unknown build', displayStatus);
|
|
483
|
+
output.labelValue('Attention', formatKnownBoolean(context.status?.needs_review));
|
|
484
|
+
output.labelValue('Evidence', formatCompactPage(context.evidence));
|
|
485
|
+
let items = context.evidence?.items || [];
|
|
486
|
+
if (items.length > 0) {
|
|
487
|
+
output.blank();
|
|
488
|
+
output.print(' Evidence');
|
|
489
|
+
for (let item of items) {
|
|
490
|
+
let result = item.result || item.status || 'unknown';
|
|
491
|
+
let percentage = getComparisonDiffPercentage(item);
|
|
492
|
+
let detail = percentage == null ? '' : ` · ${percentage}% diff`;
|
|
493
|
+
output.print(` ${colors.dim('•')} ${getComparisonName(item)}: ${result}${detail}`);
|
|
327
494
|
}
|
|
328
|
-
}
|
|
495
|
+
}
|
|
496
|
+
if (context.evidence?.page?.has_more) {
|
|
497
|
+
let next = context.suggested_commands?.find(command => command.label === 'Load next evidence page');
|
|
498
|
+
if (next) {
|
|
499
|
+
output.blank();
|
|
500
|
+
output.labelValue('Next', next.command);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
if (context.links?.build_url) {
|
|
504
|
+
output.labelValue('Build URL', context.links.build_url);
|
|
505
|
+
}
|
|
329
506
|
}
|
|
330
|
-
function
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
let
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
let comparisonSummary = summarizeComparisons(comparisons);
|
|
344
|
-
let payload = {
|
|
345
|
-
resource: 'build_agent_context',
|
|
346
|
-
source: context.source || source || 'cloud',
|
|
347
|
-
scope: context.scope || null,
|
|
348
|
-
project: {
|
|
349
|
-
organization: context.scope?.organization?.slug || null,
|
|
350
|
-
slug: context.scope?.project?.slug || null,
|
|
351
|
-
name: context.scope?.project?.name || null,
|
|
352
|
-
visibility: context.scope?.project?.visibility || null
|
|
353
|
-
},
|
|
354
|
-
build: context.build || null,
|
|
355
|
-
baseline: {
|
|
356
|
-
selected: context.baseline?.selected || null,
|
|
357
|
-
selection_reason: context.baseline?.selection_reason || null
|
|
358
|
-
},
|
|
359
|
-
status: formatAgentStatus(context.status),
|
|
360
|
-
summary: formatAgentSummary(context, comparisons),
|
|
361
|
-
evidence,
|
|
362
|
-
links: context.links || {},
|
|
363
|
-
preview: context.preview || null,
|
|
364
|
-
next_actions: buildAgentNextActions(context, comparisonSummary)
|
|
365
|
-
};
|
|
366
|
-
if (includeSet.has('screenshots')) {
|
|
367
|
-
payload.screenshots = context.screenshots || [];
|
|
507
|
+
function displayCompactComparisonContext(output, context) {
|
|
508
|
+
output.header('context', 'comparison');
|
|
509
|
+
let comparison = context.comparison || {};
|
|
510
|
+
let displayState = getComparisonDisplayState(comparison);
|
|
511
|
+
displayCompactHeading(output, context, getComparisonName(comparison), displayState);
|
|
512
|
+
output.labelValue('Images', comparison.diff?.image_url || comparison.analysis?.diff_image_url ? 'baseline/current/diff available' : 'unavailable');
|
|
513
|
+
output.labelValue('Similar history', formatCompactPage(context.history?.similar_by_fingerprint));
|
|
514
|
+
output.labelValue('Recent history', formatCompactPage(context.history?.recent_by_name));
|
|
515
|
+
let similar = context.history?.similar_by_fingerprint?.items || [];
|
|
516
|
+
if (similar.length > 0) {
|
|
517
|
+
output.blank();
|
|
518
|
+
output.print(' Similar Diffs');
|
|
519
|
+
printComparisonList(output, similar);
|
|
368
520
|
}
|
|
369
|
-
|
|
370
|
-
|
|
521
|
+
let recent = context.history?.recent_by_name?.items || [];
|
|
522
|
+
if (recent.length > 0) {
|
|
523
|
+
output.blank();
|
|
524
|
+
output.print(' Recent Diffs');
|
|
525
|
+
printComparisonList(output, recent);
|
|
526
|
+
}
|
|
527
|
+
if (context.links?.comparison_url) {
|
|
528
|
+
output.labelValue('Comparison URL', context.links.comparison_url);
|
|
371
529
|
}
|
|
372
|
-
return payload;
|
|
373
530
|
}
|
|
374
531
|
function getBuildCommentsCount(context = {}) {
|
|
375
532
|
if (Array.isArray(context.comments?.build)) {
|
|
@@ -396,12 +553,17 @@ function formatNeedsReview(status = {}) {
|
|
|
396
553
|
if (!status || status.needs_review == null) {
|
|
397
554
|
return null;
|
|
398
555
|
}
|
|
399
|
-
let pending = status.pending_comparisons || 0;
|
|
400
|
-
let unresolved = status.unresolved_comments || 0;
|
|
401
556
|
if (!status.needs_review) {
|
|
402
557
|
return 'no';
|
|
403
558
|
}
|
|
404
|
-
|
|
559
|
+
let details = [];
|
|
560
|
+
if (status.pending_comparisons != null) {
|
|
561
|
+
details.push(`${status.pending_comparisons} comparisons`);
|
|
562
|
+
}
|
|
563
|
+
if (status.unresolved_comments != null) {
|
|
564
|
+
details.push(`${status.unresolved_comments} unresolved comments`);
|
|
565
|
+
}
|
|
566
|
+
return details.length > 0 ? `yes · ${details.join(' · ')}` : 'yes';
|
|
405
567
|
}
|
|
406
568
|
function formatConfirmedRegionLabels(regions = []) {
|
|
407
569
|
return regions.map(region => region.label).filter(Boolean).slice(0, 3).join(' · ');
|
|
@@ -436,98 +598,132 @@ function printComparisonList(output, comparisons = [], {
|
|
|
436
598
|
}
|
|
437
599
|
}
|
|
438
600
|
}
|
|
601
|
+
function getGroupDisplayState(group = {}) {
|
|
602
|
+
let aggregate = group.aggregate_status || {};
|
|
603
|
+
if ((aggregate.failed_count ?? 0) > 0) return 'failed';
|
|
604
|
+
if (aggregate.has_rejected === true) return 'rejected';
|
|
605
|
+
if (aggregate.has_changes === true) return 'changed';
|
|
606
|
+
if (aggregate.has_new === true) return 'new';
|
|
607
|
+
if (aggregate.needs_review === true) return 'needs review';
|
|
608
|
+
if (aggregate.all_approved === true) return 'approved';
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
function formatViewport(viewport) {
|
|
612
|
+
if (!viewport) {
|
|
613
|
+
return null;
|
|
614
|
+
}
|
|
615
|
+
return `${viewport.width ?? '?'}×${viewport.height ?? '?'}`;
|
|
616
|
+
}
|
|
617
|
+
function printScreenshotGroups(output, groups = [], {
|
|
618
|
+
limit = 8
|
|
619
|
+
} = {}) {
|
|
620
|
+
let colors = output.getColors();
|
|
621
|
+
let visibleGroups = groups.slice(0, limit);
|
|
622
|
+
output.print(' Screenshot groups');
|
|
623
|
+
for (let group of visibleGroups) {
|
|
624
|
+
let displayState = getGroupDisplayState(group);
|
|
625
|
+
let tone = getStatusTone(colors, displayState);
|
|
626
|
+
let aggregate = group.aggregate_status || {};
|
|
627
|
+
let details = [];
|
|
628
|
+
if (group.variant_count != null) {
|
|
629
|
+
details.push(`${group.variant_count} variants`);
|
|
630
|
+
}
|
|
631
|
+
if (aggregate.needs_review_count != null) {
|
|
632
|
+
details.push(`${aggregate.needs_review_count} needs review`);
|
|
633
|
+
}
|
|
634
|
+
if (aggregate.failed_count != null && aggregate.failed_count > 0) {
|
|
635
|
+
details.push(`${aggregate.failed_count} failed`);
|
|
636
|
+
}
|
|
637
|
+
if (aggregate.max_diff_percentage != null) {
|
|
638
|
+
details.push(`${aggregate.max_diff_percentage}% max diff`);
|
|
639
|
+
}
|
|
640
|
+
output.print(` ${colors.bold(group.name)}${displayState ? ` ${tone(displayState.toUpperCase())}` : ''}`);
|
|
641
|
+
if (details.length > 0) {
|
|
642
|
+
output.print(` ${colors.dim(details.join(' · '))}`);
|
|
643
|
+
}
|
|
644
|
+
for (let variant of group.variants.slice(0, 3)) {
|
|
645
|
+
let variantDetails = [variant.review_state, variant.browser, formatViewport(variant.viewport)].filter(Boolean);
|
|
646
|
+
output.print(` ${getStatusTone(colors, variant.result)((variant.result || 'unknown').toUpperCase())}${variantDetails.length > 0 ? ` · ${colors.dim(variantDetails.join(' · '))}` : ''}`);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (groups.length > visibleGroups.length) {
|
|
650
|
+
output.print(` ${colors.dim(`...${groups.length - visibleGroups.length} more groups`)}`);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function printFailedCaptures(output, captures = []) {
|
|
654
|
+
let colors = output.getColors();
|
|
655
|
+
output.print(' Failed captures');
|
|
656
|
+
for (let capture of captures.slice(0, 8)) {
|
|
657
|
+
let details = [capture.error_message, capture.browser, formatViewport(capture.viewport), capture.screenshot.url].filter(Boolean);
|
|
658
|
+
output.print(` ${colors.bold(capture.name)} ${colors.brand.error('FAILED')}`);
|
|
659
|
+
if (details.length > 0) {
|
|
660
|
+
output.print(` ${colors.dim(details.join(' · '))}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function formatReviewSummary(review = {}) {
|
|
665
|
+
let details = [review.pending != null ? `${review.pending} pending` : null, review.approved != null ? `${review.approved} approved` : null, review.rejected != null ? `${review.rejected} rejected` : null].filter(Boolean);
|
|
666
|
+
return details.length > 0 ? details.join(' · ') : null;
|
|
667
|
+
}
|
|
439
668
|
function displayBuildContext(output, context) {
|
|
440
669
|
output.header('context', 'build');
|
|
670
|
+
let normalizedContext = normalizeBuildContext(context);
|
|
441
671
|
let colors = output.getColors();
|
|
442
|
-
let
|
|
443
|
-
let
|
|
444
|
-
let
|
|
445
|
-
let
|
|
446
|
-
let
|
|
447
|
-
let
|
|
448
|
-
let
|
|
449
|
-
|
|
450
|
-
|
|
672
|
+
let build = normalizedContext.build || {};
|
|
673
|
+
let buildTone = getStatusTone(colors, build.status);
|
|
674
|
+
let comparisons = normalizedContext.comparisons;
|
|
675
|
+
let groups = normalizedContext.groups;
|
|
676
|
+
let screenshots = normalizedContext.screenshots || [];
|
|
677
|
+
let failedCaptures = normalizedContext.failed_captures;
|
|
678
|
+
let reviewSummary = formatReviewSummary(normalizedContext.summary?.review);
|
|
679
|
+
let commentsSummary = normalizedContext.summary?.comments || {};
|
|
680
|
+
let needsReview = formatNeedsReview(normalizedContext.status);
|
|
681
|
+
let baseline = normalizedContext.baseline?.selected || null;
|
|
682
|
+
let comparisonCount = normalizedContext.summary?.comparisons?.total ?? normalizedContext.total_comparisons ?? (Array.isArray(context.comparisons) ? comparisons.length : null);
|
|
683
|
+
let screenshotCount = normalizedContext.summary?.screenshots?.total ?? normalizedContext.screenshot_count ?? (Array.isArray(context.screenshots) ? screenshots.length : null);
|
|
684
|
+
output.print(` ${colors.bold(build.name || build.id || 'unknown build')} ${buildTone((build.status || 'unknown').toUpperCase())}`);
|
|
685
|
+
output.print(` ${colors.dim(`@${normalizedContext.scope?.organization?.slug || 'unknown'}/${normalizedContext.scope?.project?.slug || 'unknown'}`)}`);
|
|
451
686
|
output.blank();
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
687
|
+
if (comparisonCount != null) {
|
|
688
|
+
output.labelValue('Comparisons', String(comparisonCount));
|
|
689
|
+
}
|
|
690
|
+
if (groups.length > 0) {
|
|
691
|
+
output.labelValue('Screenshot Groups', String(groups.length));
|
|
692
|
+
}
|
|
693
|
+
if (screenshotCount != null) {
|
|
694
|
+
output.labelValue('Screenshots', String(screenshotCount));
|
|
455
695
|
}
|
|
456
696
|
if (baseline) {
|
|
457
|
-
output.labelValue('Baseline', `${baseline.name || baseline.id || 'selected'}${
|
|
697
|
+
output.labelValue('Baseline', `${baseline.name || baseline.id || 'selected'}${normalizedContext.baseline.selection_reason ? ` · ${normalizedContext.baseline.selection_reason}` : ''}`);
|
|
458
698
|
}
|
|
459
699
|
if (needsReview) {
|
|
460
700
|
output.labelValue('Needs Review', needsReview);
|
|
461
701
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
if (context.preview) {
|
|
465
|
-
let previewUrl = context.preview.preview_url || context.preview.url;
|
|
466
|
-
output.labelValue('Preview', `${context.preview.status || 'unknown'}${previewUrl ? ' · available' : ''}`);
|
|
467
|
-
}
|
|
468
|
-
if (context.links?.build_url) {
|
|
469
|
-
output.labelValue('Build URL', context.links.build_url);
|
|
470
|
-
}
|
|
471
|
-
if (comparisons.length > 0) {
|
|
472
|
-
output.blank();
|
|
473
|
-
output.print(' Comparisons');
|
|
474
|
-
printComparisonList(output, comparisons);
|
|
702
|
+
if (reviewSummary) {
|
|
703
|
+
output.labelValue('Review', reviewSummary);
|
|
475
704
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
let
|
|
479
|
-
let
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
let baseline = context.baseline?.selected;
|
|
483
|
-
let lines = [`# Vizzly Visual Context: ${context.build?.name || context.build?.id || 'Build'}`, '', `Project: ${context.scope?.organization?.slug || 'unknown'}/${context.scope?.project?.slug || 'unknown'}`, `Build: ${context.build?.id || 'unknown'} (${context.build?.status || 'unknown'})`];
|
|
484
|
-
if (baseline) {
|
|
485
|
-
lines.push(`Approved baseline: ${baseline.name || baseline.id || 'selected'} (${baseline.approval_status || 'unknown'})`);
|
|
705
|
+
let buildComments = commentsSummary.build ?? (Array.isArray(normalizedContext.comments?.build) ? getBuildCommentsCount(normalizedContext) : null);
|
|
706
|
+
let screenshotComments = commentsSummary.screenshot ?? (Number.isInteger(normalizedContext.comments?.screenshot_count) ? getScreenshotCommentsCount(normalizedContext) : null);
|
|
707
|
+
let assignments = Array.isArray(normalizedContext.review?.assignments) ? getReviewAssignmentsCount(normalizedContext) : null;
|
|
708
|
+
let memory = [buildComments != null ? `${buildComments} build comments` : null, screenshotComments != null ? `${screenshotComments} screenshot comments` : null, assignments != null ? `${assignments} assignments` : null].filter(Boolean);
|
|
709
|
+
if (memory.length > 0) {
|
|
710
|
+
output.labelValue('Memory', memory.join(' · '));
|
|
486
711
|
}
|
|
487
|
-
if (
|
|
488
|
-
|
|
712
|
+
if (normalizedContext.preview) {
|
|
713
|
+
let previewUrl = normalizedContext.preview.preview_url || normalizedContext.preview.url;
|
|
714
|
+
output.labelValue('Preview', `${normalizedContext.preview.status || 'unknown'}${previewUrl ? ' · available' : ''}`);
|
|
489
715
|
}
|
|
490
|
-
if (
|
|
491
|
-
|
|
716
|
+
if (normalizedContext.links?.build_url) {
|
|
717
|
+
output.labelValue('Build URL', normalizedContext.links.build_url);
|
|
492
718
|
}
|
|
493
|
-
if (
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
if (context.links?.report_url) {
|
|
497
|
-
lines.push(`Report: ${context.links.report_url}`);
|
|
498
|
-
}
|
|
499
|
-
lines.push('');
|
|
500
|
-
lines.push('## Diff Summary');
|
|
501
|
-
lines.push(`- Total comparisons: ${comparisons.length}`);
|
|
502
|
-
lines.push(`- Changed: ${changed.length}`);
|
|
503
|
-
lines.push(`- New: ${fresh.length}`);
|
|
504
|
-
lines.push(`- Needs review: ${needsReview.length}`);
|
|
505
|
-
if (changed.length > 0 || fresh.length > 0) {
|
|
506
|
-
lines.push('');
|
|
507
|
-
lines.push('## Evidence To Inspect');
|
|
508
|
-
for (let comparison of [...changed, ...fresh].slice(0, 10)) {
|
|
509
|
-
let diffPercentage = getComparisonDiffPercentage(comparison);
|
|
510
|
-
let detail = diffPercentage == null ? '' : ` · ${diffPercentage}% diff`;
|
|
511
|
-
let diffUrl = comparison.diff?.image_url || comparison.analysis?.diff_image_url;
|
|
512
|
-
lines.push(`- ${getComparisonName(comparison)}: ${getComparisonDisplayState(comparison)}${detail}`);
|
|
513
|
-
if (diffUrl) {
|
|
514
|
-
lines.push(` Diff: ${diffUrl}`);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
719
|
+
if (groups.length > 0) {
|
|
720
|
+
output.blank();
|
|
721
|
+
printScreenshotGroups(output, groups);
|
|
517
722
|
}
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
for (let comparison of comparisons.slice(0, 10)) {
|
|
522
|
-
lines.push(`- ${getComparisonName(comparison)}: ${getComparisonDisplayState(comparison)}`);
|
|
523
|
-
}
|
|
524
|
-
if (comparisons.length > 10) {
|
|
525
|
-
lines.push(`- ...${comparisons.length - 10} more`);
|
|
526
|
-
}
|
|
723
|
+
if (failedCaptures.length > 0) {
|
|
724
|
+
output.blank();
|
|
725
|
+
printFailedCaptures(output, failedCaptures);
|
|
527
726
|
}
|
|
528
|
-
lines.push('');
|
|
529
|
-
lines.push('Use this as reviewed UI context. Treat approved baselines as visual truth, inspect meaningful diffs, and leave approval decisions to humans.');
|
|
530
|
-
return lines.join('\n');
|
|
531
727
|
}
|
|
532
728
|
function countScreenshotCommentEntries(groups = []) {
|
|
533
729
|
return groups.reduce((total, group) => total + (group.comments?.length || 0), 0);
|
|
@@ -631,14 +827,23 @@ export async function contextBuildCommand(buildId, options = {}, globalOptions =
|
|
|
631
827
|
return;
|
|
632
828
|
}
|
|
633
829
|
let resolvedBuildId = resolveBuildContextId(buildId, runtime, deps);
|
|
830
|
+
let {
|
|
831
|
+
compact,
|
|
832
|
+
details,
|
|
833
|
+
include,
|
|
834
|
+
query
|
|
835
|
+
} = buildCompactContextRequest(options, globalOptions);
|
|
634
836
|
output.startSpinner('Fetching build context...');
|
|
635
|
-
let context = await runtime.provider.getBuildContext(resolvedBuildId);
|
|
837
|
+
let context = await runtime.provider.getBuildContext(resolvedBuildId, query);
|
|
636
838
|
output.stopSpinner();
|
|
637
|
-
if (
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
839
|
+
if (compact) {
|
|
840
|
+
let compactContext = validateCompactContext(context, 'build');
|
|
841
|
+
let payload = validateCompactOutputSize(buildCompactBuildPayload(compactContext, include, options.cursor), 'build', details);
|
|
842
|
+
if (globalOptions.json) {
|
|
843
|
+
output.data(payload);
|
|
844
|
+
} else {
|
|
845
|
+
displayCompactBuildContext(output, payload);
|
|
846
|
+
}
|
|
642
847
|
output.cleanup();
|
|
643
848
|
return;
|
|
644
849
|
}
|
|
@@ -647,11 +852,6 @@ export async function contextBuildCommand(buildId, options = {}, globalOptions =
|
|
|
647
852
|
output.cleanup();
|
|
648
853
|
return;
|
|
649
854
|
}
|
|
650
|
-
if (options.agent) {
|
|
651
|
-
output.print(formatAgentBuildContext(context));
|
|
652
|
-
output.cleanup();
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
855
|
displayBuildContext(output, context);
|
|
656
856
|
output.cleanup();
|
|
657
857
|
} catch (error) {
|
|
@@ -680,14 +880,26 @@ export async function contextComparisonCommand(comparisonId, options = {}, globa
|
|
|
680
880
|
if (!runtime) {
|
|
681
881
|
return;
|
|
682
882
|
}
|
|
683
|
-
let
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
883
|
+
let {
|
|
884
|
+
compact,
|
|
885
|
+
details,
|
|
886
|
+
include,
|
|
887
|
+
query
|
|
888
|
+
} = buildCompactContextRequest(options, globalOptions);
|
|
688
889
|
output.startSpinner('Fetching comparison context...');
|
|
689
890
|
let context = await runtime.provider.getComparisonContext(comparisonId, query);
|
|
690
891
|
output.stopSpinner();
|
|
892
|
+
if (compact) {
|
|
893
|
+
let compactContext = validateCompactContext(context, 'comparison');
|
|
894
|
+
let payload = validateCompactOutputSize(buildCompactComparisonPayload(compactContext, include), 'comparison', details);
|
|
895
|
+
if (globalOptions.json) {
|
|
896
|
+
output.data(payload);
|
|
897
|
+
} else {
|
|
898
|
+
displayCompactComparisonContext(output, payload);
|
|
899
|
+
}
|
|
900
|
+
output.cleanup();
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
691
903
|
if (globalOptions.json) {
|
|
692
904
|
output.data(context);
|
|
693
905
|
output.cleanup();
|
|
@@ -824,20 +1036,14 @@ export async function contextReviewQueueCommand(options = {}, globalOptions = {}
|
|
|
824
1036
|
export function validateContextBuildOptions(_options = {}) {
|
|
825
1037
|
let errors = validateSourceOption(_options.source);
|
|
826
1038
|
errors.push(...validateIncludeOption(_options.include));
|
|
1039
|
+
errors.push(...validateCursor(_options.cursor));
|
|
827
1040
|
return errors;
|
|
828
1041
|
}
|
|
829
1042
|
export function validateContextComparisonOptions(options = {}) {
|
|
830
1043
|
let errors = [];
|
|
831
1044
|
errors.push(...validateSourceOption(options.source));
|
|
832
|
-
errors.push(...
|
|
833
|
-
|
|
834
|
-
}));
|
|
835
|
-
errors.push(...validateLimitRange(options.recentLimit, '--recent-limit', {
|
|
836
|
-
max: 50
|
|
837
|
-
}));
|
|
838
|
-
errors.push(...validateLimitRange(options.windowSize, '--window-size', {
|
|
839
|
-
max: 50
|
|
840
|
-
}));
|
|
1045
|
+
errors.push(...validateIncludeOption(options.include));
|
|
1046
|
+
errors.push(...validateCursor(options.cursor));
|
|
841
1047
|
return errors;
|
|
842
1048
|
}
|
|
843
1049
|
export function validateContextScreenshotOptions(options = {}) {
|