@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 +52 -45
- package/dist/commands/case-batch.js +3 -1
- package/dist/commands/crawl-contract.js +6 -0
- package/dist/commands/fleet-policy.js +1 -22
- package/dist/commands/init.js +376 -129
- package/dist/commands/install-session-hooks.js +27 -10
- package/dist/commands/login.js +133 -34
- package/dist/commands/onboarding-contract.js +6 -0
- package/dist/commands/precompute-delivery.js +6 -3
- package/dist/commands/setup.js +1 -2
- package/dist/commands/verify-onboarding.js +365 -0
- package/dist/commands/verify-precompute.js +48 -12
- package/dist/commands/verify.js +242 -84
- package/dist/index.js +108 -30
- package/dist/schema.js +5 -1
- package/dist/types.js +3 -0
- package/dist/utils/config.js +0 -16
- package/dist/utils/haystack-api.js +12 -1
- package/dist/utils/secrets.js +0 -51
- package/dist/utils/telemetry.js +32 -0
- package/package.json +2 -2
- package/schemas/init.v1.json +35 -0
- package/schemas/login.v1.json +17 -0
- package/schemas/verify-answer.v1.json +86 -0
- package/schemas/verify-onboarding.v1.json +13 -0
- package/schemas/verify.v1.json +296 -8
- package/dist/utils/detect.js +0 -507
package/README.md
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
# @haystackeditor/cli
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
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
|
-
|
|
110
|
-
|
|
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. `--
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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 #
|
|
235
|
-
haystack init --
|
|
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.
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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(
|
|
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
|
|
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
|
-
}
|