@haystackeditor/cli 0.8.0 → 0.9.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 (76) hide show
  1. package/README.md +105 -12
  2. package/dist/assets/hooks/agent-context/detect.ts +136 -0
  3. package/dist/assets/hooks/agent-context/format.ts +99 -0
  4. package/dist/assets/hooks/agent-context/index.ts +39 -0
  5. package/dist/assets/hooks/agent-context/parsers/claude.ts +253 -0
  6. package/dist/assets/hooks/agent-context/parsers/gemini.ts +155 -0
  7. package/dist/assets/hooks/agent-context/parsers/opencode.ts +174 -0
  8. package/dist/assets/hooks/agent-context/tsconfig.json +13 -0
  9. package/dist/assets/hooks/agent-context/types.ts +58 -0
  10. package/dist/assets/hooks/llm-rules-template.md +56 -0
  11. package/dist/assets/hooks/package.json +11 -0
  12. package/dist/assets/hooks/scripts/commit-msg.sh +4 -0
  13. package/dist/assets/hooks/scripts/post-commit.sh +4 -0
  14. package/dist/assets/hooks/scripts/pre-commit.sh +92 -0
  15. package/dist/assets/hooks/scripts/pre-push.sh +25 -0
  16. package/dist/assets/hooks/scripts/prepare-commit-msg.sh +3 -0
  17. package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +528 -0
  18. package/dist/assets/hooks/truncation-checker/index.ts +595 -0
  19. package/dist/assets/hooks/truncation-checker/tsconfig.json +13 -0
  20. package/dist/assets/skills/prepare-haystack.md +323 -0
  21. package/dist/assets/skills/secrets.md +164 -0
  22. package/dist/assets/skills/setup-external-sandbox.md +243 -0
  23. package/dist/assets/skills/setup-haystack.md +639 -0
  24. package/dist/assets/skills/submit.md +154 -0
  25. package/dist/assets/templates/CLAUDE.md.snippet +42 -0
  26. package/dist/assets/templates/haystack.yml +193 -0
  27. package/dist/commands/check-pending.d.ts +19 -0
  28. package/dist/commands/check-pending.js +217 -0
  29. package/dist/commands/config.d.ts +18 -12
  30. package/dist/commands/config.js +327 -52
  31. package/dist/commands/hooks.d.ts +17 -0
  32. package/dist/commands/hooks.js +269 -0
  33. package/dist/commands/install-session-hooks.d.ts +16 -0
  34. package/dist/commands/install-session-hooks.js +302 -0
  35. package/dist/commands/login.js +1 -1
  36. package/dist/commands/policy.d.ts +31 -0
  37. package/dist/commands/policy.js +365 -0
  38. package/dist/commands/skills.d.ts +8 -0
  39. package/dist/commands/skills.js +80 -0
  40. package/dist/commands/submit.d.ts +22 -0
  41. package/dist/commands/submit.js +428 -0
  42. package/dist/commands/triage.d.ts +16 -0
  43. package/dist/commands/triage.js +354 -0
  44. package/dist/index.d.ts +3 -0
  45. package/dist/index.js +317 -2
  46. package/dist/tools/detect.d.ts +50 -0
  47. package/dist/tools/detect.js +853 -0
  48. package/dist/tools/fixtures.d.ts +38 -0
  49. package/dist/tools/fixtures.js +199 -0
  50. package/dist/tools/setup.d.ts +43 -0
  51. package/dist/tools/setup.js +597 -0
  52. package/dist/triage/prompts.d.ts +31 -0
  53. package/dist/triage/prompts.js +266 -0
  54. package/dist/triage/runner.d.ts +21 -0
  55. package/dist/triage/runner.js +325 -0
  56. package/dist/triage/traces.d.ts +20 -0
  57. package/dist/triage/traces.js +305 -0
  58. package/dist/triage/types.d.ts +47 -0
  59. package/dist/triage/types.js +7 -0
  60. package/dist/types.d.ts +1387 -191
  61. package/dist/types.js +254 -2
  62. package/dist/utils/analysis-api.d.ts +108 -0
  63. package/dist/utils/analysis-api.js +194 -0
  64. package/dist/utils/config.js +1 -1
  65. package/dist/utils/git.d.ts +80 -0
  66. package/dist/utils/git.js +302 -0
  67. package/dist/utils/github-api.d.ts +83 -0
  68. package/dist/utils/github-api.js +266 -0
  69. package/dist/utils/hooks.d.ts +26 -0
  70. package/dist/utils/hooks.js +226 -0
  71. package/dist/utils/pending-state.d.ts +38 -0
  72. package/dist/utils/pending-state.js +86 -0
  73. package/dist/utils/secrets.js +3 -3
  74. package/dist/utils/skill.d.ts +1 -1
  75. package/dist/utils/skill.js +658 -1
  76. package/package.json +5 -3
@@ -0,0 +1,154 @@
1
+ # Submit PR via Haystack
2
+
3
+ **Your job**: Submit your changes as a PR using `haystack submit` instead of `gh pr create` or GitHub API.
4
+
5
+ ---
6
+
7
+ ## Why Use Haystack Submit?
8
+
9
+ When you create PRs through Haystack, they enter the **auto-merge queue**:
10
+
11
+ 1. **Haystack analyzes** your changes automatically
12
+ 2. **If approved**: PR is marked ready-to-merge and merged automatically
13
+ 3. **If issues found**: PR is routed for review with detailed feedback
14
+
15
+ This is faster than waiting for human review on every change.
16
+
17
+ ---
18
+
19
+ ## Command Reference
20
+
21
+ ```bash
22
+ # Default: Submit to auto-merge queue
23
+ haystack submit
24
+
25
+ # Request Haystack review (for complex/risky changes)
26
+ haystack submit --review
27
+
28
+ # With custom title
29
+ haystack submit --title "Fix authentication bug"
30
+
31
+ # Target different base branch
32
+ haystack submit --base develop
33
+
34
+ # With markdown PR body (write to file first to preserve formatting)
35
+ cat > /tmp/pr-body.md << 'EOF'
36
+ ## Summary
37
+ - Fixed authentication bug in session handling
38
+ - Added integration tests for token refresh
39
+
40
+ ## Test plan
41
+ - [x] Auth flow works end-to-end
42
+ EOF
43
+ haystack submit --body-file /tmp/pr-body.md
44
+ ```
45
+
46
+ > **Tip**: Always use `--body-file` instead of `--body` when the description contains markdown.
47
+ > The `--body` flag is a single CLI argument that mangles newlines and special characters (`#`, `*`, etc.).
48
+
49
+ ---
50
+
51
+ ## When to Use Each Mode
52
+
53
+ ### Auto-Merge (Default)
54
+
55
+ Use for straightforward changes that don't need human review:
56
+
57
+ - Bug fixes with clear scope
58
+ - Small features with tests
59
+ - Refactoring with no behavior change
60
+ - Documentation updates
61
+ - Dependency updates
62
+
63
+ ```bash
64
+ haystack submit
65
+ ```
66
+
67
+ ### Review Mode
68
+
69
+ > **Do NOT use `--review` unless the user explicitly asks for human review.**
70
+ > The default auto-merge queue already runs full Haystack analysis. Using `--review`
71
+ > blocks auto-merge until a human approves, which delays merging unnecessarily.
72
+
73
+ Use `--review` only when the user specifically requests human review:
74
+
75
+ - User says "get someone to review this" or "needs human eyes"
76
+ - User explicitly passes `--review` in their instructions
77
+
78
+ ```bash
79
+ haystack submit --review
80
+ ```
81
+
82
+ ---
83
+
84
+ ## Workflow
85
+
86
+ 1. **Make your changes** on a feature branch
87
+ 2. **Commit** your changes: `git add . && git commit -m "..."`
88
+ 3. **Submit** via Haystack: `haystack submit`
89
+
90
+ Haystack will:
91
+ - Create a `haystack/<your-branch>` branch
92
+ - Push to origin
93
+ - Create the PR with appropriate labels
94
+ - Trigger analysis automatically
95
+
96
+ ---
97
+
98
+ ## Prerequisites
99
+
100
+ Before using `haystack submit`, ensure you're logged in:
101
+
102
+ ```bash
103
+ haystack login
104
+ ```
105
+
106
+ This uses GitHub device flow - no tokens to copy/paste.
107
+
108
+ ---
109
+
110
+ ## Important Notes
111
+
112
+ - **Always use `haystack submit`** instead of `gh pr create` when working in Haystack-enabled repos
113
+ - The command creates a `haystack/` prefixed branch automatically
114
+ - Labels are applied automatically based on your chosen mode
115
+ - You don't need to push manually - the command handles it
116
+ - Use `haystack triage <pr>` to view full analysis results (rating, findings, agent fix prompts) for any PR at any time
117
+
118
+ ---
119
+
120
+ ## Example Session
121
+
122
+ ```
123
+ Agent: I've fixed the authentication bug. Let me submit this as a PR.
124
+
125
+ $ haystack submit --title "Fix session expiry handling"
126
+
127
+ Haystack Submit
128
+
129
+ ✓ Authenticated
130
+ ✓ Repository: acme/webapp
131
+ Current branch: fix-auth
132
+ Base branch: main
133
+ Submission type: Auto-merge queue
134
+
135
+ Creating branch haystack/fix-auth...
136
+ ✓ Created branch: haystack/fix-auth
137
+ ✓ Branch pushed
138
+ ✓ Pull request created: #142
139
+ ✓ Applied label: haystack:auto-merge
140
+
141
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
142
+
143
+ Pull Request Created!
144
+
145
+ URL: https://github.com/acme/webapp/pull/142
146
+ Title: Fix session expiry handling
147
+ Branch: haystack/fix-auth → main
148
+ Type: Auto-merge queue
149
+ Label: haystack:auto-merge
150
+
151
+ Haystack will analyze this PR and:
152
+ • Auto-merge if approved
153
+ • Route for review if issues found
154
+ ```
@@ -0,0 +1,42 @@
1
+ # Haystack Verification
2
+
3
+ This project uses Haystack for PR verification. Configuration is in `.haystack.yml`.
4
+
5
+ ## For the AI Agent
6
+
7
+ When making changes to this codebase:
8
+
9
+ 1. **After completing changes**, run verification:
10
+ - Use the `haystack_get_verify_targets` tool to see what URLs to check
11
+ - Use `haystack_get_auth_config` to get auth bypass instructions
12
+ - Start the dev server with auth bypass env vars
13
+ - Navigate to each verification URL and confirm it loads
14
+
15
+ 2. **If `.haystack.yml` doesn't exist**, help the user create one:
16
+ - Use `haystack_setup_start` to begin the wizard
17
+ - Ask each question and call `haystack_setup_answer` with responses
18
+
19
+ 3. **Fixture data** is available via `haystack_load_fixtures`:
20
+ - This loads test data from staging/local files
21
+ - Use this instead of real API calls when verifying
22
+
23
+ ## Quick Reference
24
+
25
+ ```yaml
26
+ # .haystack.yml structure
27
+ dev:
28
+ command: pnpm dev # How to start dev server
29
+ port: 3000 # What port it runs on
30
+
31
+ auth:
32
+ strategy: bypass # How to skip auth
33
+ env: "SKIP_AUTH=true" # Env var to set
34
+
35
+ fixtures: # Test data sources
36
+ - pattern: "/api/*"
37
+ source: "https://staging.example.com/api"
38
+
39
+ verify: # Pages to check
40
+ - url: "/"
41
+ wait_for: "#app"
42
+ ```
@@ -0,0 +1,193 @@
1
+ # =============================================================================
2
+ # Haystack Verification Configuration
3
+ # =============================================================================
4
+ #
5
+ # This file tells Haystack how to verify that PRs actually work.
6
+ # When a PR is opened, Haystack spins up a sandbox with your code and
7
+ # runs these verification flows to ensure changes work correctly.
8
+ #
9
+ # For setup help, run: /verify setup
10
+ # =============================================================================
11
+
12
+ name: my-saas-app
13
+
14
+ # -----------------------------------------------------------------------------
15
+ # Dev Server - How to run your app
16
+ # -----------------------------------------------------------------------------
17
+ dev:
18
+ command: pnpm dev
19
+ port: 3000
20
+ ready_pattern: "ready in" # Pattern in stdout that means server is ready
21
+ cwd: "." # Working directory (relative to repo root)
22
+ env:
23
+ NODE_ENV: development
24
+
25
+ # -----------------------------------------------------------------------------
26
+ # Authentication - How to bypass/mock auth in the sandbox
27
+ # -----------------------------------------------------------------------------
28
+ auth:
29
+ strategy: bypass
30
+ env: "SKIP_AUTH=true"
31
+
32
+ # Other options:
33
+ # strategy: mock_token
34
+ # env: "AUTH_TOKEN" # Set actual value as Haystack secret
35
+
36
+ # strategy: test_account
37
+ # username_env: "TEST_USER"
38
+ # password_env: "TEST_PASS"
39
+ # login_url: "/login"
40
+
41
+ # -----------------------------------------------------------------------------
42
+ # Fixtures - Where to get test data (so sandbox doesn't need real data)
43
+ # -----------------------------------------------------------------------------
44
+ fixtures:
45
+ # Pull user data from staging API
46
+ - pattern: "/api/users/*"
47
+ source: "https://staging.myapp.com/api/users"
48
+ headers:
49
+ Authorization: "Bearer $STAGING_API_TOKEN"
50
+
51
+ # Use local fixture file for config
52
+ - pattern: "/api/config"
53
+ source: "file://fixtures/config.json"
54
+
55
+ # Generate dashboard data dynamically
56
+ - pattern: "/api/dashboard"
57
+ source: "script://fixtures/generate-dashboard.ts"
58
+
59
+ # Let public endpoints go through
60
+ - pattern: "/api/public/*"
61
+ source: passthrough
62
+
63
+ # -----------------------------------------------------------------------------
64
+ # Verification Flows - What to actually test
65
+ # -----------------------------------------------------------------------------
66
+ # These are the flows the agent executes in the sandbox to verify PRs work.
67
+ # Think of them like E2E tests, but run by an AI that can adapt and report.
68
+
69
+ flows:
70
+ # ---------------------------------------------------------------------------
71
+ # Basic smoke test - runs on every PR
72
+ # ---------------------------------------------------------------------------
73
+ - name: "Homepage loads"
74
+ description: "Verify the homepage renders without errors"
75
+ trigger: always
76
+ steps:
77
+ - action: navigate
78
+ url: "/"
79
+ - action: wait_for
80
+ selector: "#app"
81
+ timeout_ms: 10000
82
+ - action: assert_no_errors
83
+ error_selectors:
84
+ - ".error-toast"
85
+ - ".error-modal"
86
+ - "[data-testid='error']"
87
+ - action: screenshot
88
+ name: "homepage"
89
+
90
+ # ---------------------------------------------------------------------------
91
+ # Dashboard flow - runs when dashboard files change
92
+ # ---------------------------------------------------------------------------
93
+ - name: "Dashboard functionality"
94
+ description: "Verify dashboard loads and displays data correctly"
95
+ trigger: on_change
96
+ watch_patterns:
97
+ - "src/components/dashboard/**"
98
+ - "src/pages/dashboard.tsx"
99
+ steps:
100
+ - action: navigate
101
+ url: "/dashboard"
102
+ - action: wait_for
103
+ selector: ".dashboard-grid"
104
+ - action: assert_exists
105
+ selector: ".metric-card"
106
+ message: "Dashboard should display metric cards"
107
+ - action: assert_text
108
+ selector: ".welcome-message"
109
+ contains: "Welcome"
110
+ - action: screenshot
111
+ name: "dashboard-loaded"
112
+ # Test interactivity
113
+ - action: click
114
+ selector: "[data-testid='refresh-button']"
115
+ description: "Click refresh button"
116
+ - action: wait_network_idle
117
+ timeout_ms: 5000
118
+ - action: assert_no_errors
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # Settings flow - test form interactions
122
+ # ---------------------------------------------------------------------------
123
+ - name: "Settings page"
124
+ description: "Verify settings can be viewed and saved"
125
+ trigger: on_change
126
+ watch_patterns:
127
+ - "src/components/settings/**"
128
+ - "src/pages/settings.tsx"
129
+ steps:
130
+ - action: navigate
131
+ url: "/settings"
132
+ - action: wait_for
133
+ selector: "form.settings-form"
134
+ - action: screenshot
135
+ name: "settings-initial"
136
+ # Toggle dark mode
137
+ - action: click
138
+ selector: "#dark-mode-toggle"
139
+ - action: assert_exists
140
+ selector: "body.dark-mode"
141
+ message: "Dark mode should be applied"
142
+ - action: screenshot
143
+ name: "settings-dark-mode"
144
+ # Test form input
145
+ - action: type
146
+ selector: "#display-name"
147
+ text: "Test User"
148
+ - action: click
149
+ selector: "button[type='submit']"
150
+ - action: wait_network_idle
151
+ - action: assert_no_errors
152
+
153
+ # ---------------------------------------------------------------------------
154
+ # Checkout flow - critical path, always run
155
+ # ---------------------------------------------------------------------------
156
+ - name: "Checkout flow"
157
+ description: "Verify the complete checkout process works"
158
+ trigger: always
159
+ steps:
160
+ - action: navigate
161
+ url: "/products"
162
+ - action: wait_for
163
+ selector: ".product-grid"
164
+ - action: click
165
+ selector: ".product-card:first-child .add-to-cart"
166
+ description: "Add first product to cart"
167
+ - action: wait_for
168
+ selector: ".cart-count"
169
+ - action: assert_text
170
+ selector: ".cart-count"
171
+ contains: "1"
172
+ - action: navigate
173
+ url: "/cart"
174
+ - action: wait_for
175
+ selector: ".cart-items"
176
+ - action: click
177
+ selector: ".checkout-button"
178
+ - action: wait_for
179
+ selector: ".checkout-form"
180
+ - action: screenshot
181
+ name: "checkout-form"
182
+ compare_baseline: true # Visual regression check
183
+
184
+ # -----------------------------------------------------------------------------
185
+ # Global Settings
186
+ # -----------------------------------------------------------------------------
187
+ settings:
188
+ default_timeout_ms: 10000
189
+ error_selectors:
190
+ - ".error-toast"
191
+ - ".error-boundary"
192
+ - "[role='alert'][aria-live='assertive']"
193
+ screenshot_each_step: false # Set true for debugging
@@ -0,0 +1,19 @@
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>;
@@ -0,0 +1,217 @@
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
+ }
@@ -1,19 +1,25 @@
1
1
  /**
2
- * Config commands - manage user preferences stored on Haystack Platform
3
- */
4
- /**
5
- * Get current sandbox status
2
+ * Config commands - manage preferences
3
+ *
4
+ * Repo-level preferences (sandbox_enabled, auto_merge) are stored in .haystack.json.
5
+ * User-level preferences (agentic_tool) are stored on the Haystack Platform API.
6
6
  */
7
+ type AgenticTool = 'opencode' | 'claude-code' | 'codex';
7
8
  export declare function getSandboxStatus(): Promise<void>;
8
- /**
9
- * Enable sandbox mode
10
- */
11
9
  export declare function enableSandbox(): Promise<void>;
12
- /**
13
- * Disable sandbox mode
14
- */
15
10
  export declare function disableSandbox(): Promise<void>;
11
+ export declare function handleSandbox(action?: string): Promise<void>;
12
+ export declare function getAgenticToolStatus(): Promise<void>;
13
+ export declare function setAgenticTool(tool: AgenticTool): Promise<void>;
14
+ export declare function handleAgenticTool(tool?: string): Promise<void>;
16
15
  /**
17
- * Handle sandbox subcommand
16
+ * Check if auto-merge is enabled for the current repo.
17
+ * Returns false if .haystack.json is missing or on error.
18
18
  */
19
- export declare function handleSandbox(action?: string): Promise<void>;
19
+ export declare function isAutoMergeEnabled(): Promise<boolean>;
20
+ export declare function getAutoMergeStatus(): Promise<void>;
21
+ export declare function enableAutoMerge(): Promise<void>;
22
+ export declare function disableAutoMerge(): Promise<void>;
23
+ export declare function handleAutoMerge(action?: string): Promise<void>;
24
+ export declare function handleWaitForReviewers(action?: string, reviewers?: string[]): Promise<void>;
25
+ export {};