@haystackeditor/cli 0.15.19 → 0.15.21
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 +100 -1
- package/dist/assets/skills/map-your-system.md +9 -4
- package/dist/commands/ask.js +2 -2
- package/dist/commands/config.d.ts +9 -0
- package/dist/commands/config.js +46 -14
- package/dist/commands/dismiss.d.ts +15 -3
- package/dist/commands/dismiss.js +66 -116
- package/dist/commands/mcp.js +96 -1
- package/dist/commands/policy.js +31 -12
- package/dist/commands/pr-status.d.ts +125 -2
- package/dist/commands/pr-status.js +88 -73
- package/dist/commands/pr.d.ts +1 -7
- package/dist/commands/pr.js +10 -21
- package/dist/commands/request-review.d.ts +12 -1
- package/dist/commands/request-review.js +42 -65
- package/dist/commands/review.d.ts +1 -0
- package/dist/commands/review.js +58 -60
- package/dist/commands/setup.js +4 -1
- package/dist/commands/submit.d.ts +3 -0
- package/dist/commands/submit.js +178 -38
- package/dist/commands/system-map.d.ts +42 -0
- package/dist/commands/system-map.js +228 -0
- package/dist/commands/tokens.js +3 -2
- package/dist/commands/traces.js +2 -2
- package/dist/commands/triage.d.ts +1 -6
- package/dist/commands/triage.js +51 -55
- package/dist/commands/verify-core.d.ts +424 -0
- package/dist/commands/verify-core.js +753 -0
- package/dist/commands/verify-mcp.d.ts +8 -0
- package/dist/commands/verify-mcp.js +458 -0
- package/dist/commands/verify-ops.d.ts +158 -0
- package/dist/commands/verify-ops.js +1148 -0
- package/dist/commands/verify-reseal.d.ts +9 -0
- package/dist/commands/verify-reseal.js +148 -0
- package/dist/commands/verify-sandboxes.d.ts +95 -0
- package/dist/commands/verify-sandboxes.js +352 -0
- package/dist/commands/verify.d.ts +11 -0
- package/dist/commands/verify.js +97 -0
- package/dist/index.js +343 -20
- package/dist/schema.d.ts +4 -0
- package/dist/schema.js +4 -0
- package/dist/states.d.ts +29 -0
- package/dist/states.js +46 -0
- package/dist/triage/runner.d.ts +7 -0
- package/dist/triage/runner.js +2 -2
- package/dist/types.d.ts +54 -0
- package/dist/types.js +15 -0
- package/dist/utils/action-output.d.ts +24 -0
- package/dist/utils/action-output.js +26 -0
- package/dist/utils/analysis-api.d.ts +11 -0
- package/dist/utils/analysis-api.js +29 -3
- package/dist/utils/auth.js +7 -0
- package/dist/utils/git.d.ts +4 -1
- package/dist/utils/git.js +17 -7
- package/dist/utils/haystack-api.d.ts +15 -0
- package/dist/utils/haystack-api.js +58 -7
- package/dist/utils/pr-ref.d.ts +27 -0
- package/dist/utils/pr-ref.js +56 -0
- package/package.json +2 -2
- package/schemas/action.v1.json +22 -0
- package/schemas/error.v1.json +14 -0
- package/schemas/pr-status.v1.json +61 -0
- package/schemas/pr.v1.json +80 -17
- package/schemas/setup.v1.json +66 -12
- package/schemas/submit.v1.json +88 -0
- package/schemas/triage.v1.json +71 -18
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { setVerifyToken, verifyAuthHeaders } from './verify-core.js';
|
|
6
|
+
const DEFAULT_CONTROL_PLANE = 'http://127.0.0.1:3000';
|
|
7
|
+
const SPECIMENS = new Set(['all', 'posthog-js-478', 'analytics-js-255']);
|
|
8
|
+
function openResults(url) {
|
|
9
|
+
const platform = process.platform;
|
|
10
|
+
const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
11
|
+
const args = platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
12
|
+
const child = spawn(command, args, { detached: true, stdio: 'ignore' });
|
|
13
|
+
child.unref();
|
|
14
|
+
}
|
|
15
|
+
async function readIntentTranscript(path) {
|
|
16
|
+
if (!path)
|
|
17
|
+
return undefined;
|
|
18
|
+
const absolutePath = resolve(path);
|
|
19
|
+
try {
|
|
20
|
+
return await readFile(absolutePath, 'utf8');
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
24
|
+
throw new Error(`Could not read intent transcript at ${absolutePath}: ${message}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export async function verifyCommand(specimenArg, options) {
|
|
28
|
+
const specimen = specimenArg || 'all';
|
|
29
|
+
if (!SPECIMENS.has(specimen)) {
|
|
30
|
+
throw new Error(`Unknown or unreproduced specimen "${specimen}". Choose all, posthog-js-478, or analytics-js-255.`);
|
|
31
|
+
}
|
|
32
|
+
setVerifyToken(options.token);
|
|
33
|
+
const server = (options.server || process.env.HAYSTACK_VERIFY_SERVER || DEFAULT_CONTROL_PLANE)
|
|
34
|
+
.replace(/\/+$/, '');
|
|
35
|
+
const intentTranscript = await readIntentTranscript(options.intentTranscript);
|
|
36
|
+
if (!options.json) {
|
|
37
|
+
console.error(chalk.dim('Preparing the cloud verification run…'));
|
|
38
|
+
}
|
|
39
|
+
let response;
|
|
40
|
+
try {
|
|
41
|
+
response = await fetch(`${server}/api/cloud-verifier/runs`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: { 'content-type': 'application/json', ...verifyAuthHeaders() },
|
|
44
|
+
body: JSON.stringify({
|
|
45
|
+
specimen,
|
|
46
|
+
repoPath: options.repo ? resolve(options.repo) : undefined,
|
|
47
|
+
baseRef: options.base,
|
|
48
|
+
headRef: options.head,
|
|
49
|
+
intentTranscript,
|
|
50
|
+
}),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
55
|
+
throw new Error(`Could not reach the Haystack verifier at ${server}. Start the demo control plane with \`pnpm dev\` and retry. ${message}`);
|
|
56
|
+
}
|
|
57
|
+
const raw = await response.text();
|
|
58
|
+
if (response.status === 401) {
|
|
59
|
+
throw new Error(`Control plane at ${server} requires a token (HTTP 401). Pass --token or set HAYSTACK_VERIFY_TOKEN.`);
|
|
60
|
+
}
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
let reason = raw;
|
|
63
|
+
try {
|
|
64
|
+
reason = JSON.parse(raw).error || raw;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Preserve the response body when it is not JSON.
|
|
68
|
+
}
|
|
69
|
+
throw new Error(`Verifier rejected the run (${response.status}): ${reason}`);
|
|
70
|
+
}
|
|
71
|
+
let started;
|
|
72
|
+
try {
|
|
73
|
+
started = JSON.parse(raw);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
throw new Error('Verifier returned an invalid response.');
|
|
77
|
+
}
|
|
78
|
+
if (!started.runId || !started.resultsUrl) {
|
|
79
|
+
throw new Error('Verifier response is missing runId or resultsUrl.');
|
|
80
|
+
}
|
|
81
|
+
if (options.json) {
|
|
82
|
+
process.stdout.write(`${JSON.stringify({
|
|
83
|
+
schema_version: '1.0.0',
|
|
84
|
+
run_id: started.runId,
|
|
85
|
+
status: started.status,
|
|
86
|
+
results_url: started.resultsUrl,
|
|
87
|
+
}, null, 2)}\n`);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log(`${chalk.green('✓')} Verification started: ${chalk.bold(started.runId)}`);
|
|
92
|
+
console.log(` ${chalk.dim('Results:')} ${chalk.cyan(started.resultsUrl)}`);
|
|
93
|
+
}
|
|
94
|
+
if (options.open !== false) {
|
|
95
|
+
openResults(started.resultsUrl);
|
|
96
|
+
}
|
|
97
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,7 @@ import { dirname, join } from 'node:path';
|
|
|
25
25
|
import chalk from 'chalk';
|
|
26
26
|
import { Command, Option } from 'commander';
|
|
27
27
|
import { statusCommand } from './commands/status.js';
|
|
28
|
+
import { withSchema } from './schema.js';
|
|
28
29
|
import { initCommand } from './commands/init.js';
|
|
29
30
|
import { authListCommand, authUseCommand, loginCommand, logoutCommand, whoamiCommand, } from './commands/login.js';
|
|
30
31
|
import { handleAgenticTool, handleAutoMerge, handleAutoFix, handleWaitForReviewers, isAutoMergeEnabled, isAutoFixEnabled } from './commands/config.js';
|
|
@@ -46,6 +47,8 @@ import { setupCommand } from './commands/setup.js';
|
|
|
46
47
|
import { schemaCommand, listSchemas } from './commands/schema-cmd.js';
|
|
47
48
|
import { registerWebhook, listWebhooks, rotateWebhookSecret, setWebhookEnabled, listDeliveries, replayDelivery, } from './commands/webhooks.js';
|
|
48
49
|
import { editRulesCommand, validateRulesCommand } from './commands/rules.js';
|
|
50
|
+
import { systemMapValidateCommand } from './commands/system-map.js';
|
|
51
|
+
import { verifyCommand } from './commands/verify.js';
|
|
49
52
|
import { headlessLoginCommand, headlessLogoutCommand, listTokensCommand, revokeTokenCommand, } from './commands/tokens.js';
|
|
50
53
|
// `mcp` is imported lazily inside its command action (below), NOT at the top level.
|
|
51
54
|
// It is the only module that pulls in `@modelcontextprotocol/sdk`; keeping it out of
|
|
@@ -53,12 +56,19 @@ import { headlessLoginCommand, headlessLogoutCommand, listTokensCommand, revokeT
|
|
|
53
56
|
// like `triage`/`submit`. (0.15.12 shipped `mcp.js` but published its package.json
|
|
54
57
|
// WITHOUT `@modelcontextprotocol/sdk`, so the eager import here hard-crashed every
|
|
55
58
|
// command at startup with ERR_MODULE_NOT_FOUND.)
|
|
56
|
-
async function runPublicCommand(action) {
|
|
59
|
+
async function runPublicCommand(action, json) {
|
|
57
60
|
try {
|
|
58
61
|
await action();
|
|
59
62
|
}
|
|
60
63
|
catch (err) {
|
|
61
|
-
|
|
64
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
65
|
+
// In --json mode stdout must still carry one parseable document — emit the
|
|
66
|
+
// versioned error envelope (`haystack schema error`) instead of leaving
|
|
67
|
+
// stdout empty with prose on stderr.
|
|
68
|
+
if (json) {
|
|
69
|
+
process.stdout.write(JSON.stringify(withSchema('error', { status: 'error', error: message }), null, 2) + '\n');
|
|
70
|
+
}
|
|
71
|
+
console.error(chalk.red(message));
|
|
62
72
|
process.exitCode = 1;
|
|
63
73
|
}
|
|
64
74
|
}
|
|
@@ -112,7 +122,7 @@ program
|
|
|
112
122
|
.description('Onboarding wizard — scan repos for rules/policies and write Haystack config')
|
|
113
123
|
.option('--json', 'Agent mode: emit NDJSON events on stdout, read answers on stdin (no TTY prompts)')
|
|
114
124
|
.option('--repo <repo...>', 'Repo(s) to configure (owner/name); skips the repo picker')
|
|
115
|
-
.option('-y, --yes', 'Accept defaults non-interactively (keep all discovered items, confirm write)')
|
|
125
|
+
.option('-y, --yes', 'Accept defaults non-interactively (keep all discovered items, confirm write, ENABLE auto-merge — pass --no-auto-merge to opt out)')
|
|
116
126
|
// Defining --no-auto-merge also accepts --auto-merge; default is "ask" unless
|
|
117
127
|
// one is explicitly passed (detected via getOptionValueSource below).
|
|
118
128
|
.option('--no-auto-merge', 'Set auto-merge in the written config (--auto-merge / --no-auto-merge; omit to be asked)')
|
|
@@ -155,6 +165,260 @@ program
|
|
|
155
165
|
.command('status')
|
|
156
166
|
.description('Check if .haystack.json exists and is valid')
|
|
157
167
|
.action(statusCommand);
|
|
168
|
+
const verify = program
|
|
169
|
+
.command('verify')
|
|
170
|
+
.description('Verify a change in matched cloud universes, or bisect a historical regression')
|
|
171
|
+
.argument('[specimen]', 'Launch specimen to run (default: all)')
|
|
172
|
+
.option('--repo <path>', 'Repository to analyze (defaults to the selected launch specimen)')
|
|
173
|
+
.option('--base <ref>', 'Base revision for a repository run')
|
|
174
|
+
.option('--head <ref>', 'Head revision for a repository run')
|
|
175
|
+
.option('--intent-transcript <file>', 'Coding-agent transcript used as intent evidence')
|
|
176
|
+
.option('--server <url>', 'Verifier control-plane URL (default: http://127.0.0.1:3000)')
|
|
177
|
+
.option('--token <token>', 'Bearer token for a control plane that requires auth (or HAYSTACK_VERIFY_TOKEN)')
|
|
178
|
+
.option('--no-open', 'Do not open the live results page')
|
|
179
|
+
.option('--json', 'Machine-readable run id, status, and results URL')
|
|
180
|
+
.addHelpText('after', `
|
|
181
|
+
Launch specimens:
|
|
182
|
+
posthog-js-478 Replay PostHog/posthog-js PR #478 in matched browser environments (latent-issue mode)
|
|
183
|
+
analytics-js-255 Identity routing regression with sandbox bisect
|
|
184
|
+
all Run each reproduced specimen as an isolated chapter (default)
|
|
185
|
+
|
|
186
|
+
The local control plane must be running. From the Haystack repository:
|
|
187
|
+
pnpm dev
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
haystack verify posthog-js-478 # start a run, then:
|
|
191
|
+
haystack verify watch # block until it finishes (exit 1 = tests failed)
|
|
192
|
+
haystack verify status # one-screen verdicts + live environment URLs
|
|
193
|
+
haystack verify findings # every problem found, with the readings that moved
|
|
194
|
+
haystack verify show latest 3 # one test in full: readings, timeline, SDK log
|
|
195
|
+
haystack verify diff latest 3 # base-vs-head screenshot differ (1/2/3 views)
|
|
196
|
+
haystack verify open latest 3 --live # open a failing test's live environment
|
|
197
|
+
haystack verify cleanup latest 3 # retained-state deletion/reference status
|
|
198
|
+
haystack verify refork latest 3 attempt:fix-2 # checkpoint a durable child
|
|
199
|
+
haystack verify shell latest 3 # terminal into that live universe
|
|
200
|
+
haystack verify artifacts latest # pull screenshots / DOM / video locally
|
|
201
|
+
haystack verify sandboxes --kill-stale # reclaim E2B capacity from superseded runs
|
|
202
|
+
haystack verify reseal <specimen> # rebuild a sealed bundle + restart control plane
|
|
203
|
+
`)
|
|
204
|
+
.action((specimen, options) => runPublicCommand(() => verifyCommand(specimen, options), options.json));
|
|
205
|
+
const verifyRunArg = 'Run id, chapter id (newest run of that chapter), or "latest" (default)';
|
|
206
|
+
verify
|
|
207
|
+
.command('status')
|
|
208
|
+
.description('One screen for a run: per-test verdicts, counts, live environments and expiry')
|
|
209
|
+
.argument('[run]', verifyRunArg)
|
|
210
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
211
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
212
|
+
.option('--json', 'Machine-readable summary')
|
|
213
|
+
.action(async (run, _options, cmd) => {
|
|
214
|
+
// optsWithGlobals: the parent `verify` command also declares --json/--server
|
|
215
|
+
// and commander's default parsing lets it consume those flags even when
|
|
216
|
+
// they appear after the subcommand; the merged view (self wins) restores them.
|
|
217
|
+
const options = cmd.optsWithGlobals();
|
|
218
|
+
const { verifyStatusCommand } = await import('./commands/verify-ops.js');
|
|
219
|
+
return runPublicCommand(() => verifyStatusCommand(run, options), options.json);
|
|
220
|
+
});
|
|
221
|
+
verify
|
|
222
|
+
.command('watch')
|
|
223
|
+
.description('Follow a run until it finishes; exit 0 all passed, 1 tests failed, 2 run/infra broke')
|
|
224
|
+
.argument('[run]', verifyRunArg)
|
|
225
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
226
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
227
|
+
.option('--interval <seconds>', 'Poll interval (default 3)')
|
|
228
|
+
.option('--timeout <minutes>', 'Give up after this long (default: wait forever)')
|
|
229
|
+
.option('--json', 'Suppress streaming; print the final summary as JSON')
|
|
230
|
+
.action(async (run, _options, cmd) => {
|
|
231
|
+
const options = cmd.optsWithGlobals();
|
|
232
|
+
const { verifyWatchCommand } = await import('./commands/verify-ops.js');
|
|
233
|
+
return runPublicCommand(() => verifyWatchCommand(run, options), options.json);
|
|
234
|
+
});
|
|
235
|
+
verify
|
|
236
|
+
.command('list')
|
|
237
|
+
.description('Runs newest first: chapter, verdict counts, age (control plane, else disk)')
|
|
238
|
+
.option('--limit <n>', 'Max runs to show (default 20)')
|
|
239
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
240
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
241
|
+
.option('--json', 'Machine-readable list')
|
|
242
|
+
.action(async (_options, cmd) => {
|
|
243
|
+
const options = cmd.optsWithGlobals();
|
|
244
|
+
const { verifyListCommand } = await import('./commands/verify-ops.js');
|
|
245
|
+
return runPublicCommand(() => verifyListCommand(options), options.json);
|
|
246
|
+
});
|
|
247
|
+
verify
|
|
248
|
+
.command('findings')
|
|
249
|
+
.description('Every problem the run found, in one screen: what failed, which readings moved, live links')
|
|
250
|
+
.argument('[run]', verifyRunArg)
|
|
251
|
+
.option('--full', 'Print the judge\'s full explanation for each finding')
|
|
252
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
253
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
254
|
+
.option('--json', 'Machine-readable findings')
|
|
255
|
+
.action(async (run, _options, cmd) => {
|
|
256
|
+
const options = cmd.optsWithGlobals();
|
|
257
|
+
const { verifyFindingsCommand } = await import('./commands/verify-ops.js');
|
|
258
|
+
return runPublicCommand(() => verifyFindingsCommand(run, options), options.json);
|
|
259
|
+
});
|
|
260
|
+
verify
|
|
261
|
+
.command('open')
|
|
262
|
+
.description('Open a run artifact (screenshot/video/DOM) or a failing test\'s live environment')
|
|
263
|
+
.argument('<run>', verifyRunArg)
|
|
264
|
+
.argument('<target>', 'Test selector (ordinal/title), or an artifact file name (….png/.webm/.html)')
|
|
265
|
+
.option('--live', 'Open the test\'s live environment URL instead of an artifact')
|
|
266
|
+
.option('--side <side>', 'base or head (default head)')
|
|
267
|
+
.option('--step <n>', 'Open the screenshot captured after replay step n')
|
|
268
|
+
.option('--kind <kind>', 'screenshot | video | dom (default: video, else last screenshot)')
|
|
269
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
270
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
271
|
+
.action(async (run, target, _options, cmd) => {
|
|
272
|
+
const options = cmd.optsWithGlobals();
|
|
273
|
+
const { verifyOpenCommand } = await import('./commands/verify-ops.js');
|
|
274
|
+
return runPublicCommand(() => verifyOpenCommand(run, target, options));
|
|
275
|
+
});
|
|
276
|
+
verify
|
|
277
|
+
.command('diff')
|
|
278
|
+
.description('Open a base-vs-head screenshot differ for one test (1 what changed / 2 before / 3 after)')
|
|
279
|
+
.argument('<run>', verifyRunArg)
|
|
280
|
+
.argument('<cell>', 'Test selector: ordinal, cell id, or title substring')
|
|
281
|
+
.option('--step <n>', 'Compare only the screenshot captured after replay step n')
|
|
282
|
+
.option('--out <dir>', 'Where to build the diff page (default: a temp directory)')
|
|
283
|
+
.option('--no-open', 'Build the page but do not open it')
|
|
284
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
285
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
286
|
+
.option('--json', 'Machine-readable result (implies --no-open)')
|
|
287
|
+
.action(async (run, cell, _options, cmd) => {
|
|
288
|
+
const options = { ...cmd.optsWithGlobals(), ...cmd.opts() };
|
|
289
|
+
const { verifyDiffCommand } = await import('./commands/verify-ops.js');
|
|
290
|
+
return runPublicCommand(() => verifyDiffCommand(run, cell, options), options.json);
|
|
291
|
+
});
|
|
292
|
+
verify
|
|
293
|
+
.command('cleanup')
|
|
294
|
+
.description('Show exact retained-state references, provider deletion, and cleanup-SLO status')
|
|
295
|
+
.argument('<run>', verifyRunArg)
|
|
296
|
+
.argument('<cell>', 'Stable test selector: ordinal, riskCellId, or exact planner cellKey')
|
|
297
|
+
.option('--side <side>', 'base or head (default head)')
|
|
298
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
299
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
300
|
+
.option('--json', 'Machine-readable cleanup status')
|
|
301
|
+
.action(async (run, cell, _options, cmd) => {
|
|
302
|
+
const options = cmd.optsWithGlobals();
|
|
303
|
+
const { verifyCleanupCommand } = await import('./commands/verify-ops.js');
|
|
304
|
+
return runPublicCommand(() => verifyCleanupCommand(run, cell, options), options.json);
|
|
305
|
+
});
|
|
306
|
+
verify
|
|
307
|
+
.command('reopen')
|
|
308
|
+
.description('Attach fresh E2B compute to retained Archil state from a test or exact fork ID')
|
|
309
|
+
.argument('<source>', 'Run selector, or an exact archil:…:branch:… fork ID')
|
|
310
|
+
.argument('[cell]', 'Stable test selector for a run: ordinal, riskCellId, or exact planner cellKey')
|
|
311
|
+
.option('--side <side>', 'base or head (default head)')
|
|
312
|
+
.option('--open-browser', 'Open the restarted application in a browser')
|
|
313
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
314
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
315
|
+
.option('--json', 'Machine-readable attachment details')
|
|
316
|
+
.action(async (source, cell, _options, cmd) => {
|
|
317
|
+
const options = cmd.optsWithGlobals();
|
|
318
|
+
const { verifyReopenCommand } = await import('./commands/verify-ops.js');
|
|
319
|
+
return runPublicCommand(() => verifyReopenCommand(source, cell, options), options.json);
|
|
320
|
+
});
|
|
321
|
+
verify
|
|
322
|
+
.command('refork')
|
|
323
|
+
.description('Checkpoint a reopened Archil universe into a durable child fork')
|
|
324
|
+
.argument('<source>', 'Run selector, or an exact archil:…:branch:… parent fork ID')
|
|
325
|
+
.argument('<cell-or-child-key>', 'Stable test selector for a run, or child key for an exact fork ID')
|
|
326
|
+
.argument('[child-key]', 'Structured stable child key when source is a run')
|
|
327
|
+
.option('--side <side>', 'base or head (default head; only applies to a run)')
|
|
328
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
329
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
330
|
+
.option('--json', 'Machine-readable parent, child, checkpoint, and expiry')
|
|
331
|
+
.action(async (source, cellOrChildKey, childKey, _options, cmd) => {
|
|
332
|
+
const options = cmd.optsWithGlobals();
|
|
333
|
+
const { verifyReforkCommand } = await import('./commands/verify-ops.js');
|
|
334
|
+
return runPublicCommand(() => verifyReforkCommand(source, cellOrChildKey, childKey, options), options.json);
|
|
335
|
+
});
|
|
336
|
+
verify
|
|
337
|
+
.command('shell')
|
|
338
|
+
.description('Terminal into a failing test\'s live universe (e2b sandbox connect)')
|
|
339
|
+
.argument('<run>', verifyRunArg)
|
|
340
|
+
.argument('<cell>', 'Stable test selector: ordinal, riskCellId, or exact planner cellKey')
|
|
341
|
+
.option('--side <side>', 'base or head (default head; only failing tests keep a universe alive)')
|
|
342
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
343
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
344
|
+
.action(async (run, cell, _options, cmd) => {
|
|
345
|
+
const options = cmd.optsWithGlobals();
|
|
346
|
+
const { verifyShellCommand } = await import('./commands/verify-ops.js');
|
|
347
|
+
return runPublicCommand(() => verifyShellCommand(run, cell, options));
|
|
348
|
+
});
|
|
349
|
+
verify
|
|
350
|
+
.command('show')
|
|
351
|
+
.description('One test in full: finding, changed readings, request timeline, SDK log lines')
|
|
352
|
+
.argument('<run>', verifyRunArg)
|
|
353
|
+
.argument('<cell>', 'Test ordinal, cell id, planner cell key, or title substring')
|
|
354
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
355
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
356
|
+
.option('--no-timeline', 'Skip the request timeline')
|
|
357
|
+
.option('--json', 'Machine-readable detail')
|
|
358
|
+
.action(async (run, cell, _options, cmd) => {
|
|
359
|
+
const options = cmd.optsWithGlobals();
|
|
360
|
+
const { verifyShowCommand } = await import('./commands/verify-ops.js');
|
|
361
|
+
return runPublicCommand(() => verifyShowCommand(run, cell, options), options.json);
|
|
362
|
+
});
|
|
363
|
+
verify
|
|
364
|
+
.command('artifacts')
|
|
365
|
+
.description('Pull a run\'s screenshots, DOM snapshots and videos to a local directory')
|
|
366
|
+
.argument('<run>', verifyRunArg)
|
|
367
|
+
.option('--out <dir>', 'Destination (default: ./verify-artifacts/<runId>)')
|
|
368
|
+
.option('--cell <selector>', 'Only artifacts for one test (ordinal, cell id, or title substring)')
|
|
369
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
370
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
371
|
+
.option('--json', 'Machine-readable result')
|
|
372
|
+
.action(async (run, _options, cmd) => {
|
|
373
|
+
const options = cmd.optsWithGlobals();
|
|
374
|
+
const { verifyArtifactsCommand } = await import('./commands/verify-ops.js');
|
|
375
|
+
return runPublicCommand(() => verifyArtifactsCommand(run, options), options.json);
|
|
376
|
+
});
|
|
377
|
+
verify
|
|
378
|
+
.command('sandboxes')
|
|
379
|
+
.description('E2B sandbox inventory with run attribution; --kill-stale reclaims superseded ones')
|
|
380
|
+
.option('--kill-stale', 'Kill verifier-attributed sandboxes superseded by newer runs (never unattributed ones)')
|
|
381
|
+
.option('--kill-run <runId>', 'Kill every sandbox attributed to one run')
|
|
382
|
+
.option('--server <url>', 'Control-plane URL, preferred over direct E2B access (default: http://127.0.0.1:3000)')
|
|
383
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
384
|
+
.option('--json', 'Machine-readable inventory')
|
|
385
|
+
.action(async (_options, cmd) => {
|
|
386
|
+
const options = cmd.optsWithGlobals();
|
|
387
|
+
const { verifySandboxesCommand } = await import('./commands/verify-sandboxes.js');
|
|
388
|
+
return runPublicCommand(() => verifySandboxesCommand(options), options.json);
|
|
389
|
+
});
|
|
390
|
+
verify
|
|
391
|
+
.command('reseal')
|
|
392
|
+
.description('Rebuild a sealed specimen bundle (guarded) and restart the control plane onto it')
|
|
393
|
+
.argument('<specimen>', 'Specimen id, e.g. posthog-js-478')
|
|
394
|
+
.option('--source <dir>', 'Specimen source directory holding reseal.sh')
|
|
395
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
396
|
+
.option('--repo-root <path>', 'Repository holding demo/cloud-verifier (default: walk up from cwd)')
|
|
397
|
+
.option('--no-restart', 'Only rebuild the bundle; leave the running server on the old checkout')
|
|
398
|
+
.action(async (specimen, _options, cmd) => {
|
|
399
|
+
const options = cmd.optsWithGlobals();
|
|
400
|
+
const { verifyResealCommand } = await import('./commands/verify-reseal.js');
|
|
401
|
+
return runPublicCommand(() => verifyResealCommand(specimen, options));
|
|
402
|
+
});
|
|
403
|
+
verify
|
|
404
|
+
.command('mcp')
|
|
405
|
+
.description('Run a stdio MCP server exposing the verifier to agents (verify_start/verify_wait/…)')
|
|
406
|
+
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
407
|
+
.option('--token <token>', 'Bearer token for a control plane that requires auth (or HAYSTACK_VERIFY_TOKEN)')
|
|
408
|
+
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
409
|
+
.action(async (_options, cmd) => {
|
|
410
|
+
const options = cmd.optsWithGlobals();
|
|
411
|
+
try {
|
|
412
|
+
// Lazy import, same reasoning as the top-level `mcp` command: only this
|
|
413
|
+
// path needs @modelcontextprotocol/sdk.
|
|
414
|
+
const { runVerifyMcpServer } = await import('./commands/verify-mcp.js');
|
|
415
|
+
await runVerifyMcpServer(options);
|
|
416
|
+
}
|
|
417
|
+
catch (err) {
|
|
418
|
+
console.error(chalk.red('verify mcp server failed:'), err instanceof Error ? err.message : err);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
});
|
|
158
422
|
program
|
|
159
423
|
.command('login')
|
|
160
424
|
.description('Add or refresh a saved GitHub account (use --headless for CI tokens)')
|
|
@@ -247,7 +511,9 @@ program
|
|
|
247
511
|
.option('--force', 'Skip pre-PR triage checks')
|
|
248
512
|
.addOption(new Option('--auto-fix', 'Alpha auto-fix for entitled repositories').hideHelp())
|
|
249
513
|
.option('--auto-merge', 'Apply auto-merge label (default: from .haystack.json, --no-auto-merge to disable)')
|
|
514
|
+
.option('--no-auto-merge', 'Do not apply the auto-merge label')
|
|
250
515
|
.option('--no-wait', 'Skip waiting for analysis results')
|
|
516
|
+
.option('--json', 'Machine-readable: one JSON document on stdout (see `haystack schema submit`), progress on stderr')
|
|
251
517
|
.option('--max-turns <n>', 'Max agentic turns per triage checker (overrides .haystack.json triage.maxTurns)', (v) => parseInt(v, 10))
|
|
252
518
|
.option('--triage-timeout <seconds>', 'Wall-clock timeout per triage checker in seconds (overrides .haystack.json triage.timeoutMs)', (v) => parseInt(v, 10))
|
|
253
519
|
.addHelpText('after', `
|
|
@@ -296,8 +562,22 @@ Review Routing:
|
|
|
296
562
|
needs-assignment queue (a teammate will pick it up)
|
|
297
563
|
• --review <username> Same label, plus requests review from that GitHub user
|
|
298
564
|
|
|
565
|
+
Machine-readable output (--json):
|
|
566
|
+
stdout carries exactly one JSON document (schema: \`haystack schema submit\`);
|
|
567
|
+
all progress goes to stderr. On success the payload includes the PR ref,
|
|
568
|
+
the resolved title/body and their sources, auto-merge/auto-fix state with
|
|
569
|
+
source, and the analysis outcome. On failure it is an error envelope
|
|
570
|
+
{ status: "error", error: "..." }.
|
|
571
|
+
|
|
572
|
+
Exit codes:
|
|
573
|
+
0 PR created/updated (including verdict "needs_input" — the PR exists;
|
|
574
|
+
read the verdict from the JSON or from \`haystack triage\`)
|
|
575
|
+
1 Hard failure (bad flags, auth, push, PR creation, blocking triage
|
|
576
|
+
findings, or the analysis status check itself failing)
|
|
577
|
+
|
|
299
578
|
Examples:
|
|
300
579
|
haystack submit # Recommended: triage, create PR, auto-merge queue
|
|
580
|
+
haystack submit --json # Agent mode: JSON on stdout, progress on stderr
|
|
301
581
|
haystack submit --force # Skip triage
|
|
302
582
|
haystack submit --no-wait # Don't wait for analysis
|
|
303
583
|
haystack submit --title "Fix auth" # Custom PR title
|
|
@@ -312,17 +592,23 @@ Examples:
|
|
|
312
592
|
haystack submit --max-turns 12 # Raise triage turn cap to 12 per checker
|
|
313
593
|
haystack submit --triage-timeout 300 # Raise triage wall-clock to 5 minutes
|
|
314
594
|
`)
|
|
315
|
-
.action(async (options) => {
|
|
316
|
-
// Resolve --auto-merge
|
|
317
|
-
|
|
595
|
+
.action(async (options, command) => {
|
|
596
|
+
// Resolve --auto-merge / --auto-fix defaults from .haystack.json when not
|
|
597
|
+
// explicitly set, and record WHERE each value came from so submit can
|
|
598
|
+
// announce the source (silent config-driven defaults are invisible to
|
|
599
|
+
// the caller otherwise).
|
|
600
|
+
const autoMergeFromCli = command.getOptionValueSource('autoMerge') === 'cli';
|
|
601
|
+
const autoFixFromCli = command.getOptionValueSource('autoFix') === 'cli';
|
|
602
|
+
if (!autoMergeFromCli) {
|
|
318
603
|
options.autoMerge = await isAutoMergeEnabled();
|
|
319
604
|
}
|
|
320
|
-
// Resolve --auto-fix default from .haystack.json (preferences.auto_fix).
|
|
321
605
|
// Auto-fix remains alpha — the explicit --auto-fix flag still surfaces
|
|
322
606
|
// discouragement warnings; opting in via repo config is the supported path.
|
|
323
|
-
if (
|
|
607
|
+
if (!autoFixFromCli) {
|
|
324
608
|
options.autoFix = await isAutoFixEnabled();
|
|
325
609
|
}
|
|
610
|
+
options.autoMergeSource = autoMergeFromCli ? 'flag' : 'config';
|
|
611
|
+
options.autoFixSource = autoFixFromCli ? 'flag' : 'config';
|
|
326
612
|
return submitCommand(options);
|
|
327
613
|
});
|
|
328
614
|
program
|
|
@@ -364,35 +650,36 @@ inbox
|
|
|
364
650
|
.command('list')
|
|
365
651
|
.description('List PRs in your Haystack inbox')
|
|
366
652
|
.option('--json', 'Output as JSON')
|
|
367
|
-
.action((options) => runPublicCommand(() => inboxListCommand(options)));
|
|
653
|
+
.action((options) => runPublicCommand(() => inboxListCommand(options), options.json));
|
|
368
654
|
const prProgram = program.command('pr').description('Inspect one pull request');
|
|
369
655
|
prProgram
|
|
370
656
|
.command('get <ref>')
|
|
371
657
|
.description('Get triage, merge blockers, and trace availability')
|
|
372
658
|
.option('--json', 'Output as JSON')
|
|
373
|
-
.action((ref, options) => runPublicCommand(() => prReadCommand(ref, options)));
|
|
659
|
+
.action((ref, options) => runPublicCommand(() => prReadCommand(ref, options), options.json));
|
|
374
660
|
program
|
|
375
661
|
.command('ask <ref> <question>')
|
|
376
662
|
.description('Ask Haystack about a pull request')
|
|
377
663
|
.option('--json', 'Output the answer and every consulted customer-facing source as JSON')
|
|
378
664
|
.option('--session <id>', 'Continue a previous Ask Haystack session')
|
|
379
|
-
.action((ref, question, options) => runPublicCommand(() => askHaystackCommand(ref, question, options)));
|
|
665
|
+
.action((ref, question, options) => runPublicCommand(() => askHaystackCommand(ref, question, options), options.json));
|
|
380
666
|
const traces = program.command('traces').description('Inspect customer-owned Entire traces');
|
|
381
667
|
traces
|
|
382
668
|
.command('list <ref>')
|
|
383
669
|
.description('List retained checkpoints for a pull request')
|
|
384
670
|
.option('--json', 'Output as JSON')
|
|
385
|
-
.action((ref, options) => runPublicCommand(() => tracesListCommand(ref, options)));
|
|
671
|
+
.action((ref, options) => runPublicCommand(() => tracesListCommand(ref, options), options.json));
|
|
386
672
|
traces
|
|
387
673
|
.command('get <ref> <checkpoint>')
|
|
388
674
|
.description('Get retained transcript chunks for one checkpoint')
|
|
389
675
|
.option('--cursor <cursor>', 'Zero-based chunk cursor')
|
|
390
676
|
.option('--limit <count>', 'Chunks per page', '20')
|
|
391
677
|
.option('--json', 'Output as JSON')
|
|
392
|
-
.action((ref, checkpoint, options) => runPublicCommand(() => tracesGetCommand(ref, checkpoint, options)));
|
|
678
|
+
.action((ref, checkpoint, options) => runPublicCommand(() => tracesGetCommand(ref, checkpoint, options), options.json));
|
|
393
679
|
program
|
|
394
680
|
.command('dismiss <pr>')
|
|
395
681
|
.description('Dismiss analysis findings for a PR')
|
|
682
|
+
.option('--json', 'Machine-readable output (see `haystack schema action`)')
|
|
396
683
|
.addHelpText('after', `
|
|
397
684
|
Dismiss analysis findings for a PR, moving it from "Issues Found" to
|
|
398
685
|
"Good to Merge" in the Haystack feed.
|
|
@@ -414,6 +701,7 @@ Examples:
|
|
|
414
701
|
program
|
|
415
702
|
.command('mark-reviewed <pr>')
|
|
416
703
|
.description('Mark human review as not needed for a PR')
|
|
704
|
+
.option('--json', 'Machine-readable output (see `haystack schema action`)')
|
|
417
705
|
.addHelpText('after', `
|
|
418
706
|
Mark human review as not needed for a PR, moving it from "Needs Review"
|
|
419
707
|
to "Good to Merge" in the Haystack feed.
|
|
@@ -435,6 +723,7 @@ Examples:
|
|
|
435
723
|
program
|
|
436
724
|
.command('undismiss <pr>')
|
|
437
725
|
.description('Undo a dismiss or mark-reviewed override for a PR')
|
|
726
|
+
.option('--json', 'Machine-readable output (see `haystack schema action`)')
|
|
438
727
|
.addHelpText('after', `
|
|
439
728
|
Clear all overrides (dismissed findings and/or review-not-needed) for a PR,
|
|
440
729
|
returning it to its original feed bucket.
|
|
@@ -452,8 +741,9 @@ Examples:
|
|
|
452
741
|
.action(undismissCommand);
|
|
453
742
|
program
|
|
454
743
|
.command('review [pr]')
|
|
455
|
-
.description('Trigger a fresh Haystack analysis for a PR')
|
|
744
|
+
.description('Trigger a fresh Haystack analysis for a PR (machine analysis — for HUMAN review use request-review)')
|
|
456
745
|
.option('--no-wait', 'Exit after triggering instead of waiting for results')
|
|
746
|
+
.option('--json', 'Machine-readable output (see `haystack schema action`)')
|
|
457
747
|
.addHelpText('after', `
|
|
458
748
|
Re-run full Haystack analysis on a PR's current head.
|
|
459
749
|
|
|
@@ -481,6 +771,7 @@ Examples:
|
|
|
481
771
|
program
|
|
482
772
|
.command('request-review <pr> [reviewer]')
|
|
483
773
|
.description('Tag a PR as needing human review')
|
|
774
|
+
.option('--json', 'Machine-readable output (see `haystack schema action`)')
|
|
484
775
|
.addHelpText('after', `
|
|
485
776
|
Tag any PR with "needs human review", even after it was created.
|
|
486
777
|
Adds the haystack:needs-review label to the PR, moving it from
|
|
@@ -551,9 +842,9 @@ config
|
|
|
551
842
|
.description('Auto-merge safe PRs submitted via haystack submit (on|off|status)')
|
|
552
843
|
.addHelpText('after', `
|
|
553
844
|
Actions:
|
|
554
|
-
on, enable
|
|
555
|
-
off, disable Disable auto-merge
|
|
556
|
-
status
|
|
845
|
+
on, enable, true Enable auto-merge for safe PRs
|
|
846
|
+
off, disable, false Disable auto-merge
|
|
847
|
+
status Show current status (default)
|
|
557
848
|
|
|
558
849
|
When enabled, PRs submitted via \`haystack submit\` will be
|
|
559
850
|
automatically merged if Haystack analysis finds no issues
|
|
@@ -571,9 +862,9 @@ config
|
|
|
571
862
|
.description('(Alpha) Auto-fix mechanical issues on PRs submitted via haystack submit (on|off|status)')
|
|
572
863
|
.addHelpText('after', `
|
|
573
864
|
Actions:
|
|
574
|
-
on, enable
|
|
575
|
-
off, disable Disable auto-fix
|
|
576
|
-
status
|
|
865
|
+
on, enable, true Enable auto-fix for mechanical issues
|
|
866
|
+
off, disable, false Disable auto-fix
|
|
867
|
+
status Show current status (default)
|
|
577
868
|
|
|
578
869
|
⚠ Auto-fix is alpha. When enabled, PRs submitted via \`haystack submit\`
|
|
579
870
|
are labeled haystack:auto-fix. The Haystack analysis pipeline then
|
|
@@ -882,6 +1173,38 @@ rules
|
|
|
882
1173
|
process.exit(1);
|
|
883
1174
|
}
|
|
884
1175
|
});
|
|
1176
|
+
// ─── system-map ──────────────────────────────────────────────────────────────
|
|
1177
|
+
const systemMap = program
|
|
1178
|
+
.command('system-map')
|
|
1179
|
+
.description('Manage .haystack/system.yml — runtime facts that help Haystack QA run your system');
|
|
1180
|
+
systemMap
|
|
1181
|
+
.command('validate')
|
|
1182
|
+
.description('Check .haystack/system.yml against the rules Haystack QA will apply')
|
|
1183
|
+
.option('--json', 'Machine-readable result')
|
|
1184
|
+
.addHelpText('after', `
|
|
1185
|
+
The system map holds VERIFIED facts about how your system works — install/
|
|
1186
|
+
build commands, how services boot and signal readiness, seed/reset commands,
|
|
1187
|
+
dev-only test logins, external services that must never be touched live, and
|
|
1188
|
+
prebuilt CI artifacts. Haystack QA uses these facts to run more of what it
|
|
1189
|
+
already wants to test; the file never selects tests.
|
|
1190
|
+
|
|
1191
|
+
Author it with the /map-your-system skill (haystack skills install), then run
|
|
1192
|
+
this before committing. Checks: valid YAML + version, no test-selection keys,
|
|
1193
|
+
no secret values, non-empty boot/login/run commands, artifact declarations.
|
|
1194
|
+
|
|
1195
|
+
Examples:
|
|
1196
|
+
haystack system-map validate
|
|
1197
|
+
haystack system-map validate --json
|
|
1198
|
+
`)
|
|
1199
|
+
.action(async (opts) => {
|
|
1200
|
+
try {
|
|
1201
|
+
await systemMapValidateCommand(opts);
|
|
1202
|
+
}
|
|
1203
|
+
catch (err) {
|
|
1204
|
+
console.error(chalk.red('validate failed:'), err instanceof Error ? err.message : err);
|
|
1205
|
+
process.exit(1);
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
885
1208
|
// ─── mcp ─────────────────────────────────────────────────────────────────────
|
|
886
1209
|
program
|
|
887
1210
|
.command('mcp')
|
package/dist/schema.d.ts
CHANGED
|
@@ -13,9 +13,13 @@ export declare const SCHEMA_VERSIONS: {
|
|
|
13
13
|
readonly triage: "2.0.0";
|
|
14
14
|
readonly setup: "1.0.0";
|
|
15
15
|
readonly pr: "2.0.0";
|
|
16
|
+
readonly 'pr-status': "1.0.0";
|
|
16
17
|
readonly inbox: "1.0.0";
|
|
17
18
|
readonly ask: "1.0.0";
|
|
18
19
|
readonly traces: "1.0.0";
|
|
20
|
+
readonly submit: "1.0.0";
|
|
21
|
+
readonly action: "1.0.0";
|
|
22
|
+
readonly error: "1.0.0";
|
|
19
23
|
};
|
|
20
24
|
export type SchemaName = keyof typeof SCHEMA_VERSIONS;
|
|
21
25
|
/** Wrap a payload with the `schema_version` envelope. */
|
package/dist/schema.js
CHANGED
|
@@ -13,9 +13,13 @@ export const SCHEMA_VERSIONS = {
|
|
|
13
13
|
triage: '2.0.0',
|
|
14
14
|
setup: '1.0.0',
|
|
15
15
|
pr: '2.0.0',
|
|
16
|
+
'pr-status': '1.0.0',
|
|
16
17
|
inbox: '1.0.0',
|
|
17
18
|
ask: '1.0.0',
|
|
18
19
|
traces: '1.0.0',
|
|
20
|
+
submit: '1.0.0',
|
|
21
|
+
action: '1.0.0',
|
|
22
|
+
error: '1.0.0',
|
|
19
23
|
};
|
|
20
24
|
/** Wrap a payload with the `schema_version` envelope. */
|
|
21
25
|
export function withSchema(name, payload) {
|