@haystackeditor/cli 0.15.16 → 0.15.18

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 (42) hide show
  1. package/README.md +45 -6
  2. package/dist/commands/ask.d.ts +14 -0
  3. package/dist/commands/ask.js +20 -0
  4. package/dist/commands/inbox.d.ts +65 -0
  5. package/dist/commands/inbox.js +137 -0
  6. package/dist/commands/mcp.js +88 -134
  7. package/dist/commands/pr.d.ts +37 -1
  8. package/dist/commands/pr.js +95 -77
  9. package/dist/commands/schema-cmd.js +1 -1
  10. package/dist/commands/traces.d.ts +48 -0
  11. package/dist/commands/traces.js +92 -0
  12. package/dist/commands/triage.d.ts +12 -0
  13. package/dist/commands/triage.js +90 -58
  14. package/dist/commands/webhooks.js +3 -6
  15. package/dist/index.js +50 -148
  16. package/dist/schema.d.ts +5 -2
  17. package/dist/schema.js +5 -2
  18. package/dist/triage/prompts.js +4 -0
  19. package/dist/utils/analysis-api.d.ts +7 -0
  20. package/dist/utils/haystack-api.d.ts +20 -0
  21. package/dist/utils/haystack-api.js +66 -0
  22. package/package.json +4 -4
  23. package/schemas/ask.v1.json +40 -0
  24. package/schemas/inbox.v1.json +27 -0
  25. package/schemas/pr.v2.json +54 -0
  26. package/schemas/traces.v1.json +53 -0
  27. package/schemas/triage.v2.json +64 -0
  28. package/dist/assets/skills/install-verification.md +0 -67
  29. package/dist/commands/verification.d.ts +0 -21
  30. package/dist/commands/verification.js +0 -291
  31. package/dist/verification/contract.d.ts +0 -240
  32. package/dist/verification/contract.js +0 -115
  33. package/dist/verification/init.d.ts +0 -10
  34. package/dist/verification/init.js +0 -243
  35. package/dist/verification/manifest.d.ts +0 -49
  36. package/dist/verification/manifest.js +0 -186
  37. package/dist/verification/runner.d.ts +0 -20
  38. package/dist/verification/runner.js +0 -102
  39. package/dist/verification/safety.d.ts +0 -10
  40. package/dist/verification/safety.js +0 -252
  41. package/dist/verification/validate.d.ts +0 -12
  42. package/dist/verification/validate.js +0 -219
package/README.md CHANGED
@@ -69,7 +69,6 @@ Create a PR from current changes. Runs pre-PR triage (code review, rules validat
69
69
 
70
70
  ```bash
71
71
  haystack submit # Triage -> create PR -> wait for analysis
72
- haystack submit --auto-fix # Discouraged alpha auto-fix for straightforward mechanical issues
73
72
  haystack submit --title "Fix auth" # Custom PR title
74
73
  haystack submit --draft # Create as draft PR
75
74
  haystack submit --force # Skip triage checks
@@ -85,8 +84,6 @@ haystack submit --review octocat # Request review from a specific teammate
85
84
 
86
85
  When `--review` is used without a username, the PR is labeled `haystack:needs-review` and appears in your team's assignment queue. When a username is provided, that person is also requested as a reviewer on GitHub.
87
86
 
88
- The `--auto-fix` flag is an alpha feature and is discouraged by default. It opts into a sandbox agent that attempts straightforward mechanical fixes before surfacing issues in the Feed. For most PRs, prefer plain `haystack submit`.
89
-
90
87
  ### `haystack triage`
91
88
 
92
89
  View Haystack analysis results for any PR. Shows the same data as the Haystack web feed: rating, verdict, structured findings with details, verified bugs, human review reasons, and agent fix prompts.
@@ -102,9 +99,51 @@ haystack triage --hook # Minimal one-liner (for session hoo
102
99
  haystack triage --clear # Clear pending submit state
103
100
  ```
104
101
 
105
- When called without a PR identifier, checks the last PR submitted via `haystack submit`. The `--hook` flag produces a single-line summary with the Haystack rating and auto-fixer status, designed for session-start hooks.
102
+ When called without a PR identifier, checks the last PR submitted via `haystack submit`. The `--hook` flag produces a single-line summary with the Haystack rating, designed for session-start hooks.
103
+
104
+ The `--json` output includes every finding and its customer-facing `suggested_fix` when available.
105
+
106
+ ### `haystack inbox list`
107
+
108
+ List PRs in your Haystack inbox with the reason each PR is present and the next action:
109
+
110
+ ```bash
111
+ haystack inbox list
112
+ haystack inbox list --json
113
+ ```
114
+
115
+ ### `haystack pr get`
116
+
117
+ Get the useful state of one PR: triage, minimal merge blockers, and customer-trace availability:
118
+
119
+ ```bash
120
+ haystack pr get 42
121
+ haystack pr get owner/repo#42 --json
122
+ ```
123
+
124
+ ### `haystack ask`
125
+
126
+ Ask Haystack Chat about a PR. Machine output contains the answer and every customer-facing source Chat consulted:
127
+
128
+ ```bash
129
+ haystack ask 42 "Why is this finding legitimate?"
130
+ haystack ask owner/repo#42 "Show the implementation" --json
131
+ haystack ask owner/repo#42 "What about its callers?" --session <session-id> --json
132
+ ```
133
+
134
+ Chat may search the complete authorized repository when the changed files are insufficient. Search results and fetched source are included in `evidence`.
135
+
136
+ ### `haystack traces`
137
+
138
+ Inspect retained customer-owned Entire checkpoints and transcript chunks:
139
+
140
+ ```bash
141
+ haystack traces list owner/repo#42 --json
142
+ haystack traces get owner/repo#42 <checkpoint-id> --json
143
+ haystack traces get owner/repo#42 <checkpoint-id> --cursor 20 --limit 20 --json
144
+ ```
106
145
 
107
- The `--json` output includes `agentFixPrompt` fields -- ready-to-paste instructions for coding agents to fix each finding.
146
+ These commands expose customer coding-session data, not Haystack's internal analysis-agent execution.
108
147
 
109
148
  ### `haystack dismiss`
110
149
 
@@ -135,7 +174,7 @@ haystack mark-reviewed acme/widgets#99 # Mark for specific repo
135
174
 
136
175
  ### `haystack pr-status`
137
176
 
138
- Show what bucket a PR is in within the Haystack pipeline (analyzing, auto-fixing, good-to-merge, issues, needs-assignment, etc.):
177
+ Show what bucket a PR is in within the Haystack pipeline (analyzing, good-to-merge, issues, needs-assignment, etc.):
139
178
 
140
179
  ```bash
141
180
  haystack pr-status 42 # Current repo, PR #42
@@ -0,0 +1,14 @@
1
+ export interface AskOptions {
2
+ json?: boolean;
3
+ session?: string;
4
+ }
5
+ export interface AskResponse {
6
+ schema_version: string;
7
+ ref: string;
8
+ head_sha: string;
9
+ answer: string;
10
+ evidence: Array<Record<string, unknown>>;
11
+ session_id: string;
12
+ }
13
+ export declare function fetchAskPayload(ref: string, question: string, options: Pick<AskOptions, 'session'>): Promise<AskResponse>;
14
+ export declare function askHaystackCommand(ref: string, question: string, options: AskOptions): Promise<void>;
@@ -0,0 +1,20 @@
1
+ import chalk from 'chalk';
2
+ import { loadToken } from '../utils/auth.js';
3
+ import { postAskHaystack } from '../utils/haystack-api.js';
4
+ import { parsePrRef } from './pr.js';
5
+ export async function fetchAskPayload(ref, question, options) {
6
+ const pr = parsePrRef(ref);
7
+ const token = await loadToken({ owner: pr.owner, repo: pr.repo });
8
+ if (!token)
9
+ throw new Error('Not authenticated. Run `haystack login` first.');
10
+ return postAskHaystack(pr.owner, pr.repo, pr.prNumber, question, options.session, token);
11
+ }
12
+ export async function askHaystackCommand(ref, question, options) {
13
+ const response = await fetchAskPayload(ref, question, options);
14
+ if (options.json) {
15
+ console.log(JSON.stringify(response, null, 2));
16
+ return;
17
+ }
18
+ console.log(`\n${response.answer}\n`);
19
+ console.log(chalk.dim(`${response.evidence.length} source${response.evidence.length === 1 ? '' : 's'} consulted · session ${response.session_id}\n`));
20
+ }
@@ -0,0 +1,65 @@
1
+ interface GitHubPull {
2
+ number: number;
3
+ title: string;
4
+ html_url: string;
5
+ draft?: boolean;
6
+ user: {
7
+ id: number;
8
+ login: string;
9
+ };
10
+ assignees?: Array<{
11
+ id: number;
12
+ login: string;
13
+ }>;
14
+ requested_reviewers?: Array<{
15
+ id: number;
16
+ login: string;
17
+ }>;
18
+ head: {
19
+ sha: string;
20
+ ref: string;
21
+ };
22
+ base: {
23
+ ref: string;
24
+ };
25
+ }
26
+ interface InboxCandidate extends GitHubPull {
27
+ owner: string;
28
+ repo: string;
29
+ }
30
+ interface AnalysisSummary {
31
+ analysisStatus?: 'pending' | 'in_queue' | 'ready' | 'error';
32
+ analysisVerdict?: 'clean' | 'has-issues' | 'needs-review';
33
+ haystackRating?: number;
34
+ needsHumanReview?: boolean;
35
+ findingsCount?: number;
36
+ merge?: {
37
+ status: string;
38
+ reason: string;
39
+ blocking_checks: string[];
40
+ action: string | null;
41
+ };
42
+ }
43
+ export interface InboxListOptions {
44
+ json?: boolean;
45
+ }
46
+ export declare function isRelevantToUser(pr: GitHubPull, userId: number): boolean;
47
+ export declare function classify(candidate: InboxCandidate, analysis: AnalysisSummary | undefined): {
48
+ bucket: string;
49
+ reason: string;
50
+ action: string;
51
+ };
52
+ export declare function fetchInboxPayload(token: string): Promise<{
53
+ pull_requests: {
54
+ bucket: string;
55
+ reason: string;
56
+ action: string;
57
+ ref: string;
58
+ title: string;
59
+ head_sha: string;
60
+ }[];
61
+ } & {
62
+ schema_version: string;
63
+ }>;
64
+ export declare function inboxListCommand(options: InboxListOptions): Promise<void>;
65
+ export {};
@@ -0,0 +1,137 @@
1
+ import chalk from 'chalk';
2
+ import { loadToken } from '../utils/auth.js';
3
+ import { fetchCliIdentity, fetchGitHubJson, fetchHaystackInstallations, haystackJson, } from '../utils/haystack-api.js';
4
+ import { withSchema } from '../schema.js';
5
+ async function fetchOpenPulls(owner, repo, token) {
6
+ const pulls = [];
7
+ for (let page = 1;; page++) {
8
+ const batch = await fetchGitHubJson(`/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls?state=open&per_page=100&page=${page}`, token);
9
+ pulls.push(...batch);
10
+ if (batch.length < 100)
11
+ return pulls;
12
+ }
13
+ }
14
+ export function isRelevantToUser(pr, userId) {
15
+ return pr.user.id === userId
16
+ || (pr.assignees ?? []).some((user) => user.id === userId)
17
+ || (pr.requested_reviewers ?? []).some((user) => user.id === userId);
18
+ }
19
+ async function mapConcurrent(values, concurrency, fn) {
20
+ const results = new Array(values.length);
21
+ let next = 0;
22
+ await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, async () => {
23
+ while (next < values.length) {
24
+ const index = next++;
25
+ results[index] = await fn(values[index]);
26
+ }
27
+ }));
28
+ return results;
29
+ }
30
+ export function classify(candidate, analysis) {
31
+ const findingCount = analysis?.findingsCount ?? 0;
32
+ const failedChecks = analysis?.merge?.blocking_checks ?? [];
33
+ if (analysis?.analysisStatus === 'error') {
34
+ return {
35
+ bucket: 'needs_shepherding',
36
+ reason: 'Haystack analysis failed.',
37
+ action: 'Retry the analysis or inspect the failure.',
38
+ };
39
+ }
40
+ if (!analysis || analysis.analysisStatus === 'pending' || analysis.analysisStatus === 'in_queue') {
41
+ return {
42
+ bucket: 'analyzing',
43
+ reason: 'Haystack is still analyzing this PR.',
44
+ action: 'Wait for analysis to complete.',
45
+ };
46
+ }
47
+ if (analysis.analysisVerdict === 'has-issues' || (analysis.haystackRating ?? 5) < 4) {
48
+ return {
49
+ bucket: 'needs_shepherding',
50
+ reason: findingCount > 0
51
+ ? `Haystack found ${findingCount} author-actionable finding${findingCount === 1 ? '' : 's'}.`
52
+ : 'Haystack found author-actionable issues.',
53
+ action: 'Read the triage findings and resolve the issues.',
54
+ };
55
+ }
56
+ if (failedChecks.length > 0 || analysis.merge?.status === 'blocked') {
57
+ return {
58
+ bucket: 'needs_shepherding',
59
+ reason: failedChecks.length > 0
60
+ ? `${failedChecks.length} required check${failedChecks.length === 1 ? ' is' : 's are'} failing: ${failedChecks.join(', ')}.`
61
+ : (analysis.merge?.reason ?? 'The PR is blocked from merging.'),
62
+ action: analysis.merge?.action ?? 'Resolve the merge blockers.',
63
+ };
64
+ }
65
+ if (analysis.analysisVerdict === 'needs-review' || analysis.needsHumanReview) {
66
+ return {
67
+ bucket: 'needs_review',
68
+ reason: 'Analysis is clean, but review policy requires a human reviewer.',
69
+ action: 'Complete or request the required review.',
70
+ };
71
+ }
72
+ return {
73
+ bucket: 'good_to_merge',
74
+ reason: 'Haystack found no blocking issues.',
75
+ action: 'No user action is required.',
76
+ };
77
+ }
78
+ export async function fetchInboxPayload(token) {
79
+ const [identity, installations] = await Promise.all([
80
+ fetchCliIdentity(token),
81
+ fetchHaystackInstallations(token),
82
+ ]);
83
+ const repos = [...new Set(installations.flatMap((i) => i.repositories ?? []).map((r) => r.full_name))];
84
+ const batches = await mapConcurrent(repos, 8, async (fullName) => {
85
+ const [owner, repo] = fullName.split('/');
86
+ if (!owner || !repo)
87
+ throw new Error(`Invalid repository returned by Haystack: ${fullName}`);
88
+ const pulls = await fetchOpenPulls(owner, repo, token);
89
+ return pulls
90
+ .filter((pr) => isRelevantToUser(pr, identity.id))
91
+ .map((pr) => ({ ...pr, owner, repo }));
92
+ });
93
+ const candidates = batches.flat();
94
+ const refs = candidates.map((pr) => `${pr.owner}/${pr.repo}#${pr.number}`);
95
+ const analysis = refs.length === 0
96
+ ? {}
97
+ : await haystackJson('/api/bulk-analysis-summaries', token, {
98
+ method: 'POST',
99
+ headers: { 'Content-Type': 'application/json' },
100
+ body: JSON.stringify({
101
+ prIdentifiers: refs,
102
+ prBaseBranches: Object.fromEntries(candidates.map((pr) => [`${pr.owner}/${pr.repo}#${pr.number}`, pr.base.ref])),
103
+ prHeadShas: Object.fromEntries(candidates.map((pr) => [`${pr.owner}/${pr.repo}#${pr.number}`, pr.head.sha])),
104
+ }),
105
+ });
106
+ return withSchema('inbox', {
107
+ pull_requests: candidates.map((pr) => {
108
+ const ref = `${pr.owner}/${pr.repo}#${pr.number}`;
109
+ return {
110
+ ref,
111
+ title: pr.title,
112
+ head_sha: pr.head.sha,
113
+ ...classify(pr, analysis[ref]),
114
+ };
115
+ }),
116
+ });
117
+ }
118
+ export async function inboxListCommand(options) {
119
+ const token = await loadToken();
120
+ if (!token)
121
+ throw new Error('Not authenticated. Run `haystack login` first.');
122
+ const payload = await fetchInboxPayload(token);
123
+ if (options.json) {
124
+ console.log(JSON.stringify(payload, null, 2));
125
+ return;
126
+ }
127
+ if (payload.pull_requests.length === 0) {
128
+ console.log(chalk.dim('\nNo PRs need your attention.\n'));
129
+ return;
130
+ }
131
+ console.log(chalk.bold('\nHaystack inbox\n'));
132
+ for (const pr of payload.pull_requests) {
133
+ console.log(`${chalk.cyan(pr.ref)} ${chalk.bold(pr.title)}`);
134
+ console.log(` ${pr.reason}`);
135
+ console.log(` ${chalk.dim('Next:')} ${pr.action}\n`);
136
+ }
137
+ }
@@ -1,71 +1,74 @@
1
- /**
2
- * `haystack mcp` — Model Context Protocol server.
3
- *
4
- * Exposes Haystack's read surface to Claude Code, Cursor, Codex, etc. as
5
- * stdio MCP tools. Every tool wraps the same library function that powers
6
- * the corresponding `--json` CLI command, so an MCP client and a webhook
7
- * receiver see byte-identical payloads.
8
- *
9
- * Tools:
10
- * triage_pr(ref) same shape as `haystack triage <ref> --json`
11
- * pr_state(ref) PR state in the Haystack pipeline (schemas/pr.v1.json)
12
- * schema(name) prints the JSON Schema for a noun (debugging)
13
- *
14
- * Auth: inherited from the local CLI (loadToken). MCP clients run as the
15
- * operator's user, so they get whatever credentials are on disk — same model
16
- * as `git`, `gh`, etc.
17
- */
18
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
19
2
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
20
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
3
+ import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
21
4
  import { readFileSync } from 'node:fs';
22
5
  import { fileURLToPath } from 'node:url';
23
6
  import { dirname, join } from 'node:path';
24
- import { parsePrRef, prReadCommand } from './pr.js';
25
- import { fetchRatingSynthesis, fetchAutoFixManifest } from '../utils/analysis-api.js';
7
+ import { fetchInboxPayload } from './inbox.js';
8
+ import { fetchPrStatePayload } from './pr.js';
9
+ import { fetchTriagePayload } from './triage.js';
10
+ import { fetchAskPayload } from './ask.js';
11
+ import { fetchTracesGetPayload, fetchTracesListPayload } from './traces.js';
26
12
  import { loadToken } from '../utils/auth.js';
27
- import { SCHEMA_VERSIONS, withSchema } from '../schema.js';
13
+ import { SCHEMA_VERSIONS } from '../schema.js';
14
+ const refInput = {
15
+ type: 'string',
16
+ description: 'PR reference: bare number, owner/repo#number, or GitHub URL.',
17
+ };
28
18
  const TOOLS = [
29
19
  {
30
- name: 'triage_pr',
31
- description: 'Fetch the latest triage results for a pull request. Returns rating, findings, ' +
32
- 'and which findings the auto-fixer is already handling. Identical shape to ' +
33
- '`haystack triage <ref> --json`.',
20
+ name: 'inbox_list',
21
+ description: 'List PRs in the user\'s Haystack inbox with the reason and next action.',
22
+ inputSchema: { type: 'object', properties: {} },
23
+ },
24
+ {
25
+ name: 'pr_get',
26
+ description: 'Get useful state for one PR: triage, minimal merge blockers, trace availability, and conditional fixer outcome.',
27
+ inputSchema: { type: 'object', properties: { ref: refInput }, required: ['ref'] },
28
+ },
29
+ {
30
+ name: 'triage_get',
31
+ description: 'Get the complete Haystack verdict and every customer-facing finding for one PR.',
32
+ inputSchema: { type: 'object', properties: { ref: refInput }, required: ['ref'] },
33
+ },
34
+ {
35
+ name: 'ask_haystack',
36
+ description: 'Ask Haystack about a PR. Returns the answer and every customer-facing source consulted.',
34
37
  inputSchema: {
35
38
  type: 'object',
36
39
  properties: {
37
- ref: {
38
- type: 'string',
39
- description: 'PR reference: bare number, owner/repo#number, or GitHub URL.',
40
- },
40
+ ref: refInput,
41
+ question: { type: 'string', description: 'Question about the pull request.' },
42
+ session_id: { type: 'string', description: 'Optional prior Ask session to continue.' },
41
43
  },
42
- required: ['ref'],
44
+ required: ['ref', 'question'],
43
45
  },
44
46
  },
45
47
  {
46
- name: 'pr_state',
47
- description: 'Show the Haystack state of a single PR: bucket, last triage rating, merge state.',
48
+ name: 'traces_list',
49
+ description: 'List retained customer-owned Entire checkpoints for one PR.',
50
+ inputSchema: { type: 'object', properties: { ref: refInput }, required: ['ref'] },
51
+ },
52
+ {
53
+ name: 'traces_get',
54
+ description: 'Get a page of retained customer-owned transcript chunks for one checkpoint.',
48
55
  inputSchema: {
49
56
  type: 'object',
50
57
  properties: {
51
- ref: { type: 'string', description: 'PR reference.' },
58
+ ref: refInput,
59
+ checkpoint_id: { type: 'string' },
60
+ cursor: { type: 'string' },
61
+ limit: { type: 'integer', minimum: 1, maximum: 100 },
52
62
  },
53
- required: ['ref'],
63
+ required: ['ref', 'checkpoint_id'],
54
64
  },
55
65
  },
56
66
  {
57
67
  name: 'schema',
58
- description: 'Return the JSON Schema for a Haystack --json command output. ' +
59
- 'Use this to discover the exact shape of triage / setup / pr envelopes.',
68
+ description: 'Return the committed JSON Schema for a Haystack machine response.',
60
69
  inputSchema: {
61
70
  type: 'object',
62
- properties: {
63
- name: {
64
- type: 'string',
65
- enum: Object.keys(SCHEMA_VERSIONS),
66
- description: 'Schema noun.',
67
- },
68
- },
71
+ properties: { name: { type: 'string', enum: Object.keys(SCHEMA_VERSIONS) } },
69
72
  required: ['name'],
70
73
  },
71
74
  },
@@ -73,113 +76,64 @@ const TOOLS = [
73
76
  async function readSchema(name) {
74
77
  const major = SCHEMA_VERSIONS[name].split('.')[0];
75
78
  const here = dirname(fileURLToPath(import.meta.url));
76
- // dist/commands/mcp.js → ../../schemas/<name>.v<major>.json
77
- const p = join(here, '..', '..', 'schemas', `${name}.v${major}.json`);
78
- return JSON.parse(readFileSync(p, 'utf8'));
79
- }
80
- /** Capture process.stdout written by CLI commands and return it. MCP tool
81
- * callbacks must return their result, not write it. The simplest way to
82
- * reuse the existing --json printers without refactoring them is to
83
- * redirect stdout for the duration of the call. */
84
- async function captureStdout(fn) {
85
- const origWrite = process.stdout.write.bind(process.stdout);
86
- let captured = '';
87
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
- process.stdout.write = (chunk, encoding, cb) => {
89
- captured += typeof chunk === 'string' ? chunk : new TextDecoder().decode(chunk);
90
- if (typeof encoding === 'function')
91
- encoding();
92
- else if (cb)
93
- cb();
94
- return true;
95
- };
96
- try {
97
- await fn();
98
- }
99
- finally {
100
- process.stdout.write = origWrite;
101
- }
102
- return captured;
103
- }
104
- function jsonFromCapture(captured) {
105
- try {
106
- return JSON.parse(captured);
107
- }
108
- catch {
109
- return { error: 'tool produced non-JSON output', raw: captured.slice(0, 1000) };
110
- }
79
+ return JSON.parse(readFileSync(join(here, '..', '..', 'schemas', `${name}.v${major}.json`), 'utf8'));
111
80
  }
112
81
  export async function runMcpServer() {
113
- const server = new Server({
114
- name: 'haystack-cli',
115
- version: '1.0.0',
116
- }, {
117
- capabilities: { tools: {} },
118
- });
82
+ const server = new Server({ name: 'haystack-cli', version: '1.0.0' }, { capabilities: { tools: {} } });
119
83
  server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
120
84
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
121
85
  const { name, arguments: args } = request.params;
122
- const ok = (data) => ({
123
- content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
124
- });
125
- const fail = (msg) => ({
126
- isError: true,
127
- content: [{ type: 'text', text: msg }],
128
- });
86
+ const input = (args ?? {});
87
+ const ok = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
88
+ const fail = (message) => ({ isError: true, content: [{ type: 'text', text: message }] });
129
89
  try {
130
- if (name === 'triage_pr') {
131
- const ref = args?.ref;
132
- if (!ref)
90
+ if (name === 'inbox_list') {
91
+ const token = await loadToken();
92
+ if (!token)
93
+ return fail('Not authenticated. Run `haystack login` first.');
94
+ return ok(await fetchInboxPayload(token));
95
+ }
96
+ if (name === 'pr_get') {
97
+ if (typeof input.ref !== 'string')
133
98
  return fail('ref required');
134
- const pr = parsePrRef(ref);
135
- const token = (await loadToken()) ?? undefined;
136
- const [synthesis, manifest] = await Promise.all([
137
- fetchRatingSynthesis(pr.owner, pr.repo, pr.prNumber, token),
138
- fetchAutoFixManifest(pr.owner, pr.repo, pr.prNumber, token),
139
- ]);
140
- if (!synthesis)
141
- return fail(`No triage data for ${pr.owner}/${pr.repo}#${pr.prNumber}`);
142
- // Mirror triage --json envelope.
143
- return ok(withSchema('triage', {
144
- owner: pr.owner,
145
- repo: pr.repo,
146
- prNumber: pr.prNumber,
147
- rating: synthesis.haystackRating,
148
- autoFixerHandling: manifest?.fixedItems ?? [],
149
- autoFixerSkipped: manifest?.leftForReview ?? [],
150
- findings: (synthesis.synthesisDisplay ?? []).map((f) => ({
151
- category: f.category,
152
- summary: f.summary,
153
- detail: f.detail,
154
- agentFixPrompt: f.agentFixPrompt ?? null,
155
- source: f.source ?? null,
156
- })),
99
+ return ok(await fetchPrStatePayload(input.ref));
100
+ }
101
+ if (name === 'triage_get') {
102
+ if (typeof input.ref !== 'string')
103
+ return fail('ref required');
104
+ return ok(await fetchTriagePayload(input.ref));
105
+ }
106
+ if (name === 'ask_haystack') {
107
+ if (typeof input.ref !== 'string' || typeof input.question !== 'string')
108
+ return fail('ref and question required');
109
+ return ok(await fetchAskPayload(input.ref, input.question, {
110
+ session: typeof input.session_id === 'string' ? input.session_id : undefined,
157
111
  }));
158
112
  }
159
- if (name === 'pr_state') {
160
- const ref = args?.ref;
161
- if (!ref)
113
+ if (name === 'traces_list') {
114
+ if (typeof input.ref !== 'string')
162
115
  return fail('ref required');
163
- const captured = await captureStdout(async () => {
164
- await prReadCommand(ref, { json: true });
165
- });
166
- return ok(jsonFromCapture(captured));
116
+ return ok(await fetchTracesListPayload(input.ref));
117
+ }
118
+ if (name === 'traces_get') {
119
+ if (typeof input.ref !== 'string' || typeof input.checkpoint_id !== 'string')
120
+ return fail('ref and checkpoint_id required');
121
+ return ok(await fetchTracesGetPayload(input.ref, input.checkpoint_id, {
122
+ cursor: typeof input.cursor === 'string' ? input.cursor : undefined,
123
+ limit: typeof input.limit === 'number' ? String(input.limit) : undefined,
124
+ }));
167
125
  }
168
126
  if (name === 'schema') {
169
- const n = args?.name;
170
- if (!n || !(n in SCHEMA_VERSIONS)) {
127
+ const schema = input.name;
128
+ if (!(schema in SCHEMA_VERSIONS))
171
129
  return fail(`unknown schema; choose one of ${Object.keys(SCHEMA_VERSIONS).join(', ')}`);
172
- }
173
- return ok(await readSchema(n));
130
+ return ok(await readSchema(schema));
174
131
  }
175
132
  return fail(`unknown tool: ${name}`);
176
133
  }
177
- catch (err) {
178
- return fail(`tool error: ${err instanceof Error ? err.message : String(err)}`);
134
+ catch (error) {
135
+ return fail(error instanceof Error ? error.message : String(error));
179
136
  }
180
137
  });
181
- const transport = new StdioServerTransport();
182
- await server.connect(transport);
183
- // The server runs until stdin closes. Stdio MCP gives no exit signal beyond
184
- // that; we don't return.
138
+ await server.connect(new StdioServerTransport());
185
139
  }
@@ -4,7 +4,43 @@ export interface ParsedPR {
4
4
  prNumber: number;
5
5
  }
6
6
  export declare function parsePrRef(ref: string): ParsedPR;
7
+ interface GitHubPr {
8
+ number: number;
9
+ title: string;
10
+ state: 'open' | 'closed';
11
+ merged: boolean;
12
+ mergeable_state?: string;
13
+ head: {
14
+ sha: string;
15
+ ref: string;
16
+ };
17
+ base: {
18
+ ref: string;
19
+ };
20
+ }
21
+ interface MergeQueueEntry {
22
+ status?: string;
23
+ detail?: string;
24
+ failedChecks?: string[];
25
+ actionNeeded?: string;
26
+ }
27
+ export declare function mergePublicState(meta: GitHubPr, entry: MergeQueueEntry | undefined, queueAvailable?: boolean): {
28
+ status: string;
29
+ reason: string;
30
+ blocking_checks: string[];
31
+ action: string | null;
32
+ };
33
+ export declare function fetchMergePublicState(meta: GitHubPr, prNumber: number, owner: string, repo: string, token: string): Promise<{
34
+ status: string;
35
+ reason: string;
36
+ blocking_checks: string[];
37
+ action: string | null;
38
+ }>;
7
39
  export interface PrReadOptions {
8
40
  json?: boolean;
9
41
  }
10
- export declare function prReadCommand(ref: string, opts: PrReadOptions): Promise<void>;
42
+ export declare function fetchPrStatePayload(ref: string): Promise<Record<string, unknown> & {
43
+ schema_version: string;
44
+ }>;
45
+ export declare function prReadCommand(ref: string, options: PrReadOptions): Promise<void>;
46
+ export {};