@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 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.
@@ -6,16 +6,16 @@
6
6
  ┌─────────────────────────────────────────────────────────────┐
7
7
  │ CLI Layer │
8
8
  │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
9
- │ │ show │ │ comment │ │ push │ │ review │ ... │
9
+ │ │ show │ │ comment │ │ push │ │ vote │ ... │
10
10
  │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │
11
11
  └───────┼───────────┼───────────┼───────────┼─────────────────┘
12
12
  │ │ │ │
13
13
  ┌───────┴───────────┴───────────┴───────────┴─────────────────┐
14
14
  │ Service Layer │
15
- │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
16
- │ │ GerritApi │ │ ConfigService│ ReviewStrategy│ │
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, review utils)
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
  ```
@@ -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": "2.0.9",
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
@@ -48,6 +48,44 @@ const program = new Command()
48
48
 
49
49
  program.name('ger').description('LLM-centric Gerrit CLI tool').version(getVersion())
50
50
 
51
+ program.addHelpText(
52
+ 'after',
53
+ `
54
+ CHANGE-ID FORMATS
55
+ Accepts numeric change numbers (12345) or full Change-IDs (I1234abc...).
56
+ Many commands auto-detect from HEAD commit's Change-Id footer when the
57
+ argument is omitted.
58
+
59
+ OUTPUT FORMATS
60
+ --json Structured JSON output for programmatic consumption
61
+ --xml XML with CDATA-wrapped content, optimized for LLM consumption
62
+ (default) Plain text for human reading
63
+ Most commands support both --json and --xml.
64
+
65
+ PIPING / STDIN
66
+ comment Reads message from stdin if no -m flag is provided
67
+ comment --batch Reads a JSON array from stdin for bulk commenting
68
+
69
+ AUTO-DETECTION
70
+ These commands auto-detect the change from HEAD's Change-Id footer when
71
+ the change-id argument is omitted:
72
+ show, build-status, topic, rebase, extract-url, diff, comments, vote
73
+
74
+ COMMON LLM WORKFLOWS
75
+ Review a change: ger show <id> → ger diff <id> → ger comments <id>
76
+ Post a review: ger comment <id> -m "..." → ger vote <id> <label> <score>
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"]
79
+ Check CI: ger build-status <id> --exit-status
80
+
81
+ EXIT CODES
82
+ build-status --exit-status returns non-zero on build failure (useful for scripting).
83
+
84
+ SUBCOMMAND HELP
85
+ Run ger <command> --help for detailed usage and examples.
86
+ `,
87
+ )
88
+
51
89
  registerCommands(program)
52
90
 
53
91
  program.parse(process.argv)