@haystackeditor/cli 0.15.18 → 0.15.20

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 (53) hide show
  1. package/README.md +86 -1
  2. package/dist/assets/skills/map-your-system.md +143 -0
  3. package/dist/commands/ask.js +2 -2
  4. package/dist/commands/config.d.ts +9 -0
  5. package/dist/commands/config.js +46 -14
  6. package/dist/commands/dismiss.d.ts +15 -3
  7. package/dist/commands/dismiss.js +66 -116
  8. package/dist/commands/mcp.js +96 -1
  9. package/dist/commands/policy.js +31 -12
  10. package/dist/commands/pr-status.d.ts +125 -2
  11. package/dist/commands/pr-status.js +88 -73
  12. package/dist/commands/pr.d.ts +1 -7
  13. package/dist/commands/pr.js +10 -21
  14. package/dist/commands/request-review.d.ts +12 -1
  15. package/dist/commands/request-review.js +42 -65
  16. package/dist/commands/review.d.ts +25 -0
  17. package/dist/commands/review.js +191 -0
  18. package/dist/commands/setup.js +4 -1
  19. package/dist/commands/skills.js +4 -0
  20. package/dist/commands/submit.d.ts +3 -0
  21. package/dist/commands/submit.js +178 -38
  22. package/dist/commands/system-map.d.ts +42 -0
  23. package/dist/commands/system-map.js +228 -0
  24. package/dist/commands/tokens.js +3 -2
  25. package/dist/commands/traces.js +2 -2
  26. package/dist/commands/triage.d.ts +1 -6
  27. package/dist/commands/triage.js +51 -55
  28. package/dist/index.js +116 -19
  29. package/dist/schema.d.ts +4 -0
  30. package/dist/schema.js +4 -0
  31. package/dist/states.d.ts +29 -0
  32. package/dist/states.js +46 -0
  33. package/dist/triage/runner.d.ts +7 -0
  34. package/dist/triage/runner.js +2 -2
  35. package/dist/utils/action-output.d.ts +24 -0
  36. package/dist/utils/action-output.js +26 -0
  37. package/dist/utils/analysis-api.d.ts +44 -0
  38. package/dist/utils/analysis-api.js +137 -6
  39. package/dist/utils/auth.js +7 -0
  40. package/dist/utils/git.d.ts +4 -1
  41. package/dist/utils/git.js +17 -7
  42. package/dist/utils/haystack-api.d.ts +15 -0
  43. package/dist/utils/haystack-api.js +58 -7
  44. package/dist/utils/pr-ref.d.ts +27 -0
  45. package/dist/utils/pr-ref.js +56 -0
  46. package/package.json +1 -1
  47. package/schemas/action.v1.json +22 -0
  48. package/schemas/error.v1.json +14 -0
  49. package/schemas/pr-status.v1.json +61 -0
  50. package/schemas/pr.v1.json +80 -17
  51. package/schemas/setup.v1.json +66 -12
  52. package/schemas/submit.v1.json +88 -0
  53. package/schemas/triage.v1.json +71 -18
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ import { dirname, join } from 'node:path';
25
25
  import chalk from 'chalk';
26
26
  import { Command, Option } from 'commander';
27
27
  import { statusCommand } from './commands/status.js';
28
+ import { withSchema } from './schema.js';
28
29
  import { initCommand } from './commands/init.js';
29
30
  import { authListCommand, authUseCommand, loginCommand, logoutCommand, whoamiCommand, } from './commands/login.js';
30
31
  import { handleAgenticTool, handleAutoMerge, handleAutoFix, handleWaitForReviewers, isAutoMergeEnabled, isAutoFixEnabled } from './commands/config.js';
@@ -36,6 +37,7 @@ import { listPolicies, addPolicy, removePolicy, initPolicies, addInstruction } f
36
37
  import { triageCommand } from './commands/triage.js';
37
38
  import { dismissCommand, markReviewedCommand, undismissCommand } from './commands/dismiss.js';
38
39
  import { requestReviewCommand } from './commands/request-review.js';
40
+ import { reviewCommand } from './commands/review.js';
39
41
  import { prStatusCommand } from './commands/pr-status.js';
40
42
  import { prReadCommand } from './commands/pr.js';
41
43
  import { inboxListCommand } from './commands/inbox.js';
@@ -45,6 +47,7 @@ import { setupCommand } from './commands/setup.js';
45
47
  import { schemaCommand, listSchemas } from './commands/schema-cmd.js';
46
48
  import { registerWebhook, listWebhooks, rotateWebhookSecret, setWebhookEnabled, listDeliveries, replayDelivery, } from './commands/webhooks.js';
47
49
  import { editRulesCommand, validateRulesCommand } from './commands/rules.js';
50
+ import { systemMapValidateCommand } from './commands/system-map.js';
48
51
  import { headlessLoginCommand, headlessLogoutCommand, listTokensCommand, revokeTokenCommand, } from './commands/tokens.js';
49
52
  // `mcp` is imported lazily inside its command action (below), NOT at the top level.
50
53
  // It is the only module that pulls in `@modelcontextprotocol/sdk`; keeping it out of
@@ -52,12 +55,19 @@ import { headlessLoginCommand, headlessLogoutCommand, listTokensCommand, revokeT
52
55
  // like `triage`/`submit`. (0.15.12 shipped `mcp.js` but published its package.json
53
56
  // WITHOUT `@modelcontextprotocol/sdk`, so the eager import here hard-crashed every
54
57
  // command at startup with ERR_MODULE_NOT_FOUND.)
55
- async function runPublicCommand(action) {
58
+ async function runPublicCommand(action, json) {
56
59
  try {
57
60
  await action();
58
61
  }
59
62
  catch (err) {
60
- console.error(chalk.red(err instanceof Error ? err.message : String(err)));
63
+ const message = err instanceof Error ? err.message : String(err);
64
+ // In --json mode stdout must still carry one parseable document — emit the
65
+ // versioned error envelope (`haystack schema error`) instead of leaving
66
+ // stdout empty with prose on stderr.
67
+ if (json) {
68
+ process.stdout.write(JSON.stringify(withSchema('error', { status: 'error', error: message }), null, 2) + '\n');
69
+ }
70
+ console.error(chalk.red(message));
61
71
  process.exitCode = 1;
62
72
  }
63
73
  }
@@ -111,7 +121,7 @@ program
111
121
  .description('Onboarding wizard — scan repos for rules/policies and write Haystack config')
112
122
  .option('--json', 'Agent mode: emit NDJSON events on stdout, read answers on stdin (no TTY prompts)')
113
123
  .option('--repo <repo...>', 'Repo(s) to configure (owner/name); skips the repo picker')
114
- .option('-y, --yes', 'Accept defaults non-interactively (keep all discovered items, confirm write)')
124
+ .option('-y, --yes', 'Accept defaults non-interactively (keep all discovered items, confirm write, ENABLE auto-merge — pass --no-auto-merge to opt out)')
115
125
  // Defining --no-auto-merge also accepts --auto-merge; default is "ask" unless
116
126
  // one is explicitly passed (detected via getOptionValueSource below).
117
127
  .option('--no-auto-merge', 'Set auto-merge in the written config (--auto-merge / --no-auto-merge; omit to be asked)')
@@ -246,7 +256,9 @@ program
246
256
  .option('--force', 'Skip pre-PR triage checks')
247
257
  .addOption(new Option('--auto-fix', 'Alpha auto-fix for entitled repositories').hideHelp())
248
258
  .option('--auto-merge', 'Apply auto-merge label (default: from .haystack.json, --no-auto-merge to disable)')
259
+ .option('--no-auto-merge', 'Do not apply the auto-merge label')
249
260
  .option('--no-wait', 'Skip waiting for analysis results')
261
+ .option('--json', 'Machine-readable: one JSON document on stdout (see `haystack schema submit`), progress on stderr')
250
262
  .option('--max-turns <n>', 'Max agentic turns per triage checker (overrides .haystack.json triage.maxTurns)', (v) => parseInt(v, 10))
251
263
  .option('--triage-timeout <seconds>', 'Wall-clock timeout per triage checker in seconds (overrides .haystack.json triage.timeoutMs)', (v) => parseInt(v, 10))
252
264
  .addHelpText('after', `
@@ -295,8 +307,22 @@ Review Routing:
295
307
  needs-assignment queue (a teammate will pick it up)
296
308
  • --review <username> Same label, plus requests review from that GitHub user
297
309
 
310
+ Machine-readable output (--json):
311
+ stdout carries exactly one JSON document (schema: \`haystack schema submit\`);
312
+ all progress goes to stderr. On success the payload includes the PR ref,
313
+ the resolved title/body and their sources, auto-merge/auto-fix state with
314
+ source, and the analysis outcome. On failure it is an error envelope
315
+ { status: "error", error: "..." }.
316
+
317
+ Exit codes:
318
+ 0 PR created/updated (including verdict "needs_input" — the PR exists;
319
+ read the verdict from the JSON or from \`haystack triage\`)
320
+ 1 Hard failure (bad flags, auth, push, PR creation, blocking triage
321
+ findings, or the analysis status check itself failing)
322
+
298
323
  Examples:
299
324
  haystack submit # Recommended: triage, create PR, auto-merge queue
325
+ haystack submit --json # Agent mode: JSON on stdout, progress on stderr
300
326
  haystack submit --force # Skip triage
301
327
  haystack submit --no-wait # Don't wait for analysis
302
328
  haystack submit --title "Fix auth" # Custom PR title
@@ -311,17 +337,23 @@ Examples:
311
337
  haystack submit --max-turns 12 # Raise triage turn cap to 12 per checker
312
338
  haystack submit --triage-timeout 300 # Raise triage wall-clock to 5 minutes
313
339
  `)
314
- .action(async (options) => {
315
- // Resolve --auto-merge default from .haystack.json when not explicitly set
316
- if (options.autoMerge === undefined) {
340
+ .action(async (options, command) => {
341
+ // Resolve --auto-merge / --auto-fix defaults from .haystack.json when not
342
+ // explicitly set, and record WHERE each value came from so submit can
343
+ // announce the source (silent config-driven defaults are invisible to
344
+ // the caller otherwise).
345
+ const autoMergeFromCli = command.getOptionValueSource('autoMerge') === 'cli';
346
+ const autoFixFromCli = command.getOptionValueSource('autoFix') === 'cli';
347
+ if (!autoMergeFromCli) {
317
348
  options.autoMerge = await isAutoMergeEnabled();
318
349
  }
319
- // Resolve --auto-fix default from .haystack.json (preferences.auto_fix).
320
350
  // Auto-fix remains alpha — the explicit --auto-fix flag still surfaces
321
351
  // discouragement warnings; opting in via repo config is the supported path.
322
- if (options.autoFix === undefined) {
352
+ if (!autoFixFromCli) {
323
353
  options.autoFix = await isAutoFixEnabled();
324
354
  }
355
+ options.autoMergeSource = autoMergeFromCli ? 'flag' : 'config';
356
+ options.autoFixSource = autoFixFromCli ? 'flag' : 'config';
325
357
  return submitCommand(options);
326
358
  });
327
359
  program
@@ -363,35 +395,36 @@ inbox
363
395
  .command('list')
364
396
  .description('List PRs in your Haystack inbox')
365
397
  .option('--json', 'Output as JSON')
366
- .action((options) => runPublicCommand(() => inboxListCommand(options)));
398
+ .action((options) => runPublicCommand(() => inboxListCommand(options), options.json));
367
399
  const prProgram = program.command('pr').description('Inspect one pull request');
368
400
  prProgram
369
401
  .command('get <ref>')
370
402
  .description('Get triage, merge blockers, and trace availability')
371
403
  .option('--json', 'Output as JSON')
372
- .action((ref, options) => runPublicCommand(() => prReadCommand(ref, options)));
404
+ .action((ref, options) => runPublicCommand(() => prReadCommand(ref, options), options.json));
373
405
  program
374
406
  .command('ask <ref> <question>')
375
407
  .description('Ask Haystack about a pull request')
376
408
  .option('--json', 'Output the answer and every consulted customer-facing source as JSON')
377
409
  .option('--session <id>', 'Continue a previous Ask Haystack session')
378
- .action((ref, question, options) => runPublicCommand(() => askHaystackCommand(ref, question, options)));
410
+ .action((ref, question, options) => runPublicCommand(() => askHaystackCommand(ref, question, options), options.json));
379
411
  const traces = program.command('traces').description('Inspect customer-owned Entire traces');
380
412
  traces
381
413
  .command('list <ref>')
382
414
  .description('List retained checkpoints for a pull request')
383
415
  .option('--json', 'Output as JSON')
384
- .action((ref, options) => runPublicCommand(() => tracesListCommand(ref, options)));
416
+ .action((ref, options) => runPublicCommand(() => tracesListCommand(ref, options), options.json));
385
417
  traces
386
418
  .command('get <ref> <checkpoint>')
387
419
  .description('Get retained transcript chunks for one checkpoint')
388
420
  .option('--cursor <cursor>', 'Zero-based chunk cursor')
389
421
  .option('--limit <count>', 'Chunks per page', '20')
390
422
  .option('--json', 'Output as JSON')
391
- .action((ref, checkpoint, options) => runPublicCommand(() => tracesGetCommand(ref, checkpoint, options)));
423
+ .action((ref, checkpoint, options) => runPublicCommand(() => tracesGetCommand(ref, checkpoint, options), options.json));
392
424
  program
393
425
  .command('dismiss <pr>')
394
426
  .description('Dismiss analysis findings for a PR')
427
+ .option('--json', 'Machine-readable output (see `haystack schema action`)')
395
428
  .addHelpText('after', `
396
429
  Dismiss analysis findings for a PR, moving it from "Issues Found" to
397
430
  "Good to Merge" in the Haystack feed.
@@ -413,6 +446,7 @@ Examples:
413
446
  program
414
447
  .command('mark-reviewed <pr>')
415
448
  .description('Mark human review as not needed for a PR')
449
+ .option('--json', 'Machine-readable output (see `haystack schema action`)')
416
450
  .addHelpText('after', `
417
451
  Mark human review as not needed for a PR, moving it from "Needs Review"
418
452
  to "Good to Merge" in the Haystack feed.
@@ -434,6 +468,7 @@ Examples:
434
468
  program
435
469
  .command('undismiss <pr>')
436
470
  .description('Undo a dismiss or mark-reviewed override for a PR')
471
+ .option('--json', 'Machine-readable output (see `haystack schema action`)')
437
472
  .addHelpText('after', `
438
473
  Clear all overrides (dismissed findings and/or review-not-needed) for a PR,
439
474
  returning it to its original feed bucket.
@@ -449,9 +484,39 @@ Examples:
449
484
  haystack undismiss acme/widgets#99 # Undo for specific repo
450
485
  `)
451
486
  .action(undismissCommand);
487
+ program
488
+ .command('review [pr]')
489
+ .description('Trigger a fresh Haystack analysis for a PR (machine analysis — for HUMAN review use request-review)')
490
+ .option('--no-wait', 'Exit after triggering instead of waiting for results')
491
+ .option('--json', 'Machine-readable output (see `haystack schema action`)')
492
+ .addHelpText('after', `
493
+ Re-run full Haystack analysis on a PR's current head.
494
+
495
+ Haystack analyzes a PR once when it's opened (or marked ready); later
496
+ pushes only get a resolution check against the existing findings. Use
497
+ this command when new commits deserve fresh detection, or any time you
498
+ want a second opinion.
499
+
500
+ When called without a PR identifier, uses the last submitted PR.
501
+
502
+ PR identifier formats:
503
+ (none) Last submitted PR
504
+ 123 PR number (uses current repo)
505
+ #123 PR number with hash
506
+ owner/repo#123 Fully qualified
507
+ https://github.com/owner/repo/pull/123 GitHub URL
508
+
509
+ Examples:
510
+ haystack review # Last submitted PR
511
+ haystack review 42 # Current repo, PR #42
512
+ haystack review acme/widgets#99 # Specific repo
513
+ haystack review 42 --no-wait # Trigger and exit
514
+ `)
515
+ .action(reviewCommand);
452
516
  program
453
517
  .command('request-review <pr> [reviewer]')
454
518
  .description('Tag a PR as needing human review')
519
+ .option('--json', 'Machine-readable output (see `haystack schema action`)')
455
520
  .addHelpText('after', `
456
521
  Tag any PR with "needs human review", even after it was created.
457
522
  Adds the haystack:needs-review label to the PR, moving it from
@@ -522,9 +587,9 @@ config
522
587
  .description('Auto-merge safe PRs submitted via haystack submit (on|off|status)')
523
588
  .addHelpText('after', `
524
589
  Actions:
525
- on, enable Enable auto-merge for safe PRs
526
- off, disable Disable auto-merge
527
- status Show current status (default)
590
+ on, enable, true Enable auto-merge for safe PRs
591
+ off, disable, false Disable auto-merge
592
+ status Show current status (default)
528
593
 
529
594
  When enabled, PRs submitted via \`haystack submit\` will be
530
595
  automatically merged if Haystack analysis finds no issues
@@ -542,9 +607,9 @@ config
542
607
  .description('(Alpha) Auto-fix mechanical issues on PRs submitted via haystack submit (on|off|status)')
543
608
  .addHelpText('after', `
544
609
  Actions:
545
- on, enable Enable auto-fix for mechanical issues
546
- off, disable Disable auto-fix
547
- status Show current status (default)
610
+ on, enable, true Enable auto-fix for mechanical issues
611
+ off, disable, false Disable auto-fix
612
+ status Show current status (default)
548
613
 
549
614
  ⚠ Auto-fix is alpha. When enabled, PRs submitted via \`haystack submit\`
550
615
  are labeled haystack:auto-fix. The Haystack analysis pipeline then
@@ -853,6 +918,38 @@ rules
853
918
  process.exit(1);
854
919
  }
855
920
  });
921
+ // ─── system-map ──────────────────────────────────────────────────────────────
922
+ const systemMap = program
923
+ .command('system-map')
924
+ .description('Manage .haystack/system.yml — runtime facts that help Haystack QA run your system');
925
+ systemMap
926
+ .command('validate')
927
+ .description('Check .haystack/system.yml against the rules Haystack QA will apply')
928
+ .option('--json', 'Machine-readable result')
929
+ .addHelpText('after', `
930
+ The system map holds VERIFIED facts about how your system works — install/
931
+ build commands, how services boot and signal readiness, seed/reset commands,
932
+ dev-only test logins, external services that must never be touched live, and
933
+ prebuilt CI artifacts. Haystack QA uses these facts to run more of what it
934
+ already wants to test; the file never selects tests.
935
+
936
+ Author it with the /map-your-system skill (haystack skills install), then run
937
+ this before committing. Checks: valid YAML + version, no test-selection keys,
938
+ no secret values, non-empty boot/login/run commands, artifact declarations.
939
+
940
+ Examples:
941
+ haystack system-map validate
942
+ haystack system-map validate --json
943
+ `)
944
+ .action(async (opts) => {
945
+ try {
946
+ await systemMapValidateCommand(opts);
947
+ }
948
+ catch (err) {
949
+ console.error(chalk.red('validate failed:'), err instanceof Error ? err.message : err);
950
+ process.exit(1);
951
+ }
952
+ });
856
953
  // ─── mcp ─────────────────────────────────────────────────────────────────────
857
954
  program
858
955
  .command('mcp')
package/dist/schema.d.ts CHANGED
@@ -13,9 +13,13 @@ export declare const SCHEMA_VERSIONS: {
13
13
  readonly triage: "2.0.0";
14
14
  readonly setup: "1.0.0";
15
15
  readonly pr: "2.0.0";
16
+ readonly 'pr-status': "1.0.0";
16
17
  readonly inbox: "1.0.0";
17
18
  readonly ask: "1.0.0";
18
19
  readonly traces: "1.0.0";
20
+ readonly submit: "1.0.0";
21
+ readonly action: "1.0.0";
22
+ readonly error: "1.0.0";
19
23
  };
20
24
  export type SchemaName = keyof typeof SCHEMA_VERSIONS;
21
25
  /** Wrap a payload with the `schema_version` envelope. */
package/dist/schema.js CHANGED
@@ -13,9 +13,13 @@ export const SCHEMA_VERSIONS = {
13
13
  triage: '2.0.0',
14
14
  setup: '1.0.0',
15
15
  pr: '2.0.0',
16
+ 'pr-status': '1.0.0',
16
17
  inbox: '1.0.0',
17
18
  ask: '1.0.0',
18
19
  traces: '1.0.0',
20
+ submit: '1.0.0',
21
+ action: '1.0.0',
22
+ error: '1.0.0',
19
23
  };
20
24
  /** Wrap a payload with the `schema_version` envelope. */
21
25
  export function withSchema(name, payload) {
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Canonical machine-readable PR-state vocabulary for every CLI JSON surface.
3
+ *
4
+ * One concept, one spelling: all `--json` output uses snake_case state tokens.
5
+ * Before this module existed the same state appeared as `good_to_merge`
6
+ * (triage/pr get/inbox) and `good-to-merge` (pr-status, raw server buckets),
7
+ * so no single parser worked across commands. Server responses that use
8
+ * kebab-case are normalized at the edge via `normalizeFeedBucket`.
9
+ */
10
+ /** Public triage verdict — emitted by `triage`, `pr get`, `submit`, `pr-status`. */
11
+ export declare const TRIAGE_VERDICTS: readonly ["good_to_merge", "needs_review", "needs_input"];
12
+ export type TriageVerdict = (typeof TRIAGE_VERDICTS)[number];
13
+ /**
14
+ * Map the server's internal analysis verdict ('clean' | 'has-issues' |
15
+ * 'needs-review', with a rating-based fallback for pre-verdict analyses) to
16
+ * the public triage verdict. This mapping used to be duplicated inline in
17
+ * triage.ts and pr.ts.
18
+ */
19
+ export declare function toPublicVerdict(analysisVerdict: string | null | undefined, haystackRating?: number | null): TriageVerdict;
20
+ /**
21
+ * Known feed buckets in canonical snake_case form. The server may introduce
22
+ * new buckets; `normalizeFeedBucket` converts mechanically so unknown buckets
23
+ * pass through in the same convention instead of crashing or leaking kebab-case.
24
+ */
25
+ export declare const FEED_BUCKETS: readonly ["analyzing", "auto_fixing", "good_to_merge", "failed_when_merging", "issues", "needs_assignment", "needs_shepherding", "needs_fixing", "awaiting_review", "review_requested", "snoozed"];
26
+ export type FeedBucket = (typeof FEED_BUCKETS)[number];
27
+ /** Normalize a server feed bucket (kebab-case) to the canonical snake_case form. */
28
+ export declare function normalizeFeedBucket(bucket: string): string;
29
+ export declare function normalizeFeedBucket(bucket: string | null | undefined): string | null;
package/dist/states.js ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Canonical machine-readable PR-state vocabulary for every CLI JSON surface.
3
+ *
4
+ * One concept, one spelling: all `--json` output uses snake_case state tokens.
5
+ * Before this module existed the same state appeared as `good_to_merge`
6
+ * (triage/pr get/inbox) and `good-to-merge` (pr-status, raw server buckets),
7
+ * so no single parser worked across commands. Server responses that use
8
+ * kebab-case are normalized at the edge via `normalizeFeedBucket`.
9
+ */
10
+ /** Public triage verdict — emitted by `triage`, `pr get`, `submit`, `pr-status`. */
11
+ export const TRIAGE_VERDICTS = ['good_to_merge', 'needs_review', 'needs_input'];
12
+ /**
13
+ * Map the server's internal analysis verdict ('clean' | 'has-issues' |
14
+ * 'needs-review', with a rating-based fallback for pre-verdict analyses) to
15
+ * the public triage verdict. This mapping used to be duplicated inline in
16
+ * triage.ts and pr.ts.
17
+ */
18
+ export function toPublicVerdict(analysisVerdict, haystackRating) {
19
+ const verdict = analysisVerdict ?? ((haystackRating ?? 0) >= 5 ? 'clean' : 'has-issues');
20
+ if (verdict === 'clean')
21
+ return 'good_to_merge';
22
+ if (verdict === 'needs-review')
23
+ return 'needs_review';
24
+ return 'needs_input';
25
+ }
26
+ /**
27
+ * Known feed buckets in canonical snake_case form. The server may introduce
28
+ * new buckets; `normalizeFeedBucket` converts mechanically so unknown buckets
29
+ * pass through in the same convention instead of crashing or leaking kebab-case.
30
+ */
31
+ export const FEED_BUCKETS = [
32
+ 'analyzing',
33
+ 'auto_fixing',
34
+ 'good_to_merge',
35
+ 'failed_when_merging',
36
+ 'issues',
37
+ 'needs_assignment',
38
+ 'needs_shepherding',
39
+ 'needs_fixing',
40
+ 'awaiting_review',
41
+ 'review_requested',
42
+ 'snoozed',
43
+ ];
44
+ export function normalizeFeedBucket(bucket) {
45
+ return bucket == null ? null : bucket.replace(/-/g, '_');
46
+ }
@@ -16,6 +16,13 @@ export interface RunTriageOptions {
16
16
  * Returns the first one found, or null if none available.
17
17
  */
18
18
  export declare function detectAgenticCLI(): AgenticCLI | null;
19
+ /**
20
+ * Build the CLI command + args for the detected agentic tool.
21
+ */
22
+ export declare function buildCommand(cli: AgenticCLI, prompt: string, maxTurns: number): {
23
+ command: string;
24
+ args: string[];
25
+ };
19
26
  /**
20
27
  * Run pre-PR triage checks using parallel sub-agent processes.
21
28
  *
@@ -86,7 +86,7 @@ export function detectAgenticCLI() {
86
86
  /**
87
87
  * Build the CLI command + args for the detected agentic tool.
88
88
  */
89
- function buildCommand(cli, prompt, maxTurns) {
89
+ export function buildCommand(cli, prompt, maxTurns) {
90
90
  switch (cli) {
91
91
  case 'claude':
92
92
  return {
@@ -101,7 +101,7 @@ function buildCommand(cli, prompt, maxTurns) {
101
101
  case 'codex':
102
102
  return {
103
103
  command: 'codex',
104
- args: ['exec', prompt, '--full-auto', '--ephemeral'],
104
+ args: ['exec', prompt, '--full-auto'],
105
105
  };
106
106
  case 'gemini':
107
107
  return {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared machine-readable envelope for the mutating commands
3
+ * (dismiss, mark-reviewed, undismiss, request-review, review).
4
+ *
5
+ * One schema for all of them (`haystack schema action`): every payload
6
+ * carries `action`, `status`, and `ref`, so an agent can drive any state
7
+ * change with a single parser. Success payloads go to stdout as the only
8
+ * stdout content; human/progress output stays on stderr in --json mode.
9
+ */
10
+ export type ActionName = 'dismiss' | 'mark_reviewed' | 'undismiss' | 'request_review' | 'trigger_review' | 'clear_pending';
11
+ export interface ActionPayload extends Record<string, unknown> {
12
+ action: ActionName;
13
+ status: 'ok';
14
+ ref: string;
15
+ /** Human-readable statement of what changed. */
16
+ detail: string;
17
+ }
18
+ /** Print the success envelope (JSON mode only — human output is the caller's). */
19
+ export declare function emitActionPayload(payload: ActionPayload): void;
20
+ /**
21
+ * Print a failure (JSON error envelope on stdout when --json, red text on
22
+ * stderr always) and exit 1.
23
+ */
24
+ export declare function failAction(json: boolean | undefined, action: ActionName, ref: string | null, message: string): never;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared machine-readable envelope for the mutating commands
3
+ * (dismiss, mark-reviewed, undismiss, request-review, review).
4
+ *
5
+ * One schema for all of them (`haystack schema action`): every payload
6
+ * carries `action`, `status`, and `ref`, so an agent can drive any state
7
+ * change with a single parser. Success payloads go to stdout as the only
8
+ * stdout content; human/progress output stays on stderr in --json mode.
9
+ */
10
+ import chalk from 'chalk';
11
+ import { withSchema } from '../schema.js';
12
+ /** Print the success envelope (JSON mode only — human output is the caller's). */
13
+ export function emitActionPayload(payload) {
14
+ console.log(JSON.stringify(withSchema('action', payload), null, 2));
15
+ }
16
+ /**
17
+ * Print a failure (JSON error envelope on stdout when --json, red text on
18
+ * stderr always) and exit 1.
19
+ */
20
+ export function failAction(json, action, ref, message) {
21
+ if (json) {
22
+ process.stdout.write(JSON.stringify(withSchema('action', { action, status: 'error', ref, error: message }), null, 2) + '\n');
23
+ }
24
+ console.error(chalk.red(`\n${message}\n`));
25
+ process.exit(1);
26
+ }
@@ -110,6 +110,39 @@ export type AnalysisReadyResult = {
110
110
  status: 'error';
111
111
  message: string;
112
112
  };
113
+ export type TriggerAnalysisResult = {
114
+ status: 'started';
115
+ requestId: string;
116
+ } | {
117
+ status: 'already_processing';
118
+ requestId: string;
119
+ } | {
120
+ status: 'rate_limited';
121
+ message: string;
122
+ retryAfterSeconds?: number;
123
+ } | {
124
+ status: 'denied';
125
+ message: string;
126
+ } | {
127
+ status: 'error';
128
+ message: string;
129
+ };
130
+ /**
131
+ * Trigger a fresh full analysis for a PR via the auth-worker
132
+ * (`/api/analysis-trigger/{owner}/{repo}/{pr}`). Requires auth — triggers
133
+ * start a real analysis run. Returns the request id to wait on.
134
+ */
135
+ export declare function triggerAnalysis(owner: string, repo: string, prNumber: number, token: string, headSha?: string): Promise<TriggerAnalysisResult>;
136
+ export type TriggerRunStatus = 'pending' | 'COMPLETED' | 'FAILED' | 'SUPERSEDED' | 'error';
137
+ /**
138
+ * Poll the status of a triggered run by request id. The PR-level result
139
+ * endpoint can't distinguish a fresh re-run on the same head from the
140
+ * previous completed analysis — the request id can.
141
+ */
142
+ export declare function fetchTriggerStatus(requestId: string, token: string): Promise<{
143
+ status: TriggerRunStatus;
144
+ message?: string;
145
+ }>;
113
146
  /**
114
147
  * Check if analysis results are available for a PR by polling the result endpoint.
115
148
  * Returns 'ready' if complete, 'pending' if not yet available, or 'error' for server failures.
@@ -139,5 +172,16 @@ export interface AutoFixManifest {
139
172
  timestamp: string;
140
173
  resultSha?: string;
141
174
  }
175
+ /**
176
+ * Null means "artifact does not exist (yet)" — 404s and transient
177
+ * network/parse failures. Auth failures (401/403) THROW a classified
178
+ * HaystackApiError instead: collapsing them into null used to make an
179
+ * expired token read as "not available yet — try again in a moment",
180
+ * sending agents into a retry loop when the fix was `haystack login`.
181
+ */
142
182
  export declare function fetchAutoFixManifest(owner: string, repo: string, prNumber: number, token?: string): Promise<AutoFixManifest | null>;
183
+ /**
184
+ * Null means "synthesis does not exist (yet)". Auth failures (401/403) THROW
185
+ * a classified HaystackApiError — see fetchAutoFixManifest for why.
186
+ */
143
187
  export declare function fetchRatingSynthesis(owner: string, repo: string, prNumber: number, token?: string): Promise<RatingSynthesis | null>;