@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.
Files changed (109) hide show
  1. package/README.md +39 -463
  2. package/dist/capture/app-config.js +30 -6
  3. package/dist/commands/capture-brief.js +45 -35
  4. package/dist/commands/capture-contract.js +4 -4
  5. package/dist/commands/crawl-report.js +196 -0
  6. package/dist/commands/db-profile-upload.js +3 -3
  7. package/dist/commands/feedback.js +66 -0
  8. package/dist/commands/init-telemetry.js +55 -9
  9. package/dist/commands/init.js +8 -3
  10. package/dist/commands/lockfile-pin.js +307 -0
  11. package/dist/commands/telemetry-token.js +3 -3
  12. package/dist/commands/tokens.js +4 -4
  13. package/dist/commands/verify-explore.js +3 -3
  14. package/dist/commands/verify-history.js +2 -2
  15. package/dist/commands/verify-onboarding.js +13 -16
  16. package/dist/commands/verify-precompute.js +3 -4
  17. package/dist/commands/verify.js +31 -118
  18. package/dist/index.js +55 -968
  19. package/dist/schema.js +4 -10
  20. package/dist/utils/haystack-api.js +8 -36
  21. package/package.json +1 -5
  22. package/schemas/feedback.v1.json +13 -0
  23. package/schemas/pre-verify.v2.json +240 -0
  24. package/schemas/verify-raw.v1.json +1132 -0
  25. package/schemas/verify.v2.json +655 -0
  26. package/dist/assets/hooks/agent-context/detect.ts +0 -316
  27. package/dist/assets/hooks/agent-context/format.ts +0 -100
  28. package/dist/assets/hooks/agent-context/index.ts +0 -41
  29. package/dist/assets/hooks/agent-context/parsers/claude.ts +0 -262
  30. package/dist/assets/hooks/agent-context/parsers/codex.ts +0 -416
  31. package/dist/assets/hooks/agent-context/parsers/gemini.ts +0 -155
  32. package/dist/assets/hooks/agent-context/parsers/opencode.ts +0 -174
  33. package/dist/assets/hooks/agent-context/tsconfig.json +0 -14
  34. package/dist/assets/hooks/agent-context/types.ts +0 -58
  35. package/dist/assets/hooks/llm-rules-template.md +0 -59
  36. package/dist/assets/hooks/package-lock.json +0 -598
  37. package/dist/assets/hooks/package.json +0 -12
  38. package/dist/assets/hooks/scripts/commit-msg.sh +0 -5
  39. package/dist/assets/hooks/scripts/post-commit.sh +0 -5
  40. package/dist/assets/hooks/scripts/pre-commit.sh +0 -175
  41. package/dist/assets/hooks/scripts/pre-push.sh +0 -25
  42. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +0 -5
  43. package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +0 -528
  44. package/dist/assets/hooks/truncation-checker/index.ts +0 -595
  45. package/dist/assets/hooks/truncation-checker/tsconfig.json +0 -13
  46. package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +0 -2051
  47. package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +0 -4
  48. package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +0 -3411
  49. package/dist/assets/skills/map-your-system.md +0 -143
  50. package/dist/assets/skills/submit.md +0 -200
  51. package/dist/commands/ask.js +0 -20
  52. package/dist/commands/cloud-verifier-behaviors.js +0 -218
  53. package/dist/commands/cloud-verifier-data-store-census.js +0 -539
  54. package/dist/commands/cloud-verifier-data-store-drift.js +0 -158
  55. package/dist/commands/cloud-verifier-identity-census.js +0 -4060
  56. package/dist/commands/cloud-verifier-materialization.js +0 -704
  57. package/dist/commands/cloud-verifier-pascal-selector-census.js +0 -1382
  58. package/dist/commands/cloud-verifier-python-manifest-selector-census.js +0 -2015
  59. package/dist/commands/cloud-verifier-specialized-operational-census.js +0 -11432
  60. package/dist/commands/cloud-verifier-universe.js +0 -10178
  61. package/dist/commands/config.js +0 -549
  62. package/dist/commands/design-verify.js +0 -311
  63. package/dist/commands/dismiss.js +0 -159
  64. package/dist/commands/hooks.js +0 -226
  65. package/dist/commands/inbox.js +0 -137
  66. package/dist/commands/mcp.js +0 -201
  67. package/dist/commands/policy.js +0 -371
  68. package/dist/commands/pr-status.js +0 -207
  69. package/dist/commands/pr.js +0 -105
  70. package/dist/commands/prepare-universe-review.js +0 -1092
  71. package/dist/commands/production-source-deny-policy.js +0 -100
  72. package/dist/commands/request-review.js +0 -74
  73. package/dist/commands/review.js +0 -191
  74. package/dist/commands/rules.js +0 -98
  75. package/dist/commands/scaffold-provisional-universe.js +0 -806
  76. package/dist/commands/setup.js +0 -1170
  77. package/dist/commands/skills.js +0 -447
  78. package/dist/commands/status.js +0 -35
  79. package/dist/commands/submit.js +0 -745
  80. package/dist/commands/system-map.js +0 -228
  81. package/dist/commands/triage.js +0 -598
  82. package/dist/commands/webhooks.js +0 -241
  83. package/dist/states.js +0 -46
  84. package/dist/tools/detect.js +0 -832
  85. package/dist/triage/astra.js +0 -202
  86. package/dist/triage/prompts.js +0 -188
  87. package/dist/triage/runner.js +0 -200
  88. package/dist/triage/types.js +0 -7
  89. package/dist/types.js +0 -326
  90. package/dist/utils/action-output.js +0 -26
  91. package/dist/utils/analysis-api.js +0 -416
  92. package/dist/utils/config.js +0 -54
  93. package/dist/utils/design-verifier-api.js +0 -294
  94. package/dist/utils/design-verifier-history.js +0 -79
  95. package/dist/utils/design-verifier-result.js +0 -424
  96. package/dist/utils/github-api.js +0 -324
  97. package/dist/utils/pending-state.js +0 -86
  98. package/dist/utils/pr-ref.js +0 -56
  99. package/dist/utils/prompter.js +0 -328
  100. package/schemas/action.v1.json +0 -22
  101. package/schemas/ask.v1.json +0 -40
  102. package/schemas/inbox.v1.json +0 -27
  103. package/schemas/pr-status.v1.json +0 -61
  104. package/schemas/pr.v1.json +0 -97
  105. package/schemas/pr.v3.json +0 -45
  106. package/schemas/setup.v1.json +0 -75
  107. package/schemas/submit.v1.json +0 -90
  108. package/schemas/triage.v1.json +0 -103
  109. package/schemas/triage.v2.json +0 -64
package/README.md CHANGED
@@ -37,49 +37,24 @@ or `HAYSTACK_TELEMETRY_DISABLED=1` to turn this telemetry off.
37
37
 
38
38
  ---
39
39
 
40
- ## For AI agents: the machine-readable contract
41
-
42
- Most consumers of this CLI are coding agents. These invariants hold everywhere:
43
-
44
- - **`--json` means pure stdout.** When `--json` is passed, stdout carries exactly
45
- one JSON document (NDJSON stream for `setup --json`); all progress, spinners,
46
- and prose go to stderr. On failure, stdout still gets a versioned error
47
- envelope (`haystack schema error`: `{"schema_version": "1.0.0", "status":
48
- "error", "error": "..."}`) and the process exits 1.
49
- - **Every JSON payload is versioned and schema'd.** Payloads carry
50
- `schema_version`; print the contract with `haystack schema <name>`
51
- (`haystack schema` lists all: `triage`, `pr`, `pr-status`, `inbox`, `ask`,
52
- `traces`, `submit`, `action`, `cloud-verifier`, `error`, `setup`). Schemas
53
- are JSON-Schema 2020-12 and CI-guarded against drift.
54
- - **One state vocabulary.** All state tokens are snake_case across every
55
- command: verdicts are `good_to_merge` / `needs_review` / `needs_input`;
56
- feed buckets are `analyzing`, `good_to_merge`, `issues`, `needs_assignment`,
57
- `needs_shepherding`, etc. No command emits kebab-case states.
58
- - **PR refs are uniform.** Every command taking a PR accepts `123`, `#123`,
59
- `owner/repo#123`, or a GitHub PR URL. Bare numbers infer the repo from the
60
- `origin` remote.
61
- - **Exit codes**: 0 = the requested action happened (a created PR with findings
62
- is still 0 — read the verdict from the JSON); 1 = it did not. Commands never
63
- print an error and exit 0.
64
- - **Waiting is bounded and skippable.** `submit` waits up to 10 min for
65
- analysis, `triage` polls up to 5 min, `review` up to 25 min; every waiting
66
- command takes `--no-wait`. Non-terminal outcomes include a `next` field with
67
- the follow-up command (usually `haystack triage <ref> --json`).
68
- - **"review" disambiguation**: `haystack review` re-runs the *machine*
69
- analysis. For *human* review use `haystack submit --review` or
70
- `haystack request-review`.
71
-
72
- **The agent workflow for submitting:**
73
-
74
- ```bash
75
- haystack submit --json # emits the submit payload incl. ref + analysis verdict
76
- haystack triage <ref> --json # poll findings later / after --no-wait
77
- ```
78
-
79
- **`haystack mcp`** runs a stdio MCP server exposing the same payloads as tools:
80
- `inbox_list`, `pr_get`, `pr_status`, `triage_get`, `ask_haystack`,
81
- `dismiss`, `mark_reviewed`, `undismiss`, `request_review`, `trigger_review`,
82
- and `schema`.
40
+ ## For coding agents
41
+
42
+ Most users of this CLI are coding agents. What to know:
43
+
44
+ - **After a change, run `haystack verify`** inside the checkout. Nothing needs
45
+ to be committed or pushed. Say what you were asked to do with
46
+ `--intent "<the task, in the user's words>"`, and what to try with
47
+ `--idea "<as you would tell a tester>"` (repeat it for more).
48
+ - **First time in a repository**, `haystack init --yes` sets it up and starts
49
+ onboarding the app. `haystack verify onboarding` shows where onboarding is.
50
+ When onboarding asks a question, answer it with
51
+ `haystack verify answer <question-id> <choice>`.
52
+ - **`--json` means pure stdout.** stdout carries exactly one JSON document and
53
+ everything else goes to stderr. A failure still prints the error envelope
54
+ (`haystack schema error`) and exits 1. `haystack schema <name>` prints any
55
+ command's contract.
56
+ - **When Haystack gets in your way**, tell us in a sentence and keep going:
57
+ `haystack feedback "<what happened>"`. The team sees it at once.
83
58
 
84
59
  **`haystack verify`** shows the product blast radius of your current change:
85
60
  where it shows up in the running app, and what broke. Inside a git checkout
@@ -126,45 +101,25 @@ is no time limit, and Ctrl-C stops waiting, never the crawl. It then prints:
126
101
 
127
102
  `--no-wait` prints the crawl's current state and returns. `--account <login>`
128
103
  picks a saved account, and `--repo owner/repo` names the repository when
129
- `origin` does not. `--json` prints one document, `{ "schema_version", "crawl" }`,
130
- where `crawl` is the crawl as the service returns it, with its `answer` (what it
131
- found so far, or its answer once its time is up) while no sealed `manifest`
132
- exists (or `null` when no crawl of the capture could be started);
133
- `haystack schema verify` prints its schema. Exit codes follow
134
- `haystack case-batch status`: 0 when the crawl answered or finished (the bugs
104
+ `origin` does not. `--json` prints the same report as one document: the
105
+ headline, the findings (bugs first, each with its steps), where the change showed
106
+ up, what became of each idea, and what never ran (`haystack schema verify`).
107
+ `--raw` prints the crawl's whole record as the service returns it, for scripts;
108
+ it is large (`haystack schema verify-raw`). Exit codes: 0 when the crawl
109
+ answered or finished (the bugs
135
110
  it found are in the output) or is still running under `--no-wait`; 2 when it ended
136
111
  without finishing (stopped early, or cancelled because a newer stop in the
137
112
  repository replaced it) or the machines it used could not be proven shut down;
138
113
  1 when the command failed or no crawl could be started.
139
114
 
140
- **The stop hook.** `haystack hooks install-session --cli claude` installs a
141
- Claude Code Stop hook that runs `haystack verify precompute --hook` whenever an
142
- agent turn stops. It captures the checkout within five seconds and hands the
143
- request to a detached sender; the service then starts the change's analysis
144
- and, for a repository set up for crawling, its crawl in the background, so
145
- `haystack verify` usually finds the crawl already running or finished. A newer stop in the same repository replaces an
146
- older crawl that has not finished. `haystack verify precompute` (without
147
- `--hook`) submits the same capture in the foreground and prints one line for
148
- the crawl it started.
149
-
150
- The hosted fleet run of exact pushed commits stays available:
151
-
152
- ```bash
153
- haystack verify hosted start owner/repo --base <sha> --head <sha> --json
154
- haystack verify hosted status cv_<48-lowercase-hex-characters> --wait --json
155
- haystack verify history owner/repo --limit 20
156
- ```
157
-
158
- Intent is optional there: `--intent-file <path>` accepts JSON with exactly
159
- `problem`, `goal`, and `intended_outcomes`. It waits up to 35 minutes by
160
- default; pass `--no-wait` to return after the fleet run is queued. Repeating the
161
- same repository, commits, and intent reuses the same run; choose a new
162
- `--idempotency-key` when a fresh execution is intentional. A fresh run on the
163
- same base reuses the repository's onboarding: phases that finished are not
164
- redone, and a phase that stopped terminally starts again once the fleet runs a
165
- different engine release (on the same release it stays stopped). A wait that expires
166
- prints `timed_out: true` and `next_command`, then exits 2. Use the returned
167
- account-bound `next_command` verbatim on machines with multiple saved accounts.
115
+ **The stop hook.** `haystack init` installs a Claude Code Stop hook that runs
116
+ `haystack verify precompute --hook` whenever an agent turn stops. It captures
117
+ the checkout within five seconds and hands the request to a detached sender;
118
+ the service then starts the change's crawl in the background, so
119
+ `haystack verify` usually finds the crawl already running or finished. A newer
120
+ stop in the same repository replaces an older crawl that has not finished.
121
+ `haystack verify precompute` (without `--hook`) submits the same capture in the
122
+ foreground and prints one line for the crawl it started.
168
123
 
169
124
  Retained fleet cases remain available through their exact case IDs:
170
125
 
@@ -173,67 +128,10 @@ haystack verify cases <run-id> --repo owner/repo
173
128
  haystack verify explore <run-id> <case-id> --repo owner/repo
174
129
  ```
175
130
 
176
- Agents can use the same lifecycle with `haystack verify hosted mcp --account
177
- <login>`. Its tools are `verify_start`, `verify_status`, and `verify_wait`.
178
-
179
- To repeat independent fleet runs for one exact commit pair:
180
-
181
- ```bash
182
- haystack verify hosted reproducibility owner/repo \
183
- --base <40-character-commit-sha> \
184
- --head <40-character-commit-sha> \
185
- --runs 3 --json
186
- ```
187
-
188
- The report includes each run's fleet receipt. Each ordinal has a distinct
189
- idempotency key derived from the printed series key, repository, commits, and
190
- intent; `--series-key <key>` resumes the same series. Independent plans do not
191
- share an execution identity, so reproducibility remains unestablished and the
192
- command exits 2. A timeout stops the series and includes the active run's resume
193
- command.
194
-
195
- **`haystack case-batch`** runs an already-generated product-fuzzing case
196
- batch on the hosted coordinator. The laptop only submits and polls: it holds no
197
- fleet credential, creates no VM, and kills nothing.
198
-
199
- ```bash
200
- haystack case-batch submit --repository owner/repo \
201
- --base <40-character-commit-sha> --head <40-character-commit-sha> \
202
- --cases cases.json --combinations space.json --max-concurrent 200 --json
203
- haystack case-batch status <run-id> --repository owner/repo --watch --json
204
- haystack case-batch cancel <run-id> --repository owner/repo
205
- haystack case-batch bundle <run-id> --repository owner/repo --out ./bundle
206
- ```
207
-
208
- Cases and the combination space travel inline and the whole request is bounded
209
- at 8 MiB; no source patch is sent unless `--source-patch <file>` names the
210
- precompute-captured `{ cacheKey, patchSha256, patchGzBase64 }` record.
211
- `status` follows the case cursor to the end of the list and prints one row per
212
- case — id, position, status, unknown reason, wall time and whether cleanup was
213
- proven — including when the batch ends `incomplete`, which exits 2 with a
214
- summary rather than a single failure. `haystack schema case-batch` prints the
215
- `--json` schema.
216
-
217
- **`haystack setup --json`** speaks NDJSON: events (`question`, `permission`,
218
- `progress`, `result`) on stdout, replies on stdin keyed by
219
- `requestID`/`permissionID`. `haystack schema setup` documents the full
220
- protocol, including the reply shapes. Pre-supply answers with `--repo`,
221
- `--yes`, `--no-auto-merge`, or `--answers <file>` to skip questions. Note:
222
- `--yes` enables auto-merge (the wizard default) — pass `--no-auto-merge` to
223
- opt out.
224
-
225
131
  ---
226
132
 
227
133
  ## CLI Commands
228
134
 
229
- ### `haystack setup`
230
-
231
- Interactive onboarding wizard — scan your repos and generate `.haystack.json`:
232
-
233
- ```bash
234
- haystack setup
235
- ```
236
-
237
135
  ### `haystack init`
238
136
 
239
137
  Sets the repository up for `haystack verify` and starts onboarding the app. It
@@ -253,14 +151,6 @@ Exit codes: 0 set up, 1 failed, 2 changes shown but not made, 3 onboarding
253
151
  blocked, 4 not logged in, 5 the Haystack GitHub App is not installed, 6 set up
254
152
  but onboarding stopped before finishing (run it again).
255
153
 
256
- ### `haystack status`
257
-
258
- Check if your project is configured:
259
-
260
- ```bash
261
- haystack status
262
- ```
263
-
264
154
  ### `haystack login`
265
155
 
266
156
  Authenticate with GitHub (required for setup and secrets):
@@ -278,311 +168,18 @@ without waiting, shows them to you, then runs `haystack login` to finish.
278
168
  haystack logout
279
169
  ```
280
170
 
281
- ### `haystack submit`
282
-
283
- Create a PR from current changes. Runs pre-PR triage (code review, rules validation), pushes your branch, and opens the PR.
284
-
285
- ```bash
286
- haystack submit # Triage -> create PR -> wait for analysis
287
- haystack submit --json # Agent mode: one JSON doc on stdout, progress on stderr
288
- haystack submit --title "Fix auth" # Custom PR title
289
- haystack submit --draft # Create as draft PR
290
- haystack submit --force # Skip triage checks
291
- haystack submit --no-wait # Don't wait for analysis results
292
- ```
293
-
294
- **Pre-PR triage**: two checkers run in parallel, each one structured call to
295
- `gpt-6-astra` over the OpenAI Responses API at reasoning effort `xhigh`.
296
- The key comes from `OPENAI_API_KEY`
297
- if set, else from the SSM SecureString `/haystack/secrets/shared/prod/OPENAI_API_KEY`
298
- (us-west-2) through the default AWS credential chain (e.g. `AWS_PROFILE=prod`).
299
- With neither, each checker reports `no OpenAI key (...)` and submit continues.
300
-
301
- - **code-review** reads `git diff -U10 origin/<base>...HEAD` and reports objective bugs.
302
- - **rules-validator** runs when `.haystack/pr-rules.yml` or an agent
303
- instruction file (`CLAUDE.md`, `AGENTS.md`, `COPILOT.md`, `.cursorrules`,
304
- `.github/copilot-instructions.md`) exists, and checks the diff against them.
305
-
306
- Each finding carries `file`, `line`, `severity` (`error`/`warning`/`info`),
307
- `category`, a one-sentence summary and a concrete failure scenario; results
308
- are saved to `.haystack/triage/<checker>.json`. A finding with severity
309
- `error` stops the submit (`--force` skips triage). A checker that cannot run
310
- (missing key, API error, stalled stream) prints `code review failed: <error>`
311
- and the submit continues. The reviewer sees only the diff, not the rest of the
312
- repository. `--triage-timeout <sec>` (or `.haystack.json` `triage.timeoutMs`)
313
- aborts a checker whose response stream has been silent that long (default
314
- 300s); there is no total-time cap.
315
-
316
- The `--json` payload (`haystack schema submit`) reports the PR ref, the
317
- resolved title/body and where each came from, auto-merge/auto-fix state with
318
- its source (flag vs `.haystack.json`), and the analysis outcome in the shared
319
- verdict vocabulary.
320
-
321
- **Review routing**: By default, PRs go to the auto-merge queue -- if analysis passes, the PR is merged automatically. Use `--review` to route it for human review instead:
322
-
323
- ```bash
324
- haystack submit --review # Needs review (goes to assignment queue)
325
- haystack submit --review octocat # Request review from a specific teammate
326
- ```
171
+ ### `haystack feedback`
327
172
 
328
- When `--review` is used without a username, the PR is labeled `haystack:needs-review` and appears in your team's assignment queue. When a username is provided, that person is also requested as a reviewer on GitHub.
329
-
330
- ### `haystack triage`
331
-
332
- View Haystack analysis results for any PR. Shows the same data as the Haystack web feed: rating, verdict, structured findings with details, verified bugs, human review reasons, and agent fix prompts.
173
+ Tell the Haystack team something went wrong, confused you, or is missing. It
174
+ lands with the team at once, with the CLI version, your platform and the
175
+ checkout's repository.
333
176
 
334
177
  ```bash
335
- haystack triage # Last submitted PR
336
- haystack triage 42 # Current repo, PR #42
337
- haystack triage owner/repo#99 # Fully qualified
338
- haystack triage https://github.com/o/r/pull/1 # From GitHub URL
339
- haystack triage 42 --json # Machine-readable JSON output
340
- haystack triage 42 --no-wait # Don't wait if analysis is pending
341
- haystack triage --hook # Minimal one-liner (for session hooks)
342
- haystack triage --clear # Clear pending submit state
178
+ haystack feedback "verify said the app never started, but it was up on :3000"
179
+ haystack feedback --run cv_<id> "the bug it reported is the intended change"
180
+ echo "longer report" | haystack feedback -
343
181
  ```
344
182
 
345
- When called without a PR identifier, checks the last PR submitted via `haystack submit`. The `--hook` flag produces a single-line summary with the Haystack rating, designed for session-start hooks.
346
-
347
- The `--json` output includes every finding and its customer-facing `suggested_fix` when available.
348
-
349
- ### `haystack inbox list`
350
-
351
- List PRs in your Haystack inbox with the reason each PR is present and the next action:
352
-
353
- ```bash
354
- haystack inbox list
355
- haystack inbox list --json
356
- ```
357
-
358
- ### `haystack pr get`
359
-
360
- Get the useful state of one PR: triage and minimal merge blockers:
361
-
362
- ```bash
363
- haystack pr get 42
364
- haystack pr get owner/repo#42 --json
365
- ```
366
-
367
- ### `haystack ask`
368
-
369
- Ask Haystack Chat about a PR. Machine output contains the answer and every customer-facing source Chat consulted:
370
-
371
- ```bash
372
- haystack ask 42 "Why is this finding legitimate?"
373
- haystack ask owner/repo#42 "Show the implementation" --json
374
- haystack ask owner/repo#42 "What about its callers?" --session <session-id> --json
375
- ```
376
-
377
- Chat may search the complete authorized repository when the changed files are insufficient. Search results and fetched source are included in `evidence`.
378
-
379
- ### `haystack review`
380
-
381
- Trigger a fresh *machine* analysis of a PR's current head (Haystack analyzes once at PR open; later pushes only get a resolution check). For requesting *human* review, use `haystack request-review`.
382
-
383
- ```bash
384
- haystack review 42 # Re-analyze and wait for the result
385
- haystack review 42 --no-wait # Trigger and exit
386
- haystack review 42 --json # Machine-readable outcome (schema: action)
387
- ```
388
-
389
- ### `haystack request-review`
390
-
391
- Tag a PR as needing human review (adds `haystack:needs-review`), optionally requesting a specific GitHub user:
392
-
393
- ```bash
394
- haystack request-review 42 # Into the needs-assignment queue
395
- haystack request-review 42 octocat # Also request review from octocat
396
- haystack request-review 42 --json
397
- ```
398
-
399
- ### `haystack dismiss`
400
-
401
- Dismiss analysis findings for a PR, moving it from "Issues Found" to "Good to Merge" in the feed. The override is tied to the PR's current HEAD commit.
402
-
403
- ```bash
404
- haystack dismiss 42 # Dismiss findings for PR #42
405
- haystack dismiss acme/widgets#99 # Dismiss for specific repo
406
- haystack dismiss 42 --json # Machine-readable (schema: action)
407
- ```
408
-
409
- ### `haystack undismiss`
410
-
411
- Clear all overrides (dismissed findings and/or review-not-needed) for a PR, returning it to its original feed bucket.
412
-
413
- ```bash
414
- haystack undismiss 42 # Undo overrides for PR #42
415
- haystack undismiss acme/widgets#99 # Undo for specific repo
416
- ```
417
-
418
- ### `haystack mark-reviewed`
419
-
420
- Mark human review as not needed for a PR, moving it from "Needs Review" to "Good to Merge" in the feed. The override is tied to the PR's current HEAD commit.
421
-
422
- ```bash
423
- haystack mark-reviewed 42 # Mark review not needed for PR #42
424
- haystack mark-reviewed acme/widgets#99 # Mark for specific repo
425
- ```
426
-
427
- ### `haystack pr-status`
428
-
429
- Show what bucket a PR is in within the Haystack pipeline (analyzing, good-to-merge, issues, needs-assignment, etc.):
430
-
431
- ```bash
432
- haystack pr-status 42 # Current repo, PR #42
433
- haystack pr-status acme/widgets#99 # Specific repo
434
- haystack pr-status https://github.com/o/r/pull/1 # From URL
435
- haystack pr-status 42 --json # Machine-readable output
436
- ```
437
-
438
- ### `haystack config`
439
-
440
- Manage user preferences:
441
-
442
- ```bash
443
- # Agentic tool selection
444
- haystack config agentic-tool # Show current setting
445
- haystack config agentic-tool opencode # Use Haystack billing (default)
446
- haystack config agentic-tool claude-code # Use your Claude Max subscription
447
- haystack config agentic-tool codex # Use your ChatGPT subscription
448
-
449
- # Auto-merge for safe PRs
450
- haystack config auto-merge # Show current status
451
- haystack config auto-merge on # Enable auto-merge
452
- haystack config auto-merge off # Disable auto-merge
453
-
454
- # AI reviewer wait list (merge queue waits for ALL configured bots before merging)
455
- haystack config wait-for-reviewers # Show status
456
- haystack config wait-for-reviewers add cursor # Wait for Cursor BugBot
457
- haystack config wait-for-reviewers add cursor coderabbit # Add multiple
458
- haystack config wait-for-reviewers remove cursor # Stop waiting
459
- haystack config wait-for-reviewers clear # Wait for none
460
- # Also accepts raw GitHub bot usernames (must end in [bot]):
461
- haystack config wait-for-reviewers add cursor-bugbot[bot]
462
- ```
463
-
464
- Reviewer names are validated: anything that is neither a known friendly name
465
- nor a `...[bot]` username is rejected (a stored typo would make the merge
466
- queue wait forever for a bot that can't post).
467
-
468
- ### `haystack skills`
469
-
470
- Manage AI skills for your coding CLI:
471
-
472
- ```bash
473
- haystack skills install # Install portable .agents/skills
474
- haystack skills install --cli codex # Portable install for Codex
475
- haystack skills install --cli claude # Also install Claude command shims
476
- haystack skills install --cli manual # Install portable skills and print their location
477
- haystack skills list # List available skills
478
- haystack skills scaffold-provisional-universe --input /tmp/facts.json # Write a missing-authority receipt
479
- haystack skills prepare-universe-review --json # Build a filtered reviewer source snapshot
480
- haystack skills validate-universe --json # Parse and cross-check universe artifacts
481
- ```
482
-
483
- Installed skills include `/map-cloud-verifier-universe`, which asks the
484
- customer's coding agent to map the application's production dependencies and
485
- uses a read-only reviewer subagent to check the map before Cloud Verifier
486
- onboarding. It first checks for an authoritative hosted-deployment source; if
487
- that source is private, it produces a provisional map and one bounded request
488
- for a summary from the customer's local replicator or catalog. It never asks
489
- for production credentials or direct Haystack access to production.
490
-
491
- Start a customer-owned Cloud Verifier adapter before a universe map exists, from
492
- only the three selections the user actually makes — which application, which
493
- environment, and which approved replica destination:
494
-
495
- ```bash
496
- haystack cloud-verifier connector bootstrap \
497
- --application-id Checkout \
498
- --environment-id production \
499
- --destination-policy-file ./replica-destination-policy.json
500
- ```
501
-
502
- Application and environment accept a plain name and are normalized to stable IDs
503
- (`application:checkout`, `environment:production`); pass an explicit
504
- `application:<id>` when you want to choose it yourself. The destination is named
505
- by its approved policy document, whose SHA-256 the command computes and reports.
506
- Pass `--destination-policy-sha256 <64-hex>` instead only if your tooling already
507
- holds the digest. Exactly one of the two is required.
508
-
509
- The scaffold stays blocked until the customer's coding agent completes bounded
510
- local discovery and provider-profile conformance. It includes the lifecycle and
511
- binding executable, encrypted connector-owned vault contract, cold golden
512
- refresh scheduler, and a setup/update-only reviewer-subagent request. No
513
- reviewer runs on ordinary verification requests or routine golden refreshes.
514
-
515
- ### `haystack hooks`
516
-
517
- Manage git hooks for AI agent quality checks:
518
-
519
- ```bash
520
- # Install hooks
521
- haystack hooks install
522
- haystack hooks install --force # Overwrite existing hooks
523
-
524
- # Status
525
- haystack hooks status # Check installation status
526
-
527
- # Session hooks (triage on CLI start; Claude Code's Stop hook starts a crawl,
528
- # see `haystack verify`)
529
- haystack hooks install-session # Auto-detect CLIs
530
- haystack hooks install-session --cli claude # Claude Code only
531
- haystack hooks install-session --cli all # All detected CLIs
532
- haystack hooks session-status # Check session hook status
533
- ```
534
-
535
- ### `haystack case-batch`
536
-
537
- Submit and poll product-fuzzing case batches on the hosted coordinator
538
- (`CASE-BATCH-V1`):
539
-
540
- ```bash
541
- # Submit an ordered batch against one frozen world pair
542
- haystack case-batch submit --repository owner/repo \
543
- --base <sha> --head <sha> \
544
- --cases cases.json --combinations space.json \
545
- --base-world frz_<16-hex> --head-world frz_<16-hex> \
546
- --max-concurrent 200 --per-case-wall-ms 15000 --total-budget-ms 600000 --json
547
-
548
- # Read every per-case outcome; --watch polls until terminal or --max-wall
549
- haystack case-batch status <run-id> --repository owner/repo --watch --json
550
-
551
- # Request cancellation (a claimed batch drains until its owner proves cleanup)
552
- haystack case-batch cancel <run-id> --repository owner/repo
553
-
554
- # Download the replay bundle, verifying every artifact digest
555
- haystack case-batch bundle <run-id> --repository owner/repo --out ./bundle
556
- ```
557
-
558
- `bundle` requires the service's sealed SHA-256 for `manifest.json` (served as
559
- its ETag), because the manifest is the one artifact with no parent digest to
560
- check it against; without it the command fails and writes nothing. Every other
561
- artifact is checked against the sha256 and byte length the manifest declares
562
- for it.
563
-
564
- Knobs and what turning them does:
565
-
566
- | Flag | Default | Effect |
567
- |------|---------|--------|
568
- | `--max-concurrent <n>` | the case count, capped at 500 | Ceiling only. Admitted concurrency is the smaller of it and the fleet's free capacity; the run's receipt reports both. |
569
- | `--per-case-wall-ms <ms>` | 120000 | A case that overruns is killed and recorded `UNKNOWN`/`wall-budget`, never dropped. Above the MVP's measured 103.8 s maximum, so it does not kill cases running at today's cost. |
570
- | `--total-budget-ms <ms>` | 1800000 (the contract ceiling) | Bounds launches from the first launch. Cases already launched run to their own wall budget. |
571
- | `--warm-settle-ms <ms>` | absent (0) | Each arm adopts only a warm clone that has been ready at least this long, 0 to the per-case wall. A clone adopted right after its restore attaches cold (quiet fleet: connectMs 5.8 s at 0 vs 1.1 s after 3.5-6 s of idle); each second of settle can delay a case's first create by up to a second. |
572
- | `--idempotency-key <key>` | derived from the whole submission | An exact retry returns the original run; a new key is a new execution. |
573
- | `--interval <seconds>` | 10 | Polling interval while `--watch` is set. |
574
- | `--max-wall <minutes>` | 45 | Total polling wall. Hitting it prints the snapshot, says `timed_out`, and exits 2. |
575
-
576
- `status` exits 0 for a `completed` batch with cleanup proven, and 2 for
577
- `incomplete`, `cancelled`, unproven cleanup, or a wait that hits `--max-wall`.
578
- Either way every per-case row is printed. A `--json` row from a v4 batch also
579
- carries `node_ids: { base1, head }` (the node each arm's VM was placed on) and
580
- `failure: { source, arm, error_class, error_code, node_id }` (the typed fact
581
- behind an `UNKNOWN` that is not an arrival or wall outcome, and behind an
582
- `arrival-failed` whose two arms stalled on the same step, `same-step-stall`); each
583
- is `null` when the outcome has none. A case in `--cases` may carry `repeatIndex` (1-16), which
584
- must be part of its `contentDigest`, to run as a repeat next to its original.
585
-
586
183
  ### `haystack db profile`
587
184
 
588
185
  Describe your production Postgres database so Haystack can build a full-size
@@ -722,27 +319,6 @@ whole profile succeeds. On a primary, dead-row shares come from
722
319
  `pg_stat_user_tables`; a table with rows but no counts there (statistics reset)
723
320
  stops the run and asks for `ANALYZE` on that table.
724
321
 
725
- ### `haystack policy`
726
-
727
- Manage review policies (`.haystack/review-policy.md`):
728
-
729
- ```bash
730
- # List and inspect
731
- haystack policy list # List all policies
732
-
733
- # Add policies
734
- haystack policy add # Interactive add
735
- haystack policy add "Database changes" # Start with name
736
- haystack policy add-instruction "Never flag weak test coverage as needing review"
737
-
738
- # Remove policies
739
- haystack policy remove "Database changes"
740
-
741
- # Initialize with defaults
742
- haystack policy init # Create with sensible defaults
743
- haystack policy init --force # Overwrite existing
744
- ```
745
-
746
322
  ---
747
323
 
748
324
  ## How It Works
@@ -12,10 +12,11 @@
12
12
  * publish the directories (relative to the app) the build step publishes the manifest into, so haystack-capture-step,
13
13
  * which loads nothing of the CLI, can withdraw a stale current.json there when the manifest was not made.
14
14
  */
15
+ import { spawnSync } from 'node:child_process';
15
16
  import { existsSync, readFileSync } from 'node:fs';
16
- import { join } from 'node:path';
17
+ import { join, posix } from 'node:path';
17
18
  import { CAPTURE_KEY_PATTERN, CAPTURE_STATUS_PATH, CAPTURE_VERSION } from '../commands/capture-contract.js';
18
- import { haystackFetch, haystackJson } from '../utils/haystack-api.js';
19
+ import { haystackFetch, haystackJson, repoApiPath } from '../utils/haystack-api.js';
19
20
  export const CAPTURE_CONFIG_PATH = '.haystack/capture.json';
20
21
  export function captureConfigFile(appDir) {
21
22
  return join(appDir, CAPTURE_CONFIG_PATH);
@@ -42,6 +43,29 @@ export function readCaptureConfig(appDir) {
42
43
  }
43
44
  return parsed;
44
45
  }
46
+ /** The directories (repository-relative, posix; '.' for the root) holding a .haystack/capture.json: those git lists
47
+ * (committed, or not yet), and the candidates that have one even when ignored. */
48
+ export function captureConfigDirs(gitRoot, candidates) {
49
+ const listed = spawnSync('git', ['ls-files', '-z', '--cached', '--others', '--exclude-standard', '--', CAPTURE_CONFIG_PATH,
50
+ `:(glob)**/${CAPTURE_CONFIG_PATH}`], { cwd: gitRoot, encoding: 'utf8' });
51
+ if (listed.status !== 0)
52
+ throw new Error(`git ls-files failed in ${gitRoot}: ${listed.stderr.trim()}`);
53
+ return [...new Set([...listed.stdout.split('\0').filter(path => path !== '' && !path.split('/').includes('node_modules'))
54
+ .map(path => posix.dirname(posix.dirname(path))), ...candidates])].filter(dir => existsSync(captureConfigFile(join(gitRoot, dir)))).sort();
55
+ }
56
+ /** The apps (repository-relative, posix; '.' for the root) whose .haystack/capture.json records them: the user's earlier
57
+ * answer to which app serves production. Records git lists (committed, or not yet), and those in the candidate app
58
+ * directories even when ignored. */
59
+ export function recordedApps(gitRoot, candidates) {
60
+ return captureConfigDirs(gitRoot, candidates).filter(dir => {
61
+ try {
62
+ return readCaptureConfig(join(gitRoot, dir))?.app === dir;
63
+ }
64
+ catch (error) {
65
+ throw new Error(`${posix.join(dir, CAPTURE_CONFIG_PATH)}: ${error instanceof Error ? error.message : String(error)}`);
66
+ }
67
+ });
68
+ }
45
69
  export function formatCaptureConfig(config) {
46
70
  return `${JSON.stringify(config, null, 2)}\n`;
47
71
  }
@@ -76,8 +100,8 @@ export function parseOrigins(given) {
76
100
  /** Registers the application and its production origins (rule 8b): POST /api/agent/cloud-verifier/capture/key, a
77
101
  * repository member's call, idempotent (the same application gets the same key; new origins replace the old). */
78
102
  export async function registerCaptureApp(token, repository, app, origins) {
79
- const query = new URLSearchParams({ repository, app });
80
- const response = await haystackJson(`/api/agent/cloud-verifier/capture/key?${query}`, token, {
103
+ const query = new URLSearchParams({ app });
104
+ const response = await haystackJson(repoApiPath(repository, `/capture/key?${query}`), token, {
81
105
  method: 'POST',
82
106
  headers: { 'Content-Type': 'application/json' },
83
107
  body: JSON.stringify({ origins }),
@@ -92,8 +116,8 @@ export async function registerCaptureApp(token, repository, app, origins) {
92
116
  /** The application's registration as Haystack holds it now (GET /api/agent/cloud-verifier/capture/status, lane A): its key
93
117
  * and production origins, or null when the application is not registered (404). */
94
118
  export async function readCaptureRegistration(token, repository, app) {
95
- const query = new URLSearchParams({ repository, app });
96
- const response = await haystackFetch(`${CAPTURE_STATUS_PATH}?${query}`, token, {}, [404]);
119
+ const query = new URLSearchParams({ app });
120
+ const response = await haystackFetch(repoApiPath(repository, `${CAPTURE_STATUS_PATH}?${query}`), token, {}, [404]);
97
121
  if (response.status === 404) {
98
122
  await response.body?.cancel();
99
123
  return null;