@haystackeditor/cli 0.11.0 → 0.12.1

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/dist/index.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * npx @haystackeditor/cli status # Check configuration
12
12
  * npx @haystackeditor/cli login # Authenticate with GitHub
13
13
  * npx @haystackeditor/cli submit # Create a PR (auto-merge or review)
14
- * npx @haystackeditor/cli check-pending # Check analysis status
14
+ * npx @haystackeditor/cli triage # Check last submitted PR
15
15
  * npx @haystackeditor/cli triage 123 # View analysis results for a PR
16
16
  * npx @haystackeditor/cli dismiss 123 # Dismiss findings for a PR
17
17
  * npx @haystackeditor/cli mark-reviewed 123 # Mark review as not needed
@@ -23,11 +23,10 @@ import { Command } from 'commander';
23
23
  import { statusCommand } from './commands/status.js';
24
24
  import { initCommand } from './commands/init.js';
25
25
  import { loginCommand, logoutCommand } from './commands/login.js';
26
- import { handleAgenticTool, handleAutoMerge, handleWaitForReviewers } from './commands/config.js';
26
+ import { handleAgenticTool, handleAutoMerge, handleWaitForReviewers, isAutoMergeEnabled } from './commands/config.js';
27
27
  import { installSkills, listSkills } from './commands/skills.js';
28
28
  import { hooksInstall, hooksStatus, hooksUpdate } from './commands/hooks.js';
29
29
  import { submitCommand } from './commands/submit.js';
30
- import { checkPendingCommand } from './commands/check-pending.js';
31
30
  import { installSessionHooks, sessionHooksStatus } from './commands/install-session-hooks.js';
32
31
  import { listPolicies, addPolicy, removePolicy, initPolicies, addInstruction } from './commands/policy.js';
33
32
  import { triageCommand } from './commands/triage.js';
@@ -39,7 +38,7 @@ const program = new Command();
39
38
  program
40
39
  .name('haystack')
41
40
  .description('Haystack CLI — automated PR review, triage, and merge queue')
42
- .version('0.11.0');
41
+ .version('0.12.1');
43
42
  program
44
43
  .command('init')
45
44
  .description('Create .haystack.json configuration')
@@ -95,7 +94,8 @@ program
95
94
  .option('--draft', 'Create as draft PR')
96
95
  .option('--review [reviewer]', 'Request human review (BLOCKS auto-merge — only use when explicitly asked)')
97
96
  .option('--force', 'Skip pre-PR triage checks')
98
- .option('--auto-fix', 'Auto-fix analysis issues before surfacing to Feed')
97
+ .option('--auto-fix', 'Alpha auto-fix for straightforward mechanical issues (discouraged)')
98
+ .option('--auto-merge', 'Apply auto-merge label (default: from .haystack.json, --no-auto-merge to disable)')
99
99
  .option('--no-wait', 'Skip waiting for analysis results')
100
100
  .addHelpText('after', `
101
101
  This command is designed for AI coding agents to submit PRs.
@@ -113,12 +113,17 @@ Pre-PR Triage:
113
113
 
114
114
  Use --force to skip triage entirely.
115
115
  Use --no-wait to skip waiting for analysis results.
116
- Use --auto-fix to auto-fix analysis issues before surfacing to Feed.
116
+ Use --auto-fix only when explicitly opting into the alpha fixer for
117
+ straightforward mechanical issues.
117
118
 
118
119
  Review Routing:
119
120
  By default, PRs enter the auto-merge queue and merge automatically once
120
121
  Haystack analysis passes. This is the correct default for most PRs.
121
122
 
123
+ ⚠ --auto-fix is currently alpha and discouraged by default. Prefer plain
124
+ haystack submit unless you explicitly want the sandbox fixer to attempt
125
+ straightforward mechanical fixes before issues hit the Feed.
126
+
122
127
  ⚠ Only use --review when the user EXPLICITLY asks for human review.
123
128
  --review BLOCKS auto-merge — the PR will NOT merge until a human approves,
124
129
  which delays merging. Do not add --review "just to be safe".
@@ -128,8 +133,8 @@ Review Routing:
128
133
  • --review <username> Same label, plus requests review from that GitHub user
129
134
 
130
135
  Examples:
131
- haystack submit --auto-fix # Recommended: triage, create PR, auto-fix issues
132
- haystack submit # Triage, create PR, auto-merge queue (no auto-fix)
136
+ haystack submit # Recommended: triage, create PR, auto-merge queue
137
+ haystack submit --auto-fix # Discouraged alpha: auto-fix straightforward mechanical issues
133
138
  haystack submit --force # Skip triage
134
139
  haystack submit --no-wait # Don't wait for analysis
135
140
  haystack submit --title "Fix auth" # Custom PR title
@@ -141,38 +146,28 @@ Examples:
141
146
  haystack submit --review # ⚠ Blocks auto-merge, needs human approval
142
147
  haystack submit --review octocat # ⚠ Blocks auto-merge, requests review from octocat
143
148
  `)
144
- .action(submitCommand);
145
- program
146
- .command('check-pending')
147
- .description('Check status of pending Haystack analysis')
148
- .option('--json', 'Output as JSON')
149
- .option('--hook', 'Minimal output for session-start hooks')
150
- .option('--clear', 'Clear pending submit state')
151
- .addHelpText('after', `
152
- After \`haystack submit\`, this checks whether the analysis finished
153
- and shows a two-state verdict:
154
-
155
- Good to merge — No issues found, safe to merge
156
- Needs your input — Bugs or rule violations found
157
-
158
- This runs automatically on CLI session start if session hooks are installed.
159
-
160
- Examples:
161
- haystack check-pending # Rich terminal output
162
- haystack check-pending --hook # Minimal output (for hooks)
163
- haystack check-pending --json # Machine-readable JSON
164
- haystack check-pending --clear # Clear pending state
165
- `)
166
- .action(checkPendingCommand);
149
+ .action(async (options) => {
150
+ // Resolve --auto-merge default from .haystack.json when not explicitly set
151
+ if (options.autoMerge === undefined) {
152
+ options.autoMerge = await isAutoMergeEnabled();
153
+ }
154
+ return submitCommand(options);
155
+ });
167
156
  program
168
- .command('triage <pr>')
157
+ .command('triage [pr]')
169
158
  .description('View Haystack analysis results for a PR')
170
159
  .option('--json', 'Output as JSON')
160
+ .option('--hook', 'Minimal single-line output for session-start hooks')
161
+ .option('--clear', 'Clear pending submit state')
171
162
  .option('--no-wait', 'Exit immediately if analysis is still pending')
172
163
  .addHelpText('after', `
173
164
  Look up triage results (bugs, rule violations, verdict) for any PR.
165
+ When called without a PR identifier, checks the last submitted PR.
166
+
167
+ Runs automatically on CLI session start if session hooks are installed.
174
168
 
175
169
  PR identifier formats:
170
+ (none) Last submitted PR
176
171
  123 PR number (uses current repo)
177
172
  #123 PR number with hash
178
173
  owner/repo#123 Fully qualified
@@ -182,11 +177,14 @@ By default, if analysis is still in progress, the command will poll
182
177
  for up to 5 minutes. Use --no-wait to exit immediately instead.
183
178
 
184
179
  Examples:
180
+ haystack triage # Last submitted PR
185
181
  haystack triage 42 # Current repo, PR #42
186
182
  haystack triage acme/widgets#99 # Specific repo
187
183
  haystack triage https://github.com/o/r/pull/1 # From URL
188
184
  haystack triage 42 --json # Machine-readable output
189
185
  haystack triage 42 --no-wait # Don't wait if pending
186
+ haystack triage --hook # Session-start hook output
187
+ haystack triage --clear # Clear pending state
190
188
  `)
191
189
  .action(triageCommand);
192
190
  program
@@ -429,7 +427,7 @@ hooks
429
427
  .description('Install session-start hooks for coding CLIs')
430
428
  .option('--cli <name>', 'Target CLI: claude, codex, gemini, or all')
431
429
  .addHelpText('after', `
432
- Configures your coding CLI to run \`haystack check-pending\` on session start.
430
+ Configures your coding CLI to run \`haystack triage --hook\` on session start.
433
431
  This shows pending PR analysis results when you open a new terminal session.
434
432
 
435
433
  Claude Code: Native SessionStart hook (.claude/settings.json)
@@ -146,7 +146,10 @@ function extractHumanText(content) {
146
146
  return null;
147
147
  const textParts = [];
148
148
  for (const block of content) {
149
- if (block?.type !== 'text' || typeof block.text !== 'string')
149
+ // Support both old ({type: "text", text}) and new ({id, text}) content blocks
150
+ if (typeof block?.text !== 'string')
151
+ continue;
152
+ if (block.type && block.type !== 'text')
150
153
  continue;
151
154
  const stripped = stripWrappers(block.text);
152
155
  if (stripped && !isSlashCommand(stripped)) {
@@ -231,9 +234,12 @@ function extractUserMessages(tracePath) {
231
234
  }
232
235
  const entries = parseJsonl(content, tracePath);
233
236
  for (const entry of entries) {
234
- if (entry?.type !== 'user')
237
+ // Support both old format (entry.message.role === 'user') and new (entry.type === 'user')
238
+ const role = entry.message?.role ?? entry?.type;
239
+ if (role !== 'user')
235
240
  continue;
236
- const msg = entry.message?.content;
241
+ // Support both old format ({message: {content}}) and new ({content} at top level)
242
+ const msg = entry.message?.content ?? entry.content;
237
243
  if (!msg)
238
244
  continue;
239
245
  const text = extractHumanText(msg);
package/dist/types.d.ts CHANGED
@@ -84,14 +84,18 @@ declare const MergeQueueConfigSchema: z.ZodObject<{
84
84
  min_batch_size: z.ZodOptional<z.ZodNumber>;
85
85
  /** Maximum PRs per batch (cap: 20) */
86
86
  max_batch_size: z.ZodOptional<z.ZodNumber>;
87
+ /** Seconds to wait after grace period for more PRs to accumulate (default: 30, 0 to disable) */
88
+ batch_window_seconds: z.ZodOptional<z.ZodNumber>;
87
89
  }, "strip", z.ZodTypeAny, {
88
90
  enabled?: boolean | undefined;
89
91
  min_batch_size?: number | undefined;
90
92
  max_batch_size?: number | undefined;
93
+ batch_window_seconds?: number | undefined;
91
94
  }, {
92
95
  enabled?: boolean | undefined;
93
96
  min_batch_size?: number | undefined;
94
97
  max_batch_size?: number | undefined;
98
+ batch_window_seconds?: number | undefined;
95
99
  }>>;
96
100
  /** Wait for external AI code reviewers before merging — all bots must post */
97
101
  wait_for_reviewer: z.ZodOptional<z.ZodObject<{
@@ -117,6 +121,7 @@ declare const MergeQueueConfigSchema: z.ZodObject<{
117
121
  enabled?: boolean | undefined;
118
122
  min_batch_size?: number | undefined;
119
123
  max_batch_size?: number | undefined;
124
+ batch_window_seconds?: number | undefined;
120
125
  } | undefined;
121
126
  wait_for_reviewer?: {
122
127
  bots: string[];
@@ -133,6 +138,7 @@ declare const MergeQueueConfigSchema: z.ZodObject<{
133
138
  enabled?: boolean | undefined;
134
139
  min_batch_size?: number | undefined;
135
140
  max_batch_size?: number | undefined;
141
+ batch_window_seconds?: number | undefined;
136
142
  } | undefined;
137
143
  wait_for_reviewer?: {
138
144
  bots: string[];
@@ -1182,14 +1188,18 @@ export declare const HaystackConfigSchema: z.ZodObject<{
1182
1188
  min_batch_size: z.ZodOptional<z.ZodNumber>;
1183
1189
  /** Maximum PRs per batch (cap: 20) */
1184
1190
  max_batch_size: z.ZodOptional<z.ZodNumber>;
1191
+ /** Seconds to wait after grace period for more PRs to accumulate (default: 30, 0 to disable) */
1192
+ batch_window_seconds: z.ZodOptional<z.ZodNumber>;
1185
1193
  }, "strip", z.ZodTypeAny, {
1186
1194
  enabled?: boolean | undefined;
1187
1195
  min_batch_size?: number | undefined;
1188
1196
  max_batch_size?: number | undefined;
1197
+ batch_window_seconds?: number | undefined;
1189
1198
  }, {
1190
1199
  enabled?: boolean | undefined;
1191
1200
  min_batch_size?: number | undefined;
1192
1201
  max_batch_size?: number | undefined;
1202
+ batch_window_seconds?: number | undefined;
1193
1203
  }>>;
1194
1204
  /** Wait for external AI code reviewers before merging — all bots must post */
1195
1205
  wait_for_reviewer: z.ZodOptional<z.ZodObject<{
@@ -1215,6 +1225,7 @@ export declare const HaystackConfigSchema: z.ZodObject<{
1215
1225
  enabled?: boolean | undefined;
1216
1226
  min_batch_size?: number | undefined;
1217
1227
  max_batch_size?: number | undefined;
1228
+ batch_window_seconds?: number | undefined;
1218
1229
  } | undefined;
1219
1230
  wait_for_reviewer?: {
1220
1231
  bots: string[];
@@ -1231,6 +1242,7 @@ export declare const HaystackConfigSchema: z.ZodObject<{
1231
1242
  enabled?: boolean | undefined;
1232
1243
  min_batch_size?: number | undefined;
1233
1244
  max_batch_size?: number | undefined;
1245
+ batch_window_seconds?: number | undefined;
1234
1246
  } | undefined;
1235
1247
  wait_for_reviewer?: {
1236
1248
  bots: string[];
@@ -1251,6 +1263,7 @@ export declare const HaystackConfigSchema: z.ZodObject<{
1251
1263
  enabled?: boolean | undefined;
1252
1264
  min_batch_size?: number | undefined;
1253
1265
  max_batch_size?: number | undefined;
1266
+ batch_window_seconds?: number | undefined;
1254
1267
  } | undefined;
1255
1268
  wait_for_reviewer?: {
1256
1269
  bots: string[];
@@ -1372,6 +1385,7 @@ export declare const HaystackConfigSchema: z.ZodObject<{
1372
1385
  enabled?: boolean | undefined;
1373
1386
  min_batch_size?: number | undefined;
1374
1387
  max_batch_size?: number | undefined;
1388
+ batch_window_seconds?: number | undefined;
1375
1389
  } | undefined;
1376
1390
  wait_for_reviewer?: {
1377
1391
  bots: string[];
package/dist/types.js CHANGED
@@ -138,6 +138,8 @@ const MergeQueueConfigSchema = z.object({
138
138
  min_batch_size: z.number().optional(),
139
139
  /** Maximum PRs per batch (cap: 20) */
140
140
  max_batch_size: z.number().optional(),
141
+ /** Seconds to wait after grace period for more PRs to accumulate (default: 30, 0 to disable) */
142
+ batch_window_seconds: z.number().optional(),
141
143
  }).optional(),
142
144
  /** Wait for external AI code reviewers before merging — all bots must post */
143
145
  wait_for_reviewer: z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@haystackeditor/cli",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "Set up Haystack for your project — automated PR review, triage, and merge queue",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,19 +0,0 @@
1
- /**
2
- * check-pending command — Check status of pending Haystack analysis.
3
- *
4
- * Reads `.haystack/pending-submit.json`, polls the analysis API if needed,
5
- * and outputs a two-state verdict:
6
- * - "Good to merge" (no issues)
7
- * - "Needs your input" (issues found)
8
- *
9
- * Output modes:
10
- * --hook Minimal single-line for session-start hooks
11
- * --json Machine-readable JSON
12
- * (default) Rich terminal output
13
- */
14
- export interface CheckPendingOptions {
15
- json?: boolean;
16
- hook?: boolean;
17
- clear?: boolean;
18
- }
19
- export declare function checkPendingCommand(options: CheckPendingOptions): Promise<void>;
@@ -1,217 +0,0 @@
1
- /**
2
- * check-pending command — Check status of pending Haystack analysis.
3
- *
4
- * Reads `.haystack/pending-submit.json`, polls the analysis API if needed,
5
- * and outputs a two-state verdict:
6
- * - "Good to merge" (no issues)
7
- * - "Needs your input" (issues found)
8
- *
9
- * Output modes:
10
- * --hook Minimal single-line for session-start hooks
11
- * --json Machine-readable JSON
12
- * (default) Rich terminal output
13
- */
14
- import chalk from 'chalk';
15
- import { loadPendingSubmit, updatePendingSubmit, clearPendingSubmit } from '../utils/pending-state.js';
16
- import { checkAnalysisReady, fetchAnalysisResults } from '../utils/analysis-api.js';
17
- import { findExistingPR } from '../utils/github-api.js';
18
- import { loadToken } from './login.js';
19
- // ============================================================================
20
- // Polling
21
- // ============================================================================
22
- const POLL_INTERVAL_MS = 3_000;
23
- const POLL_TIMEOUT_MS = 30_000;
24
- /**
25
- * Poll the result endpoint until analysis is ready or timeout.
26
- */
27
- async function waitForAnalysis(owner, repo, prNumber, timeoutMs, token) {
28
- const deadline = Date.now() + timeoutMs;
29
- let consecutiveErrors = 0;
30
- while (Date.now() < deadline) {
31
- const result = await checkAnalysisReady(owner, repo, prNumber, token);
32
- if (result.status === 'ready')
33
- return 'completed';
34
- if (result.status === 'error') {
35
- consecutiveErrors++;
36
- if (consecutiveErrors >= 5)
37
- return 'failed';
38
- }
39
- else {
40
- consecutiveErrors = 0;
41
- }
42
- await new Promise(r => setTimeout(r, POLL_INTERVAL_MS));
43
- }
44
- return 'pending';
45
- }
46
- // ============================================================================
47
- // Output formatting
48
- // ============================================================================
49
- function printHookOutput(pending) {
50
- if (!pending.analysisResult) {
51
- console.log(`[Haystack] PR #${pending.prNumber}: Analysis in progress...`);
52
- return;
53
- }
54
- const result = pending.analysisResult;
55
- if (result.state === 'good-to-merge') {
56
- if (result.autoMerged) {
57
- console.log(`[Haystack] \u2705 PR #${pending.prNumber} "${getBranchLabel(pending)}": Auto-merged`);
58
- }
59
- else {
60
- console.log(`[Haystack] \u2705 PR #${pending.prNumber} "${getBranchLabel(pending)}": Good to merge`);
61
- }
62
- }
63
- else {
64
- const parts = [];
65
- if (result.bugCount > 0)
66
- parts.push(`${result.bugCount} bug(s)`);
67
- if (result.ruleViolationCount > 0)
68
- parts.push(`${result.ruleViolationCount} rule violation(s)`);
69
- if (parts.length === 0)
70
- parts.push(`${result.warningCount} warning(s)`);
71
- console.log(`[Haystack] \u26A0\uFE0F PR #${pending.prNumber} "${getBranchLabel(pending)}": Needs your input (${parts.join(', ')})`);
72
- console.log(` Review: ${result.reviewUrl}`);
73
- }
74
- }
75
- function printRichOutput(pending) {
76
- console.log(chalk.bold('\n' + '\u2501'.repeat(50)));
77
- console.log(chalk.bold(` Haystack Analysis: PR #${pending.prNumber}`));
78
- console.log(chalk.bold('\u2501'.repeat(50) + '\n'));
79
- console.log(` ${chalk.dim('Branch:')} ${pending.branch} \u2192 ${pending.baseBranch}`);
80
- console.log(` ${chalk.dim('URL:')} ${chalk.cyan(pending.prUrl)}`);
81
- if (!pending.analysisResult) {
82
- if (pending.status === 'polling') {
83
- console.log(` ${chalk.dim('Status:')} ${chalk.yellow('Analysis in progress...')}`);
84
- }
85
- else if (pending.status === 'failed') {
86
- console.log(` ${chalk.dim('Status:')} ${chalk.red('Analysis failed')}`);
87
- }
88
- else if (pending.status === 'stale') {
89
- console.log(` ${chalk.dim('Status:')} ${chalk.dim('Stale (submitted > 2h ago)')}`);
90
- }
91
- console.log('');
92
- return;
93
- }
94
- const result = pending.analysisResult;
95
- if (result.state === 'good-to-merge') {
96
- if (result.autoMerged) {
97
- console.log(` ${chalk.dim('Verdict:')} ${chalk.green.bold('Auto-merged')}`);
98
- console.log(` ${chalk.dim('Issues:')} ${chalk.green('None — PR was automatically merged')}`);
99
- }
100
- else {
101
- console.log(` ${chalk.dim('Verdict:')} ${chalk.green.bold('Good to merge')}`);
102
- console.log(` ${chalk.dim('Issues:')} ${chalk.green('None')}`);
103
- }
104
- }
105
- else {
106
- console.log(` ${chalk.dim('Verdict:')} ${chalk.yellow.bold('Needs your input')}`);
107
- console.log(` ${chalk.dim('Issues:')} ${result.summary}`);
108
- console.log('');
109
- for (const issue of result.issues) {
110
- const icon = issue.severity === 'error' ? chalk.red('\u2717') : chalk.yellow('!');
111
- const loc = issue.line ? `${issue.file}:${issue.line}` : issue.file;
112
- console.log(` ${icon} ${chalk.dim(loc)} ${issue.message}`);
113
- }
114
- }
115
- console.log(`\n ${chalk.dim('Review:')} ${chalk.cyan(result.reviewUrl)}`);
116
- console.log('');
117
- }
118
- function printJsonOutput(pending) {
119
- const output = {
120
- prNumber: pending.prNumber,
121
- prUrl: pending.prUrl,
122
- branch: pending.branch,
123
- baseBranch: pending.baseBranch,
124
- status: pending.status,
125
- submittedAt: pending.submittedAt,
126
- verdict: pending.analysisResult?.state || null,
127
- bugCount: pending.analysisResult?.bugCount || 0,
128
- warningCount: pending.analysisResult?.warningCount || 0,
129
- ruleViolationCount: pending.analysisResult?.ruleViolationCount || 0,
130
- summary: pending.analysisResult?.summary || null,
131
- issues: pending.analysisResult?.issues || [],
132
- reviewUrl: pending.analysisResult?.reviewUrl || null,
133
- autoMerged: pending.analysisResult?.autoMerged || false,
134
- };
135
- console.log(JSON.stringify(output, null, 2));
136
- }
137
- function getBranchLabel(pending) {
138
- return pending.branch.replace(/^refs\/heads\//, '');
139
- }
140
- // ============================================================================
141
- // Main command
142
- // ============================================================================
143
- export async function checkPendingCommand(options) {
144
- // Handle --clear
145
- if (options.clear) {
146
- clearPendingSubmit();
147
- if (!options.hook) {
148
- console.log(chalk.dim('Pending submit state cleared.'));
149
- }
150
- return;
151
- }
152
- // Load pending state
153
- const pending = loadPendingSubmit();
154
- if (!pending) {
155
- if (options.json) {
156
- console.log(JSON.stringify({ status: 'none' }));
157
- }
158
- else if (!options.hook) {
159
- // Silent for hook mode — no pending submits is fine
160
- console.log(chalk.dim('No pending submits.\n'));
161
- }
162
- return;
163
- }
164
- // Load token for private repo auth (best-effort, don't block if unavailable)
165
- const token = await loadToken() || undefined;
166
- // If still polling, try to resolve it
167
- if (pending.status === 'polling') {
168
- // Quick check: is the PR still open?
169
- try {
170
- const pr = await findExistingPR(pending.owner, pending.repo, pending.branch);
171
- if (!pr) {
172
- // PR was closed or merged
173
- updatePendingSubmit({ status: 'stale', resolvedAt: new Date().toISOString() });
174
- pending.status = 'stale';
175
- }
176
- }
177
- catch {
178
- // Auth not available or API error — continue with polling
179
- }
180
- if (pending.status === 'polling') {
181
- // For hook mode, do a single quick poll (no waiting)
182
- const timeout = options.hook ? 5_000 : POLL_TIMEOUT_MS;
183
- const result = await waitForAnalysis(pending.owner, pending.repo, pending.prNumber, timeout, token);
184
- if (result === 'completed') {
185
- // Fetch the actual analysis results
186
- try {
187
- const verdict = await fetchAnalysisResults(pending.owner, pending.repo, pending.prNumber, token);
188
- updatePendingSubmit({
189
- status: 'completed',
190
- analysisResult: verdict,
191
- resolvedAt: new Date().toISOString(),
192
- });
193
- pending.status = 'completed';
194
- pending.analysisResult = verdict;
195
- }
196
- catch {
197
- // Could not fetch results — leave as polling
198
- }
199
- }
200
- else if (result === 'failed') {
201
- updatePendingSubmit({ status: 'failed', resolvedAt: new Date().toISOString() });
202
- pending.status = 'failed';
203
- }
204
- // 'pending' — leave as-is for next check
205
- }
206
- }
207
- // Output based on mode
208
- if (options.json) {
209
- printJsonOutput(pending);
210
- }
211
- else if (options.hook) {
212
- printHookOutput(pending);
213
- }
214
- else {
215
- printRichOutput(pending);
216
- }
217
- }