@haystackeditor/cli 0.15.20 → 0.15.21

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.
@@ -0,0 +1,97 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { spawn } from 'node:child_process';
3
+ import { resolve } from 'node:path';
4
+ import chalk from 'chalk';
5
+ import { setVerifyToken, verifyAuthHeaders } from './verify-core.js';
6
+ const DEFAULT_CONTROL_PLANE = 'http://127.0.0.1:3000';
7
+ const SPECIMENS = new Set(['all', 'posthog-js-478', 'analytics-js-255']);
8
+ function openResults(url) {
9
+ const platform = process.platform;
10
+ const command = platform === 'darwin' ? 'open' : platform === 'win32' ? 'cmd' : 'xdg-open';
11
+ const args = platform === 'win32' ? ['/c', 'start', '', url] : [url];
12
+ const child = spawn(command, args, { detached: true, stdio: 'ignore' });
13
+ child.unref();
14
+ }
15
+ async function readIntentTranscript(path) {
16
+ if (!path)
17
+ return undefined;
18
+ const absolutePath = resolve(path);
19
+ try {
20
+ return await readFile(absolutePath, 'utf8');
21
+ }
22
+ catch (error) {
23
+ const message = error instanceof Error ? error.message : String(error);
24
+ throw new Error(`Could not read intent transcript at ${absolutePath}: ${message}`);
25
+ }
26
+ }
27
+ export async function verifyCommand(specimenArg, options) {
28
+ const specimen = specimenArg || 'all';
29
+ if (!SPECIMENS.has(specimen)) {
30
+ throw new Error(`Unknown or unreproduced specimen "${specimen}". Choose all, posthog-js-478, or analytics-js-255.`);
31
+ }
32
+ setVerifyToken(options.token);
33
+ const server = (options.server || process.env.HAYSTACK_VERIFY_SERVER || DEFAULT_CONTROL_PLANE)
34
+ .replace(/\/+$/, '');
35
+ const intentTranscript = await readIntentTranscript(options.intentTranscript);
36
+ if (!options.json) {
37
+ console.error(chalk.dim('Preparing the cloud verification run…'));
38
+ }
39
+ let response;
40
+ try {
41
+ response = await fetch(`${server}/api/cloud-verifier/runs`, {
42
+ method: 'POST',
43
+ headers: { 'content-type': 'application/json', ...verifyAuthHeaders() },
44
+ body: JSON.stringify({
45
+ specimen,
46
+ repoPath: options.repo ? resolve(options.repo) : undefined,
47
+ baseRef: options.base,
48
+ headRef: options.head,
49
+ intentTranscript,
50
+ }),
51
+ });
52
+ }
53
+ catch (error) {
54
+ const message = error instanceof Error ? error.message : String(error);
55
+ throw new Error(`Could not reach the Haystack verifier at ${server}. Start the demo control plane with \`pnpm dev\` and retry. ${message}`);
56
+ }
57
+ const raw = await response.text();
58
+ if (response.status === 401) {
59
+ throw new Error(`Control plane at ${server} requires a token (HTTP 401). Pass --token or set HAYSTACK_VERIFY_TOKEN.`);
60
+ }
61
+ if (!response.ok) {
62
+ let reason = raw;
63
+ try {
64
+ reason = JSON.parse(raw).error || raw;
65
+ }
66
+ catch {
67
+ // Preserve the response body when it is not JSON.
68
+ }
69
+ throw new Error(`Verifier rejected the run (${response.status}): ${reason}`);
70
+ }
71
+ let started;
72
+ try {
73
+ started = JSON.parse(raw);
74
+ }
75
+ catch {
76
+ throw new Error('Verifier returned an invalid response.');
77
+ }
78
+ if (!started.runId || !started.resultsUrl) {
79
+ throw new Error('Verifier response is missing runId or resultsUrl.');
80
+ }
81
+ if (options.json) {
82
+ process.stdout.write(`${JSON.stringify({
83
+ schema_version: '1.0.0',
84
+ run_id: started.runId,
85
+ status: started.status,
86
+ results_url: started.resultsUrl,
87
+ }, null, 2)}\n`);
88
+ }
89
+ else {
90
+ console.log('');
91
+ console.log(`${chalk.green('✓')} Verification started: ${chalk.bold(started.runId)}`);
92
+ console.log(` ${chalk.dim('Results:')} ${chalk.cyan(started.resultsUrl)}`);
93
+ }
94
+ if (options.open !== false) {
95
+ openResults(started.resultsUrl);
96
+ }
97
+ }
package/dist/index.js CHANGED
@@ -48,6 +48,7 @@ import { schemaCommand, listSchemas } from './commands/schema-cmd.js';
48
48
  import { registerWebhook, listWebhooks, rotateWebhookSecret, setWebhookEnabled, listDeliveries, replayDelivery, } from './commands/webhooks.js';
49
49
  import { editRulesCommand, validateRulesCommand } from './commands/rules.js';
50
50
  import { systemMapValidateCommand } from './commands/system-map.js';
51
+ import { verifyCommand } from './commands/verify.js';
51
52
  import { headlessLoginCommand, headlessLogoutCommand, listTokensCommand, revokeTokenCommand, } from './commands/tokens.js';
52
53
  // `mcp` is imported lazily inside its command action (below), NOT at the top level.
53
54
  // It is the only module that pulls in `@modelcontextprotocol/sdk`; keeping it out of
@@ -164,6 +165,260 @@ program
164
165
  .command('status')
165
166
  .description('Check if .haystack.json exists and is valid')
166
167
  .action(statusCommand);
168
+ const verify = program
169
+ .command('verify')
170
+ .description('Verify a change in matched cloud universes, or bisect a historical regression')
171
+ .argument('[specimen]', 'Launch specimen to run (default: all)')
172
+ .option('--repo <path>', 'Repository to analyze (defaults to the selected launch specimen)')
173
+ .option('--base <ref>', 'Base revision for a repository run')
174
+ .option('--head <ref>', 'Head revision for a repository run')
175
+ .option('--intent-transcript <file>', 'Coding-agent transcript used as intent evidence')
176
+ .option('--server <url>', 'Verifier control-plane URL (default: http://127.0.0.1:3000)')
177
+ .option('--token <token>', 'Bearer token for a control plane that requires auth (or HAYSTACK_VERIFY_TOKEN)')
178
+ .option('--no-open', 'Do not open the live results page')
179
+ .option('--json', 'Machine-readable run id, status, and results URL')
180
+ .addHelpText('after', `
181
+ Launch specimens:
182
+ posthog-js-478 Replay PostHog/posthog-js PR #478 in matched browser environments (latent-issue mode)
183
+ analytics-js-255 Identity routing regression with sandbox bisect
184
+ all Run each reproduced specimen as an isolated chapter (default)
185
+
186
+ The local control plane must be running. From the Haystack repository:
187
+ pnpm dev
188
+
189
+ Examples:
190
+ haystack verify posthog-js-478 # start a run, then:
191
+ haystack verify watch # block until it finishes (exit 1 = tests failed)
192
+ haystack verify status # one-screen verdicts + live environment URLs
193
+ haystack verify findings # every problem found, with the readings that moved
194
+ haystack verify show latest 3 # one test in full: readings, timeline, SDK log
195
+ haystack verify diff latest 3 # base-vs-head screenshot differ (1/2/3 views)
196
+ haystack verify open latest 3 --live # open a failing test's live environment
197
+ haystack verify cleanup latest 3 # retained-state deletion/reference status
198
+ haystack verify refork latest 3 attempt:fix-2 # checkpoint a durable child
199
+ haystack verify shell latest 3 # terminal into that live universe
200
+ haystack verify artifacts latest # pull screenshots / DOM / video locally
201
+ haystack verify sandboxes --kill-stale # reclaim E2B capacity from superseded runs
202
+ haystack verify reseal <specimen> # rebuild a sealed bundle + restart control plane
203
+ `)
204
+ .action((specimen, options) => runPublicCommand(() => verifyCommand(specimen, options), options.json));
205
+ const verifyRunArg = 'Run id, chapter id (newest run of that chapter), or "latest" (default)';
206
+ verify
207
+ .command('status')
208
+ .description('One screen for a run: per-test verdicts, counts, live environments and expiry')
209
+ .argument('[run]', verifyRunArg)
210
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
211
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
212
+ .option('--json', 'Machine-readable summary')
213
+ .action(async (run, _options, cmd) => {
214
+ // optsWithGlobals: the parent `verify` command also declares --json/--server
215
+ // and commander's default parsing lets it consume those flags even when
216
+ // they appear after the subcommand; the merged view (self wins) restores them.
217
+ const options = cmd.optsWithGlobals();
218
+ const { verifyStatusCommand } = await import('./commands/verify-ops.js');
219
+ return runPublicCommand(() => verifyStatusCommand(run, options), options.json);
220
+ });
221
+ verify
222
+ .command('watch')
223
+ .description('Follow a run until it finishes; exit 0 all passed, 1 tests failed, 2 run/infra broke')
224
+ .argument('[run]', verifyRunArg)
225
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
226
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
227
+ .option('--interval <seconds>', 'Poll interval (default 3)')
228
+ .option('--timeout <minutes>', 'Give up after this long (default: wait forever)')
229
+ .option('--json', 'Suppress streaming; print the final summary as JSON')
230
+ .action(async (run, _options, cmd) => {
231
+ const options = cmd.optsWithGlobals();
232
+ const { verifyWatchCommand } = await import('./commands/verify-ops.js');
233
+ return runPublicCommand(() => verifyWatchCommand(run, options), options.json);
234
+ });
235
+ verify
236
+ .command('list')
237
+ .description('Runs newest first: chapter, verdict counts, age (control plane, else disk)')
238
+ .option('--limit <n>', 'Max runs to show (default 20)')
239
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
240
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
241
+ .option('--json', 'Machine-readable list')
242
+ .action(async (_options, cmd) => {
243
+ const options = cmd.optsWithGlobals();
244
+ const { verifyListCommand } = await import('./commands/verify-ops.js');
245
+ return runPublicCommand(() => verifyListCommand(options), options.json);
246
+ });
247
+ verify
248
+ .command('findings')
249
+ .description('Every problem the run found, in one screen: what failed, which readings moved, live links')
250
+ .argument('[run]', verifyRunArg)
251
+ .option('--full', 'Print the judge\'s full explanation for each finding')
252
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
253
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
254
+ .option('--json', 'Machine-readable findings')
255
+ .action(async (run, _options, cmd) => {
256
+ const options = cmd.optsWithGlobals();
257
+ const { verifyFindingsCommand } = await import('./commands/verify-ops.js');
258
+ return runPublicCommand(() => verifyFindingsCommand(run, options), options.json);
259
+ });
260
+ verify
261
+ .command('open')
262
+ .description('Open a run artifact (screenshot/video/DOM) or a failing test\'s live environment')
263
+ .argument('<run>', verifyRunArg)
264
+ .argument('<target>', 'Test selector (ordinal/title), or an artifact file name (….png/.webm/.html)')
265
+ .option('--live', 'Open the test\'s live environment URL instead of an artifact')
266
+ .option('--side <side>', 'base or head (default head)')
267
+ .option('--step <n>', 'Open the screenshot captured after replay step n')
268
+ .option('--kind <kind>', 'screenshot | video | dom (default: video, else last screenshot)')
269
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
270
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
271
+ .action(async (run, target, _options, cmd) => {
272
+ const options = cmd.optsWithGlobals();
273
+ const { verifyOpenCommand } = await import('./commands/verify-ops.js');
274
+ return runPublicCommand(() => verifyOpenCommand(run, target, options));
275
+ });
276
+ verify
277
+ .command('diff')
278
+ .description('Open a base-vs-head screenshot differ for one test (1 what changed / 2 before / 3 after)')
279
+ .argument('<run>', verifyRunArg)
280
+ .argument('<cell>', 'Test selector: ordinal, cell id, or title substring')
281
+ .option('--step <n>', 'Compare only the screenshot captured after replay step n')
282
+ .option('--out <dir>', 'Where to build the diff page (default: a temp directory)')
283
+ .option('--no-open', 'Build the page but do not open it')
284
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
285
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
286
+ .option('--json', 'Machine-readable result (implies --no-open)')
287
+ .action(async (run, cell, _options, cmd) => {
288
+ const options = { ...cmd.optsWithGlobals(), ...cmd.opts() };
289
+ const { verifyDiffCommand } = await import('./commands/verify-ops.js');
290
+ return runPublicCommand(() => verifyDiffCommand(run, cell, options), options.json);
291
+ });
292
+ verify
293
+ .command('cleanup')
294
+ .description('Show exact retained-state references, provider deletion, and cleanup-SLO status')
295
+ .argument('<run>', verifyRunArg)
296
+ .argument('<cell>', 'Stable test selector: ordinal, riskCellId, or exact planner cellKey')
297
+ .option('--side <side>', 'base or head (default head)')
298
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
299
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
300
+ .option('--json', 'Machine-readable cleanup status')
301
+ .action(async (run, cell, _options, cmd) => {
302
+ const options = cmd.optsWithGlobals();
303
+ const { verifyCleanupCommand } = await import('./commands/verify-ops.js');
304
+ return runPublicCommand(() => verifyCleanupCommand(run, cell, options), options.json);
305
+ });
306
+ verify
307
+ .command('reopen')
308
+ .description('Attach fresh E2B compute to retained Archil state from a test or exact fork ID')
309
+ .argument('<source>', 'Run selector, or an exact archil:…:branch:… fork ID')
310
+ .argument('[cell]', 'Stable test selector for a run: ordinal, riskCellId, or exact planner cellKey')
311
+ .option('--side <side>', 'base or head (default head)')
312
+ .option('--open-browser', 'Open the restarted application in a browser')
313
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
314
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
315
+ .option('--json', 'Machine-readable attachment details')
316
+ .action(async (source, cell, _options, cmd) => {
317
+ const options = cmd.optsWithGlobals();
318
+ const { verifyReopenCommand } = await import('./commands/verify-ops.js');
319
+ return runPublicCommand(() => verifyReopenCommand(source, cell, options), options.json);
320
+ });
321
+ verify
322
+ .command('refork')
323
+ .description('Checkpoint a reopened Archil universe into a durable child fork')
324
+ .argument('<source>', 'Run selector, or an exact archil:…:branch:… parent fork ID')
325
+ .argument('<cell-or-child-key>', 'Stable test selector for a run, or child key for an exact fork ID')
326
+ .argument('[child-key]', 'Structured stable child key when source is a run')
327
+ .option('--side <side>', 'base or head (default head; only applies to a run)')
328
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
329
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
330
+ .option('--json', 'Machine-readable parent, child, checkpoint, and expiry')
331
+ .action(async (source, cellOrChildKey, childKey, _options, cmd) => {
332
+ const options = cmd.optsWithGlobals();
333
+ const { verifyReforkCommand } = await import('./commands/verify-ops.js');
334
+ return runPublicCommand(() => verifyReforkCommand(source, cellOrChildKey, childKey, options), options.json);
335
+ });
336
+ verify
337
+ .command('shell')
338
+ .description('Terminal into a failing test\'s live universe (e2b sandbox connect)')
339
+ .argument('<run>', verifyRunArg)
340
+ .argument('<cell>', 'Stable test selector: ordinal, riskCellId, or exact planner cellKey')
341
+ .option('--side <side>', 'base or head (default head; only failing tests keep a universe alive)')
342
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
343
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
344
+ .action(async (run, cell, _options, cmd) => {
345
+ const options = cmd.optsWithGlobals();
346
+ const { verifyShellCommand } = await import('./commands/verify-ops.js');
347
+ return runPublicCommand(() => verifyShellCommand(run, cell, options));
348
+ });
349
+ verify
350
+ .command('show')
351
+ .description('One test in full: finding, changed readings, request timeline, SDK log lines')
352
+ .argument('<run>', verifyRunArg)
353
+ .argument('<cell>', 'Test ordinal, cell id, planner cell key, or title substring')
354
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
355
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
356
+ .option('--no-timeline', 'Skip the request timeline')
357
+ .option('--json', 'Machine-readable detail')
358
+ .action(async (run, cell, _options, cmd) => {
359
+ const options = cmd.optsWithGlobals();
360
+ const { verifyShowCommand } = await import('./commands/verify-ops.js');
361
+ return runPublicCommand(() => verifyShowCommand(run, cell, options), options.json);
362
+ });
363
+ verify
364
+ .command('artifacts')
365
+ .description('Pull a run\'s screenshots, DOM snapshots and videos to a local directory')
366
+ .argument('<run>', verifyRunArg)
367
+ .option('--out <dir>', 'Destination (default: ./verify-artifacts/<runId>)')
368
+ .option('--cell <selector>', 'Only artifacts for one test (ordinal, cell id, or title substring)')
369
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
370
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
371
+ .option('--json', 'Machine-readable result')
372
+ .action(async (run, _options, cmd) => {
373
+ const options = cmd.optsWithGlobals();
374
+ const { verifyArtifactsCommand } = await import('./commands/verify-ops.js');
375
+ return runPublicCommand(() => verifyArtifactsCommand(run, options), options.json);
376
+ });
377
+ verify
378
+ .command('sandboxes')
379
+ .description('E2B sandbox inventory with run attribution; --kill-stale reclaims superseded ones')
380
+ .option('--kill-stale', 'Kill verifier-attributed sandboxes superseded by newer runs (never unattributed ones)')
381
+ .option('--kill-run <runId>', 'Kill every sandbox attributed to one run')
382
+ .option('--server <url>', 'Control-plane URL, preferred over direct E2B access (default: http://127.0.0.1:3000)')
383
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
384
+ .option('--json', 'Machine-readable inventory')
385
+ .action(async (_options, cmd) => {
386
+ const options = cmd.optsWithGlobals();
387
+ const { verifySandboxesCommand } = await import('./commands/verify-sandboxes.js');
388
+ return runPublicCommand(() => verifySandboxesCommand(options), options.json);
389
+ });
390
+ verify
391
+ .command('reseal')
392
+ .description('Rebuild a sealed specimen bundle (guarded) and restart the control plane onto it')
393
+ .argument('<specimen>', 'Specimen id, e.g. posthog-js-478')
394
+ .option('--source <dir>', 'Specimen source directory holding reseal.sh')
395
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
396
+ .option('--repo-root <path>', 'Repository holding demo/cloud-verifier (default: walk up from cwd)')
397
+ .option('--no-restart', 'Only rebuild the bundle; leave the running server on the old checkout')
398
+ .action(async (specimen, _options, cmd) => {
399
+ const options = cmd.optsWithGlobals();
400
+ const { verifyResealCommand } = await import('./commands/verify-reseal.js');
401
+ return runPublicCommand(() => verifyResealCommand(specimen, options));
402
+ });
403
+ verify
404
+ .command('mcp')
405
+ .description('Run a stdio MCP server exposing the verifier to agents (verify_start/verify_wait/…)')
406
+ .option('--server <url>', 'Control-plane URL (default: http://127.0.0.1:3000)')
407
+ .option('--token <token>', 'Bearer token for a control plane that requires auth (or HAYSTACK_VERIFY_TOKEN)')
408
+ .option('--repo-root <path>', 'Repository holding .haystack/verify (default: walk up from cwd)')
409
+ .action(async (_options, cmd) => {
410
+ const options = cmd.optsWithGlobals();
411
+ try {
412
+ // Lazy import, same reasoning as the top-level `mcp` command: only this
413
+ // path needs @modelcontextprotocol/sdk.
414
+ const { runVerifyMcpServer } = await import('./commands/verify-mcp.js');
415
+ await runVerifyMcpServer(options);
416
+ }
417
+ catch (err) {
418
+ console.error(chalk.red('verify mcp server failed:'), err instanceof Error ? err.message : err);
419
+ process.exit(1);
420
+ }
421
+ });
167
422
  program
168
423
  .command('login')
169
424
  .description('Add or refresh a saved GitHub account (use --headless for CI tokens)')
package/dist/types.d.ts CHANGED
@@ -719,11 +719,52 @@ declare const TriageConfigSchema: z.ZodObject<{
719
719
  maxTurns?: Partial<Record<"code-review" | "rules-validator" | "intent-drift", number>> | undefined;
720
720
  timeoutMs?: number | undefined;
721
721
  }>;
722
+ declare const PreferencesSchema: z.ZodObject<{
723
+ /** Run analysis automatically for PR webhook events (default: true) */
724
+ automatic_analysis: z.ZodOptional<z.ZodBoolean>;
725
+ /** Enable sandbox-backed verification for this repository */
726
+ sandbox_enabled: z.ZodOptional<z.ZodBoolean>;
727
+ /** Enroll submitted PRs in auto-merge by default */
728
+ auto_merge: z.ZodOptional<z.ZodBoolean>;
729
+ /** Allow Haystack to attempt automatic fixes */
730
+ auto_fix: z.ZodOptional<z.ZodBoolean>;
731
+ }, "strip", z.ZodTypeAny, {
732
+ automatic_analysis?: boolean | undefined;
733
+ sandbox_enabled?: boolean | undefined;
734
+ auto_merge?: boolean | undefined;
735
+ auto_fix?: boolean | undefined;
736
+ }, {
737
+ automatic_analysis?: boolean | undefined;
738
+ sandbox_enabled?: boolean | undefined;
739
+ auto_merge?: boolean | undefined;
740
+ auto_fix?: boolean | undefined;
741
+ }>;
722
742
  export declare const HaystackConfigSchema: z.ZodObject<{
723
743
  /** Config version (must be "1") */
724
744
  version: z.ZodLiteral<"1">;
725
745
  /** Project name (for display) */
726
746
  name: z.ZodOptional<z.ZodString>;
747
+ /** Repository-level Haystack behavior */
748
+ preferences: z.ZodOptional<z.ZodObject<{
749
+ /** Run analysis automatically for PR webhook events (default: true) */
750
+ automatic_analysis: z.ZodOptional<z.ZodBoolean>;
751
+ /** Enable sandbox-backed verification for this repository */
752
+ sandbox_enabled: z.ZodOptional<z.ZodBoolean>;
753
+ /** Enroll submitted PRs in auto-merge by default */
754
+ auto_merge: z.ZodOptional<z.ZodBoolean>;
755
+ /** Allow Haystack to attempt automatic fixes */
756
+ auto_fix: z.ZodOptional<z.ZodBoolean>;
757
+ }, "strip", z.ZodTypeAny, {
758
+ automatic_analysis?: boolean | undefined;
759
+ sandbox_enabled?: boolean | undefined;
760
+ auto_merge?: boolean | undefined;
761
+ auto_fix?: boolean | undefined;
762
+ }, {
763
+ automatic_analysis?: boolean | undefined;
764
+ sandbox_enabled?: boolean | undefined;
765
+ auto_merge?: boolean | undefined;
766
+ auto_fix?: boolean | undefined;
767
+ }>>;
727
768
  /** Package manager override (auto-detected if not specified) */
728
769
  package_manager: z.ZodOptional<z.ZodEnum<["npm", "pnpm", "yarn", "bun"]>>;
729
770
  /** Development server configuration */
@@ -1315,6 +1356,12 @@ export declare const HaystackConfigSchema: z.ZodObject<{
1315
1356
  root?: string | undefined;
1316
1357
  depends_on?: string[] | undefined;
1317
1358
  }> | undefined;
1359
+ preferences?: {
1360
+ automatic_analysis?: boolean | undefined;
1361
+ sandbox_enabled?: boolean | undefined;
1362
+ auto_merge?: boolean | undefined;
1363
+ auto_fix?: boolean | undefined;
1364
+ } | undefined;
1318
1365
  package_manager?: "npm" | "pnpm" | "yarn" | "bun" | undefined;
1319
1366
  dev_server?: {
1320
1367
  command: string;
@@ -1442,6 +1489,12 @@ export declare const HaystackConfigSchema: z.ZodObject<{
1442
1489
  root?: string | undefined;
1443
1490
  depends_on?: string[] | undefined;
1444
1491
  }> | undefined;
1492
+ preferences?: {
1493
+ automatic_analysis?: boolean | undefined;
1494
+ sandbox_enabled?: boolean | undefined;
1495
+ auto_merge?: boolean | undefined;
1496
+ auto_fix?: boolean | undefined;
1497
+ } | undefined;
1445
1498
  package_manager?: "npm" | "pnpm" | "yarn" | "bun" | undefined;
1446
1499
  dev_server?: {
1447
1500
  command: string;
@@ -1559,6 +1612,7 @@ export type NetworkConfig = z.infer<typeof NetworkConfigSchema>;
1559
1612
  export type SecretDeclaration = z.infer<typeof SecretDeclarationSchema>;
1560
1613
  export type MergeQueueConfig = z.infer<typeof MergeQueueConfigSchema>;
1561
1614
  export type TriageConfig = z.infer<typeof TriageConfigSchema>;
1615
+ export type Preferences = z.infer<typeof PreferencesSchema>;
1562
1616
  /**
1563
1617
  * Project detection results
1564
1618
  */
package/dist/types.js CHANGED
@@ -280,6 +280,19 @@ const TriageConfigSchema = z.object({
280
280
  timeoutMs: z.number().int().positive().optional(),
281
281
  });
282
282
  // =============================================================================
283
+ // REPOSITORY PREFERENCES
284
+ // =============================================================================
285
+ const PreferencesSchema = z.object({
286
+ /** Run analysis automatically for PR webhook events (default: true) */
287
+ automatic_analysis: z.boolean().optional(),
288
+ /** Enable sandbox-backed verification for this repository */
289
+ sandbox_enabled: z.boolean().optional(),
290
+ /** Enroll submitted PRs in auto-merge by default */
291
+ auto_merge: z.boolean().optional(),
292
+ /** Allow Haystack to attempt automatic fixes */
293
+ auto_fix: z.boolean().optional(),
294
+ });
295
+ // =============================================================================
283
296
  // MAIN CONFIG
284
297
  // =============================================================================
285
298
  export const HaystackConfigSchema = z.object({
@@ -287,6 +300,8 @@ export const HaystackConfigSchema = z.object({
287
300
  version: z.literal('1'),
288
301
  /** Project name (for display) */
289
302
  name: z.string().optional(),
303
+ /** Repository-level Haystack behavior */
304
+ preferences: PreferencesSchema.optional(),
290
305
  /** Package manager override (auto-detected if not specified) */
291
306
  package_manager: z.enum(['npm', 'pnpm', 'yarn', 'bun']).optional(),
292
307
  /** Development server configuration */
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@haystackeditor/cli",
3
- "version": "0.15.20",
3
+ "version": "0.15.21",
4
4
  "description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
5
5
  "type": "module",
6
6
  "bin": {
7
- "haystack": "./dist/index.js"
7
+ "haystack": "dist/index.js"
8
8
  },
9
9
  "scripts": {
10
10
  "build": "rm -rf dist && tsc && cp -r src/assets dist/assets",