@haystackeditor/cli 0.18.0 → 0.20.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 CHANGED
@@ -1,13 +1,23 @@
1
1
  # @haystackeditor/cli
2
2
 
3
- Set up Haystack for your project. When PRs are opened, Haystack automatically reviews them for bugs, instruction drift, and rule violations — then routes them to the right place.
3
+ `haystack verify` runs your app with and without your change, explores both side by side, and reports where the change shows up and what it broke.
4
4
 
5
5
  ## Quick Start
6
6
 
7
7
  ```bash
8
8
  npm install -g @haystackeditor/cli
9
- haystack login
10
- haystack setup
9
+ haystack login # GitHub sign-in: open the URL it prints and enter the code
10
+ haystack init # shows what it will change, then starts onboarding your app
11
+ haystack verify # after a change: crawl the app with and without it
12
+ ```
13
+
14
+ There is no configuration file: Haystack works out how to run your app from
15
+ what the repository already keeps working (lockfiles, Dockerfiles, compose
16
+ files, package scripts). Or have your coding agent do the setup — paste this
17
+ into Claude Code, Codex or Cursor:
18
+
19
+ ```text
20
+ Set up Haystack for me: fetch https://haystack.sh/agents.md and follow it
11
21
  ```
12
22
 
13
23
  Requires Node.js 22.18 or newer.
@@ -17,15 +27,6 @@ once a day and prints one line on stderr when a newer version exists. It never
17
27
  runs with `--json`, when stdout is not a TTY, or when `CI` is set. Set
18
28
  `HAYSTACK_NO_UPDATE_CHECK=1` to turn it off.
19
29
 
20
- The `setup` command walks you through an interactive wizard:
21
-
22
- 1. **Select repositories** to configure
23
- 2. **Scan for coding rules** (conventions your team follows)
24
- 3. **Scan for CI/bot signals** (checks to wait for before merging)
25
- 4. **Scan for review policies** (who should review what)
26
- 5. **Review and toggle** discovered items
27
- 6. **Write `.haystack.json`** to your repos
28
-
29
30
  The CLI sends limited operational events to PostHog by default so Haystack can
30
31
  measure setup reliability. Events contain a pseudonymous install identifier
31
32
  (a random id generated once and stored in `~/.haystack/telemetry-id`; it is
@@ -106,8 +107,11 @@ acknowledged; it never shows a crawl of another revision or another title.
106
107
  A crawl builds the app with and without your change, starts from the changed
107
108
  code, reaches it in the running app, explores outward with both builds side by
108
109
  side, and double-checks and judges every difference. `haystack verify` waits
109
- until the crawl finishes, printing each step as it happens; there is no time
110
- limit, and Ctrl-C stops waiting, never the crawl. It then prints:
110
+ for the crawl, printing each step as it happens and each bug the moment the
111
+ crawl finds it (each read is held by the service until the crawl changes, so
112
+ nothing waits on a polling interval). It finishes when the crawl answers, as
113
+ soon as its time is up, without waiting for its machines to shut down; there
114
+ is no time limit, and Ctrl-C stops waiting, never the crawl. It then prints:
111
115
 
112
116
  - a headline: how many bugs it found, none, or why the crawl could not finish;
113
117
  - **Where your change shows up in the app**: every changed spot as
@@ -120,14 +124,15 @@ limit, and Ctrl-C stops waiting, never the crawl. It then prints:
120
124
  - **What the crawl never ran**: changed files no path ran, and how many of the
121
125
  functions your change affects ran.
122
126
 
123
- `--no-wait` prints the crawl's current state and returns. `--interval
124
- <seconds>` sets the polling interval (default 5), `--account <login>` picks a
125
- saved account, and `--repo owner/repo` names the repository when `origin` does
126
- not. `--json` prints one document, `{ "schema_version", "crawl" }`, where
127
- `crawl` is the crawl as the service returns it (or `null` when no crawl of the
128
- capture could be started); `haystack schema verify` prints its schema. Exit codes
129
- follow `haystack case-batch status`: 0 when the crawl finished (the bugs it
130
- found are in the output) or is still running under `--no-wait`; 2 when it ended
127
+ `--no-wait` prints the crawl's current state and returns. `--account <login>`
128
+ 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
135
+ it found are in the output) or is still running under `--no-wait`; 2 when it ended
131
136
  without finishing (stopped early, or cancelled because a newer stop in the
132
137
  repository replaced it) or the machines it used could not be proven shut down;
133
138
  1 when the command failed or no crawl could be started.
@@ -228,13 +233,23 @@ haystack setup
228
233
 
229
234
  ### `haystack init`
230
235
 
231
- Quick local setup — auto-detects your project and creates `.haystack.json` without scanning:
236
+ Sets the repository up for `haystack verify` and starts onboarding the app. It
237
+ shows each change as a diff and makes it only with `--yes` (or a yes at the
238
+ prompt): Claude Code's Stop hook in the per-developer
239
+ `.claude/settings.local.json` (kept out of commits), and a short note in
240
+ AGENTS.md telling coding agents to run `haystack verify` after a change.
241
+ Running it again changes only what is missing and shows where onboarding is.
232
242
 
233
243
  ```bash
234
- haystack init # Auto-detect and create config
235
- haystack init --force # Overwrite existing .haystack.json
244
+ haystack init # Preview, then confirm
245
+ haystack init --yes --json # Apply without asking; one JSON document (haystack schema init)
246
+ haystack verify onboarding # Where onboarding is; --wait follows it
236
247
  ```
237
248
 
249
+ Exit codes: 0 set up, 1 failed, 2 changes shown but not made, 3 onboarding
250
+ blocked, 4 not logged in, 5 the Haystack GitHub App is not installed, 6 set up
251
+ but onboarding stopped before finishing (run it again).
252
+
238
253
  ### `haystack status`
239
254
 
240
255
  Check if your project is configured:
@@ -252,6 +267,8 @@ haystack login
252
267
  ```
253
268
 
254
269
  This uses GitHub's device flow - you'll get a code to enter at github.com/login/device.
270
+ A coding agent runs `haystack login --no-wait --json` to get the URL and code
271
+ without waiting, shows them to you, then runs `haystack login` to finish.
255
272
 
256
273
  ```bash
257
274
  # Log out (removes stored credentials)
@@ -725,28 +742,18 @@ haystack policy init --force # Overwrite existing
725
742
 
726
743
  ---
727
744
 
728
- ## Configuration
729
-
730
- The `setup` wizard writes `.haystack.json` to your repos with discovered rules, signals, and policies. You can also create a base config locally with `haystack init`:
731
-
732
- ```json
733
- {
734
- "version": "1",
735
- "name": "my-app"
736
- }
737
- ```
738
-
739
- ---
740
-
741
745
  ## How It Works
742
746
 
743
- 1. Run `haystack setup` to configure your repos (or `haystack init` for local-only config)
744
- 2. Install the [Haystack GitHub App](https://github.com/apps/haystack-code-reviewer-pr-hook/installations/new)
745
- 3. When PRs are opened, Haystack automatically:
746
- - Analyzes the code for bugs, instruction drift, and rule violations
747
- - Reports results on the PR
748
- - Routes the PR to the right inbox tab (Good to Merge, Issues Found, etc.)
749
- - If auto-merge is enabled, clean PRs merge automatically
747
+ 1. `haystack init` starts onboarding: Haystack reads the repository, builds the
748
+ app, starts it with its databases and services, prepares its data and test
749
+ accounts, and proves one workflow works. It needs the
750
+ [Haystack GitHub App](https://github.com/apps/haystack-code-reviewer-pr-hook/installations/new)
751
+ on the repository's owner.
752
+ 2. After a change, `haystack verify` builds the app at the base and with your
753
+ change on Haystack's isolated machines (no internet access), explores both
754
+ side by side starting from the changed code, and judges every difference.
755
+ 3. It prints where your change shows up, the bugs it found with the steps to
756
+ see each, and the changed code it never ran. See https://haystack.sh/docs.
750
757
 
751
758
  ## License
752
759
 
@@ -90,6 +90,8 @@ function requireRunId(runId) {
90
90
  }
91
91
  return runId;
92
92
  }
93
+ /** How long any gateway request may take unless its caller says otherwise. */
94
+ export const GATEWAY_TIMEOUT_MS = 120_000;
93
95
  /** Every outbound request. The header set is built here and nowhere else, so
94
96
  * the credential surface of this command is one line: the login bearer.
95
97
  * `haystack verify` reads crawls through this same client. */
@@ -104,7 +106,7 @@ export async function gatewayFetch(path, token, init = { method: 'GET' }) {
104
106
  method: init.method,
105
107
  headers,
106
108
  ...(init.body === undefined ? {} : { body: init.body }),
107
- signal: AbortSignal.timeout(120_000),
109
+ signal: AbortSignal.timeout(init.timeoutMs ?? GATEWAY_TIMEOUT_MS),
108
110
  });
109
111
  }
110
112
  async function gatewayJson(path, token, init = { method: 'GET' }) {
@@ -1,3 +1,9 @@
1
1
  export const CRAWL_TITLE_MAX_CHARS = 200;
2
+ /** Amendment 8: the time a crawl may be asked to take, whole seconds from 1 to 30 minutes. */
3
+ export const CRAWL_BUDGET_MIN_MS = 60_000;
4
+ export const CRAWL_BUDGET_MAX_MS = 30 * 60_000;
2
5
  export const CRAWL_MAX_FINDINGS = 50;
3
6
  export const CRAWL_MAX_FINDING_STEPS = 64;
7
+ /** Amendment 9: what a crawl has found so far (`exploring`) or its answer at the budget (`answered`), before the sealed manifest;
8
+ * findings carry no images. A read may pass `waitAfter=<updatedAt>` to be held until the crawl changes (at most CRAWL_WAIT_MAX_MS). */
9
+ export const CRAWL_WAIT_MAX_MS = 25_000;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `haystack admin fleet-policy show|diff|set|revoke|history|import-kv`: the
2
+ * `haystack admin fleet-policy show|diff|set|revoke|history`: the
3
3
  * operator command for the fleet policy authority in D1 (FLEET PHASE 1,
4
4
  * design Appendix A). The only credential is the operator's Cloudflare Access
5
5
  * token from `cloudflared access token -app=<worker URL>`, sent as
@@ -282,24 +282,3 @@ export function fleetPolicyHistoryCommand(repository, options) {
282
282
  return FLEET_POLICY_CLI_EXIT.ok;
283
283
  });
284
284
  }
285
- export function fleetPolicyImportKvCommand(options) {
286
- return run(async () => {
287
- const dryRun = options.dryRun === true;
288
- const result = await post(workerOrigin(options), FLEET_POLICY_ADMIN_ROUTES.importKv, { dryRun });
289
- if (result.kind !== 'ok')
290
- return reportFailure(result);
291
- const { policies, overrides } = result.body;
292
- console.log(chalk.bold(`${dryRun ? 'dry run: ' : ''}${policies.length} policy keys, ${overrides.length} tenant override keys`));
293
- for (const item of policies) {
294
- const scope = item.scope ? ` ${item.scope.tenantId} ${item.scope.repositoryId}` : '';
295
- console.log(` ${item.outcome} ${item.key}${scope}${item.repositoryFullName ? ` (${item.repositoryFullName})` : ''}`
296
- + `${item.scopeKind ? ` ${item.scopeKind}` : ''}${item.binding ? ` [${bindingText(item.binding)}]` : ''}`
297
- + `${item.policySha256 ? ` ${item.policySha256}` : ''}${item.detail ? `: ${item.detail}` : ''}`);
298
- }
299
- for (const item of overrides) {
300
- console.log(` ${item.outcome} ${item.key}${item.tenantId ? ` -> ${item.tenantId}` : ''}${item.detail ? `: ${item.detail}` : ''}`);
301
- }
302
- const failed = [...policies, ...overrides].filter(item => item.outcome === 'conflict' || item.outcome === 'invalid');
303
- return failed.length === 0 ? FLEET_POLICY_CLI_EXIT.ok : FLEET_POLICY_CLI_EXIT.error;
304
- });
305
- }