@aaronshaf/ger 0.1.4 → 0.1.6
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/package.json +1 -1
- package/src/cli/commands/review.ts +3 -3
- package/src/cli/index.ts +7 -7
- package/src/prompts/system-inline-review.md +26 -11
- package/src/prompts/system-overall-review.md +11 -0
- package/src/services/review-strategy.ts +13 -47
- package/tests/unit/services/review-strategy.test.ts +12 -157
package/package.json
CHANGED
|
@@ -89,7 +89,7 @@ interface ReviewOptions {
|
|
|
89
89
|
comment?: boolean
|
|
90
90
|
yes?: boolean
|
|
91
91
|
prompt?: string
|
|
92
|
-
|
|
92
|
+
tool?: string
|
|
93
93
|
systemPrompt?: string
|
|
94
94
|
}
|
|
95
95
|
|
|
@@ -331,12 +331,12 @@ export const reviewCommand = (changeId: string, options: ReviewOptions = {}) =>
|
|
|
331
331
|
|
|
332
332
|
if (availableStrategies.length === 0) {
|
|
333
333
|
return yield* Effect.fail(
|
|
334
|
-
new Error('No AI tools available. Please install claude, gemini, or
|
|
334
|
+
new Error('No AI tools available. Please install claude, gemini, or opencode CLI.'),
|
|
335
335
|
)
|
|
336
336
|
}
|
|
337
337
|
|
|
338
338
|
// Select strategy based on user preference
|
|
339
|
-
const selectedStrategy = yield* reviewStrategy.selectStrategy(options.
|
|
339
|
+
const selectedStrategy = yield* reviewStrategy.selectStrategy(options.tool)
|
|
340
340
|
yield* Console.log(`✓ Using AI tool: ${selectedStrategy.name}`)
|
|
341
341
|
|
|
342
342
|
// Load custom review prompt if provided
|
package/src/cli/index.ts
CHANGED
|
@@ -368,15 +368,12 @@ program
|
|
|
368
368
|
.option('-y, --yes', 'Skip confirmation prompts when posting comments')
|
|
369
369
|
.option('--debug', 'Show debug output including AI responses')
|
|
370
370
|
.option('--prompt <file>', 'Path to custom review prompt file (e.g., ~/prompts/review.md)')
|
|
371
|
-
.option(
|
|
372
|
-
'--provider <provider>',
|
|
373
|
-
'Preferred AI provider (claude-sdk, claude, gemini, codex, opencode)',
|
|
374
|
-
)
|
|
371
|
+
.option('--tool <tool>', 'Preferred AI tool (claude, gemini, opencode)')
|
|
375
372
|
.option('--system-prompt <prompt>', 'Custom system prompt for the AI')
|
|
376
373
|
.addHelpText(
|
|
377
374
|
'after',
|
|
378
375
|
`
|
|
379
|
-
This command uses AI (
|
|
376
|
+
This command uses AI (claude CLI, gemini CLI, or opencode CLI) to review a Gerrit change.
|
|
380
377
|
It performs a two-stage review process:
|
|
381
378
|
|
|
382
379
|
1. Generates inline comments for specific code issues
|
|
@@ -387,7 +384,7 @@ Use --comment to post the review to Gerrit (with confirmation prompts).
|
|
|
387
384
|
Use --comment --yes to post without confirmation.
|
|
388
385
|
|
|
389
386
|
Requirements:
|
|
390
|
-
- One of these AI tools must be available:
|
|
387
|
+
- One of these AI tools must be available: claude CLI, gemini CLI, or opencode CLI
|
|
391
388
|
- Gerrit credentials must be configured (run 'ger setup' first)
|
|
392
389
|
|
|
393
390
|
Examples:
|
|
@@ -400,6 +397,9 @@ Examples:
|
|
|
400
397
|
# Review and auto-post comments without prompting
|
|
401
398
|
$ ger review 12345 --comment --yes
|
|
402
399
|
|
|
400
|
+
# Use specific AI tool
|
|
401
|
+
$ ger review 12345 --tool gemini
|
|
402
|
+
|
|
403
403
|
# Show debug output to troubleshoot issues
|
|
404
404
|
$ ger review 12345 --debug
|
|
405
405
|
`,
|
|
@@ -411,7 +411,7 @@ Examples:
|
|
|
411
411
|
yes: options.yes,
|
|
412
412
|
debug: options.debug,
|
|
413
413
|
prompt: options.prompt,
|
|
414
|
-
|
|
414
|
+
tool: options.tool,
|
|
415
415
|
systemPrompt: options.systemPrompt,
|
|
416
416
|
}).pipe(
|
|
417
417
|
Effect.provide(ReviewStrategyServiceLive),
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
##
|
|
1
|
+
## IMMEDIATE TASK: ANALYZE CODE AND GENERATE INLINE COMMENTS
|
|
2
2
|
|
|
3
|
-
**
|
|
4
|
-
**NEVER USE BACKTICKS ANYWHERE IN YOUR RESPONSE - they cause shell execution errors.**
|
|
3
|
+
**YOU MUST ANALYZE THE PROVIDED CODE CHANGES RIGHT NOW AND GENERATE INLINE COMMENTS.**
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
**CRITICAL OUTPUT REQUIREMENT:**
|
|
6
|
+
- YOUR ENTIRE OUTPUT MUST BE WRAPPED IN <response></response> TAGS
|
|
7
|
+
- NEVER USE BACKTICKS ANYWHERE IN YOUR RESPONSE - they cause shell execution errors
|
|
8
|
+
- Output ONLY a JSON array wrapped in response tags
|
|
9
|
+
- **EMPTY ARRAY IS PERFECTLY VALID** for clean code without issues
|
|
10
|
+
- No other text before or after the tags
|
|
11
|
+
|
|
12
|
+
**START YOUR ANALYSIS NOW. DO NOT ASK QUESTIONS. DO NOT WAIT FOR MORE INPUT.**
|
|
7
13
|
|
|
8
14
|
## JSON Structure for Inline Comments
|
|
9
15
|
|
|
@@ -21,12 +27,17 @@ The JSON array must contain inline comment objects with these fields:
|
|
|
21
27
|
- "start_character": Optional column start (integer)
|
|
22
28
|
- "end_character": Optional column end (integer)
|
|
23
29
|
|
|
24
|
-
**
|
|
30
|
+
**CRITICAL LINE NUMBER RULES:**
|
|
31
|
+
1. **ALWAYS use final file line numbers, NEVER diff line numbers**
|
|
32
|
+
2. Line numbers must match the NEW version of the file after all changes
|
|
33
|
+
3. Use `git show HEAD:path/to/file` or examine the final file to get correct line numbers
|
|
34
|
+
4. If you see "+50" in a diff, the actual line number is NOT 50 - check the final file
|
|
35
|
+
5. Every comment MUST have either "line" OR "range". Comments without valid line numbers will be rejected.
|
|
25
36
|
|
|
26
37
|
### Optional Fields
|
|
27
38
|
- "side": "REVISION" (new code, default) or "PARENT" (original code)
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
**VERIFICATION STEP**: Before adding any comment, verify the line number by checking the final file content to ensure your line number points to the exact code you're commenting on.
|
|
30
41
|
|
|
31
42
|
## Comment Quality Guidelines
|
|
32
43
|
|
|
@@ -85,11 +96,11 @@ You are running in a git repository with full access to:
|
|
|
85
96
|
- All project files for architectural understanding
|
|
86
97
|
- Use these commands to provide comprehensive, accurate reviews
|
|
87
98
|
|
|
88
|
-
## FINAL
|
|
99
|
+
## FINAL TASK INSTRUCTION
|
|
89
100
|
|
|
90
|
-
**
|
|
101
|
+
**ANALYZE THE CODE CHANGES NOW AND OUTPUT YOUR INLINE COMMENTS IMMEDIATELY.**
|
|
91
102
|
|
|
92
|
-
|
|
103
|
+
Your output format must be:
|
|
93
104
|
```
|
|
94
105
|
<response>
|
|
95
106
|
[]
|
|
@@ -97,6 +108,8 @@ Example formats:
|
|
|
97
108
|
```
|
|
98
109
|
(Empty array for clean code - this is GOOD!)
|
|
99
110
|
|
|
111
|
+
OR:
|
|
112
|
+
|
|
100
113
|
```
|
|
101
114
|
<response>
|
|
102
115
|
[{"file": "auth.js", "line": 42, "message": "🤖 SQL injection vulnerability: query uses string concatenation"}]
|
|
@@ -104,10 +117,12 @@ Example formats:
|
|
|
104
117
|
```
|
|
105
118
|
(Only comment on real problems)
|
|
106
119
|
|
|
107
|
-
**REQUIREMENTS**:
|
|
120
|
+
**CRITICAL REQUIREMENTS**:
|
|
108
121
|
- Every message must start with "🤖 "
|
|
109
122
|
- Never use backticks in your response
|
|
110
123
|
- Empty arrays are encouraged for clean code
|
|
111
124
|
- Focus on bugs, security, crashes - ignore style preferences
|
|
112
125
|
- Use git commands to understand context before commenting
|
|
113
|
-
- NO TEXT OUTSIDE THE <response></response> TAGS
|
|
126
|
+
- NO TEXT OUTSIDE THE <response></response> TAGS
|
|
127
|
+
|
|
128
|
+
**DO YOUR ANALYSIS NOW. STOP ASKING QUESTIONS. GENERATE THE REVIEW.**
|
|
@@ -41,11 +41,16 @@ Gerrit uses a LIMITED markdown subset. Follow these rules EXACTLY:
|
|
|
41
41
|
|
|
42
42
|
**YOUR ENTIRE OUTPUT MUST BE WRAPPED IN <response></response> TAGS.**
|
|
43
43
|
|
|
44
|
+
**IMMEDIATE TASK**: Analyze the code changes provided below and write a comprehensive engineering review.
|
|
45
|
+
|
|
44
46
|
Start with "🤖 [Your Tool Name] ([Your Model])" then provide a **CONCISE** engineering assessment. Examples:
|
|
45
47
|
- If you are Claude Sonnet 4: "🤖 Claude (Sonnet 4)"
|
|
48
|
+
- If you are Gemini: "🤖 Gemini (1.5 Pro)" or "🤖 Gemini (1.5 Flash)"
|
|
46
49
|
- For clean code: "No significant issues found. Change is ready for merge."
|
|
47
50
|
- For problematic code: Focus only on critical/important issues, skip minor concerns
|
|
48
51
|
|
|
52
|
+
**YOU MUST ANALYZE THE PROVIDED CODE CHANGES AND WRITE A REVIEW NOW.**
|
|
53
|
+
|
|
49
54
|
## Example Output Format
|
|
50
55
|
|
|
51
56
|
<response>
|
|
@@ -175,3 +180,9 @@ CRITICAL FORMATTING RULES:
|
|
|
175
180
|
- Use exactly 4 spaces to start each line of code blocks
|
|
176
181
|
- Keep code blocks simple and readable
|
|
177
182
|
- Add proper spacing for readability
|
|
183
|
+
|
|
184
|
+
## TASK SUMMARY
|
|
185
|
+
|
|
186
|
+
**ANALYZE THE CODE CHANGES PROVIDED ABOVE AND WRITE YOUR ENGINEERING REVIEW IMMEDIATELY.**
|
|
187
|
+
|
|
188
|
+
Do not ask for clarification. Do not wait for more input. Analyze the provided code changes, git history, and changed files, then write your review following the format requirements above.
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { Context, Data, Effect, Layer } from 'effect'
|
|
2
|
-
import { Console } from 'effect'
|
|
3
2
|
import { exec } from 'node:child_process'
|
|
4
3
|
import { promisify } from 'node:util'
|
|
5
4
|
|
|
6
5
|
const execAsync = promisify(exec)
|
|
7
6
|
|
|
7
|
+
// Shared response extraction logic for all AI tools
|
|
8
|
+
const extractResponse = (stdout: string): string => {
|
|
9
|
+
// Extract response from <response> tags or use full output
|
|
10
|
+
const responseMatch = stdout.match(/<response>([\s\S]*?)<\/response>/i)
|
|
11
|
+
return responseMatch ? responseMatch[1].trim() : stdout.trim()
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
// Simple strategy focused only on review needs
|
|
9
15
|
export class ReviewStrategyError extends Data.TaggedError('ReviewStrategyError')<{
|
|
10
16
|
message: string
|
|
@@ -76,9 +82,7 @@ export const claudeCliStrategy: ReviewStrategy = {
|
|
|
76
82
|
}),
|
|
77
83
|
})
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
const responseMatch = result.stdout.match(/<response>([\s\S]*?)<\/response>/i)
|
|
81
|
-
return responseMatch ? responseMatch[1].trim() : result.stdout.trim()
|
|
85
|
+
return extractResponse(result.stdout)
|
|
82
86
|
}),
|
|
83
87
|
}
|
|
84
88
|
|
|
@@ -136,7 +140,7 @@ export const geminiCliStrategy: ReviewStrategy = {
|
|
|
136
140
|
}),
|
|
137
141
|
})
|
|
138
142
|
|
|
139
|
-
return result.stdout
|
|
143
|
+
return extractResponse(result.stdout)
|
|
140
144
|
}),
|
|
141
145
|
}
|
|
142
146
|
|
|
@@ -194,35 +198,7 @@ export const openCodeCliStrategy: ReviewStrategy = {
|
|
|
194
198
|
}),
|
|
195
199
|
})
|
|
196
200
|
|
|
197
|
-
return result.stdout
|
|
198
|
-
}),
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export const codexCliStrategy: ReviewStrategy = {
|
|
202
|
-
name: 'Codex CLI',
|
|
203
|
-
isAvailable: () =>
|
|
204
|
-
Effect.gen(function* () {
|
|
205
|
-
const result = yield* Effect.tryPromise({
|
|
206
|
-
try: () => execAsync('which codex'),
|
|
207
|
-
catch: () => null,
|
|
208
|
-
}).pipe(Effect.orElseSucceed(() => null))
|
|
209
|
-
|
|
210
|
-
return Boolean(result && result.stdout.trim())
|
|
211
|
-
}),
|
|
212
|
-
executeReview: (prompt, options = {}) =>
|
|
213
|
-
Effect.gen(function* () {
|
|
214
|
-
const command = `codex exec "${prompt.replace(/"/g, '\\"')}"`
|
|
215
|
-
|
|
216
|
-
const result = yield* Effect.tryPromise({
|
|
217
|
-
try: () => execAsync(command, { cwd: options.cwd }),
|
|
218
|
-
catch: (error) =>
|
|
219
|
-
new ReviewStrategyError({
|
|
220
|
-
message: `Codex CLI failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
221
|
-
cause: error,
|
|
222
|
-
}),
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
return result.stdout.trim()
|
|
201
|
+
return extractResponse(result.stdout)
|
|
226
202
|
}),
|
|
227
203
|
}
|
|
228
204
|
|
|
@@ -247,12 +223,7 @@ export const ReviewStrategyServiceLive = Layer.succeed(
|
|
|
247
223
|
ReviewStrategyService.of({
|
|
248
224
|
getAvailableStrategies: () =>
|
|
249
225
|
Effect.gen(function* () {
|
|
250
|
-
const strategies = [
|
|
251
|
-
claudeCliStrategy,
|
|
252
|
-
geminiCliStrategy,
|
|
253
|
-
openCodeCliStrategy,
|
|
254
|
-
codexCliStrategy,
|
|
255
|
-
]
|
|
226
|
+
const strategies = [claudeCliStrategy, geminiCliStrategy, openCodeCliStrategy]
|
|
256
227
|
const available: ReviewStrategy[] = []
|
|
257
228
|
|
|
258
229
|
for (const strategy of strategies) {
|
|
@@ -267,12 +238,7 @@ export const ReviewStrategyServiceLive = Layer.succeed(
|
|
|
267
238
|
|
|
268
239
|
selectStrategy: (preferredName?: string) =>
|
|
269
240
|
Effect.gen(function* () {
|
|
270
|
-
const strategies = [
|
|
271
|
-
claudeCliStrategy,
|
|
272
|
-
geminiCliStrategy,
|
|
273
|
-
openCodeCliStrategy,
|
|
274
|
-
codexCliStrategy,
|
|
275
|
-
]
|
|
241
|
+
const strategies = [claudeCliStrategy, geminiCliStrategy, openCodeCliStrategy]
|
|
276
242
|
const available: ReviewStrategy[] = []
|
|
277
243
|
|
|
278
244
|
for (const strategy of strategies) {
|
|
@@ -285,7 +251,7 @@ export const ReviewStrategyServiceLive = Layer.succeed(
|
|
|
285
251
|
if (available.length === 0) {
|
|
286
252
|
return yield* Effect.fail(
|
|
287
253
|
new ReviewStrategyError({
|
|
288
|
-
message: 'No AI tools available. Please install claude, gemini, or
|
|
254
|
+
message: 'No AI tools available. Please install claude, gemini, or opencode CLI.',
|
|
289
255
|
}),
|
|
290
256
|
)
|
|
291
257
|
}
|
|
@@ -7,11 +7,6 @@ interface MockDeps {
|
|
|
7
7
|
spawn: (command: string, options: any) => any
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
// Mock for Claude SDK
|
|
11
|
-
interface MockClaudeSDK {
|
|
12
|
-
query: any // Will be a Bun mock function
|
|
13
|
-
}
|
|
14
|
-
|
|
15
10
|
// Test implementation that mirrors the real strategy structure
|
|
16
11
|
const createTestStrategy = (name: string, command: string, flags: string[], deps: MockDeps) => ({
|
|
17
12
|
name,
|
|
@@ -74,47 +69,6 @@ const createTestStrategy = (name: string, command: string, flags: string[], deps
|
|
|
74
69
|
}),
|
|
75
70
|
})
|
|
76
71
|
|
|
77
|
-
// Test implementation for Claude SDK strategy
|
|
78
|
-
const createSDKStrategy = (
|
|
79
|
-
name: string,
|
|
80
|
-
deps: { sdk: MockClaudeSDK | null; hasApiKey: boolean },
|
|
81
|
-
) => ({
|
|
82
|
-
name,
|
|
83
|
-
isAvailable: () =>
|
|
84
|
-
Effect.gen(function* () {
|
|
85
|
-
return Boolean(deps.sdk && deps.hasApiKey)
|
|
86
|
-
}),
|
|
87
|
-
executeReview: (prompt: string, options: { cwd?: string; systemPrompt?: string } = {}) =>
|
|
88
|
-
Effect.gen(function* () {
|
|
89
|
-
if (!deps.sdk) {
|
|
90
|
-
return yield* Effect.fail(new Error(`${name} not available`))
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const result = yield* Effect.tryPromise({
|
|
94
|
-
try: async () => {
|
|
95
|
-
for await (const message of deps.sdk!.query({
|
|
96
|
-
prompt,
|
|
97
|
-
options: {
|
|
98
|
-
maxTurns: 3,
|
|
99
|
-
customSystemPrompt: options.systemPrompt || 'You are a code review expert.',
|
|
100
|
-
allowedTools: ['Read', 'Grep', 'Glob'],
|
|
101
|
-
cwd: options.cwd,
|
|
102
|
-
},
|
|
103
|
-
})) {
|
|
104
|
-
if (message.type === 'result' && message.subtype === 'success' && message.result) {
|
|
105
|
-
return message.result
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
throw new Error('No result received')
|
|
109
|
-
},
|
|
110
|
-
catch: (error) =>
|
|
111
|
-
new Error(`${name} failed: ${error instanceof Error ? error.message : String(error)}`),
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
return result
|
|
115
|
-
}),
|
|
116
|
-
})
|
|
117
|
-
|
|
118
72
|
describe('Review Strategy', () => {
|
|
119
73
|
let mockExecAsync: any
|
|
120
74
|
let mockSpawn: any
|
|
@@ -266,6 +220,14 @@ describe('Review Strategy', () => {
|
|
|
266
220
|
expect(response).toBe('Gemini response')
|
|
267
221
|
expect(mockSpawn).toHaveBeenCalledWith('gemini -p', expect.any(Object))
|
|
268
222
|
})
|
|
223
|
+
|
|
224
|
+
it('should extract response from tags', async () => {
|
|
225
|
+
setupSuccessfulExecution('<response>Gemini tagged content</response>')
|
|
226
|
+
|
|
227
|
+
const response = await Effect.runPromise(geminiStrategy.executeReview('Test prompt'))
|
|
228
|
+
|
|
229
|
+
expect(response).toBe('Gemini tagged content')
|
|
230
|
+
})
|
|
269
231
|
})
|
|
270
232
|
|
|
271
233
|
describe('OpenCode CLI Strategy', () => {
|
|
@@ -295,120 +257,13 @@ describe('Review Strategy', () => {
|
|
|
295
257
|
expect(response).toBe('OpenCode response')
|
|
296
258
|
expect(mockSpawn).toHaveBeenCalledWith('opencode -p', expect.any(Object))
|
|
297
259
|
})
|
|
298
|
-
})
|
|
299
260
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
let claudeSDKStrategy: any
|
|
303
|
-
|
|
304
|
-
beforeEach(() => {
|
|
305
|
-
mockSDK = {
|
|
306
|
-
query: mock(),
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
claudeSDKStrategy = createSDKStrategy('Claude SDK', {
|
|
310
|
-
sdk: mockSDK,
|
|
311
|
-
hasApiKey: true,
|
|
312
|
-
})
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
it('should check availability when SDK and API key are present', async () => {
|
|
316
|
-
const available = await Effect.runPromise(claudeSDKStrategy.isAvailable())
|
|
317
|
-
|
|
318
|
-
expect(available).toBe(true)
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
it('should check availability when SDK is missing', async () => {
|
|
322
|
-
const strategy = createSDKStrategy('Claude SDK', {
|
|
323
|
-
sdk: null,
|
|
324
|
-
hasApiKey: true,
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
const available = await Effect.runPromise(strategy.isAvailable())
|
|
328
|
-
|
|
329
|
-
expect(available).toBe(false)
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
it('should check availability when API key is missing', async () => {
|
|
333
|
-
const strategy = createSDKStrategy('Claude SDK', {
|
|
334
|
-
sdk: mockSDK,
|
|
335
|
-
hasApiKey: false,
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
const available = await Effect.runPromise(strategy.isAvailable())
|
|
339
|
-
|
|
340
|
-
expect(available).toBe(false)
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
it('should execute review successfully', async () => {
|
|
344
|
-
mockSDK.query = mock(async function* () {
|
|
345
|
-
yield { type: 'message', content: 'Thinking...' }
|
|
346
|
-
yield { type: 'result', subtype: 'success', result: 'Claude SDK response' }
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
const response = await Effect.runPromise(
|
|
350
|
-
claudeSDKStrategy.executeReview('Test prompt', { cwd: '/tmp' }),
|
|
351
|
-
)
|
|
352
|
-
|
|
353
|
-
expect(response).toBe('Claude SDK response')
|
|
354
|
-
expect(mockSDK.query).toHaveBeenCalledWith({
|
|
355
|
-
prompt: 'Test prompt',
|
|
356
|
-
options: {
|
|
357
|
-
maxTurns: 3,
|
|
358
|
-
customSystemPrompt: 'You are a code review expert.',
|
|
359
|
-
allowedTools: ['Read', 'Grep', 'Glob'],
|
|
360
|
-
cwd: '/tmp',
|
|
361
|
-
},
|
|
362
|
-
})
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
it('should use custom system prompt', async () => {
|
|
366
|
-
mockSDK.query = mock(async function* () {
|
|
367
|
-
yield { type: 'result', subtype: 'success', result: 'Custom prompt response' }
|
|
368
|
-
})
|
|
369
|
-
|
|
370
|
-
const response = await Effect.runPromise(
|
|
371
|
-
claudeSDKStrategy.executeReview('Test prompt', {
|
|
372
|
-
systemPrompt: 'Custom review prompt',
|
|
373
|
-
}),
|
|
374
|
-
)
|
|
375
|
-
|
|
376
|
-
expect(response).toBe('Custom prompt response')
|
|
377
|
-
expect(mockSDK.query).toHaveBeenCalledWith({
|
|
378
|
-
prompt: 'Test prompt',
|
|
379
|
-
options: {
|
|
380
|
-
maxTurns: 3,
|
|
381
|
-
customSystemPrompt: 'Custom review prompt',
|
|
382
|
-
allowedTools: ['Read', 'Grep', 'Glob'],
|
|
383
|
-
cwd: undefined,
|
|
384
|
-
},
|
|
385
|
-
})
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
it('should handle SDK failure', async () => {
|
|
389
|
-
mockSDK.query = mock(async function* () {
|
|
390
|
-
throw new Error('SDK error')
|
|
391
|
-
})
|
|
392
|
-
|
|
393
|
-
try {
|
|
394
|
-
await Effect.runPromise(claudeSDKStrategy.executeReview('Test prompt'))
|
|
395
|
-
expect(false).toBe(true) // Should not reach here
|
|
396
|
-
} catch (error: any) {
|
|
397
|
-
expect(error.message).toContain('Claude SDK failed')
|
|
398
|
-
}
|
|
399
|
-
})
|
|
261
|
+
it('should extract response from tags', async () => {
|
|
262
|
+
setupSuccessfulExecution('<response>OpenCode tagged content</response>')
|
|
400
263
|
|
|
401
|
-
|
|
402
|
-
mockSDK.query = mock(async function* () {
|
|
403
|
-
yield { type: 'message', content: 'No result' }
|
|
404
|
-
})
|
|
264
|
+
const response = await Effect.runPromise(opencodeStrategy.executeReview('Test prompt'))
|
|
405
265
|
|
|
406
|
-
|
|
407
|
-
await Effect.runPromise(claudeSDKStrategy.executeReview('Test prompt'))
|
|
408
|
-
expect(false).toBe(true) // Should not reach here
|
|
409
|
-
} catch (error: any) {
|
|
410
|
-
expect(error.message).toContain('No result received')
|
|
411
|
-
}
|
|
266
|
+
expect(response).toBe('OpenCode tagged content')
|
|
412
267
|
})
|
|
413
268
|
})
|
|
414
269
|
|