@haystackeditor/cli 0.25.1 → 0.27.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 +39 -463
- package/dist/capture/app-config.js +30 -6
- package/dist/commands/capture-brief.js +45 -35
- package/dist/commands/capture-contract.js +4 -4
- package/dist/commands/crawl-report.js +196 -0
- package/dist/commands/db-profile-upload.js +3 -3
- package/dist/commands/feedback.js +66 -0
- package/dist/commands/init-telemetry.js +55 -9
- package/dist/commands/init.js +8 -3
- package/dist/commands/lockfile-pin.js +307 -0
- package/dist/commands/telemetry-token.js +3 -3
- package/dist/commands/tokens.js +4 -4
- package/dist/commands/verify-explore.js +3 -3
- package/dist/commands/verify-history.js +2 -2
- package/dist/commands/verify-onboarding.js +13 -16
- package/dist/commands/verify-precompute.js +3 -4
- package/dist/commands/verify.js +31 -118
- package/dist/index.js +55 -968
- package/dist/schema.js +4 -10
- package/dist/utils/haystack-api.js +8 -36
- package/package.json +1 -5
- package/schemas/feedback.v1.json +13 -0
- package/schemas/pre-verify.v2.json +240 -0
- package/schemas/verify-raw.v1.json +1132 -0
- package/schemas/verify.v2.json +655 -0
- package/dist/assets/hooks/agent-context/detect.ts +0 -316
- package/dist/assets/hooks/agent-context/format.ts +0 -100
- package/dist/assets/hooks/agent-context/index.ts +0 -41
- package/dist/assets/hooks/agent-context/parsers/claude.ts +0 -262
- package/dist/assets/hooks/agent-context/parsers/codex.ts +0 -416
- package/dist/assets/hooks/agent-context/parsers/gemini.ts +0 -155
- package/dist/assets/hooks/agent-context/parsers/opencode.ts +0 -174
- package/dist/assets/hooks/agent-context/tsconfig.json +0 -14
- package/dist/assets/hooks/agent-context/types.ts +0 -58
- package/dist/assets/hooks/llm-rules-template.md +0 -59
- package/dist/assets/hooks/package-lock.json +0 -598
- package/dist/assets/hooks/package.json +0 -12
- package/dist/assets/hooks/scripts/commit-msg.sh +0 -5
- package/dist/assets/hooks/scripts/post-commit.sh +0 -5
- package/dist/assets/hooks/scripts/pre-commit.sh +0 -175
- package/dist/assets/hooks/scripts/pre-push.sh +0 -25
- package/dist/assets/hooks/scripts/prepare-commit-msg.sh +0 -5
- package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +0 -528
- package/dist/assets/hooks/truncation-checker/index.ts +0 -595
- package/dist/assets/hooks/truncation-checker/tsconfig.json +0 -13
- package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +0 -2051
- package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +0 -4
- package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +0 -3411
- package/dist/assets/skills/map-your-system.md +0 -143
- package/dist/assets/skills/submit.md +0 -200
- package/dist/commands/ask.js +0 -20
- package/dist/commands/cloud-verifier-behaviors.js +0 -218
- package/dist/commands/cloud-verifier-data-store-census.js +0 -539
- package/dist/commands/cloud-verifier-data-store-drift.js +0 -158
- package/dist/commands/cloud-verifier-identity-census.js +0 -4060
- package/dist/commands/cloud-verifier-materialization.js +0 -704
- package/dist/commands/cloud-verifier-pascal-selector-census.js +0 -1382
- package/dist/commands/cloud-verifier-python-manifest-selector-census.js +0 -2015
- package/dist/commands/cloud-verifier-specialized-operational-census.js +0 -11432
- package/dist/commands/cloud-verifier-universe.js +0 -10178
- package/dist/commands/config.js +0 -549
- package/dist/commands/design-verify.js +0 -311
- package/dist/commands/dismiss.js +0 -159
- package/dist/commands/hooks.js +0 -226
- package/dist/commands/inbox.js +0 -137
- package/dist/commands/mcp.js +0 -201
- package/dist/commands/policy.js +0 -371
- package/dist/commands/pr-status.js +0 -207
- package/dist/commands/pr.js +0 -105
- package/dist/commands/prepare-universe-review.js +0 -1092
- package/dist/commands/production-source-deny-policy.js +0 -100
- package/dist/commands/request-review.js +0 -74
- package/dist/commands/review.js +0 -191
- package/dist/commands/rules.js +0 -98
- package/dist/commands/scaffold-provisional-universe.js +0 -806
- package/dist/commands/setup.js +0 -1170
- package/dist/commands/skills.js +0 -447
- package/dist/commands/status.js +0 -35
- package/dist/commands/submit.js +0 -745
- package/dist/commands/system-map.js +0 -228
- package/dist/commands/triage.js +0 -598
- package/dist/commands/webhooks.js +0 -241
- package/dist/states.js +0 -46
- package/dist/tools/detect.js +0 -832
- package/dist/triage/astra.js +0 -202
- package/dist/triage/prompts.js +0 -188
- package/dist/triage/runner.js +0 -200
- package/dist/triage/types.js +0 -7
- package/dist/types.js +0 -326
- package/dist/utils/action-output.js +0 -26
- package/dist/utils/analysis-api.js +0 -416
- package/dist/utils/config.js +0 -54
- package/dist/utils/design-verifier-api.js +0 -294
- package/dist/utils/design-verifier-history.js +0 -79
- package/dist/utils/design-verifier-result.js +0 -424
- package/dist/utils/github-api.js +0 -324
- package/dist/utils/pending-state.js +0 -86
- package/dist/utils/pr-ref.js +0 -56
- package/dist/utils/prompter.js +0 -328
- package/schemas/action.v1.json +0 -22
- package/schemas/ask.v1.json +0 -40
- package/schemas/inbox.v1.json +0 -27
- package/schemas/pr-status.v1.json +0 -61
- package/schemas/pr.v1.json +0 -97
- package/schemas/pr.v3.json +0 -45
- package/schemas/setup.v1.json +0 -75
- package/schemas/submit.v1.json +0 -90
- package/schemas/triage.v1.json +0 -103
- package/schemas/triage.v2.json +0 -64
package/dist/index.js
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Haystack CLI
|
|
3
|
+
* Haystack CLI: `haystack verify` runs your app with and without a change and shows what it broke.
|
|
4
4
|
*
|
|
5
|
-
* Set
|
|
6
|
-
*
|
|
5
|
+
* haystack init Set this repository up for `haystack verify` and start onboarding the app
|
|
6
|
+
* haystack verify After a change: crawl the app with and without it, and report what broke
|
|
7
|
+
* haystack feedback Tell the Haystack team what went wrong
|
|
8
|
+
* haystack login Sign in with GitHub
|
|
7
9
|
*
|
|
8
|
-
*
|
|
9
|
-
* npx @haystackeditor/cli init # Set this repository up for `haystack verify`
|
|
10
|
-
* npx @haystackeditor/cli setup # Interactive onboarding wizard
|
|
11
|
-
* npx @haystackeditor/cli status # Check configuration
|
|
12
|
-
* npx @haystackeditor/cli login # Authenticate with GitHub
|
|
13
|
-
* npx @haystackeditor/cli submit # Create a PR (auto-merge or review)
|
|
14
|
-
* npx @haystackeditor/cli triage # Check last submitted PR
|
|
15
|
-
* npx @haystackeditor/cli triage 123 # View analysis results for a PR
|
|
16
|
-
* npx @haystackeditor/cli dismiss 123 # Dismiss findings for a PR
|
|
17
|
-
* npx @haystackeditor/cli mark-reviewed 123 # Mark review as not needed
|
|
18
|
-
* npx @haystackeditor/cli request-review 123 # Tag a PR as needing human review
|
|
19
|
-
* npx @haystackeditor/cli pr-status 123 # Show PR status in Haystack pipeline
|
|
20
|
-
* npx @haystackeditor/cli config # Manage preferences
|
|
10
|
+
* Without a global install: npx -y -p @haystackeditor/cli@latest haystack <command>
|
|
21
11
|
*/
|
|
22
12
|
import { readFileSync } from 'node:fs';
|
|
23
13
|
import { fileURLToPath } from 'node:url';
|
|
@@ -29,7 +19,6 @@ import { lazy } from './lazy.js';
|
|
|
29
19
|
// Command modules load on first use (see src/lazy.ts). Keep new commands
|
|
30
20
|
// on this pattern: a static import here puts its whole dependency graph on
|
|
31
21
|
// the startup path of every invocation, including --version and --help.
|
|
32
|
-
const statusCommand = lazy(() => import('./commands/status.js'), 'statusCommand');
|
|
33
22
|
const initCommand = lazy(() => import('./commands/init.js'), 'initCommand');
|
|
34
23
|
const captureManifestCommand = lazy(() => import('./commands/capture-manifest.js'), 'captureManifestCommand');
|
|
35
24
|
const authListCommand = lazy(() => import('./commands/login.js'), 'authListCommand');
|
|
@@ -37,56 +26,11 @@ const authUseCommand = lazy(() => import('./commands/login.js'), 'authUseCommand
|
|
|
37
26
|
const loginCommand = lazy(() => import('./commands/login.js'), 'loginCommand');
|
|
38
27
|
const logoutCommand = lazy(() => import('./commands/login.js'), 'logoutCommand');
|
|
39
28
|
const whoamiCommand = lazy(() => import('./commands/login.js'), 'whoamiCommand');
|
|
40
|
-
const handleAgenticTool = lazy(() => import('./commands/config.js'), 'handleAgenticTool');
|
|
41
|
-
const handleAutoMerge = lazy(() => import('./commands/config.js'), 'handleAutoMerge');
|
|
42
|
-
const handleAutoFix = lazy(() => import('./commands/config.js'), 'handleAutoFix');
|
|
43
|
-
const handleWaitForReviewers = lazy(() => import('./commands/config.js'), 'handleWaitForReviewers');
|
|
44
|
-
const isAutoMergeEnabled = lazy(() => import('./commands/config.js'), 'isAutoMergeEnabled');
|
|
45
|
-
const isAutoFixEnabled = lazy(() => import('./commands/config.js'), 'isAutoFixEnabled');
|
|
46
|
-
const installSkills = lazy(() => import('./commands/skills.js'), 'installSkills');
|
|
47
|
-
const listSkills = lazy(() => import('./commands/skills.js'), 'listSkills');
|
|
48
|
-
const cloudVerifierUniverseValidateCommand = lazy(() => import('./commands/cloud-verifier-universe.js'), 'cloudVerifierUniverseValidateCommand');
|
|
49
|
-
const cloudVerifierIdentityCensusCommand = lazy(() => import('./commands/cloud-verifier-identity-census.js'), 'cloudVerifierIdentityCensusCommand');
|
|
50
|
-
const cloudVerifierDataStoreDriftCommand = lazy(() => import('./commands/cloud-verifier-data-store-drift.js'), 'cloudVerifierDataStoreDriftCommand');
|
|
51
|
-
const cloudVerifierBehaviorsValidateCommand = lazy(() => import('./commands/cloud-verifier-behaviors.js'), 'cloudVerifierBehaviorsValidateCommand');
|
|
52
|
-
const prepareUniverseReviewCommand = lazy(() => import('./commands/prepare-universe-review.js'), 'prepareUniverseReviewCommand');
|
|
53
|
-
const scaffoldProvisionalUniverseCommand = lazy(() => import('./commands/scaffold-provisional-universe.js'), 'scaffoldProvisionalUniverseCommand');
|
|
54
|
-
const hooksInstall = lazy(() => import('./commands/hooks.js'), 'hooksInstall');
|
|
55
|
-
const hooksStatus = lazy(() => import('./commands/hooks.js'), 'hooksStatus');
|
|
56
|
-
const submitCommand = lazy(() => import('./commands/submit.js'), 'submitCommand');
|
|
57
|
-
const installSessionHooks = lazy(() => import('./commands/install-session-hooks.js'), 'installSessionHooks');
|
|
58
|
-
const sessionHooksStatus = lazy(() => import('./commands/install-session-hooks.js'), 'sessionHooksStatus');
|
|
59
|
-
const listPolicies = lazy(() => import('./commands/policy.js'), 'listPolicies');
|
|
60
|
-
const addPolicy = lazy(() => import('./commands/policy.js'), 'addPolicy');
|
|
61
|
-
const removePolicy = lazy(() => import('./commands/policy.js'), 'removePolicy');
|
|
62
|
-
const initPolicies = lazy(() => import('./commands/policy.js'), 'initPolicies');
|
|
63
|
-
const addInstruction = lazy(() => import('./commands/policy.js'), 'addInstruction');
|
|
64
|
-
const triageCommand = lazy(() => import('./commands/triage.js'), 'triageCommand');
|
|
65
|
-
const designVerifyCommand = lazy(() => import('./commands/design-verify.js'), 'designVerifyCommand');
|
|
66
|
-
const dismissCommand = lazy(() => import('./commands/dismiss.js'), 'dismissCommand');
|
|
67
|
-
const markReviewedCommand = lazy(() => import('./commands/dismiss.js'), 'markReviewedCommand');
|
|
68
|
-
const undismissCommand = lazy(() => import('./commands/dismiss.js'), 'undismissCommand');
|
|
69
|
-
const requestReviewCommand = lazy(() => import('./commands/request-review.js'), 'requestReviewCommand');
|
|
70
|
-
const reviewCommand = lazy(() => import('./commands/review.js'), 'reviewCommand');
|
|
71
|
-
const prStatusCommand = lazy(() => import('./commands/pr-status.js'), 'prStatusCommand');
|
|
72
|
-
const prReadCommand = lazy(() => import('./commands/pr.js'), 'prReadCommand');
|
|
73
|
-
const inboxListCommand = lazy(() => import('./commands/inbox.js'), 'inboxListCommand');
|
|
74
|
-
const askHaystackCommand = lazy(() => import('./commands/ask.js'), 'askHaystackCommand');
|
|
75
29
|
const telemetryInstrumentCommand = lazy(() => import('./commands/telemetry.js'), 'telemetryInstrumentCommand');
|
|
76
30
|
const telemetrySettingsCommand = lazy(() => import('./commands/telemetry.js'), 'telemetrySettingsCommand');
|
|
77
31
|
const telemetryTokenCommand = lazy(() => import('./commands/telemetry-token.js'), 'telemetryTokenCommand');
|
|
78
|
-
const setupCommand = lazy(() => import('./commands/setup.js'), 'setupCommand');
|
|
79
32
|
const schemaCommand = lazy(() => import('./commands/schema-cmd.js'), 'schemaCommand');
|
|
80
33
|
const listSchemas = lazy(() => import('./commands/schema-cmd.js'), 'listSchemas');
|
|
81
|
-
const registerWebhook = lazy(() => import('./commands/webhooks.js'), 'registerWebhook');
|
|
82
|
-
const listWebhooks = lazy(() => import('./commands/webhooks.js'), 'listWebhooks');
|
|
83
|
-
const rotateWebhookSecret = lazy(() => import('./commands/webhooks.js'), 'rotateWebhookSecret');
|
|
84
|
-
const setWebhookEnabled = lazy(() => import('./commands/webhooks.js'), 'setWebhookEnabled');
|
|
85
|
-
const listDeliveries = lazy(() => import('./commands/webhooks.js'), 'listDeliveries');
|
|
86
|
-
const replayDelivery = lazy(() => import('./commands/webhooks.js'), 'replayDelivery');
|
|
87
|
-
const editRulesCommand = lazy(() => import('./commands/rules.js'), 'editRulesCommand');
|
|
88
|
-
const validateRulesCommand = lazy(() => import('./commands/rules.js'), 'validateRulesCommand');
|
|
89
|
-
const systemMapValidateCommand = lazy(() => import('./commands/system-map.js'), 'systemMapValidateCommand');
|
|
90
34
|
const verifyCommand = lazy(() => import('./commands/verify.js'), 'verifyCommand');
|
|
91
35
|
const preVerifyCommand = lazy(() => import('./commands/verify.js'), 'preVerifyCommand');
|
|
92
36
|
const headlessLoginCommand = lazy(() => import('./commands/tokens.js'), 'headlessLoginCommand');
|
|
@@ -96,12 +40,7 @@ const revokeTokenCommand = lazy(() => import('./commands/tokens.js'), 'revokeTok
|
|
|
96
40
|
const databaseProfileCommand = lazy(() => import('./commands/db-profile.js'), 'databaseProfileCommand');
|
|
97
41
|
const databaseProfileShowCommand = lazy(() => import('./commands/db-profile-show.js'), 'databaseProfileShowCommand');
|
|
98
42
|
const databaseProfileUploadCommand = lazy(() => import('./commands/db-profile-upload.js'), 'databaseProfileUploadCommand');
|
|
99
|
-
|
|
100
|
-
// It is the only module that pulls in `@modelcontextprotocol/sdk`; keeping it out of
|
|
101
|
-
// the startup import graph means a missing/broken SDK can never crash core commands
|
|
102
|
-
// like `triage`/`submit`. (0.15.12 shipped `mcp.js` but published its package.json
|
|
103
|
-
// WITHOUT `@modelcontextprotocol/sdk`, so the eager import here hard-crashed every
|
|
104
|
-
// command at startup with ERR_MODULE_NOT_FOUND.)
|
|
43
|
+
const feedbackCommand = lazy(() => import('./commands/feedback.js'), 'feedbackCommand');
|
|
105
44
|
async function runPublicCommand(action, json) {
|
|
106
45
|
try {
|
|
107
46
|
await action();
|
|
@@ -143,7 +82,7 @@ program
|
|
|
143
82
|
// ignored them. Every command created below inherits this setting.
|
|
144
83
|
.allowExcessArguments();
|
|
145
84
|
program
|
|
146
|
-
.command('schema [name]')
|
|
85
|
+
.command('schema [name]', { hidden: true })
|
|
147
86
|
.description('Print the JSON Schema for a command\'s --json output')
|
|
148
87
|
.action((name) => (name ? schemaCommand(name) : listSchemas()));
|
|
149
88
|
program
|
|
@@ -220,52 +159,6 @@ Examples:
|
|
|
220
159
|
haystack init --yes --json --notes haystack-notes.json
|
|
221
160
|
`)
|
|
222
161
|
.action(options => runPublicCommand(() => initCommand(options), options.json));
|
|
223
|
-
program
|
|
224
|
-
.command('setup')
|
|
225
|
-
.description('Onboarding wizard — scan repos for rules/policies and write Haystack config')
|
|
226
|
-
.option('--json', 'Agent mode: emit NDJSON events on stdout, read answers on stdin (no TTY prompts)')
|
|
227
|
-
.option('--repo <repo...>', 'Repo(s) to configure (owner/name); skips the repo picker')
|
|
228
|
-
.option('-y, --yes', 'Accept defaults non-interactively (keep all discovered items, confirm write, ENABLE auto-merge — pass --no-auto-merge to opt out)')
|
|
229
|
-
// Only the negative form exists; there is no --auto-merge. Without the flag the
|
|
230
|
-
// wizard asks (detected via getOptionValueSource below).
|
|
231
|
-
.option('--no-auto-merge', 'Turn auto-merge off in the written config (omit to be asked)')
|
|
232
|
-
.option('--answers <file>', 'JSON file of {questionId: value} pre-supplied answers')
|
|
233
|
-
.addHelpText('after', `
|
|
234
|
-
Steps: verify GitHub App → select repos → scan (rules/policies/instructions) →
|
|
235
|
-
review → write .haystack/pr-rules.yml + review-policy.md + .haystack.json.
|
|
236
|
-
|
|
237
|
-
Three ways to run:
|
|
238
|
-
• Interactive (default): prompts in your terminal.
|
|
239
|
-
• Autonomous/CI: supply every decision via flags, e.g.
|
|
240
|
-
haystack setup --repo owner/name --yes --no-auto-merge
|
|
241
|
-
• Coding agent: --json speaks NDJSON on stdout and reads answers on stdin, so an
|
|
242
|
-
agent can auto-answer or relay questions to the user. Pre-supplied answers
|
|
243
|
-
(flags / --answers) skip the matching question.
|
|
244
|
-
|
|
245
|
-
Requires authentication — run \`haystack login\` first.
|
|
246
|
-
|
|
247
|
-
Examples:
|
|
248
|
-
haystack setup
|
|
249
|
-
haystack setup --repo owner/name --yes
|
|
250
|
-
haystack setup --json --repo owner/name
|
|
251
|
-
`)
|
|
252
|
-
.action((options, command) => {
|
|
253
|
-
// auto-merge: false when --no-auto-merge was passed, otherwise undefined (ask).
|
|
254
|
-
// commander defaults autoMerge to true because of the --no- form, so check
|
|
255
|
-
// the value source rather than the value.
|
|
256
|
-
const autoMergeExplicit = command.getOptionValueSource('autoMerge') === 'cli';
|
|
257
|
-
return setupCommand({
|
|
258
|
-
json: options.json,
|
|
259
|
-
repos: options.repo,
|
|
260
|
-
yes: options.yes,
|
|
261
|
-
autoMerge: autoMergeExplicit ? options.autoMerge : undefined,
|
|
262
|
-
answersFile: options.answers,
|
|
263
|
-
});
|
|
264
|
-
});
|
|
265
|
-
program
|
|
266
|
-
.command('status')
|
|
267
|
-
.description('Check if .haystack.json exists and is valid')
|
|
268
|
-
.action(statusCommand);
|
|
269
162
|
const verify = program
|
|
270
163
|
.command('verify')
|
|
271
164
|
.description('Crawl the running app with your current change: where it shows up and what broke')
|
|
@@ -277,7 +170,8 @@ const verify = program
|
|
|
277
170
|
.option('--intent <sentence>', 'What you were asked to do, in the user\'s words (the task, not what your code does): the judge checks the app against it')
|
|
278
171
|
.option('--idea <text>', 'Something to try in the app, said as you would to a tester (repeat for more); each is explored first', (value, previous = []) => [...previous, value])
|
|
279
172
|
.option('--ideas-file <path>', 'A JSON array of more ideas')
|
|
280
|
-
.option('--json', 'The
|
|
173
|
+
.option('--json', 'The report as one JSON document on stdout (see `haystack schema verify`)')
|
|
174
|
+
.option('--raw', 'The service\'s whole crawl record as one JSON document, for scripts (large; see `haystack schema verify-raw`)')
|
|
281
175
|
.addHelpText('after', `
|
|
282
176
|
Run inside a git checkout; nothing needs to be committed or pushed. It captures
|
|
283
177
|
the checkout (committed and uncommitted changes) exactly as the stop hook does,
|
|
@@ -333,7 +227,7 @@ Examples:
|
|
|
333
227
|
haystack verify history owner/repo --limit 20
|
|
334
228
|
haystack verify hosted status cv_<48 lowercase hex characters> --wait
|
|
335
229
|
`)
|
|
336
|
-
.action(options => runPublicCommand(() => verifyCommand(options), options.json));
|
|
230
|
+
.action(options => runPublicCommand(() => verifyCommand(options), options.json || options.raw));
|
|
337
231
|
program
|
|
338
232
|
.command('pre-verify')
|
|
339
233
|
.description('Before a check: what your change touches, and what could break in it')
|
|
@@ -347,9 +241,10 @@ same way, builds and prepares the change when nothing has yet (the turn-end
|
|
|
347
241
|
hook usually has), and prints its brief: the functions the change touches, the
|
|
348
242
|
changed ones and those one step away first (every one is in --json), and
|
|
349
243
|
Haystack's own ideas of what could break, each with what to set up, what to do
|
|
350
|
-
and what to watch. With capture set up (.haystack/capture.json
|
|
351
|
-
|
|
352
|
-
never starts a
|
|
244
|
+
and what to watch. With capture set up (an app's .haystack/capture.json, such
|
|
245
|
+
as apps/web/.haystack/capture.json), it also shows, per app, the routes the
|
|
246
|
+
change touches with their share of real users' sessions. It never starts a
|
|
247
|
+
crawl.
|
|
353
248
|
|
|
354
249
|
Then hand back what you were asked to do and what to try:
|
|
355
250
|
haystack verify --intent "<what you were asked to do, in the user's words>" --idea "<something to try>"
|
|
@@ -455,7 +350,7 @@ verify
|
|
|
455
350
|
process.exitCode = 0;
|
|
456
351
|
});
|
|
457
352
|
const hostedVerify = verify
|
|
458
|
-
.command('hosted')
|
|
353
|
+
.command('hosted', { hidden: true })
|
|
459
354
|
.description('Run the production Cloud Verifier against exact GitHub commits');
|
|
460
355
|
hostedVerify
|
|
461
356
|
.command('start')
|
|
@@ -581,7 +476,7 @@ verify
|
|
|
581
476
|
return runPublicCommand(() => verifyExploreCommand(run, caseId, { ...options, repo: options.repo }), options.json);
|
|
582
477
|
});
|
|
583
478
|
const caseBatch = program
|
|
584
|
-
.command('case-batch')
|
|
479
|
+
.command('case-batch', { hidden: true })
|
|
585
480
|
.description('Submit and poll product-fuzzing case batches on the hosted coordinator (CASE-BATCH-V1)');
|
|
586
481
|
caseBatch
|
|
587
482
|
.command('submit')
|
|
@@ -712,6 +607,22 @@ just those paths with --only.
|
|
|
712
607
|
const { caseBatchBundleCommand } = await import('./commands/case-batch.js');
|
|
713
608
|
return runPublicCommand(() => caseBatchBundleCommand(runId, options), options.json);
|
|
714
609
|
});
|
|
610
|
+
program
|
|
611
|
+
.command('feedback <message...>')
|
|
612
|
+
.description('Tell the Haystack team something went wrong or got in your way (- reads the message from stdin)')
|
|
613
|
+
.option('--run <id>', 'The verification run it is about, when there is one')
|
|
614
|
+
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
615
|
+
.option('--json', 'The result as one JSON document on stdout (see `haystack schema feedback`)')
|
|
616
|
+
.addHelpText('after', `
|
|
617
|
+
For coding agents as much as people: when Haystack fails, confuses you or is
|
|
618
|
+
missing something, say so in a sentence and keep going. The team sees it at
|
|
619
|
+
once, with the CLI version, the platform and this checkout's repository.
|
|
620
|
+
|
|
621
|
+
Examples:
|
|
622
|
+
haystack feedback "verify said the app never started, but it was up on :3000"
|
|
623
|
+
haystack feedback --run cv_<id> "the bug it reported is the intended change"
|
|
624
|
+
`)
|
|
625
|
+
.action((words, options) => runPublicCommand(() => feedbackCommand(words, options), options.json));
|
|
715
626
|
program
|
|
716
627
|
.command('login')
|
|
717
628
|
.description('Add or refresh a saved GitHub account (use --headless for CI tokens)')
|
|
@@ -744,7 +655,7 @@ GitHub expires it.
|
|
|
744
655
|
program
|
|
745
656
|
.command('logout [login]')
|
|
746
657
|
.description('Remove a saved GitHub account (use --headless to remove the local CLI token)')
|
|
747
|
-
.option('--headless', 'Remove the local hsk_live_* token file (does not revoke server-side — use `haystack
|
|
658
|
+
.option('--headless', 'Remove the local hsk_live_* token file (does not revoke server-side — use `haystack auth revoke <id>`)')
|
|
748
659
|
.action(async (login, opts) => {
|
|
749
660
|
if (opts.headless) {
|
|
750
661
|
try {
|
|
@@ -758,24 +669,37 @@ program
|
|
|
758
669
|
}
|
|
759
670
|
return logoutCommand(login);
|
|
760
671
|
});
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
672
|
+
program
|
|
673
|
+
.command('whoami')
|
|
674
|
+
.description('Show the active Haystack GitHub account')
|
|
675
|
+
.action(whoamiCommand);
|
|
676
|
+
const authProgram = program
|
|
677
|
+
.command('auth')
|
|
678
|
+
.description('Saved accounts and long-lived (hsk_live) tokens');
|
|
679
|
+
authProgram
|
|
764
680
|
.command('list')
|
|
765
|
-
.description('List
|
|
681
|
+
.description('List saved Haystack GitHub accounts')
|
|
682
|
+
.action(authListCommand);
|
|
683
|
+
authProgram
|
|
684
|
+
.command('use <login>')
|
|
685
|
+
.description('Set the active Haystack GitHub account')
|
|
686
|
+
.action(authUseCommand);
|
|
687
|
+
authProgram
|
|
688
|
+
.command('tokens')
|
|
689
|
+
.description('List your long-lived (hsk_live) tokens')
|
|
766
690
|
.option('--json', 'Machine-readable output')
|
|
767
691
|
.action(async (opts) => {
|
|
768
692
|
try {
|
|
769
693
|
await listTokensCommand(opts);
|
|
770
694
|
}
|
|
771
695
|
catch (err) {
|
|
772
|
-
console.error(chalk.red('
|
|
696
|
+
console.error(chalk.red('tokens failed:'), err instanceof Error ? err.message : err);
|
|
773
697
|
process.exit(1);
|
|
774
698
|
}
|
|
775
699
|
});
|
|
776
|
-
|
|
700
|
+
authProgram
|
|
777
701
|
.command('revoke <id>')
|
|
778
|
-
.description('Revoke a token by id (cannot be undone)')
|
|
702
|
+
.description('Revoke a long-lived token by id (cannot be undone)')
|
|
779
703
|
.action(async (id) => {
|
|
780
704
|
try {
|
|
781
705
|
await revokeTokenCommand(id);
|
|
@@ -785,186 +709,6 @@ tokens
|
|
|
785
709
|
process.exit(1);
|
|
786
710
|
}
|
|
787
711
|
});
|
|
788
|
-
program
|
|
789
|
-
.command('whoami')
|
|
790
|
-
.description('Show the active Haystack GitHub account')
|
|
791
|
-
.action(whoamiCommand);
|
|
792
|
-
const authProgram = program
|
|
793
|
-
.command('auth')
|
|
794
|
-
.description('Manage saved Haystack GitHub accounts');
|
|
795
|
-
authProgram
|
|
796
|
-
.command('list')
|
|
797
|
-
.description('List saved Haystack GitHub accounts')
|
|
798
|
-
.action(authListCommand);
|
|
799
|
-
authProgram
|
|
800
|
-
.command('use <login>')
|
|
801
|
-
.description('Set the active Haystack GitHub account')
|
|
802
|
-
.action(authUseCommand);
|
|
803
|
-
program
|
|
804
|
-
.command('submit')
|
|
805
|
-
.description('Create a PR from current changes')
|
|
806
|
-
.option('--account <login>', 'Saved Haystack account login to use for GitHub API calls')
|
|
807
|
-
.option('--title <title>', 'PR title (default: last commit message)')
|
|
808
|
-
.option('--body <body>', 'PR body (default: commit messages since base)')
|
|
809
|
-
.option('--body-file <path>', 'Read PR body from file (use "-" for stdin)')
|
|
810
|
-
.option('--base <branch>', 'Base branch (default: main or master)')
|
|
811
|
-
.option('--draft', 'Create as draft PR')
|
|
812
|
-
.option('--review [reviewer]', 'Request human review (BLOCKS auto-merge — only use when explicitly asked)')
|
|
813
|
-
.option('--force', 'Skip pre-PR triage checks')
|
|
814
|
-
.addOption(new Option('--auto-fix', 'Alpha auto-fix for entitled repositories').hideHelp())
|
|
815
|
-
.option('--auto-merge', 'Apply auto-merge label (default: from .haystack.json, --no-auto-merge to disable)')
|
|
816
|
-
.option('--no-auto-merge', 'Do not apply the auto-merge label')
|
|
817
|
-
.option('--no-wait', 'Skip waiting for analysis results')
|
|
818
|
-
.option('--json', 'Machine-readable: one JSON document on stdout (see `haystack schema submit`), progress on stderr')
|
|
819
|
-
.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))
|
|
820
|
-
.addHelpText('after', `
|
|
821
|
-
This command is designed for AI coding agents to submit PRs.
|
|
822
|
-
|
|
823
|
-
1. Runs pre-PR triage (code review and rules) on gpt-6-astra
|
|
824
|
-
2. Pushes the current branch to origin
|
|
825
|
-
3. Creates a pull request on GitHub
|
|
826
|
-
4. Waits for Haystack analysis results (triggered via GitHub App webhook)
|
|
827
|
-
|
|
828
|
-
Pre-PR Triage:
|
|
829
|
-
Before creating the PR, haystack sends the diff to gpt-6-astra (OpenAI
|
|
830
|
-
Responses API, reasoning effort "xhigh", structured output) in parallel for:
|
|
831
|
-
• Code review bugs (logic errors, null crashes, security issues, secrets)
|
|
832
|
-
• Rule violations (from .haystack/pr-rules.yml and CLAUDE.md/AGENTS.md etc.)
|
|
833
|
-
|
|
834
|
-
Uses OPENAI_API_KEY if set, else the SSM parameter
|
|
835
|
-
/haystack/secrets/shared/prod/OPENAI_API_KEY (us-west-2) via the default
|
|
836
|
-
AWS credential chain. If a checker call fails, submit prints
|
|
837
|
-
"<checker> failed: <error>" and continues; only findings with severity
|
|
838
|
-
"error" block the PR.
|
|
839
|
-
|
|
840
|
-
Use --force to skip triage entirely.
|
|
841
|
-
Use --no-wait to skip waiting for analysis results.
|
|
842
|
-
|
|
843
|
-
• --triage-timeout <sec> Abort a checker when the model stream has been
|
|
844
|
-
silent this long (default: 300s). There is no
|
|
845
|
-
total-time cap.
|
|
846
|
-
|
|
847
|
-
Persist it in .haystack.json to apply per project:
|
|
848
|
-
|
|
849
|
-
{
|
|
850
|
-
"triage": {
|
|
851
|
-
"timeoutMs": 300000
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
Review Routing:
|
|
856
|
-
If repo auto-merge is enabled in .haystack.json, plain \`haystack submit\`
|
|
857
|
-
enters the auto-merge queue after submission. Otherwise it still runs the
|
|
858
|
-
same Haystack analysis workflow without enabling auto-merge.
|
|
859
|
-
|
|
860
|
-
⚠ Only use --review when the user EXPLICITLY asks for human review.
|
|
861
|
-
--review BLOCKS auto-merge — the PR will NOT merge until a human approves,
|
|
862
|
-
which delays merging. Do not add --review "just to be safe".
|
|
863
|
-
|
|
864
|
-
• --review Labels the PR "haystack:needs-review" for the
|
|
865
|
-
needs-assignment queue (a teammate will pick it up)
|
|
866
|
-
• --review <username> Same label, plus requests review from that GitHub user
|
|
867
|
-
|
|
868
|
-
Machine-readable output (--json):
|
|
869
|
-
stdout carries exactly one JSON document (schema: \`haystack schema submit\`);
|
|
870
|
-
all progress goes to stderr. On success the payload includes the PR ref,
|
|
871
|
-
the resolved title/body and their sources, auto-merge/auto-fix state with
|
|
872
|
-
source, and the analysis outcome. On failure it is an error envelope
|
|
873
|
-
{ status: "error", error: "..." }.
|
|
874
|
-
|
|
875
|
-
Exit codes:
|
|
876
|
-
0 PR created/updated (including verdict "needs_input" — the PR exists;
|
|
877
|
-
read the verdict from the JSON or from \`haystack triage\`)
|
|
878
|
-
1 Hard failure (bad flags, auth, push, PR creation, blocking triage
|
|
879
|
-
findings, or the analysis status check itself failing)
|
|
880
|
-
|
|
881
|
-
Examples:
|
|
882
|
-
haystack submit # Recommended: triage, create PR, auto-merge queue
|
|
883
|
-
haystack submit --json # Agent mode: JSON on stdout, progress on stderr
|
|
884
|
-
haystack submit --force # Skip triage
|
|
885
|
-
haystack submit --no-wait # Don't wait for analysis
|
|
886
|
-
haystack submit --title "Fix auth" # Custom PR title
|
|
887
|
-
haystack submit --body "## Summary" # Custom PR body (inline)
|
|
888
|
-
haystack submit --body-file body.md # Read body from file (supports markdown)
|
|
889
|
-
echo "## Summary" | haystack submit --body-file - # Read body from stdin
|
|
890
|
-
haystack submit --base develop # Target a different base branch
|
|
891
|
-
haystack submit --draft # Create as draft PR
|
|
892
|
-
haystack submit --review # ⚠ Blocks auto-merge, needs human approval
|
|
893
|
-
haystack submit --review octocat # ⚠ Blocks auto-merge, requests review from octocat
|
|
894
|
-
haystack submit --account octocat # Use a specific saved Haystack account for this submit
|
|
895
|
-
haystack submit --triage-timeout 600 # Allow 10 minutes of model silence per checker
|
|
896
|
-
`)
|
|
897
|
-
.action(async (options, command) => {
|
|
898
|
-
// Resolve --auto-merge / --auto-fix defaults from .haystack.json when not
|
|
899
|
-
// explicitly set, and record WHERE each value came from so submit can
|
|
900
|
-
// announce the source (silent config-driven defaults are invisible to
|
|
901
|
-
// the caller otherwise).
|
|
902
|
-
const autoMergeFromCli = command.getOptionValueSource('autoMerge') === 'cli';
|
|
903
|
-
const autoFixFromCli = command.getOptionValueSource('autoFix') === 'cli';
|
|
904
|
-
if (!autoMergeFromCli) {
|
|
905
|
-
options.autoMerge = await isAutoMergeEnabled();
|
|
906
|
-
}
|
|
907
|
-
// Auto-fix remains alpha — the explicit --auto-fix flag still surfaces
|
|
908
|
-
// discouragement warnings; opting in via repo config is the supported path.
|
|
909
|
-
if (!autoFixFromCli) {
|
|
910
|
-
options.autoFix = await isAutoFixEnabled();
|
|
911
|
-
}
|
|
912
|
-
options.autoMergeSource = autoMergeFromCli ? 'flag' : 'config';
|
|
913
|
-
options.autoFixSource = autoFixFromCli ? 'flag' : 'config';
|
|
914
|
-
return submitCommand(options);
|
|
915
|
-
});
|
|
916
|
-
program
|
|
917
|
-
.command('triage [pr]')
|
|
918
|
-
.description('View Haystack analysis results for a PR')
|
|
919
|
-
.option('--json', 'Output as JSON')
|
|
920
|
-
.option('--hook', 'Minimal single-line output for session-start hooks')
|
|
921
|
-
.option('--clear', 'Clear pending submit state')
|
|
922
|
-
.option('--no-wait', 'Exit immediately if analysis is still pending')
|
|
923
|
-
.addHelpText('after', `
|
|
924
|
-
Look up triage results (bugs, rule violations, verdict) for any PR.
|
|
925
|
-
When called without a PR identifier, checks the last submitted PR.
|
|
926
|
-
|
|
927
|
-
Runs automatically on CLI session start if session hooks are installed.
|
|
928
|
-
|
|
929
|
-
PR identifier formats:
|
|
930
|
-
(none) Last submitted PR
|
|
931
|
-
123 PR number (uses current repo)
|
|
932
|
-
#123 PR number with hash
|
|
933
|
-
owner/repo#123 Fully qualified
|
|
934
|
-
https://github.com/owner/repo/pull/123 GitHub URL
|
|
935
|
-
|
|
936
|
-
By default, if analysis is still in progress, the command will poll
|
|
937
|
-
for up to 5 minutes. Use --no-wait to exit immediately instead.
|
|
938
|
-
|
|
939
|
-
Examples:
|
|
940
|
-
haystack triage # Last submitted PR
|
|
941
|
-
haystack triage 42 # Current repo, PR #42
|
|
942
|
-
haystack triage acme/widgets#99 # Specific repo
|
|
943
|
-
haystack triage https://github.com/o/r/pull/1 # From URL
|
|
944
|
-
haystack triage 42 --json # Machine-readable output
|
|
945
|
-
haystack triage 42 --no-wait # Don't wait if pending
|
|
946
|
-
haystack triage --hook # Session-start hook output
|
|
947
|
-
haystack triage --clear # Clear pending state
|
|
948
|
-
`)
|
|
949
|
-
.action(triageCommand);
|
|
950
|
-
const inbox = program.command('inbox').description('List Haystack work needing your attention');
|
|
951
|
-
inbox
|
|
952
|
-
.command('list')
|
|
953
|
-
.description('List PRs in your Haystack inbox')
|
|
954
|
-
.option('--json', 'Output as JSON')
|
|
955
|
-
.action((options) => runPublicCommand(() => inboxListCommand(options), options.json));
|
|
956
|
-
const prProgram = program.command('pr').description('Inspect one pull request');
|
|
957
|
-
prProgram
|
|
958
|
-
.command('get <ref>')
|
|
959
|
-
.description('Get triage and merge blockers')
|
|
960
|
-
.option('--json', 'Output as JSON')
|
|
961
|
-
.action((ref, options) => runPublicCommand(() => prReadCommand(ref, options), options.json));
|
|
962
|
-
program
|
|
963
|
-
.command('ask <ref> <question>')
|
|
964
|
-
.description('Ask Haystack about a pull request')
|
|
965
|
-
.option('--json', 'Output the answer and every consulted customer-facing source as JSON')
|
|
966
|
-
.option('--session <id>', 'Continue a previous Ask Haystack session')
|
|
967
|
-
.action((ref, question, options) => runPublicCommand(() => askHaystackCommand(ref, question, options), options.json));
|
|
968
712
|
const telemetry = program.command('telemetry').description('Add privacy-safe production telemetry without an application SDK')
|
|
969
713
|
.addHelpText('after', `
|
|
970
714
|
Next.js bundles its server code, so instead of \`instrument\` add the CLI as a
|
|
@@ -1098,583 +842,6 @@ start prints why no stand-in was used and names the declared ids; upload again w
|
|
|
1098
842
|
that dependency; when both apply, the newer upload is used.
|
|
1099
843
|
`)
|
|
1100
844
|
.action((file, options) => runPublicCommand(() => databaseProfileUploadCommand(file, options)));
|
|
1101
|
-
program
|
|
1102
|
-
.command('dismiss <pr>')
|
|
1103
|
-
.description('Dismiss analysis findings for a PR')
|
|
1104
|
-
.option('--json', 'Machine-readable output (see `haystack schema action`)')
|
|
1105
|
-
.addHelpText('after', `
|
|
1106
|
-
Dismiss analysis findings for a PR, moving it from "Issues Found" to
|
|
1107
|
-
"Good to Merge" in the Haystack feed.
|
|
1108
|
-
|
|
1109
|
-
The override is tied to the PR's current HEAD commit. If a new commit is
|
|
1110
|
-
pushed, the override is invalidated and you'll need to dismiss again.
|
|
1111
|
-
|
|
1112
|
-
PR identifier formats:
|
|
1113
|
-
123 PR number (uses current repo)
|
|
1114
|
-
#123 PR number with hash
|
|
1115
|
-
owner/repo#123 Fully qualified
|
|
1116
|
-
https://github.com/owner/repo/pull/123 GitHub URL
|
|
1117
|
-
|
|
1118
|
-
Examples:
|
|
1119
|
-
haystack dismiss 42 # Dismiss findings for PR #42
|
|
1120
|
-
haystack dismiss acme/widgets#99 # Dismiss for specific repo
|
|
1121
|
-
`)
|
|
1122
|
-
.action(dismissCommand);
|
|
1123
|
-
program
|
|
1124
|
-
.command('design-verify <pr>')
|
|
1125
|
-
// "verification" is a banned word in top-level help (public-contract test
|
|
1126
|
-
// keeps retired auto-fix/verification wording out of the CLI surface).
|
|
1127
|
-
.description('Run a blind design-by-execution review of a PR')
|
|
1128
|
-
.option('--json', 'Machine-readable output')
|
|
1129
|
-
.option('--history', 'Show recent execution reliability for this repository without starting a run')
|
|
1130
|
-
.option('--limit <attempts>', 'Maximum recorded terminal attempts to show with --history (1–100, default 20)')
|
|
1131
|
-
.option('--no-wait', 'Return after the run is admitted instead of waiting for its verdict')
|
|
1132
|
-
.option('--run <run-id>', 'Resume and wait for an existing exact run id')
|
|
1133
|
-
.option('--poll-interval <seconds>', 'Status polling interval', '3')
|
|
1134
|
-
.option('--timeout <seconds>', 'Maximum time to wait; the server run continues after timeout', '7200')
|
|
1135
|
-
.option('--case <case-id>', 'Select one exact generated case for universe exploration')
|
|
1136
|
-
.option('--universe <role>', 'Select control-before, before, or after')
|
|
1137
|
-
.option('--replay', 'Replay the registered case command in the selected universe')
|
|
1138
|
-
.option('--exec-json <argv>', 'Run a JSON argv array in the selected universe')
|
|
1139
|
-
.option('--cwd <path>', 'Repository-relative working directory for --exec-json')
|
|
1140
|
-
.option('--command-timeout <seconds>', 'Maximum selected-universe command runtime')
|
|
1141
|
-
.option('--action <action-id>', 'Resume one exact universe action')
|
|
1142
|
-
.addHelpText('after', `
|
|
1143
|
-
Executes the change instead of reading it: infers intent from the diff
|
|
1144
|
-
alone, designs pre-registered behavioral test cases, runs control-before,
|
|
1145
|
-
before, and after in isolated sandboxes, and reports anomaly candidates
|
|
1146
|
-
outside the registered intent.
|
|
1147
|
-
|
|
1148
|
-
The command waits for a behavioral verdict by default. Use --no-wait to
|
|
1149
|
-
return the run id immediately, or --run <run-id> to resume waiting.
|
|
1150
|
-
|
|
1151
|
-
Read recent execution reliability across this repository's PRs:
|
|
1152
|
-
haystack design-verify owner/repo#123 --history --limit 20
|
|
1153
|
-
History covers recorded terminal attempts, including partial or failed runs;
|
|
1154
|
-
it does not measure bug-detection accuracy or whether changes are bug-free.
|
|
1155
|
-
|
|
1156
|
-
After a completed run, inspect or replay one retained case universe without
|
|
1157
|
-
receiving provider credentials:
|
|
1158
|
-
haystack design-verify owner/repo#123 --run RUN --case case-00001 --universe before --replay
|
|
1159
|
-
haystack design-verify owner/repo#123 --run RUN --case case-00001 --universe after --exec-json '["rg","TODO"]'
|
|
1160
|
-
|
|
1161
|
-
PR identifier formats:
|
|
1162
|
-
123 PR number (uses current repo)
|
|
1163
|
-
owner/repo#123 Fully qualified
|
|
1164
|
-
https://github.com/owner/repo/pull/123 GitHub URL
|
|
1165
|
-
`)
|
|
1166
|
-
.action(designVerifyCommand);
|
|
1167
|
-
program
|
|
1168
|
-
.command('mark-reviewed <pr>')
|
|
1169
|
-
.description('Mark human review as not needed for a PR')
|
|
1170
|
-
.option('--json', 'Machine-readable output (see `haystack schema action`)')
|
|
1171
|
-
.addHelpText('after', `
|
|
1172
|
-
Mark human review as not needed for a PR, moving it from "Needs Review"
|
|
1173
|
-
to "Good to Merge" in the Haystack feed.
|
|
1174
|
-
|
|
1175
|
-
The override is tied to the PR's current HEAD commit. If a new commit is
|
|
1176
|
-
pushed, the override is invalidated and you'll need to mark it again.
|
|
1177
|
-
|
|
1178
|
-
PR identifier formats:
|
|
1179
|
-
123 PR number (uses current repo)
|
|
1180
|
-
#123 PR number with hash
|
|
1181
|
-
owner/repo#123 Fully qualified
|
|
1182
|
-
https://github.com/owner/repo/pull/123 GitHub URL
|
|
1183
|
-
|
|
1184
|
-
Examples:
|
|
1185
|
-
haystack mark-reviewed 42 # Mark review not needed for PR #42
|
|
1186
|
-
haystack mark-reviewed acme/widgets#99 # Mark for specific repo
|
|
1187
|
-
`)
|
|
1188
|
-
.action(markReviewedCommand);
|
|
1189
|
-
program
|
|
1190
|
-
.command('undismiss <pr>')
|
|
1191
|
-
.description('Undo a dismiss or mark-reviewed override for a PR')
|
|
1192
|
-
.option('--json', 'Machine-readable output (see `haystack schema action`)')
|
|
1193
|
-
.addHelpText('after', `
|
|
1194
|
-
Clear all overrides (dismissed findings and/or review-not-needed) for a PR,
|
|
1195
|
-
returning it to its original feed bucket.
|
|
1196
|
-
|
|
1197
|
-
PR identifier formats:
|
|
1198
|
-
123 PR number (uses current repo)
|
|
1199
|
-
#123 PR number with hash
|
|
1200
|
-
owner/repo#123 Fully qualified
|
|
1201
|
-
https://github.com/owner/repo/pull/123 GitHub URL
|
|
1202
|
-
|
|
1203
|
-
Examples:
|
|
1204
|
-
haystack undismiss 42 # Undo overrides for PR #42
|
|
1205
|
-
haystack undismiss acme/widgets#99 # Undo for specific repo
|
|
1206
|
-
`)
|
|
1207
|
-
.action(undismissCommand);
|
|
1208
|
-
program
|
|
1209
|
-
.command('review [pr]')
|
|
1210
|
-
.description('Trigger a fresh Haystack analysis for a PR (machine analysis — for HUMAN review use request-review)')
|
|
1211
|
-
.option('--no-wait', 'Exit after triggering instead of waiting for results')
|
|
1212
|
-
.option('--json', 'Machine-readable output (see `haystack schema action`)')
|
|
1213
|
-
.addHelpText('after', `
|
|
1214
|
-
Re-run full Haystack analysis on a PR's current head.
|
|
1215
|
-
|
|
1216
|
-
Haystack analyzes a PR once when it's opened (or marked ready); later
|
|
1217
|
-
pushes only get a resolution check against the existing findings. Use
|
|
1218
|
-
this command when new commits deserve fresh detection, or any time you
|
|
1219
|
-
want a second opinion.
|
|
1220
|
-
|
|
1221
|
-
When called without a PR identifier, uses the last submitted PR.
|
|
1222
|
-
|
|
1223
|
-
PR identifier formats:
|
|
1224
|
-
(none) Last submitted PR
|
|
1225
|
-
123 PR number (uses current repo)
|
|
1226
|
-
#123 PR number with hash
|
|
1227
|
-
owner/repo#123 Fully qualified
|
|
1228
|
-
https://github.com/owner/repo/pull/123 GitHub URL
|
|
1229
|
-
|
|
1230
|
-
Examples:
|
|
1231
|
-
haystack review # Last submitted PR
|
|
1232
|
-
haystack review 42 # Current repo, PR #42
|
|
1233
|
-
haystack review acme/widgets#99 # Specific repo
|
|
1234
|
-
haystack review 42 --no-wait # Trigger and exit
|
|
1235
|
-
`)
|
|
1236
|
-
.action(reviewCommand);
|
|
1237
|
-
program
|
|
1238
|
-
.command('request-review <pr> [reviewer]')
|
|
1239
|
-
.description('Tag a PR as needing human review')
|
|
1240
|
-
.option('--json', 'Machine-readable output (see `haystack schema action`)')
|
|
1241
|
-
.addHelpText('after', `
|
|
1242
|
-
Tag any PR with "needs human review", even after it was created.
|
|
1243
|
-
Adds the haystack:needs-review label to the PR, moving it from
|
|
1244
|
-
"Good to Merge" to the "Needs Assignment" queue in the feed.
|
|
1245
|
-
|
|
1246
|
-
Optionally specify a reviewer to request review from a specific
|
|
1247
|
-
GitHub user.
|
|
1248
|
-
|
|
1249
|
-
PR identifier formats:
|
|
1250
|
-
123 PR number (uses current repo)
|
|
1251
|
-
#123 PR number with hash
|
|
1252
|
-
owner/repo#123 Fully qualified
|
|
1253
|
-
https://github.com/owner/repo/pull/123 GitHub URL
|
|
1254
|
-
|
|
1255
|
-
Examples:
|
|
1256
|
-
haystack request-review 42 # Tag PR #42 for human review
|
|
1257
|
-
haystack request-review 42 octocat # Tag and request review from octocat
|
|
1258
|
-
haystack request-review acme/widgets#99 # Specific repo
|
|
1259
|
-
`)
|
|
1260
|
-
.action(requestReviewCommand);
|
|
1261
|
-
program
|
|
1262
|
-
.command('pr-status <pr>')
|
|
1263
|
-
.description('Show the current Haystack status of a PR')
|
|
1264
|
-
.option('--json', 'Output as JSON')
|
|
1265
|
-
.addHelpText('after', `
|
|
1266
|
-
Show what bucket a PR is in within the Haystack pipeline:
|
|
1267
|
-
analyzing, good-to-merge, issues, needs-assignment, etc.
|
|
1268
|
-
|
|
1269
|
-
PR identifier formats:
|
|
1270
|
-
123 PR number (uses current repo)
|
|
1271
|
-
#123 PR number with hash
|
|
1272
|
-
owner/repo#123 Fully qualified
|
|
1273
|
-
https://github.com/owner/repo/pull/123 GitHub URL
|
|
1274
|
-
|
|
1275
|
-
Examples:
|
|
1276
|
-
haystack pr-status 42 # Current repo, PR #42
|
|
1277
|
-
haystack pr-status acme/widgets#99 # Specific repo
|
|
1278
|
-
haystack pr-status https://github.com/o/r/pull/1 # From URL
|
|
1279
|
-
haystack pr-status 42 --json # Machine-readable output
|
|
1280
|
-
`)
|
|
1281
|
-
.action(prStatusCommand);
|
|
1282
|
-
// Config subcommands
|
|
1283
|
-
const config = program
|
|
1284
|
-
.command('config')
|
|
1285
|
-
.description('Manage user preferences');
|
|
1286
|
-
config
|
|
1287
|
-
.command('agentic-tool [tool]')
|
|
1288
|
-
.description('Set agentic tool (opencode|claude-code|codex|status)')
|
|
1289
|
-
.addHelpText('after', `
|
|
1290
|
-
Tools:
|
|
1291
|
-
opencode OpenCode (Haystack billing) - default
|
|
1292
|
-
claude-code Claude Code (your Claude Max subscription)
|
|
1293
|
-
codex Codex CLI (your ChatGPT subscription)
|
|
1294
|
-
status Show current setting (default)
|
|
1295
|
-
|
|
1296
|
-
This sets your account-level default. Projects can override
|
|
1297
|
-
this in .haystack.json under agentic.tool.
|
|
1298
|
-
|
|
1299
|
-
Examples:
|
|
1300
|
-
haystack config agentic-tool # Show current setting
|
|
1301
|
-
haystack config agentic-tool opencode # Use Haystack billing
|
|
1302
|
-
haystack config agentic-tool claude-code # Use your Claude Max
|
|
1303
|
-
haystack config agentic-tool codex # Use your ChatGPT
|
|
1304
|
-
`)
|
|
1305
|
-
.action(handleAgenticTool);
|
|
1306
|
-
config
|
|
1307
|
-
.command('auto-merge [action]')
|
|
1308
|
-
.description('Auto-merge safe PRs submitted via haystack submit (on|off|status)')
|
|
1309
|
-
.addHelpText('after', `
|
|
1310
|
-
Actions:
|
|
1311
|
-
on, enable, true Enable auto-merge for safe PRs
|
|
1312
|
-
off, disable, false Disable auto-merge
|
|
1313
|
-
status Show current status (default)
|
|
1314
|
-
|
|
1315
|
-
When enabled, PRs submitted via \`haystack submit\` will be
|
|
1316
|
-
automatically merged if Haystack analysis finds no issues
|
|
1317
|
-
(safe to merge). PRs with bugs or rule violations still
|
|
1318
|
-
require manual review.
|
|
1319
|
-
|
|
1320
|
-
Examples:
|
|
1321
|
-
haystack config auto-merge # Show current status
|
|
1322
|
-
haystack config auto-merge on # Enable auto-merge
|
|
1323
|
-
haystack config auto-merge off # Disable auto-merge
|
|
1324
|
-
`)
|
|
1325
|
-
.action(handleAutoMerge);
|
|
1326
|
-
config
|
|
1327
|
-
.command('auto-fix [action]', { hidden: true })
|
|
1328
|
-
.description('(Alpha) Auto-fix mechanical issues on PRs submitted via haystack submit (on|off|status)')
|
|
1329
|
-
.addHelpText('after', `
|
|
1330
|
-
Actions:
|
|
1331
|
-
on, enable, true Enable auto-fix for mechanical issues
|
|
1332
|
-
off, disable, false Disable auto-fix
|
|
1333
|
-
status Show current status (default)
|
|
1334
|
-
|
|
1335
|
-
⚠ Auto-fix is alpha. When enabled, PRs submitted via \`haystack submit\`
|
|
1336
|
-
are labeled haystack:auto-fix. The Haystack analysis pipeline then
|
|
1337
|
-
dispatches the sandbox fixer for issues classified as straightforward
|
|
1338
|
-
and mechanical. Judgment calls, policy violations, and weak coverage
|
|
1339
|
-
findings still surface in the Feed for human review.
|
|
1340
|
-
|
|
1341
|
-
Examples:
|
|
1342
|
-
haystack config auto-fix # Show current status
|
|
1343
|
-
haystack config auto-fix on # Enable auto-fix (alpha)
|
|
1344
|
-
haystack config auto-fix off # Disable auto-fix
|
|
1345
|
-
`)
|
|
1346
|
-
.action(handleAutoFix);
|
|
1347
|
-
config
|
|
1348
|
-
.command('wait-for-reviewers [action] [reviewers...]')
|
|
1349
|
-
.description('Configure which AI reviewers the merge queue waits for')
|
|
1350
|
-
.addHelpText('after', `
|
|
1351
|
-
Actions:
|
|
1352
|
-
list Show configured reviewer bots (default)
|
|
1353
|
-
add <names> Add one or more reviewer bots
|
|
1354
|
-
remove <names> Remove one or more reviewer bots
|
|
1355
|
-
clear Remove all reviewer bots
|
|
1356
|
-
|
|
1357
|
-
Accepts friendly names (cursor, coderabbit) or exact
|
|
1358
|
-
GitHub bot usernames (cursor-bugbot[bot]).
|
|
1359
|
-
|
|
1360
|
-
Stored in .haystack.json under merge_queue.wait_for_reviewer.bots.
|
|
1361
|
-
The merge queue will block merging until ALL configured
|
|
1362
|
-
bots have posted a review or comment on the PR.
|
|
1363
|
-
|
|
1364
|
-
Examples:
|
|
1365
|
-
haystack config wait-for-reviewers # Show status
|
|
1366
|
-
haystack config wait-for-reviewers add cursor # Wait for Cursor BugBot
|
|
1367
|
-
haystack config wait-for-reviewers add cursor coderabbit # Add multiple
|
|
1368
|
-
haystack config wait-for-reviewers add cursor-bugbot[bot] # Raw bot username
|
|
1369
|
-
haystack config wait-for-reviewers remove cursor # Stop waiting
|
|
1370
|
-
haystack config wait-for-reviewers clear # Wait for none
|
|
1371
|
-
`)
|
|
1372
|
-
.action(handleWaitForReviewers);
|
|
1373
|
-
// Skills subcommands
|
|
1374
|
-
const skills = program
|
|
1375
|
-
.command('skills')
|
|
1376
|
-
.description('Manage AI skills for coding agents');
|
|
1377
|
-
skills
|
|
1378
|
-
.command('install')
|
|
1379
|
-
.description('Install portable Haystack skills and optional coding-CLI shims')
|
|
1380
|
-
.option('--cli <name>', 'Target CLI: claude, codex, cursor, or manual')
|
|
1381
|
-
.addHelpText('after', `
|
|
1382
|
-
This installs portable skills in the Git repository's .agents/skills directory:
|
|
1383
|
-
/submit - Submit a PR via Haystack
|
|
1384
|
-
/map-your-system - Map how Haystack QA can run this system
|
|
1385
|
-
/map-cloud-verifier-universe - Map a production-derived hermetic universe
|
|
1386
|
-
Supported CLIs:
|
|
1387
|
-
claude Also install Claude Code command shims
|
|
1388
|
-
codex Use portable .agents/skills discovery only
|
|
1389
|
-
cursor Use portable .agents/skills discovery only
|
|
1390
|
-
manual Install portable skills and show their location
|
|
1391
|
-
|
|
1392
|
-
Examples:
|
|
1393
|
-
haystack skills install # Install portable skills only
|
|
1394
|
-
haystack skills install --cli codex # Install for Codex only
|
|
1395
|
-
haystack skills install --cli claude # Also install Claude command shims
|
|
1396
|
-
`)
|
|
1397
|
-
.action(async (opts) => {
|
|
1398
|
-
try {
|
|
1399
|
-
await installSkills(opts);
|
|
1400
|
-
}
|
|
1401
|
-
catch (err) {
|
|
1402
|
-
console.error(chalk.red('skills install failed:'), err instanceof Error ? err.message : err);
|
|
1403
|
-
process.exit(1);
|
|
1404
|
-
}
|
|
1405
|
-
});
|
|
1406
|
-
skills
|
|
1407
|
-
.command('list')
|
|
1408
|
-
.description('List available Haystack skills')
|
|
1409
|
-
.action(listSkills);
|
|
1410
|
-
skills
|
|
1411
|
-
.command('census-universe')
|
|
1412
|
-
.description('Deterministically census Cloud Verifier source identities before mapping')
|
|
1413
|
-
.option('--json', 'Machine-readable result')
|
|
1414
|
-
.option('--root <path>', 'Repository root (default: current Git worktree)')
|
|
1415
|
-
.action((opts) => runPublicCommand(async () => cloudVerifierIdentityCensusCommand(opts), opts.json));
|
|
1416
|
-
skills
|
|
1417
|
-
.command('data-store-drift')
|
|
1418
|
-
.description('Check whether the detected data stores still match the checked-in map (per-PR drift gate)')
|
|
1419
|
-
.option('--json', 'Machine-readable result')
|
|
1420
|
-
.option('--root <path>', 'Repository root (default: current Git worktree)')
|
|
1421
|
-
.option('--baseline <path>', 'Baseline map path (default: .haystack/cloud-verifier/data-stores.json)')
|
|
1422
|
-
.option('--base-ref <ref>', 'Only run when the diff against this ref touches a store-relevant file')
|
|
1423
|
-
.option('--update', 'Write the current detected stores as the new baseline')
|
|
1424
|
-
.action((opts) => runPublicCommand(async () => cloudVerifierDataStoreDriftCommand(opts), opts.json));
|
|
1425
|
-
skills
|
|
1426
|
-
.command('validate-universe')
|
|
1427
|
-
.description('Validate Cloud Verifier universe artifacts and stable-ID references')
|
|
1428
|
-
.option('--json', 'Machine-readable result')
|
|
1429
|
-
.action(async (opts) => {
|
|
1430
|
-
try {
|
|
1431
|
-
await cloudVerifierUniverseValidateCommand(opts);
|
|
1432
|
-
}
|
|
1433
|
-
catch (err) {
|
|
1434
|
-
console.error(chalk.red('validate-universe failed:'), err instanceof Error ? err.message : err);
|
|
1435
|
-
process.exit(1);
|
|
1436
|
-
}
|
|
1437
|
-
});
|
|
1438
|
-
skills
|
|
1439
|
-
.command('validate-behaviors')
|
|
1440
|
-
.description('Validate customer actions and their exact runtime identities')
|
|
1441
|
-
.option('--json', 'Machine-readable result')
|
|
1442
|
-
.action(async (opts) => {
|
|
1443
|
-
try {
|
|
1444
|
-
await cloudVerifierBehaviorsValidateCommand(opts);
|
|
1445
|
-
}
|
|
1446
|
-
catch (err) {
|
|
1447
|
-
console.error(chalk.red('validate-behaviors failed:'), err instanceof Error ? err.message : err);
|
|
1448
|
-
process.exit(1);
|
|
1449
|
-
}
|
|
1450
|
-
});
|
|
1451
|
-
skills
|
|
1452
|
-
.command('scaffold-provisional-universe')
|
|
1453
|
-
.description('Create a compact missing-authority receipt from strict JSON facts')
|
|
1454
|
-
.requiredOption('--input <path>', 'Strict provisional-universe JSON input file')
|
|
1455
|
-
.action((opts) => runPublicCommand(async () => scaffoldProvisionalUniverseCommand(opts), true));
|
|
1456
|
-
skills
|
|
1457
|
-
.command('prepare-universe-review')
|
|
1458
|
-
.description('Create a tracked-source snapshot with conventional test paths removed')
|
|
1459
|
-
.option('--json', 'Machine-readable isolation result and snapshot manifest')
|
|
1460
|
-
.option('--exclude-inactive-submodule <path...>', 'Omit exact tracked gitlink paths explicitly known to be inactive')
|
|
1461
|
-
.option('--omit-unscannable-files', 'Omit files whose test content cannot be ruled out, instead of blocking the review. '
|
|
1462
|
-
+ 'The reviewer still never sees a test; the omissions are recorded as uncovered scope')
|
|
1463
|
-
.option('--full-receipt', 'Include complete non-blocking path receipts in JSON output')
|
|
1464
|
-
.action((opts) => runPublicCommand(() => prepareUniverseReviewCommand(opts), opts.json));
|
|
1465
|
-
const hooks = program
|
|
1466
|
-
.command('hooks')
|
|
1467
|
-
.description('Manage git hooks for AI agent quality checks');
|
|
1468
|
-
hooks
|
|
1469
|
-
.command('install')
|
|
1470
|
-
.description('Install Haystack git hooks')
|
|
1471
|
-
.option('-f, --force', 'Overwrite existing hooks')
|
|
1472
|
-
.addHelpText('after', `
|
|
1473
|
-
This installs:
|
|
1474
|
-
• Git hooks for AI agent quality checks (pre-commit, commit-msg, etc.)
|
|
1475
|
-
• Agent context detector (identifies AI agent sessions)
|
|
1476
|
-
• Truncation checker (prevents code truncation by LLMs)
|
|
1477
|
-
|
|
1478
|
-
Hooks are installed to <repo>/hooks/ and git is configured to use them.
|
|
1479
|
-
|
|
1480
|
-
Examples:
|
|
1481
|
-
haystack hooks install --force # Overwrite existing hooks
|
|
1482
|
-
`)
|
|
1483
|
-
.action(hooksInstall);
|
|
1484
|
-
hooks
|
|
1485
|
-
.command('status')
|
|
1486
|
-
.description('Check hooks installation status')
|
|
1487
|
-
.action(hooksStatus);
|
|
1488
|
-
hooks
|
|
1489
|
-
.command('install-session')
|
|
1490
|
-
.description('Install session-start and Stop hooks for coding CLIs')
|
|
1491
|
-
.option('--cli <name>', 'Target CLI: claude, codex, gemini, or all')
|
|
1492
|
-
.option('--shared', 'Write Claude Code hooks to the committed .claude/settings.json instead of settings.local.json')
|
|
1493
|
-
.addHelpText('after', `
|
|
1494
|
-
Configures your coding CLI to run \`haystack triage --hook\` on session start.
|
|
1495
|
-
This shows pending PR analysis results when you open a new terminal session.
|
|
1496
|
-
Claude Code also runs \`haystack verify precompute --hook\` when a session stops,
|
|
1497
|
-
which builds and freezes the changed app in the background so that
|
|
1498
|
-
\`haystack verify\` starts from there. It never crawls; \`haystack verify\` does.
|
|
1499
|
-
|
|
1500
|
-
Claude Code: Native SessionStart and Stop hooks (.claude/settings.local.json;
|
|
1501
|
-
--shared writes the committed .claude/settings.json)
|
|
1502
|
-
Codex CLI: AGENTS.md instructions
|
|
1503
|
-
Gemini CLI: GEMINI.md instructions
|
|
1504
|
-
|
|
1505
|
-
Examples:
|
|
1506
|
-
haystack hooks install-session # Auto-detect CLIs
|
|
1507
|
-
haystack hooks install-session --cli claude # Claude Code only
|
|
1508
|
-
haystack hooks install-session --cli all # All detected CLIs
|
|
1509
|
-
`)
|
|
1510
|
-
.action(installSessionHooks);
|
|
1511
|
-
hooks
|
|
1512
|
-
.command('session-status')
|
|
1513
|
-
.description('Check session hook installation status')
|
|
1514
|
-
.action(sessionHooksStatus);
|
|
1515
|
-
// Policy subcommands
|
|
1516
|
-
const policy = program
|
|
1517
|
-
.command('policy')
|
|
1518
|
-
.description('Manage review policies (.haystack/review-policy.md)');
|
|
1519
|
-
policy
|
|
1520
|
-
.command('list')
|
|
1521
|
-
.description('List all review policies')
|
|
1522
|
-
.action(listPolicies);
|
|
1523
|
-
policy
|
|
1524
|
-
.command('add [name]')
|
|
1525
|
-
.description('Add a new review policy interactively')
|
|
1526
|
-
.addHelpText('after', `
|
|
1527
|
-
This command prompts for:
|
|
1528
|
-
• Policy name (e.g., "Infrastructure changes")
|
|
1529
|
-
• File patterns (comma-separated globs, e.g., "terraform/**,*.tf")
|
|
1530
|
-
• Severity (critical, high, medium, low)
|
|
1531
|
-
• Reason (why this requires human review)
|
|
1532
|
-
|
|
1533
|
-
Examples:
|
|
1534
|
-
haystack policy add # Interactive add
|
|
1535
|
-
haystack policy add "Database changes" # Start with name
|
|
1536
|
-
`)
|
|
1537
|
-
.action(addPolicy);
|
|
1538
|
-
policy
|
|
1539
|
-
.command('remove <name>')
|
|
1540
|
-
.description('Remove a review policy by name')
|
|
1541
|
-
.action(removePolicy);
|
|
1542
|
-
policy
|
|
1543
|
-
.command('add-instruction [text]')
|
|
1544
|
-
.description('Add a semantic review instruction')
|
|
1545
|
-
.addHelpText('after', `
|
|
1546
|
-
Instructions are natural language directives that override default review behavior.
|
|
1547
|
-
They apply to all PRs (not file-pattern-gated).
|
|
1548
|
-
|
|
1549
|
-
Examples:
|
|
1550
|
-
haystack policy add-instruction "Never flag weak test coverage as needing review"
|
|
1551
|
-
haystack policy add-instruction # Interactive prompt
|
|
1552
|
-
`)
|
|
1553
|
-
.action(addInstruction);
|
|
1554
|
-
policy
|
|
1555
|
-
.command('init')
|
|
1556
|
-
.description('Create initial review-policy.md with example policies')
|
|
1557
|
-
.option('-f, --force', 'Overwrite existing file')
|
|
1558
|
-
.addHelpText('after', `
|
|
1559
|
-
Creates .haystack/review-policy.md with sensible defaults:
|
|
1560
|
-
• Infrastructure changes (terraform, pulumi, cdk)
|
|
1561
|
-
• Secret files (*.secret*, *.env*, credentials)
|
|
1562
|
-
• CI/CD pipelines (GitHub Actions, GitLab CI, etc.)
|
|
1563
|
-
|
|
1564
|
-
The generated policies appear in Haystack's "Human Review Needed"
|
|
1565
|
-
section when a PR touches matching files.
|
|
1566
|
-
`)
|
|
1567
|
-
.action(initPolicies);
|
|
1568
|
-
// ─── webhooks ────────────────────────────────────────────────────────────────
|
|
1569
|
-
//
|
|
1570
|
-
// `haystack webhooks ...` — register outbound webhook subscriptions and inspect
|
|
1571
|
-
// delivery status. Talks to the haystack-webhooks worker.
|
|
1572
|
-
//
|
|
1573
|
-
// See docs/SPEC-CLI-FIRST-CLASS.md §1 for the wire protocol (HMAC, retries,
|
|
1574
|
-
// idempotency).
|
|
1575
|
-
const webhooks = program
|
|
1576
|
-
.command('webhooks')
|
|
1577
|
-
.description('Manage outbound webhooks');
|
|
1578
|
-
webhooks
|
|
1579
|
-
.command('register')
|
|
1580
|
-
.description('Register a webhook URL for the current repo and mint a fresh HMAC secret')
|
|
1581
|
-
.requiredOption('-u, --url <url>', 'Receiver URL (https only)')
|
|
1582
|
-
.option('-e, --events <events>', 'Comma-separated event list', (val) => val.split(',').map((s) => s.trim()).filter(Boolean))
|
|
1583
|
-
.option('--secret-env <name>', 'Environment-variable name where you\'ll stash the secret (recorded in .haystack.json)')
|
|
1584
|
-
.option('--repo <owner/name>', 'Override the inferred repo')
|
|
1585
|
-
.action(async (opts) => {
|
|
1586
|
-
try {
|
|
1587
|
-
await registerWebhook(opts);
|
|
1588
|
-
}
|
|
1589
|
-
catch (err) {
|
|
1590
|
-
console.error(chalk.red('register failed:'), err instanceof Error ? err.message : err);
|
|
1591
|
-
process.exit(1);
|
|
1592
|
-
}
|
|
1593
|
-
});
|
|
1594
|
-
webhooks
|
|
1595
|
-
.command('list')
|
|
1596
|
-
.description('List active webhook registrations for the current repo')
|
|
1597
|
-
.option('--repo <owner/name>', 'Override the inferred repo')
|
|
1598
|
-
.option('--json', 'Machine-readable JSON output')
|
|
1599
|
-
.action(async (opts) => {
|
|
1600
|
-
try {
|
|
1601
|
-
await listWebhooks(opts);
|
|
1602
|
-
}
|
|
1603
|
-
catch (err) {
|
|
1604
|
-
console.error(chalk.red('list failed:'), err instanceof Error ? err.message : err);
|
|
1605
|
-
process.exit(1);
|
|
1606
|
-
}
|
|
1607
|
-
});
|
|
1608
|
-
webhooks
|
|
1609
|
-
.command('rotate-secret <id>')
|
|
1610
|
-
.description('Rotate the HMAC secret for a registration (prints the new secret once)')
|
|
1611
|
-
.option('--repo <owner/name>', 'Repo the registration belongs to (for account resolution outside its checkout)')
|
|
1612
|
-
.action(async (id, opts) => {
|
|
1613
|
-
try {
|
|
1614
|
-
await rotateWebhookSecret(id, opts.repo);
|
|
1615
|
-
}
|
|
1616
|
-
catch (err) {
|
|
1617
|
-
console.error(chalk.red('rotate-secret failed:'), err instanceof Error ? err.message : err);
|
|
1618
|
-
process.exit(1);
|
|
1619
|
-
}
|
|
1620
|
-
});
|
|
1621
|
-
webhooks
|
|
1622
|
-
.command('disable <id>')
|
|
1623
|
-
.description('Disable a registration (stops delivery; existing in-flight retries continue)')
|
|
1624
|
-
.option('--repo <owner/name>', 'Repo the registration belongs to (for account resolution outside its checkout)')
|
|
1625
|
-
.action(async (id, opts) => {
|
|
1626
|
-
try {
|
|
1627
|
-
await setWebhookEnabled(id, false, opts.repo);
|
|
1628
|
-
}
|
|
1629
|
-
catch (err) {
|
|
1630
|
-
console.error(chalk.red('disable failed:'), err instanceof Error ? err.message : err);
|
|
1631
|
-
process.exit(1);
|
|
1632
|
-
}
|
|
1633
|
-
});
|
|
1634
|
-
webhooks
|
|
1635
|
-
.command('enable <id>')
|
|
1636
|
-
.description('Re-enable a previously disabled registration')
|
|
1637
|
-
.option('--repo <owner/name>', 'Repo the registration belongs to (for account resolution outside its checkout)')
|
|
1638
|
-
.action(async (id, opts) => {
|
|
1639
|
-
try {
|
|
1640
|
-
await setWebhookEnabled(id, true, opts.repo);
|
|
1641
|
-
}
|
|
1642
|
-
catch (err) {
|
|
1643
|
-
console.error(chalk.red('enable failed:'), err instanceof Error ? err.message : err);
|
|
1644
|
-
process.exit(1);
|
|
1645
|
-
}
|
|
1646
|
-
});
|
|
1647
|
-
webhooks
|
|
1648
|
-
.command('deliveries')
|
|
1649
|
-
.description('Show recent webhook delivery attempts for the current repo')
|
|
1650
|
-
.option('--repo <owner/name>', 'Override the inferred repo')
|
|
1651
|
-
.option('--limit <n>', 'Number of deliveries (default 20)', (v) => parseInt(v, 10))
|
|
1652
|
-
.option('--json', 'Machine-readable JSON output')
|
|
1653
|
-
.action(async (opts) => {
|
|
1654
|
-
try {
|
|
1655
|
-
await listDeliveries(opts);
|
|
1656
|
-
}
|
|
1657
|
-
catch (err) {
|
|
1658
|
-
console.error(chalk.red('deliveries failed:'), err instanceof Error ? err.message : err);
|
|
1659
|
-
process.exit(1);
|
|
1660
|
-
}
|
|
1661
|
-
});
|
|
1662
|
-
webhooks
|
|
1663
|
-
.command('replay <id>')
|
|
1664
|
-
.description('Replay a previous delivery (idempotent via X-Haystack-Delivery)')
|
|
1665
|
-
.option('--repo <owner/name>', 'Repo the delivery belongs to (for account resolution outside its checkout)')
|
|
1666
|
-
.action(async (id, opts) => {
|
|
1667
|
-
try {
|
|
1668
|
-
await replayDelivery(id, opts.repo);
|
|
1669
|
-
}
|
|
1670
|
-
catch (err) {
|
|
1671
|
-
console.error(chalk.red('replay failed:'), err instanceof Error ? err.message : err);
|
|
1672
|
-
process.exit(1);
|
|
1673
|
-
}
|
|
1674
|
-
});
|
|
1675
|
-
// ─── admin ───────────────────────────────────────────────────────────────────
|
|
1676
|
-
// Operator commands. The only credential is the operator's Cloudflare Access
|
|
1677
|
-
// token (`cloudflared access token`); exit 3 means an epoch conflict (409).
|
|
1678
845
|
async function runAdminCommand(action) {
|
|
1679
846
|
try {
|
|
1680
847
|
process.exitCode = await action();
|
|
@@ -1684,7 +851,7 @@ async function runAdminCommand(action) {
|
|
|
1684
851
|
process.exitCode = 1;
|
|
1685
852
|
}
|
|
1686
853
|
}
|
|
1687
|
-
const admin = program.command('admin').description('Haystack operator commands (Cloudflare Access)');
|
|
854
|
+
const admin = program.command('admin', { hidden: true }).description('Haystack operator commands (Cloudflare Access)');
|
|
1688
855
|
const fleetPolicy = admin
|
|
1689
856
|
.command('fleet-policy')
|
|
1690
857
|
.description('Fleet policy authority in D1: approved policies per tenant and repository')
|
|
@@ -1748,86 +915,6 @@ fleetPolicy
|
|
|
1748
915
|
const { fleetPolicyHistoryCommand } = await import('./commands/fleet-policy.js');
|
|
1749
916
|
await runAdminCommand(() => fleetPolicyHistoryCommand(repository, options));
|
|
1750
917
|
});
|
|
1751
|
-
// ─── rules ───────────────────────────────────────────────────────────────────
|
|
1752
|
-
const rules = program.command('rules').description('Manage .haystack/pr-rules.yml');
|
|
1753
|
-
rules
|
|
1754
|
-
.command('edit')
|
|
1755
|
-
.description('Open .haystack/pr-rules.yml in $EDITOR (creates a starter if missing)')
|
|
1756
|
-
.action(async () => {
|
|
1757
|
-
try {
|
|
1758
|
-
await editRulesCommand();
|
|
1759
|
-
}
|
|
1760
|
-
catch (err) {
|
|
1761
|
-
console.error(chalk.red('edit failed:'), err instanceof Error ? err.message : err);
|
|
1762
|
-
process.exit(1);
|
|
1763
|
-
}
|
|
1764
|
-
});
|
|
1765
|
-
rules
|
|
1766
|
-
.command('validate')
|
|
1767
|
-
.description('Parse pr-rules.yml with the same parser the server uses')
|
|
1768
|
-
.action(async () => {
|
|
1769
|
-
try {
|
|
1770
|
-
await validateRulesCommand();
|
|
1771
|
-
}
|
|
1772
|
-
catch (err) {
|
|
1773
|
-
console.error(chalk.red('validate failed:'), err instanceof Error ? err.message : err);
|
|
1774
|
-
process.exit(1);
|
|
1775
|
-
}
|
|
1776
|
-
});
|
|
1777
|
-
// ─── system-map ──────────────────────────────────────────────────────────────
|
|
1778
|
-
const systemMap = program
|
|
1779
|
-
.command('system-map')
|
|
1780
|
-
.description('Manage .haystack/system.yml — runtime facts that help Haystack QA run your system');
|
|
1781
|
-
systemMap
|
|
1782
|
-
.command('validate')
|
|
1783
|
-
.description('Check .haystack/system.yml against the rules Haystack QA will apply')
|
|
1784
|
-
.option('--json', 'Machine-readable result')
|
|
1785
|
-
.addHelpText('after', `
|
|
1786
|
-
The system map holds VERIFIED facts about how your system works — install/
|
|
1787
|
-
build commands, how services boot and signal readiness, seed/reset commands,
|
|
1788
|
-
dev-only test logins, external services that must never be touched live, and
|
|
1789
|
-
prebuilt CI artifacts. Haystack QA uses these facts to run more of what it
|
|
1790
|
-
already wants to test; the file never selects tests.
|
|
1791
|
-
|
|
1792
|
-
Author it with the /map-your-system skill (haystack skills install), then run
|
|
1793
|
-
this before committing. Checks: valid YAML + version, no test-selection keys,
|
|
1794
|
-
no secret values, non-empty boot/login/run commands, artifact declarations.
|
|
1795
|
-
|
|
1796
|
-
Examples:
|
|
1797
|
-
haystack system-map validate
|
|
1798
|
-
haystack system-map validate --json
|
|
1799
|
-
`)
|
|
1800
|
-
.action(async (opts) => {
|
|
1801
|
-
try {
|
|
1802
|
-
await systemMapValidateCommand(opts);
|
|
1803
|
-
}
|
|
1804
|
-
catch (err) {
|
|
1805
|
-
console.error(chalk.red('validate failed:'), err instanceof Error ? err.message : err);
|
|
1806
|
-
process.exit(1);
|
|
1807
|
-
}
|
|
1808
|
-
});
|
|
1809
|
-
// ─── mcp ─────────────────────────────────────────────────────────────────────
|
|
1810
|
-
program
|
|
1811
|
-
.command('mcp')
|
|
1812
|
-
.description('Run a stdio MCP server exposing Haystack tools to Claude Code, Cursor, etc.')
|
|
1813
|
-
.action(async () => {
|
|
1814
|
-
try {
|
|
1815
|
-
// Lazy import: only this command needs @modelcontextprotocol/sdk, so we keep it
|
|
1816
|
-
// out of the startup graph (see the note where the eager import used to be).
|
|
1817
|
-
const { runMcpServer } = await import('./commands/mcp.js');
|
|
1818
|
-
await runMcpServer();
|
|
1819
|
-
}
|
|
1820
|
-
catch (err) {
|
|
1821
|
-
const code = err?.code;
|
|
1822
|
-
if (code === 'ERR_MODULE_NOT_FOUND') {
|
|
1823
|
-
console.error(chalk.red('mcp server failed:'), 'the @modelcontextprotocol/sdk package is missing. Reinstall the CLI to pull it in (e.g. `npm i -g @haystackeditor/cli@latest`).');
|
|
1824
|
-
}
|
|
1825
|
-
else {
|
|
1826
|
-
console.error(chalk.red('mcp server failed:'), err instanceof Error ? err.message : err);
|
|
1827
|
-
}
|
|
1828
|
-
process.exit(1);
|
|
1829
|
-
}
|
|
1830
|
-
});
|
|
1831
918
|
// Show help if no command provided
|
|
1832
919
|
if (process.argv.length === 2) {
|
|
1833
920
|
program.help();
|