@haystackeditor/cli 0.25.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -455
- package/dist/capture/app-config.js +25 -1
- package/dist/commands/capture-brief.js +41 -32
- package/dist/commands/feedback.js +66 -0
- package/dist/commands/init-telemetry.js +53 -7
- package/dist/commands/init.js +7 -2
- package/dist/commands/lockfile-pin.js +307 -0
- package/dist/commands/verify.js +68 -13
- package/dist/index.js +27 -923
- package/dist/schema.js +4 -10
- package/dist/utils/haystack-api.js +0 -36
- package/package.json +1 -5
- package/schemas/feedback.v1.json +13 -0
- package/schemas/pre-verify.v2.json +240 -0
- package/schemas/verify-raw.v1.json +1132 -0
- package/schemas/verify.v2.json +655 -0
- package/dist/assets/hooks/agent-context/detect.ts +0 -316
- package/dist/assets/hooks/agent-context/format.ts +0 -100
- package/dist/assets/hooks/agent-context/index.ts +0 -41
- package/dist/assets/hooks/agent-context/parsers/claude.ts +0 -262
- package/dist/assets/hooks/agent-context/parsers/codex.ts +0 -416
- package/dist/assets/hooks/agent-context/parsers/gemini.ts +0 -155
- package/dist/assets/hooks/agent-context/parsers/opencode.ts +0 -174
- package/dist/assets/hooks/agent-context/tsconfig.json +0 -14
- package/dist/assets/hooks/agent-context/types.ts +0 -58
- package/dist/assets/hooks/llm-rules-template.md +0 -59
- package/dist/assets/hooks/package-lock.json +0 -598
- package/dist/assets/hooks/package.json +0 -12
- package/dist/assets/hooks/scripts/commit-msg.sh +0 -5
- package/dist/assets/hooks/scripts/post-commit.sh +0 -5
- package/dist/assets/hooks/scripts/pre-commit.sh +0 -175
- package/dist/assets/hooks/scripts/pre-push.sh +0 -25
- package/dist/assets/hooks/scripts/prepare-commit-msg.sh +0 -5
- package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +0 -528
- package/dist/assets/hooks/truncation-checker/index.ts +0 -595
- package/dist/assets/hooks/truncation-checker/tsconfig.json +0 -13
- package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +0 -2051
- package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +0 -4
- package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +0 -3411
- package/dist/assets/skills/map-your-system.md +0 -143
- package/dist/assets/skills/submit.md +0 -200
- package/dist/commands/ask.js +0 -20
- package/dist/commands/cloud-verifier-behaviors.js +0 -218
- package/dist/commands/cloud-verifier-data-store-census.js +0 -539
- package/dist/commands/cloud-verifier-data-store-drift.js +0 -158
- package/dist/commands/cloud-verifier-identity-census.js +0 -4060
- package/dist/commands/cloud-verifier-materialization.js +0 -704
- package/dist/commands/cloud-verifier-pascal-selector-census.js +0 -1382
- package/dist/commands/cloud-verifier-python-manifest-selector-census.js +0 -2015
- package/dist/commands/cloud-verifier-specialized-operational-census.js +0 -11432
- package/dist/commands/cloud-verifier-universe.js +0 -10178
- package/dist/commands/config.js +0 -549
- package/dist/commands/design-verify.js +0 -311
- package/dist/commands/dismiss.js +0 -159
- package/dist/commands/hooks.js +0 -226
- package/dist/commands/inbox.js +0 -137
- package/dist/commands/mcp.js +0 -201
- package/dist/commands/policy.js +0 -371
- package/dist/commands/pr-status.js +0 -207
- package/dist/commands/pr.js +0 -105
- package/dist/commands/prepare-universe-review.js +0 -1092
- package/dist/commands/production-source-deny-policy.js +0 -100
- package/dist/commands/request-review.js +0 -74
- package/dist/commands/review.js +0 -191
- package/dist/commands/rules.js +0 -98
- package/dist/commands/scaffold-provisional-universe.js +0 -806
- package/dist/commands/setup.js +0 -1170
- package/dist/commands/skills.js +0 -447
- package/dist/commands/submit.js +0 -745
- package/dist/commands/system-map.js +0 -228
- package/dist/commands/triage.js +0 -598
- package/dist/commands/webhooks.js +0 -241
- package/dist/states.js +0 -46
- package/dist/tools/detect.js +0 -832
- package/dist/triage/astra.js +0 -202
- package/dist/triage/prompts.js +0 -188
- package/dist/triage/runner.js +0 -200
- package/dist/triage/types.js +0 -7
- package/dist/utils/action-output.js +0 -26
- package/dist/utils/analysis-api.js +0 -416
- package/dist/utils/design-verifier-api.js +0 -294
- package/dist/utils/design-verifier-history.js +0 -79
- package/dist/utils/design-verifier-result.js +0 -424
- package/dist/utils/github-api.js +0 -324
- package/dist/utils/pending-state.js +0 -86
- package/dist/utils/pr-ref.js +0 -56
- package/dist/utils/prompter.js +0 -328
- package/schemas/action.v1.json +0 -22
- package/schemas/ask.v1.json +0 -40
- package/schemas/inbox.v1.json +0 -27
- package/schemas/pr-status.v1.json +0 -61
- package/schemas/pr.v1.json +0 -97
- package/schemas/pr.v3.json +0 -45
- package/schemas/setup.v1.json +0 -75
- package/schemas/submit.v1.json +0 -90
- package/schemas/triage.v1.json +0 -103
- package/schemas/triage.v2.json +0 -64
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design-verify command — Kick off a blind design-by-execution verification
|
|
3
|
-
* run for a PR.
|
|
4
|
-
*
|
|
5
|
-
* haystack design-verify <pr>
|
|
6
|
-
*
|
|
7
|
-
* Unlike `haystack review` (finding-oriented analysis of the diff), design
|
|
8
|
-
* verification executes the change: it infers intent from the diff alone,
|
|
9
|
-
* designs pre-registered behavioral test cases, runs control-before, before,
|
|
10
|
-
* and after in isolated sandboxes, and reports evidence-backed anomaly
|
|
11
|
-
* candidates outside the registered intent.
|
|
12
|
-
*
|
|
13
|
-
* The command waits by default and returns an agent-facing behavioral result.
|
|
14
|
-
* --no-wait preserves the asynchronous trigger-only path.
|
|
15
|
-
*/
|
|
16
|
-
import chalk from 'chalk';
|
|
17
|
-
import { loadToken } from '../utils/auth.js';
|
|
18
|
-
import { parsePrRef } from '../utils/pr-ref.js';
|
|
19
|
-
import { getDesignVerifierHistory, getDesignVerifierUniverseAction, startDesignVerifierUniverseAction, triggerDesignVerifier, waitForDesignVerifier, waitForDesignVerifierUniverseAction, } from '../utils/design-verifier-api.js';
|
|
20
|
-
import { buildDesignVerifierAgentReport, designVerifierStageLabel, formatDesignVerifierAgentReport, } from '../utils/design-verifier-result.js';
|
|
21
|
-
import { formatDesignVerifierHistory } from '../utils/design-verifier-history.js';
|
|
22
|
-
function emit(json, payload, human) {
|
|
23
|
-
if (json) {
|
|
24
|
-
console.log(JSON.stringify(payload));
|
|
25
|
-
}
|
|
26
|
-
else {
|
|
27
|
-
human();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
function parseExecArgv(source) {
|
|
31
|
-
let value;
|
|
32
|
-
try {
|
|
33
|
-
value = JSON.parse(source);
|
|
34
|
-
}
|
|
35
|
-
catch (error) {
|
|
36
|
-
throw new Error(`--exec-json is not valid JSON: ${error.message}`);
|
|
37
|
-
}
|
|
38
|
-
if (!Array.isArray(value) || value.length === 0 ||
|
|
39
|
-
value.some(argument => typeof argument !== 'string')) {
|
|
40
|
-
throw new Error('--exec-json must be a non-empty JSON array of string arguments');
|
|
41
|
-
}
|
|
42
|
-
return value;
|
|
43
|
-
}
|
|
44
|
-
function terminalSafe(value) {
|
|
45
|
-
// Preserve ordinary whitespace while removing C0/C1 controls, including
|
|
46
|
-
// ESC/OSC introducers. JSON output retains the server-returned strings.
|
|
47
|
-
let safe = '';
|
|
48
|
-
for (const character of value) {
|
|
49
|
-
const codePoint = character.codePointAt(0) ?? 0;
|
|
50
|
-
const ordinaryWhitespace = codePoint === 9 || codePoint === 10 || codePoint === 13;
|
|
51
|
-
if ((codePoint < 32 && !ordinaryWhitespace) ||
|
|
52
|
-
(codePoint >= 127 && codePoint <= 159))
|
|
53
|
-
continue;
|
|
54
|
-
safe += character;
|
|
55
|
-
}
|
|
56
|
-
return safe;
|
|
57
|
-
}
|
|
58
|
-
function printUniverseAction(action) {
|
|
59
|
-
const result = action.result;
|
|
60
|
-
if (action.status === 'queued' || action.status === 'running') {
|
|
61
|
-
console.log(chalk.dim(`${action.caseId}/${action.universe} ${action.action} is ${action.status}; action ${action.actionId}`));
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (action.status === 'failed') {
|
|
65
|
-
console.error(chalk.red(`Universe ${action.action} failed: ${action.error || 'unknown error'}`));
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
if (!result) {
|
|
69
|
-
console.error(chalk.red(`Universe ${action.action} completed without a result.`));
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
console.log(chalk.dim(`${action.caseId}/${action.universe} ${action.action} (${result.durationMs}ms, exit ${result.exitCode ?? 'unknown'})`));
|
|
73
|
-
if (result.stdout) {
|
|
74
|
-
const stdout = terminalSafe(result.stdout);
|
|
75
|
-
process.stdout.write(stdout.endsWith('\n') ? stdout : `${stdout}\n`);
|
|
76
|
-
}
|
|
77
|
-
if (result.stderr) {
|
|
78
|
-
const stderr = terminalSafe(result.stderr);
|
|
79
|
-
process.stderr.write(stderr.endsWith('\n') ? stderr : `${stderr}\n`);
|
|
80
|
-
}
|
|
81
|
-
if (result.stdoutOmittedBytes || result.stderrOmittedBytes) {
|
|
82
|
-
console.error(chalk.dim(`Output truncated: ${result.stdoutOmittedBytes} stdout bytes, ${result.stderrOmittedBytes} stderr bytes omitted.`));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
async function runUniverseOperation(pr, prLabel, options, token, pollIntervalSeconds, timeoutSeconds) {
|
|
86
|
-
const requested = Boolean(options.action || options.case || options.universe ||
|
|
87
|
-
options.replay || options.execJson || options.cwd || options.commandTimeout);
|
|
88
|
-
if (!requested)
|
|
89
|
-
return false;
|
|
90
|
-
if (!options.run)
|
|
91
|
-
throw new Error('universe exploration requires --run <run-id>');
|
|
92
|
-
let actionId = options.action;
|
|
93
|
-
if (actionId) {
|
|
94
|
-
if (options.case || options.universe || options.replay || options.execJson ||
|
|
95
|
-
options.cwd || options.commandTimeout) {
|
|
96
|
-
throw new Error('--action cannot be combined with a new universe command');
|
|
97
|
-
}
|
|
98
|
-
if (options.wait === false) {
|
|
99
|
-
const loaded = await getDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, actionId, token);
|
|
100
|
-
if (loaded.status !== 'ok')
|
|
101
|
-
throw new Error(loaded.message);
|
|
102
|
-
emit(options.json, { ok: true, pr: prLabel, ...loaded.action }, () => printUniverseAction(loaded.action));
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
if (!options.case || !/^case-\d{5}$/.test(options.case)) {
|
|
108
|
-
throw new Error('a canonical --case case-00000 is required');
|
|
109
|
-
}
|
|
110
|
-
if (!options.universe || !['control-before', 'before', 'after'].includes(options.universe)) {
|
|
111
|
-
throw new Error('--universe must be control-before, before, or after');
|
|
112
|
-
}
|
|
113
|
-
if (Boolean(options.replay) === Boolean(options.execJson)) {
|
|
114
|
-
throw new Error('choose exactly one of --replay or --exec-json');
|
|
115
|
-
}
|
|
116
|
-
if (options.replay && (options.cwd || options.commandTimeout)) {
|
|
117
|
-
throw new Error('--cwd and --command-timeout apply only to --exec-json');
|
|
118
|
-
}
|
|
119
|
-
const commandTimeoutMs = options.commandTimeout === undefined
|
|
120
|
-
? undefined
|
|
121
|
-
: Number(options.commandTimeout) * 1000;
|
|
122
|
-
if (commandTimeoutMs !== undefined &&
|
|
123
|
-
!(Number.isInteger(commandTimeoutMs) && commandTimeoutMs >= 1_000 && commandTimeoutMs <= 300_000)) {
|
|
124
|
-
throw new Error('--command-timeout must be between 1 and 300 seconds');
|
|
125
|
-
}
|
|
126
|
-
const started = await startDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, token, {
|
|
127
|
-
caseId: options.case,
|
|
128
|
-
universe: options.universe,
|
|
129
|
-
action: options.replay ? 'replay' : 'exec',
|
|
130
|
-
...(options.execJson ? { argv: parseExecArgv(options.execJson) } : {}),
|
|
131
|
-
...(options.cwd ? { cwd: options.cwd } : {}),
|
|
132
|
-
...(commandTimeoutMs === undefined ? {} : { timeoutMs: commandTimeoutMs }),
|
|
133
|
-
});
|
|
134
|
-
if (started.status !== 'ok')
|
|
135
|
-
throw new Error(started.message);
|
|
136
|
-
actionId = started.action.actionId;
|
|
137
|
-
if (options.wait === false) {
|
|
138
|
-
emit(options.json, { ok: true, pr: prLabel, ...started.action }, () => {
|
|
139
|
-
console.log(chalk.green('✓ Universe action queued'));
|
|
140
|
-
console.log(chalk.dim(` Action: ${actionId}`));
|
|
141
|
-
console.log(chalk.dim(` Resume: haystack design-verify ${prLabel} --run ${options.run} --action ${actionId}`));
|
|
142
|
-
});
|
|
143
|
-
return true;
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
const waited = await waitForDesignVerifierUniverseAction(pr.owner, pr.repo, pr.prNumber, options.run, actionId, token, { intervalMs: pollIntervalSeconds * 1000, timeoutMs: timeoutSeconds * 1000 });
|
|
147
|
-
if (waited.status === 'timeout') {
|
|
148
|
-
throw new Error(`Timed out waiting; resume with: haystack design-verify ${prLabel} --run ${options.run} --action ${actionId}`);
|
|
149
|
-
}
|
|
150
|
-
if (waited.status === 'error')
|
|
151
|
-
throw new Error(waited.message);
|
|
152
|
-
emit(options.json, {
|
|
153
|
-
ok: waited.action.status === 'completed' && waited.action.result?.exitCode === 0,
|
|
154
|
-
pr: prLabel,
|
|
155
|
-
...waited.action,
|
|
156
|
-
}, () => printUniverseAction(waited.action));
|
|
157
|
-
process.exitCode = waited.action.status === 'failed'
|
|
158
|
-
? 2
|
|
159
|
-
: waited.action.result?.exitCode === 0 ? 0 : 1;
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
export async function designVerifyCommand(identifier, options) {
|
|
163
|
-
let pr;
|
|
164
|
-
try {
|
|
165
|
-
pr = parsePrRef(identifier, 'design-verify');
|
|
166
|
-
}
|
|
167
|
-
catch (err) {
|
|
168
|
-
emit(options.json, { ok: false, error: err.message }, () => {
|
|
169
|
-
console.error(chalk.red(err.message));
|
|
170
|
-
});
|
|
171
|
-
process.exitCode = 1;
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
const prLabel = `${pr.owner}/${pr.repo}#${pr.prNumber}`;
|
|
175
|
-
let token = null;
|
|
176
|
-
try {
|
|
177
|
-
token = await loadToken({ owner: pr.owner, repo: pr.repo });
|
|
178
|
-
}
|
|
179
|
-
catch (err) {
|
|
180
|
-
emit(options.json, { ok: false, pr: prLabel, error: `Could not load Haystack credentials: ${err.message}` }, () => {
|
|
181
|
-
console.error(chalk.red(`Could not load Haystack credentials: ${err.message}`));
|
|
182
|
-
});
|
|
183
|
-
process.exitCode = 1;
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (!token) {
|
|
187
|
-
emit(options.json, { ok: false, pr: prLabel, error: 'Authentication required. Run `haystack login` first.' }, () => {
|
|
188
|
-
console.error(chalk.red('Design verification requires authentication. Run `haystack login` first.'));
|
|
189
|
-
});
|
|
190
|
-
process.exitCode = 1;
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
if (options.history || options.limit !== undefined) {
|
|
194
|
-
try {
|
|
195
|
-
if (!options.history)
|
|
196
|
-
throw new Error('--limit requires --history');
|
|
197
|
-
if (options.run || options.case || options.universe || options.replay || options.execJson ||
|
|
198
|
-
options.cwd || options.commandTimeout || options.action || options.wait === false) {
|
|
199
|
-
throw new Error('--history cannot be combined with run or universe operations');
|
|
200
|
-
}
|
|
201
|
-
const limit = Number(options.limit ?? '20');
|
|
202
|
-
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) {
|
|
203
|
-
throw new Error('--limit must be between 1 and 100');
|
|
204
|
-
}
|
|
205
|
-
const loaded = await getDesignVerifierHistory(pr.owner, pr.repo, token, limit);
|
|
206
|
-
if (loaded.status !== 'ok')
|
|
207
|
-
throw new Error(loaded.message);
|
|
208
|
-
emit(options.json, { ...loaded.history }, () => console.log(formatDesignVerifierHistory(loaded.history)));
|
|
209
|
-
}
|
|
210
|
-
catch (error) {
|
|
211
|
-
emit(options.json, { ok: false, error: error.message }, () => {
|
|
212
|
-
console.error(chalk.red(error.message));
|
|
213
|
-
});
|
|
214
|
-
process.exitCode = 2;
|
|
215
|
-
}
|
|
216
|
-
return;
|
|
217
|
-
}
|
|
218
|
-
const pollIntervalSeconds = Number(options.pollInterval || '3');
|
|
219
|
-
const timeoutSeconds = Number(options.timeout || '7200');
|
|
220
|
-
if (!(Number.isFinite(pollIntervalSeconds) && pollIntervalSeconds >= 1 && pollIntervalSeconds <= 60)) {
|
|
221
|
-
emit(options.json, { ok: false, error: '--poll-interval must be between 1 and 60 seconds' }, () => {
|
|
222
|
-
console.error(chalk.red('--poll-interval must be between 1 and 60 seconds'));
|
|
223
|
-
});
|
|
224
|
-
process.exitCode = 2;
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
if (!(Number.isFinite(timeoutSeconds) && timeoutSeconds >= 1 && timeoutSeconds <= 7200)) {
|
|
228
|
-
emit(options.json, { ok: false, error: '--timeout must be between 1 and 7200 seconds' }, () => {
|
|
229
|
-
console.error(chalk.red('--timeout must be between 1 and 7200 seconds'));
|
|
230
|
-
});
|
|
231
|
-
process.exitCode = 2;
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
try {
|
|
235
|
-
if (await runUniverseOperation(pr, prLabel, options, token, pollIntervalSeconds, timeoutSeconds))
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
catch (error) {
|
|
239
|
-
emit(options.json, { ok: false, pr: prLabel, error: error.message }, () => {
|
|
240
|
-
console.error(chalk.red(error.message));
|
|
241
|
-
});
|
|
242
|
-
process.exitCode = 2;
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
if (!options.json) {
|
|
246
|
-
console.log(chalk.dim(options.run
|
|
247
|
-
? `\nResuming design verification ${options.run}...`
|
|
248
|
-
: `\nStarting design verification for ${prLabel}...`));
|
|
249
|
-
}
|
|
250
|
-
const result = options.run
|
|
251
|
-
? { status: 'duplicate', runId: options.run }
|
|
252
|
-
: await triggerDesignVerifier(pr.owner, pr.repo, pr.prNumber, token);
|
|
253
|
-
switch (result.status) {
|
|
254
|
-
case 'started':
|
|
255
|
-
case 'queued':
|
|
256
|
-
case 'duplicate':
|
|
257
|
-
if (options.wait === false) {
|
|
258
|
-
emit(options.json, { ok: true, pr: prLabel, runId: result.runId, outcome: result.status }, () => {
|
|
259
|
-
const suffix = result.status === 'queued'
|
|
260
|
-
? ' (at capacity — queued; it will start as a slot frees up)'
|
|
261
|
-
: '';
|
|
262
|
-
console.log(chalk.green(`✓ Design verification ${result.status}${suffix}`));
|
|
263
|
-
console.log(chalk.dim(` Run: ${result.runId}`));
|
|
264
|
-
console.log(chalk.dim(` Resume: haystack design-verify ${prLabel} --run ${result.runId}`));
|
|
265
|
-
});
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
break;
|
|
269
|
-
default:
|
|
270
|
-
emit(options.json, { ok: false, pr: prLabel, error: result.message, kind: result.status }, () => {
|
|
271
|
-
console.error(chalk.red(`✗ ${result.message}`));
|
|
272
|
-
});
|
|
273
|
-
process.exitCode = 1;
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
let lastProgress = '';
|
|
277
|
-
const waited = await waitForDesignVerifier(pr.owner, pr.repo, pr.prNumber, result.runId, token, {
|
|
278
|
-
intervalMs: pollIntervalSeconds * 1000,
|
|
279
|
-
timeoutMs: timeoutSeconds * 1000,
|
|
280
|
-
onProgress: (run) => {
|
|
281
|
-
if (options.json)
|
|
282
|
-
return;
|
|
283
|
-
const label = designVerifierStageLabel(run.stage);
|
|
284
|
-
if (label === lastProgress)
|
|
285
|
-
return;
|
|
286
|
-
lastProgress = label;
|
|
287
|
-
console.error(chalk.dim(` ${label}...`));
|
|
288
|
-
},
|
|
289
|
-
});
|
|
290
|
-
if (waited.status === 'timeout') {
|
|
291
|
-
const message = `Timed out waiting; the run continues. Resume with: haystack design-verify ${prLabel} --run ${result.runId}`;
|
|
292
|
-
emit(options.json, { ok: false, status: 'pending', runId: result.runId, error: message }, () => {
|
|
293
|
-
console.error(chalk.yellow(message));
|
|
294
|
-
});
|
|
295
|
-
process.exitCode = 2;
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
if (waited.status === 'error') {
|
|
299
|
-
emit(options.json, { ok: false, status: 'failed', runId: result.runId, error: waited.message }, () => {
|
|
300
|
-
console.error(chalk.red(`Verification status failed: ${waited.message}`));
|
|
301
|
-
});
|
|
302
|
-
process.exitCode = 2;
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
const report = buildDesignVerifierAgentReport(waited.run);
|
|
306
|
-
if (options.json)
|
|
307
|
-
console.log(JSON.stringify(report));
|
|
308
|
-
else
|
|
309
|
-
console.log(`\n${formatDesignVerifierAgentReport(report)}`);
|
|
310
|
-
process.exitCode = report.exitCode;
|
|
311
|
-
}
|
package/dist/commands/dismiss.js
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dismiss & mark-reviewed commands — User overrides for PR findings.
|
|
3
|
-
*
|
|
4
|
-
* haystack dismiss <pr> — Dismiss analysis findings for a PR
|
|
5
|
-
* haystack mark-reviewed <pr> — Mark human review as not needed for a PR
|
|
6
|
-
*
|
|
7
|
-
* Each posts a user override to the Haystack API so the PR moves toward
|
|
8
|
-
* "Good to Merge" in the feed.
|
|
9
|
-
*
|
|
10
|
-
* Accepts a PR identifier in several formats:
|
|
11
|
-
* 123 # Uses current repo's owner/repo
|
|
12
|
-
* owner/repo#123 # Fully qualified
|
|
13
|
-
* https://github.com/owner/repo/pull/123 # GitHub URL
|
|
14
|
-
*/
|
|
15
|
-
import chalk from 'chalk';
|
|
16
|
-
import { emitActionPayload, failAction } from '../utils/action-output.js';
|
|
17
|
-
import { resolveAuthContext } from '../utils/auth.js';
|
|
18
|
-
import { classifyHttpError } from '../utils/haystack-api.js';
|
|
19
|
-
import { githubRestBaseFor } from '../utils/github-api.js';
|
|
20
|
-
import { parsePrRef } from '../utils/pr-ref.js';
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// GitHub API helpers
|
|
23
|
-
// ============================================================================
|
|
24
|
-
async function getPRHeadSha(owner, repo, prNumber, token) {
|
|
25
|
-
const response = await fetch(`${githubRestBaseFor(token)}/repos/${owner}/${repo}/pulls/${prNumber}`, {
|
|
26
|
-
headers: {
|
|
27
|
-
Authorization: `Bearer ${token}`,
|
|
28
|
-
Accept: 'application/vnd.github.v3+json',
|
|
29
|
-
'User-Agent': 'Haystack-CLI',
|
|
30
|
-
},
|
|
31
|
-
});
|
|
32
|
-
if (!response.ok) {
|
|
33
|
-
if (response.status === 404) {
|
|
34
|
-
throw new Error(`PR #${prNumber} not found in ${owner}/${repo}`);
|
|
35
|
-
}
|
|
36
|
-
throw await classifyHttpError(response, `Fetching PR #${prNumber}`);
|
|
37
|
-
}
|
|
38
|
-
const data = await response.json();
|
|
39
|
-
return data.head.sha;
|
|
40
|
-
}
|
|
41
|
-
// ============================================================================
|
|
42
|
-
// User override API
|
|
43
|
-
// ============================================================================
|
|
44
|
-
const HAYSTACK_API = 'https://haystackeditor.com';
|
|
45
|
-
async function postUserOverride(repoFullName, prNumber, commitSha, type, token) {
|
|
46
|
-
const response = await fetch(`${HAYSTACK_API}/api/user-override`, {
|
|
47
|
-
method: 'POST',
|
|
48
|
-
headers: {
|
|
49
|
-
Authorization: `Bearer ${token}`,
|
|
50
|
-
'Content-Type': 'application/json',
|
|
51
|
-
'User-Agent': 'Haystack-CLI',
|
|
52
|
-
},
|
|
53
|
-
body: JSON.stringify({ repoFullName, prNumber, commitSha, type }),
|
|
54
|
-
});
|
|
55
|
-
if (!response.ok) {
|
|
56
|
-
throw await classifyHttpError(response, 'User override');
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
async function deleteUserOverrides(repoFullName, prNumber, token) {
|
|
60
|
-
const response = await fetch(`${HAYSTACK_API}/api/user-override`, {
|
|
61
|
-
method: 'DELETE',
|
|
62
|
-
headers: {
|
|
63
|
-
Authorization: `Bearer ${token}`,
|
|
64
|
-
'Content-Type': 'application/json',
|
|
65
|
-
'User-Agent': 'Haystack-CLI',
|
|
66
|
-
},
|
|
67
|
-
body: JSON.stringify({ repoFullName, prNumber }),
|
|
68
|
-
});
|
|
69
|
-
if (!response.ok) {
|
|
70
|
-
throw await classifyHttpError(response, 'Clearing user overrides');
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// ============================================================================
|
|
74
|
-
// Pure payload functions (shared by the CLI commands and the MCP server —
|
|
75
|
-
// they throw instead of exiting, and never write to stdout)
|
|
76
|
-
// ============================================================================
|
|
77
|
-
/** Post a user override and return the action payload. Throws on failure. */
|
|
78
|
-
export async function performOverride(identifier, type, commandName) {
|
|
79
|
-
const pr = parsePrRef(identifier, commandName);
|
|
80
|
-
const authContext = await resolveAuthContext({ owner: pr.owner, repo: pr.repo });
|
|
81
|
-
const repoFullName = `${pr.owner}/${pr.repo}`;
|
|
82
|
-
const headSha = await getPRHeadSha(pr.owner, pr.repo, pr.prNumber, authContext.token);
|
|
83
|
-
await postUserOverride(repoFullName, pr.prNumber, headSha, type, authContext.token);
|
|
84
|
-
return {
|
|
85
|
-
action: type === 'findings-dismissed' ? 'dismiss' : 'mark_reviewed',
|
|
86
|
-
status: 'ok',
|
|
87
|
-
ref: `${repoFullName}#${pr.prNumber}`,
|
|
88
|
-
head_sha: headSha,
|
|
89
|
-
detail: type === 'findings-dismissed'
|
|
90
|
-
? 'Findings dismissed; the PR moves to "Good to Merge". A new commit with net-new issues resurfaces it.'
|
|
91
|
-
: 'Human review marked as not needed; the PR moves to "Good to Merge". A new commit invalidates the override.',
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
/** Clear all overrides and return the action payload. Throws on failure. */
|
|
95
|
-
export async function performUndismiss(identifier) {
|
|
96
|
-
const pr = parsePrRef(identifier, 'undismiss');
|
|
97
|
-
const authContext = await resolveAuthContext({ owner: pr.owner, repo: pr.repo });
|
|
98
|
-
const repoFullName = `${pr.owner}/${pr.repo}`;
|
|
99
|
-
await deleteUserOverrides(repoFullName, pr.prNumber, authContext.token);
|
|
100
|
-
return {
|
|
101
|
-
action: 'undismiss',
|
|
102
|
-
status: 'ok',
|
|
103
|
-
ref: `${repoFullName}#${pr.prNumber}`,
|
|
104
|
-
detail: 'All overrides cleared; the PR returns to its original feed bucket.',
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
async function runOverride(identifier, type, commandName, options) {
|
|
108
|
-
const action = type === 'findings-dismissed' ? 'dismiss' : 'mark_reviewed';
|
|
109
|
-
const actionLabel = type === 'findings-dismissed'
|
|
110
|
-
? 'Dismissing findings'
|
|
111
|
-
: 'Marking review as not needed';
|
|
112
|
-
if (!options.json)
|
|
113
|
-
console.log(chalk.dim(`\n${actionLabel} for ${identifier}...`));
|
|
114
|
-
let payload;
|
|
115
|
-
try {
|
|
116
|
-
payload = await performOverride(identifier, type, commandName);
|
|
117
|
-
}
|
|
118
|
-
catch (err) {
|
|
119
|
-
failAction(options.json, action, null, err.message);
|
|
120
|
-
return; // unreachable — satisfies TS narrowing
|
|
121
|
-
}
|
|
122
|
-
if (options.json) {
|
|
123
|
-
emitActionPayload(payload);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
if (type === 'findings-dismissed') {
|
|
127
|
-
console.log(chalk.green(`\n Findings dismissed for ${payload.ref}`));
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
console.log(chalk.green(`\n Review marked as not needed for ${payload.ref}`));
|
|
131
|
-
}
|
|
132
|
-
console.log(chalk.dim(` Commit: ${payload.head_sha.slice(0, 7)}`));
|
|
133
|
-
console.log(chalk.dim(' The PR will move to "Good to Merge" in the feed.'));
|
|
134
|
-
console.log(chalk.dim(' If a new commit introduces net-new issues, the PR will resurface.\n'));
|
|
135
|
-
}
|
|
136
|
-
export async function dismissCommand(identifier, options = {}) {
|
|
137
|
-
await runOverride(identifier, 'findings-dismissed', 'dismiss', options);
|
|
138
|
-
}
|
|
139
|
-
export async function markReviewedCommand(identifier, options = {}) {
|
|
140
|
-
await runOverride(identifier, 'review-not-needed', 'mark-reviewed', options);
|
|
141
|
-
}
|
|
142
|
-
export async function undismissCommand(identifier, options = {}) {
|
|
143
|
-
if (!options.json)
|
|
144
|
-
console.log(chalk.dim(`\nClearing overrides for ${identifier}...`));
|
|
145
|
-
let payload;
|
|
146
|
-
try {
|
|
147
|
-
payload = await performUndismiss(identifier);
|
|
148
|
-
}
|
|
149
|
-
catch (err) {
|
|
150
|
-
failAction(options.json, 'undismiss', null, err.message);
|
|
151
|
-
return; // unreachable — satisfies TS narrowing
|
|
152
|
-
}
|
|
153
|
-
if (options.json) {
|
|
154
|
-
emitActionPayload(payload);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
console.log(chalk.green(`\n Overrides cleared for ${payload.ref}`));
|
|
158
|
-
console.log(chalk.dim(' The PR will return to its original feed bucket.\n'));
|
|
159
|
-
}
|