@haystackeditor/cli 0.15.17 → 0.15.19
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 +45 -6
- package/dist/assets/skills/map-your-system.md +138 -0
- package/dist/commands/ask.d.ts +14 -0
- package/dist/commands/ask.js +20 -0
- package/dist/commands/inbox.d.ts +65 -0
- package/dist/commands/inbox.js +137 -0
- package/dist/commands/mcp.js +88 -134
- package/dist/commands/pr.d.ts +37 -1
- package/dist/commands/pr.js +95 -77
- package/dist/commands/review.d.ts +24 -0
- package/dist/commands/review.js +193 -0
- package/dist/commands/schema-cmd.js +1 -1
- package/dist/commands/skills.js +4 -0
- package/dist/commands/traces.d.ts +48 -0
- package/dist/commands/traces.js +92 -0
- package/dist/commands/triage.d.ts +12 -0
- package/dist/commands/triage.js +90 -58
- package/dist/commands/webhooks.js +3 -6
- package/dist/index.js +79 -148
- package/dist/schema.d.ts +5 -2
- package/dist/schema.js +5 -2
- package/dist/utils/analysis-api.d.ts +40 -0
- package/dist/utils/analysis-api.js +108 -3
- package/dist/utils/haystack-api.d.ts +20 -0
- package/dist/utils/haystack-api.js +66 -0
- package/package.json +4 -4
- package/schemas/ask.v1.json +40 -0
- package/schemas/inbox.v1.json +27 -0
- package/schemas/pr.v2.json +54 -0
- package/schemas/traces.v1.json +53 -0
- package/schemas/triage.v2.json +64 -0
- package/dist/assets/skills/install-verification.md +0 -67
- package/dist/commands/verification.d.ts +0 -21
- package/dist/commands/verification.js +0 -291
- package/dist/verification/contract.d.ts +0 -240
- package/dist/verification/contract.js +0 -115
- package/dist/verification/init.d.ts +0 -10
- package/dist/verification/init.js +0 -243
- package/dist/verification/manifest.d.ts +0 -49
- package/dist/verification/manifest.js +0 -186
- package/dist/verification/runner.d.ts +0 -20
- package/dist/verification/runner.js +0 -102
- package/dist/verification/safety.d.ts +0 -10
- package/dist/verification/safety.js +0 -252
- package/dist/verification/validate.d.ts +0 -12
- package/dist/verification/validate.js +0 -219
package/dist/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import { readFileSync } from 'node:fs';
|
|
|
23
23
|
import { fileURLToPath } from 'node:url';
|
|
24
24
|
import { dirname, join } from 'node:path';
|
|
25
25
|
import chalk from 'chalk';
|
|
26
|
-
import { Command } from 'commander';
|
|
26
|
+
import { Command, Option } from 'commander';
|
|
27
27
|
import { statusCommand } from './commands/status.js';
|
|
28
28
|
import { initCommand } from './commands/init.js';
|
|
29
29
|
import { authListCommand, authUseCommand, loginCommand, logoutCommand, whoamiCommand, } from './commands/login.js';
|
|
@@ -36,12 +36,16 @@ import { listPolicies, addPolicy, removePolicy, initPolicies, addInstruction } f
|
|
|
36
36
|
import { triageCommand } from './commands/triage.js';
|
|
37
37
|
import { dismissCommand, markReviewedCommand, undismissCommand } from './commands/dismiss.js';
|
|
38
38
|
import { requestReviewCommand } from './commands/request-review.js';
|
|
39
|
+
import { reviewCommand } from './commands/review.js';
|
|
39
40
|
import { prStatusCommand } from './commands/pr-status.js';
|
|
41
|
+
import { prReadCommand } from './commands/pr.js';
|
|
42
|
+
import { inboxListCommand } from './commands/inbox.js';
|
|
43
|
+
import { askHaystackCommand } from './commands/ask.js';
|
|
44
|
+
import { tracesGetCommand, tracesListCommand } from './commands/traces.js';
|
|
40
45
|
import { setupCommand } from './commands/setup.js';
|
|
41
46
|
import { schemaCommand, listSchemas } from './commands/schema-cmd.js';
|
|
42
47
|
import { registerWebhook, listWebhooks, rotateWebhookSecret, setWebhookEnabled, listDeliveries, replayDelivery, } from './commands/webhooks.js';
|
|
43
48
|
import { editRulesCommand, validateRulesCommand } from './commands/rules.js';
|
|
44
|
-
import { verificationInitCommand, verificationValidateCommand, verificationPreflightCommand, verificationRunCommand, verificationCollectCommand, verificationCheckSafetyCommand, } from './commands/verification.js';
|
|
45
49
|
import { headlessLoginCommand, headlessLogoutCommand, listTokensCommand, revokeTokenCommand, } from './commands/tokens.js';
|
|
46
50
|
// `mcp` is imported lazily inside its command action (below), NOT at the top level.
|
|
47
51
|
// It is the only module that pulls in `@modelcontextprotocol/sdk`; keeping it out of
|
|
@@ -49,11 +53,15 @@ import { headlessLoginCommand, headlessLogoutCommand, listTokensCommand, revokeT
|
|
|
49
53
|
// like `triage`/`submit`. (0.15.12 shipped `mcp.js` but published its package.json
|
|
50
54
|
// WITHOUT `@modelcontextprotocol/sdk`, so the eager import here hard-crashed every
|
|
51
55
|
// command at startup with ERR_MODULE_NOT_FOUND.)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
async function runPublicCommand(action) {
|
|
57
|
+
try {
|
|
58
|
+
await action();
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
57
65
|
/** Read the published version from package.json (dist/index.js → ../package.json)
|
|
58
66
|
* so `haystack --version` can't drift from the package version. Falls back
|
|
59
67
|
* gracefully so the flag never throws if the file can't be read. */
|
|
@@ -77,7 +85,7 @@ program
|
|
|
77
85
|
.version(getVersion());
|
|
78
86
|
program
|
|
79
87
|
.command('schema [name]')
|
|
80
|
-
.description('Print the JSON Schema for a command\'s --json output
|
|
88
|
+
.description('Print the JSON Schema for a command\'s --json output')
|
|
81
89
|
.action((name) => {
|
|
82
90
|
if (!name) {
|
|
83
91
|
listSchemas();
|
|
@@ -237,7 +245,7 @@ program
|
|
|
237
245
|
.option('--draft', 'Create as draft PR')
|
|
238
246
|
.option('--review [reviewer]', 'Request human review (BLOCKS auto-merge — only use when explicitly asked)')
|
|
239
247
|
.option('--force', 'Skip pre-PR triage checks')
|
|
240
|
-
.
|
|
248
|
+
.addOption(new Option('--auto-fix', 'Alpha auto-fix for entitled repositories').hideHelp())
|
|
241
249
|
.option('--auto-merge', 'Apply auto-merge label (default: from .haystack.json, --no-auto-merge to disable)')
|
|
242
250
|
.option('--no-wait', 'Skip waiting for analysis results')
|
|
243
251
|
.option('--max-turns <n>', 'Max agentic turns per triage checker (overrides .haystack.json triage.maxTurns)', (v) => parseInt(v, 10))
|
|
@@ -258,8 +266,6 @@ Pre-PR Triage:
|
|
|
258
266
|
|
|
259
267
|
Use --force to skip triage entirely.
|
|
260
268
|
Use --no-wait to skip waiting for analysis results.
|
|
261
|
-
Use --auto-fix only when explicitly opting into the alpha fixer for
|
|
262
|
-
straightforward mechanical issues.
|
|
263
269
|
|
|
264
270
|
Triage budgets:
|
|
265
271
|
• --max-turns <n> Raise/lower the per-checker tool-use turn cap.
|
|
@@ -282,10 +288,6 @@ Review Routing:
|
|
|
282
288
|
enters the auto-merge queue after submission. Otherwise it still runs the
|
|
283
289
|
same Haystack analysis workflow without enabling auto-merge.
|
|
284
290
|
|
|
285
|
-
⚠ --auto-fix is currently alpha and discouraged by default. Prefer plain
|
|
286
|
-
haystack submit unless you explicitly want the sandbox fixer to attempt
|
|
287
|
-
straightforward mechanical fixes before issues hit the Feed.
|
|
288
|
-
|
|
289
291
|
⚠ Only use --review when the user EXPLICITLY asks for human review.
|
|
290
292
|
--review BLOCKS auto-merge — the PR will NOT merge until a human approves,
|
|
291
293
|
which delays merging. Do not add --review "just to be safe".
|
|
@@ -296,7 +298,6 @@ Review Routing:
|
|
|
296
298
|
|
|
297
299
|
Examples:
|
|
298
300
|
haystack submit # Recommended: triage, create PR, auto-merge queue
|
|
299
|
-
haystack submit --auto-fix # Discouraged alpha: auto-fix straightforward mechanical issues
|
|
300
301
|
haystack submit --force # Skip triage
|
|
301
302
|
haystack submit --no-wait # Don't wait for analysis
|
|
302
303
|
haystack submit --title "Fix auth" # Custom PR title
|
|
@@ -358,6 +359,37 @@ Examples:
|
|
|
358
359
|
haystack triage --clear # Clear pending state
|
|
359
360
|
`)
|
|
360
361
|
.action(triageCommand);
|
|
362
|
+
const inbox = program.command('inbox').description('List Haystack work needing your attention');
|
|
363
|
+
inbox
|
|
364
|
+
.command('list')
|
|
365
|
+
.description('List PRs in your Haystack inbox')
|
|
366
|
+
.option('--json', 'Output as JSON')
|
|
367
|
+
.action((options) => runPublicCommand(() => inboxListCommand(options)));
|
|
368
|
+
const prProgram = program.command('pr').description('Inspect one pull request');
|
|
369
|
+
prProgram
|
|
370
|
+
.command('get <ref>')
|
|
371
|
+
.description('Get triage, merge blockers, and trace availability')
|
|
372
|
+
.option('--json', 'Output as JSON')
|
|
373
|
+
.action((ref, options) => runPublicCommand(() => prReadCommand(ref, options)));
|
|
374
|
+
program
|
|
375
|
+
.command('ask <ref> <question>')
|
|
376
|
+
.description('Ask Haystack about a pull request')
|
|
377
|
+
.option('--json', 'Output the answer and every consulted customer-facing source as JSON')
|
|
378
|
+
.option('--session <id>', 'Continue a previous Ask Haystack session')
|
|
379
|
+
.action((ref, question, options) => runPublicCommand(() => askHaystackCommand(ref, question, options)));
|
|
380
|
+
const traces = program.command('traces').description('Inspect customer-owned Entire traces');
|
|
381
|
+
traces
|
|
382
|
+
.command('list <ref>')
|
|
383
|
+
.description('List retained checkpoints for a pull request')
|
|
384
|
+
.option('--json', 'Output as JSON')
|
|
385
|
+
.action((ref, options) => runPublicCommand(() => tracesListCommand(ref, options)));
|
|
386
|
+
traces
|
|
387
|
+
.command('get <ref> <checkpoint>')
|
|
388
|
+
.description('Get retained transcript chunks for one checkpoint')
|
|
389
|
+
.option('--cursor <cursor>', 'Zero-based chunk cursor')
|
|
390
|
+
.option('--limit <count>', 'Chunks per page', '20')
|
|
391
|
+
.option('--json', 'Output as JSON')
|
|
392
|
+
.action((ref, checkpoint, options) => runPublicCommand(() => tracesGetCommand(ref, checkpoint, options)));
|
|
361
393
|
program
|
|
362
394
|
.command('dismiss <pr>')
|
|
363
395
|
.description('Dismiss analysis findings for a PR')
|
|
@@ -418,6 +450,34 @@ Examples:
|
|
|
418
450
|
haystack undismiss acme/widgets#99 # Undo for specific repo
|
|
419
451
|
`)
|
|
420
452
|
.action(undismissCommand);
|
|
453
|
+
program
|
|
454
|
+
.command('review [pr]')
|
|
455
|
+
.description('Trigger a fresh Haystack analysis for a PR')
|
|
456
|
+
.option('--no-wait', 'Exit after triggering instead of waiting for results')
|
|
457
|
+
.addHelpText('after', `
|
|
458
|
+
Re-run full Haystack analysis on a PR's current head.
|
|
459
|
+
|
|
460
|
+
Haystack analyzes a PR once when it's opened (or marked ready); later
|
|
461
|
+
pushes only get a resolution check against the existing findings. Use
|
|
462
|
+
this command when new commits deserve fresh detection, or any time you
|
|
463
|
+
want a second opinion.
|
|
464
|
+
|
|
465
|
+
When called without a PR identifier, uses the last submitted PR.
|
|
466
|
+
|
|
467
|
+
PR identifier formats:
|
|
468
|
+
(none) Last submitted PR
|
|
469
|
+
123 PR number (uses current repo)
|
|
470
|
+
#123 PR number with hash
|
|
471
|
+
owner/repo#123 Fully qualified
|
|
472
|
+
https://github.com/owner/repo/pull/123 GitHub URL
|
|
473
|
+
|
|
474
|
+
Examples:
|
|
475
|
+
haystack review # Last submitted PR
|
|
476
|
+
haystack review 42 # Current repo, PR #42
|
|
477
|
+
haystack review acme/widgets#99 # Specific repo
|
|
478
|
+
haystack review 42 --no-wait # Trigger and exit
|
|
479
|
+
`)
|
|
480
|
+
.action(reviewCommand);
|
|
421
481
|
program
|
|
422
482
|
.command('request-review <pr> [reviewer]')
|
|
423
483
|
.description('Tag a PR as needing human review')
|
|
@@ -447,7 +507,7 @@ program
|
|
|
447
507
|
.option('--json', 'Output as JSON')
|
|
448
508
|
.addHelpText('after', `
|
|
449
509
|
Show what bucket a PR is in within the Haystack pipeline:
|
|
450
|
-
analyzing,
|
|
510
|
+
analyzing, good-to-merge, issues, needs-assignment, etc.
|
|
451
511
|
|
|
452
512
|
PR identifier formats:
|
|
453
513
|
123 PR number (uses current repo)
|
|
@@ -507,7 +567,7 @@ Examples:
|
|
|
507
567
|
`)
|
|
508
568
|
.action(handleAutoMerge);
|
|
509
569
|
config
|
|
510
|
-
.command('auto-fix [action]')
|
|
570
|
+
.command('auto-fix [action]', { hidden: true })
|
|
511
571
|
.description('(Alpha) Auto-fix mechanical issues on PRs submitted via haystack submit (on|off|status)')
|
|
512
572
|
.addHelpText('after', `
|
|
513
573
|
Actions:
|
|
@@ -698,7 +758,7 @@ section when a PR touches matching files.
|
|
|
698
758
|
// idempotency).
|
|
699
759
|
const webhooks = program
|
|
700
760
|
.command('webhooks')
|
|
701
|
-
.description('Manage outbound webhooks
|
|
761
|
+
.description('Manage outbound webhooks');
|
|
702
762
|
webhooks
|
|
703
763
|
.command('register')
|
|
704
764
|
.description('Register a webhook URL for the current repo and mint a fresh HMAC secret')
|
|
@@ -822,135 +882,6 @@ rules
|
|
|
822
882
|
process.exit(1);
|
|
823
883
|
}
|
|
824
884
|
});
|
|
825
|
-
// ─── verification ────────────────────────────────────────────────────────────
|
|
826
|
-
//
|
|
827
|
-
// Repo-local verification contract (.haystack/verification.yml): how to boot
|
|
828
|
-
// the app, prove it's ready, log in as test personas, run scenarios, and
|
|
829
|
-
// collect evidence into a standard artifact manifest + review packet.
|
|
830
|
-
// See docs/VERIFICATION-CONTRACT.md.
|
|
831
|
-
const verification = program
|
|
832
|
-
.command('verification')
|
|
833
|
-
.description('Repo verification contract — validate, run scenarios, collect evidence');
|
|
834
|
-
verification
|
|
835
|
-
.command('init')
|
|
836
|
-
.description('Scaffold .haystack/verification.yml, preflight/smoke scripts, and the agent skill')
|
|
837
|
-
.option('-f, --force', 'Overwrite existing files')
|
|
838
|
-
.option('--skill', 'Only write the install-verification agent skill (hand the integration to a coding agent)')
|
|
839
|
-
.addHelpText('after', `
|
|
840
|
-
Creates:
|
|
841
|
-
.haystack/verification.yml the verification contract
|
|
842
|
-
scripts/haystack-preflight app-is-ready check
|
|
843
|
-
scripts/haystack-smoke boot + capture-evidence smoke scenario
|
|
844
|
-
.haystack/AGENTS.md how agents should use the contract
|
|
845
|
-
.haystack/skills/install-verification/SKILL.md skill for completing the integration
|
|
846
|
-
|
|
847
|
-
Detection fills in framework, package manager, dev command, port, and any
|
|
848
|
-
auth-bypass env var found in the repo's own env examples. Review the output —
|
|
849
|
-
detection is a starting point, not truth.
|
|
850
|
-
`)
|
|
851
|
-
.action(async (opts) => {
|
|
852
|
-
try {
|
|
853
|
-
await verificationInitCommand(opts);
|
|
854
|
-
}
|
|
855
|
-
catch (err) {
|
|
856
|
-
console.error(chalk.red('init failed:'), err instanceof Error ? err.message : err);
|
|
857
|
-
process.exit(1);
|
|
858
|
-
}
|
|
859
|
-
});
|
|
860
|
-
verification
|
|
861
|
-
.command('validate')
|
|
862
|
-
.description('Check whether the repo satisfies the verification contract')
|
|
863
|
-
.option('--json', 'Machine-readable report')
|
|
864
|
-
.addHelpText('after', `
|
|
865
|
-
Readiness verdicts:
|
|
866
|
-
ready all checks pass (exit 0)
|
|
867
|
-
partially_ready usable, with gaps (exit 0)
|
|
868
|
-
not_ready contract missing/invalid or commands broken (exit 1)
|
|
869
|
-
unsafe contract permits real side effects, or login helpers
|
|
870
|
-
lack a production-disabled proof (exit 2)
|
|
871
|
-
`)
|
|
872
|
-
.action(async (opts) => {
|
|
873
|
-
try {
|
|
874
|
-
await verificationValidateCommand(opts);
|
|
875
|
-
}
|
|
876
|
-
catch (err) {
|
|
877
|
-
console.error(chalk.red('validate failed:'), err instanceof Error ? err.message : err);
|
|
878
|
-
process.exit(1);
|
|
879
|
-
}
|
|
880
|
-
});
|
|
881
|
-
verification
|
|
882
|
-
.command('preflight')
|
|
883
|
-
.description('Run the contract preflight — is the app up and answering?')
|
|
884
|
-
.action(async () => {
|
|
885
|
-
try {
|
|
886
|
-
await verificationPreflightCommand();
|
|
887
|
-
}
|
|
888
|
-
catch (err) {
|
|
889
|
-
console.error(chalk.red('preflight failed:'), err instanceof Error ? err.message : err);
|
|
890
|
-
process.exit(1);
|
|
891
|
-
}
|
|
892
|
-
});
|
|
893
|
-
verification
|
|
894
|
-
.command('run <scenario>')
|
|
895
|
-
.description('Run a contract scenario and write the artifact manifest + review packet')
|
|
896
|
-
.option('--json', 'Print the manifest as JSON')
|
|
897
|
-
.option('--setup', 'Run environment.setup first (clean-clone mode)')
|
|
898
|
-
.option('--skip-preflight', 'Skip the preflight check')
|
|
899
|
-
.option('--timeout <seconds>', 'Override the scenario timeout', (v) => parseInt(v, 10))
|
|
900
|
-
.addHelpText('after', `
|
|
901
|
-
Runs preflight, then the scenario command, capturing stdout/stderr to the
|
|
902
|
-
artifact directory. Ends with a status:
|
|
903
|
-
verified scenario passed (exit 0)
|
|
904
|
-
failed scenario failed (exit 1)
|
|
905
|
-
environment_not_ready setup/preflight failed before the scenario ran (exit 1)
|
|
906
|
-
unsafe_to_verify safety policy permits real side effects (exit 2)
|
|
907
|
-
|
|
908
|
-
Evidence lands in the contract's artifact_dir:
|
|
909
|
-
manifest-<scenario>.json machine-readable evidence manifest
|
|
910
|
-
review-packet-<scenario>.md human-readable review packet
|
|
911
|
-
logs/ captured command output
|
|
912
|
-
|
|
913
|
-
Examples:
|
|
914
|
-
haystack verification run smoke
|
|
915
|
-
haystack verification run billing_upgrade --json
|
|
916
|
-
haystack verification run smoke --setup # clean-clone mode (CI)
|
|
917
|
-
`)
|
|
918
|
-
.action(async (scenario, opts) => {
|
|
919
|
-
try {
|
|
920
|
-
await verificationRunCommand(scenario, opts);
|
|
921
|
-
}
|
|
922
|
-
catch (err) {
|
|
923
|
-
console.error(chalk.red('run failed:'), err instanceof Error ? err.message : err);
|
|
924
|
-
process.exit(1);
|
|
925
|
-
}
|
|
926
|
-
});
|
|
927
|
-
verification
|
|
928
|
-
.command('collect-artifacts')
|
|
929
|
-
.description('Rebuild an evidence manifest from artifacts already on disk (status: inconclusive)')
|
|
930
|
-
.option('--scenario <name>', 'Scenario name to record in the manifest')
|
|
931
|
-
.option('--json', 'Print the manifest as JSON')
|
|
932
|
-
.action(async (opts) => {
|
|
933
|
-
try {
|
|
934
|
-
await verificationCollectCommand(opts);
|
|
935
|
-
}
|
|
936
|
-
catch (err) {
|
|
937
|
-
console.error(chalk.red('collect-artifacts failed:'), err instanceof Error ? err.message : err);
|
|
938
|
-
process.exit(1);
|
|
939
|
-
}
|
|
940
|
-
});
|
|
941
|
-
verification
|
|
942
|
-
.command('check-safety')
|
|
943
|
-
.description('Verify the verification setup is safe (policy, prod-disabled proof, dev-route gating, secret scan)')
|
|
944
|
-
.option('--json', 'Machine-readable report')
|
|
945
|
-
.action(async (opts) => {
|
|
946
|
-
try {
|
|
947
|
-
await verificationCheckSafetyCommand(opts);
|
|
948
|
-
}
|
|
949
|
-
catch (err) {
|
|
950
|
-
console.error(chalk.red('check-safety failed:'), err instanceof Error ? err.message : err);
|
|
951
|
-
process.exit(1);
|
|
952
|
-
}
|
|
953
|
-
});
|
|
954
885
|
// ─── mcp ─────────────────────────────────────────────────────────────────────
|
|
955
886
|
program
|
|
956
887
|
.command('mcp')
|
package/dist/schema.d.ts
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
* uses the same envelope as the `--json` output, byte-for-byte.
|
|
11
11
|
*/
|
|
12
12
|
export declare const SCHEMA_VERSIONS: {
|
|
13
|
-
readonly triage: "
|
|
13
|
+
readonly triage: "2.0.0";
|
|
14
14
|
readonly setup: "1.0.0";
|
|
15
|
-
readonly pr: "
|
|
15
|
+
readonly pr: "2.0.0";
|
|
16
|
+
readonly inbox: "1.0.0";
|
|
17
|
+
readonly ask: "1.0.0";
|
|
18
|
+
readonly traces: "1.0.0";
|
|
16
19
|
};
|
|
17
20
|
export type SchemaName = keyof typeof SCHEMA_VERSIONS;
|
|
18
21
|
/** Wrap a payload with the `schema_version` envelope. */
|
package/dist/schema.js
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
* uses the same envelope as the `--json` output, byte-for-byte.
|
|
11
11
|
*/
|
|
12
12
|
export const SCHEMA_VERSIONS = {
|
|
13
|
-
triage: '
|
|
13
|
+
triage: '2.0.0',
|
|
14
14
|
setup: '1.0.0',
|
|
15
|
-
pr: '
|
|
15
|
+
pr: '2.0.0',
|
|
16
|
+
inbox: '1.0.0',
|
|
17
|
+
ask: '1.0.0',
|
|
18
|
+
traces: '1.0.0',
|
|
16
19
|
};
|
|
17
20
|
/** Wrap a payload with the `schema_version` envelope. */
|
|
18
21
|
export function withSchema(name, payload) {
|
|
@@ -44,6 +44,12 @@ export interface SynthesisDisplayIssue {
|
|
|
44
44
|
detail: string;
|
|
45
45
|
agentFixPrompt?: string;
|
|
46
46
|
source?: string;
|
|
47
|
+
changeKey?: string;
|
|
48
|
+
filePath?: string;
|
|
49
|
+
startLine?: number;
|
|
50
|
+
endLine?: number;
|
|
51
|
+
severity?: 'critical' | 'high' | 'medium' | 'low';
|
|
52
|
+
mechanicalFix?: boolean;
|
|
47
53
|
}
|
|
48
54
|
export interface VerifiedBug {
|
|
49
55
|
originalTitle: string;
|
|
@@ -104,6 +110,39 @@ export type AnalysisReadyResult = {
|
|
|
104
110
|
status: 'error';
|
|
105
111
|
message: string;
|
|
106
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
|
+
}>;
|
|
107
146
|
/**
|
|
108
147
|
* Check if analysis results are available for a PR by polling the result endpoint.
|
|
109
148
|
* Returns 'ready' if complete, 'pending' if not yet available, or 'error' for server failures.
|
|
@@ -131,6 +170,7 @@ export interface AutoFixManifest {
|
|
|
131
170
|
reason: string;
|
|
132
171
|
}>;
|
|
133
172
|
timestamp: string;
|
|
173
|
+
resultSha?: string;
|
|
134
174
|
}
|
|
135
175
|
export declare function fetchAutoFixManifest(owner: string, repo: string, prNumber: number, token?: string): Promise<AutoFixManifest | null>;
|
|
136
176
|
export declare function fetchRatingSynthesis(owner: string, repo: string, prNumber: number, token?: string): Promise<RatingSynthesis | null>;
|
|
@@ -61,9 +61,114 @@ function apiHeaders(token) {
|
|
|
61
61
|
}
|
|
62
62
|
return headers;
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
/**
|
|
65
|
+
* Trigger a fresh full analysis for a PR via the auth-worker
|
|
66
|
+
* (`/api/analysis-trigger/{owner}/{repo}/{pr}`). Requires auth — triggers
|
|
67
|
+
* start a real analysis run. Returns the request id to wait on.
|
|
68
|
+
*/
|
|
69
|
+
export async function triggerAnalysis(owner, repo, prNumber, token, headSha) {
|
|
70
|
+
const url = `${ANALYSIS_API_BASE}/api/analysis-trigger/${owner}/${repo}/${prNumber}`;
|
|
71
|
+
let response;
|
|
72
|
+
try {
|
|
73
|
+
// Force semantics ride on a server-set header at the trigger route.
|
|
74
|
+
// commitSha matters for dedup: without it, ingestion matches this
|
|
75
|
+
// request onto any in-flight run for an OLDER head and the CLI would
|
|
76
|
+
// wait on (and render) stale results.
|
|
77
|
+
response = await fetch(url, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
headers: { ...apiHeaders(token), 'Content-Type': 'application/json' },
|
|
80
|
+
body: JSON.stringify(headSha ? { commitSha: headSha } : {}),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
return { status: 'error', message: err.message };
|
|
85
|
+
}
|
|
86
|
+
let data = {};
|
|
87
|
+
let parseFailed = false;
|
|
88
|
+
try {
|
|
89
|
+
data = await response.json();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Recorded, not swallowed: for non-OK responses the status-code handling
|
|
93
|
+
// below surfaces the failure; a 2xx with an unparseable body is its own
|
|
94
|
+
// explicit error (never "empty data") so callers can't mistake a
|
|
95
|
+
// malformed response for a normal one.
|
|
96
|
+
parseFailed = true;
|
|
97
|
+
}
|
|
98
|
+
if (parseFailed && response.ok) {
|
|
99
|
+
return { status: 'error', message: `Trigger endpoint returned invalid JSON (HTTP ${response.status})` };
|
|
100
|
+
}
|
|
101
|
+
if (response.status === 429) {
|
|
102
|
+
return {
|
|
103
|
+
status: 'rate_limited',
|
|
104
|
+
message: data.message || 'Too many analysis requests. Please wait before trying again.',
|
|
105
|
+
retryAfterSeconds: data.retryAfter,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (response.status === 401 || response.status === 403) {
|
|
109
|
+
return {
|
|
110
|
+
status: 'denied',
|
|
111
|
+
message: data.message || data.error || `HTTP ${response.status}`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (!response.ok) {
|
|
115
|
+
return { status: 'error', message: data.message || data.error || `HTTP ${response.status}` };
|
|
116
|
+
}
|
|
117
|
+
if (!data.request_id) {
|
|
118
|
+
return { status: 'error', message: 'Trigger response did not include a request id' };
|
|
119
|
+
}
|
|
120
|
+
if (data.status === 'already_processing') {
|
|
121
|
+
return { status: 'already_processing', requestId: data.request_id };
|
|
122
|
+
}
|
|
123
|
+
return { status: 'started', requestId: data.request_id };
|
|
124
|
+
}
|
|
125
|
+
// Terminal states of the v3 status vocabulary (map_status_for_frontend in
|
|
126
|
+
// haystack-v3-request-status). Everything else — QUEUED, PROCESSING,
|
|
127
|
+
// CLONING, PREPARING, ANALYZING, not_found (row lag), and any state a newer
|
|
128
|
+
// backend introduces — reads as pending: misclassifying a healthy
|
|
129
|
+
// in-progress state as an error would abort the wait.
|
|
130
|
+
const TERMINAL_STATUSES = {
|
|
131
|
+
COMPLETED: 'COMPLETED',
|
|
132
|
+
FAILED: 'FAILED',
|
|
133
|
+
TIMED_OUT: 'FAILED',
|
|
134
|
+
SUPERSEDED: 'SUPERSEDED',
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Poll the status of a triggered run by request id. The PR-level result
|
|
138
|
+
* endpoint can't distinguish a fresh re-run on the same head from the
|
|
139
|
+
* previous completed analysis — the request id can.
|
|
140
|
+
*/
|
|
141
|
+
export async function fetchTriggerStatus(requestId, token) {
|
|
142
|
+
const url = `${ANALYSIS_API_BASE}/api/analysis-trigger/status/${requestId}`;
|
|
143
|
+
try {
|
|
144
|
+
const response = await fetch(url, { headers: apiHeaders(token) });
|
|
145
|
+
// The status Lambda returns not_found as a 404 WITH a JSON body — parse
|
|
146
|
+
// before rejecting on status code so "not yet written" is distinguishable
|
|
147
|
+
// from a real transport failure.
|
|
148
|
+
let data = {};
|
|
149
|
+
try {
|
|
150
|
+
data = await response.json();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return { status: 'error', message: `HTTP ${response.status}` };
|
|
154
|
+
}
|
|
155
|
+
const status = typeof data.status === 'string' ? data.status : '';
|
|
156
|
+
const terminal = TERMINAL_STATUSES[status];
|
|
157
|
+
if (terminal) {
|
|
158
|
+
return { status: terminal, message: data.message };
|
|
159
|
+
}
|
|
160
|
+
// Only successful responses (plus the documented 404 not_found row-lag
|
|
161
|
+
// case) read as pending — a 5xx carrying {"status":"error"} must count
|
|
162
|
+
// toward the error budget, not spin the wait for the full timeout.
|
|
163
|
+
if (response.ok || (response.status === 404 && status === 'not_found')) {
|
|
164
|
+
return { status: 'pending', message: data.message };
|
|
165
|
+
}
|
|
166
|
+
return { status: 'error', message: data.message || `HTTP ${response.status}` };
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
return { status: 'error', message: err.message };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
67
172
|
/**
|
|
68
173
|
* Check if analysis results are available for a PR by polling the result endpoint.
|
|
69
174
|
* Returns 'ready' if complete, 'pending' if not yet available, or 'error' for server failures.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface UserInstallation {
|
|
2
|
+
id: number;
|
|
3
|
+
account?: {
|
|
4
|
+
login?: string;
|
|
5
|
+
} | null;
|
|
6
|
+
repositories?: Array<{
|
|
7
|
+
full_name: string;
|
|
8
|
+
pushed_at?: string | null;
|
|
9
|
+
}>;
|
|
10
|
+
}
|
|
11
|
+
export interface CliIdentity {
|
|
12
|
+
login: string;
|
|
13
|
+
id: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function haystackJson<T>(path: string, token: string, init?: RequestInit): Promise<T>;
|
|
16
|
+
export declare function fetchCliIdentity(token: string): Promise<CliIdentity>;
|
|
17
|
+
export declare function fetchHaystackInstallations(token: string): Promise<UserInstallation[]>;
|
|
18
|
+
export declare function fetchGitHubJson<T>(path: string, token: string, init?: RequestInit): Promise<T>;
|
|
19
|
+
export declare function fetchAnalysisArtifact<T>(owner: string, repo: string, prNumber: number, file: string, token: string): Promise<T | null>;
|
|
20
|
+
export declare function postAskHaystack<T>(owner: string, repo: string, prNumber: number, question: string, sessionId: string | undefined, token: string): Promise<T>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const HAYSTACK_API_BASE = process.env.HAYSTACK_API_BASE ?? 'https://haystackeditor.com';
|
|
2
|
+
function headers(token, extra) {
|
|
3
|
+
const result = new Headers(extra);
|
|
4
|
+
result.set('Authorization', `Bearer ${token}`);
|
|
5
|
+
result.set('Accept', 'application/json');
|
|
6
|
+
result.set('User-Agent', 'Haystack-CLI');
|
|
7
|
+
return result;
|
|
8
|
+
}
|
|
9
|
+
async function errorMessage(response) {
|
|
10
|
+
const text = await response.text().catch(() => '');
|
|
11
|
+
if (!text)
|
|
12
|
+
return `HTTP ${response.status}`;
|
|
13
|
+
try {
|
|
14
|
+
const parsed = JSON.parse(text);
|
|
15
|
+
return parsed.message ?? parsed.error ?? text;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function haystackJson(path, token, init = {}) {
|
|
22
|
+
const response = await fetch(`${HAYSTACK_API_BASE}${path}`, {
|
|
23
|
+
...init,
|
|
24
|
+
headers: headers(token, init.headers),
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
throw new Error(`${response.status} ${await errorMessage(response)}`);
|
|
28
|
+
}
|
|
29
|
+
return response.json();
|
|
30
|
+
}
|
|
31
|
+
export async function fetchCliIdentity(token) {
|
|
32
|
+
return haystackJson('/api/cli/me', token);
|
|
33
|
+
}
|
|
34
|
+
export async function fetchHaystackInstallations(token) {
|
|
35
|
+
const data = await haystackJson('/api/github/user-installations', token);
|
|
36
|
+
return data.installations ?? [];
|
|
37
|
+
}
|
|
38
|
+
export async function fetchGitHubJson(path, token, init = {}) {
|
|
39
|
+
return haystackJson(`/api/github${path}`, token, init);
|
|
40
|
+
}
|
|
41
|
+
export async function fetchAnalysisArtifact(owner, repo, prNumber, file, token) {
|
|
42
|
+
const ref = encodeURIComponent(`${owner}/${repo}#${prNumber}`);
|
|
43
|
+
const response = await fetch(`${HAYSTACK_API_BASE}/api/analysis/${ref}/${file}`, {
|
|
44
|
+
headers: headers(token, { 'Cache-Control': 'no-cache' }),
|
|
45
|
+
});
|
|
46
|
+
if (response.status === 404)
|
|
47
|
+
return null;
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
throw new Error(`${response.status} ${await errorMessage(response)}`);
|
|
50
|
+
}
|
|
51
|
+
const data = await response.json();
|
|
52
|
+
if (!data || typeof data !== 'object' || !('download_url' in data) || !data.download_url) {
|
|
53
|
+
return data;
|
|
54
|
+
}
|
|
55
|
+
const download = await fetch(data.download_url, { headers: { 'User-Agent': 'Haystack-CLI' } });
|
|
56
|
+
if (!download.ok)
|
|
57
|
+
throw new Error(`Artifact download failed: HTTP ${download.status}`);
|
|
58
|
+
return download.json();
|
|
59
|
+
}
|
|
60
|
+
export async function postAskHaystack(owner, repo, prNumber, question, sessionId, token) {
|
|
61
|
+
return haystackJson(`/api/review-chat/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/${prNumber}/ask`, token, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: { 'Content-Type': 'application/json' },
|
|
64
|
+
body: JSON.stringify({ question, session_id: sessionId }),
|
|
65
|
+
});
|
|
66
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haystackeditor/cli",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.19",
|
|
4
4
|
"description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"haystack": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"build": "
|
|
10
|
+
"build": "rm -rf dist && tsc && cp -r src/assets dist/assets",
|
|
11
11
|
"dev": "tsc --watch",
|
|
12
12
|
"start": "node dist/index.js",
|
|
13
13
|
"check:schemas": "node scripts/check-schemas.mjs",
|
|
14
14
|
"release": "node scripts/release.mjs",
|
|
15
15
|
"prepublishOnly": "node scripts/no-direct-publish.mjs",
|
|
16
|
-
"test": "vitest run"
|
|
16
|
+
"test": "pnpm build && vitest run && node --test tests/*.test.mjs"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"haystack",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"fast-glob": "3.3.3",
|
|
42
42
|
"glob": "10.5.0",
|
|
43
43
|
"inquirer": "9.3.8",
|
|
44
|
-
"ws": "8.
|
|
44
|
+
"ws": "8.21.0",
|
|
45
45
|
"yaml": "2.8.2",
|
|
46
46
|
"zod": "3.25.76"
|
|
47
47
|
},
|