@aaronshaf/ger 2.0.9 → 3.0.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/README.md +0 -26
- package/docs/prd/architecture.md +6 -48
- package/docs/prd/commands.md +26 -20
- package/index.ts +0 -19
- package/llms.txt +0 -14
- package/package.json +1 -5
- package/src/api/gerrit.ts +23 -33
- package/src/cli/commands/set-ready.ts +76 -0
- package/src/cli/commands/set-wip.ts +76 -0
- package/src/cli/commands/setup.ts +0 -1
- package/src/cli/index.ts +38 -0
- package/src/cli/register-commands.ts +3 -102
- package/src/cli/register-state-commands.ts +106 -0
- package/src/utils/diff-formatters.ts +32 -0
- package/src/cli/commands/review.ts +0 -486
- package/src/prompts/default-review.md +0 -86
- package/src/prompts/system-inline-review.md +0 -135
- package/src/prompts/system-overall-review.md +0 -206
- package/src/services/review-strategy.ts +0 -292
|
@@ -1,486 +0,0 @@
|
|
|
1
|
-
import { Effect, pipe, Schema } from 'effect'
|
|
2
|
-
import { ReviewStrategyService, ReviewStrategyError } from '@/services/review-strategy'
|
|
3
|
-
import { commentCommandWithInput } from './comment'
|
|
4
|
-
import { Console } from 'effect'
|
|
5
|
-
import { GerritApiService } from '@/api/gerrit'
|
|
6
|
-
import { buildEnhancedPrompt } from '@/utils/review-prompt-builder'
|
|
7
|
-
import * as fs from 'node:fs/promises'
|
|
8
|
-
import * as fsSync from 'node:fs'
|
|
9
|
-
import * as os from 'node:os'
|
|
10
|
-
import * as path from 'node:path'
|
|
11
|
-
import { fileURLToPath } from 'node:url'
|
|
12
|
-
import { dirname } from 'node:path'
|
|
13
|
-
import * as readline from 'node:readline'
|
|
14
|
-
import { GitWorktreeService } from '@/services/git-worktree'
|
|
15
|
-
|
|
16
|
-
// Get the directory of this module
|
|
17
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
18
|
-
const __dirname = dirname(__filename)
|
|
19
|
-
|
|
20
|
-
// Effect-based file reading helper
|
|
21
|
-
const readFileEffect = (filePath: string): Effect.Effect<string, Error, never> =>
|
|
22
|
-
Effect.tryPromise({
|
|
23
|
-
try: () => fs.readFile(filePath, 'utf8'),
|
|
24
|
-
catch: (error) => new Error(`Failed to read file ${filePath}: ${error}`),
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
// Load default prompts from .md files using Effect
|
|
28
|
-
const loadDefaultPrompts = Effect.gen(function* () {
|
|
29
|
-
const defaultReviewPrompt = yield* readFileEffect(
|
|
30
|
-
path.join(__dirname, '../../prompts/default-review.md'),
|
|
31
|
-
)
|
|
32
|
-
const inlineReviewSystemPrompt = yield* readFileEffect(
|
|
33
|
-
path.join(__dirname, '../../prompts/system-inline-review.md'),
|
|
34
|
-
)
|
|
35
|
-
const overallReviewSystemPrompt = yield* readFileEffect(
|
|
36
|
-
path.join(__dirname, '../../prompts/system-overall-review.md'),
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
return {
|
|
40
|
-
defaultReviewPrompt,
|
|
41
|
-
inlineReviewSystemPrompt,
|
|
42
|
-
overallReviewSystemPrompt,
|
|
43
|
-
}
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
// Helper to expand tilde in file paths
|
|
47
|
-
const expandTilde = (filePath: string): string => {
|
|
48
|
-
if (filePath.startsWith('~/')) {
|
|
49
|
-
return path.join(os.homedir(), filePath.slice(2))
|
|
50
|
-
}
|
|
51
|
-
return filePath
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Helper to read prompt file using Effect
|
|
55
|
-
const readPromptFileEffect = (filePath: string): Effect.Effect<string | null, never, never> =>
|
|
56
|
-
Effect.gen(function* () {
|
|
57
|
-
const expanded = expandTilde(filePath)
|
|
58
|
-
|
|
59
|
-
// Check if file exists using sync method since Effect doesn't have a convenient async exists check
|
|
60
|
-
const exists = yield* Effect.try(() => fsSync.existsSync(expanded)).pipe(
|
|
61
|
-
Effect.catchAll(() => Effect.succeed(false)),
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
if (!exists) {
|
|
65
|
-
return null
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Read file using async Effect
|
|
69
|
-
const content = yield* readFileEffect(expanded).pipe(
|
|
70
|
-
Effect.catchAll(() => Effect.succeed(null)),
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
return content
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
interface ReviewOptions {
|
|
77
|
-
debug?: boolean
|
|
78
|
-
dryRun?: boolean
|
|
79
|
-
comment?: boolean
|
|
80
|
-
yes?: boolean
|
|
81
|
-
prompt?: string
|
|
82
|
-
tool?: string
|
|
83
|
-
systemPrompt?: string
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Schema for validating AI-generated inline comments
|
|
87
|
-
const InlineCommentSchema = Schema.Struct({
|
|
88
|
-
file: Schema.String,
|
|
89
|
-
message: Schema.String,
|
|
90
|
-
side: Schema.optional(Schema.String),
|
|
91
|
-
line: Schema.optional(Schema.Number),
|
|
92
|
-
range: Schema.optional(
|
|
93
|
-
Schema.Struct({
|
|
94
|
-
start_line: Schema.Number,
|
|
95
|
-
end_line: Schema.Number,
|
|
96
|
-
start_character: Schema.optional(Schema.Number),
|
|
97
|
-
end_character: Schema.optional(Schema.Number),
|
|
98
|
-
}),
|
|
99
|
-
),
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
interface InlineComment extends Schema.Schema.Type<typeof InlineCommentSchema> {}
|
|
103
|
-
|
|
104
|
-
// Helper to validate and fix AI-generated inline comments
|
|
105
|
-
const validateAndFixInlineComments = (
|
|
106
|
-
rawComments: unknown[],
|
|
107
|
-
availableFiles: string[],
|
|
108
|
-
): Effect.Effect<InlineComment[], never, never> =>
|
|
109
|
-
Effect.gen(function* () {
|
|
110
|
-
const validComments: InlineComment[] = []
|
|
111
|
-
|
|
112
|
-
for (const rawComment of rawComments) {
|
|
113
|
-
// Validate comment structure using Effect Schema
|
|
114
|
-
const parseResult = yield* Schema.decodeUnknown(InlineCommentSchema)(rawComment).pipe(
|
|
115
|
-
Effect.catchTag('ParseError', (_parseError) =>
|
|
116
|
-
Effect.gen(function* () {
|
|
117
|
-
yield* Console.warn('Skipping comment with invalid structure')
|
|
118
|
-
return yield* Effect.succeed(null)
|
|
119
|
-
}),
|
|
120
|
-
),
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
if (!parseResult) {
|
|
124
|
-
continue
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const comment = parseResult
|
|
128
|
-
|
|
129
|
-
// Skip comments with invalid line formats (like ":range")
|
|
130
|
-
if (!comment.line && !comment.range) {
|
|
131
|
-
yield* Console.warn('Skipping comment with invalid line format')
|
|
132
|
-
continue
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Try to find the correct file path
|
|
136
|
-
let correctFilePath = comment.file
|
|
137
|
-
|
|
138
|
-
// If the file path doesn't exist exactly, try to find a match
|
|
139
|
-
if (!availableFiles.includes(comment.file)) {
|
|
140
|
-
// Look for files that end with the provided path (secure path matching)
|
|
141
|
-
const matchingFiles = availableFiles.filter((file) => {
|
|
142
|
-
const normalizedFile = file.replace(/\\/g, '/')
|
|
143
|
-
const normalizedComment = comment.file.replace(/\\/g, '/')
|
|
144
|
-
|
|
145
|
-
// Only match if the comment path is a suffix of the file path with proper boundaries
|
|
146
|
-
return (
|
|
147
|
-
normalizedFile.endsWith(normalizedComment) &&
|
|
148
|
-
(normalizedFile === normalizedComment ||
|
|
149
|
-
normalizedFile.endsWith(`/${normalizedComment}`))
|
|
150
|
-
)
|
|
151
|
-
})
|
|
152
|
-
|
|
153
|
-
if (matchingFiles.length === 1) {
|
|
154
|
-
correctFilePath = matchingFiles[0]
|
|
155
|
-
yield* Console.log(`Fixed file path: ${comment.file} -> ${correctFilePath}`)
|
|
156
|
-
} else if (matchingFiles.length > 1) {
|
|
157
|
-
// Multiple matches, try to pick the most likely one (exact suffix match)
|
|
158
|
-
const exactMatch = matchingFiles.find((file) => file.endsWith(`/${comment.file}`))
|
|
159
|
-
if (exactMatch) {
|
|
160
|
-
correctFilePath = exactMatch
|
|
161
|
-
yield* Console.log(
|
|
162
|
-
`Fixed file path (exact match): ${comment.file} -> ${correctFilePath}`,
|
|
163
|
-
)
|
|
164
|
-
} else {
|
|
165
|
-
yield* Console.warn(`Multiple file matches for ${comment.file}. Skipping comment.`)
|
|
166
|
-
continue
|
|
167
|
-
}
|
|
168
|
-
} else {
|
|
169
|
-
yield* Console.warn(`File not found in change: ${comment.file}. Skipping comment.`)
|
|
170
|
-
continue
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Update the comment with the correct file path and add to valid comments
|
|
175
|
-
validComments.push({ ...comment, file: correctFilePath })
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return validComments
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
// Helper function to prompt user for confirmation
|
|
182
|
-
const promptUser = (message: string): Effect.Effect<boolean, never> =>
|
|
183
|
-
Effect.async<boolean, never>((resume) => {
|
|
184
|
-
const rl = readline.createInterface({
|
|
185
|
-
input: process.stdin,
|
|
186
|
-
output: process.stdout,
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
rl.question(`${message} [y/N]: `, (answer: string) => {
|
|
190
|
-
rl.close()
|
|
191
|
-
resume(Effect.succeed(answer.toLowerCase() === 'y'))
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
export const reviewCommand = (
|
|
196
|
-
changeId: string,
|
|
197
|
-
options: ReviewOptions = {},
|
|
198
|
-
): Effect.Effect<
|
|
199
|
-
void,
|
|
200
|
-
Error | ReviewStrategyError,
|
|
201
|
-
GerritApiService | ReviewStrategyService | GitWorktreeService
|
|
202
|
-
> =>
|
|
203
|
-
Effect.gen(function* () {
|
|
204
|
-
const reviewStrategy = yield* ReviewStrategyService
|
|
205
|
-
const gitService = yield* GitWorktreeService
|
|
206
|
-
|
|
207
|
-
// Load default prompts
|
|
208
|
-
const prompts = yield* loadDefaultPrompts
|
|
209
|
-
|
|
210
|
-
// Validate preconditions
|
|
211
|
-
yield* gitService.validatePreconditions()
|
|
212
|
-
|
|
213
|
-
// Check for available AI strategies
|
|
214
|
-
yield* Console.log('→ Checking AI tool availability...')
|
|
215
|
-
const availableStrategies = yield* reviewStrategy.getAvailableStrategies()
|
|
216
|
-
|
|
217
|
-
if (availableStrategies.length === 0) {
|
|
218
|
-
return yield* Effect.fail(
|
|
219
|
-
new Error('No AI tools available. Please install claude, gemini, or opencode CLI.'),
|
|
220
|
-
)
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Select strategy based on user preference
|
|
224
|
-
const selectedStrategy = yield* reviewStrategy.selectStrategy(options.tool)
|
|
225
|
-
yield* Console.log(`✓ Using AI tool: ${selectedStrategy.name}`)
|
|
226
|
-
|
|
227
|
-
// Load custom review prompt if provided
|
|
228
|
-
let userReviewPrompt = prompts.defaultReviewPrompt
|
|
229
|
-
|
|
230
|
-
if (options.prompt) {
|
|
231
|
-
const customPrompt = yield* readPromptFileEffect(options.prompt)
|
|
232
|
-
if (customPrompt) {
|
|
233
|
-
userReviewPrompt = customPrompt
|
|
234
|
-
yield* Console.log(`✓ Using custom review prompt from ${options.prompt}`)
|
|
235
|
-
} else {
|
|
236
|
-
yield* Console.log(`⚠ Could not read custom prompt file: ${options.prompt}`)
|
|
237
|
-
yield* Console.log('→ Using default review prompt')
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Use Effect's resource management for worktree lifecycle
|
|
242
|
-
yield* Effect.acquireUseRelease(
|
|
243
|
-
// Acquire: Create worktree and setup
|
|
244
|
-
Effect.gen(function* () {
|
|
245
|
-
const worktreeInfo = yield* gitService.createWorktree(changeId)
|
|
246
|
-
yield* gitService.fetchAndCheckoutPatchset(worktreeInfo)
|
|
247
|
-
return worktreeInfo
|
|
248
|
-
}),
|
|
249
|
-
|
|
250
|
-
// Use: Run the enhanced review process
|
|
251
|
-
(worktreeInfo) =>
|
|
252
|
-
Effect.gen(function* () {
|
|
253
|
-
// Switch to worktree directory
|
|
254
|
-
const originalCwd = process.cwd()
|
|
255
|
-
process.chdir(worktreeInfo.path)
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
// Get changed files from git
|
|
259
|
-
const changedFiles = yield* gitService.getChangedFiles()
|
|
260
|
-
|
|
261
|
-
yield* Console.log(`→ Found ${changedFiles.length} changed files`)
|
|
262
|
-
if (options.debug) {
|
|
263
|
-
yield* Console.log(`[DEBUG] Changed files: ${changedFiles.join(', ')}`)
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// Stage 1: Generate inline comments
|
|
267
|
-
yield* Console.log(`→ Generating inline comments for change ${changeId}...`)
|
|
268
|
-
|
|
269
|
-
const inlinePrompt = yield* buildEnhancedPrompt(
|
|
270
|
-
userReviewPrompt,
|
|
271
|
-
prompts.inlineReviewSystemPrompt,
|
|
272
|
-
changeId,
|
|
273
|
-
changedFiles,
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
// Run inline review using selected strategy
|
|
277
|
-
if (options.debug) {
|
|
278
|
-
yield* Console.log(`[DEBUG] Running inline review with ${selectedStrategy.name}`)
|
|
279
|
-
yield* Console.log(`[DEBUG] Working directory: ${worktreeInfo.path}`)
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const inlineResponse = yield* reviewStrategy
|
|
283
|
-
.executeWithStrategy(selectedStrategy, inlinePrompt, {
|
|
284
|
-
cwd: worktreeInfo.path,
|
|
285
|
-
systemPrompt: options.systemPrompt || prompts.inlineReviewSystemPrompt,
|
|
286
|
-
})
|
|
287
|
-
.pipe(
|
|
288
|
-
Effect.catchTag('ReviewStrategyError', (error) =>
|
|
289
|
-
Effect.gen(function* () {
|
|
290
|
-
yield* Console.error(`✗ Inline review failed: ${error.message}`)
|
|
291
|
-
return yield* Effect.fail(new Error(error.message))
|
|
292
|
-
}),
|
|
293
|
-
),
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
if (options.debug) {
|
|
297
|
-
yield* Console.log(`[DEBUG] Inline review completed`)
|
|
298
|
-
yield* Console.log(`[DEBUG] Response length: ${inlineResponse.length} chars`)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Response content is ready for parsing
|
|
302
|
-
const extractedInlineResponse = inlineResponse.trim()
|
|
303
|
-
|
|
304
|
-
if (options.debug) {
|
|
305
|
-
yield* Console.log(
|
|
306
|
-
`[DEBUG] Extracted response for parsing:\n${extractedInlineResponse}`,
|
|
307
|
-
)
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Parse JSON array from response
|
|
311
|
-
const inlineCommentsArray = yield* Effect.tryPromise({
|
|
312
|
-
try: () => Promise.resolve(JSON.parse(extractedInlineResponse)),
|
|
313
|
-
catch: (error) => new Error(`Invalid JSON response: ${error}`),
|
|
314
|
-
}).pipe(
|
|
315
|
-
Effect.catchAll((error) =>
|
|
316
|
-
Effect.gen(function* () {
|
|
317
|
-
yield* Console.error(`✗ Failed to parse inline comments JSON: ${error}`)
|
|
318
|
-
yield* Console.error(`Raw extracted response: "${extractedInlineResponse}"`)
|
|
319
|
-
if (!options.debug) {
|
|
320
|
-
yield* Console.error('Run with --debug to see full AI output')
|
|
321
|
-
}
|
|
322
|
-
return yield* Effect.fail(error)
|
|
323
|
-
}),
|
|
324
|
-
),
|
|
325
|
-
)
|
|
326
|
-
|
|
327
|
-
// Validate that the response is an array
|
|
328
|
-
if (!Array.isArray(inlineCommentsArray)) {
|
|
329
|
-
yield* Console.error('✗ AI response is not an array of comments')
|
|
330
|
-
return yield* Effect.fail(new Error('Invalid inline comments format'))
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// Validate and fix inline comments
|
|
334
|
-
const originalCount = inlineCommentsArray.length
|
|
335
|
-
const inlineComments = yield* validateAndFixInlineComments(
|
|
336
|
-
inlineCommentsArray,
|
|
337
|
-
changedFiles,
|
|
338
|
-
)
|
|
339
|
-
const validCount = inlineComments.length
|
|
340
|
-
|
|
341
|
-
if (originalCount > validCount) {
|
|
342
|
-
yield* Console.log(
|
|
343
|
-
`→ Filtered ${originalCount - validCount} invalid comments, ${validCount} remain`,
|
|
344
|
-
)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Handle inline comments output/posting
|
|
348
|
-
yield* handleInlineComments(inlineComments, changeId, options)
|
|
349
|
-
|
|
350
|
-
// Stage 2: Generate overall review comment
|
|
351
|
-
yield* Console.log(`→ Generating overall review comment for change ${changeId}...`)
|
|
352
|
-
|
|
353
|
-
const overallPrompt = yield* buildEnhancedPrompt(
|
|
354
|
-
userReviewPrompt,
|
|
355
|
-
prompts.overallReviewSystemPrompt,
|
|
356
|
-
changeId,
|
|
357
|
-
changedFiles,
|
|
358
|
-
)
|
|
359
|
-
|
|
360
|
-
// Run overall review using selected strategy
|
|
361
|
-
if (options.debug) {
|
|
362
|
-
yield* Console.log(`[DEBUG] Running overall review with ${selectedStrategy.name}`)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
const overallResponse = yield* reviewStrategy
|
|
366
|
-
.executeWithStrategy(selectedStrategy, overallPrompt, {
|
|
367
|
-
cwd: worktreeInfo.path,
|
|
368
|
-
systemPrompt: options.systemPrompt || prompts.overallReviewSystemPrompt,
|
|
369
|
-
})
|
|
370
|
-
.pipe(
|
|
371
|
-
Effect.catchTag('ReviewStrategyError', (error) =>
|
|
372
|
-
Effect.gen(function* () {
|
|
373
|
-
yield* Console.error(`✗ Overall review failed: ${error.message}`)
|
|
374
|
-
return yield* Effect.fail(new Error(error.message))
|
|
375
|
-
}),
|
|
376
|
-
),
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
if (options.debug) {
|
|
380
|
-
yield* Console.log(`[DEBUG] Overall review completed`)
|
|
381
|
-
yield* Console.log(`[DEBUG] Response length: ${overallResponse.length} chars`)
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
// Response content is ready for use
|
|
385
|
-
const extractedOverallResponse = overallResponse.trim()
|
|
386
|
-
|
|
387
|
-
// Handle overall review output/posting
|
|
388
|
-
yield* handleOverallReview(extractedOverallResponse, changeId, options)
|
|
389
|
-
} finally {
|
|
390
|
-
// Always restore original working directory
|
|
391
|
-
process.chdir(originalCwd)
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
yield* Console.log(`✓ Review complete for ${changeId}`)
|
|
395
|
-
}),
|
|
396
|
-
|
|
397
|
-
// Release: Always cleanup worktree
|
|
398
|
-
(worktreeInfo) => gitService.cleanup(worktreeInfo),
|
|
399
|
-
)
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
// Helper function to handle inline comments output/posting
|
|
403
|
-
const handleInlineComments = (
|
|
404
|
-
inlineComments: InlineComment[],
|
|
405
|
-
changeId: string,
|
|
406
|
-
options: ReviewOptions,
|
|
407
|
-
): Effect.Effect<void, Error, GerritApiService> =>
|
|
408
|
-
Effect.gen(function* () {
|
|
409
|
-
if (!options.comment) {
|
|
410
|
-
// Display mode
|
|
411
|
-
if (inlineComments.length > 0) {
|
|
412
|
-
yield* Console.log('\n━━━━━━ INLINE COMMENTS ━━━━━━')
|
|
413
|
-
for (const comment of inlineComments) {
|
|
414
|
-
yield* Console.log(`\n📍 ${comment.file}${comment.line ? `:${comment.line}` : ''}`)
|
|
415
|
-
yield* Console.log(comment.message)
|
|
416
|
-
}
|
|
417
|
-
} else {
|
|
418
|
-
yield* Console.log('\n→ No inline comments')
|
|
419
|
-
}
|
|
420
|
-
} else {
|
|
421
|
-
// Comment posting mode
|
|
422
|
-
if (inlineComments.length > 0) {
|
|
423
|
-
yield* Console.log('\n━━━━━━ INLINE COMMENTS TO POST ━━━━━━')
|
|
424
|
-
for (const comment of inlineComments) {
|
|
425
|
-
yield* Console.log(`\n📍 ${comment.file}${comment.line ? `:${comment.line}` : ''}`)
|
|
426
|
-
yield* Console.log(comment.message)
|
|
427
|
-
}
|
|
428
|
-
yield* Console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
429
|
-
|
|
430
|
-
const shouldPost =
|
|
431
|
-
options.yes || (yield* promptUser('\nPost these inline comments to Gerrit?'))
|
|
432
|
-
|
|
433
|
-
if (shouldPost) {
|
|
434
|
-
yield* pipe(
|
|
435
|
-
commentCommandWithInput(changeId, JSON.stringify(inlineComments), { batch: true }),
|
|
436
|
-
Effect.catchAll((error) =>
|
|
437
|
-
Effect.gen(function* () {
|
|
438
|
-
yield* Console.error(`✗ Failed to post inline comments: ${error}`)
|
|
439
|
-
return yield* Effect.fail(error)
|
|
440
|
-
}),
|
|
441
|
-
),
|
|
442
|
-
)
|
|
443
|
-
yield* Console.log(`✓ Inline comments posted for ${changeId}`)
|
|
444
|
-
} else {
|
|
445
|
-
yield* Console.log('→ Inline comments not posted')
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
})
|
|
450
|
-
|
|
451
|
-
// Helper function to handle overall review output/posting
|
|
452
|
-
const handleOverallReview = (
|
|
453
|
-
overallResponse: string,
|
|
454
|
-
changeId: string,
|
|
455
|
-
options: ReviewOptions,
|
|
456
|
-
): Effect.Effect<void, Error, GerritApiService> =>
|
|
457
|
-
Effect.gen(function* () {
|
|
458
|
-
if (!options.comment) {
|
|
459
|
-
// Display mode
|
|
460
|
-
yield* Console.log('\n━━━━━━ OVERALL REVIEW ━━━━━━')
|
|
461
|
-
yield* Console.log(overallResponse)
|
|
462
|
-
yield* Console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
463
|
-
} else {
|
|
464
|
-
// Comment posting mode
|
|
465
|
-
yield* Console.log('\n━━━━━━ OVERALL REVIEW TO POST ━━━━━━')
|
|
466
|
-
yield* Console.log(overallResponse)
|
|
467
|
-
yield* Console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
468
|
-
|
|
469
|
-
const shouldPost = options.yes || (yield* promptUser('\nPost this overall review to Gerrit?'))
|
|
470
|
-
|
|
471
|
-
if (shouldPost) {
|
|
472
|
-
yield* pipe(
|
|
473
|
-
commentCommandWithInput(changeId, overallResponse, {}),
|
|
474
|
-
Effect.catchAll((error) =>
|
|
475
|
-
Effect.gen(function* () {
|
|
476
|
-
yield* Console.error(`✗ Failed to post review comment: ${error}`)
|
|
477
|
-
return yield* Effect.fail(error)
|
|
478
|
-
}),
|
|
479
|
-
),
|
|
480
|
-
)
|
|
481
|
-
yield* Console.log(`✓ Overall review posted for ${changeId}`)
|
|
482
|
-
} else {
|
|
483
|
-
yield* Console.log('→ Overall review not posted')
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
})
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
# Engineering Code Review - Signal Over Noise
|
|
2
|
-
|
|
3
|
-
You are conducting a technical code review for experienced engineers. **PRIORITY: Find actual problems, not generate busy work.**
|
|
4
|
-
|
|
5
|
-
## Core Principles
|
|
6
|
-
|
|
7
|
-
**SIGNAL > NOISE**: Only comment on issues that materially impact correctness, security, performance, or maintainability. Silence is better than noise.
|
|
8
|
-
|
|
9
|
-
**NO PRAISE NEEDED**: Don't compliment good code. Engineers expect competent code by default.
|
|
10
|
-
|
|
11
|
-
**EMPTY RESPONSES ARE VALID**: Small changes without issues should result in empty inline comments. The overall review can simply note "No significant issues found."
|
|
12
|
-
|
|
13
|
-
**FOCUS ON REAL PROBLEMS**:
|
|
14
|
-
- Bugs that will cause runtime failures
|
|
15
|
-
- Security vulnerabilities
|
|
16
|
-
- Performance bottlenecks
|
|
17
|
-
- Architectural mistakes
|
|
18
|
-
- Missing error handling
|
|
19
|
-
|
|
20
|
-
**ASSUME COMPETENCE**: The author is an experienced engineer who made intentional decisions. Question only when you see genuine problems.
|
|
21
|
-
|
|
22
|
-
## Review Categories (Priority Order)
|
|
23
|
-
|
|
24
|
-
### 1. CRITICAL ISSUES (Must Fix)
|
|
25
|
-
- **Correctness**: Logic errors, race conditions, data corruption risks
|
|
26
|
-
- **Security**: Authentication bypasses, injection vulnerabilities, data exposure
|
|
27
|
-
- **Data Loss**: Operations that could destroy or corrupt user data
|
|
28
|
-
- **Breaking Changes**: Incompatible API/schema changes without migration
|
|
29
|
-
- **Production Impact**: Issues that would cause outages or severe degradation
|
|
30
|
-
|
|
31
|
-
### 2. SIGNIFICANT CONCERNS (Should Fix)
|
|
32
|
-
- **Performance**: Memory leaks, N+1 queries, inefficient algorithms
|
|
33
|
-
- **Error Handling**: Missing error cases, silent failures, poor recovery
|
|
34
|
-
- **Resource Management**: Unclosed connections, file handles, cleanup issues
|
|
35
|
-
- **Type Safety**: Unsafe casts, missing validation, schema mismatches
|
|
36
|
-
- **Concurrency**: Deadlock risks, thread safety issues, synchronization problems
|
|
37
|
-
|
|
38
|
-
### 3. CODE QUALITY (Consider Fixing)
|
|
39
|
-
- **Architecture**: Design pattern violations, coupling issues, abstraction leaks
|
|
40
|
-
- **Maintainability**: Complex logic without justification, unclear naming
|
|
41
|
-
- **Testing**: Missing test coverage for critical paths, brittle test design
|
|
42
|
-
- **Documentation**: Misleading comments, missing API documentation
|
|
43
|
-
- **Best Practices**: Framework misuse, anti-patterns, deprecated APIs
|
|
44
|
-
|
|
45
|
-
### 4. MINOR IMPROVEMENTS (Optional)
|
|
46
|
-
- **Consistency**: Deviations from established patterns without reason
|
|
47
|
-
- **Efficiency**: Minor optimization opportunities
|
|
48
|
-
- **Clarity**: Code that works but could be more readable
|
|
49
|
-
- **Future-Proofing**: Anticipating likely future requirements
|
|
50
|
-
|
|
51
|
-
## What NOT to Review (Common Time Wasters)
|
|
52
|
-
|
|
53
|
-
- **Code style/formatting**: Handled by automated tools
|
|
54
|
-
- **Personal preferences**: Different != wrong
|
|
55
|
-
- **Compliments**: "Looks good!" wastes everyone's time
|
|
56
|
-
- **Nitpicks**: Minor wording, variable names, spacing
|
|
57
|
-
- **Micro-optimizations**: Unless there's a proven performance problem
|
|
58
|
-
- **Already working code**: If it works and isn't broken, don't fix it
|
|
59
|
-
- **Suggestions for "better" approaches**: Only if current approach has concrete problems
|
|
60
|
-
|
|
61
|
-
## Before Commenting, Ask Yourself
|
|
62
|
-
|
|
63
|
-
1. **Will this cause a runtime failure?** → Critical issue, comment required
|
|
64
|
-
2. **Will this create a security vulnerability?** → Critical issue, comment required
|
|
65
|
-
3. **Will this significantly harm performance?** → Important issue, comment required
|
|
66
|
-
4. **Will this make the code unmaintainable?** → Consider commenting
|
|
67
|
-
5. **Is this just a different way to solve the same problem?** → Skip it
|
|
68
|
-
|
|
69
|
-
## Output Guidelines
|
|
70
|
-
|
|
71
|
-
**INLINE COMMENTS**: Only for specific line-level issues. Empty array is perfectly valid.
|
|
72
|
-
- Start with "🤖 "
|
|
73
|
-
- Be direct: "This will cause X bug" not "Consider maybe perhaps changing this"
|
|
74
|
-
- Provide specific fixes when possible
|
|
75
|
-
|
|
76
|
-
**OVERALL REVIEW**: Required even if no inline comments.
|
|
77
|
-
- For clean code: "No significant issues found. Change is ready."
|
|
78
|
-
- For problematic code: Focus on the most important issues only
|
|
79
|
-
- Skip the pleasantries, get to the point
|
|
80
|
-
|
|
81
|
-
## Success Metrics
|
|
82
|
-
|
|
83
|
-
- **Good review**: Finds 1-3 real issues that would cause problems
|
|
84
|
-
- **Great review**: Catches a critical bug before production
|
|
85
|
-
- **Bad review**: 10+ nitpicky comments about style preferences
|
|
86
|
-
- **Terrible review**: "Great job! LGTM!" with zero value added
|