@haystackeditor/cli 0.25.0 → 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.
Files changed (113) hide show
  1. package/README.md +39 -455
  2. package/dist/capture/adapters/django.js +7 -0
  3. package/dist/capture/adapters/index.js +10 -0
  4. package/dist/capture/adapters/next-build.js +113 -0
  5. package/dist/capture/adapters/next.js +272 -105
  6. package/dist/capture/adapters/nuxt.js +7 -0
  7. package/dist/capture/adapters/rails.js +7 -0
  8. package/dist/capture/adapters/react-router.js +7 -0
  9. package/dist/capture/adapters/sveltekit.js +7 -0
  10. package/dist/capture/adapters/vite.js +14 -0
  11. package/dist/capture/app-config.js +27 -2
  12. package/dist/capture/csp.js +7 -6
  13. package/dist/capture/url-rewrites.js +232 -0
  14. package/dist/capture-step.js +3 -0
  15. package/dist/commands/capture-brief.js +41 -32
  16. package/dist/commands/capture-manifest.js +25 -17
  17. package/dist/commands/feedback.js +66 -0
  18. package/dist/commands/init-capture.js +44 -27
  19. package/dist/commands/init-telemetry.js +77 -14
  20. package/dist/commands/init.js +19 -4
  21. package/dist/commands/lockfile-pin.js +307 -0
  22. package/dist/commands/verify-onboarding.js +16 -0
  23. package/dist/commands/verify.js +70 -15
  24. package/dist/index.js +29 -923
  25. package/dist/schema.js +4 -10
  26. package/dist/utils/haystack-api.js +0 -36
  27. package/package.json +1 -5
  28. package/schemas/feedback.v1.json +13 -0
  29. package/schemas/init.v1.json +1 -1
  30. package/schemas/pre-verify.v2.json +240 -0
  31. package/schemas/verify-raw.v1.json +1132 -0
  32. package/schemas/verify.v2.json +655 -0
  33. package/dist/assets/hooks/agent-context/detect.ts +0 -316
  34. package/dist/assets/hooks/agent-context/format.ts +0 -100
  35. package/dist/assets/hooks/agent-context/index.ts +0 -41
  36. package/dist/assets/hooks/agent-context/parsers/claude.ts +0 -262
  37. package/dist/assets/hooks/agent-context/parsers/codex.ts +0 -416
  38. package/dist/assets/hooks/agent-context/parsers/gemini.ts +0 -155
  39. package/dist/assets/hooks/agent-context/parsers/opencode.ts +0 -174
  40. package/dist/assets/hooks/agent-context/tsconfig.json +0 -14
  41. package/dist/assets/hooks/agent-context/types.ts +0 -58
  42. package/dist/assets/hooks/llm-rules-template.md +0 -59
  43. package/dist/assets/hooks/package-lock.json +0 -598
  44. package/dist/assets/hooks/package.json +0 -12
  45. package/dist/assets/hooks/scripts/commit-msg.sh +0 -5
  46. package/dist/assets/hooks/scripts/post-commit.sh +0 -5
  47. package/dist/assets/hooks/scripts/pre-commit.sh +0 -175
  48. package/dist/assets/hooks/scripts/pre-push.sh +0 -25
  49. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +0 -5
  50. package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +0 -528
  51. package/dist/assets/hooks/truncation-checker/index.ts +0 -595
  52. package/dist/assets/hooks/truncation-checker/tsconfig.json +0 -13
  53. package/dist/assets/skills/map-cloud-verifier-universe/SKILL.md +0 -2051
  54. package/dist/assets/skills/map-cloud-verifier-universe/agents/openai.yaml +0 -4
  55. package/dist/assets/skills/map-cloud-verifier-universe/references/output-contract.md +0 -3411
  56. package/dist/assets/skills/map-your-system.md +0 -143
  57. package/dist/assets/skills/submit.md +0 -200
  58. package/dist/commands/ask.js +0 -20
  59. package/dist/commands/cloud-verifier-behaviors.js +0 -218
  60. package/dist/commands/cloud-verifier-data-store-census.js +0 -539
  61. package/dist/commands/cloud-verifier-data-store-drift.js +0 -158
  62. package/dist/commands/cloud-verifier-identity-census.js +0 -4060
  63. package/dist/commands/cloud-verifier-materialization.js +0 -704
  64. package/dist/commands/cloud-verifier-pascal-selector-census.js +0 -1382
  65. package/dist/commands/cloud-verifier-python-manifest-selector-census.js +0 -2015
  66. package/dist/commands/cloud-verifier-specialized-operational-census.js +0 -11432
  67. package/dist/commands/cloud-verifier-universe.js +0 -10178
  68. package/dist/commands/config.js +0 -549
  69. package/dist/commands/design-verify.js +0 -311
  70. package/dist/commands/dismiss.js +0 -159
  71. package/dist/commands/hooks.js +0 -226
  72. package/dist/commands/inbox.js +0 -137
  73. package/dist/commands/mcp.js +0 -201
  74. package/dist/commands/policy.js +0 -371
  75. package/dist/commands/pr-status.js +0 -207
  76. package/dist/commands/pr.js +0 -105
  77. package/dist/commands/prepare-universe-review.js +0 -1092
  78. package/dist/commands/production-source-deny-policy.js +0 -100
  79. package/dist/commands/request-review.js +0 -74
  80. package/dist/commands/review.js +0 -191
  81. package/dist/commands/rules.js +0 -98
  82. package/dist/commands/scaffold-provisional-universe.js +0 -806
  83. package/dist/commands/setup.js +0 -1170
  84. package/dist/commands/skills.js +0 -447
  85. package/dist/commands/submit.js +0 -745
  86. package/dist/commands/system-map.js +0 -228
  87. package/dist/commands/triage.js +0 -598
  88. package/dist/commands/webhooks.js +0 -241
  89. package/dist/states.js +0 -46
  90. package/dist/tools/detect.js +0 -832
  91. package/dist/triage/astra.js +0 -202
  92. package/dist/triage/prompts.js +0 -188
  93. package/dist/triage/runner.js +0 -200
  94. package/dist/triage/types.js +0 -7
  95. package/dist/utils/action-output.js +0 -26
  96. package/dist/utils/analysis-api.js +0 -416
  97. package/dist/utils/design-verifier-api.js +0 -294
  98. package/dist/utils/design-verifier-history.js +0 -79
  99. package/dist/utils/design-verifier-result.js +0 -424
  100. package/dist/utils/github-api.js +0 -324
  101. package/dist/utils/pending-state.js +0 -86
  102. package/dist/utils/pr-ref.js +0 -56
  103. package/dist/utils/prompter.js +0 -328
  104. package/schemas/action.v1.json +0 -22
  105. package/schemas/ask.v1.json +0 -40
  106. package/schemas/inbox.v1.json +0 -27
  107. package/schemas/pr-status.v1.json +0 -61
  108. package/schemas/pr.v1.json +0 -97
  109. package/schemas/pr.v3.json +0 -45
  110. package/schemas/setup.v1.json +0 -75
  111. package/schemas/submit.v1.json +0 -90
  112. package/schemas/triage.v1.json +0 -103
  113. 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
- }
@@ -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
- }