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