@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.
- package/README.md +105 -12
- package/dist/assets/hooks/agent-context/detect.ts +136 -0
- package/dist/assets/hooks/agent-context/format.ts +99 -0
- package/dist/assets/hooks/agent-context/index.ts +39 -0
- package/dist/assets/hooks/agent-context/parsers/claude.ts +253 -0
- package/dist/assets/hooks/agent-context/parsers/gemini.ts +155 -0
- package/dist/assets/hooks/agent-context/parsers/opencode.ts +174 -0
- package/dist/assets/hooks/agent-context/tsconfig.json +13 -0
- package/dist/assets/hooks/agent-context/types.ts +58 -0
- package/dist/assets/hooks/llm-rules-template.md +56 -0
- package/dist/assets/hooks/package.json +11 -0
- package/dist/assets/hooks/scripts/commit-msg.sh +4 -0
- package/dist/assets/hooks/scripts/post-commit.sh +4 -0
- package/dist/assets/hooks/scripts/pre-commit.sh +92 -0
- package/dist/assets/hooks/scripts/pre-push.sh +25 -0
- package/dist/assets/hooks/scripts/prepare-commit-msg.sh +3 -0
- package/dist/assets/hooks/truncation-checker/ast-analyzer.ts +528 -0
- package/dist/assets/hooks/truncation-checker/index.ts +595 -0
- package/dist/assets/hooks/truncation-checker/tsconfig.json +13 -0
- package/dist/assets/skills/prepare-haystack.md +323 -0
- package/dist/assets/skills/secrets.md +164 -0
- package/dist/assets/skills/setup-external-sandbox.md +243 -0
- package/dist/assets/skills/setup-haystack.md +639 -0
- package/dist/assets/skills/submit.md +154 -0
- package/dist/assets/templates/CLAUDE.md.snippet +42 -0
- package/dist/assets/templates/haystack.yml +193 -0
- package/dist/commands/check-pending.d.ts +19 -0
- package/dist/commands/check-pending.js +217 -0
- package/dist/commands/config.d.ts +18 -12
- package/dist/commands/config.js +327 -52
- package/dist/commands/hooks.d.ts +17 -0
- package/dist/commands/hooks.js +269 -0
- package/dist/commands/install-session-hooks.d.ts +16 -0
- package/dist/commands/install-session-hooks.js +302 -0
- package/dist/commands/login.js +1 -1
- package/dist/commands/policy.d.ts +31 -0
- package/dist/commands/policy.js +365 -0
- package/dist/commands/skills.d.ts +8 -0
- package/dist/commands/skills.js +80 -0
- package/dist/commands/submit.d.ts +22 -0
- package/dist/commands/submit.js +428 -0
- package/dist/commands/triage.d.ts +16 -0
- package/dist/commands/triage.js +354 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +317 -2
- package/dist/tools/detect.d.ts +50 -0
- package/dist/tools/detect.js +853 -0
- package/dist/tools/fixtures.d.ts +38 -0
- package/dist/tools/fixtures.js +199 -0
- package/dist/tools/setup.d.ts +43 -0
- package/dist/tools/setup.js +597 -0
- package/dist/triage/prompts.d.ts +31 -0
- package/dist/triage/prompts.js +266 -0
- package/dist/triage/runner.d.ts +21 -0
- package/dist/triage/runner.js +325 -0
- package/dist/triage/traces.d.ts +20 -0
- package/dist/triage/traces.js +305 -0
- package/dist/triage/types.d.ts +47 -0
- package/dist/triage/types.js +7 -0
- package/dist/types.d.ts +1387 -191
- package/dist/types.js +254 -2
- package/dist/utils/analysis-api.d.ts +108 -0
- package/dist/utils/analysis-api.js +194 -0
- package/dist/utils/config.js +1 -1
- package/dist/utils/git.d.ts +80 -0
- package/dist/utils/git.js +302 -0
- package/dist/utils/github-api.d.ts +83 -0
- package/dist/utils/github-api.js +266 -0
- package/dist/utils/hooks.d.ts +26 -0
- package/dist/utils/hooks.js +226 -0
- package/dist/utils/pending-state.d.ts +38 -0
- package/dist/utils/pending-state.js +86 -0
- package/dist/utils/secrets.js +3 -3
- package/dist/utils/skill.d.ts +1 -1
- package/dist/utils/skill.js +658 -1
- package/package.json +5 -3
package/dist/types.js
CHANGED
|
@@ -1,6 +1,258 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Haystack CLI Types
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Zod schemas for .haystack.json configuration + CLI-only types.
|
|
5
|
+
* The config schema matches what agent/modal/sandbox.py expects to read.
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// VERIFICATION COMMANDS
|
|
10
|
+
// =============================================================================
|
|
11
|
+
const VerificationCommandSchema = z.object({
|
|
12
|
+
/** Human-readable name (e.g., "Build", "Lint", "Typecheck") */
|
|
13
|
+
name: z.string(),
|
|
14
|
+
/** The shell command to run (e.g., "pnpm build", "pnpm lint") */
|
|
15
|
+
run: z.string(),
|
|
16
|
+
});
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// FIXTURE SOURCES
|
|
19
|
+
// =============================================================================
|
|
20
|
+
const FixtureEntrySchema = z.object({
|
|
21
|
+
/** Where to load fixture data from */
|
|
22
|
+
source: z.string().refine((val) => val === 'passthrough' ||
|
|
23
|
+
val.startsWith('file://') ||
|
|
24
|
+
val.startsWith('https://') ||
|
|
25
|
+
val.startsWith('http://') ||
|
|
26
|
+
val.startsWith('s3://') ||
|
|
27
|
+
val.startsWith('r2://') ||
|
|
28
|
+
val.startsWith('script://'), {
|
|
29
|
+
message: 'Source must be passthrough, or start with file://, https://, http://, s3://, r2://, or script://',
|
|
30
|
+
}),
|
|
31
|
+
/** Headers to include when fetching from remote source */
|
|
32
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
33
|
+
/** Cookies to include when fetching from remote source */
|
|
34
|
+
cookies: z.string().optional(),
|
|
35
|
+
});
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// DEV SERVER
|
|
38
|
+
// =============================================================================
|
|
39
|
+
const DevServerSchema = z.object({
|
|
40
|
+
/** Command to start dev server (e.g., "pnpm dev") */
|
|
41
|
+
command: z.string(),
|
|
42
|
+
/** Port the dev server runs on */
|
|
43
|
+
port: z.number(),
|
|
44
|
+
/** Pattern in stdout that indicates server is ready (e.g., "Local:") */
|
|
45
|
+
ready_pattern: z.string().optional(),
|
|
46
|
+
/** Environment variables to set when starting the server */
|
|
47
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
48
|
+
});
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// BABYSITTER CONFIGURATION
|
|
51
|
+
// =============================================================================
|
|
52
|
+
/** Valid AI reviewer source identifiers that the merge queue can wait for */
|
|
53
|
+
export const AI_REVIEWER_SOURCES = [
|
|
54
|
+
'cursor',
|
|
55
|
+
'coderabbit',
|
|
56
|
+
'copilot',
|
|
57
|
+
'greptile',
|
|
58
|
+
'sourcery',
|
|
59
|
+
'qodo',
|
|
60
|
+
'codeant',
|
|
61
|
+
'ellipsis',
|
|
62
|
+
'korbit',
|
|
63
|
+
'panto',
|
|
64
|
+
'bito',
|
|
65
|
+
'graphite',
|
|
66
|
+
'deepsource',
|
|
67
|
+
'snyk',
|
|
68
|
+
'codacy',
|
|
69
|
+
'sonarcloud',
|
|
70
|
+
'sweep',
|
|
71
|
+
'codeguru',
|
|
72
|
+
'codereviewer',
|
|
73
|
+
'codium',
|
|
74
|
+
];
|
|
75
|
+
export const AI_REVIEWER_DISPLAY_NAMES = {
|
|
76
|
+
cursor: 'Cursor BugBot',
|
|
77
|
+
coderabbit: 'CodeRabbit',
|
|
78
|
+
copilot: 'GitHub Copilot',
|
|
79
|
+
greptile: 'Greptile',
|
|
80
|
+
sourcery: 'Sourcery',
|
|
81
|
+
qodo: 'Qodo',
|
|
82
|
+
codeant: 'CodeAnt AI',
|
|
83
|
+
ellipsis: 'Ellipsis',
|
|
84
|
+
korbit: 'Korbit AI',
|
|
85
|
+
panto: 'Panto AI',
|
|
86
|
+
bito: 'Bito AI',
|
|
87
|
+
graphite: 'Graphite',
|
|
88
|
+
deepsource: 'DeepSource',
|
|
89
|
+
snyk: 'Snyk',
|
|
90
|
+
codacy: 'Codacy',
|
|
91
|
+
sonarcloud: 'SonarCloud',
|
|
92
|
+
sweep: 'Sweep AI',
|
|
93
|
+
codeguru: 'Amazon CodeGuru',
|
|
94
|
+
codereviewer: 'CodeReviewBot',
|
|
95
|
+
codium: 'Codium',
|
|
96
|
+
};
|
|
97
|
+
const AIReviewerSourceSchema = z.enum(AI_REVIEWER_SOURCES);
|
|
98
|
+
const BabysitterConfigSchema = z.object({
|
|
99
|
+
/** Enable the Haystack auto-merge queue */
|
|
100
|
+
merge_queue: z.boolean().optional(),
|
|
101
|
+
/** Automatically rebase PRs that develop merge conflicts after pushes to the base branch */
|
|
102
|
+
auto_resolve_conflicts: z.boolean().optional(),
|
|
103
|
+
/** Grace period configuration before auto-merging */
|
|
104
|
+
merge_debounce: z.object({
|
|
105
|
+
/** Default wait time in minutes before auto-merging */
|
|
106
|
+
default_minutes: z.number().optional(),
|
|
107
|
+
/** Per-author wait time overrides (author login → minutes) */
|
|
108
|
+
authors: z.record(z.string(), z.number()).optional(),
|
|
109
|
+
}).optional(),
|
|
110
|
+
/** AI reviewer sources to wait for before starting the merge grace period */
|
|
111
|
+
expected_ai_reviewers: z.array(AIReviewerSourceSchema).optional(),
|
|
112
|
+
});
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// SERVICE (for monorepos)
|
|
115
|
+
// =============================================================================
|
|
116
|
+
const ServiceSchema = z.object({
|
|
117
|
+
/** Root directory relative to repo root */
|
|
118
|
+
root: z.string().optional(),
|
|
119
|
+
/** Command to start the service */
|
|
120
|
+
command: z.string(),
|
|
121
|
+
/** Port the service runs on (not required for batch jobs) */
|
|
122
|
+
port: z.number().optional(),
|
|
123
|
+
/** Pattern in stdout that indicates service is ready */
|
|
124
|
+
ready_pattern: z.string().optional(),
|
|
125
|
+
/** Environment variables to set */
|
|
126
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
127
|
+
/** Service type: 'server' (default) or 'batch' (run once, no port) */
|
|
128
|
+
type: z.enum(['server', 'batch']).optional(),
|
|
129
|
+
/** Services that must be started before this service */
|
|
130
|
+
depends_on: z.array(z.string()).optional(),
|
|
131
|
+
});
|
|
132
|
+
// =============================================================================
|
|
133
|
+
// VERIFICATION
|
|
134
|
+
// =============================================================================
|
|
135
|
+
const VerificationSchema = z.object({
|
|
136
|
+
/** Commands to run for verification (build, lint, test, etc.) */
|
|
137
|
+
commands: z.array(VerificationCommandSchema).optional(),
|
|
138
|
+
/** Fixture data sources keyed by endpoint pattern */
|
|
139
|
+
fixtures: z.record(z.string(), FixtureEntrySchema).optional(),
|
|
140
|
+
});
|
|
141
|
+
// =============================================================================
|
|
142
|
+
// AUTH CONFIGURATION
|
|
143
|
+
// =============================================================================
|
|
144
|
+
const AuthStrategySchema = z.enum(['bypass', 'fixture', 'token', 'test_account']);
|
|
145
|
+
const TokenInjectionSchema = z.object({
|
|
146
|
+
type: z.enum(['cookie', 'localStorage', 'sessionStorage', 'header']),
|
|
147
|
+
name: z.string(),
|
|
148
|
+
secret: z.string(),
|
|
149
|
+
cookie_options: z
|
|
150
|
+
.object({
|
|
151
|
+
path: z.string().optional(),
|
|
152
|
+
domain: z.string().optional(),
|
|
153
|
+
secure: z.boolean().optional(),
|
|
154
|
+
httpOnly: z.boolean().optional(),
|
|
155
|
+
sameSite: z.enum(['Strict', 'Lax', 'None']).optional(),
|
|
156
|
+
})
|
|
157
|
+
.optional(),
|
|
158
|
+
});
|
|
159
|
+
const LoginStepSchema = z.object({
|
|
160
|
+
action: z.enum(['navigate', 'type', 'click', 'wait', 'submit']),
|
|
161
|
+
url: z.string().optional(),
|
|
162
|
+
selector: z.string().optional(),
|
|
163
|
+
value: z.string().optional(),
|
|
164
|
+
timeout: z.number().optional(),
|
|
165
|
+
});
|
|
166
|
+
const TestAccountSchema = z.object({
|
|
167
|
+
credentials_secret: z.string(),
|
|
168
|
+
login_flow: z.array(LoginStepSchema),
|
|
169
|
+
success_selector: z.string().optional(),
|
|
170
|
+
failure_selector: z.string().optional(),
|
|
171
|
+
});
|
|
172
|
+
const AuthConfigSchema = z.object({
|
|
173
|
+
strategy: AuthStrategySchema.default('bypass'),
|
|
174
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
175
|
+
fixture: z
|
|
176
|
+
.object({
|
|
177
|
+
pattern: z.string(),
|
|
178
|
+
source: z.string(),
|
|
179
|
+
})
|
|
180
|
+
.optional(),
|
|
181
|
+
token: TokenInjectionSchema.optional(),
|
|
182
|
+
test_account: TestAccountSchema.optional(),
|
|
183
|
+
fallback: AuthStrategySchema.optional(),
|
|
184
|
+
});
|
|
185
|
+
// =============================================================================
|
|
186
|
+
// DATABASE CONFIGURATION
|
|
187
|
+
// =============================================================================
|
|
188
|
+
const DatabaseStrategySchema = z.enum(['fixture', 'memory', 'seed', 'docker', 'remote']);
|
|
189
|
+
const DockerServiceSchema = z.object({
|
|
190
|
+
image: z.string(),
|
|
191
|
+
port: z.number(),
|
|
192
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
193
|
+
health_check: z.string().optional(),
|
|
194
|
+
health_timeout: z.number().optional(),
|
|
195
|
+
});
|
|
196
|
+
const DatabaseConfigBaseSchema = z.object({
|
|
197
|
+
strategy: DatabaseStrategySchema.default('fixture'),
|
|
198
|
+
env: z.record(z.string(), z.string()).optional(),
|
|
199
|
+
seed_command: z.string().optional(),
|
|
200
|
+
migrate_command: z.string().optional(),
|
|
201
|
+
services: z.array(DockerServiceSchema).optional(),
|
|
202
|
+
read_only: z.boolean().optional(),
|
|
203
|
+
ready_timeout: z.number().optional(),
|
|
204
|
+
});
|
|
205
|
+
const DatabaseConfigSchema = DatabaseConfigBaseSchema.extend({
|
|
206
|
+
fallback: DatabaseConfigBaseSchema.optional(),
|
|
207
|
+
});
|
|
208
|
+
// =============================================================================
|
|
209
|
+
// AGENTIC CONFIGURATION
|
|
210
|
+
// =============================================================================
|
|
211
|
+
const AgenticToolSchema = z.enum(['opencode', 'claude-code', 'codex']);
|
|
212
|
+
const AgenticSchema = z.object({
|
|
213
|
+
tool: AgenticToolSchema.optional(),
|
|
214
|
+
});
|
|
215
|
+
// =============================================================================
|
|
216
|
+
// NETWORK CONFIGURATION
|
|
217
|
+
// =============================================================================
|
|
218
|
+
const NetworkConfigSchema = z.object({
|
|
219
|
+
/** Additional hosts/domains to allow egress to */
|
|
220
|
+
allow: z.array(z.string()).optional(),
|
|
221
|
+
});
|
|
222
|
+
// =============================================================================
|
|
223
|
+
// SECRET DECLARATION
|
|
224
|
+
// =============================================================================
|
|
225
|
+
const SecretDeclarationSchema = z.object({
|
|
226
|
+
description: z.string().optional(),
|
|
227
|
+
required: z.boolean().optional(),
|
|
228
|
+
services: z.array(z.string()).optional(),
|
|
229
|
+
});
|
|
230
|
+
// =============================================================================
|
|
231
|
+
// MAIN CONFIG
|
|
232
|
+
// =============================================================================
|
|
233
|
+
export const HaystackConfigSchema = z.object({
|
|
234
|
+
/** Config version (must be "1") */
|
|
235
|
+
version: z.literal('1'),
|
|
236
|
+
/** Project name (for display) */
|
|
237
|
+
name: z.string().optional(),
|
|
238
|
+
/** Package manager override (auto-detected if not specified) */
|
|
239
|
+
package_manager: z.enum(['npm', 'pnpm', 'yarn', 'bun']).optional(),
|
|
240
|
+
/** Development server configuration */
|
|
241
|
+
dev_server: DevServerSchema.optional(),
|
|
242
|
+
/** Multiple services (for monorepos) */
|
|
243
|
+
services: z.record(z.string(), ServiceSchema).optional(),
|
|
244
|
+
/** Verification configuration */
|
|
245
|
+
verification: VerificationSchema.optional(),
|
|
246
|
+
/** Auth configuration for sandbox verification */
|
|
247
|
+
auth: AuthConfigSchema.optional(),
|
|
248
|
+
/** Agentic configuration (project-level override) */
|
|
249
|
+
agentic: AgenticSchema.optional(),
|
|
250
|
+
/** Database configuration for sandbox verification */
|
|
251
|
+
database: DatabaseConfigSchema.optional(),
|
|
252
|
+
/** Network egress configuration */
|
|
253
|
+
network: NetworkConfigSchema.optional(),
|
|
254
|
+
/** Secrets required by this project */
|
|
255
|
+
secrets: z.record(z.string(), SecretDeclarationSchema).optional(),
|
|
256
|
+
/** Babysitter configuration for autonomous PR maintenance */
|
|
257
|
+
babysitter: BabysitterConfigSchema.optional(),
|
|
258
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Haystack Analysis API client.
|
|
3
|
+
*
|
|
4
|
+
* Polls the result endpoint by PR identifier to check if analysis is ready,
|
|
5
|
+
* then fetches detailed results (bug detection + rule violations).
|
|
6
|
+
*
|
|
7
|
+
* Analysis is triggered automatically by the GitHub App webhook when a PR
|
|
8
|
+
* is created or updated — the CLI does not need to trigger it directly.
|
|
9
|
+
*/
|
|
10
|
+
export interface AnalysisVerdict {
|
|
11
|
+
state: 'good-to-merge' | 'needs-input' | 'failed';
|
|
12
|
+
bugCount: number;
|
|
13
|
+
warningCount: number;
|
|
14
|
+
ruleViolationCount: number;
|
|
15
|
+
summary: string;
|
|
16
|
+
issues: AnalysisIssue[];
|
|
17
|
+
reviewUrl: string;
|
|
18
|
+
/** Whether the PR was auto-merged after analysis */
|
|
19
|
+
autoMerged?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export interface AnalysisIssue {
|
|
22
|
+
file: string;
|
|
23
|
+
line?: number;
|
|
24
|
+
message: string;
|
|
25
|
+
severity: 'error' | 'warning' | 'info';
|
|
26
|
+
source: 'bug-detection' | 'rule-violation';
|
|
27
|
+
}
|
|
28
|
+
export interface SynthesisDisplayIssue {
|
|
29
|
+
category: string;
|
|
30
|
+
summary: string;
|
|
31
|
+
detail: string;
|
|
32
|
+
agentFixPrompt?: string;
|
|
33
|
+
source?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface VerifiedBug {
|
|
36
|
+
originalTitle: string;
|
|
37
|
+
verified: boolean;
|
|
38
|
+
assessedSeverity: 'low' | 'medium' | 'high' | 'critical';
|
|
39
|
+
likelihood?: 'rare' | 'uncommon' | 'common';
|
|
40
|
+
rationale: string;
|
|
41
|
+
source?: {
|
|
42
|
+
type: string;
|
|
43
|
+
commentId?: number;
|
|
44
|
+
author?: string;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface HumanReviewReason {
|
|
48
|
+
type: string;
|
|
49
|
+
severity: string;
|
|
50
|
+
reason: string;
|
|
51
|
+
source?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface RatingSynthesis {
|
|
54
|
+
haystackRating: 1 | 2 | 3 | 4 | 5;
|
|
55
|
+
analysisVerdict?: 'clean' | 'has-issues';
|
|
56
|
+
synthesis: string;
|
|
57
|
+
synthesisDisplay?: SynthesisDisplayIssue[];
|
|
58
|
+
verifiedBugs: VerifiedBug[];
|
|
59
|
+
reviewRecommendation: {
|
|
60
|
+
canAutoMerge: boolean;
|
|
61
|
+
};
|
|
62
|
+
needsHumanReview?: boolean;
|
|
63
|
+
humanReviewReasons?: HumanReviewReason[];
|
|
64
|
+
phase?: 1 | 2;
|
|
65
|
+
verificationImpact?: string;
|
|
66
|
+
verificationConfidence?: {
|
|
67
|
+
level: 'high' | 'medium' | 'low' | 'none';
|
|
68
|
+
summary: string;
|
|
69
|
+
};
|
|
70
|
+
verification?: {
|
|
71
|
+
success: boolean;
|
|
72
|
+
lint?: {
|
|
73
|
+
success: boolean;
|
|
74
|
+
output?: string;
|
|
75
|
+
};
|
|
76
|
+
test?: {
|
|
77
|
+
success: boolean;
|
|
78
|
+
output?: string;
|
|
79
|
+
};
|
|
80
|
+
build?: {
|
|
81
|
+
success: boolean;
|
|
82
|
+
output?: string;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export type AnalysisReadyResult = {
|
|
87
|
+
status: 'ready';
|
|
88
|
+
} | {
|
|
89
|
+
status: 'pending';
|
|
90
|
+
} | {
|
|
91
|
+
status: 'error';
|
|
92
|
+
message: string;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Check if analysis results are available for a PR by polling the result endpoint.
|
|
96
|
+
* Returns 'ready' if complete, 'pending' if not yet available, or 'error' for server failures.
|
|
97
|
+
*/
|
|
98
|
+
export declare function checkAnalysisReady(owner: string, repo: string, prNumber: number, token?: string): Promise<AnalysisReadyResult>;
|
|
99
|
+
/**
|
|
100
|
+
* Fetch analysis results once complete. Combines bug detection and rule violations.
|
|
101
|
+
*/
|
|
102
|
+
export declare function fetchAnalysisResults(owner: string, repo: string, prNumber: number, token?: string): Promise<AnalysisVerdict>;
|
|
103
|
+
/**
|
|
104
|
+
* Fetch the full rating synthesis for a PR.
|
|
105
|
+
* This is the rich analysis data shown on the Haystack feed — includes
|
|
106
|
+
* verdict, rating, structured findings, verified bugs, and agent fix prompts.
|
|
107
|
+
*/
|
|
108
|
+
export declare function fetchRatingSynthesis(owner: string, repo: string, prNumber: number, token?: string): Promise<RatingSynthesis | null>;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Haystack Analysis API client.
|
|
3
|
+
*
|
|
4
|
+
* Polls the result endpoint by PR identifier to check if analysis is ready,
|
|
5
|
+
* then fetches detailed results (bug detection + rule violations).
|
|
6
|
+
*
|
|
7
|
+
* Analysis is triggered automatically by the GitHub App webhook when a PR
|
|
8
|
+
* is created or updated — the CLI does not need to trigger it directly.
|
|
9
|
+
*/
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Constants
|
|
12
|
+
// ============================================================================
|
|
13
|
+
const PROD_API_BASE = 'https://8z9ssbamdj.execute-api.us-west-2.amazonaws.com/prod';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// API helpers
|
|
16
|
+
// ============================================================================
|
|
17
|
+
function apiHeaders(token) {
|
|
18
|
+
const headers = {
|
|
19
|
+
'Cache-Control': 'no-cache',
|
|
20
|
+
'User-Agent': 'Haystack-CLI',
|
|
21
|
+
};
|
|
22
|
+
if (token) {
|
|
23
|
+
headers['X-GitHub-Token'] = token;
|
|
24
|
+
}
|
|
25
|
+
return headers;
|
|
26
|
+
}
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// API functions
|
|
29
|
+
// ============================================================================
|
|
30
|
+
/**
|
|
31
|
+
* Check if analysis results are available for a PR by polling the result endpoint.
|
|
32
|
+
* Returns 'ready' if complete, 'pending' if not yet available, or 'error' for server failures.
|
|
33
|
+
*/
|
|
34
|
+
export async function checkAnalysisReady(owner, repo, prNumber, token) {
|
|
35
|
+
const url = `${PROD_API_BASE}/v3/result/${owner}/${repo}/${prNumber}`;
|
|
36
|
+
let response;
|
|
37
|
+
try {
|
|
38
|
+
response = await fetch(url, {
|
|
39
|
+
headers: apiHeaders(token),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return { status: 'error', message: err.message };
|
|
44
|
+
}
|
|
45
|
+
if (response.status === 404)
|
|
46
|
+
return { status: 'pending' };
|
|
47
|
+
if (!response.ok) {
|
|
48
|
+
return { status: 'error', message: `HTTP ${response.status}` };
|
|
49
|
+
}
|
|
50
|
+
let data;
|
|
51
|
+
try {
|
|
52
|
+
data = await response.json();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return { status: 'error', message: 'Invalid JSON response' };
|
|
56
|
+
}
|
|
57
|
+
return data.status === 'COMPLETED' ? { status: 'ready' } : { status: 'pending' };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Fetch analysis results once complete. Combines bug detection and rule violations.
|
|
61
|
+
*/
|
|
62
|
+
export async function fetchAnalysisResults(owner, repo, prNumber, token) {
|
|
63
|
+
const prIdentifier = `${owner}/${repo}#${prNumber}`;
|
|
64
|
+
const encodedId = encodeURIComponent(prIdentifier);
|
|
65
|
+
const reviewUrl = `https://haystackeditor.com/review/${owner}/${repo}/${prNumber}`;
|
|
66
|
+
const issues = [];
|
|
67
|
+
// Fetch bug detection
|
|
68
|
+
try {
|
|
69
|
+
const bugUrl = `${PROD_API_BASE}/v2/analysis/${encodedId}/bug_detection.json`;
|
|
70
|
+
const bugResp = await fetch(bugUrl, {
|
|
71
|
+
headers: apiHeaders(token),
|
|
72
|
+
});
|
|
73
|
+
if (bugResp.ok) {
|
|
74
|
+
const bugData = await bugResp.json();
|
|
75
|
+
if (bugData.bugs) {
|
|
76
|
+
for (const bug of bugData.bugs) {
|
|
77
|
+
issues.push({
|
|
78
|
+
file: bug.file || 'unknown',
|
|
79
|
+
line: bug.line,
|
|
80
|
+
message: bug.description || 'Bug detected',
|
|
81
|
+
severity: bug.severity === 'critical' || bug.severity === 'high' ? 'error' : 'warning',
|
|
82
|
+
source: 'bug-detection',
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Bug detection not available — not fatal
|
|
90
|
+
}
|
|
91
|
+
// Fetch rule violations
|
|
92
|
+
try {
|
|
93
|
+
const rulesUrl = `${PROD_API_BASE}/v2/analysis/${encodedId}/rule_violations.json`;
|
|
94
|
+
const rulesResp = await fetch(rulesUrl, {
|
|
95
|
+
headers: apiHeaders(token),
|
|
96
|
+
});
|
|
97
|
+
if (rulesResp.ok) {
|
|
98
|
+
const rulesData = await rulesResp.json();
|
|
99
|
+
if (rulesData.violations) {
|
|
100
|
+
for (const v of rulesData.violations) {
|
|
101
|
+
issues.push({
|
|
102
|
+
file: v.file || 'unknown',
|
|
103
|
+
line: v.line,
|
|
104
|
+
message: v.message || `Rule violation: ${v.rule || 'unknown'}`,
|
|
105
|
+
severity: v.severity === 'error' ? 'error' : 'warning',
|
|
106
|
+
source: 'rule-violation',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Rule violations not available — not fatal
|
|
114
|
+
}
|
|
115
|
+
// Fetch merge decision (to check if auto-merged)
|
|
116
|
+
let autoMerged = false;
|
|
117
|
+
try {
|
|
118
|
+
const mergeUrl = `${PROD_API_BASE}/v2/analysis/${encodedId}/merge_decision.json`;
|
|
119
|
+
const mergeResp = await fetch(mergeUrl, {
|
|
120
|
+
headers: apiHeaders(token),
|
|
121
|
+
});
|
|
122
|
+
if (mergeResp.ok) {
|
|
123
|
+
const mergeData = await mergeResp.json();
|
|
124
|
+
autoMerged = mergeData.autoMerged === true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Merge decision not available — not fatal
|
|
129
|
+
}
|
|
130
|
+
const bugCount = issues.filter(i => i.source === 'bug-detection').length;
|
|
131
|
+
const ruleViolationCount = issues.filter(i => i.source === 'rule-violation').length;
|
|
132
|
+
const warningCount = issues.filter(i => i.severity === 'warning').length;
|
|
133
|
+
const errorCount = issues.filter(i => i.severity === 'error').length;
|
|
134
|
+
const state = errorCount === 0 && warningCount === 0 ? 'good-to-merge' : 'needs-input';
|
|
135
|
+
// Build summary
|
|
136
|
+
let summary;
|
|
137
|
+
if (state === 'good-to-merge') {
|
|
138
|
+
summary = autoMerged ? 'No issues found. PR was auto-merged.' : 'No issues found. Safe to merge.';
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
const parts = [];
|
|
142
|
+
if (errorCount > 0)
|
|
143
|
+
parts.push(`${errorCount} error(s)`);
|
|
144
|
+
if (warningCount > 0)
|
|
145
|
+
parts.push(`${warningCount} warning(s)`);
|
|
146
|
+
if (bugCount > 0)
|
|
147
|
+
parts.push(`${bugCount} bug(s)`);
|
|
148
|
+
if (ruleViolationCount > 0)
|
|
149
|
+
parts.push(`${ruleViolationCount} rule violation(s)`);
|
|
150
|
+
summary = parts.join(', ') + ' found.';
|
|
151
|
+
}
|
|
152
|
+
return {
|
|
153
|
+
state,
|
|
154
|
+
bugCount,
|
|
155
|
+
warningCount,
|
|
156
|
+
ruleViolationCount,
|
|
157
|
+
summary,
|
|
158
|
+
issues,
|
|
159
|
+
reviewUrl,
|
|
160
|
+
autoMerged,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Fetch the full rating synthesis for a PR.
|
|
165
|
+
* This is the rich analysis data shown on the Haystack feed — includes
|
|
166
|
+
* verdict, rating, structured findings, verified bugs, and agent fix prompts.
|
|
167
|
+
*/
|
|
168
|
+
export async function fetchRatingSynthesis(owner, repo, prNumber, token) {
|
|
169
|
+
// Use v3/result endpoint (same as the Lambda serves generic files)
|
|
170
|
+
const url = `${PROD_API_BASE}/v3/result/${owner}/${repo}/${prNumber}/rating_synthesis.json`;
|
|
171
|
+
try {
|
|
172
|
+
const response = await fetch(url, {
|
|
173
|
+
headers: apiHeaders(token),
|
|
174
|
+
});
|
|
175
|
+
if (!response.ok)
|
|
176
|
+
return null;
|
|
177
|
+
const data = await response.json();
|
|
178
|
+
// Handle presigned URL redirect (Lambda returns { download_url } for large files)
|
|
179
|
+
// Presigned S3 URLs include auth in query params — do NOT forward the GitHub token
|
|
180
|
+
// (S3 rejects requests with both presigned auth and an Authorization/custom header)
|
|
181
|
+
if (data.download_url) {
|
|
182
|
+
const s3Resp = await fetch(data.download_url, {
|
|
183
|
+
headers: { 'User-Agent': 'Haystack-CLI' },
|
|
184
|
+
});
|
|
185
|
+
if (!s3Resp.ok)
|
|
186
|
+
return null;
|
|
187
|
+
return await s3Resp.json();
|
|
188
|
+
}
|
|
189
|
+
return data;
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
package/dist/utils/config.js
CHANGED
|
@@ -9,7 +9,7 @@ const CONFIG_FILENAME = '.haystack.json';
|
|
|
9
9
|
*/
|
|
10
10
|
export async function findConfigPath(startDir = process.cwd()) {
|
|
11
11
|
let dir = startDir;
|
|
12
|
-
|
|
12
|
+
for (;;) {
|
|
13
13
|
const configPath = path.join(dir, CONFIG_FILENAME);
|
|
14
14
|
try {
|
|
15
15
|
await fs.access(configPath);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git utilities for haystack submit command.
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for git operations: branch management, push, remote parsing.
|
|
5
|
+
*/
|
|
6
|
+
export { findGitRoot } from './hooks.js';
|
|
7
|
+
export interface GitRemoteInfo {
|
|
8
|
+
owner: string;
|
|
9
|
+
repo: string;
|
|
10
|
+
remoteName: string;
|
|
11
|
+
}
|
|
12
|
+
export interface GitBranchInfo {
|
|
13
|
+
currentBranch: string;
|
|
14
|
+
defaultBranch: string;
|
|
15
|
+
isClean: boolean;
|
|
16
|
+
hasUncommittedChanges: boolean;
|
|
17
|
+
hasUnpushedCommits: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Get the current branch name
|
|
21
|
+
*/
|
|
22
|
+
export declare function getCurrentBranch(): string;
|
|
23
|
+
/**
|
|
24
|
+
* Get the default branch (main or master)
|
|
25
|
+
*/
|
|
26
|
+
export declare function getDefaultBranch(): string;
|
|
27
|
+
/**
|
|
28
|
+
* Check if working directory has uncommitted changes
|
|
29
|
+
*/
|
|
30
|
+
export declare function hasUncommittedChanges(): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Check if current branch has unpushed commits
|
|
33
|
+
*/
|
|
34
|
+
export declare function hasUnpushedCommits(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Get comprehensive branch info
|
|
37
|
+
*/
|
|
38
|
+
export declare function getBranchInfo(): GitBranchInfo;
|
|
39
|
+
/**
|
|
40
|
+
* Create a new branch from current HEAD
|
|
41
|
+
*/
|
|
42
|
+
export declare function createBranch(branchName: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* Check if a branch exists locally
|
|
45
|
+
*/
|
|
46
|
+
export declare function branchExists(branchName: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Check if a branch exists on remote
|
|
49
|
+
*/
|
|
50
|
+
export declare function remoteBranchExists(remoteName: string, branchName: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Checkout an existing branch
|
|
53
|
+
*/
|
|
54
|
+
export declare function checkoutBranch(branchName: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Push current branch to remote with upstream tracking
|
|
57
|
+
*/
|
|
58
|
+
export declare function pushBranch(remoteName: string, branchName: string): void;
|
|
59
|
+
/**
|
|
60
|
+
* Parse remote URL to extract owner and repo
|
|
61
|
+
* Supports both SSH and HTTPS URLs
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseRemoteUrl(remoteName?: string): GitRemoteInfo;
|
|
64
|
+
/**
|
|
65
|
+
* Get the last commit message (first line only)
|
|
66
|
+
*/
|
|
67
|
+
export declare function getLastCommitMessage(): string;
|
|
68
|
+
/**
|
|
69
|
+
* Get the last commit SHA (short)
|
|
70
|
+
*/
|
|
71
|
+
export declare function getLastCommitSha(): string;
|
|
72
|
+
/**
|
|
73
|
+
* Get full commit messages (subject + body) for all commits since base branch.
|
|
74
|
+
* Returns the combined log, or empty string if no commits or on error.
|
|
75
|
+
*/
|
|
76
|
+
export declare function getCommitMessagesSinceBase(baseBranch: string): string;
|
|
77
|
+
/**
|
|
78
|
+
* Get the number of commits ahead of base branch
|
|
79
|
+
*/
|
|
80
|
+
export declare function getCommitsAhead(baseBranch: string): number;
|