@aaronshaf/ger 2.0.10 → 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 +1 -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
package/README.md
CHANGED
|
@@ -93,9 +93,6 @@ ger rebase 12345
|
|
|
93
93
|
ger submit 12345
|
|
94
94
|
ger restore 12345
|
|
95
95
|
|
|
96
|
-
# AI-powered code review (requires claude, llm, or opencode CLI)
|
|
97
|
-
ger review 12345
|
|
98
|
-
ger review 12345 --dry-run # Preview without posting
|
|
99
96
|
```
|
|
100
97
|
|
|
101
98
|
## Commands
|
|
@@ -711,29 +708,6 @@ ger groups-members project-reviewers --xml
|
|
|
711
708
|
- Group IDs can be names, numeric IDs, or UUIDs
|
|
712
709
|
- Use groups with `ger add-reviewer --group` to add entire teams as reviewers
|
|
713
710
|
|
|
714
|
-
### AI-Powered Review
|
|
715
|
-
|
|
716
|
-
The `ger review` command provides automated code review using AI tools (claude, llm, or opencode CLI).
|
|
717
|
-
|
|
718
|
-
```bash
|
|
719
|
-
# Full AI review with inline and overall comments
|
|
720
|
-
ger review 12345
|
|
721
|
-
|
|
722
|
-
# Preview what would be posted without actually posting
|
|
723
|
-
ger review 12345 --dry-run
|
|
724
|
-
|
|
725
|
-
# Show debug output including AI responses
|
|
726
|
-
ger review 12345 --debug
|
|
727
|
-
```
|
|
728
|
-
|
|
729
|
-
The review command performs a two-stage review process:
|
|
730
|
-
1. **Inline comments**: Specific code issues with line-by-line feedback
|
|
731
|
-
2. **Overall review**: High-level assessment and recommendations
|
|
732
|
-
|
|
733
|
-
Requirements:
|
|
734
|
-
- One of these AI tools must be installed: `claude`, `llm`, or `opencode`
|
|
735
|
-
- Gerrit credentials must be configured (`ger setup`)
|
|
736
|
-
|
|
737
711
|
## Claude Code Skill
|
|
738
712
|
|
|
739
713
|
This repository includes a Claude Code Agent Skill that teaches Claude how to work effectively with Gerrit using the ger CLI. The skill provides Claude with expertise in Gerrit workflows, command usage, and best practices.
|
package/docs/prd/architecture.md
CHANGED
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
┌─────────────────────────────────────────────────────────────┐
|
|
7
7
|
│ CLI Layer │
|
|
8
8
|
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
|
9
|
-
│ │ show │ │ comment │ │ push │ │
|
|
9
|
+
│ │ show │ │ comment │ │ push │ │ vote │ ... │
|
|
10
10
|
│ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
|
|
11
11
|
└───────┼───────────┼───────────┼───────────┼─────────────────┘
|
|
12
12
|
│ │ │ │
|
|
13
13
|
┌───────┴───────────┴───────────┴───────────┴─────────────────┐
|
|
14
14
|
│ Service Layer │
|
|
15
|
-
│ ┌──────────────┐ ┌──────────────┐
|
|
16
|
-
│ │ GerritApi │ │ ConfigService│
|
|
17
|
-
│ │ Service │ │ │
|
|
18
|
-
│ └──────────────┘ └──────────────┘
|
|
15
|
+
│ ┌──────────────┐ ┌──────────────┐ │
|
|
16
|
+
│ │ GerritApi │ │ ConfigService│ │
|
|
17
|
+
│ │ Service │ │ │ │
|
|
18
|
+
│ └──────────────┘ └──────────────┘ │
|
|
19
19
|
└───────┬───────────────────────────────────────────────────────┘
|
|
20
20
|
│
|
|
21
21
|
┌───────┴─────────────────────────────────────────────────────┐
|
|
@@ -46,7 +46,6 @@ src/
|
|
|
46
46
|
│
|
|
47
47
|
├── services/ # Business logic services
|
|
48
48
|
│ ├── config.ts # Configuration management
|
|
49
|
-
│ ├── review-strategy.ts # AI tool strategies
|
|
50
49
|
│ ├── git-worktree.ts # Git worktree operations
|
|
51
50
|
│ └── commit-hook.ts # Gerrit hook installation
|
|
52
51
|
│
|
|
@@ -59,12 +58,7 @@ src/
|
|
|
59
58
|
│ ├── git-commit.ts # Git operations
|
|
60
59
|
│ ├── formatters.ts # Output formatting
|
|
61
60
|
│ ├── shell-safety.ts # XML/CDATA handling
|
|
62
|
-
│ └── ... (diff, comment
|
|
63
|
-
│
|
|
64
|
-
├── prompts/ # AI review prompts
|
|
65
|
-
│ ├── default-review.md
|
|
66
|
-
│ ├── system-inline-review.md
|
|
67
|
-
│ └── system-overall-review.md
|
|
61
|
+
│ └── ... (diff, comment utils)
|
|
68
62
|
│
|
|
69
63
|
└── i18n/ # Internationalization (planned)
|
|
70
64
|
|
|
@@ -207,42 +201,6 @@ User: echo '...' | ger comment 12345
|
|
|
207
201
|
Success/Error
|
|
208
202
|
```
|
|
209
203
|
|
|
210
|
-
## AI Integration Flow
|
|
211
|
-
|
|
212
|
-
```
|
|
213
|
-
User: ger review 12345
|
|
214
|
-
│
|
|
215
|
-
▼
|
|
216
|
-
┌─────────────────────┐
|
|
217
|
-
│ Fetch change diff │
|
|
218
|
-
│ (API: GET /patch) │
|
|
219
|
-
└─────────┬───────────┘
|
|
220
|
-
│
|
|
221
|
-
▼
|
|
222
|
-
┌─────────────────────┐
|
|
223
|
-
│ Detect AI tool │
|
|
224
|
-
│ (claude/llm/etc) │
|
|
225
|
-
└─────────┬───────────┘
|
|
226
|
-
│
|
|
227
|
-
▼
|
|
228
|
-
┌─────────────────────┐ ┌─────────────────────┐
|
|
229
|
-
│ Stage 1: Inline │────►│ AI: Generate inline │
|
|
230
|
-
│ comments │ │ comments JSON │
|
|
231
|
-
└─────────┬───────────┘ └─────────────────────┘
|
|
232
|
-
│
|
|
233
|
-
▼
|
|
234
|
-
┌─────────────────────┐ ┌─────────────────────┐
|
|
235
|
-
│ Stage 2: Overall │────►│ AI: Generate │
|
|
236
|
-
│ review │ │ summary │
|
|
237
|
-
└─────────┬───────────┘ └─────────────────────┘
|
|
238
|
-
│
|
|
239
|
-
▼
|
|
240
|
-
┌─────────────────────┐
|
|
241
|
-
│ API: POST /review │
|
|
242
|
-
│ (all comments) │
|
|
243
|
-
└─────────────────────┘
|
|
244
|
-
```
|
|
245
|
-
|
|
246
204
|
## Configuration Architecture
|
|
247
205
|
|
|
248
206
|
```
|
package/docs/prd/commands.md
CHANGED
|
@@ -121,6 +121,32 @@ ger restore <change-id>
|
|
|
121
121
|
ger restore <change-id> -m "Needed after all"
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
### set-ready
|
|
125
|
+
|
|
126
|
+
Mark a WIP change as ready for review via the Gerrit REST API. Does not require a git push.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
ger set-ready <change-id>
|
|
130
|
+
ger set-ready <change-id> -m "Ready for another look"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
| Option | Description |
|
|
134
|
+
|--------|-------------|
|
|
135
|
+
| `-m <message>` | Optional message to include with the status change |
|
|
136
|
+
|
|
137
|
+
### set-wip
|
|
138
|
+
|
|
139
|
+
Mark a change as work-in-progress via the Gerrit REST API. Does not require a git push.
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
ger set-wip <change-id>
|
|
143
|
+
ger set-wip <change-id> -m "Still in progress"
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
| Option | Description |
|
|
147
|
+
|--------|-------------|
|
|
148
|
+
| `-m <message>` | Optional message to include with the status change |
|
|
149
|
+
|
|
124
150
|
### workspace
|
|
125
151
|
|
|
126
152
|
View local git branch tracking information.
|
|
@@ -236,26 +262,6 @@ ger vote <change-id> --label "Custom-Label" +1
|
|
|
236
262
|
| `--label <name> <score>` | Custom label vote |
|
|
237
263
|
| `-m <message>` | Optional message with vote |
|
|
238
264
|
|
|
239
|
-
### review
|
|
240
|
-
|
|
241
|
-
AI-powered code review (multi-stage).
|
|
242
|
-
|
|
243
|
-
```bash
|
|
244
|
-
ger review <change-id>
|
|
245
|
-
ger review <change-id> --tool claude # Specific AI tool
|
|
246
|
-
ger review # Auto-detect change from HEAD
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
| Option | Description |
|
|
250
|
-
|--------|-------------|
|
|
251
|
-
| `--tool <name>` | AI tool (claude, llm, opencode, gemini) |
|
|
252
|
-
| `--inline-only` | Only post inline comments |
|
|
253
|
-
| `--overall-only` | Only post overall review |
|
|
254
|
-
|
|
255
|
-
**Stages:**
|
|
256
|
-
1. **Inline**: Generate line-specific comments
|
|
257
|
-
2. **Overall**: Generate high-level assessment
|
|
258
|
-
|
|
259
265
|
### add-reviewer
|
|
260
266
|
|
|
261
267
|
Add reviewers or groups to a change.
|
package/index.ts
CHANGED
|
@@ -61,25 +61,6 @@ export {
|
|
|
61
61
|
type ConfigErrorFields,
|
|
62
62
|
} from './src/services/config'
|
|
63
63
|
|
|
64
|
-
// ============================================================================
|
|
65
|
-
// Review Strategy Service
|
|
66
|
-
// ============================================================================
|
|
67
|
-
|
|
68
|
-
export {
|
|
69
|
-
// Strategy types
|
|
70
|
-
type ReviewStrategy,
|
|
71
|
-
// Built-in strategies
|
|
72
|
-
claudeCliStrategy,
|
|
73
|
-
geminiCliStrategy,
|
|
74
|
-
openCodeCliStrategy,
|
|
75
|
-
// Service
|
|
76
|
-
ReviewStrategyService,
|
|
77
|
-
ReviewStrategyServiceLive,
|
|
78
|
-
type ReviewStrategyServiceImpl,
|
|
79
|
-
// Errors
|
|
80
|
-
ReviewStrategyError,
|
|
81
|
-
type ReviewStrategyErrorFields,
|
|
82
|
-
} from './src/services/review-strategy'
|
|
83
64
|
|
|
84
65
|
// ============================================================================
|
|
85
66
|
// Git Worktree Service
|
package/llms.txt
CHANGED
|
@@ -114,14 +114,6 @@ ger extract-url "build-summary-report" | tail -1 # Latest Jenkins URL
|
|
|
114
114
|
ger extract-url "jenkins" --json | jq -r '.urls[-1]'
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
-
### AI Review
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
ger review <change-id> # AI-powered review (requires claude/gemini/opencode CLI)
|
|
121
|
-
ger review <change-id> --tool claude
|
|
122
|
-
ger review <change-id> --comment --yes # Post review comments
|
|
123
|
-
```
|
|
124
|
-
|
|
125
117
|
### Groups
|
|
126
118
|
|
|
127
119
|
```bash
|
|
@@ -179,12 +171,6 @@ ger comment 12345 -m "LGTM" # Add comment
|
|
|
179
171
|
ger vote 12345 --code-review +2 # Approve
|
|
180
172
|
```
|
|
181
173
|
|
|
182
|
-
### Post AI review
|
|
183
|
-
|
|
184
|
-
```bash
|
|
185
|
-
ger review 12345 --comment --yes
|
|
186
|
-
```
|
|
187
|
-
|
|
188
174
|
### Check build and get failures
|
|
189
175
|
|
|
190
176
|
```bash
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aaronshaf/ger",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Gerrit CLI and SDK - A modern CLI tool and TypeScript SDK for Gerrit Code Review, built with Effect-TS",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gerrit",
|
|
@@ -30,10 +30,6 @@
|
|
|
30
30
|
"import": "./src/services/config.ts",
|
|
31
31
|
"types": "./src/services/config.ts"
|
|
32
32
|
},
|
|
33
|
-
"./services/review-strategy": {
|
|
34
|
-
"import": "./src/services/review-strategy.ts",
|
|
35
|
-
"types": "./src/services/review-strategy.ts"
|
|
36
|
-
},
|
|
37
33
|
"./services/git-worktree": {
|
|
38
34
|
"import": "./src/services/git-worktree.ts",
|
|
39
35
|
"types": "./src/services/git-worktree.ts"
|
package/src/api/gerrit.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
AccountInfo,
|
|
20
20
|
} from '@/schemas/gerrit'
|
|
21
21
|
import { filterMeaningfulMessages } from '@/utils/message-filters'
|
|
22
|
+
import { convertToUnifiedDiff } from '@/utils/diff-formatters'
|
|
22
23
|
import { ConfigService } from '@/services/config'
|
|
23
24
|
import { normalizeChangeIdentifier } from '@/utils/change-id'
|
|
24
25
|
|
|
@@ -93,6 +94,8 @@ export interface GerritApiServiceImpl {
|
|
|
93
94
|
readonly getTopic: (changeId: string) => Effect.Effect<string | null, ApiError>
|
|
94
95
|
readonly setTopic: (changeId: string, topic: string) => Effect.Effect<string, ApiError>
|
|
95
96
|
readonly deleteTopic: (changeId: string) => Effect.Effect<void, ApiError>
|
|
97
|
+
readonly setReady: (changeId: string, message?: string) => Effect.Effect<void, ApiError>
|
|
98
|
+
readonly setWip: (changeId: string, message?: string) => Effect.Effect<void, ApiError>
|
|
96
99
|
}
|
|
97
100
|
|
|
98
101
|
export const GerritApiService: Context.Tag<GerritApiServiceImpl, GerritApiServiceImpl> =
|
|
@@ -455,39 +458,6 @@ export const GerritApiServiceLive: Layer.Layer<GerritApiService, never, ConfigSe
|
|
|
455
458
|
return yield* getPatch(changeId, revisionId)
|
|
456
459
|
})
|
|
457
460
|
|
|
458
|
-
const convertToUnifiedDiff = (diff: FileDiffContent, filePath: string): string => {
|
|
459
|
-
const lines: string[] = []
|
|
460
|
-
|
|
461
|
-
if (diff.diff_header) {
|
|
462
|
-
lines.push(...diff.diff_header)
|
|
463
|
-
} else {
|
|
464
|
-
lines.push(`--- a/${filePath}`)
|
|
465
|
-
lines.push(`+++ b/${filePath}`)
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
for (const section of diff.content) {
|
|
469
|
-
if (section.ab) {
|
|
470
|
-
for (const line of section.ab) {
|
|
471
|
-
lines.push(` ${line}`)
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (section.a) {
|
|
476
|
-
for (const line of section.a) {
|
|
477
|
-
lines.push(`-${line}`)
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
if (section.b) {
|
|
482
|
-
for (const line of section.b) {
|
|
483
|
-
lines.push(`+${line}`)
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
return lines.join('\n')
|
|
489
|
-
}
|
|
490
|
-
|
|
491
461
|
const getComments = (changeId: string, revisionId = 'current') =>
|
|
492
462
|
Effect.gen(function* () {
|
|
493
463
|
const { credentials, authHeader } = yield* getCredentialsAndAuth
|
|
@@ -667,6 +637,24 @@ export const GerritApiServiceLive: Layer.Layer<GerritApiService, never, ConfigSe
|
|
|
667
637
|
yield* makeRequest(getTopicUrl(credentials.host, normalized), authHeader, 'DELETE')
|
|
668
638
|
})
|
|
669
639
|
|
|
640
|
+
const setReady = (changeId: string, message?: string) =>
|
|
641
|
+
Effect.gen(function* () {
|
|
642
|
+
const { credentials, authHeader } = yield* getCredentialsAndAuth
|
|
643
|
+
const normalized = yield* normalizeAndValidate(changeId)
|
|
644
|
+
const url = `${credentials.host}/a/changes/${encodeURIComponent(normalized)}/ready`
|
|
645
|
+
const body = message ? { message } : {}
|
|
646
|
+
yield* makeRequest(url, authHeader, 'POST', body)
|
|
647
|
+
})
|
|
648
|
+
|
|
649
|
+
const setWip = (changeId: string, message?: string) =>
|
|
650
|
+
Effect.gen(function* () {
|
|
651
|
+
const { credentials, authHeader } = yield* getCredentialsAndAuth
|
|
652
|
+
const normalized = yield* normalizeAndValidate(changeId)
|
|
653
|
+
const url = `${credentials.host}/a/changes/${encodeURIComponent(normalized)}/wip`
|
|
654
|
+
const body = message ? { message } : {}
|
|
655
|
+
yield* makeRequest(url, authHeader, 'POST', body)
|
|
656
|
+
})
|
|
657
|
+
|
|
670
658
|
return {
|
|
671
659
|
getChange,
|
|
672
660
|
listChanges,
|
|
@@ -694,6 +682,8 @@ export const GerritApiServiceLive: Layer.Layer<GerritApiService, never, ConfigSe
|
|
|
694
682
|
getTopic,
|
|
695
683
|
setTopic,
|
|
696
684
|
deleteTopic,
|
|
685
|
+
setReady,
|
|
686
|
+
setWip,
|
|
697
687
|
}
|
|
698
688
|
}),
|
|
699
689
|
)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Effect } from 'effect'
|
|
2
|
+
import { type ApiError, GerritApiService } from '@/api/gerrit'
|
|
3
|
+
import { sanitizeCDATA } from '@/utils/shell-safety'
|
|
4
|
+
|
|
5
|
+
interface SetReadyOptions {
|
|
6
|
+
message?: string
|
|
7
|
+
xml?: boolean
|
|
8
|
+
json?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const setReadyCommand = (
|
|
12
|
+
changeId?: string,
|
|
13
|
+
options: SetReadyOptions = {},
|
|
14
|
+
): Effect.Effect<void, ApiError, GerritApiService> =>
|
|
15
|
+
Effect.gen(function* () {
|
|
16
|
+
const gerritApi = yield* GerritApiService
|
|
17
|
+
|
|
18
|
+
if (!changeId) {
|
|
19
|
+
console.error('✗ Change ID is required')
|
|
20
|
+
console.error(' Usage: ger set-ready <change-id>')
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Try to fetch change details for richer output, but don't let it block the mutation
|
|
25
|
+
let changeNumber: number | undefined
|
|
26
|
+
let subject: string | undefined
|
|
27
|
+
try {
|
|
28
|
+
const change = yield* gerritApi.getChange(changeId)
|
|
29
|
+
changeNumber = change._number
|
|
30
|
+
subject = change.subject
|
|
31
|
+
} catch {
|
|
32
|
+
// Proceed without change details
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
yield* gerritApi.setReady(changeId, options.message)
|
|
36
|
+
|
|
37
|
+
if (options.json) {
|
|
38
|
+
console.log(
|
|
39
|
+
JSON.stringify(
|
|
40
|
+
{
|
|
41
|
+
status: 'success',
|
|
42
|
+
...(changeNumber !== undefined
|
|
43
|
+
? { change_number: changeNumber }
|
|
44
|
+
: { change_id: changeId }),
|
|
45
|
+
...(subject !== undefined ? { subject } : {}),
|
|
46
|
+
...(options.message ? { message: options.message } : {}),
|
|
47
|
+
},
|
|
48
|
+
null,
|
|
49
|
+
2,
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
} else if (options.xml) {
|
|
53
|
+
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
54
|
+
console.log(`<set_ready_result>`)
|
|
55
|
+
console.log(` <status>success</status>`)
|
|
56
|
+
if (changeNumber !== undefined) {
|
|
57
|
+
console.log(` <change_number>${changeNumber}</change_number>`)
|
|
58
|
+
} else {
|
|
59
|
+
console.log(` <change_id>${changeId}</change_id>`)
|
|
60
|
+
}
|
|
61
|
+
if (subject !== undefined) {
|
|
62
|
+
console.log(` <subject><![CDATA[${sanitizeCDATA(subject)}]]></subject>`)
|
|
63
|
+
}
|
|
64
|
+
if (options.message) {
|
|
65
|
+
console.log(` <message><![CDATA[${sanitizeCDATA(options.message)}]]></message>`)
|
|
66
|
+
}
|
|
67
|
+
console.log(`</set_ready_result>`)
|
|
68
|
+
} else {
|
|
69
|
+
const label = changeNumber !== undefined ? `${changeNumber}` : changeId
|
|
70
|
+
const suffix = subject !== undefined ? `: ${subject}` : ''
|
|
71
|
+
console.log(`✓ Marked change ${label} as ready for review${suffix}`)
|
|
72
|
+
if (options.message) {
|
|
73
|
+
console.log(` Message: ${options.message}`)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
})
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Effect } from 'effect'
|
|
2
|
+
import { type ApiError, GerritApiService } from '@/api/gerrit'
|
|
3
|
+
import { sanitizeCDATA } from '@/utils/shell-safety'
|
|
4
|
+
|
|
5
|
+
interface SetWipOptions {
|
|
6
|
+
message?: string
|
|
7
|
+
xml?: boolean
|
|
8
|
+
json?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const setWipCommand = (
|
|
12
|
+
changeId?: string,
|
|
13
|
+
options: SetWipOptions = {},
|
|
14
|
+
): Effect.Effect<void, ApiError, GerritApiService> =>
|
|
15
|
+
Effect.gen(function* () {
|
|
16
|
+
const gerritApi = yield* GerritApiService
|
|
17
|
+
|
|
18
|
+
if (!changeId) {
|
|
19
|
+
console.error('✗ Change ID is required')
|
|
20
|
+
console.error(' Usage: ger set-wip <change-id>')
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Try to fetch change details for richer output, but don't let it block the mutation
|
|
25
|
+
let changeNumber: number | undefined
|
|
26
|
+
let subject: string | undefined
|
|
27
|
+
try {
|
|
28
|
+
const change = yield* gerritApi.getChange(changeId)
|
|
29
|
+
changeNumber = change._number
|
|
30
|
+
subject = change.subject
|
|
31
|
+
} catch {
|
|
32
|
+
// Proceed without change details
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
yield* gerritApi.setWip(changeId, options.message)
|
|
36
|
+
|
|
37
|
+
if (options.json) {
|
|
38
|
+
console.log(
|
|
39
|
+
JSON.stringify(
|
|
40
|
+
{
|
|
41
|
+
status: 'success',
|
|
42
|
+
...(changeNumber !== undefined
|
|
43
|
+
? { change_number: changeNumber }
|
|
44
|
+
: { change_id: changeId }),
|
|
45
|
+
...(subject !== undefined ? { subject } : {}),
|
|
46
|
+
...(options.message ? { message: options.message } : {}),
|
|
47
|
+
},
|
|
48
|
+
null,
|
|
49
|
+
2,
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
} else if (options.xml) {
|
|
53
|
+
console.log(`<?xml version="1.0" encoding="UTF-8"?>`)
|
|
54
|
+
console.log(`<set_wip_result>`)
|
|
55
|
+
console.log(` <status>success</status>`)
|
|
56
|
+
if (changeNumber !== undefined) {
|
|
57
|
+
console.log(` <change_number>${changeNumber}</change_number>`)
|
|
58
|
+
} else {
|
|
59
|
+
console.log(` <change_id>${changeId}</change_id>`)
|
|
60
|
+
}
|
|
61
|
+
if (subject !== undefined) {
|
|
62
|
+
console.log(` <subject><![CDATA[${sanitizeCDATA(subject)}]]></subject>`)
|
|
63
|
+
}
|
|
64
|
+
if (options.message) {
|
|
65
|
+
console.log(` <message><![CDATA[${sanitizeCDATA(options.message)}]]></message>`)
|
|
66
|
+
}
|
|
67
|
+
console.log(`</set_wip_result>`)
|
|
68
|
+
} else {
|
|
69
|
+
const label = changeNumber !== undefined ? `${changeNumber}` : changeId
|
|
70
|
+
const suffix = subject !== undefined ? `: ${subject}` : ''
|
|
71
|
+
console.log(`✓ Marked change ${label} as work-in-progress${suffix}`)
|
|
72
|
+
if (options.message) {
|
|
73
|
+
console.log(` Message: ${options.message}`)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
})
|
|
@@ -260,7 +260,6 @@ const setupEffect = (configService: ConfigServiceImpl) =>
|
|
|
260
260
|
Effect.tap(() => Console.log('You can now use:')),
|
|
261
261
|
Effect.tap(() => Console.log(' • "ger mine" to view your changes')),
|
|
262
262
|
Effect.tap(() => Console.log(' • "ger show <change-id>" to view change details')),
|
|
263
|
-
Effect.tap(() => Console.log(' • "ger review <change-id>" to review with AI')),
|
|
264
263
|
Effect.catchAll((error) =>
|
|
265
264
|
pipe(
|
|
266
265
|
Console.error(
|
package/src/cli/index.ts
CHANGED
|
@@ -75,6 +75,7 @@ COMMON LLM WORKFLOWS
|
|
|
75
75
|
Review a change: ger show <id> → ger diff <id> → ger comments <id>
|
|
76
76
|
Post a review: ger comment <id> -m "..." → ger vote <id> <label> <score>
|
|
77
77
|
Manage changes: ger push, ger checkout <id>, ger abandon <id>, ger submit <id>
|
|
78
|
+
WIP toggle: ger set-wip <id>, ger set-ready <id> [-m "message"]
|
|
78
79
|
Check CI: ger build-status <id> --exit-status
|
|
79
80
|
|
|
80
81
|
EXIT CODES
|
|
@@ -2,11 +2,8 @@ import type { Command } from 'commander'
|
|
|
2
2
|
import { Effect } from 'effect'
|
|
3
3
|
import { GerritApiServiceLive } from '@/api/gerrit'
|
|
4
4
|
import { ConfigServiceLive } from '@/services/config'
|
|
5
|
-
import { ReviewStrategyServiceLive } from '@/services/review-strategy'
|
|
6
|
-
import { GitWorktreeServiceLive } from '@/services/git-worktree'
|
|
7
5
|
import { CommitHookServiceLive } from '@/services/commit-hook'
|
|
8
|
-
import {
|
|
9
|
-
import { restoreCommand } from './commands/restore'
|
|
6
|
+
import { registerStateCommands } from './register-state-commands'
|
|
10
7
|
import { rebaseCommand } from './commands/rebase'
|
|
11
8
|
import { submitCommand } from './commands/submit'
|
|
12
9
|
import { topicCommand, TOPIC_HELP_TEXT } from './commands/topic'
|
|
@@ -23,7 +20,6 @@ import { installHookCommand } from './commands/install-hook'
|
|
|
23
20
|
import { mineCommand } from './commands/mine'
|
|
24
21
|
import { openCommand } from './commands/open'
|
|
25
22
|
import { pushCommand, PUSH_HELP_TEXT } from './commands/push'
|
|
26
|
-
import { reviewCommand } from './commands/review'
|
|
27
23
|
import { searchCommand, SEARCH_HELP_TEXT } from './commands/search'
|
|
28
24
|
import { setup } from './commands/setup'
|
|
29
25
|
import { showCommand, SHOW_HELP_TEXT } from './commands/show'
|
|
@@ -240,43 +236,8 @@ export function registerCommands(program: Command): void {
|
|
|
240
236
|
)
|
|
241
237
|
})
|
|
242
238
|
|
|
243
|
-
// abandon
|
|
244
|
-
program
|
|
245
|
-
.command('abandon [change-id]')
|
|
246
|
-
.description(
|
|
247
|
-
'Abandon a change (interactive mode if no change-id provided; accepts change number or Change-ID)',
|
|
248
|
-
)
|
|
249
|
-
.option('-m, --message <message>', 'Abandon message')
|
|
250
|
-
.option('--xml', 'XML output for LLM consumption')
|
|
251
|
-
.option('--json', 'JSON output for programmatic consumption')
|
|
252
|
-
.action(async (changeId, options) => {
|
|
253
|
-
await executeEffect(
|
|
254
|
-
abandonCommand(changeId, options).pipe(
|
|
255
|
-
Effect.provide(GerritApiServiceLive),
|
|
256
|
-
Effect.provide(ConfigServiceLive),
|
|
257
|
-
),
|
|
258
|
-
options,
|
|
259
|
-
'abandon_result',
|
|
260
|
-
)
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
// restore command
|
|
264
|
-
program
|
|
265
|
-
.command('restore <change-id>')
|
|
266
|
-
.description('Restore an abandoned change (accepts change number or Change-ID)')
|
|
267
|
-
.option('-m, --message <message>', 'Restoration message')
|
|
268
|
-
.option('--xml', 'XML output for LLM consumption')
|
|
269
|
-
.option('--json', 'JSON output for programmatic consumption')
|
|
270
|
-
.action(async (changeId, options) => {
|
|
271
|
-
await executeEffect(
|
|
272
|
-
restoreCommand(changeId, options).pipe(
|
|
273
|
-
Effect.provide(GerritApiServiceLive),
|
|
274
|
-
Effect.provide(ConfigServiceLive),
|
|
275
|
-
),
|
|
276
|
-
options,
|
|
277
|
-
'restore_result',
|
|
278
|
-
)
|
|
279
|
-
})
|
|
239
|
+
// abandon / restore / set-ready / set-wip commands
|
|
240
|
+
registerStateCommands(program)
|
|
280
241
|
|
|
281
242
|
// rebase command
|
|
282
243
|
program
|
|
@@ -636,64 +597,4 @@ Note:
|
|
|
636
597
|
process.exit(1)
|
|
637
598
|
}
|
|
638
599
|
})
|
|
639
|
-
|
|
640
|
-
// review command
|
|
641
|
-
program
|
|
642
|
-
.command('review <change-id>')
|
|
643
|
-
.description(
|
|
644
|
-
'AI-powered code review that analyzes changes and optionally posts comments (accepts change number or Change-ID)',
|
|
645
|
-
)
|
|
646
|
-
.option('--comment', 'Post the review as comments (prompts for confirmation)')
|
|
647
|
-
.option('-y, --yes', 'Skip confirmation prompts when posting comments')
|
|
648
|
-
.option('--debug', 'Show debug output including AI responses')
|
|
649
|
-
.option('--prompt <file>', 'Path to custom review prompt file (e.g., ~/prompts/review.md)')
|
|
650
|
-
.option('--tool <tool>', 'Preferred AI tool (claude, gemini, opencode)')
|
|
651
|
-
.option('--system-prompt <prompt>', 'Custom system prompt for the AI')
|
|
652
|
-
.addHelpText(
|
|
653
|
-
'after',
|
|
654
|
-
`
|
|
655
|
-
This command uses AI (claude CLI, gemini CLI, or opencode CLI) to review a Gerrit change.
|
|
656
|
-
It performs a two-stage review process:
|
|
657
|
-
1. Generates inline comments for specific code issues
|
|
658
|
-
2. Generates an overall review comment
|
|
659
|
-
By default, the review is only displayed in the terminal.
|
|
660
|
-
Use --comment to post the review to Gerrit (with confirmation prompts).
|
|
661
|
-
Use --comment --yes to post without confirmation.
|
|
662
|
-
|
|
663
|
-
Requirements:
|
|
664
|
-
- One of these AI tools must be available: claude CLI, gemini CLI, or opencode CLI
|
|
665
|
-
- Gerrit credentials must be configured (run 'ger setup' first)
|
|
666
|
-
|
|
667
|
-
Examples:
|
|
668
|
-
$ ger review 12345
|
|
669
|
-
$ ger review If5a3ae8cb5a107e187447802358417f311d0c4b1
|
|
670
|
-
$ ger review 12345 --comment
|
|
671
|
-
$ ger review 12345 --comment --yes
|
|
672
|
-
$ ger review 12345 --tool gemini
|
|
673
|
-
$ ger review 12345 --debug
|
|
674
|
-
|
|
675
|
-
Note: Both change number (e.g., 12345) and Change-ID (e.g., If5a3ae8...) formats are accepted
|
|
676
|
-
`,
|
|
677
|
-
)
|
|
678
|
-
.action(async (changeId, options) => {
|
|
679
|
-
try {
|
|
680
|
-
const effect = reviewCommand(changeId, {
|
|
681
|
-
comment: options.comment,
|
|
682
|
-
yes: options.yes,
|
|
683
|
-
debug: options.debug,
|
|
684
|
-
prompt: options.prompt,
|
|
685
|
-
tool: options.tool,
|
|
686
|
-
systemPrompt: options.systemPrompt,
|
|
687
|
-
}).pipe(
|
|
688
|
-
Effect.provide(ReviewStrategyServiceLive),
|
|
689
|
-
Effect.provide(GerritApiServiceLive),
|
|
690
|
-
Effect.provide(ConfigServiceLive),
|
|
691
|
-
Effect.provide(GitWorktreeServiceLive),
|
|
692
|
-
)
|
|
693
|
-
await Effect.runPromise(effect)
|
|
694
|
-
} catch (error) {
|
|
695
|
-
console.error('✗ Error:', error instanceof Error ? error.message : String(error))
|
|
696
|
-
process.exit(1)
|
|
697
|
-
}
|
|
698
|
-
})
|
|
699
600
|
}
|