@haystackeditor/cli 0.15.31 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -61
- package/dist/assets/hooks/scripts/commit-msg.sh +3 -0
- package/dist/assets/hooks/scripts/post-commit.sh +3 -0
- package/dist/assets/hooks/scripts/pre-commit.sh +11 -6
- package/dist/assets/hooks/scripts/pre-push.sh +3 -0
- package/dist/assets/hooks/scripts/prepare-commit-msg.sh +3 -0
- package/dist/commands/case-batch-contract.js +694 -0
- package/dist/commands/case-batch.js +1011 -0
- package/dist/commands/cloud-verifier-identity-census.js +5 -2
- package/dist/commands/combination-search-hook.js +139 -0
- package/dist/commands/dismiss.js +2 -1
- package/dist/commands/hooks.js +66 -7
- package/dist/commands/install-session-hooks.js +131 -44
- package/dist/commands/policy.js +32 -45
- package/dist/commands/precompute-delivery.js +6 -1
- package/dist/commands/scaffold-provisional-universe.js +8 -10
- package/dist/commands/setup.js +32 -10
- package/dist/commands/submit.js +39 -71
- package/dist/commands/telemetry.js +17 -2
- package/dist/commands/triage.js +2 -1
- package/dist/commands/verify-explore.js +1 -0
- package/dist/commands/verify-hosted-mcp.js +3 -12
- package/dist/commands/verify-hosted-reproducibility.js +49 -547
- package/dist/commands/verify-hosted.js +66 -168
- package/dist/commands/verify-precompute.js +64 -11
- package/dist/commands/verify.js +51 -139
- package/dist/index.js +290 -366
- package/dist/lazy.js +8 -0
- package/dist/schema.js +2 -1
- package/dist/tools/detect.js +3 -24
- package/dist/triage/astra.js +199 -0
- package/dist/triage/prompts.js +145 -179
- package/dist/triage/runner.js +104 -300
- package/dist/triage/types.js +2 -2
- package/dist/types.js +2 -6
- package/dist/utils/auth.js +14 -2
- package/dist/utils/git.js +60 -29
- package/dist/utils/github-api.js +14 -1
- package/dist/utils/haystack-api.js +38 -7
- package/dist/utils/hooks.js +43 -6
- package/dist/utils/prompter.js +25 -10
- package/dist/utils/safe-write.js +31 -0
- package/dist/utils/secret-paths.js +115 -0
- package/dist/utils/secrets.js +0 -1
- package/dist/utils/telemetry.js +34 -14
- package/dist/utils/update-check.js +151 -0
- package/package.json +19 -14
- package/schemas/case-batch.v1.json +235 -0
- package/schemas/cloud-verifier.v1.json +55 -100
- package/schemas/submit.v1.json +4 -2
- package/dist/commands/ask.d.ts +0 -14
- package/dist/commands/cloud-verifier-behaviors.d.ts +0 -27
- package/dist/commands/cloud-verifier-data-store-census.d.ts +0 -47
- package/dist/commands/cloud-verifier-data-store-drift.d.ts +0 -42
- package/dist/commands/cloud-verifier-identity-census.d.ts +0 -88
- package/dist/commands/cloud-verifier-materialization.d.ts +0 -16
- package/dist/commands/cloud-verifier-pascal-selector-census.d.ts +0 -29
- package/dist/commands/cloud-verifier-python-manifest-selector-census.d.ts +0 -27
- package/dist/commands/cloud-verifier-specialized-operational-census.d.ts +0 -51
- package/dist/commands/cloud-verifier-universe.d.ts +0 -31
- package/dist/commands/config.d.ts +0 -46
- package/dist/commands/design-verify.d.ts +0 -33
- package/dist/commands/dismiss.d.ts +0 -29
- package/dist/commands/hooks.d.ts +0 -13
- package/dist/commands/inbox.d.ts +0 -65
- package/dist/commands/init.d.ts +0 -10
- package/dist/commands/install-session-hooks.d.ts +0 -17
- package/dist/commands/login.d.ts +0 -8
- package/dist/commands/mcp.d.ts +0 -1
- package/dist/commands/policy.d.ts +0 -31
- package/dist/commands/pr-status.d.ts +0 -144
- package/dist/commands/pr.d.ts +0 -40
- package/dist/commands/precompute-delivery-contract.d.ts +0 -68
- package/dist/commands/precompute-delivery.d.ts +0 -20
- package/dist/commands/prepare-universe-review.d.ts +0 -115
- package/dist/commands/production-source-deny-policy.d.ts +0 -15
- package/dist/commands/request-review.d.ts +0 -26
- package/dist/commands/review.d.ts +0 -25
- package/dist/commands/rules.d.ts +0 -4
- package/dist/commands/scaffold-provisional-universe.d.ts +0 -468
- package/dist/commands/schema-cmd.d.ts +0 -2
- package/dist/commands/setup.d.ts +0 -28
- package/dist/commands/skills.d.ts +0 -8
- package/dist/commands/status.d.ts +0 -4
- package/dist/commands/submit.d.ts +0 -30
- package/dist/commands/system-map.d.ts +0 -42
- package/dist/commands/telemetry.d.ts +0 -53
- package/dist/commands/tokens.d.ts +0 -14
- package/dist/commands/triage.d.ts +0 -35
- package/dist/commands/verify-core.d.ts +0 -449
- package/dist/commands/verify-core.js +0 -789
- package/dist/commands/verify-explore.d.ts +0 -14
- package/dist/commands/verify-history.d.ts +0 -14
- package/dist/commands/verify-hosted-mcp.d.ts +0 -12
- package/dist/commands/verify-hosted-reproducibility.d.ts +0 -90
- package/dist/commands/verify-hosted.d.ts +0 -92
- package/dist/commands/verify-mcp.d.ts +0 -8
- package/dist/commands/verify-mcp.js +0 -517
- package/dist/commands/verify-ops.d.ts +0 -158
- package/dist/commands/verify-ops.js +0 -1148
- package/dist/commands/verify-precompute.d.ts +0 -30
- package/dist/commands/verify-reproducibility.d.ts +0 -85
- package/dist/commands/verify-reproducibility.js +0 -494
- package/dist/commands/verify-reseal.d.ts +0 -9
- package/dist/commands/verify-reseal.js +0 -148
- package/dist/commands/verify-sandboxes.d.ts +0 -95
- package/dist/commands/verify-sandboxes.js +0 -352
- package/dist/commands/verify.d.ts +0 -28
- package/dist/commands/webhooks.d.ts +0 -30
- package/dist/index.d.ts +0 -22
- package/dist/schema.d.ts +0 -28
- package/dist/states.d.ts +0 -29
- package/dist/tools/detect.d.ts +0 -50
- package/dist/triage/prompts.d.ts +0 -24
- package/dist/triage/runner.d.ts +0 -34
- package/dist/triage/types.d.ts +0 -42
- package/dist/types/verify-history.d.ts +0 -38
- package/dist/types.d.ts +0 -1684
- package/dist/utils/action-output.d.ts +0 -24
- package/dist/utils/analysis-api.d.ts +0 -187
- package/dist/utils/auth.d.ts +0 -79
- package/dist/utils/config.d.ts +0 -24
- package/dist/utils/design-verifier-api.d.ts +0 -208
- package/dist/utils/design-verifier-history.d.ts +0 -46
- package/dist/utils/design-verifier-result.d.ts +0 -74
- package/dist/utils/detect.d.ts +0 -43
- package/dist/utils/git.d.ts +0 -135
- package/dist/utils/github-api.d.ts +0 -104
- package/dist/utils/haystack-api.d.ts +0 -37
- package/dist/utils/hooks.d.ts +0 -12
- package/dist/utils/pending-state.d.ts +0 -40
- package/dist/utils/pr-ref.d.ts +0 -27
- package/dist/utils/prompter.d.ts +0 -85
- package/dist/utils/secrets.d.ts +0 -47
- package/dist/utils/telemetry.d.ts +0 -19
- /package/dist/commands/{precompute-delivery-worker.d.ts → combination-search-hook-contract.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -24,39 +24,71 @@ import { fileURLToPath } from 'node:url';
|
|
|
24
24
|
import { dirname, join } from 'node:path';
|
|
25
25
|
import chalk from 'chalk';
|
|
26
26
|
import { Command, Option } from 'commander';
|
|
27
|
-
import { statusCommand } from './commands/status.js';
|
|
28
27
|
import { withSchema } from './schema.js';
|
|
29
|
-
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
28
|
+
import { lazy } from './lazy.js';
|
|
29
|
+
// Command modules load on first use (see src/lazy.ts). Keep new commands
|
|
30
|
+
// on this pattern: a static import here puts its whole dependency graph on
|
|
31
|
+
// the startup path of every invocation, including --version and --help.
|
|
32
|
+
const statusCommand = lazy(() => import('./commands/status.js'), 'statusCommand');
|
|
33
|
+
const initCommand = lazy(() => import('./commands/init.js'), 'initCommand');
|
|
34
|
+
const authListCommand = lazy(() => import('./commands/login.js'), 'authListCommand');
|
|
35
|
+
const authUseCommand = lazy(() => import('./commands/login.js'), 'authUseCommand');
|
|
36
|
+
const loginCommand = lazy(() => import('./commands/login.js'), 'loginCommand');
|
|
37
|
+
const logoutCommand = lazy(() => import('./commands/login.js'), 'logoutCommand');
|
|
38
|
+
const whoamiCommand = lazy(() => import('./commands/login.js'), 'whoamiCommand');
|
|
39
|
+
const handleAgenticTool = lazy(() => import('./commands/config.js'), 'handleAgenticTool');
|
|
40
|
+
const handleAutoMerge = lazy(() => import('./commands/config.js'), 'handleAutoMerge');
|
|
41
|
+
const handleAutoFix = lazy(() => import('./commands/config.js'), 'handleAutoFix');
|
|
42
|
+
const handleWaitForReviewers = lazy(() => import('./commands/config.js'), 'handleWaitForReviewers');
|
|
43
|
+
const isAutoMergeEnabled = lazy(() => import('./commands/config.js'), 'isAutoMergeEnabled');
|
|
44
|
+
const isAutoFixEnabled = lazy(() => import('./commands/config.js'), 'isAutoFixEnabled');
|
|
45
|
+
const installSkills = lazy(() => import('./commands/skills.js'), 'installSkills');
|
|
46
|
+
const listSkills = lazy(() => import('./commands/skills.js'), 'listSkills');
|
|
47
|
+
const cloudVerifierUniverseValidateCommand = lazy(() => import('./commands/cloud-verifier-universe.js'), 'cloudVerifierUniverseValidateCommand');
|
|
48
|
+
const cloudVerifierIdentityCensusCommand = lazy(() => import('./commands/cloud-verifier-identity-census.js'), 'cloudVerifierIdentityCensusCommand');
|
|
49
|
+
const cloudVerifierDataStoreDriftCommand = lazy(() => import('./commands/cloud-verifier-data-store-drift.js'), 'cloudVerifierDataStoreDriftCommand');
|
|
50
|
+
const cloudVerifierBehaviorsValidateCommand = lazy(() => import('./commands/cloud-verifier-behaviors.js'), 'cloudVerifierBehaviorsValidateCommand');
|
|
51
|
+
const prepareUniverseReviewCommand = lazy(() => import('./commands/prepare-universe-review.js'), 'prepareUniverseReviewCommand');
|
|
52
|
+
const scaffoldProvisionalUniverseCommand = lazy(() => import('./commands/scaffold-provisional-universe.js'), 'scaffoldProvisionalUniverseCommand');
|
|
53
|
+
const hooksInstall = lazy(() => import('./commands/hooks.js'), 'hooksInstall');
|
|
54
|
+
const hooksStatus = lazy(() => import('./commands/hooks.js'), 'hooksStatus');
|
|
55
|
+
const submitCommand = lazy(() => import('./commands/submit.js'), 'submitCommand');
|
|
56
|
+
const installSessionHooks = lazy(() => import('./commands/install-session-hooks.js'), 'installSessionHooks');
|
|
57
|
+
const sessionHooksStatus = lazy(() => import('./commands/install-session-hooks.js'), 'sessionHooksStatus');
|
|
58
|
+
const listPolicies = lazy(() => import('./commands/policy.js'), 'listPolicies');
|
|
59
|
+
const addPolicy = lazy(() => import('./commands/policy.js'), 'addPolicy');
|
|
60
|
+
const removePolicy = lazy(() => import('./commands/policy.js'), 'removePolicy');
|
|
61
|
+
const initPolicies = lazy(() => import('./commands/policy.js'), 'initPolicies');
|
|
62
|
+
const addInstruction = lazy(() => import('./commands/policy.js'), 'addInstruction');
|
|
63
|
+
const triageCommand = lazy(() => import('./commands/triage.js'), 'triageCommand');
|
|
64
|
+
const designVerifyCommand = lazy(() => import('./commands/design-verify.js'), 'designVerifyCommand');
|
|
65
|
+
const dismissCommand = lazy(() => import('./commands/dismiss.js'), 'dismissCommand');
|
|
66
|
+
const markReviewedCommand = lazy(() => import('./commands/dismiss.js'), 'markReviewedCommand');
|
|
67
|
+
const undismissCommand = lazy(() => import('./commands/dismiss.js'), 'undismissCommand');
|
|
68
|
+
const requestReviewCommand = lazy(() => import('./commands/request-review.js'), 'requestReviewCommand');
|
|
69
|
+
const reviewCommand = lazy(() => import('./commands/review.js'), 'reviewCommand');
|
|
70
|
+
const prStatusCommand = lazy(() => import('./commands/pr-status.js'), 'prStatusCommand');
|
|
71
|
+
const prReadCommand = lazy(() => import('./commands/pr.js'), 'prReadCommand');
|
|
72
|
+
const inboxListCommand = lazy(() => import('./commands/inbox.js'), 'inboxListCommand');
|
|
73
|
+
const askHaystackCommand = lazy(() => import('./commands/ask.js'), 'askHaystackCommand');
|
|
74
|
+
const telemetryInstrumentCommand = lazy(() => import('./commands/telemetry.js'), 'telemetryInstrumentCommand');
|
|
75
|
+
const setupCommand = lazy(() => import('./commands/setup.js'), 'setupCommand');
|
|
76
|
+
const schemaCommand = lazy(() => import('./commands/schema-cmd.js'), 'schemaCommand');
|
|
77
|
+
const listSchemas = lazy(() => import('./commands/schema-cmd.js'), 'listSchemas');
|
|
78
|
+
const registerWebhook = lazy(() => import('./commands/webhooks.js'), 'registerWebhook');
|
|
79
|
+
const listWebhooks = lazy(() => import('./commands/webhooks.js'), 'listWebhooks');
|
|
80
|
+
const rotateWebhookSecret = lazy(() => import('./commands/webhooks.js'), 'rotateWebhookSecret');
|
|
81
|
+
const setWebhookEnabled = lazy(() => import('./commands/webhooks.js'), 'setWebhookEnabled');
|
|
82
|
+
const listDeliveries = lazy(() => import('./commands/webhooks.js'), 'listDeliveries');
|
|
83
|
+
const replayDelivery = lazy(() => import('./commands/webhooks.js'), 'replayDelivery');
|
|
84
|
+
const editRulesCommand = lazy(() => import('./commands/rules.js'), 'editRulesCommand');
|
|
85
|
+
const validateRulesCommand = lazy(() => import('./commands/rules.js'), 'validateRulesCommand');
|
|
86
|
+
const systemMapValidateCommand = lazy(() => import('./commands/system-map.js'), 'systemMapValidateCommand');
|
|
87
|
+
const verifyCommand = lazy(() => import('./commands/verify.js'), 'verifyCommand');
|
|
88
|
+
const headlessLoginCommand = lazy(() => import('./commands/tokens.js'), 'headlessLoginCommand');
|
|
89
|
+
const headlessLogoutCommand = lazy(() => import('./commands/tokens.js'), 'headlessLogoutCommand');
|
|
90
|
+
const listTokensCommand = lazy(() => import('./commands/tokens.js'), 'listTokensCommand');
|
|
91
|
+
const revokeTokenCommand = lazy(() => import('./commands/tokens.js'), 'revokeTokenCommand');
|
|
60
92
|
// `mcp` is imported lazily inside its command action (below), NOT at the top level.
|
|
61
93
|
// It is the only module that pulls in `@modelcontextprotocol/sdk`; keeping it out of
|
|
62
94
|
// the startup import graph means a missing/broken SDK can never crash core commands
|
|
@@ -99,17 +131,14 @@ const program = new Command();
|
|
|
99
131
|
program
|
|
100
132
|
.name('haystack')
|
|
101
133
|
.description('Haystack CLI — automated PR review, triage, and merge queue')
|
|
102
|
-
.version(getVersion())
|
|
134
|
+
.version(getVersion())
|
|
135
|
+
// commander 13 made surplus positional arguments an error; the CLI has always
|
|
136
|
+
// ignored them. Every command created below inherits this setting.
|
|
137
|
+
.allowExcessArguments();
|
|
103
138
|
program
|
|
104
139
|
.command('schema [name]')
|
|
105
140
|
.description('Print the JSON Schema for a command\'s --json output')
|
|
106
|
-
.action((name) =>
|
|
107
|
-
if (!name) {
|
|
108
|
-
listSchemas();
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
schemaCommand(name);
|
|
112
|
-
});
|
|
141
|
+
.action((name) => (name ? schemaCommand(name) : listSchemas()));
|
|
113
142
|
program
|
|
114
143
|
.command('init')
|
|
115
144
|
.description('Create .haystack.json configuration')
|
|
@@ -130,9 +159,9 @@ program
|
|
|
130
159
|
.option('--json', 'Agent mode: emit NDJSON events on stdout, read answers on stdin (no TTY prompts)')
|
|
131
160
|
.option('--repo <repo...>', 'Repo(s) to configure (owner/name); skips the repo picker')
|
|
132
161
|
.option('-y, --yes', 'Accept defaults non-interactively (keep all discovered items, confirm write, ENABLE auto-merge — pass --no-auto-merge to opt out)')
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
.option('--no-auto-merge', '
|
|
162
|
+
// Only the negative form exists; there is no --auto-merge. Without the flag the
|
|
163
|
+
// wizard asks (detected via getOptionValueSource below).
|
|
164
|
+
.option('--no-auto-merge', 'Turn auto-merge off in the written config (omit to be asked)')
|
|
136
165
|
.option('--answers <file>', 'JSON file of {questionId: value} pre-supplied answers')
|
|
137
166
|
.addHelpText('after', `
|
|
138
167
|
Steps: verify GitHub App → select repos → scan (rules/policies/instructions) →
|
|
@@ -154,9 +183,9 @@ Examples:
|
|
|
154
183
|
haystack setup --json --repo owner/name
|
|
155
184
|
`)
|
|
156
185
|
.action((options, command) => {
|
|
157
|
-
//
|
|
158
|
-
//
|
|
159
|
-
//
|
|
186
|
+
// auto-merge: false when --no-auto-merge was passed, otherwise undefined (ask).
|
|
187
|
+
// commander defaults autoMerge to true because of the --no- form, so check
|
|
188
|
+
// the value source rather than the value.
|
|
160
189
|
const autoMergeExplicit = command.getOptionValueSource('autoMerge') === 'cli';
|
|
161
190
|
return setupCommand({
|
|
162
191
|
json: options.json,
|
|
@@ -172,55 +201,53 @@ program
|
|
|
172
201
|
.action(statusCommand);
|
|
173
202
|
const verify = program
|
|
174
203
|
.command('verify')
|
|
175
|
-
.description('Verify
|
|
176
|
-
.
|
|
177
|
-
.option('--
|
|
178
|
-
.option('--
|
|
179
|
-
.option('--head <ref>', 'Head revision for a repository run')
|
|
204
|
+
.description('Verify the current change with the hosted Haystack fleet')
|
|
205
|
+
.option('--repo <owner/repo>', 'GitHub repository (default: origin remote)')
|
|
206
|
+
.option('--base <ref>', 'Base revision (default: merge base of HEAD and origin/default branch)')
|
|
207
|
+
.option('--head <ref>', 'Head revision (default: current HEAD commit)')
|
|
180
208
|
.option('--intent-file <path>', 'Optional intent JSON with problem, goal, and intended_outcomes')
|
|
181
209
|
.option('--idempotency-key <key>', 'Stable retry key (default: derived from repository, refs, and intent)')
|
|
182
210
|
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
183
|
-
.
|
|
211
|
+
.option('--no-wait', 'Return after the hosted run is queued')
|
|
184
212
|
.option('--interval <seconds>', 'Hosted polling interval (default 5)')
|
|
185
213
|
.option('--timeout <minutes>', 'Maximum hosted wait (default 35)')
|
|
186
|
-
.option('--intent-transcript <file>', 'Coding-agent transcript used as intent evidence')
|
|
187
|
-
.option('--server <url>', 'Verifier control-plane URL (default: http://127.0.0.1:3000)')
|
|
188
|
-
.option('--token <token>', 'Bearer token for a control plane that requires auth (or HAYSTACK_VERIFY_TOKEN)')
|
|
189
|
-
.option('--no-open', 'Do not open the live results page')
|
|
190
214
|
.option('--json', 'Machine-readable run status and results')
|
|
191
215
|
.addHelpText('after', `
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
Repository verification uses the authenticated hosted fleet and resolves refs
|
|
198
|
-
once. Intent is optional. Hosted status also resumes these runs.
|
|
199
|
-
|
|
200
|
-
For launch specimens, run the local control plane from the Haystack repository:
|
|
201
|
-
pnpm dev
|
|
216
|
+
Run inside a git checkout after pushing your current commit. The repository
|
|
217
|
+
comes from origin, head from HEAD, and base from git merge-base HEAD with
|
|
218
|
+
origin's default branch (origin/HEAD, falling back to origin/main).
|
|
219
|
+
Explicit --repo, --base, and --head values override these defaults.
|
|
220
|
+
Intent is optional. Hosted status resumes an existing run.
|
|
202
221
|
|
|
203
222
|
Examples:
|
|
204
|
-
haystack verify
|
|
223
|
+
haystack verify
|
|
224
|
+
haystack verify --json
|
|
225
|
+
haystack verify --repo owner/repo --base main --head feature --no-wait
|
|
205
226
|
haystack verify history owner/repo --limit 20
|
|
206
|
-
haystack verify hosted
|
|
207
|
-
haystack verify posthog-js-478 # start a run, then:
|
|
208
|
-
haystack verify reproducibility posthog-js-478 --runs 3
|
|
209
|
-
haystack verify watch # block until it finishes (exit 1 = tests failed)
|
|
210
|
-
haystack verify status # one-screen verdicts + live environment URLs
|
|
211
|
-
haystack verify findings # every problem found, with the readings that moved
|
|
212
|
-
haystack verify show latest 3 # one test in full: readings, timeline, SDK log
|
|
213
|
-
haystack verify diff latest 3 # base-vs-head screenshot differ (1/2/3 views)
|
|
214
|
-
haystack verify open latest 3 --live # open a failing test's live environment
|
|
215
|
-
haystack verify cleanup latest 3 # retained-state deletion/reference status
|
|
216
|
-
haystack verify refork latest 3 attempt:fix-2 # checkpoint a durable child
|
|
217
|
-
haystack verify shell latest 3 # terminal into that live universe
|
|
218
|
-
haystack verify artifacts latest # pull screenshots / DOM / video locally
|
|
219
|
-
haystack verify sandboxes --kill-stale # reclaim E2B capacity from superseded runs
|
|
220
|
-
haystack verify reseal <specimen> # rebuild a sealed bundle + restart control plane
|
|
227
|
+
haystack verify hosted status cv_<48 lowercase hex characters> --wait
|
|
221
228
|
`)
|
|
222
|
-
.action(
|
|
223
|
-
|
|
229
|
+
.action(options => runPublicCommand(() => verifyCommand(options), options.json));
|
|
230
|
+
/**
|
|
231
|
+
* Options for a `verify` subcommand: its own values, plus any option the user
|
|
232
|
+
* passed to an ancestor (`haystack verify --no-wait hosted status ...`).
|
|
233
|
+
* Ancestors' defaults are not inherited. verify's lone --no-wait gets
|
|
234
|
+
* commander's implicit default of true, which would otherwise make
|
|
235
|
+
* `verify hosted status` wait although it waits only with --wait.
|
|
236
|
+
*/
|
|
237
|
+
function verifyCommandOptions(command) {
|
|
238
|
+
const commands = [];
|
|
239
|
+
for (let current = command; current; current = current.parent) {
|
|
240
|
+
commands.unshift(current);
|
|
241
|
+
}
|
|
242
|
+
const options = { ...command.opts() };
|
|
243
|
+
for (const current of commands) {
|
|
244
|
+
for (const [name, value] of Object.entries(current.opts())) {
|
|
245
|
+
if (current.getOptionValueSource(name) !== 'default')
|
|
246
|
+
options[name] = value;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return options;
|
|
250
|
+
}
|
|
224
251
|
verify
|
|
225
252
|
.command('history')
|
|
226
253
|
.description('Read your recent finished hosted verification runs without starting a run')
|
|
@@ -237,7 +264,7 @@ Examples:
|
|
|
237
264
|
haystack verify history owner/repo --limit 50 --account my-login --json
|
|
238
265
|
`)
|
|
239
266
|
.action(async (repository, _options, cmd) => {
|
|
240
|
-
const options =
|
|
267
|
+
const options = verifyCommandOptions(cmd);
|
|
241
268
|
const { verifyHistoryCommand } = await import('./commands/verify-history.js');
|
|
242
269
|
return runPublicCommand(() => verifyHistoryCommand(repository, options), options.json);
|
|
243
270
|
});
|
|
@@ -260,17 +287,16 @@ hostedVerify
|
|
|
260
287
|
.command('start')
|
|
261
288
|
.description('Start an authenticated production verification and wait for its terminal result')
|
|
262
289
|
.argument('<repository>', 'Exact GitHub owner/repository name')
|
|
263
|
-
// `verify` already owns --base/--head for
|
|
290
|
+
// `verify` already owns --base/--head for repository verification.
|
|
264
291
|
// Re-declaring these as Commander requiredOption values makes the parent
|
|
265
292
|
// consume them and the child then reject a valid invocation. Runtime
|
|
266
293
|
// validation below accepts the inherited values and still fails closed.
|
|
267
294
|
.option('--base <sha>', 'Required: exact 40-character lowercase base commit SHA')
|
|
268
295
|
.option('--head <sha>', 'Required: exact 40-character lowercase head commit SHA')
|
|
269
|
-
.option('--intent-file <path>', '
|
|
296
|
+
.option('--intent-file <path>', 'Optional JSON with exactly problem, goal, and intended_outcomes')
|
|
270
297
|
.option('--idempotency-key <key>', 'Stable retry key (default: deterministic from repository and commits)')
|
|
271
|
-
.option('--replay-plan-from <run-id>', 'Reuse the exact validated risk plan from a completed owned run')
|
|
272
298
|
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
273
|
-
.option('--no-wait', 'Return after the
|
|
299
|
+
.option('--no-wait', 'Return after the hosted run is queued')
|
|
274
300
|
.option('--interval <seconds>', 'Polling interval while waiting (default 5)')
|
|
275
301
|
.option('--timeout <minutes>', 'Maximum wait (default 35)')
|
|
276
302
|
.option('--json', 'Versioned machine-readable run status and terminal output')
|
|
@@ -278,18 +304,15 @@ hostedVerify
|
|
|
278
304
|
The default idempotency key makes an exact retry return the original run. Pass
|
|
279
305
|
--idempotency-key with a new stable key only when you intentionally want a
|
|
280
306
|
fresh production execution of the same commit pair.
|
|
281
|
-
--replay-plan-from requires the same repository and exact commit pair and emits
|
|
282
|
-
a schema-9 terminal receipt with replay provenance.
|
|
283
307
|
|
|
284
308
|
Examples:
|
|
285
309
|
haystack verify hosted start owner/repo --base <sha> --head <sha> --intent-file <path>
|
|
286
|
-
haystack verify hosted start owner/repo --base <sha> --head <sha> --intent-file <path> --replay-plan-from cv_<48 lowercase hex characters>
|
|
287
310
|
haystack verify hosted start owner/repo --base <sha> --head <sha> --intent-file <path> --no-wait --json
|
|
288
311
|
`)
|
|
289
312
|
.action(async (repository, _options, cmd) => {
|
|
290
|
-
const options =
|
|
313
|
+
const options = verifyCommandOptions(cmd);
|
|
291
314
|
const { verifyHostedStartCommand } = await import('./commands/verify-hosted.js');
|
|
292
|
-
return runPublicCommand(() => verifyHostedStartCommand(repository, options), options.json);
|
|
315
|
+
return runPublicCommand(() => verifyHostedStartCommand(repository, { ...options, base: options.base, head: options.head }), options.json);
|
|
293
316
|
});
|
|
294
317
|
hostedVerify
|
|
295
318
|
.command('status')
|
|
@@ -305,43 +328,37 @@ Example:
|
|
|
305
328
|
haystack verify hosted status cv_<48 lowercase hex characters> --wait --json
|
|
306
329
|
`)
|
|
307
330
|
.action(async (runId, _options, cmd) => {
|
|
308
|
-
const options =
|
|
331
|
+
const options = verifyCommandOptions(cmd);
|
|
309
332
|
const { verifyHostedStatusCommand } = await import('./commands/verify-hosted.js');
|
|
310
333
|
return runPublicCommand(() => verifyHostedStatusCommand(runId, options), options.json);
|
|
311
334
|
});
|
|
312
335
|
hostedVerify
|
|
313
336
|
.command('reproducibility')
|
|
314
|
-
.description('
|
|
337
|
+
.description('Repeat independent fleet runs and report each run receipt')
|
|
315
338
|
.argument('<repository>', 'Exact GitHub owner/repository name')
|
|
316
339
|
.option('--base <sha>', 'Required: exact 40-character lowercase base commit SHA')
|
|
317
340
|
.option('--head <sha>', 'Required: exact 40-character lowercase head commit SHA')
|
|
318
|
-
.option('--intent-file <path>', '
|
|
319
|
-
.option('--runs <n>', 'Number of sequential
|
|
341
|
+
.option('--intent-file <path>', 'Optional intent JSON reused for every fleet run')
|
|
342
|
+
.option('--runs <n>', 'Number of sequential fleet runs (default 3; min 2, max 10)')
|
|
320
343
|
.option('--series-key <key>', 'Stable series key for resumable idempotent retries')
|
|
321
|
-
.option('--fresh-plans', 'Run the planner independently for every run instead of replaying the first plan')
|
|
322
344
|
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
323
345
|
.option('--interval <seconds>', 'Polling interval for each run (default 5)')
|
|
324
346
|
.option('--timeout <minutes>', 'Maximum wait per run (default 35)')
|
|
325
|
-
.option('--json', '
|
|
347
|
+
.option('--json', 'Machine-readable series with each fleet run receipt')
|
|
326
348
|
.addHelpText('after', `
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
With --fresh-plans, every run plans independently. A reproducible verdict then
|
|
333
|
-
requires one stable-id plan variant, one assessment variant, no replay
|
|
334
|
-
provenance, and no inconclusive cells. Use this mode to detect planner drift;
|
|
335
|
-
use the default mode to isolate executor or assessment drift.
|
|
349
|
+
Each run independently plans and verifies the same exact commit pair. The
|
|
350
|
+
series key gives each run a distinct, resumable idempotency key.
|
|
351
|
+
Independent plans do not share an execution identity: the report leaves
|
|
352
|
+
reproducibility unestablished and exits 2. A timeout stops the series and
|
|
353
|
+
includes the command to resume the active run.
|
|
336
354
|
|
|
337
355
|
Example:
|
|
338
|
-
haystack verify hosted reproducibility owner/repo --base <sha> --head <sha> --
|
|
339
|
-
haystack verify hosted reproducibility owner/repo --base <sha> --head <sha> --intent-file <path> --runs 3 --fresh-plans
|
|
356
|
+
haystack verify hosted reproducibility owner/repo --base <sha> --head <sha> --runs 3 --json
|
|
340
357
|
`)
|
|
341
358
|
.action(async (repository, _options, cmd) => {
|
|
342
|
-
const options =
|
|
359
|
+
const options = verifyCommandOptions(cmd);
|
|
343
360
|
const { verifyHostedReproducibilityCommand } = await import('./commands/verify-hosted-reproducibility.js');
|
|
344
|
-
return runPublicCommand(() => verifyHostedReproducibilityCommand(repository, options), options.json);
|
|
361
|
+
return runPublicCommand(() => verifyHostedReproducibilityCommand(repository, { ...options, base: options.base, head: options.head }), options.json);
|
|
345
362
|
});
|
|
346
363
|
hostedVerify
|
|
347
364
|
.command('mcp')
|
|
@@ -353,7 +370,7 @@ No credential, repository source, provider metadata, or sandbox mutation tool
|
|
|
353
370
|
is exposed through this production server.
|
|
354
371
|
`)
|
|
355
372
|
.action(async (_options, cmd) => {
|
|
356
|
-
const options =
|
|
373
|
+
const options = verifyCommandOptions(cmd);
|
|
357
374
|
try {
|
|
358
375
|
const { runHostedVerifyMcpServer } = await import('./commands/verify-hosted-mcp.js');
|
|
359
376
|
await runHostedVerifyMcpServer(options);
|
|
@@ -363,48 +380,15 @@ is exposed through this production server.
|
|
|
363
380
|
process.exit(1);
|
|
364
381
|
}
|
|
365
382
|
});
|
|
366
|
-
verify
|
|
367
|
-
.command('reproducibility')
|
|
368
|
-
.description('Separate frozen-plan drift from executor/judgment nondeterminism')
|
|
369
|
-
.argument('<specimen>', 'Specimen id, e.g. posthog-js-478')
|
|
370
|
-
.option('--runs <n>', 'Number of sequential runs to compare (default 3; min 2, max 20)')
|
|
371
|
-
.option('--scope <scope>', 'executor replays one frozen plan (default); planner/full independently replan each run', 'executor')
|
|
372
|
-
.option('--plan-source <runId>', 'executor scope: replay this existing structured frozen plan for every run')
|
|
373
|
-
.option('--interval <seconds>', 'Polling interval for each run (default 3)')
|
|
374
|
-
.option('--timeout <minutes>', 'Per-run timeout (default: wait forever)')
|
|
375
|
-
.option('--server <url>', 'Verifier control-plane URL (default: http://127.0.0.1:3000)')
|
|
376
|
-
.option('--token <token>', 'Bearer token for a control plane that requires auth (or HAYSTACK_VERIFY_TOKEN)')
|
|
377
|
-
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
378
|
-
.option('--json', 'Machine-readable stable/unstable cell report')
|
|
379
|
-
.action(async (specimen, commandOptions, cmd) => {
|
|
380
|
-
const options = { ...cmd.optsWithGlobals(), ...commandOptions };
|
|
381
|
-
const { verifyReproducibilityCommand } = await import('./commands/verify-reproducibility.js');
|
|
382
|
-
return runPublicCommand(() => verifyReproducibilityCommand(specimen, options), options.json);
|
|
383
|
-
});
|
|
384
|
-
verify
|
|
385
|
-
.command('status')
|
|
386
|
-
.description('One screen for a run: per-test verdicts, counts, live environments and expiry')
|
|
387
|
-
.argument('[run]', verifyRunArg)
|
|
388
|
-
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
389
|
-
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
390
|
-
.option('--json', 'Machine-readable summary')
|
|
391
|
-
.action(async (run, _options, cmd) => {
|
|
392
|
-
// optsWithGlobals: the parent `verify` command also declares --json/--server
|
|
393
|
-
// and commander's default parsing lets it consume those flags even when
|
|
394
|
-
// they appear after the subcommand; the merged view (self wins) restores them.
|
|
395
|
-
const options = cmd.optsWithGlobals();
|
|
396
|
-
const { verifyStatusCommand } = await import('./commands/verify-ops.js');
|
|
397
|
-
return runPublicCommand(() => verifyStatusCommand(run, options), options.json);
|
|
398
|
-
});
|
|
399
383
|
verify
|
|
400
384
|
.command('cases <run>')
|
|
401
385
|
.description('List cloud verification cases available to explore for 24 hours after completion')
|
|
402
386
|
.option('--repo <owner/repo>', 'Required exact GitHub repository; uses your Haystack login')
|
|
403
387
|
.option('--json', 'Machine-readable case IDs and expiry times')
|
|
404
388
|
.action(async (run, _options, cmd) => {
|
|
405
|
-
const options = cmd
|
|
389
|
+
const options = verifyCommandOptions(cmd);
|
|
406
390
|
const { verifyCasesCommand } = await import('./commands/verify-explore.js');
|
|
407
|
-
return runPublicCommand(() => verifyCasesCommand(run, options), options.json);
|
|
391
|
+
return runPublicCommand(() => verifyCasesCommand(run, { ...options, repo: options.repo }), options.json);
|
|
408
392
|
});
|
|
409
393
|
verify
|
|
410
394
|
.command('explore <run> <case-id>')
|
|
@@ -420,210 +404,141 @@ verify
|
|
|
420
404
|
.option('--no-wait', 'Return after submitting the action')
|
|
421
405
|
.option('--json', 'Machine-readable action and command output')
|
|
422
406
|
.action(async (run, caseId, _options, cmd) => {
|
|
423
|
-
const options = cmd
|
|
407
|
+
const options = verifyCommandOptions(cmd);
|
|
424
408
|
const { verifyExploreCommand } = await import('./commands/verify-explore.js');
|
|
425
|
-
return runPublicCommand(() => verifyExploreCommand(run, caseId, options), options.json);
|
|
426
|
-
});
|
|
427
|
-
verify
|
|
428
|
-
.command('watch')
|
|
429
|
-
.description('Follow a run until it finishes; exit 0 all passed, 1 tests failed, 2 run/infra broke')
|
|
430
|
-
.argument('[run]', verifyRunArg)
|
|
431
|
-
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
432
|
-
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
433
|
-
.option('--interval <seconds>', 'Poll interval (default 3)')
|
|
434
|
-
.option('--timeout <minutes>', 'Give up after this long (default: wait forever)')
|
|
435
|
-
.option('--json', 'Suppress streaming; print the final summary as JSON')
|
|
436
|
-
.action(async (run, _options, cmd) => {
|
|
437
|
-
const options = cmd.optsWithGlobals();
|
|
438
|
-
const { verifyWatchCommand } = await import('./commands/verify-ops.js');
|
|
439
|
-
return runPublicCommand(() => verifyWatchCommand(run, options), options.json);
|
|
440
|
-
});
|
|
441
|
-
verify
|
|
442
|
-
.command('list')
|
|
443
|
-
.description('Runs newest first: chapter, verdict counts, age (control plane, else disk)')
|
|
444
|
-
.option('--limit <n>', 'Max runs to show (default 20)')
|
|
445
|
-
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
446
|
-
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
447
|
-
.option('--json', 'Machine-readable list')
|
|
448
|
-
.action(async (_options, cmd) => {
|
|
449
|
-
const options = cmd.optsWithGlobals();
|
|
450
|
-
const { verifyListCommand } = await import('./commands/verify-ops.js');
|
|
451
|
-
return runPublicCommand(() => verifyListCommand(options), options.json);
|
|
452
|
-
});
|
|
453
|
-
verify
|
|
454
|
-
.command('findings')
|
|
455
|
-
.description('Every problem the run found, in one screen: what failed, which readings moved, live links')
|
|
456
|
-
.argument('[run]', verifyRunArg)
|
|
457
|
-
.option('--full', 'Print the judge\'s full explanation for each finding')
|
|
458
|
-
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
459
|
-
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
460
|
-
.option('--json', 'Machine-readable findings')
|
|
461
|
-
.action(async (run, _options, cmd) => {
|
|
462
|
-
const options = cmd.optsWithGlobals();
|
|
463
|
-
const { verifyFindingsCommand } = await import('./commands/verify-ops.js');
|
|
464
|
-
return runPublicCommand(() => verifyFindingsCommand(run, options), options.json);
|
|
465
|
-
});
|
|
466
|
-
verify
|
|
467
|
-
.command('open')
|
|
468
|
-
.description('Open a run artifact (screenshot/video/DOM) or a failing test\'s live environment')
|
|
469
|
-
.argument('<run>', verifyRunArg)
|
|
470
|
-
.argument('<target>', 'Test selector (ordinal/title), or an artifact file name (….png/.webm/.html)')
|
|
471
|
-
.option('--live', 'Open the test\'s live environment URL instead of an artifact')
|
|
472
|
-
.option('--side <side>', 'base or head (default head)')
|
|
473
|
-
.option('--step <n>', 'Open the screenshot captured after replay step n')
|
|
474
|
-
.option('--kind <kind>', 'screenshot | video | dom (default: video, else last screenshot)')
|
|
475
|
-
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
476
|
-
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
477
|
-
.action(async (run, target, _options, cmd) => {
|
|
478
|
-
const options = cmd.optsWithGlobals();
|
|
479
|
-
const { verifyOpenCommand } = await import('./commands/verify-ops.js');
|
|
480
|
-
return runPublicCommand(() => verifyOpenCommand(run, target, options));
|
|
409
|
+
return runPublicCommand(() => verifyExploreCommand(run, caseId, { ...options, repo: options.repo }), options.json);
|
|
481
410
|
});
|
|
482
|
-
|
|
483
|
-
.command('
|
|
484
|
-
.description('
|
|
485
|
-
|
|
486
|
-
.
|
|
487
|
-
.
|
|
488
|
-
.
|
|
489
|
-
.
|
|
490
|
-
.
|
|
491
|
-
.
|
|
492
|
-
.
|
|
493
|
-
.
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
.
|
|
500
|
-
.
|
|
501
|
-
.
|
|
502
|
-
.
|
|
503
|
-
.option('--
|
|
504
|
-
.
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
.
|
|
517
|
-
|
|
518
|
-
.
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
.option('--json', 'Machine-readable attachment details')
|
|
522
|
-
.action(async (source, cell, _options, cmd) => {
|
|
523
|
-
const options = cmd.optsWithGlobals();
|
|
524
|
-
const { verifyReopenCommand } = await import('./commands/verify-ops.js');
|
|
525
|
-
return runPublicCommand(() => verifyReopenCommand(source, cell, options), options.json);
|
|
526
|
-
});
|
|
527
|
-
verify
|
|
528
|
-
.command('refork')
|
|
529
|
-
.description('Checkpoint a reopened Archil universe into a durable child fork')
|
|
530
|
-
.argument('<source>', 'Run selector, or an exact archil:…:branch:… parent fork ID')
|
|
531
|
-
.argument('<cell-or-child-key>', 'Stable test selector for a run, or child key for an exact fork ID')
|
|
532
|
-
.argument('[child-key]', 'Structured stable child key when source is a run')
|
|
533
|
-
.option('--side <side>', 'base or head (default head; only applies to a run)')
|
|
534
|
-
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
535
|
-
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
536
|
-
.option('--json', 'Machine-readable parent, child, checkpoint, and expiry')
|
|
537
|
-
.action(async (source, cellOrChildKey, childKey, _options, cmd) => {
|
|
538
|
-
const options = cmd.optsWithGlobals();
|
|
539
|
-
const { verifyReforkCommand } = await import('./commands/verify-ops.js');
|
|
540
|
-
return runPublicCommand(() => verifyReforkCommand(source, cellOrChildKey, childKey, options), options.json);
|
|
541
|
-
});
|
|
542
|
-
verify
|
|
543
|
-
.command('shell')
|
|
544
|
-
.description('Terminal into a failing test\'s live universe (e2b sandbox connect)')
|
|
545
|
-
.argument('<run>', verifyRunArg)
|
|
546
|
-
.argument('<cell>', 'Stable test selector: ordinal, riskCellId, or exact planner cellKey')
|
|
547
|
-
.option('--side <side>', 'base or head (default head; only failing tests keep a universe alive)')
|
|
548
|
-
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
549
|
-
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
550
|
-
.action(async (run, cell, _options, cmd) => {
|
|
551
|
-
const options = cmd.optsWithGlobals();
|
|
552
|
-
const { verifyShellCommand } = await import('./commands/verify-ops.js');
|
|
553
|
-
return runPublicCommand(() => verifyShellCommand(run, cell, options));
|
|
554
|
-
});
|
|
555
|
-
verify
|
|
556
|
-
.command('show')
|
|
557
|
-
.description('One test in full: finding, changed readings, request timeline, SDK log lines')
|
|
558
|
-
.argument('<run>', verifyRunArg)
|
|
559
|
-
.argument('<cell>', 'Test ordinal, cell id, planner cell key, or title substring')
|
|
560
|
-
.option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
|
|
561
|
-
.option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
|
|
562
|
-
.option('--no-timeline', 'Skip the request timeline')
|
|
563
|
-
.option('--json', 'Machine-readable detail')
|
|
564
|
-
.action(async (run, cell, _options, cmd) => {
|
|
565
|
-
const options = cmd.optsWithGlobals();
|
|
566
|
-
const { verifyShowCommand } = await import('./commands/verify-ops.js');
|
|
567
|
-
return runPublicCommand(() => verifyShowCommand(run, cell, options), options.json);
|
|
411
|
+
const caseBatch = program
|
|
412
|
+
.command('case-batch')
|
|
413
|
+
.description('Submit and poll product-fuzzing case batches on the hosted coordinator (CASE-BATCH-V1)');
|
|
414
|
+
caseBatch
|
|
415
|
+
.command('submit')
|
|
416
|
+
.description('Submit an ordered batch of already-generated cases against one frozen world pair')
|
|
417
|
+
.requiredOption('--repository <owner/repo>', 'Exact GitHub owner/repository name')
|
|
418
|
+
.requiredOption('--base <sha>', 'Exact 40-character lowercase base commit SHA')
|
|
419
|
+
.requiredOption('--head <sha>', 'Exact 40-character lowercase head commit SHA')
|
|
420
|
+
.requiredOption('--cases <file>', 'JSON file: the ordered ProductCase array (or { "cases": [...] })')
|
|
421
|
+
.requiredOption('--combinations <file>', 'JSON file: the combination space each case start resolves against')
|
|
422
|
+
.option('--base-world <id>', 'Existing frozen base world (frz_ plus 16 hex); omit to prepare it')
|
|
423
|
+
.option('--head-world <id>', 'Existing frozen head world (frz_ plus 16 hex); omit to prepare it')
|
|
424
|
+
.option('--driver-bundle <sha256>', 'The worlds carry a resident driver running this probe bundle; sealed cases run on it')
|
|
425
|
+
.option('--max-concurrent <n>', 'Concurrency ceiling, 1-500 (default: the case count, capped at 500)')
|
|
426
|
+
.option('--per-case-wall-ms <ms>', 'Per-case wall budget, 1000-600000 (default 120000)')
|
|
427
|
+
.option('--total-budget-ms <ms>', 'Launch budget for the batch, 10000-1800000 (default 1800000)')
|
|
428
|
+
.option('--warm-settle-ms <ms>', 'Adopt only warm clones ready at least this long, 0 to the per-case wall (default: absent = 0)')
|
|
429
|
+
.option('--idempotency-key <key>', 'Stable retry key (default: deterministic from the whole submission)')
|
|
430
|
+
.option('--source-patch <file>', 'JSON file with the precompute-captured { cacheKey, patchSha256, patchGzBase64 }')
|
|
431
|
+
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
432
|
+
.option('--json', 'Versioned machine-readable admission receipt')
|
|
433
|
+
.addHelpText('after', `
|
|
434
|
+
Inputs travel inline and the whole request is bounded at 8 MiB. No source patch
|
|
435
|
+
is sent unless --source-patch names one.
|
|
436
|
+
|
|
437
|
+
--max-concurrent is a ceiling, not a reservation: admitted concurrency is the
|
|
438
|
+
smaller of it and what the fleet's free capacity allows, and the batch's own
|
|
439
|
+
receipt reports both. --per-case-wall-ms kills a case that overruns and records
|
|
440
|
+
it as UNKNOWN/wall-budget rather than dropping it. --total-budget-ms bounds
|
|
441
|
+
launches only; cases already launched run to their own wall budget.
|
|
442
|
+
|
|
443
|
+
Example:
|
|
444
|
+
haystack case-batch submit --repository owner/repo --base <sha> --head <sha> \\
|
|
445
|
+
--cases cases.json --combinations space.json --max-concurrent 200 --json
|
|
446
|
+
`)
|
|
447
|
+
.action(async (options) => {
|
|
448
|
+
const { caseBatchSubmitCommand } = await import('./commands/case-batch.js');
|
|
449
|
+
return runPublicCommand(() => caseBatchSubmitCommand(options), options.json);
|
|
568
450
|
});
|
|
569
|
-
|
|
570
|
-
.command('
|
|
571
|
-
.description('
|
|
572
|
-
.argument('<run>',
|
|
573
|
-
.
|
|
574
|
-
.option('--
|
|
575
|
-
.option('--
|
|
576
|
-
.option('--
|
|
577
|
-
.option('--
|
|
578
|
-
.
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
451
|
+
caseBatch
|
|
452
|
+
.command('status')
|
|
453
|
+
.description('Read a case batch and every per-case outcome, optionally watching until terminal')
|
|
454
|
+
.argument('<run-id>', 'Case batch run id (cv_ followed by 48 lowercase hex characters)')
|
|
455
|
+
.requiredOption('--repository <owner/repo>', 'Exact GitHub owner/repository name')
|
|
456
|
+
.option('--watch', 'Keep polling until the batch is terminal or --max-wall expires')
|
|
457
|
+
.option('--interval <seconds>', 'Polling interval while watching, 1-300 (default 10)')
|
|
458
|
+
.option('--max-wall <minutes>', 'Maximum total polling wall, 1-240 (default 45)')
|
|
459
|
+
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
460
|
+
.option('--json', 'Versioned machine-readable snapshot with every per-case row')
|
|
461
|
+
.addHelpText('after', `
|
|
462
|
+
Case rows are read with bounded pagination and the cursor is followed to the
|
|
463
|
+
end of the list. A batch that ends incomplete still prints every per-case
|
|
464
|
+
outcome; the command then exits 2 with a summary of what is missing. Exit 2
|
|
465
|
+
also covers a cancelled batch, unproven cleanup, and a wait that hits
|
|
466
|
+
--max-wall.
|
|
467
|
+
|
|
468
|
+
Example:
|
|
469
|
+
haystack case-batch status cv_<48 hex> --repository owner/repo --watch --json
|
|
470
|
+
`)
|
|
471
|
+
.action(async (runId, options) => {
|
|
472
|
+
const { caseBatchStatusCommand } = await import('./commands/case-batch.js');
|
|
473
|
+
return runPublicCommand(() => caseBatchStatusCommand(runId, options), options.json);
|
|
582
474
|
});
|
|
583
|
-
|
|
584
|
-
.command('
|
|
585
|
-
.description('
|
|
586
|
-
.
|
|
587
|
-
.
|
|
588
|
-
.option('--
|
|
589
|
-
.option('--
|
|
590
|
-
.
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
475
|
+
caseBatch
|
|
476
|
+
.command('cancel')
|
|
477
|
+
.description('Request cancellation of an owned case batch')
|
|
478
|
+
.argument('<run-id>', 'Case batch run id (cv_ followed by 48 lowercase hex characters)')
|
|
479
|
+
.requiredOption('--repository <owner/repo>', 'Exact GitHub owner/repository name')
|
|
480
|
+
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
481
|
+
.option('--json', 'Versioned machine-readable cancellation receipt')
|
|
482
|
+
.addHelpText('after', `
|
|
483
|
+
A claimed batch stays draining until its current owner proves physical VM
|
|
484
|
+
cleanup and posts its terminal checkpoint.
|
|
485
|
+
`)
|
|
486
|
+
.action(async (runId, options) => {
|
|
487
|
+
const { caseBatchCancelCommand } = await import('./commands/case-batch.js');
|
|
488
|
+
return runPublicCommand(() => caseBatchCancelCommand(runId, options), options.json);
|
|
595
489
|
});
|
|
596
|
-
|
|
597
|
-
.command('
|
|
598
|
-
.description('
|
|
599
|
-
.argument('<
|
|
600
|
-
.
|
|
601
|
-
.
|
|
602
|
-
.
|
|
603
|
-
.option('--
|
|
604
|
-
.
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
490
|
+
caseBatch
|
|
491
|
+
.command('finalize')
|
|
492
|
+
.description('Make a stranded owned case batch terminal (cancelled or incomplete) and free its world-pair lock')
|
|
493
|
+
.argument('<run-id>', 'Case batch run id (cv_ followed by 48 lowercase hex characters)')
|
|
494
|
+
.requiredOption('--repository <owner/repo>', 'Exact GitHub owner/repository name')
|
|
495
|
+
.requiredOption('--status <status>', 'Terminal status to record: cancelled or incomplete')
|
|
496
|
+
.requiredOption('--reason <text>', 'Why the batch is stranded; recorded as the batch error')
|
|
497
|
+
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
498
|
+
.option('--json', 'Versioned machine-readable finalize receipt')
|
|
499
|
+
.addHelpText('after', `
|
|
500
|
+
For a batch no owner can finish: a retired version no runner claims, a
|
|
501
|
+
checkpoint the service keeps refusing, a dead runner. Accepted only when the
|
|
502
|
+
batch was cancelled, or has gone 15 minutes without an update, and no runner
|
|
503
|
+
holds a live claim on it; otherwise refused with a typed reason. Finalize does
|
|
504
|
+
not assert cleanup: the receipt reports cleanup exactly as the batch last
|
|
505
|
+
recorded it.
|
|
506
|
+
|
|
507
|
+
Example:
|
|
508
|
+
haystack case-batch finalize cv_<48 hex> --repository owner/repo \\
|
|
509
|
+
--status cancelled --reason "v1 batch stranded after the v2 roll" --json
|
|
510
|
+
`)
|
|
511
|
+
.action(async (runId, options) => {
|
|
512
|
+
const { caseBatchFinalizeCommand } = await import('./commands/case-batch.js');
|
|
513
|
+
return runPublicCommand(() => caseBatchFinalizeCommand(runId, options), options.json);
|
|
608
514
|
});
|
|
609
|
-
|
|
610
|
-
.command('
|
|
611
|
-
.description('
|
|
612
|
-
.
|
|
613
|
-
.
|
|
614
|
-
.
|
|
615
|
-
.
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
515
|
+
caseBatch
|
|
516
|
+
.command('bundle')
|
|
517
|
+
.description('Download a case batch replay bundle, verifying every artifact digest')
|
|
518
|
+
.argument('<run-id>', 'Case batch run id (cv_ followed by 48 lowercase hex characters)')
|
|
519
|
+
.requiredOption('--repository <owner/repo>', 'Exact GitHub owner/repository name')
|
|
520
|
+
.requiredOption('--out <dir>', 'Directory to write the verified bundle into')
|
|
521
|
+
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
522
|
+
.option('--only <path>', 'Fetch only this listed artifact, or every artifact under this directory prefix '
|
|
523
|
+
+ '(ending in /); repeatable. The manifest and its pages are always fetched.', (value, previous = []) => [...previous, value])
|
|
524
|
+
.option('--json', 'Versioned machine-readable list of verified artifacts')
|
|
525
|
+
.addHelpText('after', `
|
|
526
|
+
The manifest is fetched first and checked against the digest the service sealed
|
|
527
|
+
it under; that sealed digest is required, because the manifest is the one
|
|
528
|
+
artifact with no parent digest to check it against. Every artifact the manifest
|
|
529
|
+
lists (and every PNG and case records.json those pages list) is then fetched and
|
|
530
|
+
checked against the digest (and, where declared, the byte length) its listing
|
|
531
|
+
names before it is written. Any failure stops the command; it never writes
|
|
532
|
+
unverified bytes.
|
|
533
|
+
|
|
534
|
+
A batch's PNGs are uploaded after it is terminal. A PNG the service reports as
|
|
535
|
+
still uploading is not written: it is listed as pending (\`pending\` in --json)
|
|
536
|
+
and the command exits 75 once everything else is verified. Rerun it, or fetch
|
|
537
|
+
just those paths with --only.
|
|
538
|
+
`)
|
|
539
|
+
.action(async (runId, options) => {
|
|
540
|
+
const { caseBatchBundleCommand } = await import('./commands/case-batch.js');
|
|
541
|
+
return runPublicCommand(() => caseBatchBundleCommand(runId, options), options.json);
|
|
627
542
|
});
|
|
628
543
|
program
|
|
629
544
|
.command('login')
|
|
@@ -720,37 +635,39 @@ program
|
|
|
720
635
|
.option('--no-auto-merge', 'Do not apply the auto-merge label')
|
|
721
636
|
.option('--no-wait', 'Skip waiting for analysis results')
|
|
722
637
|
.option('--json', 'Machine-readable: one JSON document on stdout (see `haystack schema submit`), progress on stderr')
|
|
723
|
-
.option('--
|
|
724
|
-
.option('--triage-timeout <seconds>', 'Wall-clock timeout per triage checker in seconds (overrides .haystack.json triage.timeoutMs)', (v) => parseInt(v, 10))
|
|
638
|
+
.option('--triage-timeout <seconds>', 'Abort a triage checker after this many seconds with no data from the model (overrides .haystack.json triage.timeoutMs)', (v) => parseInt(v, 10))
|
|
725
639
|
.addHelpText('after', `
|
|
726
640
|
This command is designed for AI coding agents to submit PRs.
|
|
727
641
|
|
|
728
|
-
1. Runs pre-PR triage (code review and rules)
|
|
642
|
+
1. Runs pre-PR triage (code review and rules) on gpt-6-astra
|
|
729
643
|
2. Pushes the current branch to origin
|
|
730
644
|
3. Creates a pull request on GitHub
|
|
731
645
|
4. Waits for Haystack analysis results (triggered via GitHub App webhook)
|
|
732
646
|
|
|
733
647
|
Pre-PR Triage:
|
|
734
|
-
Before creating the PR, haystack
|
|
735
|
-
|
|
736
|
-
•
|
|
648
|
+
Before creating the PR, haystack sends the diff to gpt-6-astra (OpenAI
|
|
649
|
+
Responses API, reasoning effort "max", structured output) in parallel for:
|
|
650
|
+
• Code review bugs (logic errors, null crashes, security issues, secrets)
|
|
651
|
+
• Rule violations (from .haystack/pr-rules.yml and CLAUDE.md/AGENTS.md etc.)
|
|
652
|
+
|
|
653
|
+
Uses OPENAI_API_KEY if set, else the SSM parameter
|
|
654
|
+
/haystack/secrets/shared/prod/OPENAI_API_KEY (us-west-2) via the default
|
|
655
|
+
AWS credential chain. If a checker call fails, submit prints
|
|
656
|
+
"<checker> failed: <error>" and continues; only findings with severity
|
|
657
|
+
"error" block the PR.
|
|
737
658
|
|
|
738
659
|
Use --force to skip triage entirely.
|
|
739
660
|
Use --no-wait to skip waiting for analysis results.
|
|
740
661
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
Applies the same N to both.
|
|
745
|
-
• --triage-timeout <sec> Raise/lower the per-checker wall-clock timeout
|
|
746
|
-
(default: 180s).
|
|
662
|
+
• --triage-timeout <sec> Abort a checker when the model stream has been
|
|
663
|
+
silent this long (default: 300s). There is no
|
|
664
|
+
total-time cap.
|
|
747
665
|
|
|
748
|
-
Persist
|
|
666
|
+
Persist it in .haystack.json to apply per project:
|
|
749
667
|
|
|
750
668
|
{
|
|
751
669
|
"triage": {
|
|
752
|
-
"
|
|
753
|
-
"timeoutMs": 240000
|
|
670
|
+
"timeoutMs": 300000
|
|
754
671
|
}
|
|
755
672
|
}
|
|
756
673
|
|
|
@@ -794,8 +711,7 @@ Examples:
|
|
|
794
711
|
haystack submit --review # ⚠ Blocks auto-merge, needs human approval
|
|
795
712
|
haystack submit --review octocat # ⚠ Blocks auto-merge, requests review from octocat
|
|
796
713
|
haystack submit --account octocat # Use a specific saved Haystack account for this submit
|
|
797
|
-
haystack submit --
|
|
798
|
-
haystack submit --triage-timeout 300 # Raise triage wall-clock to 5 minutes
|
|
714
|
+
haystack submit --triage-timeout 600 # Allow 10 minutes of model silence per checker
|
|
799
715
|
`)
|
|
800
716
|
.action(async (options, command) => {
|
|
801
717
|
// Resolve --auto-merge / --auto-fix defaults from .haystack.json when not
|
|
@@ -1285,12 +1201,14 @@ hooks
|
|
|
1285
1201
|
.command('install-session')
|
|
1286
1202
|
.description('Install session-start and Stop hooks for coding CLIs')
|
|
1287
1203
|
.option('--cli <name>', 'Target CLI: claude, codex, gemini, or all')
|
|
1204
|
+
.option('--shared', 'Write Claude Code hooks to the committed .claude/settings.json instead of settings.local.json')
|
|
1288
1205
|
.addHelpText('after', `
|
|
1289
1206
|
Configures your coding CLI to run \`haystack triage --hook\` on session start.
|
|
1290
1207
|
This shows pending PR analysis results when you open a new terminal session.
|
|
1291
1208
|
Claude Code also runs \`haystack verify precompute --hook\` when a session stops.
|
|
1292
1209
|
|
|
1293
|
-
Claude Code: Native SessionStart and Stop hooks (.claude/settings.json
|
|
1210
|
+
Claude Code: Native SessionStart and Stop hooks (.claude/settings.local.json;
|
|
1211
|
+
--shared writes the committed .claude/settings.json)
|
|
1294
1212
|
Codex CLI: AGENTS.md instructions
|
|
1295
1213
|
Gemini CLI: GEMINI.md instructions
|
|
1296
1214
|
|
|
@@ -1548,4 +1466,10 @@ program
|
|
|
1548
1466
|
if (process.argv.length === 2) {
|
|
1549
1467
|
program.help();
|
|
1550
1468
|
}
|
|
1551
|
-
program.
|
|
1469
|
+
await program.parseAsync();
|
|
1470
|
+
// Runs only after the command's action has settled; --version, --help and
|
|
1471
|
+
// commands that call process.exit never reach it. See src/utils/update-check.ts.
|
|
1472
|
+
{
|
|
1473
|
+
const { notifyIfUpdateAvailable } = await import('./utils/update-check.js');
|
|
1474
|
+
await notifyIfUpdateAvailable(getVersion());
|
|
1475
|
+
}
|